t102-topbar-position.mjs 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. /**
  2. * 票 102 静态闸门(二):一级菜单的位置不得由秒针决定,同时保留票 37 的溢出兜底。
  3. *
  4. * 维护者 2026-11-02:「按钮在居中偏左(略微左移,不与背景装饰线条重合);目前由于时间字体
  5. * 长短不平均,时间跳动会导致按钮位置左右移动,需避免。」
  6. *
  7. * 关键认知:让菜单移动的真凶是**时钟读数的不定宽**,不是右列的 min-content 兜底本身。
  8. * 所以正确解是「固定时钟宽度」+「保留 min-content 下限」这对组合:
  9. * - 时钟进固定数字槽位后,右列的 min-content 只剩常量,不再随秒针变 → 位置稳定;
  10. * - min-content 下限还在 → 窄视口内容超宽时不会像票 37 那样向左溢出压住「预警处置」。
  11. * 只把右列改成 minmax(0, 1fr) 也能消除抖动,但会撤掉溢出兜底(上一版就犯了这个错),
  12. * 本闸门同时锁这两条,改回 0 或删掉槽位都会转红。
  13. * 断言一律落在对应选择器的规则体内,不用全文件子串(见 t102-shell-source 的说明)。
  14. * 用法:node verify/t102-topbar-position.mjs [源码路径]。
  15. */
  16. import { readShell, ruleBody, declaration, check, assertModeNavIntact, report } from './t102-shell-source.mjs'
  17. const source = readShell(process.argv[2])
  18. // 入口一:中列(菜单)的位置不得由右列内容决定。
  19. // 票 102 当时靠「右列 min-content 变成常量 + 时钟固定槽位」;票 103 起状态栏已脱流
  20. // (.cockpit-shell__top-meta 绝对定位),右列不再承载任何内容,两列等分即可,
  21. // 中列位置与内容彻底无关。此处只锁「两列等分、中列真居中」这一结构事实,
  22. // 原先那条 min-content 下限的断言随设计变更一并退役(票 103 的闸门接管碰撞校验)。
  23. const columns = declaration(source, 'grid-template-columns')
  24. check(columns.includes('minmax(240px, 1fr) auto minmax(240px, 1fr)'), '顶栏三列须两列等分(中列位置与右列内容无关)')
  25. // 入口二:一级菜单整组平移,位置与内容彻底解耦。用 transform 不用 margin。
  26. const modes = ruleBody(source, 'cockpit-shell__modes')
  27. check(/transform:\s*translateX\(/.test(modes), '一级菜单必须整组 translateX 左移(居中偏左)')
  28. check(!/margin-left/.test(modes.split('\n').filter((l) => /^\s*[a-z-]+\s*:/.test(l)).join('\n')), '左移不得用 margin-left(会被 grid 再分配,等于把裁决权交回内容)')
  29. // 入口三:时钟读数宽度恒由声明决定,而不是由「这一刻几点几分几秒」决定。
  30. // 菜单位置虽已与右列无关,但时钟自身仍不许随秒针改变占位(否则状态栏内部会左右抖)。
  31. const clock = ruleBody(source, 'cockpit-shell__clock')
  32. check(/display:\s*inline-block/.test(clock), '时钟槽位要 display:inline-block 才吃得到 min-width')
  33. check(/flex:\s*0 0 auto/.test(clock), '时钟槽位不参与挤压')
  34. check(/text-align:\s*right/.test(clock), '时钟槽位内右对齐(左缘固定,只右侧字形变化)')
  35. check(/font-variant-numeric:\s*tabular-nums/.test(clock), '时钟数字必须等宽')
  36. const slot = clock.match(/min-width:\s*calc\((\d+(?:\.\d+)?)\s*\*\s*var\(--u\)\)/)
  37. check(Boolean(slot), '时钟必须有固定 min-width 数字槽位(右列下限靠它保持常量)')
  38. if (slot) {
  39. // 最宽一次读数(2384×1160 实测 HH:MM:SS 占 ~101u)必须塞得进槽位,否则 min-width 形同虚设。
  40. check(Number(slot[1]) >= 101, `时钟槽位 ${slot[1]}u 不足以容纳最宽读数(实测 ~101u)`)
  41. }
  42. // 位置改动只许动位置:导航语义、归位/返回全球按钮、时钟本体都不变。
  43. assertModeNavIntact(source)
  44. check(source.includes('cockpit-shell__home'), '归位按钮保持原样')
  45. check(source.includes('cockpit-shell__return-global'), '返回全球按钮保持原样')
  46. check(source.includes('cockpit-shell__clock'), '时钟保持原样')
  47. report('t102-topbar-position')