Files
Jyotisha/docs/tasks/PROGRESS-cend-settings-billing-20260916.md
T
Jesse_ChenandClaude Opus 5 34628ecb67
Independent Staging Quality Gate / validate (push) Canceled after 6m18s
Independent Staging Quality Gate / publish (push) Canceled after 0s
feat(settings): 表单分区收窄到 440px,套餐卡收敛成两种互斥状态
任务书 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
2026-09-16 06:24:11 +00:00

14 KiB
Raw Blame History

PROGRESS · R8 设置弹窗 + 会员充值(2026-09-16

任务书:docs/tasks/TASK-cend-surfaces-claude-alignment-20260916.md §R8T8.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 Bgzip 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.tsxbilling-panel.tsxglobals.css、三个测试文件、DESIGN.mdglobals.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 → parseSettingsQuerytab: "credits"plan: "monthly" 两条);initialTab 覆盖规则与 ref + scrollIntoView 接线断言在案。浏览器里真滚一次做不到,见环境缺口
7 支付关闭 / 空态 / packagesError 三分支不变 通过 这三处 JSX 与文案 git diff 内无改动;billing-panel.test.tsmembership-page.test.ts 相关断言未动且全绿
8 tsc 0 错;lint 0 errorwarning ≤ 118 通过 tsc 0 错;lint 0 error / 118 warning,与基线持平
9 npm test 失败清单与基线逐条一致 通过 diff base.txt after.txt → 无差异。tests 3350 → 3354+4),pass 3304 → 3308fail 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 B0.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-698BUG-554 在其上)。两者是同一个现象的两层:「切分区时弹窗盒子变大小」。

复查两条的防复发措施是否仍然成立:

  1. BUG-554「四个分区共用一个类」 —— 仍成立。accountDialogClasses 四个分区仍全部映射到 settings-modal,由 account-dialog-overlay.test.tsall four settings panes share one dialog class 直接断言常量(本轮未动该常量、未动该测试)。
  2. BUG-554「必须同时声明 width 与 height」 —— 仍在,但按 BUG-698 的更正,它只是存在性断言,不算防线,本轮不依赖它。
  3. BUG-698「height 用 dvh 必须 vh 基线 + @supports (height: 1dvh) 升级」 —— 仍成立。本轮未新增任何全高容器,未碰 5100 附近区段;viewport-unit-fallback-contract.test.ts 三条全绿。
  4. BUG-698「新写 CSS 契约要么断到产物、要么可被破坏性验证证伪」 —— 本轮照办:三条新断言各做了一次破坏性验证(上表),并额外把三条新规则在 next build 产物 CSS 里逐条取证(上一节)。

本轮改动为什么在机制上不可能让盒子再跳:

  • 弹窗盒子的尺寸由 .settings-modal 上的 width + height 决定,四个分区共用这一个类;本轮没动这个类,也没动 .account-settings-shellgrid-template-columns
  • 新增的上限是 .settings-dialog-content--form > *,作用域严格落在滚动容器的子元素上。子元素变窄只会让 .settings-dialog-contentoverflow-y:automin-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 条。


环境缺口(不得记为通过)

  1. 设置弹窗四个分区切换不跳尺寸 —— 做不到,是缺口。本会话无 Chrome、无登录态,无法打开弹窗量四个分区的 computed size。已做的是上一节的结构级复验 + 产物 CSS 取证。BUG-698 当时是用「真实 next build 产物 CSS + 逐节点复刻 DOM」在 Chrome 里量的 866.80 × 630.40 / 640px,本轮没有重跑那个 harness。请产品负责人在真机上按 docs/testing/ 清单走一遍:登录 → 打开设置 → 在 个人资料 / 星盘资料 / 账户与点数 / 通用设置 之间来回切,确认弹窗外框宽高不变、只有右侧内容在滚。
  2. 支付下单流程 —— 做不到,是缺口。无受控账号、无支付渠道凭据。membershipHref("credits") / ?plan=monthly解析链路已真跑(新测试),但「点进去 → 对应 tab 选中 → 卡片滚到视口中央并获得焦点 → 下单 → 收银台」整条留给产品负责人实测。
  3. ?plan= 滚动定位的视觉确认 —— 删掉 --highlighted 后,该卡在视觉上不再有任何高亮,只有滚动位置与程序焦点。tabIndex={-1} 上的程序化 focus() 通常不会触发 :focus-visible,因此屏幕上大概率看不出「就是这张」。这是任务书 D13 明确要求的结果(「不再改变外观」),但是否可接受需要产品负责人在真机上确认;若确认不可接受,应另立单加一个不与 is-current / is-recommended 冲突的定位提示(例如短时的 :focus-visible 描边或滚动后的一次性动效),不要把旧的第三层描边加回来。
  4. 无 Dockernpm test 的 31 条失败与基线逐条一致,其中含计费 / 订阅套件,非本轮引入。

未做 / 不在本轮

  • chart-library-panel.tsx:任务书写「如需」,本轮方案不需要改它(列表分区通过不带修饰符实现铺满),未改。
  • 其它轮次(R4/R5/R6/R7)的区段一律未碰。