fix: MindSpace remote sync, thumbnail fallback, and Plaza URL guards

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>
This commit is contained in:
john
2026-07-05 07:57:04 +08:00
parent 4781bbc156
commit d3239ff292
20 changed files with 646 additions and 58 deletions
+24
View File
@@ -104,6 +104,30 @@ pnpm setup:plaza-tunnel
| `H5_PORT` | 8081 | Portal / API 端口 |
| `PLAZA_LOCAL_HOST` | plaza.tkmind.cn | 本地域名 |
| `PLAZA_PUBLIC_BASE` | https://plaza.tkmind.cn | 公开 URL 前缀 |
| `VITE_PLAZA_BASE` | (见下) | Portal 前端「逛 Plaza」链接;**编译期**写入 dist |
### Portal「逛 Plaza」链接(MindSpace / 发布成功页)
Plaza 是独立域名 `plaza.tkmind.cn`,与 Portal `m.tkmind.cn` 不同。前端通过 `src/utils/publicUrl.ts``public-site-bases.mjs` 解析:
| 环境 | 期望链接 |
|------|----------|
| 本机 `127.0.0.1` / `localhost` | `http://127.0.0.1:3001/plaza` |
| 生产 `m.tkmind.cn` | `https://plaza.tkmind.cn/plaza` |
**为何生产曾出现 127** `VITE_*``vite build` 时写入 bundle。若本机 `.env``VITE_PLAZA_BASE=http://127.0.0.1:3001`,未覆盖就直接打生产包,会把 127 带进 `m.tkmind.cn`
**彻底修复(三层):**
1. **运行时守卫**:在 `m.tkmind.cn` 等公网 host 上,即使 bundle 内是 loopback,也强制用 `https://plaza.tkmind.cn`(见 `public-site-bases.mjs`)。
2. **构建默认值**`scripts/build-portal-runtime.mjs` 在未显式设置时注入 `VITE_PLAZA_BASE=https://plaza.tkmind.cn`
3. **勿在生产构建用本地 .env**:发 Portal runtime 前确认 `npm run build` / `build-portal-runtime.mjs` 未读取带 127 的 `VITE_PLAZA_BASE`
验证:
```bash
node --test public-site-bases.test.mjs
```
## 已弃用
+1
View File
@@ -7,6 +7,7 @@
| 文档 | 保护内容 |
|------|----------|
| [mindspace-publish-and-chat-finish.md](./mindspace-publish-and-chat-finish.md) | ① `edit_file` 覆盖 `public/*.html` ② Finish 后聊天不清空、不暴露 agent 内部前缀 |
| [mindspace-remote-page-sync-and-thumbnail.md](./mindspace-remote-page-sync-and-thumbnail.md) | ① remote 模式 public HTML 入库 sync ② storage 缺失时缩略图/读页回退 workspace HTML |
## 自动化
@@ -0,0 +1,100 @@
# 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` 定位内联说明。