t102-topbar-position.mjs 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
  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. // 入口一:右列保留 min-content 下限(溢出兜底,票 37)——但见入口三,它现在必须是常量。
  19. const columns = declaration(source, 'grid-template-columns')
  20. check(columns.includes('minmax(240px, 1fr) auto minmax(min-content, 1fr)'), '顶栏三列必须保留右列 min-content 下限')
  21. // 入口二:一级菜单整组平移,位置与内容彻底解耦。用 transform 不用 margin。
  22. const modes = ruleBody(source, 'cockpit-shell__modes')
  23. check(/transform:\s*translateX\(calc\(-\d+(?:\.\d+)?\s*\*\s*var\(--u\)\)\)/.test(modes), '一级菜单必须整组 translateX 左移(居中偏左)')
  24. check(!/margin-left/.test(modes), '左移不得用 margin-left(会被 grid 再分配,等于把裁决权交回内容)')
  25. // 入口三:时钟读数宽度恒由声明决定,而不是由「这一刻几点几分几秒」决定。
  26. // 这是让入口一的下限变成常量的前提 —— 槽位一旦被删,min-content 就又开始随秒针抖。
  27. const clock = ruleBody(source, 'cockpit-shell__clock')
  28. check(/display:\s*inline-block/.test(clock), '时钟槽位要 display:inline-block 才吃得到 min-width')
  29. check(/flex:\s*0 0 auto/.test(clock), '时钟槽位不参与挤压')
  30. check(/text-align:\s*right/.test(clock), '时钟槽位内右对齐(左缘固定,只右侧字形变化)')
  31. check(/font-variant-numeric:\s*tabular-nums/.test(clock), '时钟数字必须等宽')
  32. const slot = clock.match(/min-width:\s*calc\((\d+(?:\.\d+)?)\s*\*\s*var\(--u\)\)/)
  33. check(Boolean(slot), '时钟必须有固定 min-width 数字槽位(右列下限靠它保持常量)')
  34. if (slot) {
  35. // 最宽一次读数(2384×1160 实测 HH:MM:SS 占 ~101u)必须塞得进槽位,否则 min-width 形同虚设。
  36. check(Number(slot[1]) >= 101, `时钟槽位 ${slot[1]}u 不足以容纳最宽读数(实测 ~101u)`)
  37. }
  38. // 位置改动只许动位置:导航语义、归位/返回全球按钮、时钟本体都不变。
  39. assertModeNavIntact(source)
  40. check(source.includes('cockpit-shell__home'), '归位按钮保持原样')
  41. check(source.includes('cockpit-shell__return-global'), '返回全球按钮保持原样')
  42. check(source.includes('cockpit-shell__clock'), '时钟保持原样')
  43. report('t102-topbar-position')