docs: split meeting product work into task books

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
jesse-ux
2026-09-22 11:19:35 +08:00
co-authored by Claude Code
parent 1bc6a954c5
commit 10baeb2fa8
8 changed files with 611 additions and 0 deletions
+7
View File
@@ -149,6 +149,13 @@
| `TASK-secondary-page-entry-20260918.md` | `PROGRESS-secondary-page-entry-20260918.md` | 真机反馈:星盘 / 星历 / 报告进入时抖一下——三页都是「矮的等待文案块 → 高的正文块」一次推挤,且 `use-chart-page` 无缓存所以每次进入都抖(BUG-966)。产品已拍板方案一:统一外壳 + 缓存 + 预取消灭中间态,**不加 spinner**,红线不动。另含 BUG-967:标签页跨过部署后客户端导航静默失效(BUG-965 已复现确认,刷新即恢复),要按 `NEXT_PUBLIC_GIT_COMMIT``/api/health` 比对自愈。基线 `41902067` | 待验收 | `9ccb65b7` |
| `TASK-account-dialog-inert-20260918.md` | `PROGRESS-account-dialog-inert-20260918.md` | 真机:账户弹窗打开后整个弹窗点不动、退出登录做不了,刷新依旧——`e4e73f56``SidebarInset` 搬进 layout 后 `inert={modalOpen}` 罩住了没有 portal 的 `AccountDialogOverlay`(BUG-968P0,代码级确认)。另含 BUG-969:校正「换一件事问」后无下文,服务端已出下一题且快照重算完整,客户端停在开场状态,GET 路由与客户端两端静默,本单只做可观测 + 不静默 + 题干进正文。基线 `1061514f` | 待验收 | `824ecff0` |
| `TASK-first-paint-dead-screen-fallback-20260917.md` | — | 真机:首页永远停在「正在载入账户」,兜底全在没跑起来的 bundle 里(BUG-936 investigating)。根 layout 加与 bundle 无关的内联兜底 + 去掉本仓正则后行断言 | 待领取 | — |
| `TASK-chart-profile-update-consistency-20260922.md` | `PROGRESS-chart-profile-update-consistency-20260922.md` | 更新出生资料后 `/api/account → /api/chart-view` 真值一致、adopted tuple 完整、错误分类可诊断、旧 chart snapshot 失效;串行核对既有星盘打开链路,不把 `chart_profiles``/chart` 真值 | 待领取 | — |
| `TASK-consultation-subject-binding-20260922.md` | `PROGRESS-consultation-subject-binding-20260922.md` | 普通聊天按会话绑定解析 self/owned other 的服务端人物资料;资料更新读最新,删除/越权/role 冲突 fail-closed,不 fallback 到本人;**先于人物选择器** | 待领取 | — |
| `TASK-chat-subject-picker-20260922.md` | `PROGRESS-chat-subject-picker-20260922.md` | 开始聊天选择人物;顶部“当前星盘”可访问入口;第一条消息后锁定,换人新建会话;依赖 subject binding 合入 staging | 待领取 | — |
| `TASK-report-public-content-20260922.md` | `PROGRESS-report-public-content-20260922.md` | 普通报告 canonical allowlist/projection:页面、下载、API 统一只展示用户可读正文、结论和必要限制说明;隐藏 workflow/技法/评分/raw tool/任务元数据 | 待领取 | — |
| `TASK-home-bootstrap-reliability-20260922.md` | `PROGRESS-home-bootstrap-reliability-20260922.md` | BUG-936 首屏 bundle 无关 fallback、可读失败状态和用户触发重试;复用既有 first-paint 任务,不重复添加 loading,不提前标记 resolved | 待领取 | — |
| `TASK-starter-entry-soften-20260922.md` | `PROGRESS-starter-entry-soften-20260922.md` | 弱化“今日星语 / 生时校正”入口默认边框与交互状态;保留 native button、44px 命中区、既有动作和 reduced-motion,不恢复旧卡片 | 待领取 | — |
| `TASK-icon-motion-audit-20260922.md` | `PROGRESS-icon-motion-audit-20260922.md` | P2 图标/轻量动效库存与合同审计;优先 Lucide/既有 glyph,不强行换图标、不新增 loading 或循环动画 | 待领取 | — |
| `TASK-consultation-answer-start-anchor-20260917.md` | `PROGRESS-consultation-answer-start-anchor-20260917.md` | 主会话回答落在结尾:`useConversationScrollAnchor` 是贴底跟随,流式期间视口钉在最后一个字,回答开头滚出视口;改为发送后问题钉顶、回答向下长、长出视口显示「跳到最新」、末尾动态留白;产品追加拍板:校正面同一语义(推翻 BUG-041/048 贴底),本轮开头 = 用户行或新助手行。BUG 段 930 起 | 已验收(经修复单) | `worktree/green-harbor-5be3` |
| `TASK-consultation-answer-start-anchor-fix-20260917.md` | `PROGRESS-consultation-answer-start-anchor-fix-20260917.md` | 验收修复单:F1 头就是留白行时留白按整视口算(BUG-931);F2 留白只在钉住期间存在(BUG-932);前置:先修 e4e73f56 的两处 TS 错否则门禁不过 | 已验收 | `cc1a8980`Claude 验收:tsc 0 / lint 0 error / npm test 3457 条 39 红与 11c0028d 逐条一致、新增 2 条绿 / `next build --webpack` 通过、`/` Static、首屏 gzip 591,242(较 09-16 基线 582,800 +1.45%,含会话列表单)/ Chrome 真实布局 S1–S6 全部通过,S6 新助手行距顶 16px 且增高不动,S5 不再写留白);真机六条欠 |
| — | `PROGRESS-starter-greeting-20260917.md` | 首页开场语改成 claude.ai 式单行问候:`starter-greeting.ts` 的「称呼 + 追问句」五时段十五条收成三个池子(时段 / 星期 / 回访,按 `variantSelection` 确定性取一条),副标题行与 `.starter-salutation` 下线,h1 降到 `clamp(24px, 2.8vw, 30px)`;追问移到输入框占位符「想聊什么都可以」。产品直接拍板,非 Bug,不占 BUG 号 | 待验收 | `codex/starter-greeting-20260917` |
@@ -0,0 +1,107 @@
# 任务书 · 更新出生资料后星盘真值一致性与可诊断错误(2026-09-22)
## 0. 基线与串行关系
- 基线 commit`1bc6a954c597e2817fe72bfedad93119ab19a527`(当前 `origin/staging`)。执行方开工前必须重新 `git fetch origin --prune`,再以最新 `origin/staging` 为基线。
- 执行分支:`codex/chart-profile-update-consistency-20260922`;工作树:`.worktrees/chart-profile-update-consistency-20260922`
- 本单是星盘现场测试暴露的功能修复,不是整页重设计。与既有 `TASK-chart-page-blocking-open-20260915.md``TASK-chart-vedastro-decouple-20260915.md``TASK-readonly-pages-fix-20260916.md` 有交集时必须串行:先核对这些分支是否已合入 staging;未合入时不得同时改同一符号,按“既有星盘打开链路 → 本单资料真值一致性”顺序执行。
- 本单不接入 `chart_profiles` 作为 `/chart` 的主资料来源;`/chart` 继续使用账户权威 `profiles`。人物资料库属于普通聊天的另一条 subject binding 任务。
- 与既有星盘打开链路任务严格串行:先完成并核对 `TASK-chart-page-blocking-open-20260915.md``TASK-chart-vedastro-decouple-20260915.md``TASK-readonly-pages-fix-20260916.md` 的 staging 状态,再处理本单真值/缓存;不得并行修改相同 chart-view loader、engine client 或错误映射符号。
## 1. 事故实证
会议现场反馈:用户已经更新出生时间,但打开星盘仍提示没有可显示内容,或点击后无法打开。按产品设计,填写出生资料后应能生成并查看星盘。
调查已确认的代码事实(按符号定位,不依赖脆弱行号):
- `frontend/src/app/(app)/chart/page.tsx` 只挂载客户端 chart 壳;`frontend/src/hooks/use-chart-page.ts` 通过 `refreshChartPage()` 请求 `/api/chart-view`
- `frontend/src/lib/chart-view-client.ts` 对响应做 JSON/Zod 合同解析;解析失败或请求失败会归并为通用 `chart_unavailable`
- `frontend/src/lib/secondary-page-data.ts``chartCache` 是模块级单值缓存,没有账户/profile 版本键;资料 PATCH 成功后没有明确的 chart snapshot 失效边界。
- `frontend/src/lib/chart-view-service.ts``profiles` 读取 `ACCOUNT_BIRTH_SELECT`,再经 `server-owned-birth-profile.ts` 与 timezone resolver 组装排盘资料。
- accepted/confirmed 资料存在 `active_birth_date` 但缺 `active_birth_timezone_offset` 时,`server-owned-birth-profile` / `birth-profile-timezone` 会抛出 `adopted_birth_calculation_incomplete` 或 timezone error;当前错误路径可能最终只显示通用不可用状态。
- `frontend/src/lib/account-profile-patch.ts` 的普通账户 PATCH 主要处理 active time/status,不会在所有声明变化后原子维护 adopted date/offset/provenance;迁移 `frontend/supabase/migrations/20260920020000_adopted_birth_date.sql` 的 trigger 会在特定字段变化时清理 adopted tuple。这需要实证核对,不能在任务书外擅改出生资料语义。
- `loadChartView` 对 profile 查询错误的区分不足,数据库读取失败可能被误认为资料不完整。
相关历史:BUG-005、BUG-009、BUG-018、BUG-073、BUG-076、BUG-715717、BUG-936。BUG-936 是首页 JS 死屏,不替代本单的 `/chart` 数据真值问题。
## 2. 根因
当前已确认的是“错误分类、资料更新一致性和客户端旧快照存在断点”;具体现场账号命中的单一数据库状态尚未证明。因此本单不得把某个假设写成已确认根因。优先验证并修复三层:
1. `/api/account` PATCH、数据库 trigger/adoption 与 `profiles` 中 date/time/offset/provenance 的原子一致性;
2. `GET /api/chart-view` 对 profile/query/timezone/engine/schema 各类失败的结构化分类;
3. profile 更新、account refresh、chart request 和 `chartCache` 之间的失效与竞态。
## 3. 决策记录(产品授权)
- 产品授权:填写出生资料后必须能够生成并查看星盘;不能用“资料还在,过一会儿再打开”掩盖永久错误。
- 保持现有安全语义:reported、accepted、confirmed、candidate、active provenance 不得混为一谈;confirmed 的普通资料编辑不得覆盖受保护的 active time。
- 失败要可理解、可诊断;不得把数据库错误、资料不完整、时区 adopted tuple 错误、引擎忙/超时和响应格式错误静默归成一个空状态。
- 本单不顺手改星盘算法、Dasha、校正算法、Node/Ayanamsa 业务规则,也不把 `chart_profiles` 作为 `/chart` 真值。
- 若现有迁移/trigger 与上述边界冲突,执行方必须停在任务书与 BUG 记录中,不能自行放宽或删除保护。
## 4. 硬红线
1. 不能信任客户端出生日期、时间、地点或 offset 覆盖服务端 profile 真值。
2. 不得通过清空 `active_birth_*`、放宽 accepted/confirmed 校验或 fallback 到不一致声明值来“修复”页面。
3. 不得把数据库 query error 当作 `birth_profile_incomplete`;日志不得记录姓名、出生日期/时间、坐标、完整请求体、Cookie、JWT、密钥或模型原文。
4. 不新增 spinner、骨架屏或第二套滚动/加载机制;继续遵守 `frontend/DESIGN.md``frontend/docs/VOICE.md`
5. 不修改 `scripts/jyotish_api_server.py` 增长红线,不改 `.gitea/workflows/**`,不提升 `main`
6. 若动表,必须通过向后兼容迁移并运行 `npm run test:db --prefix frontend`;删除/重命名/收紧约束必须另轮。
7. 任何既有断言变更都要写“原值 / 新值 / 原因”;测试名不得减少。
## 5. 任务分解与验收标准
### T1 · 端到端重放并锁定服务端真值
- 用 synthetic/golden fixture 覆盖 reported、accepted、confirmed、跨午夜 adopted date;不要写真实用户资料。
- 逐段核对 `PATCH /api/account → GET /api/account → GET /api/chart-view`,确认 status、reported time、active time、active date、timezone id/offset、provenance 的语义一致。
- 若发现 PATCH/trigger 清掉 adopted tuple 后仍留下可排盘的 active 状态,修复为原子、可解释的状态转换;confirmed 保护保持不变。
验收:同一 fixture 的 account 与 chart-view 返回相同的 server-owned date/time/offsetaccepted/confirmed 不出现可排盘但 adopted tuple 不完整的状态;reported 仍按声明字段排盘;跨午夜使用 adopted dateconfirmed 普通编辑仍不能覆盖 active time。
### T2 · 锁定 chart-view 错误分类
- 为 profile query error、profile incomplete、adopted calculation incomplete、timezone resolver failure、engine 429、engine timeout、bad payload、response schema failure 建立稳定内部分类和用户可读映射。
- 保留 HTTP/API 兼容性,除非测试证明现有状态码无法表达安全边界;不要把 200 的结构化业务状态随意改成异常 500。
- 日志只记稳定类别、route、耗时和必要的 HTTP status,不记出生资料。
验收:每类错误有独立测试;数据库读取失败不再伪装成 profile incomplete;用户页面不再只显示无上下文的“没有可显示内容”;`VOICE.md` 规定的“失败不写过会儿再打开”口径保持一致。
### T3 · 修复更新后的缓存与竞态
- account PATCH 成功后明确失效/重新验证 chart snapshot,至少按 account + profile version/真值指纹隔离;不得让旧 snapshot 在新资料已成功保存后长期冒充新结果。
- 检查 `refreshAccount` 与 chart request 的顺序,避免并发请求读到半更新 profileengine cache key 继续包含 date/time/offset、ayanamsa、node mode 等已有必要维度。
验收:资料更新后重新进入 `/chart` 取得新 date/time/offset;失败时旧 snapshot 不覆盖已确认失效的 profile;同账户不同资料不会互相命中;现有 secondary-page cache 合同不被削弱。
### T4 · 补完整回归链路
至少覆盖:PATCH 后 chart-view 新资料、reported/accepted/confirmed、跨午夜、缺 active offset、query error、引擎 busy/timeout/bad payload、schema parse、旧 snapshot 失效、D1 planets/houses 合同。保留现有 `chart-view-route``chart-view-engine``server-owned-birth-profile``adopted-birth-date``account-api``secondary-page-entry` 测试名。
## 6. 让步顺序
1. T1 服务端真值一致性;2. T2 错误分类;3. T3 缓存/竞态;4. T4 全链路回归。若时间不足,不能以只改文案代替 T1;缓存优化可延期,但必须把旧快照风险记入进度和 `BLOCKED.md`
## 7. 开工前置命令
```bash
git status -sb
git fetch origin --prune
git worktree add -b codex/chart-profile-update-consistency-20260922 .worktrees/chart-profile-update-consistency-20260922 origin/staging
cd .worktrees/chart-profile-update-consistency-20260922/frontend
./node_modules/.bin/tsc --noEmit
npm run lint
npm test
npm run build
```
开工时再次执行 `grep -n '^## BUG-' docs/BUG_HISTORY.md | tail -1`,若最大号变化,以当时最大号为准,不复用本文件预估号。
## 8. BUG 编号起点
本轮写任务书时 `docs/BUG_HISTORY.md` 最大号为 BUG-995。实现方若确认本现象属于新 Bug,开工前再次核对最大号,按最大号 + 1 连续编号;若只是 BUG-073/076 或 BUG-715717 的复发,必须关联原记录,不得伪装成无关新 Bug。
## 9. 交付记录
代码实现同轮必须更新 `docs/BUG_HISTORY.md``docs/tasks/PROGRESS-chart-profile-update-consistency-20260922.md`;若有用户可见状态或文案变化,更新 `CHANGELOG.md`。验收未闭环前不得写 `resolved`,不得声称 staging 已部署。
@@ -0,0 +1,91 @@
# 任务书 · 开始聊天选择人物与会话对象锁定(2026-09-22)
## 0. 基线与串行依赖
- 基线 commit`1bc6a954c597e2817fe72bfedad93119ab19a527`;执行方开工前必须 fetch 并以最新 `origin/staging` 重核。
- 执行分支:`codex/chat-subject-picker-20260922`;工作树:`.worktrees/chat-subject-picker-20260922`
- 强依赖:必须在 `TASK-consultation-subject-binding-20260922.md` 的服务端 resolver、`/api/consult` 接入和服务端测试合入 staging 后再开工;不得两个分支并行改会话 binding 或聊天顶栏。
- 与首页可靠性、starter-entry 和图标动效任务遵守文件级串行:若这些任务尚未合入,不能同时修改 `page.tsx``starter-home.tsx`、聊天顶栏或相关共享 CSS;功能绑定先于 UI 选择器,入口视觉和图标审计随后进行。
- 本轮 other 只覆盖普通聊天。报告、每日星语、生时校正不接入人物选择器。
## 1. 会议事实与现状
会议明确:切换其他人物后聊天不能继续使用本人资料;“我的星盘”不适合查看他人,建议改为“当前星盘”;业务认可点击聊天顶部人名展开人物列表;开发侧提出开始聊天时选对象,会话进行中不再更换,业务认可方向但未展开所有边界。
代码事实:
- `frontend/src/hooks/use-session-management.ts` 的新会话目前隐式继承账户级 `activeChartId`,没有开始聊天前的显式确认流程。
- `frontend/src/app/(app)/page.tsx``.chat-header-chart` 当前是静态 `span`,没有 button/popover/键盘语义。
- `frontend/src/components/chart-library-panel.tsx` 是账户设置资料管理,不应整块复制成聊天选择器。
- `frontend/src/lib/home-profile.ts` 已有 `chartSnapshotForSession``sessionChartLabel``ChatSession` 已有 chart profile 三字段,但 UI 选择必须最终写会话 binding,而不是只改 localStorage。
- `frontend/DESIGN.md` Chat header 目前是一行 quiet chart chip;本轮改动必须同步 DESIGN,并保持 44px 透明命中区、暖色 palette、popover 120ms 与 reduced-motion。
## 2. 决策记录(产品已拍板)
- 第一条消息发送前可以选择人物;本人可作为明确的默认选中项,但必须让用户看得到当前对象。
- 第一条消息发送后人物锁定;需要换人时新建会话,不改绑旧会话。
- 历史会话人物绑定固定;打开历史会话不得修改其 binding。
- 人物资料编辑后,后续普通聊天读取该人物最新资料。
- 人物删除后旧会话可阅读,但发送阻断,不自动退回本人。
- 顶部人物名是人物入口;空会话可选择,有消息会话点击其他人物时走“新建会话并使用此人物”路径,不能静默改绑。
## 3. 硬红线
1. 不得只改 `activeChartId` 或 localStorage;选择必须改变新会话的服务端 binding,或创建新会话。
2. 有消息的会话不得改人物;不得通过重写 `chart_profile_name` 伪造切换。
3. 人物列表和错误态不得泄露其他账户资料;删除人物不得回退 self。
4. 使用现有 `ChatComposer``useConversationScrollAnchor`、既有 popover/加载机制;不新增输入框、滚动跟随或 spinner。
5. `Home()` 的 useState/useRef 和行数增长冻结不得被绕过;新状态放 hook/lib/组件。
6. 所有可见文案对照 `frontend/docs/VOICE.md`;交互合同同步 `frontend/DESIGN.md`;源码符号/旧文案删除前先 `git grep -n` 覆盖 `tests/ frontend/`
## 4. 任务分解与验收标准
### T1 · 会话启动选择器
- 从 chart library 读取本人和当前用户拥有的 other 资料,建立独立的 `ChatProfilePicker`/等价组件,不复制账户设置编辑表单。
- 空会话首次发送前显示当前人物与切换入口;选择状态清晰;资料加载中/失败/不完整有明确非阻塞状态。
- 新会话创建时写入服务端确认过的 `chart_profile_id`,服务端名称/关系不能被客户端任意覆盖。
验收:新建聊天能看到 self + owned other;选择 other 后创建/发送的会话绑定正确;未选择时默认行为可理解且不依赖隐式 localStorage;加载失败不创建错误 binding。
### T2 · 顶部当前星盘入口
-`page.tsx``.chat-header-chart` 静态 span 改为可访问 button/等价控件,名称改为“当前星盘”语义,实际显示当前人物名。
- click、Enter、Space 均能打开/关闭 popover;实现 `aria-expanded``aria-controls`、焦点回收、Escape、点击外部关闭。
- 列表显示本人、owned other、当前选中、资料删除/不可用状态;至少 44px 触控目标,遵循现有 popover motion。
验收:键盘和触摸均可用;顶栏仍保持单行/移动端尺寸;不引入第二个顶部入口或第二套列表。
### T3 · 已有消息的锁定行为
- 空会话选择人物直接更新该会话的待发送 binding。
- 有消息的会话选择其他人物不改旧会话;给出“新建会话并使用此人物”的单一路径,创建后才发送。
- 发送中/流式请求期间不允许创建冲突 binding;等待当前请求结束后再执行新会话动作。
验收:旧会话历史和服务端 subject 不变;新会话绑定新人物;刷新、深链、跨设备仍一致;删除人物显示阻断而不是改成本人。
### T4 · UI/行为回归
至少覆盖:picker self/other、默认状态、空会话选择、已有消息锁定、新建会话路径、键盘/焦点/44px、资料加载失败、删除资料、刷新/深链。扩展现有 `chart-library-session``session-open-preserves-identity`、chat session write/authority 合同;不删除旧测试名。
## 5. 让步顺序
T1 服务端绑定接线 > T2 可访问入口 > T3 锁定/新建路径 > T4 细节视觉。若无法安全实现顶部入口,宁可保留静态显示并记录 blocked,不得交付假切换。
## 6. 开工前置命令
```bash
git status -sb
git fetch origin --prune
# 确认 consultation-subject-binding 已合入 origin/staging 后再创建本分支
git worktree add -b codex/chat-subject-picker-20260922 .worktrees/chat-subject-picker-20260922 origin/staging
cd .worktrees/chat-subject-picker-20260922/frontend
./node_modules/.bin/tsc --noEmit
npm run lint
npm test
npm run build
```
## 7. 记录与验收
同轮更新 `frontend/DESIGN.md`、必要的 `frontend/docs/VOICE.md``docs/tasks/PROGRESS-chat-subject-picker-20260922.md``docs/tasks/README.md` 状态板;若确认原始人物错配 Bug 尚未有记录,按开工时 `BUG_HISTORY.md` 最大编号 +1 连续登记并关联相关历史。浏览器级登录验收写入 `docs/testing/`;未具备登录态/Chrome 时写环境缺口,不得写通过。
@@ -0,0 +1,115 @@
# 任务书 · 普通聊天人物资料服务端真值与会话绑定(2026-09-22)
## 0. 基线与依赖
- 基线 commit`1bc6a954c597e2817fe72bfedad93119ab19a527`(当前 `origin/staging`);执行方开工前必须 `git fetch origin --prune` 并重新确认。
- 执行分支:`codex/consultation-subject-binding-20260922`;工作树:`.worktrees/consultation-subject-binding-20260922`
- 本单先于人物选择 UI。`TASK-chat-subject-picker-20260922.md` 必须等待本单的服务端 subject resolver 与集成测试完成后再开工;两轮不得同时改 `page.tsx`、会话绑定或咨询 route。
- 本轮 other 资料只覆盖普通聊天;报告、今日星语、生时校正和其他入口另立任务,不得顺手扩范围。
- 与本日人物选择 UI 任务严格串行:本单先完成并合入 staging,`TASK-chat-subject-picker-20260922.md` 才能开工;两单不得同时改 `frontend/src/app/api/consult/route.ts`、会话 binding 契约或聊天顶栏接线。
## 1. 事故实证
会议反馈:切换到其他人物后,聊天仍使用用户本人的资料。
调查已确认:
- `frontend/src/hooks/use-session-management.ts``startNewChat` 通过账户级 `activeChartId` 调用 `chartSnapshotForSession`,会话已有 `chart_profile_id/name/role` 元数据,但没有服务端 subject 解析。
- `frontend/src/app/api/consult/route.ts` 读取会话时只取 `chart_profile_role`,不取 `chart_profile_id`;随后通过 `frontend/src/lib/consultation-route-service.ts``loadProfile(userId)` 无条件读取登录用户的 `profiles`
- `frontend/src/hooks/use-consultation-run.ts` 从全局 `profile` 组装客户端出生字段;这些字段不能作为服务端真值,否则会绕过所有权和资料完整性边界。
- `frontend/src/app/api/chart-profiles/route.ts``[id]/route.ts` 有基本 `user_id` 所有权约束,但没有接入咨询真值。
- 删除 other 资料后前端可能显示“资料已删除”,而 `/api/consult` 仍回退到本人,形成 fail-open 和用户误导。
相关历史:BUG-001、BUG-010、BUG-018、BUG-073、BUG-076、BUG-011、BUG-188;当前最大号开工前重新核对。
## 2. 根因
会话绑定字段目前只是客户端可写/可读的展示元数据,不是服务端确认过的 subject binding。咨询服务没有按 `chat_sessions.chart_profile_id` 解析人物,而是固定使用当前登录用户 `profiles`,所以 UI、会话显示与实际排盘对象可以不一致。
## 3. 决策记录(产品已拍板)
| 决策 | 本轮口径 |
|---|---|
| 会话人物 | 第一条消息发送后锁定;如需换人,新建会话,不改绑已有消息的会话 |
| 资料编辑 | 会话保留人物 ID,后续普通聊天使用该人物最新资料 |
| 删除人物 | 旧会话保留阅读;资料不存在/被删除时阻断继续发送,不自动退回本人 |
| other 范围 | 本轮只打通普通聊天;报告、每日星语、生时校正另立任务 |
| self 真值 | `self` 始终映射当前用户权威 `profiles`,不信任 chart library JSON 或客户端出生字段 |
| 名称/关系 | 会话快照用于历史展示;排盘对象由服务端按 ID/role 重新解析 |
## 4. 硬红线
1. 不得只改 `activeChartId`、顶部文案或客户端 request body 就声称修复。
2. 不得信任客户端 `name/year/month/day/hour/minute/city/lat/lon/tz` 覆盖服务端 resolver。
3. `other` 必须校验当前用户所有权、存在性、role、资料完整性;越权、伪造 role/name、删除或缺失必须 fail-closed,不得 fallback 到 self。
4. 有消息的会话不得 PATCH 改绑;若实现层需要字段存在,必须由服务端拒绝并覆盖测试。
5. 不改数据库结构,除非任务书另列迁移;不改计费语义、模型选择、普通聊天以外入口。
6. 不把出生资料、姓名、会话原文或凭据写进测试 fixture、日志、BUG 历史或任务记录;fixture 只能使用 synthetic/golden 数据。
7. 不增加 `page.tsx` 的 Home 状态/引用,不新增第二个输入框、滚动跟随或加载动画。
## 5. 任务分解与验收标准
### T1 · 服务端 subject resolver
新增独立服务端 helper(建议放 `frontend/src/lib/`,不要把实现堆进 `route.ts`):
- 输入当前 `userId` 与会话 binding;读取服务端会话和 chart profile。
- `self` → 当前用户 `profiles``other` → 当前用户拥有的 `chart_profiles.profile`
- 服务端重新派生 name/role;会话展示快照不作为排盘真值。
- 统一处理不存在、删除、越权、role 不一致、资料不完整,并返回稳定内部错误类别。
- 客户端出生字段只能被忽略或用于非权威一致性检查,绝不能覆盖 resolver。
验收:self/other 都由服务端真值生成;other 不会取当前用户 profiles;越权 ID、伪造 name/role、缺资料、删除资料均 fail-closed;错误不泄露内部数据库细节。
### T2 · 接入咨询主链
- `frontend/src/app/api/consult/route.ts` 读取 `chart_profile_id` 及必要 binding 信息。
- 将 resolver 注入 `frontend/src/lib/consultation-route-service.ts` 的 prepare 流程。
- 保留会话 owner 校验、会话类型校验、计费前失败语义;resolver 失败不得扣点、不得进入模型调用。
- 只修改普通聊天咨询入口,不扩到报告、每日星语、校正。
验收:同一问题分别绑定 self/other 时,传入计算与最终咨询上下文的出生资料来自各自服务端 profile;resolver 失败时没有模型调用、没有扣点、公开错误可理解。
### T3 · 会话绑定写入与不可变边界
- `frontend/src/app/api/sessions/route.ts``[id]/route.ts` 在写入时校验 chart profile ID/role/name 的一致性,或由服务端根据 ID 填充展示字段。
- 未发送第一条消息的空会话可按产品规则创建绑定;已有消息的会话不得改绑。
- 刷新、深链、跨设备恢复保持 binding;打开历史会话不能只靠全局 `activeChartId` 改变实际对象。
验收:客户端提交任意伪造 name/role 不能伪造会话主体;服务端保存的绑定可被再次解析;有消息后改绑明确失败;空会话策略与下一轮 picker 一致。
### T4 · 服务端回归测试
新增或扩展测试,至少覆盖:
- self 使用权威 profilesother 使用当前用户拥有的 chart profile
- 另一用户 ID、随机 ID、role/name 不匹配、删除/缺失/不完整资料;
- 客户端出生字段与服务端资料冲突时仍使用服务端;
- `/api/consult` 集成确实把 other 资料交给 chart/consultation preparation
- resolver 失败不扣点、不调模型、不 fallback;
- 会话有消息后改绑拒绝;刷新/深链保留 binding;
- 并发删除与发送时结果确定且 fail-closed。
保留现有测试名;若改变既有断言,进度记录写原值/新值/原因。
## 6. 让步顺序
T1 > T2 > T4 > T3 的 UI 接线。若时间不足,优先完成 resolver、咨询接入和服务端集成测试;不得先交付只改前端显示的 picker。
## 7. 开工前置命令
```bash
git status -sb
git fetch origin --prune
git worktree add -b codex/consultation-subject-binding-20260922 .worktrees/consultation-subject-binding-20260922 origin/staging
cd .worktrees/consultation-subject-binding-20260922/frontend
./node_modules/.bin/tsc --noEmit
npm run lint
npm test
```
开工时核对:`grep -n '^## BUG-' docs/BUG_HISTORY.md | tail -1`。当前任务书编写时最大号是 BUG-995;若确认新 Bug,按开工时最大号 +1,若为 BUG-073/076 复发则关联原记录。
## 8. 交付与记录
实现同轮必须更新 `docs/BUG_HISTORY.md`,说明现象、触发条件、根因、修复、验证和防复发;如尚不能闭环,状态只能是 `investigating``blocked`。写 `docs/tasks/PROGRESS-consultation-subject-binding-20260922.md`,并在 `docs/tasks/README.md` 状态板登记。不得声称已部署,除非 staging SHA 与 `/api/health.deployment.gitCommit` 核对一致。
@@ -0,0 +1,78 @@
# 任务书 · 首页加载失败可恢复性与 BUG-936 收口(2026-09-22
## 0. 基线与已有任务关系
- 基线 commit`1bc6a954c597e2817fe72bfedad93119ab19a527`;执行方开工前 fetch 并重核最新 `origin/staging`
- 执行分支:`codex/home-bootstrap-reliability-20260922`;工作树:`.worktrees/home-bootstrap-reliability-20260922`
- 既有 `docs/tasks/TASK-first-paint-dead-screen-fallback-20260917.md` 已定义 BUG-936 的首屏内联 fallback 方案。本单不是另起炉灶:若该任务尚未合入,执行方应直接按其未完成项执行并以本单产品验收为准;若已合入,禁止重复添加第二套 fallback。
- 本单与 `/chart` 数据真值任务独立;不要用首页 bootstrap 改动掩盖星盘 API 问题。
- 本单与人物选择器都涉及首页装配时,优先完成并合入本单的 bundle 无关 fallback,再开始 `TASK-chat-subject-picker-20260922.md` 的首页接线;执行方不得同时修改 `page.tsx` 或同一 loading/reveal 符号。
## 1. 事故实证
BUG-936`docs/BUG_HISTORY.md`)记录:iPhone Safari 可能永久停在“正在载入账户 / 同步个人资料与对话记录”。已确认 `/api/account``/api/sessions``/api/models` 等接口返回 200;预渲染 HTML 本身包含加载屏;4 秒揭幕、8 秒 bootstrap timeout 和 401 处理全部在可能未执行的客户端 bundle 中。只要 chunk 未加载、解析失败、旧缓存损坏或 hydrate 失败,CSS 转圈会无限继续。
现有任务书已确认:根 layout 需要与 bundle 无关的内联经典脚本;不能自动刷新;本仓自有后行断言需移除,但不能声称因此解决所有 Safari 版本兼容问题。
## 2. 根因与边界
本单负责的是“首屏失败时用户没有恢复出口和诊断”的确定性缺陷;具体设备 iOS 版本、chunk、缓存或内容拦截器根因仍需 staging/真人分流,不得提前编造。正常接口速度不是本单的优化目标。
## 3. 决策记录
- 首屏必须在 JS 未执行、关键 chunk 失败或 hydrate 超时时给出可读失败状态和“重新加载”动作。
- 兜底必须与主 bundle 无关:内联、经典 `<script>`、ES5、无外部依赖、无 React、不得自动循环刷新。
- 正常加载不能闪现 fallback;成功揭幕后不恢复阻塞 spinner。
- 支持的浏览器基线与低版本 Safari 的行为需在 docs/testing 记录,不通过继续降低 Next 构建目标解决。
- 不升级 Next/React/依赖,不重写首页 bootstrap,不改后端账户/会话 API。
## 4. 硬红线
1. 不新增第二套 loading animation;不改变“一个等待、一次揭幕”的现有语义。
2. 不能把失败详情、出生资料、聊天内容、token、secret 或完整请求体写入观测日志。
3. 不自动刷新,不制造刷新循环;重试只能由用户明确点击。
4. `/` 必须保持 `○ Static`,首屏 gzip 变化在 ±2%;不增加 `Home()` 状态/引用。
5. 既有测试名不减少;断言变化写原值/新值/原因。
## 5. 任务分解与验收标准
### T1 · 复核并补齐 bundle 无关 fallback
- 复核既有 `TASK-first-paint-dead-screen-fallback-20260917.md` 的 layout inline script、hydrated 标记、超时和可读文案。
- 触发条件必须同时满足“仍是 `.app-loading`”与“尚未 hydrated”;正常 hydrate 不替换内容。
- fallback 提供重新加载按钮和低版本/清除站点数据的分流提示,文案遵循 VOICE;不暴露内部异常原文。
验收:JS 完全不执行或关键 chunk 失败时,fallback 能独立显示;按钮可用;正常现代浏览器不闪现;不依赖 module/React/外部脚本。
### T2 · 运行时错误分流与观测
- 覆盖 hydrate 超时、chunk 404/解析失败、旧缓存、弱网、接口错误五类场景;不把它们都伪装成接口慢。
- 如增加匿名观测,只记录类别、构建标识(非用户资料)和耗时;不得记录敏感 payload。
- 说明 BUG-936 仍需用户侧 iOS/缓存信息才能完成根因确认;兜底补上不等于所有 Safari 用户都已修复。
验收:每类场景有源码合同或测试;至少一份 `docs/testing/home-bootstrap-reliability-20260922.md` 真人/模拟清单;低版本 Safari 和 chunk 失败的环境缺口如实列出。
### T3 · 既有语法下限清理与回归
若尚未完成既有任务中的 sentence split 清理:统一工具替代本仓后行断言,逐例锁定行为;不得扩大到无关依赖。验收 `git grep -n '(?<=' frontend/src` 无结果(若已有任务已完成,只保留验证,不重复改)。
## 6. 让步顺序
T1 > T2 > T3。T1 必须可独立交付;T2 没有真实 iPhone/Chrome 时只能写环境缺口;不得以 build 成功代替浏览器验收。
## 7. 开工前置命令
```bash
git status -sb
git fetch origin --prune
git worktree add -b codex/home-bootstrap-reliability-20260922 .worktrees/home-bootstrap-reliability-20260922 origin/staging
cd .worktrees/home-bootstrap-reliability-20260922/frontend
./node_modules/.bin/tsc --noEmit
npm run lint
npm test
npm run build
```
## 8. 记录
更新 `docs/BUG_HISTORY.md` BUG-936(只能从 investigating 改为“兜底已补、根因待分流”,有真实证据后再 resolved),写 `docs/tasks/PROGRESS-home-bootstrap-reliability-20260922.md``docs/testing/` 清单;必要时更新 `CHANGELOG.md``frontend/DESIGN.md``frontend/docs/VOICE.md`。不得声称 staging 已部署,除非 health SHA 已核对。
@@ -0,0 +1,63 @@
# 任务书 · 图标与轻量动效审计(2026-09-22)
## 0. 基线与依赖
- 基线 commit`1bc6a954c597e2817fe72bfedad93119ab19a527`;开工前 fetch 并重核最新 `origin/staging`
- 执行分支:`codex/icon-motion-audit-20260922`;工作树:`.worktrees/icon-motion-audit-20260922`
- P2 可选任务,依赖首页入口弱边框的状态合同,但不阻塞星盘、聊天人物、报告和首屏可靠性。
-`TASK-starter-entry-soften-20260922.md` 串行:先完成入口弱边框的 CSS/状态合同,再审计同一批入口;不得并行修改 `starter-home.tsx` 或相关 CSS。与人物选择器的首页组件改动也须遵守仓库的串行纪律。
## 1. 会议事实与现状
业务提出常见星星图标辨识度一般,可探索月亮、月相、线条感图案;也明确没有合适方案可以暂不更换。业务认可轻量、可爱、扁平化动效,但要求支持阅读和思考,不要炫目。
调查确认现有基础已较完整:Lucide、planet glyph、InlineSpinner、shimmer、onboarding/dialog 动效和 `prefers-reduced-motion` 均已存在;DESIGN 已有按钮/row/popover/tooltip/dialog/消息进入时长约束,且禁止恢复 `thinking-orbs` 或新增多套 loading。
## 2. 决策记录
- 本轮先做库存和合同审计,不强行换掉星星图标;只有有明确辨识度、语义、对比度和移动端预算的方案才可进入小改。
- 图标优先复用 Lucide 和既有 planet glyphbrand mark 保持静态。
- 动效只允许既有 120ms/160ms/180ms 预算和 `transform`/`opacity`/颜色/filter;非 active loading 不新增循环动画。
- `prefers-reduced-motion` 下移除或降级非必要动画。
## 3. 硬红线
1. 不全站替换图标,不新增第二套 composer、滚动跟随或 loading 动画。
2. 不为了视觉通过修改业务状态、等待逻辑或隐藏错误。
3. 装饰图标必须 `aria-hidden`;语义图标必须有可访问名称,不能只靠颜色表达状态。
4. 不升级依赖,不改无关页面,不引入外部图标包。
## 4. 任务分解与验收标准
### T1 · 图标/动效库存表
盘点首页入口、聊天 header、popover、按钮、报告、星盘 glyph 和 loading 相关实现,标记来源、语义、状态和调用方;识别无调用旧样式,但不在本轮删除无关代码。
### T2 · 合同审计
检查动效时长、允许属性、循环动画、reduced-motion、键盘/触屏、44px 目标和图标文字空间;必要时补源码合同或可访问性测试,不把截图差异当作唯一证据。
### T3 · 可选小改
只有 T1/T2 证明某个图标明显影响识别且存在低风险方案时,才替换单个入口图标;同步 DESIGN/CHANGELOG 和测试。没有合适方案则只交付审计记录,不改图标。
## 5. 让步顺序
T1 > T2 > T3。预算不足时只做库存和合同,禁止为“看起来有变化”强行换图标或加动画。
## 6. 开工前置命令
```bash
git status -sb
git fetch origin --prune
git worktree add -b codex/icon-motion-audit-20260922 .worktrees/icon-motion-audit-20260922 origin/staging
cd .worktrees/icon-motion-audit-20260922/frontend
./node_modules/.bin/tsc --noEmit
npm run lint
npm test
npm run build
```
## 7. 记录与验收
`docs/tasks/PROGRESS-icon-motion-audit-20260922.md`;若只完成审计,明确写“未换图标、未新增动效”。涉及 UI 时更新 `frontend/DESIGN.md`;浏览器级 reduced-motion/屏幕阅读器验收缺环境则写 `docs/testing/`,不得宣称通过。
@@ -0,0 +1,84 @@
# 任务书 · 普通用户报告公开内容分层与导出投影(2026-09-22)
## 0. 基线与先决条件
- 基线 commit`1bc6a954c597e2817fe72bfedad93119ab19a527`;开工前 fetch 并以最新 `origin/staging` 重核。
- 执行分支:`codex/report-public-content-20260922`;工作树:`.worktrees/report-public-content-20260922`
- 业务已拍板:普通用户导出的报告隐藏内部技法和工作流字段;只保留用户可读正文、结论和必要限制说明。专业参考若保留,另立明确入口和规则。
- 本单与人物 other 资料无关;不得把报告接入人物切换任务。
- 本单不与人物选择器共享实现文件;如同批执行,先完成服务端 subject binding,再由报告单独接入其 canonical projection,禁止把两种资料真值边界混在同一 helper 中。
## 1. 事故实证
会议反馈:网站生成的报告夹带不该展示的内容,具体字段会上未列清。代码调查确认存在公开合同分裂:
- 普通聊天消息已经按 BUG-011 移除内部证据徽章、Technique Audit、单条回答下载控件。
- `frontend/src/lib/consultation-report-export.ts``consultationReportMarkdown()` 仍可能序列化 `technique_truth``workflow_route``workflow_status``precise_timing``missing_layers`、Claim boundary 等内部字段;现有导出测试仍将其作为预期结果。
- `frontend/src/lib/personal-report-longform-download.ts` 优先使用已有 Markdown,下载前主要去除图盘 fence,没有统一公开内容投影。
- `frontend/src/app/api/reports/[reportId]/professional-reference/route.ts` 会返回已存储的完整 Markdown;接口名是 professional reference,但用户入口可能只是笼统“导出报告”。
- `frontend/src/components/personal-report/personal-report-markdown-view.tsx` 的 HTML/URL/XSS 过滤是结构安全措施,不等于内部字段分类。
相关历史:BUG-011、BUG-058、BUG-086094、BUG-188。它们已明确内部路由、未执行技法、评分、原始响应和用户敏感内容不得进入普通用户可见层。
## 2. 根因
报告阅读、咨询导出、个人报告下载和 professional-reference 返回的是不同边界:聊天层隐藏了内部字段,但 canonical Markdown/导出/API 没有统一普通用户 allowlist。因此不能只在 React 下载按钮前用正则删除,也不能凭感觉删除会上未定义的内容。
## 3. 决策记录(产品已拍板)
1. 普通用户报告只展示:用户可读正文、结论、行动建议(若业务允许)和必要的限制说明。
2. 普通报告禁止:`technique_truth``workflow_route``workflow_status``precise_timing``missing_layers`、内部评分/权重/执行账本、内部 URL、secret、模型调试字段、raw tool response、job/attempt/provider 元数据。
3. 若专业参考仍需展示部分方法证据,必须单独命名、单独入口、单独权限/标识和单独合同;本单不把它混入普通导出。
4. 旧报告不能因缓存或已存 Markdown 绕过新公开规则;优先在服务端 canonical 投影边界统一处理。
## 4. 硬红线
- 不得把普通报告变成内部调试报告;不得把“更专业”当作公开内部执行轨迹的理由。
- 不得只在客户端用脆弱字符串正则作唯一隐私防线。
- 不得破坏 Markdown 的既有 XSS、危险 URL、HTML 和 `jyotish-chart` fence 安全合同。
- 不新增 PDF/Canvas/jsPDF/Playwright/Puppeteer 导出链路,不升级依赖。
- 不修改星盘计算、报告生成算法或人物绑定;只治理公开投影边界。
- 不在 fixture、日志、任务书、BUG 历史中写真实姓名、出生资料、会话原文、凭据、secret。
## 5. 任务分解与验收标准
### T1 · 建立字段级公开投影
- 在服务端/共享 canonical 层新增明确的普通报告投影 helper;字段白名单优先于黑名单。
- 区分聊天导出、个人报告详情、普通 Markdown 下载和专业参考文档类型,不用接口名字猜用户可见级别。
- 内部限制字段若需要保留其产品含义,转成自然语言限制说明,不透出内部 key/value。
验收:每个字段有公开/内部归类;普通报告在同一 synthetic fixture 下只含允许内容;没有字段时不虚构;图盘 fence 规则继续有效。
### T2 · 接入所有普通输出边界
- `consultation-report-export.ts``personal-report-longform-download.ts`、报告详情/阅读路径和 API 返回统一使用投影。
- `/professional-reference` 若继续存在,必须显式区分专业参考与普通报告;若本轮不改专业参考,至少阻止它被普通下载路径当作普通报告 fallback。
- 处理旧缓存 Markdown/已存文档:下载和 API 返回不能绕过投影。
验收:页面阅读、下载文件、直接 API 响应与普通公开合同一致;禁止字段不会因缓存命中、旧报告或备用路径复现;专业参考不会被误标为普通报告。
### T3 · 回归与安全测试
覆盖:字段存在/缺失、旧缓存、专业参考路径、图盘 fence、Markdown HTML/script/iframe/object/embed/img、危险 URL、内部 URL/secret/模型字段扫描。保留现有 `consultation-report-export``personal-report-longform-md`、报告归属/ready 检查测试名;断言变化写原值/新值/原因。
## 6. 让步顺序
T1 字段合同 > T2 canonical 接入 > T3 回归测试。若专业参考产品边界尚未实现,普通下载必须 fail-safe 使用普通投影,不能以“接口叫 professional-reference”为由透出内部字段。
## 7. 开工前置命令
```bash
git status -sb
git fetch origin --prune
git worktree add -b codex/report-public-content-20260922 .worktrees/report-public-content-20260922 origin/staging
cd .worktrees/report-public-content-20260922/frontend
./node_modules/.bin/tsc --noEmit
npm run lint
npm test
npm run build
```
## 8. 交付记录
同轮写 `docs/tasks/PROGRESS-report-public-content-20260922.md`,更新 `docs/BUG_HISTORY.md`(关联 BUG-011/058/188;若确认新用户可见回归,按开工时最大号连续编号)和必要的 `CHANGELOG.md`。视觉/文案变化同步 `frontend/DESIGN.md` / `frontend/docs/VOICE.md`。没有登录态的浏览器级导出验收写入 `docs/testing/`,不得写成通过。
@@ -0,0 +1,66 @@
# 任务书 · 首页“今日星语 / 生时校正”入口弱化边框(2026-09-22)
## 0. 基线与依赖
- 基线 commit`1bc6a954c597e2817fe72bfedad93119ab19a527`;执行方开工前 fetch 并重核最新 `origin/staging`
- 执行分支:`codex/starter-entry-soften-20260922`;工作树:`.worktrees/starter-entry-soften-20260922`
- 这是局部视觉轮次,不得与人物选择轮次同时修改同一首页组件;功能 P0 任务优先。
-`TASK-chat-subject-picker-20260922.md` 串行:人物选择器先完成其 `page.tsx`/聊天顶栏与入口接线并合入 staging,本单再改 starter entry 及其 CSS;执行方不得并行修改同一首页组件。
- 入口当前已统一为 `.starter-entry`,不恢复旧卡片,不重做首页。
## 1. 会议事实与现状
业务认可新版整体布局,主要建议是降低“今日星语”和“生时校正”的视觉强调:边框较抢眼,可考虑淡色块、轻阴影、弱化边框。调查确认两者已经使用统一 `.starter-entry`、native button、现有 Sparkles/Clock3 图标和已有 opening 状态;没有证据需要恢复旧卡片或新增功能入口。
相关约束:BUG-918(移动端空间)、BUG-934(旧卡片测试不可恢复)、`frontend/DESIGN.md` 现有 44px、focus、motion 规则、`frontend/docs/VOICE.md`
## 2. 决策记录
- 只调整 `.starter-entry` 的视觉状态:默认、hover、pressed、focus-visible、disabled/opening、窄屏宽度。
- 保留 native button、当前入口函数、暖色 palette、现有图标和 reduced-motion;不引入新的卡片层级。
- 视觉 pill 可保持约 36px,但实际透明触控命中区必须至少 44px。
- opening 状态不新增 spinner;不写“正在加载”类新文案。
- 图标更换与轻量动效审计另立 P2,不阻塞本单。
## 3. 硬红线
1. 不恢复已删除的旧卡片 class/入口,不改业务动作和数据流。
2. 不借视觉优化修改人物切换、聊天输入、滚动跟随或加载动画。
3. focus-visible 必须比默认 hairline 更清晰;不能为弱化边框牺牲键盘可用性或对比度。
4. 不新增循环动画、spinner 或依赖升级。
5. UI 改动同提交更新 `frontend/DESIGN.md`;可见文案变化先对照 VOICE;源码符号改动前 `git grep -n` 覆盖 tests/frontend。
## 4. 任务分解与验收标准
### T1 · 形成可测量视觉合同
`frontend/DESIGN.md` 写清默认 border token/宽度、hover、pressed surface tint、focus ring、disabled/opening 和 reduced-motion 规则;明确 36px 视觉高度与 44px 命中区如何共存。不得只写“更轻”。
### T2 · 实现局部 CSS/组件调整
只改 `frontend/src/components/starter-home.tsx``frontend/src/app/globals.css` 及必要的局部合同测试;保留入口语义、`aria`、disabled/opening 行为。移动端按 375px 宽度预算处理文字和图标,不裁切、不重叠、不布局抖动。
### T3 · 回归测试
扩展/新增测试覆盖真实 `.starter-entry`、入口函数、键盘焦点、44px 命中区、hover/pressed/focus、reduced-motion、375px 文本布局;不得重新断言已删除旧卡片 class(BUG-934)。
## 5. 让步顺序
T1 > T2 > T3。没有明确 token 和对比度规则时不改 CSS;若窄屏无法保持合同,宁可记录 blocked,不缩小触控目标。
## 6. 开工前置命令
```bash
git status -sb
git fetch origin --prune
git worktree add -b codex/starter-entry-soften-20260922 .worktrees/starter-entry-soften-20260922 origin/staging
cd .worktrees/starter-entry-soften-20260922/frontend
./node_modules/.bin/tsc --noEmit
npm run lint
npm test
npm run build
```
## 7. 记录与验收
`docs/tasks/PROGRESS-starter-entry-soften-20260922.md`,同轮更新 `frontend/DESIGN.md`;如产生用户可感知文案/行为更新 `CHANGELOG.md`。验收按 tsc/lint/npm test/build/Static/gzip 口径,浏览器截图或无 Chrome 环境缺口写入 `docs/testing/`,不声称通过。