产品拍板下线而非装回入口:删前端链路与 ?archived=1, 迁移清空存量 archived_at(不 bump updated_at),列留待后轮。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0199rbQDTsUbCVw84wc8BTFe
12 KiB
TASK 归档能力整体下线(BUG-991)
- 日期:2026-09-21
- 基线 commit:
origin/staginghead(开工时git fetch后以实际 head 为准;当前11e4d5ff,最近一次含门禁路径的提交是df329fa9,staging 已部署该 SHA) - 分支:
codex/archive-retire-20260921 - BUG 编号:BUG-991(已在
docs/BUG_HISTORY.md以investigating记录,本轮改成resolved,不新占号) - 关联记录:BUG-990、BUG-553、BUG-926
1. 事故实证
会话行菜单里有「归档」,点了之后这条会话从侧栏消失,此后没有任何界面能把它列出来或恢复。
- 侧栏分组页眉上原本有切换按钮
session-nav-toggle(文案「归档 N」/「返回」),在92558ee6(2026-08-22,fix(web): stack mobile audit tables and restore sidebar hierarchy) 被删除。 - 但它背后整条链路都留着:
onToggleArchivedView、archivedCount、showingArchived、toggleArchivedView()、GET /api/sessions?archived=1、applyArchiveFilter。 - 全仓
grep -rn "ToggleArchivedView" frontend/src的非测试命中只有两处:sidebar-session-row.tsx的类型声明,和use-home-shell-registration.ts里提供它的那一行。没有任何渲染调用点。 app-sidebar.tsx:278与:286两处sessionControls?.showingArchived ? "归档记录" : …因此恒取后半边。- 行菜单里的「恢复」(
sidebar-session-row.tsx:178)同样不可达:归档行永远不进useSessionManagement的visibleSessions(非归档视图分支isListedSidebarSession直接if (session.archivedAt) return false)。
顺带一提,这个半截状态直到 2026-09-21 才被发现,是因为 BUG-987 新加的真实 Postgres 测试第一次跑到归档分支,炸出了 BUG-990(兼容层 not(col,"is",null) 未实现 → ?archived=1 回 500)。也就是说归档视图的接口自 a1956deb(09-06)起就是坏的,而没人发现,因为界面上根本点不到它。
docs/testing/ 里还有两份让人「切到归档视图」的走查条目(session-list-title-and-order-20260906.md 第 63–67 行、home-state-lowering-batch2-20260916.md 的 C2 / C4),从 08-22 起就无法执行。
2. 根因
删掉一个视图的唯一入口时,只删了按钮,没有下线它的数据链路,于是留下「接口活着、界面没有」的半截功能。类型声明、provider 装配、服务端查询参数和数据库列都还在,源码正则合同与 tsc 都不会报——它们只检查写了什么,不检查有没有人调用。
3. 决策记录
- D1(产品 2026-09-21 拍板):把归档能力整体下线,不是把入口装回去。 依据:这个功能半截了一个月没人发现,说明没人用;产品一贯口径是「多余入口宁可删除也不修」。删除会话的能力保留不动。
- D2:
archived_at列本轮不删。 AGENTS.md §7.6 要求删列拆两轮。本轮只写一条向前迁移把存量archived_at清空,并保留列表查询里的is("archived_at", null)守卫;删列另开一轮,优先级低,不做也行。 - D3:不得回退 BUG-990 对兼容层的修复。
not(col,"is",null)编译is not null是 PostgREST 语义补全,local-postgres-not.test.ts必须原样保留,即使本轮之后它在业务代码里没有调用者。 - D4:存量被归档的会话一律放回列表,不询问、不保留标记。它们本来就是用户自己的对话,而且他现在没有别的办法看到它们。
4. 硬红线
- 不得顺手改会话删除能力(
DELETE /api/sessions/[id]、行菜单的「删除」、BUG-505 那条链路)。归档和删除是两回事。 - 不得回退或弱化
frontend/tests/local-postgres-not.test.ts(D3)。 - 不得删
archived_at列(D2)。迁移只能是update … set archived_at = null,幂等、可重复执行。 - 删掉任何既有断言都要在进度记录里写「原值 / 新值 / 原因」三栏(AGENTS.md §7.3);测试总数下降必须逐条解释。
frontend/src/app/(app)/page.tsx的useState/useRef数不得增长(本轮预期是减少或持平)。- 旧 bundle 兼容:
PATCH /api/sessions/[id]收到archived_at不得回 400,按messages的既有做法解析后忽略。
5. 任务分解
T1|前端下线(BUG-991)
要删的(以符号定位,不写行号):
sidebar-session-row.tsx:行菜单里的「归档 / 恢复」项与Archive/ArchiveRestore两个图标 import;SidebarSession.archived字段;SidebarSessionControls的archivedCount、showingArchived、onToggleArchivedView、onToggleArchived。app-sidebar.tsx:两处showingArchived ? "归档记录" : …三元条件塌成常量(aria-label="最近对话"、页眉文案「最近」)。use-home-shell-registration.ts:archivedCount计算与onToggleArchivedView/onToggleArchived装配。use-session-management.ts:toggleArchived(restoring/nextArchivedAt那段)、toggleArchivedView、showArchivedSessions状态与它在visibleSessions、loadMoreSessions、loadArchivedSessions里的分支;visibleSessions收敛成单一的isListedSidebarSession过滤。session-list-filter.ts:applyArchiveFilter的archived参数与not(...)分支(改成固定is("archived_at", null),函数可并入调用处);cloudListIncludesSession的archivedView入参。home-cloud-sync.ts:fetchSessions的archived选项与params.set("archived","1");applyLegacySessionControls里 archived 的那一半(这段会从旧 localStorage 重新造出归档行,必须删,pinned那一半保留);readLegacySessionControlIds/clearLegacySessionControlKeys/sessionControlsStorageKey的"archived"分支——clearLegacySessionControlKeys仍要清掉那个 localStorage key,只是不再据它写库。session-sidebar-row.ts:archived: Boolean(session.archivedAt)映射。
保留:ChatSession.archivedAt 类型与 home-cloud-sync 的读取映射(列还在,读回来不报错),isListedSidebarSession 里 if (session.archivedAt) return false 这条守卫。
- 验收标准:
grep -rn "ToggleArchivedView\|showArchivedSessions\|archivedCount\|showingArchived" frontend/src无命中。grep -rn "归档" frontend/src --include=*.tsx --include=*.ts(非测试)无命中。sidebar-contract.test.ts补一条:行菜单项恰好是 {重命名、收藏/取消收藏、分享、删除},不含归档——这条是防复发的主断言。- 会话删除路径的测试全部不变且仍绿。
T2|服务端下线(BUG-991)
GET /api/sessions:删archived查询参数与isArchivedSessionQuery,列表恒查未归档;session-cursor.ts里isArchivedSessionQuery若再无调用者一并删除。chat-session-write-contract.ts:chatSessionMetadataPatchSchema去掉archived_at;extractChatSessionMetadataPatch保留"archived_at" in record的读取但不放进 patch(D2/红线 6 的向后兼容),并在注释里写明「旧 bundle 仍可发送,服务端解析后忽略」;ChatSessionMetadataPatch类型去掉该字段。/api/sessions/[id]的 PATCH 不得再写archived_at。- 验收标准:
chat-session-authority.test.ts:新增「PATCH 收到archived_at返回 2xx 且不落库」的合同断言(与既有messages那条同形)。database-session-list-visibility.test.ts:删除归档视图那一半断言(原值:applyArchiveFilter(..., true)返回[archivedRectification];新值:删除;原因:归档视图已下线,该分支不再有调用者)。保留「archived_at非空的行不出现在列表里」这条正向断言——列还在,守卫仍需被测。同步收敛cloudListIncludesSession的六组组合到三组。local-postgres-not.test.ts一字不动仍绿(D3)。
T3|存量数据放回列表(BUG-991)
- 新增向前业务迁移
frontend/supabase/migrations/<ts>_retire_chat_session_archive.sql:update public.chat_sessions set archived_at = null where archived_at is not null;幂等(跑第二次匹配 0 行),带注释说明列保留待后续轮次删除。 - 迁移器会在部署前自动应用(AGENTS.md §7.6),与 T1/T2 同轮安全:迁移只清数据不改结构,对当前已部署的旧代码向后兼容(旧代码读到的只是「没有归档会话」)。
- 验收标准:
npm run test:db中新增或扩展一条:迁移后select count(*) from public.chat_sessions where archived_at is not null= 0,重复执行迁移仍 0 行受影响。- 迁移不得触碰
pinned、messages、updated_at(尤其不得 bumpupdated_at,否则全部历史会话会一起跳到列表顶端并被当成今天活动过——这会直接毁掉刚修好的 BUG-988)。
T4|记录(BUG-991)
docs/BUG_HISTORY.md的 BUG-991:investigating→resolved,补修复、验证、修复版本;防复发写「删除某个视图的唯一入口时必须同轮下线其数据链路,或留一条断言入口存在的测试」。CHANGELOG.md:一句用户能懂的——归档功能撤掉了,以前归档过的对话回到列表里,删除不受影响。frontend/DESIGN.md:会话行菜单的项目清单同提交更新。docs/testing/session-list-title-and-order-20260906.md第 63–67 行、docs/testing/home-state-lowering-batch2-20260916.md的 C2 与 C4 里涉及归档视图的条目:划掉并注明「归档能力已于 2026-09-21 下线(BUG-991)」,不要删除整份清单。docs/tasks/README.md状态板加行。
6. 交付前必须全跑
cd frontend && ./node_modules/.bin/tsc --noEmit→ 0 错npm run lint→ 0 error,warning 不得超过 119npm test→ 全量。本轮测试总数会下降(删了归档断言),必须逐条列出删了哪些、原值新值原因;失败清单与基线逐条比对npm run test:db→ T3 必跑;无 Docker 时按 §7 让步npm run build→/仍○ Static,首屏 gzip 与基线 ±2%(预期略降)
7. 让步顺序
- 无 Docker →
test:db跑不了:迁移与测试照写照提交,BLOCKED.md记明,最终以门禁 run 转绿为准;BUG-991 在门禁绿之前不得标resolved。 - T3 迁移与 T1/T2 拆轮 → 允许,但顺序必须是先 T3 后 T1/T2:先把存量放回列表,再拆掉读取归档的能力。反过来会让已归档会话在两次部署之间彻底看不到。
- T1、T2 不得拆开单独交付——只删界面不删接口,等于把今天这个半截状态原样留着。
8. 开工前置命令
cd /workspace/Jyotisha
git status -sb | head -1
git fetch origin --prune
git worktree add -b codex/archive-retire-20260921 \
.worktrees/archive-retire-20260921 origin/staging
cd .worktrees/archive-retire-20260921/frontend
ln -s /workspace/Jyotisha/frontend/node_modules node_modules # 或 npm ci
./node_modules/.bin/tsc --noEmit
npm test 2>&1 | tail -20 # 记录基线总数与失败清单
grep -rn "归档" ../frontend/src --include=*.tsx --include=*.ts | grep -v '\.test\.' # 记录开工命中
开工必读:docs/BUG_HISTORY.md 的 BUG-991、BUG-990、BUG-553;AGENTS.md §2、§3、§5、§6、§7(尤其 §7.3 改断言三栏说明、§7.6 迁移拆轮)。
纯前端 + 一条数据迁移,不要求 scripts/pre_work_check.py。
9. 部署后真人走查(产品负责人)
- 会话行的「…」菜单里不再有「归档」。
- 以前归档过的对话重新出现在侧栏(如果你归档过的话),且它们的时间没有被改成今天。
- 删除会话仍然正常。