Skip to content

fix(contracts): attribute component-manifest tokens to every CSS rule and anchor group class matchers - #6226

Open
dapsychyoo wants to merge 3 commits into
nexu-io:mainfrom
dapsychyoo:fix/components-manifest-extractor-6224
Open

fix(contracts): attribute component-manifest tokens to every CSS rule and anchor group class matchers#6226
dapsychyoo wants to merge 3 commits into
nexu-io:mainfrom
dapsychyoo:fix/components-manifest-extractor-6224

Conversation

@dapsychyoo

Copy link
Copy Markdown

Fixes #6224

Why

While packaging a third-party design system for Open Design I ran extractComponentsManifest against our fixture and found the component inventory it feeds into the agent's ## Reference component manifest prompt block was silently wrong (issue #6224). The pain: the agent is told the design system has components it doesn't have (form fields, layout primitives that are really Tailwind utility substrings), and is not told about token wiring it does have (every other flat rule and all nested-rule tokens were dropped) — and then follows "Prefer these manifest entries over inventing new component shapes" against bad data.

All three defects live in the same extractor (packages/contracts/src/design-systems/components-manifest.ts):

  1. The rule regex in extractSelectorTokenReferences consumed the closing }, so every other consecutive flat rule (and the first rule after a :root block) lost its token attribution.
  2. The [^{}]* body could not represent nested CSS blocks (what Tailwind v4 emits for states), dropping tokens and attributing them to declaration text as a pseudo-selector.
  3. Unanchored substring classMatchers (/form/i, /grid/i, /status/i, …) misclassified utility classes (transition-transform → inputs, grid-cols-2 → layout), and the \bselect\b selector matcher fired on the .select-none utility rule.

What users will see

No UI changes. Users with an active design system get a correct ## Reference component manifest block in the agent context: token lists now match what a human reads in the fixture CSS (including Tailwind v4 nested state rules), and component groups no longer claim form/layout/badge components that only exist as substrings of utility classes. Agent output should track the actual design system more faithfully.

Surface area

  • UI
  • Keyboard shortcut
  • CLI / env var
  • API / contract — behavior change in packages/contracts (extractComponentsManifest); no shape/type changes
  • Extension point — all 150 committed design-systems/*/components.manifest.json caches regenerated from the fixed extractor (guard enforces byte-parity with the extractor output)
  • i18n keys
  • New top-level dependency
  • Default behavior change
  • None

Note on the third-party naming contract: class matchers are now anchored to name segments (^form(?:$|-), ^grid$, …), matching the pattern of the existing ^btn(?:$|-) matcher. A root class must start with the group's term (e.g. card-*, not rack-card) — same contract the selector matchers already implied.

Screenshots

Not applicable (no UI change).

Bug fix verification

  • Test path that reproduces the bug: packages/contracts/tests/components-manifest.test.ts — three new specs: "attributes token references to every consecutive flat rule", "attributes tokens inside nested rules to real selectors, never to declaration text", "does not classify utility classes into unrelated groups via substring matches".
  • Did the test go red on main and green on this branch? yes — with the extractor unchanged from main the specs failed exactly per the issue (['--b', '--d'] instead of ['--a', '--b', '--c', '--d']; .select-none in the inputs group; missing .chip-demo:hover), and all pass after the fix.

Validation

  • pnpm --filter @open-design/contracts test — 249 passed (includes the 3 new red→green specs)
  • pnpm guard — exit 0 (the design-system manifest byte-parity check drove the regeneration of the 150 components.manifest.json caches; component-manifest extraction check passes across all 151 fixtures)
  • pnpm typecheck — exit 0
  • pnpm --filter @open-design/daemon test — 6108 passed; 4 failures in run-retry-runtime, run-resume-on-failure, langfuse-trace reproduce identically on a clean main worktree (pre-existing/flaky, unrelated to this change)
  • pnpm --filter @open-design/web test — 4865 passed
  • e2e/tests/scripts/check-design-system-manifests.test.ts — 16 passed

The regenerated caches diff is large but mechanical: recovered token references (previously ~half were dropped) and removed substring false-positive group memberships (e.g. metric-grid no longer classified as a layout class). Spot-checked design-systems/agentic/components.manifest.json.

🤖 Generated with Claude Code

… and anchor group class matchers

The components-manifest extractor silently produced wrong inventories:

- extractSelectorTokenReferences used a rule regex that consumed the
  closing brace, so every other consecutive flat rule (and the first rule
  after a :root block) lost its token attribution.
- The regex body could not represent nested CSS blocks (Tailwind v4 state
  output), dropping tokens and emitting declaration text as garbage
  selectors.
- Unanchored substring classMatchers (/form/i, /grid/i, /status/i, ...)
  misclassified utility classes like transition-transform and grid-cols-2
  into unrelated component groups, and the \bselect\b selector matcher
  fired on the .select-none utility rule.

Replace the single-regex rule matcher with a small brace-depth CSS
scanner shared by selector extraction and token attribution. The scanner
attributes each rule's own declarations to its resolved selector list,
resolves one-or-more levels of CSS nesting (&:hover -> .parent:hover,
descendant nesting, nested conditional at-rules), skips :root and
non-conditional at-rules, and never loses a following rule. Anchor the
substring class matchers to prefix segments and constrain the select
selector matcher to element position. Regenerate all 150 committed
components.manifest.json caches from the fixed extractor.

Fixes nexu-io#6224
@lefarcen
lefarcen requested a review from mrcfps July 29, 2026 10:04
@lefarcen lefarcen added size/XXL PR changes 1500+ lines risk/medium Medium risk: regular code changes type/bugfix Bug fix labels Jul 29, 2026
@lefarcen lefarcen added the needs-validation Runtime change detected; needs human or /explore agent validation. label Jul 29, 2026
@lefarcen

Copy link
Copy Markdown
Contributor

🧪 This PR changes the agent-facing component manifest output, so we'll want a manual QA pass before merge. Please hold off self-merging for now; we'll loop QA in once it's merge-ready.

@mrcfps mrcfps left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@dapsychyoo Thank you for the careful fix and the red-to-green regression coverage. The three reported issue cases now behave correctly, and I verified the contracts tests, typechecks, guard/cache parity, and focused manifest e2e checks locally. I found three merge-safe CSS parsing and matching edge cases to tighten; details are inline. Nice work on a tricky extractor. 🙌

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

Comment thread packages/contracts/src/design-systems/components-manifest.ts Outdated
Comment thread packages/contracts/src/design-systems/components-manifest.ts Outdated
Comment thread packages/contracts/src/design-systems/components-manifest.ts Outdated
@lefarcen

Copy link
Copy Markdown
Contributor

Hey @dapsychyoo@mrcfps has now left three non-blocking follow-ups to tighten before the next pass: make the brace scanner quote/escape-aware, traverse grouping at-rules like @scope / @starting-style, and avoid matching literal select text inside attribute values as a form-control signal.

Once those are addressed and pushed, this should be in a good spot for another review round.

💡 To drive this PR to merge hands-free, paste this to your AI coding agent (Claude Code / Codex / opencode / Cursor …):
Take over nexu-io/open-design#6226 until it merges — read https://raw.githubusercontent.com/nexu-io/looper/main/skills/pr-takeover/SKILL.md and follow it.

… and select type-selector matching

Treat quoted strings as text when stripping comments and scanning brace
structure, traverse @scope and @starting-style like other grouping
at-rules, and match select only in type-selector position instead of as
literal text inside class names or attribute values.
@lefarcen

Copy link
Copy Markdown
Contributor

Heads-up: PR #6250 is also open against the same extractor path and linked issue. Both PRs touch packages/contracts/src/design-systems/components-manifest.ts and the new components-manifest-6224 regression coverage, so it would be worth comparing approaches before the two threads drift further.

@lefarcen

Copy link
Copy Markdown
Contributor

Hey @dapsychyoo — two of the three follow-ups from @mrcfps are now resolved on the current head. The remaining open thread is the quote/escape-aware brace-scanner case, so that’s the one to finish out before the next pass.

Once that thread is addressed (or resolved with @mrcfps), this should be ready for another quick review round.

@mrcfps mrcfps left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@dapsychyoo Thanks for the careful follow-up—the select and grouping-at-rule concerns are fixed, and the test/guard/cache-parity work is solid. I found one reproducible scanner regression that can erase a Tailwind-style fixture’s selector inventory, plus one narrower nested-selector correctness issue; details are inline. This is close, and I appreciate the thoughtful work on a tricky extractor. 🙏

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

Comment thread packages/contracts/src/design-systems/components-manifest.ts Outdated
Comment thread packages/contracts/src/design-systems/components-manifest.ts Outdated
@lefarcen

Copy link
Copy Markdown
Contributor

Hey @dapsychyoo@mrcfps has now turned the current-head review into one blocking item plus one narrower non-blocking follow-up. The blocker is the CSS scanner regression around escaped quotes / escaped-brace tokens and data-URL handling; the second thread is the nested-selector & replacement inside quoted attribute values.

Please use the latest @mrcfps review as the source of truth for the next push. Once those two threads are addressed, this should be ready for another pass.

…text in the scanner

- walkCssBlock, findMatchingBrace, and stripCssComments now consume CSS
  escapes (backslash + char) as text before quote detection, so Tailwind
  arbitrary-content selectors like .content-\[\'x\'\] no longer open a
  phantom string that swallows the rest of the fixture.
- Braces inside function values (unquoted url(...) data URIs) are no
  longer structural: both scanners track paren depth and only treat {}
  as block delimiters at depth 0.
- resolveNestedSelectors expands & through a quote/escape-aware
  expandNestingSelector instead of replaceAll, so & inside quoted
  attribute values ([data-label="A&B"]) stays literal.
@lefarcen
lefarcen requested a review from mrcfps July 30, 2026 11:15

@mrcfps mrcfps left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@dapsychyoo Thank you for the careful fixes and follow-up hardening. I verified the extractor now preserves consecutive and nested-rule token attribution, distinguishes real component selectors/classes from utility substrings, and safely handles grouping at-rules, quoted/escaped CSS, function-value braces, and nested selector ampersands. The contracts suite and typechecks, focused manifest checks, full workspace typecheck, and guard/cache-parity validation all pass; all 150 regenerated manifests contain only the expected token-reference and anchored class-membership updates. Nicely done on a deceptively tricky parser change. 🙌

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

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

Labels

needs-validation Runtime change detected; needs human or /explore agent validation. risk/medium Medium risk: regular code changes size/XXL PR changes 1500+ lines type/bugfix Bug fix

Projects

None yet

3 participants