fix(home): one new-chat intent and a scoped login-return stash (BUG-1038)
Coming back to / from /people could show the previous rectification session
as a locked page: its title in the header, the composer stuck on
"正在打开生时校正…", and the new-chat greeting in the middle.
The state that survived between pages is the sessionStorage login-return
stash that secondary-page sidebar links write from the current ?c=:
- /people「和 TA 对话」used a second intent (?newChat=1) parsed by a
component mounted inside Home after bootstrap, so the bootstrap new-chat
branch never ran and the stash won.
- A stash id not in the current person's loaded list was looked up and
landed with urlAction "keep", which assumes ?c= is already in the address
bar. It was not, so the rectification auto-open never fired. The stash
also ignored which person was current.
- An in-page new chat left the stash in place.
Fix: delete NewChatDeepLink / ?newChat and route「和 TA 对话」through
newChatHref(); a looked-up stash writes ?c= back (replace-selected) and is
dropped when it belongs to another person; startNewChat and
openChatBoundToProfile clear the stash. ?c= deep links, BUG-989 and BUG-705
are unchanged.
Tests: new real-lifecycle suite mounting the real Home, sidebar and people
page (10 cases: four secondary pages + mobile drawer, 和 TA 对话 for self and
another person, out-of-scope stash, same-person stash beyond the first page,
in-page new chat), plus two contract/unit tests. Six fail on origin/staging,
all pass here. Full suite 3928 / 61 failing, failure names identical to the
0ab061b9 baseline (3916 / 61).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f4b8f26e15
commit
0a8350cc01
@@ -0,0 +1,124 @@
|
||||
# PROGRESS · 从星盘档案页回首页落到上一次生时校正(2026-09-26)
|
||||
|
||||
任务书:`docs/tasks/TASK-new-chat-from-people-20260926.md`。分支 `codex/new-chat-from-people-20260926`,worktree `.worktrees/new-chat-from-people-20260926`。执行方:Claude 子代理(产品授权直接执行)。未推送、未部署。BUG-1038(开工时最大 BUG-1037;并行单已用 BUG-1039,1038 仍空)。
|
||||
|
||||
## 基线
|
||||
|
||||
- 开工基线 `65260ca7`;实现期间 `origin/staging` 前进到 `0ab061b9`(BUG-1039 档案页样式 + 星历改版),本分支已 rebase 到 `0ab061b9`。`people-page.tsx` 冲突一处:保留 staging 的新布局,只把「和 TA 对话」那一行的跳转改成 `newChatHref()`。
|
||||
- 基线全量(`0ab061b9`,同一台机器、Node v20.19.2、Linux):3916 条,61 失败,27 跳过。61 条都是环境缺口(Node 20 没有 `mock.module` 等)。
|
||||
|
||||
## 复现与根因
|
||||
|
||||
### 怎么复现的
|
||||
|
||||
两套手段,结论一致:
|
||||
|
||||
1. **真实浏览器**:本地 `next build --webpack` + `next start`,Chrome 151 无头,用 CDP `Fetch` 拦截 `/api/*` 返回虚构账户(林遥)、一条生时校正会话(「生时校正 · 04:45–05:15」)、一条普通咨询、一个他人(阿澈)。脚本在 scratchpad,未提交。
|
||||
2. **真实 React 生命周期测试**(新增 `frontend/tests/new-chat-from-people-lifecycle.test.tsx`):挂载真实 `Home`(`app/(app)/page.tsx` 默认导出)、真实 `SessionListProvider` + `AppSidebar` + `SidebarInset`(与 `(app)/layout.tsx` 同构;layout 本身引 CSS,不能在 tsx 里 import)、真实 `PeoplePage`。客户端导航 = 保留 provider 与侧栏、换路由子树;文档加载(`window.location.assign`)= 关掉 root、重置 current-subject 模块状态、保留 sessionStorage / localStorage。
|
||||
|
||||
| 路径 | 修复前(`origin/staging`) | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 开校正 → 侧栏「星盘档案」→ 选他人「看星盘」→ `/chart` 左下角账户(回裸 `/`) | **标题「生时校正 · 04:45–05:15」、占位「正在打开生时校正…」、发送禁用、中间「又见面了,林遥」+ 今日星语 / 生时校正两个入口,永久停住** | 与产品截图逐项一致,见 `docs/testing/new-chat-from-people-20260926-before.png` |
|
||||
| 开校正 → 「星盘档案」→ 本人「和 TA 对话」(`/?newChat=1`) | 地址被写成 `/?c=<校正>`,后台再 POST 一次 open,约 2 秒后从新对话翻回旧校正界面 | 慢打开(6 s)时可见先新对话、后翻回 |
|
||||
| 开校正 → 「星盘档案」→ 他人「和 TA 对话」 | 最终是新对话(`NewChatDeepLink` 抢回),但启动先按存根 lookup 了旧校正 | 时序侥幸 |
|
||||
| 开校正 → 「星盘档案」→ 侧栏「新建对话」(`/?new=1`) | **未复现**:新对话、地址 `/`、不再 open | 浏览器 4 种变体(默认 / 慢网 500 ms / 慢打开 6 s / 只有校正会话)+ lifecycle 四个次级页与手机抽屉 |
|
||||
|
||||
产品描述的入口(`/people` 点侧栏「新建对话」)本机没有复现;和截图一模一样的画面出现在「在档案页换成他人后经不带新建意图的 `/` 回首页」。已写进 BLOCKED,请产品真机确认当时的点击路径(`docs/testing/secondary-new-chat-20260923.md` 追加节 A1–A6)。
|
||||
|
||||
### 残留状态是哪一个(D3)
|
||||
|
||||
**`sessionStorage["jyotisha.session-url-return"]`(登录返回存根)**。只读侧栏的次级页链接 `onClick={leaveChat}` → `persistLoginSessionReturn()` 把当前 `?c=`(那条校正会话)存进去;它跨路由、跨文档加载存活,下一次首页启动在「没有 `new`、没有 `c`」时拿它当落点。
|
||||
|
||||
三处缺口:
|
||||
|
||||
1. **第二套新建意图**:「和 TA 对话」用 `/?newChat=1`,由首页里挂载的 `NewChatDeepLink` 在启动**之后**解析。启动的 new-chat 分支(会忽略并清掉存根)从未走到,存根照常胜出。
|
||||
2. **存根 lookup 落地不写 `?c=`**:存根 id 不在当前人物已加载的列表里时,`resolveLookupBootstrap` 去服务端找,找到返回 `urlAction: "keep"`。`keep` 是给「地址栏已经带 `?c=`」设计的;存根来源时地址栏没有 `?c=`,`shouldAutoOpenRectificationSession` 永远不成立 → 校正会话成了活动会话却不打开 → `composerLocksAsRectification` 锁住输入框、占位「正在打开生时校正…」,中间因为该会话没有消息显示开场问候。存根还不区分人物:当前人物换成他人后,本人的校正会话照样被拿来落地。
|
||||
3. **首页内新建不清存根**:lookup 暂时失败(`unavailable`)时存根保留;之后在首页点「新建对话」,再去次级页、回裸 `/`,仍会落回旧会话。
|
||||
|
||||
任务书候选里的「外壳注册 `openingSessionId`」「校正面模块级缓存」「current-subject emit 触发 `activateFallbackSession`」都查过:外壳注册在 Home 卸载时 `registerShellControls(null)`;校正面没有模块级状态;`activateFallbackSession` 只在删除会话时调用。都不是。
|
||||
|
||||
## 改了什么
|
||||
|
||||
| 任务 | 文件 | 内容 |
|
||||
| --- | --- | --- |
|
||||
| T2 / D1 | `frontend/src/components/people-home-links.tsx` | 删除 `NewChatDeepLink`(和它唯一用到的 `useCurrentSubjectReady` import)。 |
|
||||
| T2 / D1 | `frontend/src/app/(app)/page.tsx` | 去掉 `NewChatDeepLink` 的 import 与挂载(-1 行净;`Home()` 的 useState / useRef 数不变)。 |
|
||||
| T2 / D1 | `frontend/src/components/people/people-page.tsx` | 「和 TA 对话」`openPerson(selected.id, newChatHref())`。人物仍由 `setCurrentSubject(id)` 带过去(写 localStorage,文档加载后由 `bindCurrentSubjectAccount` 读回),启动新建分支按当前人物建本地空咨询,所以 `newChatHref` 不需要带 `subjectId` 参数。 |
|
||||
| T2 | `frontend/src/lib/chat-session-url.ts` | `newChatHref` 注释写明它是唯一新建意图;`bootstrapSelectionFromLookup` 加 `origin` 参数(默认 `"url"`,行为不变):`stored-return` + found → `replace-selected`(写回 `?c=`);新状态 `other-subject` → 默认落点、清存根。 |
|
||||
| T2 / D3 | `frontend/src/lib/home-cloud-sync.ts` | `resolveLookupBootstrap` 判断来源(地址栏有无 `?c=`),加可选 `inSubjectScope` 判定;存根会话不属于当前人物时走 `other-subject`,不合并进列表。 |
|
||||
| T2 / D3 | `frontend/src/lib/home-bootstrap-run.ts` | `resolveLanding` 传 `inSubjectScope: sessionMatchesSubject(session, readCurrentSubjectId())`(放这里而不是 home-cloud-sync,避免 home-cloud-sync ↔ current-subject 循环依赖)。 |
|
||||
| T2 / D3 | `frontend/src/hooks/use-session-management.ts` | `startNewChat`、`openChatBoundToProfile` 调 `clearLoginSessionReturn()`。 |
|
||||
| T1 / T3 | `frontend/tests/new-chat-from-people-lifecycle.test.tsx`(新) | 10 条真实生命周期测试,见下。 |
|
||||
| T3 | `frontend/tests/chat-session-url.test.ts` | 新增 1 条源码合同:「和 TA 对话」走 `newChatHref()`,people-page / people-home-links / page.tsx 里不再有 `newChat=`、`"newChat"`、`NewChatDeepLink`。 |
|
||||
| T2 | `frontend/tests/session-lookup-unlisted.test.ts` | 新增 1 条:`stored-return` found → `replace-selected`;`other-subject` → 默认 + 清存根;`url` found 仍 `keep`。 |
|
||||
| T4 | `docs/BUG_HISTORY.md`、`CHANGELOG.md`、`BLOCKED.md`、`docs/tasks/README.md`、`docs/testing/secondary-new-chat-20260923.md`(追加节 A1–A6)、`docs/testing/new-chat-from-people-20260926-{before,after,after-talk}.png` | 记录。不改 UI 样式与文案,`frontend/DESIGN.md` / `VOICE.md` 不动。 |
|
||||
|
||||
不变的口径:`?c=` 深链找到仍 `keep`;BUG-989 首问前不落库、不写 `?c=`;BUG-705 lookup 与 404 / 5xx 处理;无参数 `/` 回到同一人物的上一条会话(现在会把 `?c=` 写回,校正会话照常打开)。
|
||||
|
||||
## 新增测试(lifecycle 10 条)
|
||||
|
||||
| # | 名称(节选) | 修复前 `0ab061b9` | 修复后 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1–4 | from /chart · /ephemeris · /reports · /people,侧栏「新建对话」 | 通过 | 通过 |
|
||||
| 5 | from /people (mobile drawer),侧栏「新建对话」,抽屉关闭 | 通过 | 通过 |
|
||||
| 6 | 「和 TA 对话」本人 | **失败**(地址 `/?c=<校正>`、多一次 open) | 通过 |
|
||||
| 7 | 「和 TA 对话」他人,标题旁是 TA 的名字 | 通过(时序侥幸) | 通过 |
|
||||
| 8 | 他人存根不落锁死页 | **失败**(占位「正在打开生时校正…」) | 通过 |
|
||||
| 9 | 同人存根超出首页列表:带 `?c=` 打开校正界面 | **失败**(锁死页) | 通过 |
|
||||
| 10 | 首页内新建清存根(真实 `useSessionManagement`) | **失败** | 通过 |
|
||||
|
||||
加上两条纯函数 / 源码合同(修复前都失败),共新增 12 条。1–5 在修复前就通过:它们锁的是侧栏新建入口不回退(D2),不是复现用例。
|
||||
|
||||
## 验证
|
||||
|
||||
| 项 | 结果 |
|
||||
| --- | --- |
|
||||
| `./node_modules/.bin/tsc --noEmit` | 0 错 |
|
||||
| `npm run lint` | 0 error,126 warning(基线 127:删掉的 `NewChatDeepLink` 带走一条 exhaustive-deps) |
|
||||
| 定向 `tsx --test` lifecycle + chat-session-url + session-lookup-unlisted + people-archive-view + new-chat-recovery + home-bootstrap-run + sidebar-contract + session-list-lifecycle | 全部通过 |
|
||||
| 全量 `npm test`(Node 20) | 3928 条,61 失败,27 跳过(基线 3916 / 61 / 27) |
|
||||
| 失败名单对照 | 逐条相同,新增失败 0 |
|
||||
| 测试名对照 | 消失 0,新增 12 |
|
||||
| `next build --webpack` | `/` ○、`/chart` ○、`/ephemeris` ○、`/people` ○、`/reports` ƒ、`/reports/[reportId]` ƒ,与基线一致 |
|
||||
| 首屏 gzip(`/` 预渲染 HTML 引用的 36 个 js/css,gzip -9 求和) | 基线 656,329 B → 654,283 B(-0.31%) |
|
||||
|
||||
失败名单对照命令:
|
||||
|
||||
```bash
|
||||
grep -E '^not ok' base.log | sed 's/^not ok [0-9]* - //; s/ # .*//' | sort > fail-base.txt
|
||||
grep -E '^not ok' new.log | sed 's/^not ok [0-9]* - //; s/ # .*//' | sort > fail-new.txt
|
||||
diff fail-base.txt fail-new.txt && echo SAME-FAILS # → SAME-FAILS
|
||||
grep -E "^(not )?ok [0-9]+ - " base.log | sed -E 's/^(not )?ok [0-9]+ - //; s/ # .*//' | sort > names-base.txt
|
||||
grep -E "^(not )?ok [0-9]+ - " new.log | sed -E 's/^(not )?ok [0-9]+ - //; s/ # .*//' | sort > names-new.txt
|
||||
comm -23 names-base.txt names-new.txt # → 空(无消失)
|
||||
comm -13 names-base.txt names-new.txt | wc -l # → 12
|
||||
```
|
||||
|
||||
## 改动的既有断言
|
||||
|
||||
无。`chat-session-url.test.ts`、`session-lookup-unlisted.test.ts` 只新增测试,没有改、删任何既有断言;`sidebar-contract.test.ts` 锁的新建链接 `href={newChatHref()} … onClick={closeDrawer}` 未动。
|
||||
|
||||
| 原值 | 新值 | 原因 |
|
||||
| --- | --- | --- |
|
||||
| — | — | 本轮没有修改既有断言 |
|
||||
|
||||
## 浏览器证据
|
||||
|
||||
本地 `next start`(rebase 后的构建)+ Chrome 151,1280×900,虚构数据:
|
||||
|
||||
| 路径 | 修复后结果 |
|
||||
| --- | --- |
|
||||
| 他人「看星盘」→ 左下角账户回 `/` | 标题「新对话 阿澈」,占位「想聊什么都可以」,可输入,无第二次 open(`new-chat-from-people-20260926-after.png`) |
|
||||
| 本人「和 TA 对话」 | 地址 `/`,新对话,5 秒内不翻回,无第二次 open |
|
||||
| 他人「和 TA 对话」 | 标题「新对话 阿澈」(`new-chat-from-people-20260926-after-talk.png`) |
|
||||
| 侧栏「新建对话」(默认 / 慢网 500 ms + 慢打开 6 s) | 新对话,地址 `/`,无第二次 open |
|
||||
|
||||
修复前同路径截图:`new-chat-from-people-20260926-before.png`(基线构建)。右下角「合盘历史暂时无法读取」提示是 CDP 桩没有模拟 `/api/synastry-reports` 造成的,与本单无关。
|
||||
|
||||
## 环境缺口(未写成通过)
|
||||
|
||||
- **无受控登录账号**:没有在 staging 或 iPhone Safari 上走;真机清单见 `docs/testing/secondary-new-chat-20260923.md` 追加节 A1–A6。BUG-1038 保持 investigating。
|
||||
- **产品描述的入口未复现**:见上。需要产品真机确认。
|
||||
- **浏览器里没做手机抽屉**:CDP 脚本找抽屉触发器没对上;手机抽屉由 lifecycle 测试 #5(innerWidth 390,真实 `SidebarProvider` / `AppSidebar`)覆盖。
|
||||
- **Node 22**:本机只有 Node 20,全量以失败名单逐条对照代替,Linux Node 22 全量待协调方复跑。
|
||||
- **Docker**:无,`npm run test:db` 未跑;本单不动表。
|
||||
@@ -121,7 +121,7 @@
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `TASK-starter-home-polish-20260926.md` | — | **首页开场小字与图标**:今日趋势(每日模型生成,非写死)移到问候下方副行;入口下方提示只在有未完成校正或非本人时出现,删两句固定文案并修正已校正仍显示首次文案的分支;今日星语图标 MoonStar、点数图标 Coins。排在 BUG-1038、1040 之后 | 待领取 | — |
|
||||
| `TASK-home-warm-return-20260926.md` | — | **从其他页面回首页不再放加载动画**:首页按组件挂载计揭幕,客户端返回会重跑模型列表 / 后台状态 / 入口摘要 / 今日星语(3–5 次往返 + 最多 4 秒)。产品定:每次打开网页只揭幕一次;模块级暖快照按账户隔离、只存内存,暖返回首帧即就绪、后台静默刷新;缺项回落冷启动,不许半揭幕。**排在 BUG-1038 合入后**。BUG-1040 | 待领取 | — |
|
||||
| `TASK-new-chat-from-people-20260926.md` | — | **从星盘档案页点新建对话落回上一次生时校正**(产品 09-26 真机):标题显示旧校正会话、输入框「正在打开生时校正…」,中间却是新对话开场;另有两套新建入口(`?new=1` 与 `?newChat=1`)违反 BUG-1015 一处解析红线。先复现写失败测试、定位跨页残留状态再修,合并入口。BUG-1038 | 待领取 | — |
|
||||
| `TASK-new-chat-from-people-20260926.md` | `PROGRESS-new-chat-from-people-20260926.md` | **从星盘档案页点新建对话落回上一次生时校正**(产品 09-26 真机):标题显示旧校正会话、输入框「正在打开生时校正…」,中间却是新对话开场;另有两套新建入口(`?new=1` 与 `?newChat=1`)违反 BUG-1015 一处解析红线。先复现写失败测试、定位跨页残留状态再修,合并入口。BUG-1038 | 已实现待验收(Claude 子代理直接执行;残留状态 = 次级页链接写入的登录返回存根,存根 lookup 落地不写 `?c=` 致锁死页;未推送) | `codex/new-chat-from-people-20260926` |
|
||||
| `TASK-people-archive-p1-fix-20260925.md` | `PROGRESS-people-archive-p1-fix-20260925.md` | **P0修复**:worker按持久化人物解析、真实删除预留退款、本人历史OR is、切人/消息时序及P2/P3;BUG-1031/1032,关联1030/990/1024。永久定向54/54、独立前端5探针与退款r4六场景通过;必须最终Node22 Linux全量+DB逐条基线对照 | 已验收(Claude 09-25:两 P0、两 P1、七 P2 通过;新 P2 删后跳错人、人物接口故障连累聊天 → 收尾单) | `4d801e53`(已部署,health 核对一致) |
|
||||
| `TASK-home-page-split-20260925.md` | `PROGRESS-home-page-split-20260925.md` | **首页 page.tsx 拆分(只搬不改)**:启动流程约 285 行抽成 lib 纯函数 `runHomeBootstrap`、恢复轮询约 100 行抽出、错误 / 引导屏抽组件,目标 ≤1300 行并下调增长合同基线;两个按源码切片的测试改为直接调用函数;还原 824647f2 的 import 挤行。排在 chart-ephemeris 之后、people-archive P1 之前 | 已验收(行为逐行一致;1417 行达让步线、合同基线已下调;流程违规:未跑全量致门禁红 3 次) | `da35b3e4`(已部署) |
|
||||
| `TASK-staging-gate-fix-20260924.md` | — | **修复单(阻塞 staging 部署)**:慢网首页 `0e4dd0f4` 起 5 条源码合同测试未跟上(硬跳转计数、重试 reload 正则、两处按已删语句切片)→ 门禁红,`0e4dd0f4..f125fae0` 全部未部署;另 page.tsx +9 行、订阅支付不刷新余额、原始附录三文件残留。只修测试与收尾,不改已验收行为;失败名单须与 `edc9c22c` 逐条一致 | 已验收(Claude 09-25:5 条转绿、失败名单与 edc9c22c 逐条一致 56/56、门禁 run 2877 绿;import 挤行凑数为 P3,由 home-page-split D5 还原;原始附录三文件仍未删) | `651c75e7`/`7a001456`/`824647f2`;deploy run 2879 因 ACR 网络失败,待同 SHA 重跑 |
|
||||
|
||||
Reference in New Issue
Block a user