# 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/_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. 开工前置命令 ```bash 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. 删除会话仍然正常。