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

17 KiB
Raw Blame History

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-736BUG-698 未被占用,按任务书使用 BUG-698,未顺延。

1. 开工前置:前提仍然成立

任务书断言 5094fd26 实测 结论
accountDialogClasses 四分区全是 "settings-modal" home-types.ts:211-217 四项均为 settings-modal 成立
.settings-modal 同时写死 width 与 height globals.css:1757width: 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),dialogmodel.dialogClass 在同一次 render 里都来自 activeAccountDialogpage.tsx:1510-15121827-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-modalgetBoundingClientRect() 与 computed style。窗口 1440×900。

harnessgen.js + supported.html / nodvh.htmlscratchpad,未提交)。

2.1 基线 5094fd26dvh 正常的引擎(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 的写法,补成重复声明:

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 无法证伪条件,整块原样保留:

.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-modalmax-height / min-height 一并纳入,因为 §2.2 实测里 max-height 塌成 none 也是尺寸失控的一环。其余只写 max-height 的(.session-actions-positioner.account-menu-popup.auth-shell.select-content.select-list)按任务书不动 —— 作废后只是少一个上限,不会让盒子随内容跳变。

.admin-app-shellglobals.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.ts3 条,全文件范围):

  1. height: 用 dvh 的声明必须落在 @supports (height: 1dvh) 内。
  2. 禁止重复声明式回退 —— 它会被压缩器吃掉,测试里写明了原因。
  3. @supports 里升级过的每个选择器,块外必须有 vh 基线(否则不支持的引擎干脆没有高度)。

解析前先剥 CSS 注释:本文件自己的说明注释里引用了 height: 100vh; height: 100dvh; 这个坏写法,不剥会自伤(首次运行就被这条打红,已修)。

破坏性验证(三次,跑完都还原,未提交):

破坏 预期 实测
.settings-modal 基线换回 dvh 第 1 条红 not ok 12/3 绿
.admin-app-shell 改回 height: 100vh; height: 100dvh; 第 2 条红 not ok 1 + not ok 2
删掉 .auth-page 的 vh 基线只留 @supports 第 3 条红 not ok 31/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):

/* 前 */
.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.cssDESIGN.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 → 31skipped 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 quickpytest 段 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 下四栏观感,都需要真人确认。