# PROGRESS · 首页开场语改成一行问候(2026-09-17) - 基线 commit:`b51e7aff`(`origin/staging`,2026-09-17 取) - 分支:`codex/starter-greeting-20260917`,worktree `.worktrees/starter-greeting-20260917` - 模式:直接执行(产品拍板,非 Bug,不占 BUG 号,未写 `docs/BUG_HISTORY.md`) - 未推送。 ## 1. 做了什么 | # | 改动 | 文件 | | --- | --- | --- | | 1 | 开场语从「称呼行 + 追问句」五时段十五条,换成三个池子的单行问候 | `frontend/src/lib/starter-greeting.ts` | | 2 | `createOnboardingFallbackGreeting` 改成 `欢迎,{名字}` | 同上 | | 3 | `createStartGreetingParts` 返回 `{ text }`,两个导出都多一个 `hasSessions` 形参 | 同上 | | 4 | `StarterGreeting` 删掉 `

`,只留 `

{starterGreeting.text}

` | `frontend/src/components/starter-home.tsx` | | 5 | `hasSessions` 由既有 `sessions` 列表派生(`sessions.some((session) => session.id !== activeSession?.id)`),未新增 `useState` / `useRef` | `frontend/src/app/(app)/page.tsx` | | 6 | 删 `.starter-salutation` 规则;`.starter-greeting-block h1` 字号 `clamp(26px, 3.2vw, 34px)` → `clamp(24px, 2.8vw, 30px)`,`line-height` 1.3 → 1.25;`--font-display` / 500 / `-.02em` / `text-wrap: balance` 保持;`.starter-greeting-block` 未动 | `frontend/src/app/globals.css` | | 7 | 普通输入框占位符末行 `例如:未来半年是否适合换工作?` → `想聊什么都可以`(其它分支未动) | `frontend/src/lib/rectification-session-composer-guard.ts` | | 8 | DESIGN / VOICE / CHANGELOG / 任务索引 | `frontend/DESIGN.md`、`frontend/docs/VOICE.md`、`CHANGELOG.md`、`docs/tasks/README.md` | ### 选择规则(实现口径) 给定 `(name, now, variantSelection ∈ [0,1), hasSessions)` 确定性地得到一行: 1. 时段池按 `now.getHours()`(早晨 5–11 / 中午 11–14 / 下午 14–18 / 傍晚 18–23 / 深夜其余),沿用改动前的划分; 2. 星期池按 `now.getDay()`,只有周一 / 周五 / 周六 / 周日各一条,其余星期不贡献条目; 3. 回访池只在 `hasSessions` 为真时拼进去; 4. 三池按「时段 → 星期 → 回访」顺序拼成一个数组;`name.trim()` 为空时先滤掉所有带 `{名字}` 的条目(时段池每段都留有不带称呼的条目,所以结果永不为空); 5. 下标 `clamp(floor(variantSelection * pool.length), 0, pool.length - 1)`,再把 `{名字}` 换成称呼。 句尾无句号;`今天过得怎么样?` 与 `最近还好吗,{名字}?` 保留问号。 ## 2. 测试数字 | 项 | 基线 `b51e7aff` | 本轮 | 结论 | | --- | --- | --- | --- | | `./node_modules/.bin/tsc --noEmit` | 0 错 | 0 错 | 通过 | | `npm run lint` | 0 error / 118 warning | 0 error / 118 warning | 通过(warning 数量未变,未顺手修) | | `npm test` 总数 | 3458 | 3465 | +7 | | `npm test` pass | 3407 | 3414 | +7 | | `npm test` fail | 36 | 36 | 失败清单逐条相同(见下) | | `npm test` skipped | 15 | 15 | — | | `next build` 的 `/` | `○ Static` | `○ Static` | 通过 | | 首屏 gzip(`.next/static` 全部 `*.js` 逐个 gzip 后字节合计,68 个文件,同机同命令各测一次) | 1,450,864 B | 1,450,774 B | −90 B,−0.006%,在 ±2% 内 | 失败清单逐条比对:基线 36 条与本轮 36 条排序后 `diff` 为空(`admin code functions…`、`rectification agent maps setup failures…`、`generating does not disable the textarea…`、若干 `database` / `ephemeris` / `live staging sync` 套件等)。全部是无 Docker / 无数据库 / 无外部引擎的既有环境缺口,与本轮无关。 `next build` 环境备忘:worktree 里把 `node_modules` 软链到 `.worktrees/accept-20260916/frontend/node_modules` 时,Turbopack 直接 panic(`Symlink [project]/frontend/node_modules is invalid, it points out of the filesystem root`)。按开工说明改成 `rm node_modules && npm ci` 后构建正常。基线数字的取法:`git diff > /tmp/work.patch` → `git checkout -- frontend/` → 构建取数 → `git apply /tmp/work.patch`(未用 `git stash`,AGENTS §3.5)。 ## 3. 被改的既有断言(原值 / 新值 / 原因) | 文件 · 测试 | 原值 | 新值 | 原因 | | --- | --- | --- | --- | | `tests/onboarding-presentation.test.ts` · `terminal and fallback greetings follow the current profile name` | `createStartGreeting("周宁", SUNDAY(8), 0)`;`fallbackGreeting === "周宁,从你此刻最关心的问题开始吧。"` | `createStartGreeting("周宁", SUNDAY(8), 0.25)`(= `早上好,周宁`);`fallbackGreeting === "欢迎,周宁"` | 时段池第一条改成不带称呼的「早上好」,要取带称呼的条目得换下标;兜底问候按产品口径改成单行。两条「必须带当前称呼、不得带旧称呼」的断言原样保留,另加了逐字相等断言,强度不降 | | 同上 · `the starter hero splits one time-aware greeting…` → `the starter greeting is one line drawn from daypart, weekday and returning pools` | 断言 `parts.salutation === "中午好,周宁。"`、`parts.question === "现在最想理清哪件事?"`、两半拼起来等于整行、每时段至少 12 条不同问句 | 断言 `parts.text` 按下标取到「中午好」/「中午好,周宁」,`createStartGreeting` 与 `parts.text` 相等,`variantSelection` 为 1 / 0.999999 不越界 | 「招呼语 + 问句」两半的形状被产品取消,问句池不再存在,原断言的被测对象没了。边界不越界这一条原样保留 | | 同上 · `the starter hero stays at a salutation and a question` → `the starter hero is one heading and nothing else` | 断言 `

{starterGreeting.salutation}

` 与 `

{starterGreeting.question}

` 同时存在 | 断言只剩 `

{starterGreeting.text}

`,且 `starter-salutation`、`starterGreeting.salutation|question` 在源码与 `globals.css` 里都不得出现 | 副标题行连同它的样式一起下线;断言从「必须有两行」改成「只有一行且不得有第二行」,是收紧不是放松 | | `tests/consultation-entrypoint.test.ts` · `the starter heading is drawn per visit…` | `

{starterGreeting.question}

` | `

{starterGreeting.text}

` | 同上;「每次进首页重抽一次」(`useState(() => Math.random())` + 离开首页重抽)两条断言原样保留 | | `tests/consultation-entrypoint.test.ts` · `the starter greeting keeps the display stack and display weight` | `font-size: clamp(26px,` | `font-size: clamp(24px, 2.8vw, 30px);`,并新增 `line-height: 1.25;` | 单行之后不需要 34px 上限那一档。原断言只匹配前缀,新断言匹配完整 clamp 并多钉一条 `line-height`,强度提高;`--font-display` / `font-weight: 500` / 不得是 `--font-body` 三条原样保留 | | `tests/chat-composer-queue.test.ts` · `a queued card renders above the live textarea` | fixture `placeholder: "例如:未来半年是否适合换工作?"` | `placeholder: "想聊什么都可以"` | 只是渲染 `ChatComposer` 时传的样例 prop,不是被断言的对象;跟着产品文案更新以免留下过期字面量。断言本身未动 | ## 4. 新增测试(`tests/onboarding-presentation.test.ts`,3 条 → 10 条) | 测试 | 覆盖 | | --- | --- | | `late night has its own three lines and none of them asks a follow-up` | 深夜三条逐字;23 点与 4 点同属深夜 | | `a blank name leaves only the lines that carry no name` | 名字为空(含全空白)只出不带称呼的条目;周一条目与回访池整条不可达;结果非空、不残留 `{名字}`、不以逗号收尾 | | `the weekday pool only opens on its own day` | 周五能选到 `周五快乐`,周三选不到且池子恰好 3 条;周一 / 周日各自的条目可达 | | `the returning pool needs at least one session behind the account` | `hasSessions=false` 时四条回访句一条都选不到(池子 3 条);`hasSessions=true` 时四条全部可达(池子 7 条) | | `the same inputs always produce the same line` | 同一 `(name, now, variantSelection, hasSessions)` 连调两次结果相同 | | `no greeting line uses a metaphor or trails off into a sentence` | 7 天 × 24 小时 × 2 种称呼 × 2 种 `hasSessions` × 100 个 selection:任何条目不含 `星` `月` `陪`、不以 `。` 结尾、长度 ≤ 12 | | `the greeting knows whether the account already has sessions` | `hasSessions` 必须由既有 `sessions` 列表派生,`page.tsx` 不得另开状态 | `frontend/tests/home-shell-growth-contract.test.ts` 未改动且仍绿:`page.tsx` 只多了一行 `const starterHasPriorSessions = …`,`useState` / `useRef` 计数不变。 ## 5. 环境缺口 - 无登录态、无浏览器走查:真实首页上「一行问候居中、不再一大一小、占位符文案」的观感未在浏览器里确认,仅有 `next build` + 源码/样式合同。 - 本轮未跑 `npm run test:db`(未动数据库结构)、未跑 Python 测试(无 Python 改动)。