Files
Jyotisha/docs/tasks/TASK-chat-reading-load-20260915.md
T
Jesse_ChenandClaude Fable 5 899f955d4b docs(tasks): fold two mobile blockers into the reading-load round, brief touch targets and breakpoints
2026-09-15 的 UI 走查(web + 移动端)结果落盘。

并入 TASK-chat-reading-load:折叠层里的四列表在窄屏没有重排规则
(767 的卡片化只认三列,width:100% 又让 overflow-x 永不触发),它是
折叠能不能落地的前置;全局 input/select 是 14px,iOS 聚焦会把整页放大,
命中整条注册与资料录入漏斗。

新单 TASK-mobile-touch-and-breakpoints(BUG-695~697):消息操作按钮
命中区 27×34 且相邻只隔 1px;CSS 平板上限 900px 与 sidebar-state.ts 的
1024 不一致,901–1023 是混合态;报告域 720/760/860 三刀互不对齐,
761–860 目录已塌、正文还是桌面。含断点白名单契约测试作为防复发。

另加 docs/testing/ios-keyboard-composer-20260915.md:html/body overflow
hidden + 100dvh 外壳 + sticky 输入框 + interactiveWidget 只有 Android 认,
是键盘遮挡的风险形状,但代码断不了真假,交真机确认后再决定是否立单。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
2026-09-15 04:02:50 +00:00

422 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 首页主题卡的真实成本
| 事实 | 定位 |
| --- | --- |
| 「从一个主题开始」六张卡 = `<section className="starter-themes">` | `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 36 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 所有输入框 14pxiPhone 一聚焦就整页放大
`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` 的「36 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`
- 删除 `<section className="starter-themes">` 整段(含 `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``<details className="answer-detail">` + `<summary>`),样式走 `globals.css` 新增的 `.answer-detail*`,视觉上与 `.technique-audit` 同族但不复用同一个 class。
- `<summary>` 文案:主标「完整分析」,副标用 `<small>` 写章节名,**不要只写「展开」**。例:`完整分析` + `统一参数、事业、技法审计 等 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` 非空时,渲染 `<AnswerDetailDisclosure>`,内部渲染 `report` 的 markdown。
5. 现有 `showFoldedAudit``TechniqueAuditDisclosure` **移进折叠块内部**`report` 为空时仍留在原位,闲聊场景不受影响)。
**2.4 流式期间的行为**
- `streaming === true` 时,折叠块**保持收起且不渲染 children**`<summary>``<small>` 位置显示一行安静的文字(例:`正在写…`)。不得出现 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 36 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, /36 heading-free sentences/)`
**验收标准(任务 3**
- `npm test` 里 voice 相关套件 fail=0。
- 三处口径互相一致,`grep -rn "3-6 sentence\|36 " 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`(或并入既有渲染合同套件):折叠块默认收起;`<summary>` 带章节摘要;流式态不渲染 children 且不出现 spinner / 骨架 / 「正在加载」字样;`report` 为空时不渲染折叠块。
- 首页合同:新增断言锁死「首页不再渲染主题卡、不再请求 `/api/onboarding``bootstrapPrepareReady` 不含 onboarding 项」。
- 领域注册表搬家后的新套件(4.2 第二条)。
**4.4 文档**
- `frontend/DESIGN.md`:新增「回答的两层可见性」一节(口语层 / 折叠层的判定规则、`<summary>` 文案规则、流式期间的表现),并把首页去掉主题卡后的版式写进首页那一节。**另加一条表格规则**:三列表在窄屏卡片化、四列及以上横向滚动并带滑动提示,以及表单输入 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-695697 已由 `TASK-mobile-touch-and-breakpoints-20260915.md` 占用),并按 `AGENTS.md` §5 在同一变更里补 `docs/BUG_HISTORY.md`。
- 开工时请再核一次最大号——可能已有别的轮次占用。