Skip to content

Consolidate @supports gating checks and fix ungated color-mix() in components (re-merge of #565) - #567

Merged
jackgranatowski merged 1 commit into
mainfrom
claude/pr-565-merge-error-8mnbdr
Jul 8, 2026
Merged

Consolidate @supports gating checks and fix ungated color-mix() in components (re-merge of #565)#567
jackgranatowski merged 1 commit into
mainfrom
claude/pr-565-merge-error-8mnbdr

Conversation

@jackgranatowski

Copy link
Copy Markdown
Contributor

Re-introduces the changes from #565 as a cleanly-mergeable PR.

#565's original web merge succeeded at the git level but the request errored before GitHub could update its status, leaving it frozen as "Closed" (GitHub refused to reopen it). Since the code was already in main, no fresh PR could show a diff. This was resolved by reverting it (#566) and re-adding it here, so the work lands in history with a proper "Merged" badge. The final tree is byte-for-byte identical to the original merge.


Summary

Consolidates the fragmented @supports-gating validation logic into a shared, robust scanner (tests/supports-helpers.js), expands P2 coverage from tokens.css alone to all source CSS files, and fixes an ungated color-mix() declaration in optional/components.css that violated the framework's modern-expression gating policy.

Key changes

  • New shared scanner (tests/supports-helpers.js): Implements stripSupports() and findUngatedModernExpressions() helpers that are character-based, parenthesis-aware, and comment-stripped. Replaces the line-based scanner in P7 that historically miscounted braces inside @supports prelude expressions (e.g., @property feature queries).
  • Expanded P2 coverage (tests/tier1-p2-coverage.test.js): Now scans all core/*.css and optional/*.css files (not just tokens.css), catching modern expressions used directly in component/utility rules. Uses the shared scanner so P2 and P7 can never drift.
  • New P7 whole-bundle guard (tests/tier1-p7-oldengine.test.js): Adds a second check that scans every declaration in the built bundle (custom properties AND plain properties).
  • Fixed ungated color-mix() (optional/components.css): Wrapped the .sf-btn--secondary:hover and .sf-btn--ghost:hover color-mix() background declarations in an @supports block.
  • New contract test (tests/configurator-data-contract.test.js): Validates generated configurator data files maintain their required schema.
  • Improved error messages: metaFor() helper in scripts/lib/api-index/extract.js and the FILE_TITLES pattern in scripts/gen-class-reference.js fail loudly on missing metadata.
  • Enhanced string literal handling (scripts/lib/parse.js): stripStrings() and maskStrings() now honour backslash escapes.
  • Artifact tracking safeguard (scripts/check-artifacts.js): Declared artifact outputs must be git-tracked.
  • Updated artifact registry (scripts/artifacts.json): Added classes.generated.json and the full-api demos.
  • Documentation updates: Clarified version-sync ownership in CLAUDE.md; status headers on historical reports.
  • Removed unused dependency: Removed lightningcss from devDependencies.

Generated by Claude Code

@jackgranatowski
jackgranatowski merged commit 4a09f62 into main Jul 8, 2026
9 of 10 checks passed
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@jackgranatowski, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 54 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5d256a2b-9ed8-438b-9205-388651dec9c9

📥 Commits

Reviewing files that changed from the base of the PR and between 4831518 and 8f60361.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (18)
  • CLAUDE.md
  • analysis/gap-analysis.md
  • demos/full-api-demo-with-overrides.html
  • demos/full-api-demo.html
  • demos/ultimate-override.css
  • optional/components.css
  • package.json
  • reports/full-api-audit/REPORT.md
  • scripts/artifacts.json
  • scripts/check-artifacts.js
  • scripts/gen-class-reference.js
  • scripts/lib/api-index/extract.js
  • scripts/lib/parse.js
  • tests/configurator-data-contract.test.js
  • tests/supports-helpers.js
  • tests/supports-helpers.test.js
  • tests/tier1-p2-coverage.test.js
  • tests/tier1-p7-oldengine.test.js
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/pr-565-merge-error-8mnbdr

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Consolidate @supports gating checks and fix ungated color-mix() usage

🐞 Bug fix 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Centralize @supports gating detection into a shared, robust CSS scanner used by P2 and P7.
• Expand gating coverage to all source CSS and the built bundle; add targeted regression fix for
 components.
• Harden generator/artifact workflows with contract tests, stricter metadata failures, and clearer
 docs.
Diagram

graph TD
  A["core/*.css"] --> C["P2 coverage test"] --> B["supports-helpers"]
  D["optional/*.css"] --> C
  E["dist/slashed.full.css"] --> F["P7 oldengine test"] --> B
  G["optional/components.css"] --> D
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use a real CSS parser (PostCSS/csstree) for @supports/declaration detection
  • ➕ More robust handling of edge cases (nested constructs, unusual at-rules) without bespoke scanning logic
  • ➕ Easier to extend beyond current modern-expression checks
  • ➖ Adds dependency + maintenance burden; may slow tests/CI for large bundle scans
  • ➖ Requires careful configuration to match current “strip supports then search” policy semantics
2. Perform gating checks during bundling (fail build) instead of (or in addition to) tests
  • ➕ Moves policy enforcement closer to the artifact creation point; prevents producing a bad dist file
  • ➕ Can reuse existing bundler parsing/matching utilities
  • ➖ Harder to iterate quickly when authoring source CSS (feedback comes later in pipeline)
  • ➖ Build scripts become policy-heavy; tests still desirable for regression protection

Recommendation: The PR’s shared, character-based scanner is a good fit here: it avoids new parsing dependencies while addressing the known failure modes (braces in @supports prelude, delimiters inside strings) and ensures P2/P7 cannot drift. Keep the unit tests for the scanner as the long-term safety net; consider a full parser only if the policy expands materially beyond a small set of expression-pattern checks.

Files changed (18) +420 / -101

Bug fix (2) +22 / -8
components.cssGate hover tint color-mix() backgrounds behind @supports +13/-3

Gate hover tint color-mix() backgrounds behind @supports

• Removes ungated color-mix() backgrounds from secondary/ghost button hover rules. Reintroduces them inside an @supports block with an explanatory comment to comply with the framework’s modern-expression gating policy.

optional/components.css

parse.jsFix string stripping/masking to honor backslash escapes +9/-5

Fix string stripping/masking to honor backslash escapes

• Updates stripStrings() and maskStrings() regexes to correctly handle escaped quotes (e.g., content: "\""), preventing premature string termination and false matches in downstream scanners.

scripts/lib/parse.js

Refactor (2) +52 / -26
gen-class-reference.jsHarden class reference generation and share parse helpers +32/-24

Harden class reference generation and share parse helpers

• Replaces inline comment/string stripping with shared stripComments/stripStrings and requireFile to match the authoritative audit parsing contract. Splits FILE_META into FILE_TITLES and throws a clear error if a class source file lacks a title mapping.

scripts/gen-class-reference.js

extract.jsAdd metaFor() to fail loudly on missing FILE_META entries +20/-2

Add metaFor() to fail loudly on missing FILE_META entries

• Introduces a guarded FILE_META lookup that throws actionable errors when registry-sources includes a file not described in FILE_META. Uses metaFor() for both class extraction and token entry construction to avoid opaque undefined-property crashes.

scripts/lib/api-index/extract.js

Tests (5) +289 / -54
configurator-data-contract.test.jsAdd contract test for configurator generated data schema +66/-0

Add contract test for configurator generated data schema

• Adds a node:test suite validating required keys exist for token/class/bundle rows and that generated registries are non-empty. Also asserts token-registry.generated.json mirrors token-registry.json to detect drift.

tests/configurator-data-contract.test.js

supports-helpers.jsIntroduce shared @supports scanner and modern-expression detector +113/-0

Introduce shared @supports scanner and modern-expression detector

• Adds stripSupports() (parenthesis-aware, comment-stripped, string-safe) to remove gated blocks reliably. Adds findUngatedModernExpressions() to detect ungated light-dark(), oklch(from …), and color-mix() across both custom properties and ordinary declarations.

tests/supports-helpers.js

supports-helpers.test.jsUnit test the @supports scanner edge cases +62/-0

Unit test the @supports scanner edge cases

• Adds tests covering @property feature-query prelude braces, braces/parens inside strings, and avoiding @supports matches inside string values. Validates findUngatedModernExpressions() flags ungated usage and ignores properly gated usage.

tests/supports-helpers.test.js

tier1-p2-coverage.test.jsExpand P2 to scan all source CSS using shared scanner +32/-27

Expand P2 to scan all source CSS using shared scanner

• Replaces the old tokens.css-only, line-based checks with a loop over all core/*.css and optional/*.css files. Uses findUngatedModernExpressions() and produces aggregated, actionable failure messages listing offending lines.

tests/tier1-p2-coverage.test.js

tier1-p7-oldengine.test.jsReuse shared scanner and add whole-bundle ungated-expression guard +16/-27

Reuse shared scanner and add whole-bundle ungated-expression guard

• Removes the legacy line-based stripSupports implementation and imports shared helpers. Adds a new P7 test scanning the entire built bundle for ungated modern expressions across all declarations, not just custom properties.

tests/tier1-p7-oldengine.test.js

Documentation (6) +32 / -12
CLAUDE.mdClarify version-sync ownership and which artifacts are build-derived +13/-9

Clarify version-sync ownership and which artifacts are build-derived

• Expands the version sync table to include explicit ownership and verification mechanisms. Clarifies that dist CSS stamps and the configurator version pill are build-derived and should not be manually synced.

CLAUDE.md

gap-analysis.mdAdd status header marking competitive analysis as historical snapshot +5/-0

Add status header marking competitive analysis as historical snapshot

• Adds a status/ownership comment stating the document is a one-off snapshot and not CI-managed. Provides guidance to supersede with a fresh analysis rather than editing in place.

analysis/gap-analysis.md

full-api-demo-with-overrides.htmlBump demo header version string +1/-1

Bump demo header version string

• Updates the displayed SLASHED version in the full API demo with overrides to v0.7.3.

demos/full-api-demo-with-overrides.html

full-api-demo.htmlBump demo header version string +1/-1

Bump demo header version string

• Updates the displayed SLASHED version in the baseline full API demo to v0.7.3.

demos/full-api-demo.html

ultimate-override.cssUpdate generated override banner version string +1/-1

Update generated override banner version string

• Updates the generated header comment to reflect SLASHED v0.7.3.

demos/ultimate-override.css

REPORT.mdDocument audit harness status and snapshot ownership +11/-0

Document audit harness status and snapshot ownership

• Adds an ownership/status header explaining the audit harness is manually run and not part of CI. Clarifies how committed snapshots are used by tests and when they must be refreshed.

reports/full-api-audit/REPORT.md

Other (3) +25 / -1
package.jsonRemove unused lightningcss devDependency +0/-1

Remove unused lightningcss devDependency

• Drops lightningcss from devDependencies, reducing dependency surface area.

package.json

artifacts.jsonRegister classes.generated.json and full-api demo outputs as tracked artifacts +12/-0

Register classes.generated.json and full-api demo outputs as tracked artifacts

• Adds classes.generated.json to configurator artifact lists. Introduces a new artifact entry covering full-api demo HTML/CSS outputs generated by demos/generate.mjs.

scripts/artifacts.json

check-artifacts.jsFail artifact checks when declared outputs are not git-tracked +13/-0

Fail artifact checks when declared outputs are not git-tracked

• Adds a guard to error when an artifact output is untracked/ignored, since git diff-based freshness checks cannot validate it. Prevents silently unguarded artifact drift.

scripts/check-artifacts.js

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 12 rules

Grey Divider


Remediation recommended

1. @supports suffix false match 🐞 Bug ≡ Correctness
Description
stripSupports() treats any text starting with "@supports" as the real at-rule without verifying a
right-side token boundary, so an at-rule like "@supports-foo {…}" would be stripped and could hide
ungated modern expressions from P2/P7.
Code

tests/supports-helpers.js[R51-63]

+    // Match `@supports` only at a token boundary so it is never matched inside
+    // a selector or value.
+    if (src.startsWith('@supports', i) && (i === 0 || /[\s{}();,]/.test(src[i - 1]))) {
+      // Skip the prelude to the body-opening `{` at parenthesis depth 0.
+      let j = i + '@supports'.length;
+      let paren = 0;
+      while (j < n) {
+        const c = src[j];
+        if (c === '"' || c === "'") { j = skipString(j); continue; }
+        if (c === '(') paren++;
+        else if (c === ')') paren--;
+        else if (c === '{' && paren === 0) break;
+        j++;
Relevance

⭐⭐⭐ High

Team has accepted fixes preventing test false-positives/negatives in gating scanners (e.g., PR141);
@supports scanner robustness is core (PR254/567).

PR-#141
PR-#254
PR-#567

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new scanner only checks the character *before* the match and uses startsWith('@supports', i),
so @supports-foo would satisfy the condition and be treated as a supports block. Because P2/P7
rely on this scanner, such a false match would remove unrelated blocks and reduce the effectiveness
of modern-expression gating checks.

tests/supports-helpers.js[51-77]
tests/tier1-p2-coverage.test.js[24-46]
tests/tier1-p7-oldengine.test.js[19-89]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`tests/supports-helpers.js` `stripSupports()` matches `@supports` by prefix only. If future CSS contains an unknown/custom at-rule whose name begins with `supports` (e.g. `@supports-foo { ... }`), the scanner will incorrectly treat it as a real `@supports` block and remove it, potentially masking ungated `color-mix()`/`light-dark()`/`oklch(from ...)` usages.

### Issue Context
This helper underpins both the source-level (P2) and bundle-level (P7) gating tests, so a false match here can create false negatives across the gating suite.

### Fix Focus Areas
- tests/supports-helpers.js[51-63]

### Suggested change
Tighten the `@supports` detection to also require a **right-side boundary** (i.e., the character after `@supports` must not be a CSS ident character like `[A-Za-z0-9_-]`). For example:
- Compute `const end = i + '@supports'.length; const next = src[end] ?? ''`
- Only treat it as `@supports` if `next` is empty OR `next` is a delimiter like whitespace / `(` / `{` / etc., and specifically **not** `[-\w]`.

Optionally add a small unit test in `tests/supports-helpers.test.js` asserting that `@supports-foo { ... }` is *not* stripped.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread tests/supports-helpers.js
Comment on lines +51 to +63
// Match `@supports` only at a token boundary so it is never matched inside
// a selector or value.
if (src.startsWith('@supports', i) && (i === 0 || /[\s{}();,]/.test(src[i - 1]))) {
// Skip the prelude to the body-opening `{` at parenthesis depth 0.
let j = i + '@supports'.length;
let paren = 0;
while (j < n) {
const c = src[j];
if (c === '"' || c === "'") { j = skipString(j); continue; }
if (c === '(') paren++;
else if (c === ')') paren--;
else if (c === '{' && paren === 0) break;
j++;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. @supports suffix false match 🐞 Bug ≡ Correctness

stripSupports() treats any text starting with "@supports" as the real at-rule without verifying a
right-side token boundary, so an at-rule like "@supports-foo {…}" would be stripped and could hide
ungated modern expressions from P2/P7.
Agent Prompt
### Issue description
`tests/supports-helpers.js` `stripSupports()` matches `@supports` by prefix only. If future CSS contains an unknown/custom at-rule whose name begins with `supports` (e.g. `@supports-foo { ... }`), the scanner will incorrectly treat it as a real `@supports` block and remove it, potentially masking ungated `color-mix()`/`light-dark()`/`oklch(from ...)` usages.

### Issue Context
This helper underpins both the source-level (P2) and bundle-level (P7) gating tests, so a false match here can create false negatives across the gating suite.

### Fix Focus Areas
- tests/supports-helpers.js[51-63]

### Suggested change
Tighten the `@supports` detection to also require a **right-side boundary** (i.e., the character after `@supports` must not be a CSS ident character like `[A-Za-z0-9_-]`). For example:
- Compute `const end = i + '@supports'.length; const next = src[end] ?? ''`
- Only treat it as `@supports` if `next` is empty OR `next` is a delimiter like whitespace / `(` / `{` / etc., and specifically **not** `[-\w]`.

Optionally add a small unit test in `tests/supports-helpers.test.js` asserting that `@supports-foo { ... }` is *not* stripped.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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