任务书 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
161 lines
14 KiB
Markdown
161 lines
14 KiB
Markdown
# 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 在其上)。两者是**同一个现象的两层**:「切分区时弹窗盒子变大小」。
|
||
|
||
复查两条的防复发措施是否仍然成立:
|
||
|
||
1. **BUG-554「四个分区共用一个类」** —— 仍成立。`accountDialogClasses` 四个分区仍全部映射到 `settings-modal`,由 `account-dialog-overlay.test.ts` 的 `all 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-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 条。
|
||
|
||
---
|
||
|
||
## 环境缺口(不得记为通过)
|
||
|
||
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. 无 Docker:`npm test` 的 31 条失败与基线逐条一致,其中含计费 / 订阅套件,非本轮引入。
|
||
|
||
---
|
||
|
||
## 未做 / 不在本轮
|
||
|
||
- `chart-library-panel.tsx`:任务书写「如需」,本轮方案不需要改它(列表分区通过不带修饰符实现铺满),未改。
|
||
- 其它轮次(R4/R5/R6/R7)的区段一律未碰。
|