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

31 KiB
Raw Blame History

TASK · 首页主题卡下线 + 聊天回答两层化 + 两处移动端阻塞(降低阅读负担)

  • 日期:2026-09-15
  • 基线 commitorigin/staging @ ebd5ba68docs: 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.tsxStarterHomestarter-themes-heading 那一段
卡片文案每个用户现调大模型生成,一次写十个领域的问题 frontend/src/app/api/onboarding/route.tsgenerateTextexport const maxDuration = 60
前端为它等:单次超时 50 s、最多 3 次尝试 frontend/src/lib/onboarding-client.tsdefaultPolicyrequestTimeoutMs: 50_000, maxAttempts: 3
首页揭幕被它挡着 frontend/src/app/page.tsxbootstrapPrepareReadyonboardingSettled: 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.tsnatalSpokenReportContract

  • 「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_384frontend/src/lib/agent-generation-settings.ts

于是用户问一句「我事业怎么样」,读完三句人话,第二屏立刻撞上度数 / 宫位 / 分盘 / Dasha 边界表

而渲染层对这一切是完全平铺的:frontend/src/components/chat-message-content.tsxChatMessageContent 把整篇 markdown 一次渲染到底,唯一的折叠是 TechniqueAuditDisclosure,而且 showFoldedAudit = rows.length > 0 && split.rows.length === 0 —— 正文里已经有那张表时反而不折

2.3 折叠层里的宽表在手机上读不了

移动端的表格卡片化重排只认三列表frontend/src/app/globals.css@media (max-width: 767px) 内):

.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),统一参数那节也会出宽表。它们落到基础规则:

.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 规则:

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 textareavar(--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.tsguided_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-headingstarter-theme-accordionstarter-theme-card、末尾 onboardingError 那行 starter-note)。
  • StarterHomeProps 删除 starterThemesstarterSuggestionsstartSuggestedConsultationonboardingError 四个 prop,以及不再使用的 StarterHomeTheme / StarterHomeSuggestion 类型导出与 ConsultationEntrypoint / Theme import。
  • 保留 starter-heroproduct-entrypoints(今日星语 / 生时校正)两段不动。

1.2 页面层frontend/src/app/page.tsx

  • 删除 starterThemesstarterSuggestions 两个派生值与 generalGuidedJyotishTopics 的 import。
  • 删除 onboarding state、setOnboardingonboardingErroronboardingPending,以及调用 requestOnboardingWithRecovery 的那个 useEffect(含 activeOnboardingRequestIdentityonboardingRequestIdentityisCurrentOnboardingRequestOnboardingAuthenticationError 的相关引用)。
  • 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.tsfrontend/src/lib/consultation-domain-registry.ts:它们携带 claimBoundary / strictWorkflowRoute / evidencePreview,是领域注册表合同的一部分,个人报告侧仍在用。若 lint 报出 generalGuidedJyotishTopics 完全无引用,把它删掉、defaultGuidedJyotishTopicshome-types.tsthemes 导出保留。
  • 保留 frontend/src/lib/consultation-entrypoint.tsguided_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

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.tsREPORT_HEADING.foundation),但按「任意 H2」切可以在模型漂移时自然降级,不会突然整篇不折。
  • 代码围栏内的 ## 不算切点,复用 frontend/src/lib/chat-markdown-split.tsFENCE 的同一判断方式。
  • 没有 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(流式时仍走 StreamingMarkdownmemo 优化不得破坏)。
  4. report 非空时,渲染 <AnswerDetailDisclosure>,内部渲染 report 的 markdown。
  5. 现有 showFoldedAuditTechniqueAuditDisclosure 移进折叠块内部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.tsxonboarding-chat-message.tsx。后者的文本没有 H2,行为必须零变化。
  • 个人报告页走 personal-report-document-view.tsx不经过本组件,不得受影响。
  • 会话落库、复制按钮、重新生成拿到的仍是完整原文,不是口语层。

2.6 折叠层里的宽表必须在手机上可读(本轮前置,不得延后)

处置分两档,按列数分流,不要动现有的三列规则(它有测试、工作正常):

  • 三列表:保持现状的卡片化重排,一行也不改。第三列是说明性长文本,塌成第二行读起来像图注,这是它成立的原因。
  • 四列及以上:改为真正可横向滚动,而不是继续卡片化。四字段 × N 行的表格塌成卡片会变成一条极长的竖列(Yoga 表 8 行 × 4 字段 = 32 行),比横滑更难读;而且用户是主动点开「完整分析」才看到它的,横滑是可接受的代价。

@media (max-width: 767px) 内加:

.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 表可横向滑动、不挤压、不撑破容器;页面本身不出现横向滚动条.conversationoverflow-x: hidden 必须仍然成立)。
  • 闲聊类回答(无 H2)渲染与改动前逐字一致。
  • onboarding 打字机消息渲染与改动前逐字一致。
  • 复制该条消息,得到的是完整原文(含骨架)。
  • 刷新页面重新读历史会话,折叠态一致(默认收起)。
  • 流式期间不出现 spinner / 骨架;折叠块在流式结束前不展开。

任务 3 · 口语层的形状(文案合同)

3.1 frontend/src/mastra/product-voice.tsnatalSpokenReportContract

把「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.tsAfter a 3-6 sentence spoken reply with no heading… 那句。
  • frontend/src/mastra/skill-binding.tspresent its Level 2 report template in the chat body after a 3-6 sentence spoken reply 那句。
  • frontend/src/mastra/index.tsconsultationSpokenHeadingRule("natal") 相关文案。

3.3 frontend/docs/VOICE.md 新增一节「口语层的形状」,把 3.1 的规则和一组好/坏对照写进去。坏例用「一坨连续段落 + 立刻进度数」,好例用「结论 + 要点 + 下一步」。

3.4 frontend/tests/consultation-voice-contract.test.tsfrontend/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-accordionsourceBetween 断言。
    • 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.tsfrontend/tests/onboarding-route-fake.tsfrontend/tests/onboarding-client.test.tsfrontend/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/onboardingbootstrapPrepareReady 不含 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-sizevar(--type-body-sm)(14px) 改为 var(--type-body-md)(16px)。

  • 优先做无条件提升(所有视口一致 16px)。min-height: 44px 已经在了,行高和内边距不用动,桌面端视觉变化很小。
  • 若产品在桌面端观感上不接受 16px,退而求其次只在触屏下提升:
    @media (pointer: coarse) { input:not(), select:not() { font-size: var(--type-body-md); } }
    
    两种都可以,选哪种写进 PROGRESS。不接受的是维持 14px。

5.2 不得破坏既有的两处安全值:.otp-input20px)、.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. 开工前置命令

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.pyAGENTS.md §9 末句),但 §2 分支模型与 §3 工作树规则照常。

交付:

git push origin HEAD:staging              # 快进推送,触发 backend-quality-gate

推完核对远端 SHA;远端验证失败不得声称已交付。


8. 串行与依赖

  • 本轮改 frontend/src/app/page.tsxfrontend/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@ebd5ba68docs/BUG_HISTORY.md 当前最大号 = BUG-694
  • 本任务书是产品改造,不预分配 BUG 编号。执行中若发现或引入回归,从 BUG-698 起连续编号(BUG-695697 已由 TASK-mobile-touch-and-breakpoints-20260915.md 占用),并按 AGENTS.md §5 在同一变更里补 docs/BUG_HISTORY.md
  • 开工时请再核一次最大号——可能已有别的轮次占用。