Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
13 KiB
13 KiB
TASK · 天空封面第三轮:弹层只留图、印出生日期时刻城市、过场放慢、兑换弹窗改到首条付费消息(2026-09-28)
基线与依赖
- 基线:
origin/staging=be9e4dbe(天空封面五个提交 + 验收文档,已部署 staging)。开工前git fetch origin --prune,基线 SHA 写进 PROGRESS。 - 分支 / 工作树:
codex/birth-sky-pacing-20260928/.worktrees/birth-sky-pacing-20260928(Claude 已从be9e4dbe建好,空分支)。 - 进度记录:
docs/tasks/PROGRESS-birth-sky-polish-20260928.md。 - 前置任务书:
TASK-birth-sky-cover-20260928.md、TASK-birth-sky-followup-20260928.md。 - 本单会改
frontend/src/app/(app)/page.tsx,与其他同日改page.tsx的单串行:本单开工时若有别的分支在改page.tsx,先停下告知 Claude。
事实(产品 2026-09-28 staging 真机反馈)
- 星盘页「那一刻的天空」弹层难看(截图,桌面窄窗口):
- 结构是灰色毛玻璃大面板 + 标题栏「那一刻的天空 / 关闭」+ 图 + 「保存图片」;
- 面板被拉得比图高很多,图下方一大片空白灰底;
- 实现:
frontend/src/components/birth-sky/birth-sky-dialog.tsx(.birth-sky-panel/.birth-sky-header),样式birth-sky.css。
- 封面底部没有出生资料:
drawBirthSky在事实句下只画 footnote「按你填写的时间 / 按校正后的时间」。这是第一份任务书「不印任何出生资料」的结果。 - 新账号第一次建盘看不到群星过场,只看到「解锁完整咨询」弹窗:
- 兑换弹窗由
page.tsx里setOnboardingPaywallOpen(true)所在的 effect 打开,条件是starterHomeVisible && credits <= 0 && 无有效订阅; - 本人保存出生地后,首页立刻变成 starter home,所以兑换弹窗和过场在同一刻被触发;
- 兑换弹窗打开时
modalOpen为真,SidebarInset变inert(BUG-968 的设计),而过场的<dialog>渲染在Home()里,也就是在这个inert子树内部; - DESIGN §17 写「过场在兑换弹窗之上」,但这一点只在单元测试里断言过,没有在浏览器里验证过。真实原因要执行方在无头 Chrome 里复现后才能定(见 P4)。
- 兑换弹窗由
- 过场太快:
lib/birth-sky/converge.ts里汇聚 1.5 s、文字 1 s、淡出 0.5 s,总长 3 s。
决策记录(产品 2026-09-28 授权)
- 星盘页弹层只留图:去掉灰色面板、标题栏、「关闭」文字按钮。点开后,压暗的页面上只放这张图本身,按视口完整显示;图下方一个小的「保存图片」。点图外、按 Esc 关闭。
- 封面印出生资料:日期 + 时刻 + 城市。
- 这推翻了
TASK-birth-sky-cover-20260928的「不印任何出生资料」和对应的 BFF 合同测试断言。执行方不得以既有红线为由拒改。 - 只印到城市;不印区县、经纬度、时区、名字。
- 格式:
1994年5月18日 07:40 · 杭州。取不到可靠的城市就只印1994年5月18日 07:40,不猜。 - 三处都印:星盘页图片、报告封面、首次建盘过场(它们共用
drawBirthSky)。
- 这推翻了
- 过场放慢:汇聚 3 s → 文字淡入并停留 2.5 s → 淡出 1 s,总长约 6.5 s。随时点一下或按 Esc 仍然立刻结束。「天空数据 3 秒内就绪才放,否则静默跳过」这条不变。
- 「解锁完整咨询」弹窗改到第一次付费消息被拒时再弹:
- 删掉「进入 starter home 就弹」的 effect;
- 用户发消息后,服务端以 402(点数不足)拒绝时:本页第一次弹兑换弹窗(
OnboardingRedeemPaywall),之后再遇到 402 照旧打开设置弹窗的 billing 页; - 以服务端 402 为准,前端不自己预判点数。这样免费的闲聊轮(「你好」,BUG-976)和入门问题照常免费,不会被弹窗拦住;
- 402 时草稿照旧退回输入框(现有 rollback 逻辑),兑换成功后用户点发送即可,不自动重发。
硬红线
- BFF 只下发一个已格式化的
caption字符串,例如1994年5月18日 07:40 · 杭州。仍然不得下发经纬度、时区、名字、区县、原始日期字段。 page.tsx行数不增长;Home()的useState/useRef数不增长。兑换弹窗沿用现有的onboardingPaywallOpen/onboardingPaywallShown,不新增状态。- 过场、报告封面不得变成等待,沿用原预算,不加 spinner / 骨架(DESIGN §9)。
- 汇聚最后一帧仍与导出封面逐调用一致(同一个
drawBirthSky,只插值位置)。 - 不新增 npm 依赖,不改表、不加迁移。
- 改任何既有断言都写「原值 / 新值 / 原因」三栏;动组件前先 grep
frontend/tests/与tests/里按源码文本断言的合同测试。 - 隐私:测试与样张只用虚构资料(1994-05-18 07:40 UTC+8,杭州,30.27N 120.15E)。
任务分解
P1 · 星盘页弹层只留图
- 改
birth-sky-dialog.tsx与birth-sky.css:- 原生
<dialog>保留(焦点与 Esc 都靠它);::backdrop压暗(深色半透明),dialog 本身透明、无边框、无面板。 - 图
<img>(仍是画好的 PNG,iOS 长按可存):3:4,按视口取最大,宽高都不超过视口减去上下留白和按钮高度;圆角沿用 DESIGN token。 - 图下方居中一个「保存图片」,在深色底上可读(用 DESIGN 已有的深色描边按钮样式,不新造颜色);保存失败的提示文案不变。
- 右上角保留一个只有图标的 × 关闭(
aria-label="关闭"),给键盘和读屏用;点图外、按 Esc 也关闭。 - 删掉
.birth-sky-panel、.birth-sky-header及其样式,确认没有别处引用。
- 原生
- 验收:
- 无头 Chrome 在 390×844、1280×900、1280×600 三个视口各截一张(样稿虚构资料),图完整可见、下方没有空白面板,放进 scratchpad,路径写 PROGRESS;
- 测试:弹层源码不再有标题栏与面板;有 × 和「保存图片」;点 backdrop 与 Esc 关闭。
P2 · 封面印日期 + 时刻 + 城市
- 服务端(
lib/birth-sky/service-core.ts):从服务端读到的出生资料生成caption,加进lib/birth-sky/contract.ts的合同。- 日期与时刻用出生地当地时间(就是用户填的那个),24 小时制,时刻补零:
1994年5月18日 07:40。 - 城市:写一个纯函数从地点标签里取城市,规则写在函数注释里,并各有测试:
河北省 · 邯郸市 · 峰峰矿区→邯郸;- 直辖市(如
北京市 · 朝阳区)→北京; - 取不到可靠城市(空、只有省、看不懂的格式)→ 不带城市。
- 海外地名按现有标签格式定一条保守规则;拿不准就不带城市。
- 城市去掉末尾的「市」。
- 日期与时刻用出生地当地时间(就是用户填的那个),24 小时制,时刻补零:
- 画法(
lib/birth-sky/draw.ts):caption画在事实句下面,替换原来 footnote 那一行的位置。- 时间来源只在采用了校正时间时出现:caption 后面接
· 校正后;按填写时间的不加后缀(「按你填写的时间」这句去掉)。 - 字号、颜色与原 footnote 一致;
paperpalette 同样生效。
- 时间来源只在采用了校正时间时出现:caption 后面接
VOICE.md更新:去掉「按你填写的时间 / 按校正后的时间」,写入 caption 格式与「· 校正后」后缀。- 合同测试改动:
frontend/tests/birth-sky-route.test.ts里「响应不含 1994、07:40、placeLabel……」的断言,改成「只在caption里出现日期、时刻、城市;不含经纬度、时区、名字、区县、原始日期字段」,三栏说明写进 PROGRESS。tests/test_birth_sky.py如有相关断言同样处理。 - golden:
scripts/generate_birth_sky_golden.py重新生成夹具,前端测试读新夹具。 - 验收:caption 各格式一条测试;导出 PNG 与过场停留帧里都有 caption;报告封面 paper palette 有 caption;重新截三张海报(白天 / 夜里 adopted / 傍晚 paper)放 scratchpad。
P3 · 过场放慢
converge.ts:BIRTH_SKY_CONVERGE_MS = 3_000、BIRTH_SKY_TEXT_MS = 2_500、BIRTH_SKY_FADE_MS = 1_000。文字淡入从 400 ms 放到 600 ms;整层前段淡入从 200 ms 放到 400 ms;reduced-motion 淡入 600 ms、淡出 1 s。- 时间轴测试随之改数值(三栏说明);「t=0 散开、t=1 真实位置」和「停留帧 = 导出封面」两条测试原样保留。
- DESIGN §17 时序段落同步。
- 验收:重新取 t=0、汇聚过半、汇聚完成、停留四帧,路径写 PROGRESS。
P4 · 兑换弹窗改到首条付费消息被拒(含 BUG-1080)
- 先复现,再定根因(写进 BUG-1080):
- 在无头 Chrome 里搭一个最小页面:一个带
inert的容器,里面showModal()一个<dialog>,外面 portal 一个 z-index 很高的兑换弹层。截图看过场是否可见、pointerdown能不能收到。 - 同时查首页在「保存出生地 → starter home」这一刻:兑换 effect 和
startBirthSkyReveal各自何时触发、有没有别的原因让过场被跳过(例如 3 秒预算内拿不到数据)。 - 结论写进 BUG-1080:确认的就写确认,查不清的写
investigating并列出剩下的可能。
- 在无头 Chrome 里搭一个最小页面:一个带
- 修复(不依赖根因结论,都要做):
- 删掉
page.tsx里「进入 starter home 就弹兑换弹窗」的 effect。 - 402 分流:
hooks/use-consultation-run.ts里 402 时现在调用openAccountDialog("billing", { source: "insufficient-credits" })(rejectRun与另一处source: "insufficient-credits")。改成:本页第一次 402 打开OnboardingRedeemPaywall,并置onboardingPaywallShown;之后的 402 照旧打开 billing。- 建议做法:给
useConsultationRun加一个可选依赖,例如onInsufficientCredits,返回是否已处理;page.tsx传一个复用现有 ref / state 的回调,同时删掉旧 effect,保证行数不增长。执行方可换更简单的写法,但不得新增useState/useRef。
- 建议做法:给
- 过场的
<dialog>用createPortal(…, document.body)挂到SidebarInset之外,与兑换弹窗、账户弹窗同一做法(BUG-968),让它不受inert影响。 - 兑换弹窗里「入门问题已经准备好。」这句要改,因为弹出的时机已经不是入门问题做完的时候。先对照 VOICE 改成与新时机相符的一句,写进 PROGRESS 让 Claude 验收。
- 删掉
- 生时校正对话里的 402(
lib/rectification-chat-turn-run.ts)不在本单范围,保持现状。 - 验收:
- 测试:
page.tsx不再有进入首页就弹的 effect;第一次 402 打开兑换弹窗、第二次打开 billing;402 后草稿退回输入框;免费轮不触发。 membership-page.test.ts的 “finished starter questions open the redeem paywall…” 按新时机改写,三栏说明。- 过场 portal 到
document.body有源码合同测试。 - 无头 Chrome 最小复现的截图与结论写进 PROGRESS 与 BUG-1080。
- 测试:
P5 · 文档
frontend/DESIGN.md§17:弹层只留图;caption 格式;新时序;删掉「过场在兑换弹窗之上」这句,改写兑换弹窗的新时机。frontend/docs/VOICE.md:caption 格式与「· 校正后」后缀;兑换弹窗新文案。CHANGELOG.md:一条。docs/BUG_HISTORY.md:BUG-1080(过场被兑换弹窗挡住)。docs/testing/birth-sky-cover-checklist.md追加真机条目:- 新账号(0 点)首次建盘:完整看到约 6.5 秒的过场,期间不弹兑换弹窗;
- 之后发一条正式问题:弹「解锁完整咨询」,草稿还在输入框;关掉后再发一次,打开的是 billing;
- 发「你好」这类闲聊:不弹;
- 星盘页点「那一刻的天空」:只看到图和「保存图片」,没有灰色面板;手机和电脑各看一次;
- 图底部是「日期 时刻 · 城市」;用直辖市和海外地点各试一次。
验证
tsc --noEmit0 错;npm run lint0 error。npm test全量:与基线be9e4dbe比测试名清单,不得有测试名消失;失败清单与基线(24 条 Docker / 部署套件)逐条同名。- Python:
python3 -m pytest tests/test_birth_sky.py tests/test_api_server_growth_contract.py tests/test_repo_privacy_markers.py;python3 scripts/run_quality_gate.py --profile quick。 rm -rf .next && next build:/仍○ Static;首屏 JS / CSS gzip 与基线be9e4dbe同口径对比(同一台机器、同一脚本各建一次),变化在 ±2% 内。
让步顺序
- P4 第 4 条(兑换弹窗文案)可以先保留原句,写进 PROGRESS 留给产品。
- 海外地名的城市规则可以一律不带城市。
- P1 弹层只留图、P2 caption(至少日期 + 时刻)、P3 放慢、P4 首页不再自动弹兑换弹窗,都不可让步。
交付
- 一个或两个提交,推到本地分支即可,不推送;由 Claude 独立验收后快进推 staging。
- 提交信息与 PROGRESS 引用本任务书。
BUG 编号
开工时核对 docs/BUG_HISTORY.md 最大号(写作时 BUG-1079)。过场被兑换弹窗挡住记为 BUG-1080;实现中再发现的既有缺陷从 BUG-1081 起编。