任务书 TASK-cend-surfaces-claude-alignment-20260916 §R8(T8.1 / T8.2)。 T8.1(E13):880px 弹窗里「个人资料」「通用设置」的一列表单铺满约 690px, 字段与标签被拉断。表单分区的内容区加 `settings-dialog-content--form`, 子元素上限 440px 并左对齐;「星盘资料」「账户与点数」两个列表分区保持铺满。 上限只挂在滚动容器的子元素上,不挂 `.settings-modal` / `.account-settings-shell`, 因此传不到固定尺寸的弹窗(BUG-554 / BUG-698 的现象无法复现)。 T8.2(D13):套餐卡的三个修饰符可同时命中,叠出没有设计定义的边框与底色。 收敛成两种互斥状态 `is-current`(当前套餐)与 `is-recommended`(推荐), 「推荐且当前」判给当前套餐(该卡的动作是续费)。 `highlighted` 不再进 className,删掉 `--highlighted` 那条描边规则, 定位交给 use-billing-panel 里已有的 scrollIntoView + focus。 手机端「月卡置顶」改挂 `[data-plan-alias="monthly"]`——版位不是状态, 月卡成为当前套餐后排序不变。 测试:3350 → 3354(+4),fail 31 → 31 且失败清单与基线逐条一致。 6 处既有断言改动均就地写了「原值/新值/原因」,另有 3 次破坏性验证。 tsc 0 错;lint 0 error / 118 warning(持平);干净 next build 后 `/` 仍 `○ Static`, 产物 CSS gzip 39,034 → 38,882 B(−0.39%),三条新规则在产物里逐条取证。 环境缺口(未记为通过):四个分区切换不跳尺寸、支付下单流程、`?plan=` 滚动定位的 视觉确认——无 Chrome、无受控账号,见 PROGRESS 的环境缺口段。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
14 KiB
PROGRESS · R8 设置弹窗 + 会员充值(2026-09-16)
任务书:docs/tasks/TASK-cend-surfaces-claude-alignment-20260916.md §R8(T8.1 / T8.2)
- 分支:
codex/cend-settings-billing-20260916 - 基线:
origin/staging=50ce02c8 - BUG 编号:未占用。本轮是任务书 D13 授权的产品改造,未发现新缺陷,
docs/BUG_HISTORY.md当前最大号仍为 BUG-738。 - 未 push(按要求)。
开工基线(本 worktree 实测)
| 项 | 数字 |
|---|---|
tsc --noEmit |
0 错 |
npm run lint |
0 error / 118 warning |
npm test |
tests 3350、pass 3304、fail 31、skipped 15 |
npm run build |
exit 0,/ = ○ (Static) |
产物 CSS(.next/static/chunks/*.css 合并) |
raw 251,634 B,主 bundle 242,608 B,gzip 39,034 B |
31 条无 Docker 失败已存到 /tmp/.../base.txt,其中含一批计费 / 订阅相关条目,均为环境失败,非本轮引入。
改了什么
T8.1 设置内容区收窄(E13)
frontend/src/components/account-dialog-overlay.tsx- 新增
formPanes = ["profile", "general"]与isFormPane。 - 内容区 className 变为
settings-dialog-content+(表单分区才加)settings-dialog-content--form。 - 导航项、
aria-current="page"、.settings-modal、.account-settings-shell一律未动。
- 新增
frontend/src/app/globals.css(.settings-dialog-content相邻行)- 新增
.settings-dialog-content--form > * { max-width: 440px; margin-right: auto; }。 - 上限挂在滚动容器的子元素上,不挂容器本身、不挂弹窗、不挂 shell 栅格。
- 新增
「星盘资料」「账户与点数」两个列表分区不带该修饰符,铺满不变;chart-library-panel.tsx 无需改动,未改。
T8.2 套餐卡状态收敛(D13)
frontend/src/components/billing-panel.tsx- 三修饰符拼接 →
const stateClass = isCurrent ? " is-current" : recommended ? " is-recommended" : "";,className={membership-plan-card${stateClass}}。 - 新增
data-plan-alias={alias ?? undefined},只承担版位(手机端月卡置顶),不承担状态。 highlighted保留ref/tabIndex/aria-current,不再进 className。
- 三修饰符拼接 →
frontend/src/app/globals.css.membership-plan-card--recommended→.membership-plan-card.is-recommended(声明逐字未动)。.membership-plan-card--current→.membership-plan-card.is-current(声明逐字未动)。- 删除
.membership-plan-card--highlighted(边框 + outline 那一条)。 @media (max-width: 640px)内order: -1的选择器 →.membership-plan-card[data-plan-alias="monthly"]。
滚动定位本身在 use-billing-panel.ts 的 highlight effect 里早已存在(scrollIntoView({behavior:"smooth",block:"center"}) + focus({preventScroll:true})),本轮未改该 hook,只是把外观那一层摘掉后由它独立承担定位。
三种历史组合的外观,明确且不叠加:
| 组合 | class | 外观 | 角标 |
|---|---|---|---|
| 仅推荐 | is-recommended |
--color-border-strong 描边 + canvas 底 |
推荐 |
| 仅当前 | is-current |
--color-action 描边 |
当前套餐 |
| 推荐且当前 | is-current |
同「仅当前」 | 当前套餐 |
「推荐且当前」判给当前套餐:该卡的实际动作是「续费」而不是「立即购买」,角标与按钮文案本来就已经这样分。两个 class 在同一张卡上永不共存,由测试逐组合枚举钉死。
逐条验收对照
| 约束 | 结论 | 证据 |
|---|---|---|
1 不回退 BUG-698 的 @supports (height: 1dvh) |
通过 | 未新增任何全高容器,未碰 5100 附近区段;viewport-unit-fallback-contract.test.ts 3 条全绿 |
2 不碰 .settings-dialog-nav-item[aria-current="page"] |
通过 | git diff 中该行未出现;account-dialog-overlay.test.ts 的分区菜单契约仍绿 |
| 3 四分区切换不跳尺寸 | 静态通过 + 浏览器缺口 | 见下节「BUG-554 / BUG-698 复验方式」 |
| 4 只动允许的文件与区段 | 通过 | 改动文件:account-dialog-overlay.tsx、billing-panel.tsx、globals.css、三个测试文件、DESIGN.md。globals.css 只动 .settings-dialog-content 相邻行与 .membership-plan-card*;未出现 .chart-page-* / .ephemeris-* / .report-center-* / .personal-report-* / .rectification-* |
| 5 三种组合不叠加 | 通过 | 上表;billing-panel.test.ts 新增「mutually exclusive across the three historical combinations」枚举四种输入 |
6 membershipHref 带参入口仍定位 |
链路通过 / 滚动为缺口 | 新增测试真跑 membershipHref → parseSettingsQuery(tab: "credits" 与 plan: "monthly" 两条);initialTab 覆盖规则与 ref + scrollIntoView 接线断言在案。浏览器里真滚一次做不到,见环境缺口 |
7 支付关闭 / 空态 / packagesError 三分支不变 |
通过 | 这三处 JSX 与文案 git diff 内无改动;billing-panel.test.ts、membership-page.test.ts 相关断言未动且全绿 |
8 tsc 0 错;lint 0 error,warning ≤ 118 |
通过 | tsc 0 错;lint 0 error / 118 warning,与基线持平 |
9 npm test 失败清单与基线逐条一致 |
通过 | diff base.txt after.txt → 无差异。tests 3350 → 3354(+4),pass 3304 → 3308,fail 31 → 31 |
| 10 改断言写「原值/新值/原因」 | 通过 | 见下节表;6 处,均就地三行注释 |
11 next build 后 / 保持 ○ Static |
通过 | 干净重建(rm -rf .next)后 ┌ ○ / |
| 12 同提交更新 DESIGN.md;文案对照 VOICE | 通过 | frontend/DESIGN.md 的 Settings dialog 加「Content width」,Billing pane 加「Plan card states」并改写 Deep links。无新文案(「当前套餐 / 推荐」是既有字串),VOICE.md 无需改 |
| 13 不改数据库结构 | 通过 | 无迁移文件、无 schema 改动 |
产物体积(干净重建,非缓存):合并 CSS raw 251,634 → 251,400 B(−234),gzip 39,034 → 38,882 B(−0.39%),在 ±2% 内,方向是变小(删掉了 --highlighted 那条死规则)。
补充证据:新规则确实活过 Lightning CSS 压缩 ——
产物里 settings-dialog-content--form>*{max-width:440px;margin-right:auto}、
[data-plan-alias=monthly]{order:-1}、membership-plan-card.is-current{border-color:var(--color-action)} 均在,
membership-plan-card--highlighted 出现 0 次。
过程记录:第一次
npm run build复用了.next缓存,产物 CSS 没有重新编译(旧--highlighted仍在)。上面所有产物数字来自rm -rf .next后的干净重建。
被改的既有断言(原值 / 新值 / 原因)
均已在测试文件里就地写成三行注释。
| # | 文件 | 原值 | 新值 | 原因 |
|---|---|---|---|---|
| 1 | tests/billing-panel.test.ts |
assert.match(panelSource, /membership-plan-card--recommended/) |
断言互斥的 stateClass 三元 + className={\membership-plan-card${stateClass}`}` |
D13 收敛成两种互斥状态;只断类名存在拦不住重新叠加(这正是 BUG-698 总结的「存在性断言不算防线」) |
| 2 | tests/billing-panel.test.ts |
assert.match(panelSource, /membership-plan-card--highlighted/) |
doesNotMatch 该类名 + doesNotMatch(/highlighted \?[^\n]*membership-plan-card/) + 断言 ref={highlighted ? highlightedCardRef : undefined} |
D13:URL 高亮不再改外观,改为滚动定位;断言方向反转 |
| 3 | tests/membership-page.test.ts |
@media (max-width: 640px) … .membership-plan-card--recommended { order: -1 } |
同一条规则改用 .membership-plan-card[data-plan-alias="monthly"] |
「推荐」成为互斥状态后,月卡一旦是当前套餐就丢 is-recommended;手机端「月卡置顶」是版位不是状态,挂到套餐身份上,排序结果与改前逐字相同 |
| 4 | tests/membership-page.test.ts |
doesNotMatch(cssBlock(".membership-plan-card--recommended"), /grid-column/) |
同断言,选择器改 .membership-plan-card.is-recommended |
状态类改名,断言强度不变 |
| 5 | tests/membership-page.test.ts |
match(cssBlock(".membership-plan-card--highlighted"), /outline: 2px solid color-mix\(…\)/) |
doesNotMatch(globalStyles, /membership-plan-card--highlighted/) |
该规则被删;断言改为证明它不再存在(更强,不是弱化) |
| 6 | tests/membership-page.test.ts |
match(cssBlock(".membership-plan-card--current"), /border-color: var\(--color-action\)/) 与 match(cssBlock(".membership-plan-card--recommended"), /background: var\(--color-canvas\)/);doesNotMatch(globalStyles, /membership-plan-card--recommended::before/) |
选择器改 .is-current / .is-recommended;并新增 assert.notEqual(cssBlock(is-current), cssBlock(is-recommended));::before 断言同时覆盖新旧两种写法 |
状态类改名,token 未动;新增的 notEqual 是净增强度(两种状态外观必须可区分) |
净增的断言(不在上表,因为没有改动既有断言):
tests/account-dialog-overlay.test.ts+2 个 test:表单分区带 cap / 列表分区不带;cap 不可能改变弹窗尺寸。tests/billing-panel.test.ts+2 个 test:带参入口链路真跑;三种历史组合互斥枚举。
测试总数 3350 → 3354,无任何断言被静默删除或弱化。
破坏性验证(证明新断言不是 no-op)
| 破坏 | 结果 |
|---|---|
cap 从 440px 改成 800px |
not ok 6 - form panes get a reading-width cap… |
cap 选择器提到 .settings-modal … 并加 height: 300px |
not ok 7 - the pane cap cannot resize the dialog box (BUG-554 / BUG-698 mechanism) |
把 .is-current 的声明改成与 .is-recommended 一字不差 |
not ok 11 - membership page reuses the warm parchment / terracotta tokens |
三次破坏后均已还原,git diff 干净。
BUG-554 / BUG-698 的复验方式
两条记录已完整读过(docs/BUG_HISTORY.md:11518 起为 BUG-698;BUG-554 在其上)。两者是同一个现象的两层:「切分区时弹窗盒子变大小」。
复查两条的防复发措施是否仍然成立:
- BUG-554「四个分区共用一个类」 —— 仍成立。
accountDialogClasses四个分区仍全部映射到settings-modal,由account-dialog-overlay.test.ts的all four settings panes share one dialog class直接断言常量(本轮未动该常量、未动该测试)。 - BUG-554「必须同时声明 width 与 height」 —— 仍在,但按 BUG-698 的更正,它只是存在性断言,不算防线,本轮不依赖它。
- BUG-698「
height用 dvh 必须 vh 基线 +@supports (height: 1dvh)升级」 —— 仍成立。本轮未新增任何全高容器,未碰 5100 附近区段;viewport-unit-fallback-contract.test.ts三条全绿。 - BUG-698「新写 CSS 契约要么断到产物、要么可被破坏性验证证伪」 —— 本轮照办:三条新断言各做了一次破坏性验证(上表),并额外把三条新规则在
next build产物 CSS 里逐条取证(上一节)。
本轮改动为什么在机制上不可能让盒子再跳:
- 弹窗盒子的尺寸由
.settings-modal上的width+height决定,四个分区共用这一个类;本轮没动这个类,也没动.account-settings-shell的grid-template-columns。 - 新增的上限是
.settings-dialog-content--form > *,作用域严格落在滚动容器的子元素上。子元素变窄只会让.settings-dialog-content(overflow-y:auto、min-width:0)内部留白,向上传不到固定尺寸的弹窗。 - 这一点由新测试
the pane cap cannot resize the dialog box (BUG-554 / BUG-698 mechanism)结构化守住:遍历所有含settings-dialog-content--form的规则,禁止其选择器出现settings-modal/account-settings-shell/account-modal/settings-dialog-nav,禁止规则里出现height:;并复核.settings-modal仍同时声明 width 与 height、.account-settings-shell仍声明列栅格。破坏性验证已打红。
这是静态 / 结构级复验,不是真机复验。 见环境缺口第 1 条。
环境缺口(不得记为通过)
- 设置弹窗四个分区切换不跳尺寸 —— 做不到,是缺口。本会话无 Chrome、无登录态,无法打开弹窗量四个分区的 computed size。已做的是上一节的结构级复验 + 产物 CSS 取证。BUG-698 当时是用「真实
next build产物 CSS + 逐节点复刻 DOM」在 Chrome 里量的 866.80 × 630.40 / 640px,本轮没有重跑那个 harness。请产品负责人在真机上按docs/testing/清单走一遍:登录 → 打开设置 → 在 个人资料 / 星盘资料 / 账户与点数 / 通用设置 之间来回切,确认弹窗外框宽高不变、只有右侧内容在滚。 - 支付下单流程 —— 做不到,是缺口。无受控账号、无支付渠道凭据。
membershipHref("credits")/?plan=monthly的解析链路已真跑(新测试),但「点进去 → 对应 tab 选中 → 卡片滚到视口中央并获得焦点 → 下单 → 收银台」整条留给产品负责人实测。 ?plan=滚动定位的视觉确认 —— 删掉--highlighted后,该卡在视觉上不再有任何高亮,只有滚动位置与程序焦点。tabIndex={-1}上的程序化focus()通常不会触发:focus-visible,因此屏幕上大概率看不出「就是这张」。这是任务书 D13 明确要求的结果(「不再改变外观」),但是否可接受需要产品负责人在真机上确认;若确认不可接受,应另立单加一个不与is-current/is-recommended冲突的定位提示(例如短时的:focus-visible描边或滚动后的一次性动效),不要把旧的第三层描边加回来。- 无 Docker:
npm test的 31 条失败与基线逐条一致,其中含计费 / 订阅套件,非本轮引入。
未做 / 不在本轮
chart-library-panel.tsx:任务书写「如需」,本轮方案不需要改它(列表分区通过不带修饰符实现铺满),未改。- 其它轮次(R4/R5/R6/R7)的区段一律未碰。