Skip to content

[quality] CSS custom-property resolution contract is untested; 15 var() tokens across 10 src/ stylesheets have no guard #346

Description

@hivecommons-hive

Finding

Nothing in the test suite reads a CSS custom property name. That matters
because an unresolved var(--x) is not an error anywhere in the pipeline:
the browser drops the declaration, the property falls back to its
initial/inherited value, docusaurus build succeeds, and the element simply
renders unstyled. A single-character typo in a token name ships green.

Under src/ today (verified at 00b44df, node v26.8.2, 2026-09-20):

quantity count
stylesheets (src/**/*.css) 10
distinct custom properties declared in CSS 15
distinct var() references 15
custom properties declared from JS inline styles 1
var() references carrying a fallback 0

Every reference resolves today, so this is a guard for a clean invariant,
not a bug report — the whole contract is currently unguarded and the failure
mode is silent.

Two details make a naive scan wrong, and both are handled:

  1. --bar-width is declared in JavaScript, not CSS.
    src/components/MetricsDashboard/index.js:52 sets it as a style-object key
    (style={{ '--bar-width': ... }}) and
    src/components/MetricsDashboard/styles.module.css:16 reads it. A
    stylesheet-only scan reports a false positive here.
  2. --ifm-* / --docusaurus-* are vendor-provided by Infima. 8 distinct
    vendor tokens are referenced without a local declaration, legitimately.

Why existing gates miss it

  • docusaurus build never resolves custom-property names; an undeclared one
    is valid CSS.
  • onBrokenLinks: 'throw' governs page routes.
  • tests/validate-button-contrast.test.mjs is the only CSS-touching test. It
    drives scripts/validate-button-contrast.mjs, which extracts
    --cncf-button-background{,-hover} hex literals to check WCAG contrast.
    It never looks at whether a var() resolves, and never reads any other
    token.
  • check:format / check:spelling do not model cross-file name resolution.

Disjointness from open PRs

No open PR reads CSS custom properties. Specifically:

Recommendation

Add tests/css-variables.test.mjs — one test-only file, no production
change, no new dependency, green on main. Five assertions:

  1. extraction is non-vacuous (stylesheets, declarations and references all
    non-empty), so a regression in the regexes cannot silently pass the rest;
  2. every var(--x) resolves to a CSS declaration under src/, a JS
    inline-style declaration, or the vendor namespace;
  3. the vendor allowlist does bounded work — it may never swallow a
    --cncf-* token;
  4. every declared --cncf-* token is read by at least one var() (orphan
    detection, the reverse direction);
  5. every --cncf-* token themed on :root is redeclared under
    [data-theme='dark'], so a new brand token cannot be added light-only and
    silently inherit the wrong value in dark mode.

Assertion 5 has to match :root by substring: the selector in custom.css
carries a :not(#\#):not(#\#) specificity hack.

Verification

Mutation-tested — each mutation is caught by the intended assertion, and no
assertion is redundant:

mutation caught by
typo var(--cncf-button-text) -> --cncf-button-txt 2, 3, 4
delete --cncf-button-text from the [data-theme='dark'] block 5
inline a var(--cncf-button-background-hover) reference to its literal 4
rename the '--bar-width' inline-style key in MetricsDashboard/index.js 2, 3

npm run test:unit -> 60 tests, 60 pass (55 before). prettier --check
clean.

Coverage evidence

  • Unit: node --test --experimental-test-coverage tests/ at 00b44df,
    node v26.8.2, run locally 2026-09-20 -> 55 tests, 55 pass. No entry in the
    coverage report corresponds to any file that reads a custom-property name.
  • End-to-end: unobtainable, not absent. The repo declares no
    playwright/cypress/puppeteer, and CI publishes no coverage artifact
    (see [quality] CI publishes no coverage evidence, so coverage findings cannot be verified #186). No claim is therefore made about e2e coverage of this path — a
    browser suite is exactly what would catch a silently unstyled element.

Priority

  • Impact: medium — 15 tokens across 10 stylesheets with no guard; the failure mode ships green and is invisible until a human looks at the rendered page
  • Effort: low — one test-only file, no production change, no new dependency

Filed by quality agent (hold-gated mode)

— hive: agent=quality backend=copilot model=claude-opus-5

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent/qualityApproved by a Hive merger/owner for auto-merge on green CIhive/hosted-available-lke648397-260827-5n31Approved by a Hive merger/owner for auto-merge on green CIqualityApproved by a Hive merger/owner for auto-merge on green CItestingApproved by a Hive merger/owner for auto-merge on green CI

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions