fix(home): land on the blank starter home after login and on bare / (BUG-1052)

Product decision 2026-09-27 (D1-D4), overriding the BUG-1038 login-return
stash and the "latest session" default landing:

- A full load of / without ?c= (login, typed address, bookmark, refresh of
  bare /) lands on the current person's blank starter home; an existing
  empty draft of that person is reused, otherwise one is created locally.
- ?c= and ?new=1 are unchanged; a refresh inside a conversation keeps its ?c=.
- The login-return stash is removed: redirectToLogin and sidebar links no
  longer write it, bootstrap no longer reads it and clears a leftover value
  once. replace-selected, the lookup origin/other-subject branch and the warm
  resumeRectification dependency go with it.
- A background answer being recovered no longer takes over the blank home
  (same rule BUG-1015 set for ?new=1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
Jesse_Chen
2026-09-27 00:51:51 +08:00
co-authored by Claude Opus 5.5
parent 76924e3362
commit d2551437c4
32 changed files with 629 additions and 315 deletions
@@ -0,0 +1,92 @@
# PROGRESS · 登录后 / 裸 `/` 落空白首页,删除登录返回存根(BUG-1052)· 2026-09-27
- 执行方式:直接执行(产品负责人授权子代理执行;Claude 事后独立验收)
- 基线:`origin/staging` `5f2e007f` 开工;提交前变基到 `76924e33`(只多一份任务书文档,代码与测试结果不变)
- 分支:`codex/home-landing-blank-20260927`(本地提交,未推送)
- BUG 编号:1052(开工时最大号 1051)
- 决策:产品 2026-09-27 D1–D4,推翻 BUG-1038 登录返回存根与「裸 `/` 默认落最近会话」,写在 `docs/BUG_HISTORY.md` BUG-1052 决策记录
## 结论
| 项 | 结果 |
| --- | --- |
| 根因 | 与任务单诊断一致:① 登录返回存根(401 / 侧栏次级页与「去登录」链接写入,登录后 `replace-selected` 写回 `?c=` 并打开)——真机「落在旧校正里」只能来自这条;② 默认落点取最近一条,只从生时校正换到「空咨询或任意咨询」,所以裸 `/` 通常开最近一条旧咨询。暖返回逐条镜像。补充:旧空咨询判定只看 `messages.length`,未加载消息的列表行也会被当草稿,已收紧 |
| D1 裸 `/` → 空白首页 | 完成。none / replace-clear 只复用当前人物的空草稿(`isStarterHomeDraft`),否则本地新建;冷启动用 `replaceUnsavedEmptyConsultations` 前插不留重复;暖返回同一套函数 |
| D2 `?c=` / `?new=1` | 不变,有生命周期用例锁住 |
| D3 删除存根 | 完成。删 `persistLoginSessionReturn` / `readLoginSessionReturn`、`storedReturnId`、`clearStoredReturn`、`replace-selected`、lookup 来源参数与 `other-subject`、暖返回 `resumeRectification` 依赖;`redirectToLogin` / `leaveChat` 不写、`startNewChat` / `openChatBoundToProfile` 不清;冷启动无条件 `clearLoginSessionReturn()` 清旧值。人物范围:空白首页绑定当前人物、只复用本人草稿 |
| D4 对话内刷新 | 核实成立(URL 带 `?c=` → keep),生命周期用例 + Chrome 截图 |
| 执行方解读(待验收确认) | 裸 `/` 时若有旧回答在后台生成:恢复继续,但不抢空白首页(`landingYieldsToRecovery`:只有 keep / lookup 让步),与 BUG-1015 `?new=1` 同规则 |
| 未改 | 站内浏览器「返回」退到不带 `?c=` 的历史条目(popstate)仍选列表第一条非校正会话;不是整页打开 `/`,不在 D1 列举内 |
## 改动文件
| 文件 | 改动 |
| --- | --- |
| `frontend/src/lib/chat-session-url.ts` | 选择函数去存根;lookup 结果只剩 found / missing / unavailable;删写 / 读存根,保留清理 |
| `frontend/src/lib/home-bootstrap.ts` | `isStarterHomeDraft`、新落点规则、`landingYieldsToRecovery`;`LandingSession` 加可选 `messagesHydrated` / `archivedAt` |
| `frontend/src/lib/home-bootstrap-run.ts` | 冷落点按当前人物范围、去重前插、无条件清旧存根、恢复让步规则;暖提交删存根分支与 `resumeRectification` |
| `frontend/src/lib/home-warm-start.ts` | 去 `storedReturnId` 与存根分支 |
| `frontend/src/lib/home-cloud-sync.ts` | `redirectToLogin` 不写存根;`resolveLookupBootstrap` 去存根来源 |
| `frontend/src/components/app-sidebar.tsx` | `leaveChat` 只关抽屉 |
| `frontend/src/hooks/use-session-management.ts` | 新建对话不再清存根 |
| `frontend/src/app/(app)/page.tsx` | 删暖刷新的 `resumeRectification` 一行(1370 → 1369 行;`Home()` useState 33 / useRef 37 不变) |
## 改动的既有断言(原值 / 新值 / 原因均已写在测试行内注释)
| 文件 | 用例(原标题 → 新标题) | 摘要 |
| --- | --- | --- |
| `chat-session-url.test.ts` | new chat intent overrides … plus login return → … invalid c | 去 `storedReturnId` / `clearStoredReturn` |
| 同上 | new chat bootstrap consumes the URL … | `clearStoredReturn` 条件清 → 无条件清;直接前插 → `replaceUnsavedEmptyConsultations` |
| 同上 | bootstrap reads a listed ?c= session and ignores login storage → bootstrap reads a listed ?c= session | 去存根字段 |
| 同上 | bootstrap restores a stored login return only when the URL has no c → bare / after login never returns to a stored conversation (BUG-1052) | `replace-selected` → `none`,源码反向锁 |
| 同上 | bootstrap clears an illegal or unknown session query | 去存根字段(2 处) |
| 同上 | default bootstrap selection does not write a session URL | 去存根字段;`replace-selected` 正向 → 反向 |
| 同上 | 401 login redirect stashes the current session id … → 401 login redirect stashes nothing … | 正向 `persistLoginSessionReturn` → 反向 |
| 同上 | login return storage only accepts a UUID session id → a leftover login-return stash from an older build is only ever cleared | 写 / 读 → 只清、隐私模式不抛 |
| `chat-navigation-a11y-contract.test.ts` | auth redirects stay hard document loads … | `persistLoginSessionReturn();\n replace` → `clearHomeWarmSnapshot();\n replace` + 反向 |
| `sidebar-contract.test.ts` | reaches chart, ephemeris and reports with links … | `leaveChat` 先存根 → 只关抽屉 + 反向 |
| `home-bootstrap-reveal.test.ts` | default landing replaces a latest rectification session … | `replace-selected` → `lookup`;none 选普通咨询 → 选空草稿 |
| 同上 | bare home still opens the selected ordinary chat without creating another → bare home with a latest ordinary chat lands on a new blank starter home (BUG-1052) | NeedsConsultation false → true |
| `session-lookup-unlisted.test.ts` | lookup results … | 去 `clearStoredReturn` |
| 同上 | a looked-up stashed return writes its URL back … → only a ?c= is ever looked up … (BUG-1052) | 来源参数删除,源码反向锁 |
| `home-warm-snapshot.test.ts` | warm landing follows the cold rules: ?c= keeps, stash writes back, … → … bare / is the blank starter home, ?new=1 is a new chat | bare CONS → 新建空对话;新增复用本人草稿 / 不复用他人草稿 |
| `new-chat-recovery.test.ts` | ordinary home landing still activates and announces … → bare / keeps the blank starter home while … recovers (BUG-1052) | active 旧会话 → 空白首页、notice 清空、storedCleared true;另加 `?c=` 仍被接管 |
| `new-chat-from-people-lifecycle.test.tsx` | 侧栏新建对话 ×5、他人存根不落锁死页 | 离开时存根 RECT → null(落点断言不变) |
| 同上 | a stashed session of the same person found beyond the first page opens … → a leftover stash … is ignored and cleared … (BUG-1052) | 打开校正并写回 `?c=` → 空白首页、不 lookup、清旧值 |
| 同上 | an in-page new chat drops the stashed return … → an in-page new chat leaves no return trail … | 不预置存根,断言新建不写 |
| `home-warm-return-lifecycle.test.tsx` | a stashed return to a rectification session reopens it … → a warm return to bare / after a rectification session lands on the blank starter home … | 写回 `?c=` 打开校正 → 空白首页、首帧无加载环 |
| fixtures(非断言) | `home-bootstrap-run` / `session-list-lifecycle` / `consultation-recovery` / `new-chat-recovery` / `home-warm-snapshot` | 去掉 selection 里的 `clearStoredReturn` 字段 |
新增:生命周期 5 条(裸 `/` 最近普通咨询 / 最近生时校正、`?c=` + 刷新、校正内刷新、校正内 401 → 重登)、暖返回冷启动裸 `/` 1 条、纯函数 2 条(未加载消息的行不算草稿、恢复让步规则)、`?c=` 恢复接管 1 条。改写与新增的生命周期用例放到 `origin/staging` 代码上跑:12 条失败(`?c=` 与刷新两条本就应通过,作 D2 / D4 护栏);修复后全绿。
## 验证
| 项 | 结果 |
| --- | --- |
| `tsc --noEmit` | 0 错 |
| `npm run lint` | 0 error(128 warning,改动文件无新增与本单标识符相关的 warning) |
| 全量 `npm test`(Node 22.14) | 4078 / 24 fail / 28 skip;基线 4069 / 24 / 28。失败名单与基线逐条一致(全部 Docker/DB);消失的 12 个用例名全部是改名,三栏说明在行内,新增 21 个名 |
| Python 门禁集合 | 948 passed / 1 skipped(与基线一致) |
| `npm run build -- --webpack` | `/`、`/chart`、`/ephemeris`、`/people` 均 ○ Static;已删构建产生的 `frontend/frontend/` |
| 首屏 gzip(`rootMainFiles` 4 个文件 zlib 默认级别求和) | 131,253 B;基线 130,933 B,+0.24%,在 ±2% 内 |
| 定向 13 个文件 | 167 / 0 fail |
## 本地 Chrome(headless,CDP 拦截 `/api/*` 返回虚构数据)
`next start`(本单构建)+ Chrome `--headless=new`,截图在 `docs/testing/home-landing-blank-20260927/`:
| 截图 | 场景 | 结果 |
| --- | --- | --- |
| `1-bare-latest-rectification.png` | 列表 = [生时校正(最近), 普通咨询],打开 `/` | 空白首页「新对话」、地址 `/`、无转录、未开校正 |
| `2-bare-latest-consultation.png` | 列表 = [普通咨询(最近,有消息), 生时校正],打开 `/` | 空白首页 |
| `3-bare-leftover-stash.png` | sessionStorage 预置旧存根 = 校正会话,打开 `/` | 空白首页,存根被清 |
| `4-c-link-consultation.png` | 打开 `/?c=<咨询>` | 进该对话、有问答 |
| `5-c-link-rectification.png` | 打开 `/?c=<校正>` | 进校正界面 |
| `6-refresh-in-rectification.png` | 在 5 上刷新 | 仍在校正界面、地址不变 |
`POST /api/rectification/cases/open` 共 2 次,只出现在 5 / 6。右下角「合盘历史暂时无法读取」提示是本 harness 未 mock `/api/synastry-reports`(404)所致,与本单无关。
## 环境缺口
- 无受控登录账号:真实邮箱验证码登录、真实 401 过期后重登、iPhone Safari 走查留给 `docs/testing/home-landing-blank-20260927.md`。
- 无 Docker:24 条 DB/部署测试失败,与基线名单逐条一致。
+1
View File
@@ -126,6 +126,7 @@
| 任务书 | 进度 | 主题 | 状态 | 落点 |
| --- | --- | --- | --- | --- |
| — (产品 09-27 拍板 D1–D4,直接执行) | [PROGRESS](PROGRESS-home-landing-blank-20260927.md) | **登录后 / 裸 `/` 落空白首页**:真机登录后在「首页」提问其实问进了上一次生时校正。删登录返回存根(401 / 次级页链接写、登录后写回 `?c=` 打开),裸 `/` 不再落最近会话,一律当前人物的空白首页(复用空草稿);`?c=` / `?new=1` / 对话内刷新不变;推翻 BUG-1038 存根与 BUG-599 默认落点 | 待验收 | `codex/home-landing-blank-20260927`(BUG-1052,本地未推) |
| — (产品 09-26 口头拍板 D1–D3,直接执行) | `PROGRESS-consult-answer-truncation-20260926.md` | **普通咨询回答写到一半被掐断仍扣点(BUG-1051,复发自 BUG-305)**:工具循环与写回答共用 110 秒 signal;Mastra 1.50 超时不抛错(`abort` 块 + `finish(tripwire)` 后正常关流),结算只认抛错与 `length`。D1 写回答自有 70 秒时钟(首用起算,续写 / 回答重试共用,最坏 180 秒,`maxDuration` 240);D2 写回答的最后一个流不是 `stop` 且有正文 → `answer_truncated`、不扣点、记 abort 步、不冲半句,`length` 续写不变;D3 观测加 `composeFinishReason` / `composeAborted` / `answerVisibleChars` | 已验收(真机欠) | `codex/consult-answer-truncation-20260926`(本地,未推送);新回归 15 条用真实 Mastra `Agent`(修复前 11 条红);全量失败名单 0 新增;Python 948/1;`/` ○、gzip 0%;真机清单 `docs/testing/consult-answer-truncation-20260926.md` |
| `TASK-consult-evidence-card-research-20260927.md` | `PROGRESS-consult-evidence-card-research-20260927.md` | **普通对话数据卡调研**:引擎输出逐项分五类计量(现约 4 万 token、父母问题相关约 3.5%);四处领域→技法来源对账并起草各领域数据卡(家庭拆父母/子女);卡体量与逐字一致性;按卡算的提速空间;反馈迭代埋点方案。只调研不改线上 | 待领取 | 分支 `codex/consult-evidence-card-research-20260927`;与 BUG-1053 实现单并行,不碰 consult route / stream-agent-response |
| `TASK-scroll-anchor-hook-fixes-20260926.md` | `PROGRESS-scroll-anchor-hook-fixes-20260926.md` | **滚动锚两处老问题**:直接打开已有会话时监听未挂上(BUG-1043)、校正长回答钉顶后因 96px 阈值被拉到底(BUG-1044)。排在 BUG-1042 合入后。挂载改由容器元素本身驱动(每次提交比对元素 / active / resetKey);钉顶只由用户滚动手势解除 | 已验收(Claude 09-26 直接执行:子代理复现两处根因并修复;Claude 独立复验 tsc/lint 0、全量 3970 条失败名单与基线逐条一致、四路由 ○、gzip 不变;iOS 惯性滚动留真机清单) | `da2613ff`(随 `f1d16405` 部署,health 一致) |