Files
Jyotisha/docs/tasks/TASK-archive-retire-20260921.md
T
Jesse_ChenandClaude Opus 5 f8cb1dcdee docs(tasks): 归档能力整体下线任务书(BUG-991)
产品拍板下线而非装回入口:删前端链路与 ?archived=1,
迁移清空存量 archived_at(不 bump updated_at),列留待后轮。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0199rbQDTsUbCVw84wc8BTFe
2026-09-21 18:22:52 +08:00

12 KiB
Raw Blame History

TASK 归档能力整体下线(BUG-991)

  • 日期:2026-09-21
  • 基线 commit:origin/staging head(开工时 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. 硬红线

  1. 不得顺手改会话删除能力(DELETE /api/sessions/[id]、行菜单的「删除」、BUG-505 那条链路)。归档和删除是两回事。
  2. 不得回退或弱化 frontend/tests/local-postgres-not.test.ts(D3)。
  3. 不得删 archived_at 列(D2)。迁移只能是 update … set archived_at = null,幂等、可重复执行。
  4. 删掉任何既有断言都要在进度记录里写「原值 / 新值 / 原因」三栏(AGENTS.md §7.3);测试总数下降必须逐条解释。
  5. frontend/src/app/(app)/page.tsx 的 useState / useRef 数不得增长(本轮预期是减少或持平)。
  6. 旧 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(尤其不得 bump updated_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 不得超过 119
  • npm test → 全量。本轮测试总数会下降(删了归档断言),必须逐条列出删了哪些、原值新值原因;失败清单与基线逐条比对
  • npm run test:db → T3 必跑;无 Docker 时按 §7 让步
  • npm run build → / 仍 ○ Static,首屏 gzip 与基线 ±2%(预期略降)

7. 让步顺序

  1. 无 Docker → test:db 跑不了:迁移与测试照写照提交,BLOCKED.md 记明,最终以门禁 run 转绿为准;BUG-991 在门禁绿之前不得标 resolved。
  2. T3 迁移与 T1/T2 拆轮 → 允许,但顺序必须是先 T3 后 T1/T2:先把存量放回列表,再拆掉读取归档的能力。反过来会让已归档会话在两次部署之间彻底看不到。
  3. 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. 部署后真人走查(产品负责人)

  1. 会话行的「…」菜单里不再有「归档」。
  2. 以前归档过的对话重新出现在侧栏(如果你归档过的话),且它们的时间没有被改成今天。
  3. 删除会话仍然正常。