Skip to content

fix(dashboard): define the CSS variables that were silently dropped, and guard the class - #5343

Open
songoow wants to merge 3 commits into
loopx-project:mainfrom
songoow:codex/design-token-font-definition
Open

songoow wants to merge 3 commits into
loopx-project:mainfrom
songoow:codex/design-token-font-definition

Conversation

@songoow

@songoow songoow commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator

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 the
whole 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:

Selector Before After
.delivery-acceptance-content code body font (declaration dropped) Geist Mono stack
.personal-collaboration small body font (declaration dropped) Geist Mono stack
.personal-operator-credential-status body font (declaration dropped) Geist Mono stack
.personal-operator-credential border + background dropped 1px solid / rgb(250 250 250)
.personal-operator-credential input border dropped 1px solid
.personal-goal-delete:hover colour dropped rgb(217 0 0)
.personal-refresh-control.is-error small dropped rgb(217 0 0)

What changed

styles.css declares --font-sans and --font-mono; the body face resolves
through --font-sans. --pw-font-mono was a misspelled reference and now reads
--font-mono.

personal-workspace.css used four private names with no definition at all
(--pw-danger, --pw-border, --pw-canvas, --pw-surface). These now read the
existing --pw-red, --pw-line-strong, --pw-bg, and --pw-card tokens that
all 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 a
legitimate optional token.

goal-loopx-mode.css had three var(--font-mono, monospace) fallbacks naming
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.

New check scripts/check-css-custom-properties.mjs 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. Scope is the Dashboard surface.

New test 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.

docs/development/design.md listed fallback stacks no surface used. It now
names the real per-surface declarations, the rule they depend on, and the check.

Validation

  • check-css-custom-properties passes on every commit in this branch (the fix
    lands before the check, so no commit leaves the gate red).
  • The check was mutation-tested, since a non-zero exit is its only signal:
    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.
  • Inline-style token definitions are captured: --terminal-delay is set from
    App.tsx:546 and is correctly accepted, not reported.
  • font-token, workspace-theme, and personal-workspace-contract tests, plus
    tsc --noEmit, pass.
  • smoke:personal-workspace-packaged browser smoke passes — all 25 scenarios.
  • The check runs in the existing dashboard-acceptance job, after dependency
    install 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
@theme block, collapsing the 25 type sizes and 34 border radii, container
queries) and the brutal/paper/loopx theme overlap. Those need owner decisions
I should not make unilaterally, and they are safer on top of a working token layer
than underneath it.

songoow and others added 3 commits September 30, 2026 02:57
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 cocolord left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

动机

这个 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。
  • :root font 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)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants