Conversation
Six custom properties were referenced by the Dashboard stylesheets but never defined anywhere in the repository. A bare `var(--x)` with no fallback makes the whole declaration invalid at computed-value time, so the browser dropped it and the element silently inherited the body face. Nothing logged and no test failed. Verified in Chromium against the built stylesheet: before this change `.delivery-acceptance-content code` computed to the body font; after, it computes to the Geist Mono stack. - `--font-sans` / `--font-mono` are now declared in `styles.css`. The body face resolves through `--font-sans`, and the stylesheets that referenced `--font-mono` now compute instead of dropping. - `--pw-font-mono` was a misspelled reference; it now reads `--font-mono`. - `--pw-danger`, `--pw-border`, `--pw-canvas`, and `--pw-surface` had no definition, so the Goal delete hover colour, the refresh error colour, and the operator-credential panel border, background, and input border were all being discarded. They now read the existing `--pw-red`, `--pw-line-strong`, `--pw-bg`, and `--pw-card` tokens that the three theme blocks already define, rather than adding a fourth set of names. - The three `var(--font-mono, monospace)` fallbacks in the Goal LoopX mode stylesheets named a bare `monospace` keyword, so those blocks rendered a different face from every other code surface whenever the token was missing. They now name the same family as the token. `var(--pw-surface, #fff)` in `.personal-manager-team-result` is left alone: it supplies a fallback and is a legitimate optional token. Signed-off-by: song <22676124+songoow@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Add `scripts/check-css-custom-properties.mjs` plus a focused contract test, so the class of defect fixed in the previous commit cannot return silently. The check fails on any bare `var(--token)` that no stylesheet — or inline style — defines. `var(--token, fallback)` is allowed, because a fallback is an explicit statement that the token is optional and the declaration still computes. This distinction matters: `--pw-surface` is legitimately optional in one place while its bare use elsewhere was a real bug. Two guards keep the check from passing vacuously, since a non-zero exit is its only signal. It fails when the scope yields no stylesheets, and it fails when the definition scan finds fewer than 30 tokens — a floor asserted from the tree rather than from the scan, so a broken definition regex cannot report a clean result. Both were validated by mutation: reintroducing an undefined reference, deleting a real token, and breaking the definition scan each produce exit 1. The check runs in the existing `dashboard-acceptance` job, after dependency install and before the coverage run, and is available locally as `npm run check:css-custom-properties`. `font-token.test.mjs` pins the specific decisions — what the two font tokens are defined as, that the regressed call sites resolve through them, and that the four dead private names stay gone. It joins `smoke:personal-workspace`. Scope is the Dashboard surface only. A scope is added when a real surface needs it; the marketing site defines its own tokens and `--terminal-delay` there is set from `App.tsx`, which the definition scan reads. Signed-off-by: song <22676124+songoow@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The typography section listed fallback stacks that no surface used, and did not say where the tokens live or what happens when one is missing. State the rule the fix depends on — reference the token, never repeat the stack, and define the token on the surface that uses it — and give the real declarations for both surfaces, since each bundles its own font files and the two differ. Note the check that enforces it and the `var(--token, fallback)` exception. Signed-off-by: song <22676124+songoow@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
cocolord
left a comment
There was a problem hiding this comment.
动机
这个 PR 修复了真实且可观察的 Dashboard 回归:bare var(--token) 在可见级联中没有定义时,浏览器会丢弃整条声明,代码文本、凭据状态、边框、背景和错误色因而静默回退。exact head 9b05fee62a76526738afc122c46476e5931e8c94 把旧 base 上检测到的 9 处未定义引用修到现有 token;具体 CSS 修复有明确 before/after 收益。
改动思路
styles.css 在 Dashboard 根层声明 --font-sans / --font-mono,personal workspace 把不存在的私有名称收敛到三套主题已有的 token;新脚本扫描 Dashboard CSS 的 bare var() 与 CSS/TSX 定义,并在 dashboard-acceptance 中阻断未定义引用。正向路径是扫描、求差集、空集放行;负向路径应是新增未定义 bare 引用后 exit 1。
具体改动
- workflow/package script 接入 required check,并把
font-token.test.mjs纳入 workspace smoke。 - 三个 CSS 文件恢复字体、错误色、边框和背景;design.md 记录 per-surface font token 规则。
- 新增 231 行 scanner 与 98 行 focused contract test。
关键代码讲解
collectDefinitions(scripts/check-css-custom-properties.mjs:105)构造唯一的“已定义 token”集合。collectBareReferences(:139)只收集无 fallback 的var(--token)。main(:151)执行 scope/反空检查、求差集并以 exit code 驱动 CI。:rootfont tokens(styles.css:20)为 Dashboard 两个入口提供可继承字体族。
对主干的风险
[P1] collectDefinitions 会把任意字符串对象键误判成 inline-style 定义。 第 127 行的 quoted-key 正则没有确认键位于 JSX style / CSSProperties 写入。用 exact-head 函数执行 { "--review-ghost": "not an inline style" } 与 .probe { color: var(--review-ghost); },结果把该 token 放进 definitions,undefinedReferences 为空,门禁错误通过。主题元数据、文档示例或 token catalog 都可能出现这种键;同名 CSS 拼写错误就会再次静默进入主干。最低修复是将 TS/TSX 识别限制为真实 style 写入(或 AST),保留 setProperty 分支,并提交“非样式对象键不能满足 CSS 引用”的负向 fixture。
exact head 的 scanner、font-token、workspace-theme、personal-workspace contract、TypeScript/Vite build 和 GitHub dashboard-acceptance 均通过;本机无可绑定 Chrome,未重复作者的 computed-style 截图。CI shard 3/4、聚合 pytest 与 merge-gate 虽红,但 immutable base 与 head 都复现为相同的 generated-twin / prompt-upgrade-hook 三个断言,且本 PR 不触及这些路径,属于单独的 merge-readiness hold。
我的整体评价
CSS 修复明确正向,复用现有主题 token 也合适;但这个 PR 把通用 scanner 设成 required CI,而它在核心负向路径上可被普通非样式对象键绕过,仓库又没有提交 scanner 自身的负向测试。“防止同类静默回归”是新增 231 行机制的主要收益,目前 whole-PR 证据不足以 APPROVE。修复分类并补 fixture 后,请重跑 checker、focused contracts、Dashboard build/acceptance。
English verdict: REQUEST_CHANGES for exact head 9b05fee62a76526738afc122c46476e5931e8c94. The CSS fix has clear value, but the required guard treats any quoted "--token": key in TS/TSX as an inline-style definition, so unrelated data can mask a genuinely undefined bare CSS variable. Narrow the classifier and add a committed negative fixture. Dashboard validation passes; unrelated Python shard failures reproduce unchanged on base and head.
| for (const match of text.matchAll(/setProperty\(\s*["'`](--[A-Za-z0-9_-]+)["'`]/g)) { | ||
| add(match[1], `setProperty in ${rel}`); | ||
| } | ||
| for (const match of text.matchAll(/["'`](--[A-Za-z0-9_-]+)["'`]\s*:/g)) { |
There was a problem hiding this comment.
[P1] Restrict this match to actual inline-style definitions. This regex scans every TS/TSX object, so unrelated data such as const metadata = { "--review-ghost": "not a style" } is added to definitions; a CSS .probe { color: var(--review-ghost); } is then removed from undefinedReferences and the required check exits successfully. That recreates the silent failure this gate is meant to prevent. Please use a style-aware/AST-bounded match (keeping setProperty separately) and commit a negative collision fixture plus a positive real-inline-style fixture.
Goal
Six CSS custom properties were referenced by the Dashboard stylesheets but never
defined anywhere in the repository. A bare
var(--x)with no fallback makes thewhole declaration invalid at computed-value time: the browser drops it and
the element silently inherits the body face. Nothing logs, no test fails, and the
component renders slightly wrong in production indefinitely.
This PR defines the missing tokens and adds a check so the class of defect cannot
return silently.
Observable result
Verified in Chromium against the built stylesheet, before and after:
.delivery-acceptance-content code.personal-collaboration small.personal-operator-credential-status.personal-operator-credentialborder + background1px solid/rgb(250 250 250).personal-operator-credential inputborder1px solid.personal-goal-delete:hovercolourrgb(217 0 0).personal-refresh-control.is-error smallrgb(217 0 0)What changed
styles.cssdeclares--font-sansand--font-mono; the body face resolvesthrough
--font-sans.--pw-font-monowas a misspelled reference and now reads--font-mono.personal-workspace.cssused four private names with no definition at all(
--pw-danger,--pw-border,--pw-canvas,--pw-surface). These now read theexisting
--pw-red,--pw-line-strong,--pw-bg, and--pw-cardtokens thatall three theme blocks already define. I did not add a fourth set of alias names —
four parallel token families is the condition this change is trying to reduce.
var(--pw-surface, #fff)elsewhere is left alone: it supplies a fallback and is alegitimate optional token.
goal-loopx-mode.csshad threevar(--font-mono, monospace)fallbacks naminga bare
monospacekeyword, so those blocks rendered a different face from everyother code surface whenever the token was missing. They now name the same family
as the token.
New check
scripts/check-css-custom-properties.mjsfails on any barevar(--token)that no stylesheet or inline style defines.var(--token, fallback)is allowed, because a fallback is an explicit statement that the token is
optional and the declaration still computes. Scope is the Dashboard surface.
New test
font-token.test.mjspins the specific decisions: what the two fonttokens are defined as, that the regressed call sites resolve through them, and
that the four dead private names stay gone.
docs/development/design.mdlisted fallback stacks no surface used. It nownames the real per-surface declarations, the rule they depend on, and the check.
Validation
check-css-custom-propertiespasses on every commit in this branch (the fixlands before the check, so no commit leaves the gate red).
reintroducing an undefined reference, deleting a real token, and breaking the
definition scan each produce exit 1. Two anti-vacuity guards (empty scope, and a
30-token floor asserted from the tree rather than from the scan) were each
triggered deliberately.
--terminal-delayis set fromApp.tsx:546and is correctly accepted, not reported.font-token,workspace-theme, andpersonal-workspace-contracttests, plustsc --noEmit, pass.smoke:personal-workspace-packagedbrowser smoke passes — all 25 scenarios.dashboard-acceptancejob, after dependencyinstall and before the coverage run; also available as
npm run check:css-custom-properties.Boundary
Control-plane and product surface change: this alters rendered Dashboard
behaviour, so it is proposed for review and left for the maintainer to merge.
Not in this PR, deliberately: the wider token work (moving
--color-*into a@themeblock, collapsing the 25 type sizes and 34 border radii, containerqueries) and the
brutal/paper/loopxtheme overlap. Those need owner decisionsI should not make unilaterally, and they are safer on top of a working token layer
than underneath it.