Files
Jyotisha/docs/tasks/PROGRESS-settings-dialog-size-and-nav-20260916.md
T
Jesse_ChenandClaude Opus 5 111b4a8455
Independent Staging Quality Gate / validate (push) Failing after 9m25s
Independent Staging Quality Gate / publish (push) Skipped
fix(settings): 弹窗高度补 vh 基线,分区菜单去掉强调条(BUG-698)
设置弹窗的固定高度只用 dvh 写、没有回退。不认识该单位的引擎会把整条
height 与 max-height 作废,盒子退回按内容撑开,于是切分区就跳大小——
这正是 BUG-554 现象的复发,而 BUG-554 的防复发「必须同时声明 width 与
height」只检查声明存不存在,挡不住「写了但没生效」。

实测(Chrome 151,真实产物 CSS + 复刻 DOM,1440×900):dvh 正常时四个
分区恒定 866.80×640px,**事故不复现**;摘掉 dvh 后变成 313/313/378/1130,
宽度不动——与用户描述的形状完全一致。因此机制已证实,但用户当时的浏览器
未定位,BUG-698 记为 investigating 而非 resolved。

附带发现:任务书要求照抄的重复声明式回退 `height: 100vh; height: 100dvh;`
在本仓根本发布不出去——Lightning CSS 会合并同名属性的重复声明只留最后一条,
全仓唯一那处回退(sidebar-provider)在线上早就是死的,还有一条测试专门守着
这个从未发布过的写法。改用 @supports (height: 1dvh):vh 作基线,dvh 作升级。
修复后不支持 dvh 的引擎也收敛到恒定 640px,支持的逐像素无变化。

同轮按产品决策去掉设置分区菜单的左侧/下方强调色条,选中与悬停改用面与
墨色等级区分,不用色相、不用字重。左侧会话列表的色条本轮不动。

- 新增 viewport-unit-fallback-contract(3 条,全文件),三次破坏性验证各自打红
- account-dialog-overlay 新增同尺寸契约与分区菜单契约
- 三条钉死旧 dvh 字面量的既有断言按「原值/新值/原因」更新,均未弱化
- tsc 0 错;lint 0 error / 118 warning(持平);npm test 3346/3300/fail 31,
  失败清单与基线逐字相同;/ 仍 ○ Static;样式 gzip +0.38%;
  快速门 pytest 段 792 passed / 1 skipped / 0 failed

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
2026-09-16 03:50:38 +00:00

223 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PROGRESS · 设置弹窗尺寸(BUG-698)+ 分区菜单去强调条
- 日期:2026-09-16
- 任务书:`docs/tasks/TASK-settings-dialog-size-and-nav-20260915.md`
- 分支:`codex/settings-dialog-size-and-nav-20260916`
- 工作树:`.worktrees/settings-dialog-size-and-nav-20260916`
- 实际基线:`origin/staging` @ `5094fd26`(任务书写的 `6c748d86` 已被十余个提交超车;三条前提逐条复核仍成立,见 §1)
- BUG 编号:`docs/BUG_HISTORY.md` 当前最大号 **BUG-736**`BUG-698` 未被占用,按任务书使用 `BUG-698`,未顺延。
---
## 1. 开工前置:前提仍然成立
| 任务书断言 | `5094fd26` 实测 | 结论 |
|---|---|---|
| `accountDialogClasses` 四分区全是 `"settings-modal"` | `home-types.ts:211-217` 四项均为 `settings-modal` | 成立 |
| `.settings-modal` 同时写死 width 与 height | `globals.css:1757``width: min(100vw - 32px, 880px); height: min(84dvh, 640px)` | 成立 |
| 桌面强调条 `inset 2px 0 0 var(--color-action)` | `globals.css:1762` | 成立 |
| 移动端强调条 `inset 0 -2px 0 var(--color-action)` | `globals.css:1948` | 成立 |
| 选中态与悬停态共用一条规则 | `globals.css:1761` | 成立 |
| BUG-554 防复发「必须同时声明 width 与 height」 | 仍在,且仍绿 | 成立(本轮不推翻) |
`AccountDialogOverlay` 四个分区的 DOM 结构完全一致(`account-dialog-overlay.tsx:76-94`),`dialog``model.dialogClass` 在同一次 render 里都来自 `activeAccountDialog``page.tsx:1510-1512``1827-1831`),不存在切换瞬间掉类的中间帧。**所以外框尺寸只可能由 CSS 决定。**
## 2. 任务 1.1 复现:有浏览器,有数字
会话没有登录态,但本机有 `google-chrome 151.0.7922.169`。做法是绕开登录、直接量 CSS:把 `next build` 产出的**真实 chunk CSS**`.next/static/chunks/*.css`)和按 `account-dialog-overlay.tsx` 逐节点复刻的 DOM 组成离线页面,四个分区各塞 6 / 24 / 3 / 1 段内容模拟真实内容量差,headless Chrome 量 `section.account-modal.settings-modal``getBoundingClientRect()` 与 computed style。窗口 1440×900。
harness`gen.js` + `supported.html` / `nodvh.html`scratchpad,未提交)。
### 2.1 基线 `5094fd26`dvh 正常的引擎(Chrome 151
| 分区 | rect w | rect h | computed height | computed max-height |
|---|---:|---:|---|---|
| 个人资料 | 866.80 | 630.40 | 640px | 640px |
| 星盘资料 | 866.80 | 630.40 | 640px | 640px |
| 账户与点数 | 866.80 | 630.40 | 640px | 640px |
| 通用设置 | 866.80 | 630.40 | 640px | 640px |
rect 630.40 < computed 640 是 `account-dialog-enter` 入场动画的 transform,四个分区同值,不影响结论。)
**四个分区宽高完全一致,`height` 声明没有被划掉,计算值不是 `auto`。按任务书 §5 任务 1.1 的分支判断,事故在当前浏览器上不复现。**
### 2.2 同一份 CSS,去掉 dvh 支持
把 chunk 里所有 dvh 声明摘掉,模拟不认识该单位的引擎:
| 分区 | rect w | rect h | computed height | computed max-height |
|---|---:|---:|---|---|
| 个人资料 | 866.80 | 378.58 | 384.344px | none |
| 星盘资料 | 866.80 | 1130.16 | 1147.38px | none |
| 账户与点数 | 866.80 | 313.23 | 318px | none |
| 通用设置 | 866.80 | 313.23 | 318px | none |
**宽度纹丝不动(走 `vw`),高度随内容量在 313 / 378 / 1130 之间跳。** 这正是用户原话「大小不一样,选别的就立马缩小了」的形状。任务书 §2.2 推测的机制被证实为**唯一**能产生该现象的 CSS 路径,但它以「引擎不认识 dvh」为前提。
### 2.3 结论与诚实边界
- `dvh` 的支持面是 Chrome/Edge 1082022-11)、Safari 15.42022-03)、Firefox 1012022-05)、Android WebView 108。**任何当前浏览器都支持**,所以产品负责人若在新版 Chrome / Safari 上看到该现象,本机制解释不了。
- 仍然可能命中的环境:iOS 15.015.3、未升级的 Android System WebView< 108)、以及部分把旧 WebView 钉死的 App 内置浏览器(微信/QQ 等)。对一个中文消费产品这不是零概率,但**本轮无法证明用户就在其中**。
- 因此 BUG-698 记为 **`investigating`**,不是 `resolved`。机制已确认、已加固,但用户实际环境未定位。
## 3. 任务书的修复方案不成立(本轮最重要的发现)
任务书 §1.2 让我们照抄 `globals.css:637` 的写法,补成重复声明:
```css
height: min(84vh, 640px);
height: min(84dvh, 640px);
```
**这个写法在本仓根本到不了浏览器。** 先按任务书实现了一版,`next build` 后 grep 产物:
```
.settings-modal{...;width:min(100vw - 32px,880px);height:min(84dvh,640px);max-height:min(84dvh,640px);...}
```
`vh` 那条不见了。再去查任务书引为范本的那一行:
- 源码 `globals.css:637``.group\/sidebar-provider[data-viewport] { height: 100vh; height: 100dvh; ... }`
- 产物:`sidebar-provider[data-viewport]{height:100dvh;min-height:0;overflow:hidden}`
**Lightning CSSTailwind v4 的压缩器)会合并同一规则内同名属性的重复声明,只保留最后一条。** 也就是说:全仓唯一那处「标准写法」的 vh 回退,在线上**早就是死的**;任务书若原样交付,等于交一个 no-op。
改用特性查询,Lightning CSS 无法证伪条件,整块原样保留:
```css
.settings-modal { width: min(100vw - 32px, 880px); height: min(84vh, 640px); max-height: min(84vh, 640px); ... }
@supports (height: 1dvh) {
.settings-modal { height: min(84dvh, 640px); max-height: min(84dvh, 640px); }
}
```
产物核对(`@supports` 完整保留):
```
@supports (height:1dvh){.standalone-page,.app-loading,.group\/sidebar-provider[data-viewport],.admin-app-shell,.auth-page{height:100dvh}.account-modal{max-height:min(84dvh,760px)}.settings-modal{height:min(84dvh,640px);max-height:min(84dvh,640px)}}
@supports (height:1dvh){[data-sidebar=sidebar]{height:100dvh}.account-modal{min-height:100dvh;max-height:100dvh}.settings-modal{height:100dvh;max-height:100dvh}}
```
### 3.1 修复后重测(同 harness,同窗口)
| 引擎 | 四个分区 rect | computed height |
|---|---|---|
| dvh 正常(Chrome 151 | 866.80 × 630.40 ×4,全等 | 640px ×4 |
| 去掉 `@supports` 块(模拟不支持 dvh | 866.80 × 630.40 ×4,全等 | 640px ×4 |
**支持 dvh 的浏览器行为与修复前逐像素一致(无回归);不支持 dvh 的引擎从 313/378/1130 的跳变收敛到恒定 640px。**
覆盖范围:凡 `height`(不含 `max-height` / `min-height`)用 dvh 的 8 处全部改为 vh 基线 + `@supports` 升级 —— `.standalone-page``.app-loading``.group\/sidebar-provider[data-viewport]``.admin-app-shell``.auth-page``[data-sidebar="sidebar"]``.settings-modal`(桌面与移动端)。另把 `.account-modal` / `.settings-modal``max-height` / `min-height` 一并纳入,因为 §2.2 实测里 `max-height` 塌成 `none` 也是尺寸失控的一环。其余只写 `max-height` 的(`.session-actions-positioner``.account-menu-popup``.auth-shell``.select-content``.select-list`)按任务书不动 —— 作废后只是少一个上限,不会让盒子随内容跳变。
`.admin-app-shell``globals.css` 里,属于布局类而非 antd / Refine 组件,改的是同一条 dvh 规则,不触碰任务书 §4.5 的 admin 红线。
### 3.2 自查抓到一个自己引入的回归(已修)
第一版把全部 7 个选择器的 dvh 升级都放进**顶层** `@supports`。复查 diff 时发现 `.auth-page` 的高度基线只存在于 `@media (max-width: 767px)` 内(`globals.css:1952`),顶层那条 `.auth-page { display: grid; place-items: center; padding: … }` 本来**没有高度**。放到顶层等于给桌面登录页新加了一个满视口高度约束 —— 桌面登录页原本是随内容高的。已把 `.auth-page` 的升级挪进移动端作用域内的 `@supports`
**新增的契约测试没能抓到这个**:它只验「文件里某处存在 vh 基线」,不验基线与升级是否在同一个 at-rule 作用域。这条限制已写进测试文件头部注释,提醒后来者手工对齐作用域。其余 6 个选择器的基线都在顶层,作用域本就一致,逐个复核过。
## 4. 任务 1.3 防复发:从「写没写」升级到「量不量得到」
新增 `frontend/tests/viewport-unit-fallback-contract.test.ts`3 条,全文件范围):
1. `height:` 用 dvh 的声明必须落在 `@supports (height: 1dvh)` 内。
2. 禁止重复声明式回退 —— 它会被压缩器吃掉,测试里写明了原因。
3. `@supports` 里升级过的每个选择器,块外必须有 vh 基线(否则不支持的引擎干脆没有高度)。
解析前先剥 CSS 注释:本文件自己的说明注释里引用了 `height: 100vh; height: 100dvh;` 这个坏写法,不剥会自伤(首次运行就被这条打红,已修)。
**破坏性验证**(三次,跑完都还原,未提交):
| 破坏 | 预期 | 实测 |
|---|---|---|
| `.settings-modal` 基线换回 dvh | 第 1 条红 | `not ok 1`2/3 绿 |
| `.admin-app-shell` 改回 `height: 100vh; height: 100dvh;` | 第 2 条红 | `not ok 1` + `not ok 2` |
| 删掉 `.auth-page` 的 vh 基线只留 `@supports` | 第 3 条红 | `not ok 3`1/2 绿 |
| 还原 | 全绿 | `ok 1 / ok 2 / ok 3` |
`account-dialog-overlay.test.ts` 新增 2 条:
- 同尺寸契约:`accountDialogClasses` 四个设置分区映射到同一个类名(直接 import 常量断言,不再只验单个分区的渲染),且 `logout` 与之不同。
- 分区菜单契约:`[aria-current="page"]``:hover` 规则内都不含 `box-shadow`、不含 `font-weight`,且两者必须是各自独立的规则块。破坏性验证:把强调条与合并规则塞回去 → `not ok 5`,还原 → `ok 5`
原有 `assert.match(styles, /\.settings-modal \{[^}]*width:[^}]*height:/)` **未改动**(新 CSS 仍然匹配),只在上方加注释说明它是存在性检查、真正的防线是上述两处。**本轮没有修改任何既有断言,所以不涉及「原值 / 新值 / 原因」三栏。**
## 5. 任务 2:分区菜单
改动(`globals.css`):
```css
/* 前 */
.settings-dialog-nav-item:hover, .settings-dialog-nav-item[aria-current="page"] { background: var(--color-canvas-muted); color: var(--color-ink); }
.settings-dialog-nav-item[aria-current="page"] { box-shadow: inset 2px 0 0 var(--color-action); }
/* 移动端 */ .settings-dialog-nav-item[aria-current="page"] { box-shadow: inset 0 -2px 0 var(--color-action); }
/* 后 */
.settings-dialog-nav-item:hover { background: color-mix(in srgb, var(--color-canvas-muted) 55%, transparent); color: var(--color-ink-secondary); }
.settings-dialog-nav-item[aria-current="page"] { background: var(--color-canvas-muted); color: var(--color-ink); }
/* 移动端那条整条删除 */
```
- 三态可分:默认透明 + 次级墨 → 悬停 55% 淡面 + 次级墨 → 选中实面 + 主墨。选中比悬停重一档,靠面与墨色等级,不用色相、不用 `font-weight`(任务书 §3.3)。
- 取 55% 的理由:`--color-canvas-muted` 浅色 `#ebe9e3` 压在 `--color-canvas` `#fbfaf7` 上本就只差一档,悬停若取满会和选中撞;55% 是仓库里已有的同族用法(`globals.css` 现有 84 处 `color-mix`,如 `.evidence-audit-panel` 用 68%、`.evidence-audit-row` 用 64%),**没有引入新字面色值、没有新 token**。
- 深色:`--color-canvas-muted` `#30302d``--color-ink` `#f2f0ea``--color-ink-secondary` `#b3afa4` 三者在深色块里都已定义,改动只用既有 token,`dark-theme-contract.test.ts` 无需新增条目(实跑仍绿)。深浅两套的肉眼可分辨留给真人清单。
- 移动端(≤767px)导航是顶部四栏,触摸没有 hover,选中态即实面 + 主墨,删掉下边框后仍与其余三项可分。
- 可达性:`:focus-visible` 描边规则未动;`aria-current="page"` 仍在 DOM 上,屏幕阅读器不受影响 —— 选中态不是只靠颜色。
- 左侧会话列表的 2px `--sidebar-ring` **本轮未动**(任务书 §3.2),两处观感暂时不一致,已在 `DESIGN.md` 写明是已知且授权的。
## 6. 验收
| 项 | 基线 `5094fd26` | 本轮 | 结论 |
|---|---|---|---|
| `tsc --noEmit` | 0 | 0 | 通过(中途新测试有 1 个 TS2345,已修) |
| `npm run lint` | 0 error / 118 warning | 0 error / 118 warning | 通过,warning 未上升 |
| `npm test` | tests 3341 / pass 3295 / fail 31 / skipped 15 | tests 3346 / pass 3300 / fail 31 / skipped 15 | 通过,失败清单与基线**逐字相同** |
| `next build` `/` | `○ Static` | `○ Static` | 通过 |
| 样式 gzip | 37,547 B | 37,688 B | +141 B / **+0.38%**,在 ±2% 内 |
| 快速门 pytest 段 | 792 passed / 1 skipped / 0 failed | 792 passed / 1 skipped / 0 failed | 通过 |
| `Home()` useState / useRef | 36 / 37 | 36 / 37(未碰 `page.tsx` | 未增长 |
| `page.tsx` 行数 | 1846 | 1846(未改动) | 未增长 |
gzip 口径:Next 16 的路由表不再打印 First Load JS,本轮又只改 CSS,所以量的是产物样式 chunk 本身——把新增的两个 `@supports` 块摘掉再 gzip 作对照(37,547 B),带上是 37,688 B。
本轮只动 `globals.css``DESIGN.md` 与五个测试文件,`page.tsx` 与任何 Home 级 hook 都没碰,所以没有新增 Home 级 hook,不涉及 `OPTIONAL_HOME_HOOKS` 登记(BUG-736)。
### 6.1 全量套件
`tests` 3341 → **3346**+5,正是本轮新增的 5 条),`pass` 3295 → **3300**+5),`fail` 31 → **31**`skipped` 15 → 15。测试总数没有下降。
失败清单与基线 `diff` 结果为**完全一致**(31 条,全是无 Docker 的 DB / 部署 / 引擎套件,`BLOCKED.md` BLK-002 / BLK-003):
```
comm -13 fails-baseline.txt fails-final.txt # 新增失败:空
comm -23 fails-baseline.txt fails-final.txt # 消失失败:空
diff -q fails-baseline.txt fails-final.txt # IDENTICAL
```
**中途出现过 3 条新失败,已修,原因记在 §6.1.1。**
#### 6.1.1 三条既有断言按「原值 / 新值 / 原因」改动
改 dvh 写法打红了三条把旧文本钉死的断言。三条都**保留原有意图(满视口高度 / 全视口滚动容器)**,只把断言对象换成真正会发布的写法,没有一条被弱化或删除:
| 文件 | 原值 | 新值 | 原因 |
|---|---|---|---|
| `tests/birth-time-mobile-scroll-contract.test.ts` | `/\.group\\\/sidebar-provider\[data-viewport\]\s*\{[^}]*height:\s*100vh;[^}]*height:\s*100dvh/` | 拆成两条:块内 `height:\s*100vh;`,外加 `@supports (height: 1dvh)` 内该选择器 `height: 100dvh` | **这条原本守着一个从未发布过的写法。** 重复声明式回退被 Lightning CSS 合并,产物里只剩 `height:100dvh`;断言读的是源码文本所以一直绿。换成断两半后,vh 基线与 dvh 升级都必须真在。 |
| `tests/mobile-interaction-contract.test.ts` | `css.indexOf(".auth-page { height: 100dvh; overflow-x: hidden; overflow-y: auto;")` | 同串改 `height: 100vh`,并**新增**一条断 `@supports` 内有 `.auth-page { height: 100dvh; }` | 登录页的满视口高度改为 vh 基线 + 特性查询升级。原断言只认旧字面量。断言数量从 1 增到 2,未放宽。 |
| `tests/admin-payments-contract.test.ts` | `/\.admin-app-shell \{ height: 100dvh; min-height: 0; overflow-y: auto; \}/` | 同串改 `height: 100vh`,并**新增**一条断 `@supports` 内有 `.admin-app-shell { height: 100dvh; }` | 同上。后台外壳仍是独立全视口滚动容器,只是高度写法换了。断言数量从 1 增到 2,未放宽。 |
### 6.2 快速门
`.venv/bin/python scripts/run_quality_gate.py --profile quick`**pytest 段 792 passed / 1 skipped / 0 failed**,与基线一致。
快速门整体退出 1,唯一原因是本机无 `rsync`、无 Docker 的既有环境缺口(`BLOCKED.md` BLK-002 / BLK-003),与本轮改动无关。退出码直接取自命令本身,没有隔着管道。
## 7. 环境缺口
1. **无登录态、无法在真实应用里走查。** §2 的数字是在真实产物 CSS + 逐节点复刻 DOM 的离线页面上量的,不是在跑起来的应用里量的。真实内容(星盘资料的列表/详情、账户与点数的套餐卡)可能有本 harness 没模拟到的溢出行为。浏览器级条目见 `docs/testing/settings-dialog-20260916.md`
2. **无法确认用户当时的浏览器。** 这是 BUG-698 停在 `investigating` 的唯一原因。需要产品负责人补:哪两个分区、什么浏览器与版本、窗口多大、能否截图。
3. 无 Docker:DB / 部署套件 31 条失败沿用基线(`BLOCKED.md` BLK-002 / BLK-003),与基线逐条比对见 §6.1。
4. 深浅两套主题下「悬停 vs 选中」肉眼可分辨、以及 375px 下四栏观感,都需要真人确认。