产品 2026-09-15 三项拍板,落成两份新单与两处既有单的修订: - 新增 TASK-freeze-metric-change-20260915(无 BUG 号,后面两单的前置): 两条冻结余量已用完(page.tsx 1951/1951 余 0;api server 11334/11363 余 29), 冻结从「逼新代码往外走」退化成拦路。实证:page.tsx 行数砍 59% 但 Home() 的 useState 从 56 涨到 66;api server 225 个类方法只有 12 处真碰 HTTP。 主门换成耦合指标,行数降为粗护栏;同时推翻 §6「参数式 hook 内部保持 0 个 React hook」——那正是状态搬不走的原因。 - 新增 TASK-home-state-lowering-20260915(无 BUG 号):先搬 rectification* 那 15 个 state 进已经是 dynamic 子树的校正面,Home() useState 66 → ≤53。 零行为变化;串行在 freeze-metric-change + C2 + R3 之后。 - 修订 TASK-consultation-external-evidence-cache-20260915:依赖反转,C1 排在 API server 拆解之前(它动模块级函数,拆解动类方法);补「不得新增类方法、 行数余量仅 29」的硬红线。 - 修订 TASK-api-server-decomposition-20260916:串行依赖加 C1 与 freeze-metric-change;__new__ 计数按 grep 的 4 计(原文 3 是文件数); 阶段 4 收尾口径改写;基线 11,314 → 11,334。 纯文档推送,不触发门禁、不发布镜像、不部署。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
8.6 KiB
TASK · 把校正面的 15 个状态从首页搬下去(page.tsx 状态下沉 · 第一簇)
- 日期:2026-09-15
- 基线 commit:
origin/staging@6b3248bf(开工时以最新origin/staging为准,见 §7) - 执行分支:
codex/home-state-lowering-20260915 - 落点:
frontend/src/app/page.tsx、frontend/src/hooks/use-rectification-surface.ts、frontend/src/components/conversational-birth-time-rectification.tsx(或新建的容器组件) - 串行依赖(三条,缺一不可):
TASK-freeze-metric-change-20260915—— 本单要让抽出去的 hook 持有 React 状态,那正是现行AGENTS.md §6禁止的;必须等新口径生效TASK-consultation-context-memory-20260915(C2)—— 它在改use-session-management.ts/use-consultation-run.ts,与本单同一片状态层TASK-rectification-settled-render-split-20260915(R3)—— 它在改rectification-agentic-chat.tsx,与本单同一片子树
- 规模:一簇状态换个住处。零行为变化、零文案变化。
1. 为什么是这一簇
page.tsx 现在 1,951 行,行数上限 1,951,余量 0。但行数不是病根:
| 指标 | BUG-249 当时 | 今天 |
|---|---|---|
| 文件行数 | 4,766 | 1,951 |
Home() 的 useState |
56 | 66 |
useEffect |
18 | 22 |
useRef |
— | 41 |
useCallback / useMemo |
0 / 0 | 0 / 0 |
行数砍掉 59%,状态反而从 56 涨到 66。 前几轮拆的是代码不是状态:抽出去的 hook 是参数式的,useSessionManagement(params) 开头要解构约 40 个参数,useRectificationSurface(params) 约 56 个——状态所有权一个都没搬,每搬一次还要新增一批传参。
66 个 state 按归属分群:
| 群 | 个数 |
|---|---|
rectification* |
15 |
session* |
10 |
profile* |
6 |
synastry* |
4 |
account* |
4 |
| 其余分散 | 27 |
最大的一簇服务的是一个已经是 dynamic() 懒加载的子树:<ConversationalBirthTimeRectification> 挂着 24 个 props,而喂它们的 15 个 state 全住在 Home() 里。边界最清楚、收益最大,所以第一簇搬它。
那 15 个是:
rectificationSessionId rectificationShouldStartOpening
rectificationCaseId rectificationTurns
rectificationHeaderSlot rectificationSnapshot
rectificationPendingQuestion rectificationOpeningSessionId
rectificationLoading rectificationEntrySummary
rectificationMutationPending rectificationEntrySummarySettled
rectificationError
rectificationErrorSessionId
rectificationReadonly
2. 根因
AGENTS.md §6 现行那句「参数式 hook 内部保持 0 个 React hook 的既定模式」把状态钉死在 Home() 里;同一节的行数冻结又不许 page.tsx 增长。两条合起来等于「不许再加状态」,而每一轮新功能都要加。TASK-freeze-metric-change-20260915 已经拿到产品授权推翻前半句,本单是第一个吃到新口径的轮次。
3. 决策记录
产品 2026-09-15 拍板:
page.tsx走「状态下沉」这条路,不引外部 store、不铺全局 Context Provider。理由:不引新依赖、可以一簇一簇增量做,每搬一簇page.tsx就真降一截。- 第一簇搬校正面的 15 个。 后续
session*/profile*/synastry*各自另开单,本单不碰。 - 抽出去的 hook 与子组件从此持有自己的状态(推翻 §6 旧红线,措辞由 freeze-metric-change 单落地)。执行方不得以「AGENTS 说参数式 hook 里不能有 React hook」为由拒改。
- 零行为变化。 本单不修任何已知交互缺陷,发现了写进进度记录。
4. 硬红线
- 不是 15 个都能搬。 有几个外壳自己要读(例如
rectificationSessionId/rectificationCaseId参与决定显示哪个界面、侧栏会话列表也要知道)。第一步必须逐个分类:「只服务子树 → 搬下去」「外壳也要读 → 留在外壳,但收敛成一个对象,不再是散装的多个useState」。分类表写进进度记录,不许含糊。 - 零行为变化是唯一成败判据:进入校正面、退出、刷新、从首页卡片打开、打开历史校正、开场自动触发、只读态、报错态、换模型、采用后回首页——逐条与改前一致。
next build后/仍须○ Static;首屏 JS gzip 变化在 ±2 % 内(上次实测 584,413 B)。- 测试总数不得低于开工时
origin/staging的实测;改任何既有断言必须写「原值 / 新值 / 原因」三栏(AGENTS §7.3)。 - 不得新写第二个聊天输入框、第二套滚动跟随、第二套加载动画(§6 第三条原样有效)。
- 不得顺手升级依赖、不得顺手修不在本单里的 warning。
- 不得改数据库、不得改任何 API 路由。 本单只动前端状态的住处。
5. 任务分解
5.1 先分类,再动手
把 15 个逐个归类并写进进度记录:
| 类别 | 处置 |
|---|---|
| 只服务校正子树 | 搬进子树(useRectificationSurface 变成真 hook,或由容器组件持有) |
| 外壳也要读 | 留在外壳,但合并成一个状态对象,散装 useState 数下降 |
- 验收:分类表在进度记录里,15 个一个不漏,每个写明依据(谁在读它)。
5.2 useRectificationSurface 变成真 hook
现在它要解构约 56 个参数。改成自己 useState / useEffect 持有第一类状态,对外只暴露子树真正需要的接口;page.tsx 侧的传参随之消失。
- 验收:
useRectificationSurface的参数个数显著下降,新值写进进度记录(改前约 56)。 - 验收:
<ConversationalBirthTimeRectification>的 props 个数下降,新值写进进度记录(改前 24)。
5.3 Home() 的状态计数必须真降
- 验收:
Home()的useState数从 66 降到 ≤ 53(搬走至少 13 个;允许留 2 个在外壳,多留必须逐个说明理由)。 - 验收:
useRef数不得上升(改前 41)——不许把 state 改写成 ref 来凑数字。 - 验收:
frontend/tests/home-shell-growth-contract.test.ts(由 freeze-metric-change 单建立)在本单收尾时更新为新基线,并贴一次反向验证。
5.4 行为等价证明
- 验收:改动前后各跑一次全量前端套件,失败清单逐条一致(无 Docker 时数据库套件照常红)。
- 验收:
npx tsx --test tests/rectification-*.test.ts全绿,断言零改动——如果必须改,按 §4.4 写三栏说明。 - 验收:
docs/testing/下留一份真人走查清单,覆盖 §4.2 那十条路径(本仓没有浏览器与登录态,这一项只能人工)。
5.5 记录
本单不产生 Bug 记录(不是缺陷,是结构改造),不进 CHANGELOG.md(无用户可感知变化)。若过程中确实改了任何可见样式,必须同提交更新 frontend/DESIGN.md(AGENTS §7.5)。
6. 让步顺序
- 5.1(分类)不得砍——没有分类表就动手,一定会把外壳要读的状态搬下去然后再搬回来。
- 5.2 + 5.3 是主体。
- 5.3 的目标值可以让步(比如只搬到 ≤ 56),但让步幅度和原因必须写进进度记录,不得静默降低。
- 5.4 不得砍。
- 5.5 不得砍。
7. 开工前置命令
git fetch origin --prune
# 三条串行依赖必须都已合入 staging,逐个确认
git log --oneline origin/staging | head -20
git worktree add -b codex/home-state-lowering-20260915 \
.worktrees/home-state-lowering-20260915 origin/staging
cd .worktrees/home-state-lowering-20260915/frontend
git status -sb | head -1
npm ci
# 取当时实测基线,不要抄本任务书里的数字(七条在飞分支合并后会变)
grep -cE '\buseState[<(]' src/app/page.tsx
grep -cE '\buseRef[<(]' src/app/page.tsx
grep -oE 'const \[rectification[A-Za-z]+' src/app/page.tsx | wc -l
验收命令:
./node_modules/.bin/tsc --noEmit
npm run lint # 0 error
npx tsx --test tests/rectification-*.test.ts tests/home-shell-growth-contract.test.ts
npx tsx --test tests/*.test.ts # 与基线逐条比对失败清单
npm run build # `/` 仍须 ○ Static,首屏 gzip ±2%
8. BUG 编号起点
本单不占 BUG 号。基线 6b3248bf 上最大号 BUG-720,721–732 已被两轮审计七单预占。
9. 不在本单范围
session*/profile*/synastry*/account*四簇(各自另开单)- Context Provider 或外部 store(§3.1 已否决这一轮走这两条路)
- 校正面自身的任何交互缺陷(本单零行为变化)
- API server 拆解(另一条线)