Files
john aede1e6fcb fix(mindspace): guard published pages against blocked CDN scripts
Pre-bundle Chart.js, auto-rewrite common CDN references at publish and serve time, and block unknown external script src during publication scans so interactive dashboards keep working under CSP.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-10 10:27:42 +08:00

290 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: page-data-collect
description: 在 MindSpace 页面中收集、存储并管理结构化数据(问卷、报名、台账、后台查看),基于 Page Data API 与用户私有 SQLite
---
# 页面数据收集(Page Data Collect
在 MindSpace **公开 HTML 页面**中嵌入表单、问卷、报名等交互,并将提交持久化到当前用户的 `.mindspace/private-data.sqlite`,通过平台 **Page Data API** 受控读写。
详细 API 说明见工作区外文档 `docs/page-data-api-usage.md`Memind 仓库)。
## 何时使用
- 用户要在页面里**收集并保存**数据:问卷、投票、报名、签到、意见反馈
- 用户要**后台查看提交记录**,可能带口令/密码
- 用户提到「数据交互」「sqlite」「存数据库」「提交记录」
- 在已有页面上**追加**可提交、可统计的表单区块
**不要**用于:
- 只在聊天里弹表单、不落库 → 用 `form-builder`
- 纯静态展示页、无数据读写 → 用 `static-page-publish`
## 核心原则
```text
Agent 可以建模 SQL 并注册 dataset
HTML 页面只能调用 Page Data APIpage-data-client.js);
禁止自建 Express / 独立端口 / 直接暴露 SQLite 文件路径。
```
## 方案选择:默认快车道 + 能力分支(必做)
**平台配置不由 LLM 发明**;**页面内容可由 LLM 自由生成**。
```text
用户描述需求
→ 匹配能力分支(下表 A/B/C/D,默认 A)
→ 仅冲突或无法匹配时,用 1 题让用户选分支或改口令
→ 输出「方案摘要」供确认(用户可只改口令/选项)
→ 再 load_skill / 建表 / write_file / bind
```
### 默认方案 A(无特殊说明时直接采用,不必逐条追问)
| 项 | 默认值 |
|----|--------|
| 访客 | 匿名提交(`public`,仅 `insert` |
| 后台 | **独立** HTML 页(`password`,仅 `read` |
| 后台口令 | **`88888888`**(平台要求 8~128 位;用户可指定其它合法口令覆盖) |
| 提交后修改 | 不支持(一次性) |
| 页面 | `public/*-survey.html` + `public/*-admin.html`(两文件各 bind 一次) |
用户只说「问卷/报名/收集数据 + 后台查看」且未提登录、协作改单、同页后台时 → **用方案 A,先展示摘要再开工**
口令规则:
- **禁止**接受或使用 <8 位口令(如 `888`);若用户坚持短口令,说明平台限制并代用 `88888888` 或请其给出 ≥8 位。
- bind 时 **`password` 必须传入**且会写入发布记录;禁止只改 `access_mode` 不写 `password_hash`
- 交付说明写明后台口令;用户可在 MindSpace「页面数据」面板重置。
### 能力分支(非默认需求时切换,仍用结构化选项)
| 分支 | 适用用户表述 | 前台 | 后台/协作 | 页面数 |
|------|-------------|------|-----------|--------|
| **A 匿名问卷+口令后台**(默认) | 匿名填、管理员看统计 | `public` insert | 独立页 `password` read | 2 |
| **B 登录后各自提交/查看** | 要登录、只看自己的、销售上报 | `login_required` insert+read | 同页或独立;`own_rows` | 12 |
| **C 共口令协作台账** | 团队共用一个密码、一起改 | `password` insert+read+update | 同页;先 `authenticate` | 1 |
| **D 仅公开提交无后台** | 只要收集、不要后台 | `public` insert only | 无 | 1 |
| **E 登录提交+口令管理** | 登录填报 + 管理员口令看全量 | `login_required` insert | 独立页 `password` read | 2 |
**LLM 只做分支匹配**:从用户原话判断 A~E;能确定则写入方案摘要,**不要**机械念 5 题问卷。
**仅以下情况才问用户(每次最多 1~2 点)**
1. 表述同时命中两个互斥分支(如「匿名提交」+「同页内嵌口令看全量」)→ 给 A/E 选项说明须拆页
2. 用户明确要的口令 <8 位 → 请改口令或确认用默认 `88888888`
3. 需要 B/C 但表结构是否要 `created_by_user_id` 等列尚不清楚 → 确认登录隔离
**禁止**连续多轮只输出「我先检查工作区/加载技能」而不调用工具;**第一轮工具**应是 `load_skill``list_dir` / `private_data_execute`,不是空计划。
### 平台硬约束(分支菜单边界,不可绕过)
```text
- public:可匿名 insert;服务端禁止 update / softDelete
- password:所有 API 须先 authenticate;口令 ≥8 位且须写入发布记录
- 同页不能同时「匿名 insert」+「口令 read 全量」→ 须拆前台 + 后台(方案 A/E)
- password 无法区分访客身份;「只改自己的」须 login_required + own_rows(方案 B
- 匿名提交后「凭链接改自己的」无内置能力,须改 B 或接受一次性提交
```
### 方案摘要模板(开工前展示;默认填 A,用户可改口令或换分支)
```text
## 页面数据方案(请确认或只改口令)
- 分支:A 匿名提交 + 独立口令后台
- 访客:匿名提交(public,仅 insert
- 管理员:独立后台页(password,仅 read
- 后台口令:88888888(可改为你的 ≥8 位口令)
- 提交后修改:不支持
- 页面:public/xxx.html + public/xxx-admin.html
- 内容:(由你的描述生成题目/字段/报表)
确认后开始建表与写页面。若需登录提交或团队共改,请说明,我换成 B/C 分支。
```
### 分支切换示例
**用户**:「销售要登录后才能上报,管理员用 88888888 看全部。」
**方案 E**:前台 `login_required` insert;后台独立页 `password` read;口令 `88888888`
**用户**:「小团队共用一个密码,一起维护台账。」
**方案 C**:单页 `password`bind 传 `password`;页内先 `authenticate` 再读写。
**用户**:「做个投票,不用后台。」
**方案 D**:单页 `public` insert only。
## 标准工作流
### 1. 加载本技能 + 选定分支
```text
load_skill → page-data-collect
→ 匹配分支(默认 A)→ 方案摘要 → 用户确认或仅改口令/选项
→ 禁止未确认就 bind;禁止空转计划不调用工具
```
### 2. 数据层:建表 + 注册 dataset
`private_data_execute` 建表(示例):
```sql
CREATE TABLE IF NOT EXISTS survey_responses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
q1_feature TEXT NOT NULL,
q2_usage TEXT NOT NULL,
q3_suggestion TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now', '+8 hours'))
);
```
`private_data_register_dataset` 注册(示例):
```json
{
"name": "survey_responses",
"table": "survey_responses",
"description": "TKMind 功能偏好问卷",
"actions": ["read", "insert"],
"columns": {
"read": ["id", "q1_feature", "q2_usage", "q3_suggestion", "created_at"],
"insert": ["q1_feature", "q2_usage", "q3_suggestion"]
}
}
```
### 3. 页面层:写 HTML
-`write_file` / `edit_file` 写入或更新 `public/*.html`
- 页面视觉、封面、`mindspace-cover`、页脚等规范**参照 `static-page-publish`**
- 必须引入平台脚本:
```html
<script src="/assets/page-data-client.js"></script>
```
- **第三方 JS 库**Chart.js、ECharts 等)禁止写 CDN `https://...`;发布页 CSP 只允许同源脚本。优先用平台预置路径,或下载到 `public/assets/` 后用相对路径引用:
```html
<!-- 推荐:平台已预置 Chart.js -->
<script src="/assets/chart.umd.min.js"></script>
<!-- 或页面自有副本 -->
<script src="assets/chart.umd.min.js"></script>
```
平台会在发布/访问时自动把常见 Chart.js CDN 改写为 `/assets/chart.umd.min.js`;其它未知 CDN 会在发布检查中被拦截。
### 4. 发布并绑定页面(推荐)
`private_data_bind_workspace_page` 一步完成:**创建/更新页面记录 → 发布 → 写入 Page Data 策略**。
**问卷页(匿名提交)示例:**
```json
{
"relativePath": "public/tkmind-survey.html",
"accessMode": "public",
"datasets": {
"survey_responses": {
"insert": true,
"read": false,
"columns": {
"insert": ["q1_feature", "q2_usage", "q3_suggestion"]
}
}
}
}
```
**后台页(口令查看)示例:**
```json
{
"relativePath": "public/tkmind-survey-admin.html",
"accessMode": "password",
"password": "88888888",
"datasets": {
"survey_responses": {
"insert": false,
"read": true,
"columns": {
"read": ["id", "q1_feature", "q2_usage", "q3_suggestion", "created_at"]
}
}
}
}
```
返回 `pageId`**`workspaceUrl`**`/MindSpace/<用户ID>/public/xxx.html`)。
**交付时必须优先给用户 workspaceUrl**`/u/用户名/pages/...` 仅作补充。bind 会同步工作区 HTML 到发布快照,但禁止先发布占位内容再补文件。
**dataset 名称必须与 HTML 一致**`private_data_register_dataset``name``private_data_bind_workspace_page``datasets` 键名、以及 HTML 里 `insertRow('...')` / `listRows('...')` 的字符串必须完全相同(例如都用 `tkmind_exp_survey`)。若不一致,提交会报「dataset 未授权 insert/read」。
若问卷与后台是多个 HTML 文件,对每个 `public/*.html` 各调用一次 bind
- **问卷页**`accessMode: "public"` + dataset 仅 `insert`
- **后台页**`accessMode: "password"` + dataset 仅 `read`(发布口令至少 8 位)
**顺序**:先 `write_file` 完整 HTML → 再 `bind`;禁止只写「问卷页面」占位文字就发布。
也可手动发布后调用 `private_data_set_page_policy`(需已知 `pageId`)。
### 5. 配置 Page Access Policy(手动路径)
多页场景请**按页**分别配置,勿把 `public` 问卷与 `password` 后台混在同一 `pageId` 策略里。
### 6. 前端读写
```html
<script src="/assets/page-data-client.js"></script>
<script>
// pageId 可省略:平台访问时注入 __MINDSPACE_PAGE_DATA__
const client = MindSpacePageData.createClient({ apiBase: '/api' });
// 口令页后台:await client.authenticate('88888888'); // 口令 ≥8 位,与发布时一致
// 提交:await client.insertRow('survey_responses', { q1_feature: '...', ... });
// 列表:const { rows } = await client.listRows('survey_responses', { limit: 50 });
</script>
```
## 常见场景策略
| 分支 | 场景 | 发布模式 | Page Data 能力 |
|------|------|------------|----------------|
| A(默认) | 匿名问卷 + 口令后台 | 前台 `public`;后台 `password` | insert / read(分页) |
| B | 登录用户各自提交/查看/改自己的 | `login_required` | insert/read/update + `own_rows` |
| C | 共口令协作台账 | `password` | insert/read/update(先 authenticate |
| D | 仅公开收集 | `public` | 仅 `insert` |
| E | 登录提交 + 口令管理全量 | 前台 `login_required`;后台 `password` | insert / read(分页) |
**需要 update/delete**`public` 不支持;匿名无「改自己的」→ 用 B 或 C。详见上文分支表。
## 严格禁止
1. **禁止**在方案摘要未确认时建表 / bind / 发布(默认 A 也须展示摘要;用户明确「按默认做」视为确认)
2. **禁止**让 LLM 自行发明 accessMode/拆页/口令;必须落在分支 A~E 与默认口令规则内
3. **禁止**连续两轮仅输出计划、不调用 `load_skill` / `list_dir` / `write_file` / `private_data_execute` / `bind`
4. **禁止**接受 <8 位发布口令;用户未指定口令时后台默认 **`88888888`**
5. **禁止**创建 `scripts/*-api.mjs`、Express 服务、或监听独立端口(如 `8899`
6. **禁止** HTML 中硬编码 `http://127.0.0.1:端口` 或自定义 `/api/survey/*`
7. **禁止**在 HTML 中使用 `onclick` / `oninput` 等内联事件属性(MindSpace 发布页 CSP 不允许);改用 `addEventListener`
8. **禁止**用 CDN `https://...` 引用 `<script src>`(发布页 CSP 仅允许同源;Chart.js 用 `/assets/chart.umd.min.js``public/assets/` 本地文件)
9. **禁止**在页面 JS 中直接使用 `better-sqlite3` 或读取 `.sqlite` 文件路径
10. **禁止**只 `CREATE TABLE` 而不 `private_data_register_dataset`
11. **禁止**未配置 `private_data_set_page_policy` 就让页面调用公开 API
12. **禁止**先发布占位页(如 `<p>问卷页面</p>`)再让用户访问 `/u/.../pages/...`
## 交付前自检
0. 已选定分支 AE,方案摘要已确认;`password` 页口令 ≥8 位且 bind 已传入
1. `__page_data_datasets` 中存在对应 dataset
2. `.mindspace/page-data-policies/<pageId>.json` 已写入
3. HTML 含 `page-data-client.js`;已 bind 或发布后平台会注入 pageId
4. HTML **不含** `127.0.0.1:``/api/survey/``PLACEHOLDER_PAGE_ID`
5. 向用户说明:访客如何提交、管理员如何用口令查看记录
## 回复格式
除数据能力外,优先返回 **workspaceUrl** 的 Markdown 链接 `[标题](workspaceUrl)`,并简要说明后台入口与口令(如有)。