551d6317ae
Keep archive as a PATCH of archived_at rather than DELETE, pass entry_mode through onboarding, and let popstate to the default chat reuse selectSession. Then move send/stop/recovery and session management out of page.tsx so the home surface stays within the batch-two line budget. Co-authored-by: Cursor <cursoragent@cursor.com>
72 lines
5.5 KiB
Markdown
72 lines
5.5 KiB
Markdown
# PROGRESS · 小刀轮:孤儿契约红修复 + popstate 副作用对齐(2026-09-01)
|
||
|
||
工作树:`.worktrees/contract-repairs-20260901`
|
||
分支:`codex/contract-repairs-20260901`(跟踪 `origin/staging`)
|
||
基线:`origin/staging` @ `a6148af8`(任务书提交;产品代码与第一批 `54269fcf` 相同,另加两份任务书)
|
||
|
||
不要写成 `PROGRESS.md`。小刀轮与第二批在同一工作树落地(第二批任务书写「小刀轮落地后的 origin/staging」;小刀当时尚未推送,故未另开 `home-split-batch2` 工作树)。交付为快进推 `origin/staging`。
|
||
|
||
开工后 `origin/staging` 又快进了 4 个无关文档提交(`ff97de5e`…`8b7eda9f`),未并入本树。
|
||
|
||
## 任务 1 · 归档契约
|
||
|
||
文件:`tests/test_session_management_entrypoints.py::test_archiving_never_calls_the_delete_endpoint`
|
||
|
||
| 项 | 原值 | 新值 |
|
||
| --- | --- | --- |
|
||
| 读取面 | `PAGE.read_text()`(仅 `page.tsx`) | `_home_surface()`:`page.tsx` + 第一批抽出文件 + 若存在则拼接 `hooks/use-session-management.ts`、`hooks/use-consultation-run.ts` |
|
||
| 切片 | `function toggleArchivedSession` → `async function shareSession` | 同左(两函数第二批一起搬进 session hook,切片标记仍相邻) |
|
||
| 正断言 | `"setArchivedSessionIds" in archive_action` | `'writeChatSession(sessionId, { archived_at: nextArchivedAt }, "update")' in archive_action` 且 `"archived_at: nextArchivedAt" in archive_action` |
|
||
| 负断言 | `"/api/sessions/" not in archive_action` | `'method: "DELETE"' not in archive_action` 且 `"DELETE" not in archive_action` |
|
||
| PATCH | (无) | `chat-session-write-contract.ts` 含 `method: mode === "create" ? "POST" : "PATCH"` |
|
||
|
||
同文件 `test_chat_history_management_actions_are_exposed` 的读取面从 `PAGE + STYLES + SESSION_ROW` 改为 `_home_surface() + STYLES + SESSION_ROW`,断言 token 未改。`test_chat_session_delete_is_server_controlled_and_granted` 仍读路由/迁移。
|
||
|
||
`CORE_PYTEST_TARGETS`:加入 `tests/test_session_management_entrypoints.py`,注释「Locks archive as PATCH archived_at, never HTTP DELETE (data-safety).」
|
||
|
||
## 任务 2 · 引导旅程契约
|
||
|
||
文件:`tests/test_birth_time_journey_contract.py::test_web_onboarding_uses_the_deterministic_free_journey`
|
||
|
||
| 项 | 原值 | 新值 |
|
||
| --- | --- | --- |
|
||
| page 读取面 | `page.tsx` + `home-profile.ts` | `_home_surface()`(同上,含可选 hook 文件) |
|
||
| mastra 读取面 | `frontend/src/mastra/index.ts` | `_mastra_consultation_surface()` = `index.ts` + `consultation-workflow.ts`(`index.ts` 只再导出;token 在 workflow) |
|
||
| 正断言 | `'entry_mode: entryMode' in mastra` | 不变(现码 `consultation-workflow.ts`) |
|
||
| 负断言 | `'entry_mode: "direct_chart"' not in mastra` | 不变(schema 默认值是 `entryMode: z.enum(...).default("direct_chart")`,不是 `entry_mode: "direct_chart"`;`consultation-tools.ts` 未拼进此读取面) |
|
||
|
||
白名单决定:**加入** `CORE_PYTEST_TARGETS`。整文件只读 SQL 迁移与 TS 源码,无 subprocess、无网络、无 Docker。注释「Pure source/SQL regex for the birth-time journey; no runtime services.」
|
||
|
||
## 任务 3 · popstate 副作用对齐
|
||
|
||
`applySessionPopStateRef`(现位于 `use-session-management.ts`,小刀改完后原样搬):
|
||
|
||
| 项 | 原值 | 新值 |
|
||
| --- | --- | --- |
|
||
| `requestedId` | `query.present ? query.sessionId : fallbackId`(无 `?c=` 时直接等于列表首项,空 id 分支实际上只有「列表为空」才会进) | `query.sessionId`(无 `?c=` 时进空 id 分支) |
|
||
| 空 id 分支 | `setActiveSessionId(""); return;` | 有 `fallbackId`:`sessionSelectionSource.current = "history"; selectSession(fallbackId);`;否则维持 `setActiveSessionId("")` |
|
||
|
||
有会话时 back 到 `/` 仍走 `selectSession`(清草稿/提示、切绑定星盘),来源保持 history,不二次 `pushState`。无会话时行为不变。
|
||
|
||
`frontend/tests/chat-session-url.test.ts`:
|
||
|
||
- 原 P1 合同「popstate reuses selectSession without a second push」未改断言;切片里 `writeSessionUrl(..., "push")` 仍只有 `selectSession` 内那一处。
|
||
- 新增「popstate to a missing session query reuses selectSession side effects for the default chat」,锁空 id 分支源码形态,且 popstate 切片内 `writeSessionUrl(..., "push")` 次数为 0。
|
||
|
||
`frontend/tests/home-surface.ts` 同步加入可选 hook 文件(`existsSync`),避免第二批把 `selectSession` / popstate 搬走后本文件立刻再红。
|
||
|
||
## 前瞻(第二批)
|
||
|
||
两份 Python 合同与 `chat-session-url.test.ts` 都走拼接读取。第二批把 `toggleArchivedSession` / `applySessionPopStateRef` 搬进 `use-session-management.ts` 后,hook 文件进可选清单即可。切片标记保持相邻。
|
||
|
||
## 验证
|
||
|
||
- `./node_modules/.bin/tsc --noEmit`:通过(小刀后与第二批抽取后各跑一次)。
|
||
- `python3.12 -m pytest tests/test_session_management_entrypoints.py tests/test_birth_time_journey_contract.py -q`:12 passed(3 + 9)。
|
||
- 连同 `tests/test_supabase_user_data_contract.py` `tests/test_daily_and_rectification_entrypoints.py`:24 passed。
|
||
- `tsx --test tests/chat-session-url.test.ts`:12 pass / 0 fail / 0 skipped。
|
||
- 前端全量 `npm test`:2428 pass / 0 fail / 0 skipped(小刀新增 1 条 popstate 源码合同;相对第一批 2427)。
|
||
- `./node_modules/.bin/next build --webpack`:`┌ ○ /` Static。首屏 JS gzip-9 = 501104 B(第一批后 501315 B,−0.04%)。
|
||
|
||
未改 `.gitea/workflows/**`。未动数据库。未提升 `main`。
|