d3239ff292
Ensure page sync runs via pageSyncService in remote mode, fall back to workspace HTML when storage assets are missing, and prevent production Portal from linking to loopback Plaza URLs baked in at build time. Co-authored-by: Cursor <cursoragent@cursor.com>
101 lines
4.0 KiB
Markdown
101 lines
4.0 KiB
Markdown
# MindSpace Remote 页面同步与缩略图回归守卫
|
||
|
||
日期:2026-07-05
|
||
|
||
## 事故症状
|
||
|
||
1. **缩略图 500**:`GET /api/mindspace/v1/pages/:pageId/thumbnail` 返回 500;日志/storage 为 `ENOENT`,但 `MindSpace/<userId>/public/*.html` 仍存在。
|
||
2. **页面列表缺新生成页**:103 生产 `MINDSPACE_SERVER_ADAPTER=remote` 时,Agent 新生成的 `public/*.html` 不出现在「最近页面 / 全部页面」。
|
||
3. **误判**:容易把缺页当成「MindSpace 路径解析错误」或「改回 local adapter 就能修缩略图」——两者根因不同。
|
||
|
||
## 根因
|
||
|
||
### 缩略图 500
|
||
|
||
- `h5_page_records` / `h5_asset_versions.storage_key` 指向 `data/mindspace/users/.../*.md`。
|
||
- 磁盘上该 storage 文件可能已丢失或未写入(历史 sync、清理、quota、迁移)。
|
||
- 页面 snapshot 仍保留 `relative_path: public/index.html`,工作区 HTML 是真实内容来源。
|
||
- `loadPageThumbnailContext` / `getPage` 若**只读 storage**,会在 remote 8082 上直接抛错 → Portal 返回 500。
|
||
|
||
### Remote 下缺页
|
||
|
||
- Portal `GET /mindspace/v1/pages` 会调用 `syncUserGeneratedPages()`,将 `public/*.html` 入库。
|
||
- 曾存在 `if (adapterKind === 'remote') return;`,导致 **8082 独立部署后 sync 永不执行**。
|
||
- 测试环境多为 `local`,sync 正常 → 生产/测试行为不一致。
|
||
|
||
## 必须保留的行为
|
||
|
||
### 1. Workspace HTML fallback(读内容)
|
||
|
||
在 `mindspace-pages.mjs` 中,读取页面 content / 缩略图 HTML 时:
|
||
|
||
1. 优先读 `storage_key` 对应文件;
|
||
2. 若 `ENOENT` 且 `page_type === 'html'` 且 snapshot 有 `relative_path` → 回退读 `resolvePublishDir(h5Root, userId)` 下对应 HTML;
|
||
3. `getPage` 与 `renderThumbnail` **共用** `readPageContentWithWorkspaceFallback`,禁止只修一处。
|
||
|
||
### 2. Remote 也执行 page sync
|
||
|
||
1. 抽出 `mindspace-page-sync-service.mjs` → `pageSyncService.syncUserGeneratedPages(userId)`;
|
||
2. 写入 `mindspace-server-adapter-contract.mjs`:`pageSyncService` binding;
|
||
3. **Local**:8082 / Portal 内直接跑 `syncGeneratedPagesFromPublicAssets`;
|
||
4. **Remote**:Portal 通过 RPC 调 8082 的 `pageSyncService.syncUserGeneratedPages`;
|
||
5. **`server.mjs` 禁止**在 remote 模式 early-return 跳过 sync。
|
||
|
||
触发点(保持不变):
|
||
|
||
- `GET /mindspace/v1/pages` 列表前
|
||
- 聊天 Finish 后(`onAfterFinish`)
|
||
|
||
### 3. 103 拓扑约束
|
||
|
||
- Portal:`8081`,`MINDSPACE_SERVER_ADAPTER=remote`
|
||
- MindSpace Service:`8082`,跑 **local adapter**(含 pageSyncService + storage + h5Root)
|
||
- 用户 HTML:`/Users/john/Project/Memind/MindSpace/<userId>/public/`
|
||
- Storage:`data/mindspace/users/<userId>/...`
|
||
|
||
**禁止**用「改回 Portal local adapter」作为生产长期方案;只可用于本地对比验证。
|
||
|
||
## 排查入口
|
||
|
||
```bash
|
||
# 1. 页面 DB 记录 vs storage vs workspace
|
||
mysql ... # h5_page_records + h5_page_versions.source_snapshot_json + h5_asset_versions.storage_key
|
||
|
||
# 2. storage 是否存在
|
||
ls -la data/mindspace/users/<userId>/pages/<assetId>/versions/
|
||
|
||
# 3. workspace HTML 是否存在
|
||
ls -la MindSpace/<userId>/public/
|
||
|
||
# 4. 8082 健康与 adapter
|
||
curl -s http://127.0.0.1:8082/health
|
||
|
||
# 5. 缩略图 RPC(需登录态;或直接在 8082 侧调 pageService.renderThumbnail)
|
||
curl -I 'https://m.tkmind.cn/api/mindspace/v1/pages/<pageId>/thumbnail'
|
||
```
|
||
|
||
## 改相关文件前必跑
|
||
|
||
```bash
|
||
npm run verify:mindspace-page-sync-guards
|
||
node --test mindspace-page-sync-service.test.mjs mindspace-pages.test.mjs
|
||
```
|
||
|
||
## Checklist(发 103 前)
|
||
|
||
- [ ] `mindspace-pages.mjs` 含 workspace fallback
|
||
- [ ] `pageSyncService` 在 contract + local runtime + remote proxy 中齐全
|
||
- [ ] `server.mjs` 无 remote sync skip
|
||
- [ ] **8082 与 Portal 均发布**(8082 承载 renderThumbnail + sync)
|
||
- [ ] 用真实 pageId 验证 thumbnail 200 + 列表含新页
|
||
|
||
## 相关单测
|
||
|
||
- `mindspace-page-sync-service.test.mjs`
|
||
- `mindspace-pages.test.mjs`(storage 缺失时 thumbnail / getPage fallback)
|
||
- `mindspace-page-sync.test.mjs`(public dir → DB)
|
||
|
||
## 代码内标记
|
||
|
||
搜索 `REGRESSION GUARD: mindspace-page-sync-thumbnail` 定位内联说明。
|