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>
8.0 KiB
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 已在小刀轮完成)。