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
422 lines
31 KiB
Markdown
422 lines
31 KiB
Markdown
# 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 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`
|
||
|
||
- 删除 `<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 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`(或并入既有渲染合同套件):折叠块默认收起;`<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-695~697 已由 `TASK-mobile-touch-and-breakpoints-20260915.md` 占用),并按 `AGENTS.md` §5 在同一变更里补 `docs/BUG_HISTORY.md`。
|
||
- 开工时请再核一次最大号——可能已有别的轮次占用。
|