① 状态板校正:09-15~16 这 10 份任务书的实现早已合入 staging 并经逐单 验收,状态板却仍写「待领取 / 待验收」。改成「已验收」并补上落点 SHA 与验收要点;顺带补齐 8 个 PROGRESS 列。防复发写进「命名与归档」: 实现合入的同一次推送必须同时改状态板那一行。 ② 新增 TASK-home-state-lowering-batch2-20260916(不占 BUG 号): 第一批f8e607c2已验收(useState 66→52、useRef 41→39、散装 rectification* 归零)。本单搬剩下三簇 session*10 / profile*6 / synastry*4,目标 52→≤36;并修第一批尾巴—— createRectificationShellSetters 每帧新身份多出的那条 exhaustive-deps warning(119→120),正解是稳住 setter 身份而不是塞进 deps。 ③ 新增 TASK-api-server-backdoor-close-20260916(不占 BUG 号): 实测四处 __new__ 的传递闭包只有 8 方法 / 314 行且 0 个碰 HTTP 上下文, 占全类 4%。所以关后门不必捆绑 2,000+ 行体量搬运。产品拍板阶段 2/3 不立单,由3b17c1b2换好的门禁长期推进。原 decomposition 单标为已取代。 ④ 新增 RECONCILE-20260916:另外 7 份 09-10~14 的单没有 PROGRESS、但 引用的 BUG 号都是 resolved,无法从 README 判断,列成一页纸让执行方 回填;另附三条确定没做的证据、两条未闭环状态、BLK-001 仍红。 纯文档推送,不触发门禁、不发布镜像、不部署。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
9.3 KiB
TASK · page.tsx 状态下沉第二批:session / profile / synastry 三簇 + 稳住外壳 setter
- 日期:2026-09-16
- 基线 commit:
origin/staging@4f643aa0(开工时以最新origin/staging为准,数字全部重测) - 执行分支:
codex/home-state-lowering-batch2-20260916 - 落点:
frontend/src/app/page.tsx、frontend/src/hooks/use-session-management.ts、frontend/src/hooks/use-profile-onboarding.ts、frontend/src/hooks/use-rectification-surface.ts、以及为合盘新建的 hook - 串行在
f8e607c2(第一批)之后,已满足 - 与
TASK-consultation-residual-hotspots-20260916(纯 Python)、TASK-api-server-backdoor-close-20260916(纯 Python)无文件重叠,三单可并行 - 规模:照第一批的成方再搬三簇。零行为变化、零文案变化。
1. 第一批已经证明这条路走得通
f8e607c2 把 15 个 rectification* 状态搬走,我验收实测:
| 指标 | 第一批前 | 第一批后 |
|---|---|---|
Home() 的 useState |
66 | 52 |
Home() 的 useRef |
41 | 39 |
page.tsx 行数 |
1,951 | 1,931 |
残留散装 rectification* state |
15 | 0 |
做法是:先把 15 个逐条分类成「只服务子树」与「外壳也要读」,前者搬进 useRectificationSurface,后者合并成一个对象 state。全量前端套件两侧完全相同(3329 tests / fail 31,失败清单逐条一致),首屏 gzip 0.00 % 变化,/ 仍 ○ Static。增长冻结基线往紧里收(66/41/1951 → 52/39/1931)。
本单把同一套做法用在剩下的三簇上。
2. 事故实证:剩下的 52 个里还有 20 个是成簇的
Home() 现在 52 个 useState,按前缀分群:
| 群 | 个数 | 已经有的抽取点 |
|---|---|---|
session* |
10 | use-session-management.ts(参数式,解构约 40 个参数) |
profile* |
6 | use-profile-onboarding.ts |
synastry* |
4 | 无,散在 Home() 里 |
| 其余分散 | 32 | — |
useSessionManagement(params) 的第一件事仍然是解构约 40 个参数——第一批没有碰它。这就是「代码搬走了、状态没搬」的残留部分。
另有第一批留下的一个小尾巴(执行方已在进度记录里主动披露,判断正确):
createRectificationShellSetters(setRectification) 在 render 体里无记忆化调用,每帧返回新的函数身份,于是入口摘要那个 useEffect 多了一条 react-hooks/exhaustive-deps warning(全仓 119 → 120)。进度记录写明「不能把它们放进 deps,否则每帧重拉摘要」——这个判断是对的,我核过:那几个 setter 都只是 setRectification(prev => ...) 的包装,任何身份行为都一样,当前是安全的。但它是个陷阱:后面任何人为了消除 warning 把它们塞进 deps,就会造成每帧重新拉取入口摘要。
3. 决策记录
产品 2026-09-16 授权本单,口径与第一批一致:
- 继续走「状态下沉」,不引外部 store、不铺全局 Context Provider(第一批已否决这两条,本单不重开)。
- 抽出去的 hook 与子组件持有自己的状态——
AGENTS.md§6 已于3b17c1b2改写,「参数式 hook 内部保持 0 个 React hook」那条红线已经推翻。执行方不得以旧 AGENTS 为由拒改。 - 零行为变化。 本单不修任何已知交互缺陷,发现了写进进度记录。
- 那条 warning 要在本单里消掉,做法是稳住 setter 身份,不是把它们加进 deps。
4. 硬红线
- 先分类再动手。 三簇共 20 个状态,每一个都要归到「只服务子树 → 搬下去」或「外壳也要读 → 合并成一个对象」,分类表连同「谁在读」写进进度记录,一个不漏。第一批的分类表是格式范本。
- 零行为变化是唯一成败判据。逐条对照:会话列表加载/翻页/切换/归档/置顶/重命名/删除、模型切换与同步失败、资料引导各步、头像上传、合盘发起与历史、写满后开新对话。
next build后/仍须○ Static;首屏 gzip 变化在 ±2 % 内(第一批实测 130,872 B,同一种量法)。- 测试总数不得低于开工时
origin/staging的实测;改任何既有断言必须写「原值 / 新值 / 原因」三栏(AGENTS §7.3)。第一批那批源码正则合同(foo={bar}→foo: bar)是可接受的改法范本:主语不许变。 npm run lint的 warning 数不得上升,并且本单要把 120 降回 119(消掉 §2 那条)。- 不得新写第二个聊天输入框、第二套滚动跟随、第二套加载动画(§6 第三条原样有效)。
- 不得顺手升级依赖、不得顺手修不在本单里的 warning。
5. 任务分解
5.1 稳住外壳 setter,消掉那条 warning(先做,最小)
把 createRectificationShellSetters(setRectification) 的结果记忆化(useMemo,依赖只有 setRectification,而它是 React 的稳定 setter),使四个 setter 身份跨帧稳定;然后把它们按 eslint 的要求补进那个 useEffect 的 deps。
- 验收:
npm run lint从 120 warning 回到 119,且没有新增任何其它 warning(按规则+信息归一化比对,不要只比数字)。 - 验收:入口摘要的 effect 仍然只在
accountId/bootstrapPhase变化时触发——新增一条断言或在进度记录里给出证明,不得靠肉眼。
5.2 synastry* 四个下沉(第二小,边界最清楚)
synastryRelationshipType / synastryPendingId / synastryReportCard / synastryHistory 目前散在 Home() 里,没有对应 hook。新建一个 hook 或直接让合盘子树持有。
- 验收:
Home()里const [synastry的出现次数为 0。 - 验收:合盘发起、失败、历史列表三条路径行为不变。
5.3 profile* 六个下沉
use-profile-onboarding.ts 已存在(445 行,参数式)。改成持有自己的状态。注意资料引导与 bootstrapPhase、揭幕门耦合较深,分类时要特别小心哪些是外壳要读的。
- 验收:分类表里每个
profile*都写明谁在读。 - 验收:
Home()里散装profile*state ≤ 1(合并对象),多留必须逐个说明理由。
5.4 session* 十个下沉(最大、最后做)
use-session-management.ts 是三簇里耦合最深的(约 40 个参数,522 行),而且它和校正面有交叉(第一批的进度记录写明:useSessionManagement 必须在 surface hook 之前调用,用 rectificationSessionOpenerRef 把 opener 递过去)。这条交叉关系不得破坏。
- 验收:
useSessionManagement的参数个数显著下降,新值写进进度记录(改前约 40)。 - 验收:
Home()里散装session*state ≤ 2。 - 验收:
rectificationSessionOpenerRef那条调用顺序约束仍然成立,并有一条断言钉住。
5.5 收口指标
- 验收:
Home()的useState从 52 降到 ≤ 36(三簇共 20 个,允许留 ≤ 4 个在外壳);useRef不得上升(改前 39)。 - 验收:
frontend/tests/home-shell-growth-contract.test.ts更新为新基线(只许往紧里收),并贴一次反向验证(人为加一个useState必须变红)。 - 验收:全量前端套件失败清单与开工基线逐条一致。
5.6 真人清单
照 docs/testing/home-state-lowering-20260915.md 的格式,为本单三簇补一份可照做的走查条目(本仓无浏览器与登录态)。
6. 让步顺序
- 5.1 必须做,它是第一批留下的尾巴,最小。
- 5.2 → 5.3 → 5.4 按由易到难推进。做不完可以只交前两簇,砍掉的簇在进度记录里写明,并把 5.5 的目标值按实际达成调整(只许往紧里收,不许放松)。
- 5.5、5.6 不得砍。
7. 开工前置命令
git fetch origin --prune
git worktree add -b codex/home-state-lowering-batch2-20260916 \
.worktrees/home-state-lowering-batch2-20260916 origin/staging
cd .worktrees/home-state-lowering-batch2-20260916/frontend
git status -sb | head -1
npm ci
# 重测基线,不要抄本任务书里的数
grep -cE '\buseState[<(]' src/app/page.tsx
grep -cE '\buseRef[<(]' src/app/page.tsx
for p in session profile synastry; do printf "%s: %s\n" "$p" "$(grep -cE "const \[$p" src/app/page.tsx)"; done
npm run lint 2>&1 | tail -2
开工前必读:docs/tasks/PROGRESS-home-state-lowering-20260915.md(第一批的分类表与收尾实测,是本单的格式范本)。
验收命令:
./node_modules/.bin/tsc --noEmit
npm run lint # 0 error,warning 必须 ≤ 119
npx tsx --test tests/home-shell-growth-contract.test.ts tests/rectification-*.test.ts \
tests/chat-session-*.test.ts tests/session-*.test.ts
npx tsx --test tests/*.test.ts # 与基线逐条比对失败清单
npm run build # `/` 仍须 ○ Static
8. BUG 编号起点
本单不占 BUG 号(结构改造,不是缺陷)。基线 4f643aa0 上最大号 BUG-733;734/735 已被 TASK-consultation-residual-hotspots-20260916 预占。
9. 不在本单范围
- 其余 32 个分散状态(没有成簇,逐个搬性价比低)
- Context Provider 或外部 store
- 任何交互缺陷(本单零行为变化)
- API server 一侧(另一条线)