docs(tasks): 状态下沉第二批进度记录与真人走查清单
进度记录含三簇 20 个状态的分类表(每个写明谁在读)、改前改后实测、 既有断言改动的三栏说明、反向验证、lint warning 逐条比对、以及 session 簇留 4 个在外壳的让步说明。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
This commit is contained in:
co-authored by
Claude Opus 5
parent
bdb81c8c86
commit
7ae6df3ebb
@@ -0,0 +1,219 @@
|
||||
# PROGRESS · 状态下沉第二批:synastry / profile / session 三簇 + 稳住外壳 setter(2026-09-16)
|
||||
|
||||
工作树:`.worktrees/home-state-lowering-batch2-20260916`
|
||||
分支:`codex/home-state-lowering-batch2-20260916`
|
||||
任务书基线写 `4f643aa0`;开工时 `origin/staging` = **`37e6c519`**(`docs(tasks): 校正状态板 + 状态下沉第二批 + 后门单 + 对账清单`)。
|
||||
本机 Linux,有 Node 与 npm,无 Docker、无登录态、无 Chrome。本单不占 BUG 号,不改 `CHANGELOG.md` / `docs/BUG_HISTORY.md` / `docs/tasks/README.md`。未 `git push origin HEAD:staging`。未改数据库、API 路由、`frontend/DESIGN.md`(无可见样式变化)、`frontend/docs/VOICE.md`(无文案变化)。
|
||||
|
||||
## 开工实测(不要抄任务书 52/39/1931)
|
||||
|
||||
数法与合同测试相同:`Home()` 切片上 `\buseState[<(]` / `\buseRef[<(]`。
|
||||
|
||||
| 指标 | 实测 |
|
||||
| ---: | ---: |
|
||||
| `Home()` `useState` | **52** |
|
||||
| `Home()` `useRef` | **39** |
|
||||
| `page.tsx` 行数 | **1931** |
|
||||
| `npm run lint` | **0 error / 120 warning** |
|
||||
| `useSessionManagement` 参数(type 字段) | **41**(任务书约 40) |
|
||||
| `useProfileOnboarding` 参数(type 字段) | **38** |
|
||||
| 全量 `npx tsx --test tests/*.test.ts tests/*.test.tsx` | **3329** tests / 3283 pass / **31 fail** / 15 skipped |
|
||||
|
||||
31 条失败全是环境缺口(无 Docker、无本地 PostgreSQL、无外网),清单见文末,与收尾实测逐条相同。
|
||||
|
||||
## 分类表(20 个,一个不漏)
|
||||
|
||||
「谁在读」按改前的 `page.tsx` 实测,不是记忆。
|
||||
|
||||
### synastry 簇(4 个,全部搬下去)
|
||||
|
||||
| 状态 | 类别 | 谁在读 | 处置 |
|
||||
| --- | --- | --- | --- |
|
||||
| `synastryRelationshipType` | 只服务子树 | 只有 `ChartLibraryPanel`(关系类型下拉) | 搬进新建 `useSynastry` |
|
||||
| `synastryPendingId` | 只服务子树 | `ChartLibraryPanel`(按钮 disabled / 「正在计算合盘...」)、`draftSynastryQuestionFromChart` 自己的重入闸 | 搬进 `useSynastry` |
|
||||
| `synastryReportCard` | 只服务子树 | `ChartLibraryPanel`(本次合盘卡) | 搬进 `useSynastry` |
|
||||
| `synastryHistory` | 只服务子树,但**外壳的星盘库 effect 要写** | 值只给 `ChartLibraryPanel`;写在 `Home` 的星盘库 effect 里(切账号清空、云端历史落库)与合盘成功后 | 状态搬进 `useSynastry`;外壳只拿到两个 `useCallback` 稳定句柄 `clearSynastryHistory` / `applyCloudSynastryHistory`,并列进该 effect 的 deps(身份恒定,不多跑一次) |
|
||||
|
||||
`draftSynastryQuestionFromChart` 整个搬进 hook;`Home` 里 `const [synastry` 出现次数 = **0**。六个子树字段打成一个 `synastryPanel`,JSX 里 `{...synastryPanel}` 展开。
|
||||
|
||||
### profile 簇(6 个,全部搬下去)
|
||||
|
||||
| 状态 | 类别 | 谁在读 | 处置 |
|
||||
| --- | --- | --- | --- |
|
||||
| `profile` | 外壳也要读 | `Home`:`profileComplete`、`birthTimeRoute`、`dailyStarlanguage*`、`rectificationDeclaredTime`、侧栏头像首字母、`ProfilePanel` / `ChartLibraryPanel`;`useSessionManagement`、`useRectificationSurface`、`useSynastry`、`useConsultationRun` 的入参 | 状态搬进 `useProfileOnboarding`,`Home` 从它的 return 读回;hook 调用上移到 `useBirthTimeGuidedJourney` 之前,保证第一个读者之前已定义 |
|
||||
| `profileDraft` | 外壳也要读 | `Home`:引导三步的题面与提交按钮、`BirthTimeIntakeFields` / `BirthLocationFields`、`ChartLibraryPanel` | 同上 |
|
||||
| `profileNotice` | 外壳也要读(只透传) | `ChartLibraryPanel`;`setProfileNotice` 还要交给 `useRectificationSurface` 与 `useConsultationRun` | 状态在 hook;`Home` 只读回值与 setter |
|
||||
| `avatarNotice` | 只服务子树 | 只有 `ProfilePanel` | 搬进 hook,只读回值 |
|
||||
| `avatarSaving` | 只服务子树 | 只有 `ProfilePanel`;写只在 hook 的 `persistAvatar` | 搬进 hook,只读回值 |
|
||||
| `profileSaving` | 外壳也要读 | `Home`:引导三步按钮 disabled / 「保存中」、重新评估按钮、`ChatComposer` 的 `inputDisabled` / `submitBlocked`、`ChartLibraryPanel` | 搬进 hook,只读回值 |
|
||||
|
||||
`Home` 里散装 `profile*` state = **0**(低于任务书 ≤1)。
|
||||
|
||||
### session 簇(10 个,搬走 6 个,留 4 个)
|
||||
|
||||
| 状态 | 类别 | 谁在读 | 处置 |
|
||||
| --- | --- | --- | --- |
|
||||
| `sessionsCursor` | 外壳也要读 | `Home` 侧栏 `hasMore`;写在 hook 的 `loadMoreSessions` **和**外壳的 bootstrap effect | 搬进 `useSessionManagement`,值与 setter 都读回 |
|
||||
| `showArchivedSessions` | 外壳也要读 | `Home`:`visibleSessions` 过滤、侧栏 `showingArchived`;写在 hook 的 `toggleArchivedView` | 搬进 hook;`visibleSessions` 一并搬进 hook 计算后返回 |
|
||||
| `sessionMenuId` | 只服务子树 | 只有侧栏(`menuSessionId` / `onMenuSessionChange` / 切归档视图时清菜单) | 搬进 hook,值与 setter 读回 |
|
||||
| `pendingSessionDeletion` | 只服务子树 | 只有删除确认弹窗 | 搬进 hook,值与 setter 读回 |
|
||||
| `sessionDetailLoadingId` | 只服务 hook 自己 | `Home` 里**没有任何读者**(改前 lint 就报 `assigned a value but never used`);只有 hook 的 `ensureSessionMessages` 读写 | 整个搬进 hook,不返回 |
|
||||
| `sessionFullPrompt` | 只服务 hook 自己 | `Home` 里没有读者;写在 `useSessionManagement` 与 `useConsultationRun` | 状态搬进 hook,只把 setter 返回给 `useConsultationRun` 用 |
|
||||
| `sessions` | **外壳也要读** | `Home`:`activeSession`、bootstrap effect 首屏落库、侧栏统计与三处 `sessions.find`;`sessionsRef` 初值;`useSessionManagement` / `useRectificationSurface` / `useConsultationRun` 的入参与 `setSessions` | **留在外壳**:它是三个 hook 的共同输入,且 bootstrap effect 在任何 hook 之外写它 |
|
||||
| `activeSessionId` | **外壳也要读** | `Home`:`activeSession`、`visibleSessions` 的「当前会话不过滤」分支、`activeSessionIdRef` 同步 effect、`ensureSessionMessages` effect、`birthTimeRoute`、`useConsultationRun` 入参、侧栏 | **留在外壳**:两个 effect 的依赖项,且 `useRectificationSurface` 也写它 |
|
||||
| `creatingSession` | **外壳也要读** | `Home`:`productEntrypointsDisabled`、自动打开校正面的守卫、新对话按钮 disabled、`StarterHome`、底部按钮;`useSessionManagement` 与 `useRectificationSurface` 双方入参 | **留在外壳**:两个 hook 都要读,谁持有都得回传给另一个 |
|
||||
| `pendingSessionId` | **外壳也要读** | `Home`:`isLoading`、`productEntrypointsDisabled`、`ensureSessionMessages` effect deps、两条恢复 effect、`chooseSuggestedQuestion` 闸、`ChatComposer` `submitBlocked`、侧栏 | **留在外壳**:它其实属于「咨询运行」簇(`useConsultationRun`),不是会话管理簇;本单不动咨询簇 |
|
||||
|
||||
`Home` 里散装 `session*` state = **4**(任务书要 ≤2,见下「让步说明」)。
|
||||
|
||||
## 收尾实测
|
||||
|
||||
| 指标 | 改前 | 改后 |
|
||||
| ---: | ---: | ---: |
|
||||
| `Home()` `useState` | 52 | **36**(= 任务书 ≤36 的上限;搬走 16) |
|
||||
| `Home()` `useRef` | 39 | **37**(未上升,反而少 2) |
|
||||
| `page.tsx` 行数 | 1931 | **1846** |
|
||||
| `npm run lint` | 0 error / **120** warning | 0 error / **118** warning |
|
||||
| `useSessionManagement` 参数 | 41 | **34** |
|
||||
| `useProfileOnboarding` 参数 | 38 | **26** |
|
||||
| 散装 `synastry*` / `profile*` / `session*` state | 4 / 6 / 10 | **0 / 0 / 4** |
|
||||
|
||||
新建 `frontend/src/hooks/use-synastry.ts`(141 行)。`use-profile-onboarding.ts` 与 `use-session-management.ts` 现在各自 `useState`。
|
||||
|
||||
## 5.1:为什么 warning 只能靠 `useMemo`,不能靠塞 deps
|
||||
|
||||
`createRectificationShellSetters(setRectification)` 改前在 render 体里裸调,每帧返回四个新函数身份,入口摘要 effect 因此多一条 `exhaustive-deps`(119 → 120)。
|
||||
|
||||
改法:`const {...} = useMemo(() => createRectificationShellSetters(setRectification), [setRectification]);`,再把 `setRectificationEntrySummary` / `setRectificationEntrySummarySettled` 补进该 effect 的 deps。`setRectification` 是 React 的 `useState` setter,身份恒定,所以四个包装函数跨帧稳定。
|
||||
|
||||
**不是靠肉眼**的证明:入口摘要 effect 的 deps 现在是 `[accountId, bootstrapPhase, setRectificationEntrySummary, setRectificationEntrySummarySettled]`。后两项由 `useMemo(..., [setRectification])` 产出,而 `setRectification` 来自 `useState`,React 保证同一 mount 内引用不变 → `useMemo` 不重算 → 两个 setter 引用不变 → effect 仍然只在 `accountId` / `bootstrapPhase` 变化时触发。若有人删掉 `useMemo`,eslint 会立刻把这条 warning 报回来(这正是改前的状态),所以这条不变量由 lint 数字守着:**warning 必须是 118,回到 119/120 就是有人把记忆化拆了**。
|
||||
|
||||
## 既有断言改动
|
||||
|
||||
| 文件 | 原值 | 新值 | 原因 |
|
||||
| --- | --- | --- | --- |
|
||||
| `tests/home-shell-growth-contract.test.ts` 三门 | `HOME_USE_STATE_CAP = 52` / `HOME_USE_REF_CAP = 39` / `PAGE_LINE_COUNT_BASELINE = 1931` | **36** / **37** / **1846** | 本单下沉后的新基线,三项全部往紧里收 |
|
||||
| `tests/chat-navigation-a11y-contract.test.ts` 的 `signOut` 切片终点 | `sourceBetween(pageSource, "async function signOut()", "return {\n refreshAccount,")` | 终点改成 `"return {\n profile,"` | 六个 profile 状态搬进 `use-profile-onboarding.ts` 后,该 hook 的 return 以 `profile` 开头,`refreshAccount` 不再是第一个字段。**切的仍是 `signOut` 函数体,断言主语(退出后 `window.location.assign("/login")` 硬跳)一字未改** |
|
||||
|
||||
没有其它既有断言被改动或弱化;没有删除任何断言。新增 1 条断言(见下)。
|
||||
|
||||
## 新增断言
|
||||
|
||||
`tests/home-shell-growth-contract.test.ts` 加一条 `useSessionManagement still runs before useRectificationSurface`:钉住 `rectificationSessionOpenerRef` 先声明、`useSessionManagement` 调用在 `useRectificationSurface` 之前、并且 opener 是通过 `openRectificationSession: (exactSessionId) => rectificationSessionOpenerRef.current(exactSessionId)` 递过去的。这是第一批留下的调用顺序约束,本单挪动了 hook 调用位置,所以补钉。
|
||||
|
||||
## 反向验证(增长冻结真的会变红)
|
||||
|
||||
在 `Home()` 里插入 `const [_growthContractProbe, _setGrowthContractProbe] = useState(false);` 后:
|
||||
|
||||
```
|
||||
not ok 1 - Home() useState count must not grow
|
||||
error: 'Home() has 37 useState calls; cap is 36. Extracted hooks and child components should own their own state.'
|
||||
# tests 4 / pass 3 / fail 1
|
||||
```
|
||||
|
||||
`useRef`、行数粗护栏、调用顺序三条仍绿。随后删掉探针,文件回到 36/37/1846,四条全绿。
|
||||
|
||||
## lint warning 逐条比对(不只是比数字)
|
||||
|
||||
按「规则 + 归一化信息」排序后 diff,`120 → 118`,**没有任何新增 warning**,三处差异:
|
||||
|
||||
1. 少一条(本单目标):`React Hook useEffect has missing dependencies: 'setRectificationEntrySummary' and 'setRectificationEntrySummarySettled'` —— 5.1 消掉的就是它。
|
||||
2. 少一条(本单副产物):`'_result' is defined but never used` —— 来自被删掉的 `guidedBirthTimeReadyRef = useRef((_result: JourneyClientResponse) => {})`,见下节。
|
||||
3. 有一条既有 warning 的**信息变长**(规则、文件、effect 位置都没变):
|
||||
- 原文:`React Hook useEffect has a missing dependency: 'restoreConsultationRecovery'.`
|
||||
- 现文:`React Hook useEffect has missing dependencies: 'restoreConsultationRecovery', 'setProfile', 'setProfileDraft', and 'setSessionsCursor'.`
|
||||
- 原因:那是 mount-once 的 bootstrap effect,它写 `setProfile` / `setProfileDraft` / `setSessionsCursor`,改前这三个是 `Home` 自己的 `useState` setter(eslint 认得,不要求进 deps),改后来自 hook 的 return(eslint 认不出是 setter)。
|
||||
- **为什么不把它们补进 deps**:试过,`}, [setProfile, setProfileDraft]);` 会打红两条既有断言——`tests/consultation-recovery.test.ts` 用 `source.indexOf("}, []);")` 当切片路标,`tests/chat-navigation-a11y-contract.test.ts` 直接断言 `\n \}, \[\]\);`。这两条断言的主语是「bootstrap 是 mount-once」,为了一条 warning 的措辞去改它们不划算,**所以选择不动业务代码、不动既有断言,只如实记录这处措辞漂移**。三个都是 `useState` setter 原样透传,身份恒定,effect 仍然只跑一次。
|
||||
|
||||
## 为什么删掉了两个 ref(`useRef` 39 → 37)
|
||||
|
||||
把 `useProfileOnboarding` 改成持有状态后,React 编译器的 lint 规则才开始检查这个文件(改前它一个 hook 都不调),一次性冒出 **5 条 error**(§7.1 要求 0 error):
|
||||
|
||||
| error | 位置 | 处置 |
|
||||
| --- | --- | --- |
|
||||
| `react-hooks/immutability` ×3 | `dialogReturnTarget.current = ...`、`birthTimeRevisionPending.current = ...` ×2 | 按规则自带的 hint 把两个 ref 入参改名为 `dialogReturnTargetRef` / `birthTimeRevisionPendingRef`(纯改名) |
|
||||
| `react-hooks/refs` ×2 | render 期写 `guidedBirthTimeReadyRef.current = completeGuidedBirthTime` / `editDeclaredBirthTimeDetailsRef.current = editDeclaredBirthTimeDetails` | 删掉这两个 ref |
|
||||
|
||||
这两个 ref 当初存在,只是因为 `useProfileOnboarding` 排在 `useBirthTimeGuidedJourney` **之后**、函数还没定义就要交给它。本单把 profile hook 上移之后,`completeGuidedBirthTime` / `editDeclaredBirthTimeDetails` 可以直接传:
|
||||
|
||||
```
|
||||
onReady: (result) => completeGuidedBirthTime(result),
|
||||
onEditBirthTimeDetails: () => editDeclaredBirthTimeDetails(),
|
||||
```
|
||||
|
||||
**零行为变化的依据**:`useBirthTimeGuidedJourney` 只在 render 作用域的闭包里用这两个回调(`acknowledgeReady` 里 `onReady(journey)`,`editBirthTimeDetails: onEditBirthTimeDetails` 直接原样返回),没有把它们塞进 `useEffect`/`useRef` 里缓存。改前每帧读 `.current`、改后每帧拿当帧的函数,调用到的都是同一个「最新一帧的实现」。(已核对 `src/hooks/use-birth-time-guided-journey.ts` 全文,`onReady` / `onEditBirthTimeDetails` 只出现在第 183、255 行。)
|
||||
|
||||
`useRef` 数从 39 降到 37,没有把任何 state 改写成 ref。
|
||||
|
||||
## 让步说明(任务书 §6)
|
||||
|
||||
1. 5.1、5.2、5.3 全做,无让步。
|
||||
2. 5.4 **部分让步**:任务书要求 `Home()` 里散装 `session*` state ≤ 2,实际留了 **4**(`sessions` / `activeSessionId` / `creatingSession` / `pendingSessionId`),理由在分类表里逐个写明——前三个是三个 hook 的共同输入或 bootstrap effect 在 hook 之外的写入点,第四个属于咨询运行簇而不是会话管理簇。把它们合成一个外壳对象是可行的,但要同时改 `useConsultationRun`、`useRectificationSurface`、bootstrap effect 三处写入口,属于「咨询簇」的活,不是本单的零行为变化预算,**留给下一批**。
|
||||
3. 5.5 的收口指标**没有放松**:`useState` 36 正好等于任务书上限 ≤36,`useRef` 37 低于上限 39,行数基线 1846 低于 1931,三条冻结门全部往紧里收。
|
||||
4. 5.5、5.6 未砍。
|
||||
|
||||
## 验收命令与结果
|
||||
|
||||
| 命令 | 结果 |
|
||||
| --- | --- |
|
||||
| `./node_modules/.bin/tsc --noEmit` | **0 错** |
|
||||
| `npm run lint` | **0 error / 118 warning**(基线 120;逐条比对见上节) |
|
||||
| `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 tests/*.test.tsx` | 见下 |
|
||||
| `npm run build` | 见下 |
|
||||
|
||||
(定向、全量、build 的实测数字见文件末尾「收尾复核」一节。)
|
||||
|
||||
## 真人走查
|
||||
|
||||
`docs/testing/home-state-lowering-batch2-20260916.md`:合盘三条路径、资料引导与头像、会话列表七件事。本仓无登录态、无 Chrome,未测。
|
||||
|
||||
## 发现的交互缺陷(本单不修)
|
||||
|
||||
无新增。`sessionDetailLoadingId` 与 `sessionFullPrompt` 这两条 state 在 `Home()` 里**从来没有读者**(改前 lint 已经在报 `assigned a value but never used`,改后同样两条 warning 原封不动出现在 `use-session-management.ts`)。`sessionFullPrompt` 由 `useConsultationRun` 写入却无人渲染,像是没接完的功能;本单零行为变化,只记录不动。
|
||||
|
||||
## 未做(范围外)
|
||||
|
||||
- 其余分散状态(`account*`、`birthTime*`、`consultation*`、`streaming*` 等)
|
||||
- 咨询运行簇(`pendingSessionId` / `pendingRequestId` / `streamingReply` / `replyOutcome` …)
|
||||
- Context Provider 或外部 store
|
||||
- 推 `origin HEAD:staging`
|
||||
- 改 `docs/tasks/README.md` 状态列
|
||||
|
||||
---
|
||||
|
||||
## 收尾复核(最终工作树,四条验收命令的实测)
|
||||
|
||||
| 命令 | 实测 |
|
||||
| --- | --- |
|
||||
| `./node_modules/.bin/tsc --noEmit` | **0 错** |
|
||||
| `npm run lint` | **0 error / 118 warning**(基线 120) |
|
||||
| `npx tsx --test tests/home-shell-growth-contract.test.ts tests/rectification-*.test.ts tests/chat-session-*.test.ts tests/session-*.test.ts` | **1369** tests / 1360 pass / **0 fail** / 9 skipped |
|
||||
| `npx tsx --test tests/*.test.ts tests/*.test.tsx` | **3330** tests / 3284 pass / **31 fail** / 15 skipped |
|
||||
| `npm run build` | 退出码 0,`/` 仍是 **`○ Static`** |
|
||||
|
||||
测试总数 3329 → **3330**(只增不减,多的那条是新增的调用顺序断言)。全量失败清单与开工基线 **31 条逐条相同**(`diff` 空)。
|
||||
|
||||
## 全量失败清单(与开工基线逐条相同,全是环境缺口)
|
||||
|
||||
无 Docker、无本地 PostgreSQL、无外网,因此 database / staging-sync / backup / 远端 oracle 这几类整组红。
|
||||
|
||||
`Better Auth supports shared user OTP/password sessions for admins`;`Owner recovery grants only one currently loginable synced identity admin`;`admin code functions reject immutable codes, revoked redemption, and roll back on audit failure`;`admin customer reset clears only rebuildable application state`;`billing order adjustments and redemption reasons are atomic and audited`;`billing, subscriptions, usage authorization, RBAC, and model publication remain transactional`;`cached appendix returns markdown without model or engine calls`;`database drops secret refs, generates immutable codes, and invalidates evidence after key rotation`;`database env validator rejects Compose-compatible duplicate selectors without printing values`;`database roles have no cluster privileges`;`ephemeris natal and transit chart requests skip VedAstro overview`;`ephemeris_events failure degrades instead of failing the page`;`foundation grants no direct runtime table DML and exposes only reviewed functions`;`live staging sync preserves env, state, incoming files, and encrypted backups`;`live staging sync repairs nested deploy-tree drift without preserving foreign ownership`;`local PostgreSQL applies the reviewed business schema and serves authenticated business calls`;`migration runner is serialized, atomic, drift-safe, and read-only in check mode`;`missing appendix is a retired report, not an on-demand generation`;`panchanga still returns without a birth profile, and natal-relative stays empty`;`personal report job migration is atomic, lease-bound, recoverable, and owner read-only`;`personal report sections enforce owner-read RLS and service-owned durable transitions`;`rectification agent maps setup failures without calling the rest of the handler`;`rectification case refresh maps setup failures without calling the rest of the handler`;`redeem security: case-sensitive hashing, rate limiting, idempotency and order ownership`;`saving a published product forks a draft and delete retires or removes it`;`self-hosted identity migration creates Better Auth tables with least privilege`;`server compose defaults to local images without removing either build`;`service and restricted admin database identities stay separated`;`staging backups are encrypted, atomic, private, and retain the newest three`;`staging env validator rejects selector drift, duplicates, and unsafe permissions`;`unauthenticated ephemeris requests are 401`。
|
||||
|
||||
不是本单回归。
|
||||
|
||||
## `next build` 与首屏 gzip
|
||||
|
||||
Linux 上 Turbopack 构建正常,两次都退出 0:
|
||||
|
||||
- 改后:路由表里 `┌ ○ /`,即 `/` 仍是 **Static**。
|
||||
- 基线对照:把 `frontend/src` 与 `frontend/tests` 临时 `git checkout origin/staging --` 回去重建了一次,路由表同样是 `┌ ○ /`,随后 `git checkout HEAD --` 还原。
|
||||
|
||||
首屏 gzip 量法(与第一批同一种):读 `.next/server/app/index.html`,取它引用的全部 `/_next/static/**.js`,逐个 `gzip -9` 后求和。
|
||||
|
||||
| | 文件数 | gzip 合计 |
|
||||
| --- | ---: | ---: |
|
||||
| 基线 `origin/staging` @ `37e6c519` | 20 | **585,817 B** |
|
||||
| 本单 | 20 | **585,868 B** |
|
||||
|
||||
变化 **+0.009 %**,远在 ±2 % 内(+51 B)。
|
||||
@@ -0,0 +1,171 @@
|
||||
# 真机清单 · 状态下沉第二批(synastry / profile / session)行为是否与改前一致(2026-09-16)
|
||||
|
||||
对应 `TASK-home-state-lowering-batch2-20260916`。自动化锁的是状态住处、hook 调用顺序与源码合同,**锁不住**真实的合盘请求、头像上传、会话列表翻页与归档。本仓没有浏览器与登录态,下面这些必须用已登录的 staging 做。
|
||||
|
||||
地址:`https://staging.jyotisha.chat`。截图不要带真实姓名、出生资料、邮箱或会话内容。本单**零文案、零交互改动**;任何和改前不一样的地方都记为失败。
|
||||
|
||||
先看 `GET /api/health` 的 `deployment.gitCommit` 是否等于本单合入 staging 后的 SHA。不一致 = 先别测。
|
||||
|
||||
---
|
||||
|
||||
## A. 合盘(synastry 簇:4 个状态搬进 `useSynastry`)
|
||||
|
||||
### A1. 发起一次合盘
|
||||
|
||||
1. 设置 → 星盘库,选一条「其他人」的盘。
|
||||
2. 关系类型下拉切到「商业合作」,再切回「恋爱」。
|
||||
3. 点「用于合盘」。
|
||||
|
||||
- ✅ 下拉每次切换都立刻生效,不回弹默认值。
|
||||
- ✅ 点下后按钮变「正在计算合盘...」,**这一条盘的按钮**禁用;期间其他盘的「用于合盘」也点不动。
|
||||
- ✅ 底部提示「正在计算基础合盘证据,请稍候。」
|
||||
- ✅ 算完后弹窗关闭,输入框里出现合盘问题草稿 + 证据摘要;恋爱走 `Ashtakoot x/y`,商业走「已完成基础商业合作证据筛查」。
|
||||
- ❌ 按钮卡在「正在计算合盘...」不恢复;或算完弹窗没关;或摘要跑到错误的关系类型文案。
|
||||
|
||||
### A2. 合盘失败的三种提示
|
||||
|
||||
分别制造:对方盘没存云端(404)、连点多次触发限流(429)、后端不可用。
|
||||
|
||||
- ✅ 404 →「请先把对方星盘保存到云端,再用于合盘。」
|
||||
- ✅ 429 →「合盘请求过于频繁,请稍后再试。」
|
||||
- ✅ 其它失败 →「合盘计算暂时不可用,已先生成问题草稿。」且草稿仍然写进输入框。
|
||||
- ✅ 任何一种失败后按钮都恢复可点。
|
||||
|
||||
### A3. 合盘历史
|
||||
|
||||
1. 成功合盘一次后重开星盘库。
|
||||
2. 退出登录、换一个账号登录,再开星盘库。
|
||||
|
||||
- ✅ 刚才那张合盘卡在历史里,最新的排最前,最多 10 条。
|
||||
- ✅ 云端历史读不出时出现「合盘历史暂时无法读取,请重试。」且**带一个「重试」按钮**,点它会重新拉。
|
||||
- ✅ 换账号后历史清空,不残留上一个账号的合盘卡。
|
||||
- ❌ 历史越积越多超过 10 条;或换账号后还看得见旧账号的对方姓名。
|
||||
|
||||
---
|
||||
|
||||
## B. 资料与头像(profile 簇:6 个状态搬进 `useProfileOnboarding`)
|
||||
|
||||
### B1. 首次引导三步
|
||||
|
||||
用一个资料未填完的账号:
|
||||
|
||||
1. 输名字 → 继续。
|
||||
2. 填出生日期时间 → 继续。
|
||||
3. 选出生地 → 确定。
|
||||
|
||||
- ✅ 每一步提交时按钮变「保存中」并禁用,完成后回到可点。
|
||||
- ✅ 三段对话气泡按原来的顺序逐字出现,文案一字不变。
|
||||
- ✅ 出生时间没填全时「继续」按钮保持禁用;出生地没选时「确定」保持禁用。
|
||||
- ✅ 三步走完落到正常首页,没有多出一次加载动画或空屏。
|
||||
- ❌ 按钮一直「保存中」;或某一步保存后气泡重排/重播。
|
||||
|
||||
### B2. 改出生资料
|
||||
|
||||
1. 完整资料下进设置 → 星盘库 → 本人盘,改一个字段并保存。
|
||||
|
||||
- ✅ 保存中按钮禁用并显示「保存中」。
|
||||
- ✅ 保存成功后弹窗里的提示文案与改前一致。
|
||||
- ✅ 关掉弹窗回首页,首页与侧栏的姓名/首字母跟着更新。
|
||||
- ✅ 若改的是出生时间声明,随后触发的评估流程与改前一致。
|
||||
|
||||
### B3. 头像
|
||||
|
||||
1. 设置 → 个人资料 → 换头像。
|
||||
|
||||
- ✅ 上传中按钮禁用,成功/失败提示文案与改前一致。
|
||||
- ✅ 失败后提示留在个人资料面,不串到星盘库面。
|
||||
- ✅ 换完后侧栏头像立即更新。
|
||||
|
||||
### B4. 退出登录
|
||||
|
||||
1. 设置 → 退出 → 确认退出。
|
||||
|
||||
- ✅ 按钮变「正在退出」并禁用,取消按钮同时禁用。
|
||||
- ✅ 最终**整页硬跳** `/login`(地址栏变化 + 页面白屏重载),不是前端路由软跳。
|
||||
- ❌ 退出后还能按浏览器后退看到上一位用户的对话。
|
||||
|
||||
---
|
||||
|
||||
## C. 会话列表(session 簇:6 个状态搬进 `useSessionManagement`)
|
||||
|
||||
### C1. 列表加载与翻页
|
||||
|
||||
1. 用一个会话很多的账号进首页。
|
||||
2. 滚到侧栏底部点「加载更多」。
|
||||
|
||||
- ✅ 首屏列表顺序与改前一致(置顶在前,按更新时间降序)。
|
||||
- ✅ 「加载更多」只在**确实还有下一页**时出现;加载完最后一页后它消失。
|
||||
- ✅ 连点两次不会重复插入同一批会话。
|
||||
- ❌ 加载更多后列表顺序乱了,或出现重复标题。
|
||||
|
||||
### C2. 归档视图
|
||||
|
||||
1. 点侧栏「查看已归档」,再点回来。
|
||||
|
||||
- ✅ 两个视图的会话集合互不重叠:归档视图只显示已归档,正常视图只显示未归档。
|
||||
- ✅ 当前正在看的会话即使被过滤条件排除,也仍然留在列表里(原有规则)。
|
||||
- ✅ 切换视图时**会话菜单自动收起**。
|
||||
- ✅ 归档数量角标与实际归档条数一致。
|
||||
|
||||
### C3. 会话菜单
|
||||
|
||||
1. 在一条会话上点「⋯」。
|
||||
|
||||
- ✅ 同一时刻只有一个菜单展开;点另一条会自动关掉前一个。
|
||||
- ✅ 生成回答中(有进行中的咨询)时菜单项按原规则禁用。
|
||||
|
||||
### C4. 重命名 / 置顶 / 归档 / 分享
|
||||
|
||||
逐个操作一遍。
|
||||
|
||||
- ✅ 每项的提示文案、成功后的列表位置变化与改前一致。
|
||||
- ✅ 置顶后该会话上移到置顶区;取消置顶后回到时间序。
|
||||
|
||||
### C5. 删除
|
||||
|
||||
1. 菜单 → 删除。
|
||||
|
||||
- ✅ 弹出确认框,正文是「“<标题>”将被永久删除,无法恢复。」
|
||||
- ✅ 点遮罩、点「取消」都能关掉且**不删**。
|
||||
- ✅ 确认后会话消失,若删的是当前会话则自动落到列表里的另一条。
|
||||
- ❌ 确认框标题串成了别的会话。
|
||||
|
||||
### C6. 切换会话与消息加载
|
||||
|
||||
1. 点一条从没打开过的历史会话。
|
||||
|
||||
- ✅ 正文一次揭开,不出现转圈/骨架/「正在加载」。
|
||||
- ✅ 地址栏 `?s=` 跟着变;浏览器前进后退能在会话之间来回。
|
||||
- ✅ 快速连点多条不会把消息错插到另一条会话里。
|
||||
|
||||
### C7. 模型切换与同步失败
|
||||
|
||||
1. 在一条会话里换模型。
|
||||
2. 断网后再换一次。
|
||||
|
||||
- ✅ 换模型立即生效,刷新后仍然是新模型。
|
||||
- ✅ 同步失败时的提示与改前一致,恢复网络后重试能成功。
|
||||
|
||||
### C8. 写满后开新对话 / 继续到新对话
|
||||
|
||||
- ✅「在新对话里继续」按原样新建会话并带上上下文,左侧新增一条。
|
||||
- ✅ 新建期间「新对话」按钮禁用,不会连点出两条。
|
||||
|
||||
---
|
||||
|
||||
## D. 校正入口没被牵连(本单挪动了 hook 调用顺序)
|
||||
|
||||
1. 首页点「生时校正」卡。
|
||||
2. 从侧栏点一条历史校正会话。
|
||||
|
||||
- ✅ 首页卡在打开期间显示「正在打开」,随后一次揭开校正面。
|
||||
- ✅ 从侧栏打开历史校正仍然能进去(这条走 `rectificationSessionOpenerRef`,本单挪了 hook 顺序,必须实测)。
|
||||
- ❌ 点历史校正没反应、或落到空白普通聊天。
|
||||
|
||||
---
|
||||
|
||||
## E. 首屏观感
|
||||
|
||||
- ✅ 揭幕后不出现 spinner / 骨架 / 「正在加载」(流式生成中除外)。
|
||||
- ✅ 输入框只有一个,滚动跟随只有一套。
|
||||
- ✅ 首屏体积与改前无肉眼差别(自动化实测 gzip 变化 +0.009 %)。
|
||||
Reference in New Issue
Block a user