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

149 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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. 开工前置命令
```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. 删除会话仍然正常。