551d6317ae
Keep archive as a PATCH of archived_at rather than DELETE, pass entry_mode through onboarding, and let popstate to the default chat reuse selectSession. Then move send/stop/recovery and session management out of page.tsx so the home surface stays within the batch-two line budget. Co-authored-by: Cursor <cursoragent@cursor.com>
99 lines
8.0 KiB
Markdown
99 lines
8.0 KiB
Markdown
# PROGRESS · 拆分首页巨石组件·第二批:聊天主链路(2026-09-01)
|
||
|
||
工作树:`.worktrees/contract-repairs-20260901`
|
||
分支:`codex/contract-repairs-20260901`
|
||
基线:小刀轮未推送,故未另开 `codex/home-split-batch2-20260901`;产品代码从 `a6148af8` + 小刀轮改动继续。
|
||
|
||
不要写成 `PROGRESS.md`。交付为快进推 `origin/staging`。
|
||
|
||
React Compiler 仍关(`BLOCKED.md` 2026-08-17)。未引入 context/store。无手写 `useCallback`/`useMemo`。自定义 hook 按任务书决策记录 1 允许。`site-styles` import 仍在 `page.tsx` 顶。`const BirthTimeRectification = dynamic(...)` 未动。
|
||
|
||
## 度量
|
||
|
||
| 项 | 拆前(第一批后 3505 / 小刀后约 3510) | 拆后 | 说明 |
|
||
| --- | ---: | ---: | --- |
|
||
| `frontend/src/app/page.tsx` 行数 | 3505 | 2445 | 目标 ≤2600,达标 |
|
||
| `useState(` Home / session hook / consult hook | 26 / — / — | 26 / 0 / 0 | 独占 state 未下移,见 hook 顺序 |
|
||
| `useRef(` Home / session / consult | 19 / — / — | 19 / 0 / 0 | 同上 |
|
||
| `useEffect(` Home / session / consult | 20 / — / — | 20 / 0 / 0 | 拉取/恢复 effect 仍在 Home |
|
||
| `/` 路由 | `○ Static` | `○ Static` | webpack `next build --webpack` |
|
||
| 首屏 JS gzip -9 | 第一批后 501315 B | 501104 B = 489.4 KB | −211 B / **−0.04%**(±2% 内) |
|
||
| 前端测试 | 小刀后 2428 | 2428 | 见验证 |
|
||
|
||
剩余未达标块(第三批):onboarding/profile(`refreshAccount`…`signOut`)、rectification 胶水(`refreshRectificationEntrySummary`…`handleRectificationMessagesChange`)、合盘草稿、composer `submit`。
|
||
|
||
## Hook 调用顺序(红线 1)
|
||
|
||
搬前 `Home` 内真正的 React hook 序列(`a6148af8`):
|
||
|
||
`useRouter` → `useState`×26 → `useRef`×19 → `useBirthTimeGuidedJourney` → `useEffect`×8 → `useConversationScrollAnchor` → `useEffect`×12
|
||
|
||
咨询/会话的 `useState`/`useRef` **不是连续块**:中间夹着校正会话、引导草稿、星盘库、合盘等。若只把「引擎独占」的那几项抠进 hook,会改变 `Home` 里 hook 的相对次序。
|
||
|
||
因此本轮两个新 hook **内部零 `useState`/`useRef`/`useEffect`**,只搬函数体与 `.current =` 赋值。串接后 `useState`/`useRef`/`useEffect` 次序与搬前逐一相同;新增的只是两个无内部 hook 的调用点:
|
||
|
||
| 调用点 | 位置 | 原因 |
|
||
| --- | --- | --- |
|
||
| `useSessionManagement(...)` | `useBirthTimeGuidedJourney` 之后、**第一条 `useEffect` 之前** | 紧随其后的 effect 调用 `ensureSessionMessages` |
|
||
| `useConsultationRun(...)` | `useConversationScrollAnchor` 之后、其余 `useEffect` 之前 | `send` 需要 `conversationAnchor.anchorToLatest()` |
|
||
|
||
依赖方向:session hook 先返回 `persistSession` / `updateSession` / `startNewChat` / `continueInNewChat`,由 Home 传入 consult hook。两文件 **不得、也没有** 互相 import。
|
||
|
||
`setDraft` / `setDraftTheme` / `setDraftEntrypoint` / `refreshAccount` / `openAccountDialog` / `openRectificationFromHomepage` / `openRectificationSession` 仍是 `Home` 内 **function declaration**(JS 提升),所以可以出现在 hook 调用点之上的参数列表里。这不是 React hook 顺序问题。
|
||
|
||
核对方式:对 `export default function Home` 之后的 `useRouter|useState|useRef|useEffect|useBirthTimeGuidedJourney|useConversationScrollAnchor|useSessionManagement|useConsultationRun` 计数;两 hook 文件内上述符号计数为 0。
|
||
|
||
## 任务 1–2 · 搬家
|
||
|
||
新文件(函数体与 `a6148af8` 的 `page.tsx` 逐行一致;允许差异仅缩进、`export`、经参数/返回值改道的标识符。popstate 以小刀轮为准,不回退到 `a6148af8`):
|
||
|
||
| 文件 | 行 | 内容 |
|
||
| --- | ---: | --- |
|
||
| `frontend/src/hooks/use-session-management.ts` | 443 | `updateSession` … `selectSessionModel` + 小刀修过的 `applySessionPopStateRef.current` |
|
||
| `frontend/src/hooks/use-consultation-run.ts` | 1073 | `restoreConsultationRecovery` + recovery/replay 赋值、`requestCancellation` / `confirmCancellation` / `stopResponse` / `completeConsultationInterface` / `send` / `regenerateLatestAnswer` |
|
||
|
||
函数体公证(对 `a6148af8` 去缩进后逐行):
|
||
|
||
`updateSession` 3、`persistSession` 25、`ensureSessionMessages` 24、`continueInNewChat` 7、`renameSession` 11、`deleteSession` 20、`togglePinnedSession` 10、`toggleArchivedSession` 22、`shareSession` 28、`startNewChat` 33、`startConsultationAfterRectification` 5、`selectSession` 28、`selectSessionModel` 52、`restoreConsultationRecovery` 53、`requestCancellation` 27、`confirmCancellation` 19、`stopResponse` 129、`completeConsultationInterface` 9、`send` 567、`regenerateLatestAnswer` 23;`consultationRecoveryCheck.current` / `consultationReplay.current` 赋值块亦相同。
|
||
|
||
改名清单:**零**(无业务标识符重命名)。
|
||
|
||
`applySessionPopStateRef` 相对 `a6148af8` 的差异即小刀轮任务 3,原样搬进 session hook。
|
||
|
||
## 合同测试路径(同一 token,只换读取面)
|
||
|
||
拼接件 `frontend/tests/home-surface.ts` / Python `_home_surface()`:第一批文件 + 可选 `use-session-management.ts` / `use-consultation-run.ts`(`existsSync` / `Path.exists()`)。hook 落地后自动拼进。
|
||
|
||
| 测试 | 原路径 | 新路径 |
|
||
| --- | --- | --- |
|
||
| `sidebar-contract.test.ts`「keeps the page session selection callback…」 | `page.tsx` 正则 `selectSession` | `homeSurface`(函数已搬走;断言内容未改) |
|
||
| `consultation-birth-time-mode.test.ts` homepage `consultationMode:` | `page.tsx` | `homeSurface`(token 在 `send`) |
|
||
| `tests/test_supabase_user_data_contract.py` | 已是 `_home_surface()` | 可选 hook 列入清单(`persistSession(completedSession)` 等在 consult hook) |
|
||
| `rectification-agentic-entry.test.ts` refreshAccount 切片 | `function updateSession` 作终点 | `function openAccountDialog`(`updateSession` 已搬走;若终点落在拼接后的 hook 文件,切片会吞进仍留在 page 的 `persistProfile`/`setProfileDraft`。断言内容未改) |
|
||
| `profile-state.test.ts` 同一切片 | 同上 | 同上(该文件仍只读 `page.tsx`,终点改为仍相邻的 `openAccountDialog`) |
|
||
| `chat-stream-layout.test.ts` 活动/ndjson/`regenerateLatestAnswer` | `page.tsx` | `homeSurface`(token 在 `send` / consult hook) |
|
||
| `evidence-audit-panel.test.ts` workflow receipt headers | `page.tsx` | `homeSurface` |
|
||
| `chat-notice-and-scroll-contract.test.ts` send 切片 | `page.tsx` 的 `updateSession(sessionId, () => userSession)` | `homeSurface`(仅该条;JSX/poll 仍读 `page.tsx`) |
|
||
|
||
其余已走 `homeSurface` 的聊天主链路测试(`chat-session-url` / `consultation-recovery` / `consultation-entrypoint` / `composer-isolation` / `chat-session-authority` 等)无需再改:切片标记仍在 hook 文件内且相邻。
|
||
|
||
锁 JSX/仍留在 `page.tsx` 的测试继续只读 `page.tsx`(`site-style-isolation`、`rectification-lazy-loading`、`chat-notice-and-scroll` 的 poll timer、profile/onboarding 等)。
|
||
|
||
小刀轮两个 Python 文件保持全绿(见小刀 PROGRESS)。
|
||
|
||
## 验证
|
||
|
||
- `./node_modules/.bin/tsc --noEmit`:通过。
|
||
- 聚焦合同:`consultation-recovery` / `chat-session-*` / `composer-isolation` / `consultation-entrypoint` / `starter-questions` / `chat-navigation-a11y` / `settings-mvp` / `sidebar-contract` / `consultation-birth-time-mode` / `rectification-agentic-entry` / `profile-state` 全绿。
|
||
- 全量 `npm test`:2428 pass / 0 fail / 0 skipped。
|
||
- `./node_modules/.bin/next build --webpack`:`┌ ○ /` Static。首屏 JS gzip-9 501104 B vs 第一批后 501315 B(−211 B / −0.04%)。
|
||
- 行为抽查:无登录态浏览器点选会话 / 发送咨询。由合同测试覆盖抽取正确性;popstate 行为变化已在小刀轮用源码合同锁住。
|
||
|
||
未改 `.gitea/workflows/**`。未动数据库。未提升 `main`。未重开 React Compiler。
|
||
|
||
## 明确未做
|
||
|
||
- onboarding/profile 簇与 rectification 胶水(第三批)。
|
||
- 未把 streaming/pending 等 state 下移进 hook(为保住 hook 顺序)。
|
||
- 未修任何行为(popstate 已在小刀轮完成)。
|