fix(home): request the daily card on the same condition that renders it
Independent Staging Quality Gate / validate (pull_request) Successful in 10m37s
Independent Staging Quality Gate / publish (pull_request) Has been cancelled

The daily starlanguage card sat on "正在结合你的星盘写今天的星语。" forever for
every account whose birth time was usable. Its effect bailed out on
birthTimeDisplayState(profile), which returns a value precisely when the
birth time is candidate, accepted or confirmed, so the request went out
only for accounts that had nothing to read. The guard predates the Agent
rewrite and was masked by the written-in client fallback that rewrite
deleted. It now gates on personalChartAvailable, the same fact the card
uses to render personal content, and retries once before admitting that
today has no card.

The route stops letting one engine call take the whole card down
silently: /api/chart fails into a named reason like the other four
layers, and the engine and agent budgets leave room for a cold chart and
an observed 30s generation inside the 60s ceiling.

The home also had three greeting implementations. The hero heading drew
from a static pool while the time-aware greeting lived elsewhere and the
Agent's own greeting was overwritten client-side into a field nothing
rendered. createStartGreeting now exposes its salutation and question
halves, the hero uses both, and the served greeting reaches the hero note.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-18 11:36:26 +08:00
parent 5687182980
commit 4d1a77be9e
8 changed files with 199 additions and 106 deletions
+19
View File
@@ -3941,3 +3941,22 @@
- 相关记录:ERR-103`docs/research/pre_work_error_ledger.md`,同一 Compose 现象的误诊,本次给出真实根因)、ERR-105(同一台跳板机磁盘耗尽的基础设施记录)、BUG-264(本次被卡住无法发布的修复)
- 复发自:无
- 修复版本:本地未提交候选
## BUG-267 | “今日星语”永久停在“正在结合你的星盘写今天的星语”:请求被一个反向的出生时间守卫拦住,从未发出
- 状态:resolved(本地修复,待提交与发布)
- 首次发现:2026-08-18
- 最近更新:2026-08-18
- 影响面:`/` 首页“今日星语”卡片、首页 hero 的问候与标题、`/api/daily-starlanguage` 的失败归因与超时预算、Onboarding Agent 生成的欢迎语。
- 用户现象:staging 上出生资料完整的账号打开首页,“今日星语”卡片一直显示“正在结合你的星盘写今天的星语。”,永远不出现 Agent 写的文案。同一次反馈里还问到:首页那三个问题是不是写死的,以及首页标题“今天想先理清什么?”为什么没换成登录后按时段问候的形式。
- 触发条件:`birth_time_status``candidate``accepted``confirmed` 且已有可用出生时间的任何账号打开首页。也就是说,越是资料完整的账号越必然命中。
- 根因:三处独立问题,都在同一屏上。
1. 每日星语 effect 的守卫是 `if (!hydrated || !profileComplete || birthTimeDisplayState(profile)) return;``birthTimeDisplayState` 恰好在出生时间可用(`candidate`/`accepted`/`confirmed`)时返回非 null,因此条件的实际含义是「星盘可用时不要请求」,与卡片的渲染条件 `personalChartAvailable` 完全相反,请求从未发出,state 永远停在 `pending`。这个守卫在 BUG-265 之前就存在,但那时客户端还有 `buildDailyStarlanguageCard` 写死兜底把空状态遮住了;BUG-265 删掉兜底、保留守卫,于是暴露成永久等待态。
2. `/api/daily-starlanguage``/api/chart` 是唯一没有 `.catch()` 的引擎调用(其余四层都有),且 `engineTimeoutMs` 只有 8 秒、`agentTimeoutMs` 只有 30 秒。线上实测冷路径端到端 30.6 秒,紧贴 30 秒上限;首次调用在 9.5 秒就返回 `agent_generation_failed`,正是 8 秒引擎超时被当成模型失败上报。失败原因被压成同一个字符串,无法区分是引擎、模型目录还是模型输出。
3. 首页同时存在三套问候实现:`page.tsx``greetingForHour`hero 第一行)、`starter-prompt.ts` 的 8 条静态随机池(hero 的 `h1`)、`onboarding-client.ts``createStartGreeting`(按时段+称呼,只用在 onboarding 气泡)。用户要求的按时段问候在第三套里,而 hero 标题读的是第二套,所以「改了却没生效」。同时 `/api/onboarding` 返回的 Agent 欢迎语在客户端被 `greeting: createStartGreeting(presentationName)` 覆盖,而 `onboarding.greeting` 在整个页面里没有任何渲染点,等于 Agent 每次都白写一句欢迎语。
- 修复:守卫改成 `!hydrated || !profileComplete || !personalChartAvailable`,与卡片渲染个人内容的条件对齐,并在服务端报 unavailable 时延迟 5 秒重试一次后才落到失败态,卸载时清理定时器。路由给 `/api/chart``.catch()`,把生成结果改成判别联合,失败原因区分 `chart_unavailable` / `model_unavailable` / `agent_generation_failed``engineTimeoutMs` 提到 20 秒、`agentTimeoutMs` 提到 45 秒,仍在 `maxDuration = 60` 之内。问候收敛成一套:`createStartGreeting` 拆出 `createStartGreetingParts`,返回 `{salutation, question}`hero 第一行用 salutation、`h1` 用 question,选中变体在离开首页时重抽;删除 `starter-prompt.ts``greetingForHour`。客户端不再覆盖 Agent 欢迎语,`onboarding.greeting` 落到 hero 说明行并保留原静态文案作为兜底。
- 验证:线上先证明后端是好的——带登录 Cookie 直接调 staging `/api/daily-starlanguage`,冷路径 30.6 秒返回真实 `{trend, action, caution}`,第二次 1.6 秒命中 `agent_cache``/api/health` 确认部署 SHA 就是 `origin/staging` 头部,`jyotishApi` 检查为 ok,因此排除部署落后与引擎不可用。新增 4 条回归:每日星语请求条件必须与 `personalChartAvailable` 一致且源码中不得再出现 `birthTimeDisplayState(profile)`、失败必须重试一次且清理定时器、`/api/chart` 必须带 catch 且三种失败原因各自可辨、两个超时预算有下限断言;hero 断言 salutation/question 拆分与 Agent 欢迎语落点,并禁止 `starterPrompt`/`createStarterPrompt`/`greetingForHour` 复活。前端非数据库套件 1711/1724 通过,13 个失败全部是本机 Docker/PostgreSQL fixture(与 BUG-265 同一类环境失败,与本次无关);`tsc --noEmit` 与改动文件 ESLint 清洁。未做的验证:**没有在浏览器里看过修好后的首页**——本机没有 Python 引擎与模型密钥,无法起完整栈,卡片从 `pending``ready` 的实际观感、重试是否够用、以及 hero 换行后的排版都要等 staging 发布后确认。
- 防复发:一个界面元素的「取数条件」必须和它的「渲染条件」写成同一个表达式,不能一边用 `personalChartAvailable` 渲染、一边用另一个语义相反的谓词决定是否请求。删除兜底文案时必须回头检查被兜底遮住的空状态路径是否本来就是坏的——BUG-265 删兜底是对的,但没有验证删掉之后真实账号能不能拿到内容,代价是上线即空转。同一个概念(这里是「登录后的问候」)不允许存在多套并行实现,否则改动必然落在没被渲染的那一套上。Agent 生成的字段如果没有渲染点,就不要生成,更不能在客户端覆盖后还继续消耗 token。
- 相关记录:BUG-265(本次修复的直接前序:Agent 化改造正确但守卫未同步,且其「待跟进」已经预告了首屏等待态问题)、BUG-201(每日星语 effect 依赖完整 Profile 对象的既有决定,本次沿用其引用保持策略,未改依赖形状)、BUG-200(首页文案第一人称与真实性边界)
- 复发自:无
- 修复版本:本地未提交候选