# 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。 2026-09-15 的 UI 走查又带出两处**移动端**问题,同批并入(任务 2.6、任务 5):折叠层里的四列表在手机上读不了(它是折叠能不能落地的前置),以及全局输入框 14px 导致 iPhone 聚焦时整页放大。 全部任务可以在同一分支同一轮做完,互不依赖,共用一次测试基线测量(任务 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 折叠层里的宽表在手机上读不了 移动端的表格卡片化重排**只认三列表**(`frontend/src/app/globals.css`,`@media (max-width: 767px)` 内): ```css .markdown-table table:has(thead th:nth-child(3):last-child) thead { /* 视觉隐藏表头 */ } .markdown-table table:has(thead th:nth-child(3):last-child) tbody tr { display: grid; … } ``` 技法审计表(技法 / 状态 / 说明)正好三列,重排正常。但 `natalSpokenReportContract` 还要求一张 **四列的 Yoga 表**(`name / formation / strength or hit-miss / domain effect`),统一参数那节也会出宽表。它们落到基础规则: ```css .markdown-table { overflow-x: auto; } .markdown-table table { width: 100%; } /* ← 与上一行互相抵消 */ .markdown-table th, .markdown-table td { word-break: keep-all; } /* ← 中文不断词 */ ``` `width: 100%` 让表格永远不超出容器,`overflow-x: auto` 于是永不触发;四列被压成约 85px 一柱,`keep-all` 又不许中文断词。375px 屏上要么挤成竖排单字,要么撑破容器。 **这正是任务 2 折叠层里的内容**——手机用户点开「完整分析」第一眼就撞上,所以它是折叠能不能落地的前置条件,并进本轮。 ### 2.4 所有输入框 14px,iPhone 一聚焦就整页放大 `frontend/src/app/globals.css` 的全局 input 规则: ```css input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"])…, select:not([class^="ant-"])… { min-height: 44px; …; font-size: var(--type-body-sm); } ``` `--type-body-sm = 14px`,且移动端媒体查询里**没有任何一处把它提上去**(`grep` 过 `@media (max-width: 767px)` 之后的全部规则)。 iOS Safari 对字号小于 16px 的输入框会在聚焦时自动放大页面,并且不会自己缩回去。命中的是整条注册与资料录入漏斗:登录邮箱、姓名、出生日期、出生时间、出生地搜索。 已经躲过的只有两处,改动不得破坏它们:`.otp-input` 是 20px,`.composer textarea` 是 `var(--type-body-md)`(16px)。 ### 2.5 根因一句话 - 首页:一个低价值入口押着一次 LLM 调用和整个揭幕闸门。 - 聊天:内容合同要求「全量输出」,渲染层没有「默认可见性」这一层,于是全量输出 = 全量可见。 - 移动端:宽表的响应式规则只覆盖了三列这一种形状;输入框字号沿用了正文小号,没有为触屏留出 16px 的下限。 --- ## 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.6 折叠层里的宽表必须在手机上可读(本轮前置,不得延后)** 处置分两档,按列数分流,**不要动现有的三列规则**(它有测试、工作正常): - **三列表**:保持现状的卡片化重排,一行也不改。第三列是说明性长文本,塌成第二行读起来像图注,这是它成立的原因。 - **四列及以上**:改为**真正可横向滚动**,而不是继续卡片化。四字段 × N 行的表格塌成卡片会变成一条极长的竖列(Yoga 表 8 行 × 4 字段 = 32 行),比横滑更难读;而且用户是主动点开「完整分析」才看到它的,横滑是可接受的代价。 在 `@media (max-width: 767px)` 内加: ```css .markdown-table table:not(:has(thead th:nth-child(3):last-child)) { width: auto; min-width: max-content; /* 让 .markdown-table 的 overflow-x: auto 真正生效 */ } ``` 并给 `.markdown-table` 加一个**可横滑的视觉提示**(右缘渐隐或 `scroll-shadow`,用既有 token,不引新库、不加 JS),否则用户不知道右边还有内容。`word-break: keep-all` 保留——横滑模式下它反而是对的。 同一处理必须同时覆盖:聊天里的宽表、`.technique-audit-panel`(它已有三列规则,不受影响,确认即可)。**个人报告页的表格不在本轮范围**(走 `.personal-report-*` 自己的规则)。 **验收标准(任务 2)** - 一条本命类回答:口语层直接可读,下面一个收起的「完整分析」块;展开后章节顺序与原文完全一致,一个字不少。 - 375px 宽视口下展开「完整分析」:三列技法审计表仍是卡片式;四列 Yoga 表可横向滑动、不挤压、不撑破容器;页面本身**不出现横向滚动条**(`.conversation` 的 `overflow-x: hidden` 必须仍然成立)。 - 闲聊类回答(无 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`:新增「回答的两层可见性」一节(口语层 / 折叠层的判定规则、`` 文案规则、流式期间的表现),并把首页去掉主题卡后的版式写进首页那一节。**另加一条表格规则**:三列表在窄屏卡片化、四列及以上横向滚动并带滑动提示,以及表单输入 16px 下限的理由(iOS 聚焦放大)。同一提交内完成。 - `docs/testing/chat-reading-load-20260915.md`:新建真机清单,至少含——iPhone Safari 上逐个聚焦表单输入框确认不放大;375px 下展开「完整分析」确认宽表可横滑且页面不横向滚动;折叠块在流式期间不展开、不出现转圈。 - `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% 内。 --- ### 任务 5 · 输入框字号 14 → 16(iOS 聚焦放大) **5.1** `frontend/src/app/globals.css` 的全局 input / select 规则,`font-size` 从 `var(--type-body-sm)`(14px) 改为 `var(--type-body-md)`(16px)。 - 优先做**无条件提升**(所有视口一致 16px)。`min-height: 44px` 已经在了,行高和内边距不用动,桌面端视觉变化很小。 - 若产品在桌面端观感上不接受 16px,退而求其次只在触屏下提升: ```css @media (pointer: coarse) { input:not(…), select:not(…) { font-size: var(--type-body-md); } } ``` 两种都可以,选哪种写进 PROGRESS。**不接受的是维持 14px。** **5.2** 不得破坏既有的两处安全值:`.otp-input`(20px)、`.composer textarea`(`--type-body-md`)。 **5.3** 排除项不变:`[type="radio"]`、`[type="checkbox"]`、antd(`[class^="ant-"]` / `.ant-picker input`)——admin 是 antd + Refine,不在本轮范围。 **5.4** 改完复查这几个表单的换行有没有被挤坏(字号变大会改变换行点):登录邮箱 + 验证码、姓名、出生日期、出生时间、出生地搜索下拉、设置弹窗里的各字段。桌面和 375px 各看一遍。 **验收标准(任务 5)** - `grep -nE "font-size: var\(--type-body-sm\)" frontend/src/app/globals.css` 的结果里不再包含全局 input/select 规则。 - 375px 视口下逐个聚焦上述输入框,页面不发生缩放(**真机项**:模拟器不一定复现 iOS 的放大行为,做不到就写进 `docs/testing/` 清单,不得写成「通过」)。 - 表单在 375px 与桌面两种宽度下无换行崩坏、无溢出。 --- ## 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. 任务 2.6 的横滑提示(渐隐 / scroll-shadow)若在深色主题下做不干净,**先只交「能横滑」**,提示留到下一轮,写进 PROGRESS。能横滑本身不让。 6. 任务 5 若无条件 16px 在桌面端观感上被否,退到 `(pointer: coarse)` 版本。**维持 14px 不是选项。** 7. **绝不让步**:骨架内容不得删减;完整原文必须仍然入库、可复制、可导出;`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` 无文件重叠,可并行。 - **`TASK-mobile-touch-and-breakpoints-20260915.md` 必须排在本轮之后**:两轮都改 `frontend/src/app/globals.css`,且那一轮的任务 2.2 依赖本轮是否已删掉主题卡。 --- ## 9. BUG 编号起点 - `origin/staging@ebd5ba68` 的 `docs/BUG_HISTORY.md` 当前最大号 = **BUG-694**。 - 本任务书是产品改造,**不预分配 BUG 编号**。执行中若发现或引入回归,从 **BUG-698** 起连续编号(BUG-695~697 已由 `TASK-mobile-touch-and-breakpoints-20260915.md` 占用),并按 `AGENTS.md` §5 在同一变更里补 `docs/BUG_HISTORY.md`。 - 开工时请再核一次最大号——可能已有别的轮次占用。