diff --git a/docs/tasks/README.md b/docs/tasks/README.md index 5533aa59..336cf7a5 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -117,6 +117,7 @@ | `TASK-chat-streaming-ux-20260901.md` | `PROGRESS-chat-streaming-ux-20260901.md` | 流式体验与双会话面统一 | 已验收 | `4dc0c8c7`(BUG-473~478) | | `TASK-unified-loading-20260902.md` | `PROGRESS-unified-loading-20260902.md` | 首页一次等待一次揭幕 | 已验收 | `482796fc`(BUG-479) | | — | — | 初始化后点首页「家庭」报运行合同未完成:本命第一步未强制计算工具、主题卡无入口钉死 | 待验收 | `codex/consultation-family-contract-incomplete-20260909`(BUG-630);走查 `docs/testing/consultation-family-contract-incomplete-20260909.md` | +| `TASK-chat-reading-load-20260915.md` | — | 首页「从一个主题开始」整段下线(连同 `/api/onboarding` 的 LLM 取数与揭幕闸门);聊天回答改两层可见性:口语层默认展开、第一个 H2 起的骨架进默认收起的「完整分析」折叠块;口语层定形为「结论+2–3 要点+下一步」≤400 字。内容一字不删,只改默认可见性。⚠️ 会让 BUG-630 的走查路径失效(`guided_topic` 入口下线,枚举保留) | 待领取 | `codex/chat-reading-load-20260915` | ### 个人报告 diff --git a/docs/tasks/TASK-chat-reading-load-20260915.md b/docs/tasks/TASK-chat-reading-load-20260915.md new file mode 100644 index 00000000..eda027df --- /dev/null +++ b/docs/tasks/TASK-chat-reading-load-20260915.md @@ -0,0 +1,330 @@ +# TASK · 首页主题卡下线 + 聊天回答两层化(降低阅读负担) + +- 日期:2026-09-15 +- 基线 commit:`origin/staging` @ `ebd5ba68`(`docs: clear the timezone gap and record what actually keeps the gate red`) +- 执行分支:`codex/chat-reading-load-20260915` +- 工作树:`.worktrees/chat-reading-load-20260915` +- 类型:产品改造(非事故)。BUG 编号起点见文末。 + +--- + +## 1. 产品诉求(原话) + +> 首页底下的那些问题都可以去掉;聊天过程中怎么让用户不会觉得「我现在看到字很多,我有点阅读障碍」。 + +两件事分别对应任务 1 和任务 2–3。**两件事可以在同一分支同一轮做完**,互不依赖,但共用一次测试基线测量(任务 4)。 + +--- + +## 2. 事故实证 + +行号会漂移,定位以符号名为准。以下全部核对于 `origin/staging@ebd5ba68`。 + +### 2.1 首页主题卡的真实成本 + +| 事实 | 定位 | +| --- | --- | +| 「从一个主题开始」六张卡 = `
` | `frontend/src/components/starter-home.tsx` → `StarterHome`,`starter-themes-heading` 那一段 | +| 卡片文案**每个用户现调大模型生成**,一次写十个领域的问题 | `frontend/src/app/api/onboarding/route.ts` → `generateText`(`export const maxDuration = 60`) | +| 前端为它等:单次超时 50 s、最多 3 次尝试 | `frontend/src/lib/onboarding-client.ts` → `defaultPolicy`(`requestTimeoutMs: 50_000`, `maxAttempts: 3`) | +| **首页揭幕被它挡着** | `frontend/src/app/page.tsx` → `bootstrapPrepareReady` 的 `onboardingSettled: onboarding !== null \|\| onboardingError !== ""` | +| 这份数据除了这六张卡**没有第二个消费点** | `page.tsx` 全文 `onboarding?.` 只出现在 `starterSuggestions` 的赋值处 | + +即:为了六行文案,首页每次都在等一次 LLM 生成,并把揭幕闸门押在它身上。 + +### 2.2 聊天「字很多」的根因不在排版 + +先排除排版嫌疑(`frontend/src/app/globals.css`):正文 `17px` / `line-height 1.75`(`.conversation:not(.is-empty):not(.is-rectification) .message-markdown`)、段距 `var(--space-4)`、栏宽 `min(80%, 680px)`。这些已经是舒服的值,继续调救不了。 + +真正的来源是**生成合同要求每道本命题都输出一整篇报告**: + +`frontend/src/mastra/product-voice.ts` → `natalSpokenReportContract`: + +- 「For career / wealth / marriage / family (and any natal domain reading): open with 3–6 heading-free sentences… Then write the skill Level 2 template. **骨架不可省略**」 +- 「Required body order after that opener, using these exact H2 headings: `## 统一参数与原始结构`, then `## {Chinese domain label}` …, then `## 技法审计表`, then `## 现代生活`」 +- 输出预算 `AGENT_ANSWER_OUTPUT_TOKENS = 16_384`(`frontend/src/lib/agent-generation-settings.ts`) + +于是用户问一句「我事业怎么样」,读完三句人话,**第二屏立刻撞上度数 / 宫位 / 分盘 / Dasha 边界表**。 + +而渲染层对这一切是**完全平铺**的:`frontend/src/components/chat-message-content.tsx` → `ChatMessageContent` 把整篇 markdown 一次渲染到底,唯一的折叠是 `TechniqueAuditDisclosure`,而且 `showFoldedAudit = rows.length > 0 && split.rows.length === 0` —— 正文里已经有那张表时反而**不折**。 + +### 2.3 根因一句话 + +- 首页:一个低价值入口押着一次 LLM 调用和整个揭幕闸门。 +- 聊天:内容合同要求「全量输出」,渲染层没有「默认可见性」这一层,于是全量输出 = 全量可见。 + +--- + +## 3. 决策记录(产品已授权) + +产品负责人 2026-09-15 确认,按本轮方案全量执行。逐条列明,执行方不必再问: + +1. **删除首页「从一个主题开始」整段**,连同它背后的 `/api/onboarding` 取数链路。 +2. **接受副作用**:没有可用出生分钟的用户(`personalConsultationAvailable === false`)将失去这组引导入口,面对的是问候语 + 两张产品卡 + 输入框。产品明确接受,不做替代入口(用户口径:多余入口宁可删除也不修)。 +3. **聊天回答改为两层可见性**:第一个 H2 之前的口语层默认展开,从 `## 统一参数与原始结构` 起的全部章节装进**默认收起**的折叠块。产品选了「全部收起」,不做「统一参数收起、领域章节展开」的折中。 +4. **口语层 ≤ 400 字**,形状固定为「一句结论 → 2–3 条短要点 → 一句下一步」。 +5. **不做分段生成**。`TASK-report-sectioned-generation-20260830.md` 那条路解决的是生成超时,不是阅读负担,本轮不碰。 + +### 3.1 本轮推翻 / 作废的既有条目 + +- **`natalSpokenReportContract` 的「3–6 heading-free sentences」被细化**为带形状与字数上限的口语层。骨架强制、不得跳过已执行层这两条**不变**。 +- **待验收项 BUG-630 的走查路径失效**:`docs/tasks/README.md` 「聊天主链路与首页」板上 `codex/consultation-family-contract-incomplete-20260909`(BUG-630,「主题卡无入口钉死」)的修复就是 `guided_topic` 入口 + `pinsConsultationDomains`。删掉主题卡之后,**这个入口值不再有任何发出点**,该项的浏览器走查清单 `docs/testing/consultation-family-contract-incomplete-20260909.md` 不再可执行。 + - 处理方式:**保留** `consultation-entrypoint.ts` 的 `guided_topic` 枚举值与 `pinsConsultationDomains` 行为(历史会话已把 entrypoint 持久化在会话态里,删枚举会让旧会话读不回来),只删发出点。 + - 执行方需在 PROGRESS 里写明该项转为「入口已下线,机制保留供历史会话」,并在 `docs/tasks/README.md` 对应行改状态。 + +--- + +## 4. 硬红线 + +1. **内容一个字不许删。** 骨架仍然完整生成、完整入库、完整可导出。本轮只改**默认可见性**与**口语层形状**。`AGENTS.md` Part B 与 `natalSpokenReportContract` 的「Skip an executed layer the question needs」禁令继续有效。 +2. `./node_modules/.bin/tsc --noEmit` 0 错;`npm run lint` **0 error**。 +3. **测试总数不得低于开工时 `origin/staging` 的实测**(`frontend/AGENTS.md` §7.3)。本轮会删入口、必然删断言,因此**必须新增足量测试把总数补回来**,见任务 4。 +4. 改任何既有断言必须写「原值 / 新值 / 原因」三栏。 +5. `next build` 后 `/` 仍是 `○ Static`;首屏 gzip 变化 ±2% 以内,超出要在 PROGRESS 给原因。 +6. 改 UI 的同一提交内更新 `frontend/DESIGN.md`;新文案先对照 `frontend/docs/VOICE.md`。 +7. 不动数据库结构。`profiles.onboarding_payload` / `onboarding_version` / `onboarding_generated_at` 三列**留着不迁移**。 +8. 不得顺手升级依赖、不得顺手修不在本任务书里的 warning。 +9. 揭幕后不得出现 spinner / 骨架 / 「正在加载」——流式生成中除外(`AGENTS.md` §6)。任务 2.4 的流式提示走的就是这个例外,必须是安静的一行字,不是转圈。 + +--- + +## 5. 任务分解 + +### 任务 1 · 下线首页「从一个主题开始」 + +**1.1 组件层** — `frontend/src/components/starter-home.tsx` + +- 删除 `
` 整段(含 `starter-section-heading`、`starter-theme-accordion`、`starter-theme-card`、末尾 `onboardingError` 那行 `starter-note`)。 +- 从 `StarterHomeProps` 删除 `starterThemes`、`starterSuggestions`、`startSuggestedConsultation`、`onboardingError` 四个 prop,以及不再使用的 `StarterHomeTheme` / `StarterHomeSuggestion` 类型导出与 `ConsultationEntrypoint` / `Theme` import。 +- 保留 `starter-hero` 与 `product-entrypoints`(今日星语 / 生时校正)两段不动。 + +**1.2 页面层** — `frontend/src/app/page.tsx` + +- 删除 `starterThemes`、`starterSuggestions` 两个派生值与 `generalGuidedJyotishTopics` 的 import。 +- 删除 `onboarding` state、`setOnboarding`、`onboardingError`、`onboardingPending`,以及调用 `requestOnboardingWithRecovery` 的那个 `useEffect`(含 `activeOnboardingRequestIdentity`、`onboardingRequestIdentity`、`isCurrentOnboardingRequest`、`OnboardingAuthenticationError` 的相关引用)。 +- 从 `bootstrapPrepareReady` 的入参里删掉 `onboardingSettled`,并同步改 `frontend/src/lib/`(或 hook)里 `bootstrapPrepareSettled` 的签名与其单测。 +- ⚠️ **`startSuggestedConsultation` 函数本身必须保留**:今日星语卡仍在调用它(`startSuggestedConsultation(dailyStarlanguageQuestion, "timing", "daily_starlanguage")`)。只删卡片的调用点与 prop 传递。 +- `previewMode === "onboarding"` 那条预览分支里对 `setOnboarding` 的赋值一并清理,预览态不得因此报错。 + +**1.3 接口与 agent** + +`onboarding` 这个词在本仓有**两条互不相干的链路**,删错会拆掉出生资料录入。逐个文件列明: + +| 文件 | 处置 | 理由 | +| --- | --- | --- | +| `frontend/src/app/api/onboarding/route.ts` | **删** | 建议问题的生成接口 | +| `frontend/src/lib/onboarding-client.ts` | **删** | 建议问题的取数与重试策略 | +| `frontend/src/lib/onboarding-post.ts` | **删** | 建议问题的服务端处理 | +| `frontend/src/lib/onboarding-payload.ts` | **删** | 建议问题的 schema 与 fallback | +| `frontend/src/lib/onboarding-cache-policy.ts` | **删** | 建议问题的版本与 TTL | +| `frontend/src/mastra/` 的 `getOnboardingAgent` / `onboardingInstructions` / `onboardingAgents` | **删** | 只写建议问题 | +| `frontend/src/lib/onboarding-copy.ts` | **保留** | 这是**出生资料录入**那条链路的受保护文案,与建议问题无关 | +| `frontend/src/components/onboarding-chat-message.tsx` | **保留** | 同上,是录入期的打字机消息 | +| `frontend/src/hooks/use-profile-onboarding.ts` | **保留** | 同上,是资料表单的 hook | +| `createStartGreetingParts` / `createStartGreeting` | **保留** | 首页问候语,不来自这个接口 | +| `isCurrentOnboardingRequest` / `onboardingRequestIdentity` / `OnboardingAuthenticationError` | **删** | 只服务建议问题的请求去重 | +- **保留** `frontend/src/lib/guided-jyotish-topics.ts` 与 `frontend/src/lib/consultation-domain-registry.ts`:它们携带 `claimBoundary` / `strictWorkflowRoute` / `evidencePreview`,是领域注册表合同的一部分,个人报告侧仍在用。若 lint 报出 `generalGuidedJyotishTopics` 完全无引用,把它删掉、`defaultGuidedJyotishTopics` 与 `home-types.ts` 的 `themes` 导出保留。 +- **保留** `frontend/src/lib/consultation-entrypoint.ts` 的 `guided_topic` 枚举值与 `pinsConsultationDomains` 分支(见 §3.1)。在该文件加一行注释说明「入口已于 2026-09-15 下线,枚举值保留用于读回历史会话」。 + +**1.4 样式** — `frontend/src/app/globals.css` + +- 删除 `.starter-themes`(两处:`.starter-hero, .product-entrypoints, .starter-themes` 的共享规则里摘掉选择器;以及独立的 `.starter-themes { display: grid; … }`)、`.starter-section-heading` 系列、`.starter-theme-accordion`、`.starter-theme-card` 系列、`.starter-content`,以及 `@media (min-width: 768px) and (max-width: 900px)`、`@media (max-width: 767px)` 两处响应式覆盖里的对应规则。 +- `.starter-arrow` 仍被产品卡使用,**不要删**。 + +**验收标准(任务 1)** + +- 首页登录后只剩:问候语、今日星语卡、生时校正卡、输入框;没有出生时间的账号同样如此。 +- 浏览器 Network 面板不再出现 `/api/onboarding` 请求。 +- 从冷启动到首页揭幕不再等待建议问题;`bootstrapPrepareReady` 的入参里没有 onboarding 相关项。 +- `grep -rn "starterSuggestions\|starter-theme-card\|requestOnboardingWithRecovery" frontend/src` 无结果。 +- `grep -rn "guided_topic" frontend/src` 只剩 `consultation-entrypoint.ts` 内部。 + +--- + +### 任务 2 · 聊天回答两层化(渲染层,本轮主改动) + +**2.1 新增纯函数** — `frontend/src/lib/chat-answer-split.ts` + +```ts +export type SpokenAndReportSplit = Readonly<{ + spoken: string; // 第一个 H2 之前的全部内容 + report: string; // 第一个 H2 起的全部内容(含该 H2 本身) + headings: readonly string[]; // report 里所有 H2 的文字,供折叠摘要用 +}>; +export function splitSpokenAndReport(text: string): SpokenAndReportSplit; +``` + +- 切点 = **第一行匹配 `/^##\s+\S/` 的行**(任意 H2,不写死 `统一参数与原始结构`)。合同锁死第一个 H2 是它(`frontend/src/lib/consultation-thinking-plan.ts` → `REPORT_HEADING.foundation`),但按「任意 H2」切可以在模型漂移时自然降级,不会突然整篇不折。 +- 代码围栏内的 `##` 不算切点,复用 `frontend/src/lib/chat-markdown-split.ts` 里 `FENCE` 的同一判断方式。 +- 没有 H2 时(闲聊、日常问题、onboarding 消息)返回 `{ spoken: text, report: "", headings: [] }` —— 自动全展开,不需要特判。 + +**2.2 新增折叠组件** — `frontend/src/components/chat-answer-detail.tsx` + +- 导出 `AnswerDetailDisclosure`,结构照抄既有的 `TechniqueAuditDisclosure`(`
` + ``),样式走 `globals.css` 新增的 `.answer-detail*`,视觉上与 `.technique-audit` 同族但不复用同一个 class。 +- `` 文案:主标「完整分析」,副标用 `` 写章节名,**不要只写「展开」**。例:`完整分析` + `统一参数、事业、技法审计 等 5 节`。章节名取 `headings` 前三个,多于三个补「等 N 节」。文案定稿前对照 `frontend/docs/VOICE.md`。 +- **默认收起**(不给 `open`)。 + +**2.3 接入** — `frontend/src/components/chat-message-content.tsx` + +渲染管线改成: + +1. 先跑既有的 `splitSpokenAnswerAndTechniqueAudit(text)`(把正文里的技法审计表摘成 `rows`,行为不变)。 +2. 对它的 `spoken` 结果跑 `splitSpokenAndReport`。 +3. `spoken` 部分照旧渲染进 `.message-markdown`(流式时仍走 `StreamingMarkdown`,memo 优化不得破坏)。 +4. `report` 非空时,渲染 ``,内部渲染 `report` 的 markdown。 +5. 现有 `showFoldedAudit` 的 `TechniqueAuditDisclosure` **移进折叠块内部**(`report` 为空时仍留在原位,闲聊场景不受影响)。 + +**2.4 流式期间的行为** + +- `streaming === true` 时,折叠块**保持收起且不渲染 children**,`` 的 `` 位置显示一行安静的文字(例:`正在写…`)。不得出现 spinner / 骨架 / 转圈(`AGENTS.md` §6 的流式例外只允许文字)。 +- `streaming === false` 后按 2.2 渲染正式摘要,children 正常渲染(收起态由浏览器隐藏,markdown 只解析一次)。 +- 这条同时是性能要求:流式期间不得把还在增长的 `report` 反复送进 markdown 解析器。 + +**2.5 影响面确认(执行方开工前自查)** + +- `ChatMessageContent` 的消费方只有两个:`chat-message-row.tsx` 与 `onboarding-chat-message.tsx`。后者的文本没有 H2,行为必须零变化。 +- 个人报告页走 `personal-report-document-view.tsx`,**不经过**本组件,不得受影响。 +- 会话落库、复制按钮、重新生成拿到的仍是**完整原文**,不是口语层。 + +**验收标准(任务 2)** + +- 一条本命类回答:口语层直接可读,下面一个收起的「完整分析」块;展开后章节顺序与原文完全一致,一个字不少。 +- 闲聊类回答(无 H2)渲染与改动前逐字一致。 +- onboarding 打字机消息渲染与改动前逐字一致。 +- 复制该条消息,得到的是完整原文(含骨架)。 +- 刷新页面重新读历史会话,折叠态一致(默认收起)。 +- 流式期间不出现 spinner / 骨架;折叠块在流式结束前不展开。 + +--- + +### 任务 3 · 口语层的形状(文案合同) + +**3.1** `frontend/src/mastra/product-voice.ts` → `natalSpokenReportContract` + +把「open with 3–6 heading-free sentences that answer the user's actual question (directional reading + one clause of context)」细化为带形状与上限的版本: + +- 仍然无标题、仍然在第一个 H2 之前、仍然 3–6 句。 +- 形状:**一句结论 → 2–3 条短要点 → 一句下一步**。 +- 要点必须是完整句子的短行,不超过 3 条,每条 ≤ 30 字;**不得写成名词短语枚举**(`VOICE.md` 禁的是公文枚举与审讯式清单,结论要点不在禁令内,但形似公文的写法在)。 +- 口语层总量 **≤ 400 字**。 +- 「骨架不可省略,但必须以直接回应开场」原句保留。 + +**3.2** 同步三处口径,不得只改一处: + +- `frontend/src/lib/consultation-thinking-plan.ts` 里 `After a 3-6 sentence spoken reply with no heading…` 那句。 +- `frontend/src/mastra/skill-binding.ts` 里 `present its Level 2 report template in the chat body after a 3-6 sentence spoken reply` 那句。 +- `frontend/src/mastra/index.ts` 里 `consultationSpokenHeadingRule("natal")` 相关文案。 + +**3.3** `frontend/docs/VOICE.md` 新增一节「口语层的形状」,把 3.1 的规则和一组好/坏对照写进去。坏例用「一坨连续段落 + 立刻进度数」,好例用「结论 + 要点 + 下一步」。 + +**3.4** `frontend/tests/consultation-voice-contract.test.ts` 与 `frontend/tests/agent-voice-copy-contract.test.ts` 里被改到的断言,逐条按「原值 / 新值 / 原因」三栏注释更新。已知会红的至少有 `assert.match(voice, /3–6 heading-free sentences/)`。 + +**验收标准(任务 3)** + +- `npm test` 里 voice 相关套件 fail=0。 +- 三处口径互相一致,`grep -rn "3-6 sentence\|3–6 " frontend/src` 的结果全部指向同一版规则。 +- `frontend/docs/VOICE.md` 有新节,且好/坏对照来自真实出现过的形状。 + +--- + +### 任务 4 · 测试与文档 + +**4.1 先量基线,写进 PROGRESS** + +``` +cd .worktrees/chat-reading-load-20260915/frontend +npm ci +npm test 2>&1 | tail -20 # 记下 pass / fail / tests 三个数字 +``` + +**4.2 删除的断言必须逐条列出** + +已知受影响: + +- `frontend/tests/starter-theme-layout-contract.test.ts` —— 整个文件随主题卡删除。 +- `frontend/tests/starter-questions.test.ts` —— 该文件共 15 个 test,**只有下面这些与主题卡相关**,其余 11 个(会话历史可点、模型弹窗尺寸、额度居中、账户菜单、会员入口、admin 按钮、IME、错误播报等)**必须原样保留**: + - `default starter questions derive every canonical domain with evidence and claim boundaries` —— 其中对 `consultationDomainRegistry` / `defaultGuidedJyotishTopics` 的领域注册表断言**必须搬到** `frontend/tests/consultation-domain-registry.test.ts`(或新建同名文件)继续跑,**不是删除**;只删 `starterSuggestions.map` / `starterThemes.find` / `startSuggestedConsultation(item.text, …)` 三条针对渲染的断言。 + - `profiles without a usable birth minute receive user-centered starter prompts` —— `generalGuidedJyotishTopics` 的文案与边界断言同样搬家保留;删掉 `starter-themes-heading` / `starter-theme-accordion` 的 `sourceBetween` 断言。 + - `homepage daily and topic cards start a consultation instead of filling the composer` —— 改名为只覆盖今日星语卡,删掉 `item.theme, "guided_topic"` 那条。 + - `keeps starter questions visible while the user edits a draft` —— 主题卡没了,该 test 删除。 + - `keeps the empty starter home at the top instead of auto-scrolling` —— **保留**,`starterHomeVisible` 仍在。 +- `frontend/tests/home-surface.ts` 不用改(starter-home.tsx 仍在)。 +- onboarding 相关,同样分两条链路,不要一把梭: + - **随接口删**:`frontend/tests/onboarding-route.test.ts`、`frontend/tests/onboarding-route-fake.ts`、`frontend/tests/onboarding-client.test.ts`、`frontend/tests/onboarding-cache-policy.test.ts`。 + - **保留**:`frontend/tests/onboarding-copy.test.ts`(录入期受保护文案)。 + - **部分删**:`frontend/tests/onboarding-presentation.test.ts` —— 其中 `createStartGreetingParts` / `createStartGreeting` / `createOnboardingFallbackGreeting` 的断言**保留**(首页问候语仍在),只删 `isCurrentOnboardingRequest` 相关的那部分。 + +**4.3 必须新增的测试(补回总数,且是本轮的合同)** + +- `frontend/tests/chat-answer-split.test.ts`:无 H2 原样返回;第一个 H2 切分正确;围栏内 `##` 不切;`headings` 抽取正确;`## 技法审计表` 与 `## 现代生活` 都落在 `report` 侧。 +- `frontend/tests/chat-answer-detail.test.ts`(或并入既有渲染合同套件):折叠块默认收起;`` 带章节摘要;流式态不渲染 children 且不出现 spinner / 骨架 / 「正在加载」字样;`report` 为空时不渲染折叠块。 +- 首页合同:新增断言锁死「首页不再渲染主题卡、不再请求 `/api/onboarding`、`bootstrapPrepareReady` 不含 onboarding 项」。 +- 领域注册表搬家后的新套件(4.2 第二条)。 + +**4.4 文档** + +- `frontend/DESIGN.md`:新增「回答的两层可见性」一节(口语层 / 折叠层的判定规则、`` 文案规则、流式期间的表现),并把首页去掉主题卡后的版式写进首页那一节。同一提交内完成。 +- `CHANGELOG.md`:一条日期 + 一句话标题 + 变更要点(首页主题卡下线;回答默认只展开口语层)。注明 Skill 版本**不**bump(本轮不改 Skill)。 +- `docs/tasks/PROGRESS-chat-reading-load-20260915.md`:做了什么、测试三个数字(基线 / 改后)、删了哪些断言、新增哪些、偏离与原因。 +- `docs/tasks/README.md`:把本任务书那行状态从「待领取」改成「执行中 / 待验收」;同时处理 §3.1 说的 BUG-630 那一行。 + +**验收标准(任务 4)** + +- `npm test` 的 tests 总数 **≥ 基线**,fail=0。 +- PROGRESS 里有「基线 / 改后」两组数字和删/增断言的对照清单。 +- `./node_modules/.bin/tsc --noEmit` 0 错,`npm run lint` 0 error。 +- `npm run build` 后 `/` 仍是 `○ Static`,首屏 gzip 记录在 PROGRESS,变化 ±2% 内。 + +--- + +## 6. 让步顺序 + +被挡住时按这个顺序退,退到哪一步写进 PROGRESS: + +1. 任务 2.4「流式期间不渲染折叠 children」若造成闪烁或状态错乱,**先放弃这条优化**,改为始终渲染(收起态交给浏览器隐藏)。折叠本身不让。 +2. 任务 3 若导致 voice 合同套件大面积红且一轮内收不住,**把任务 3 整体拆到下一轮**,本轮只交任务 1 + 2 + 4。渲染层折叠独立生效,不依赖文案改动。 +3. 任务 1.3 删接口若牵出 Mastra agent 注册或模型目录的连带失败,**保留 `route.ts` 与 agent 文件但断开前端调用**(前端不再请求即可拿到全部收益),把未删干净的部分写进根目录 `BLOCKED.md`。 +4. `generalGuidedJyotishTopics` 若被别处引用,保留该模块,只删首页引用。 +5. **绝不让步**:骨架内容不得删减;完整原文必须仍然入库、可复制、可导出;`guided_topic` 枚举值不得从 schema 移除;不得动数据库结构。 + +--- + +## 7. 开工前置命令 + +```bash +cd /workspace/Jyotisha +git status -sb | head -1 # 确认当前分支,主检出常被别的会话切走 +git fetch origin --prune +git worktree add -b codex/chat-reading-load-20260915 \ + .worktrees/chat-reading-load-20260915 origin/staging +cd .worktrees/chat-reading-load-20260915/frontend +npm ci +npm test 2>&1 | tail -20 # 基线三个数字,抄进 PROGRESS +./node_modules/.bin/tsc --noEmit # 基线应为 0 错 +npm run lint # 基线应为 0 error +``` + +本轮是纯前端改动,不要求跑 `scripts/pre_work_check.py`(`AGENTS.md` §9 末句),但 §2 分支模型与 §3 工作树规则照常。 + +交付: + +```bash +git push origin HEAD:staging # 快进推送,触发 backend-quality-gate +``` + +推完核对远端 SHA;远端验证失败不得声称已交付。 + +--- + +## 8. 串行与依赖 + +- 本轮改 `frontend/src/app/page.tsx` 与 `frontend/src/components/chat-message-content.tsx`。开工前确认没有别的轮次同时在改这两个文件(`docs/tasks/README.md` 状态板上「执行中」的行)。若有,**本轮排在其后**。 +- 与 `TASK-report-provenance-binding-20260915.md` 无文件重叠,可并行。 + +--- + +## 9. BUG 编号起点 + +- `origin/staging@ebd5ba68` 的 `docs/BUG_HISTORY.md` 当前最大号 = **BUG-694**。 +- 本任务书是产品改造,**不预分配 BUG 编号**。执行中若发现或引入回归,从 **BUG-695** 起连续编号,并按 `AGENTS.md` §5 在同一变更里补 `docs/BUG_HISTORY.md`。 +- 开工时请再核一次最大号——可能已有别的轮次占用。