fix(contracts): attribute component-manifest tokens to every CSS rule and anchor group class matchers - #6226
Conversation
… 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
|
🧪 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
left a comment
There was a problem hiding this comment.
@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.|
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 Once those are addressed and pushed, this should be in a good spot for another review round.
|
… 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.
|
Heads-up: PR #6250 is also open against the same extractor path and linked issue. Both PRs touch |
|
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
left a comment
There was a problem hiding this comment.
@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.|
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 Please use the latest |
…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.
mrcfps
left a comment
There was a problem hiding this comment.
@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.
Fixes #6224
Why
While packaging a third-party design system for Open Design I ran
extractComponentsManifestagainst our fixture and found the component inventory it feeds into the agent's## Reference component manifestprompt 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):extractSelectorTokenReferencesconsumed the closing}, so every other consecutive flat rule (and the first rule after a:rootblock) lost its token attribution.[^{}]*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.classMatchers(/form/i,/grid/i,/status/i, …) misclassified utility classes (transition-transform→ inputs,grid-cols-2→ layout), and the\bselect\bselector matcher fired on the.select-noneutility rule.What users will see
No UI changes. Users with an active design system get a correct
## Reference component manifestblock 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
packages/contracts(extractComponentsManifest); no shape/type changesdesign-systems/*/components.manifest.jsoncaches regenerated from the fixed extractor (guard enforces byte-parity with the extractor output)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-*, notrack-card) — same contract the selector matchers already implied.Screenshots
Not applicable (no UI change).
Bug fix verification
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".mainand green on this branch? yes — with the extractor unchanged frommainthe specs failed exactly per the issue (['--b', '--d']instead of['--a', '--b', '--c', '--d'];.select-nonein 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 150components.manifest.jsoncaches; component-manifest extraction check passes across all 151 fixtures)pnpm typecheck— exit 0pnpm --filter @open-design/daemon test— 6108 passed; 4 failures inrun-retry-runtime,run-resume-on-failure,langfuse-tracereproduce identically on a cleanmainworktree (pre-existing/flaky, unrelated to this change)pnpm --filter @open-design/web test— 4865 passede2e/tests/scripts/check-design-system-manifests.test.ts— 16 passedThe regenerated caches diff is large but mechanical: recovered token references (previously ~half were dropped) and removed substring false-positive group memberships (e.g.
metric-gridno longer classified as a layout class). Spot-checkeddesign-systems/agentic/components.manifest.json.🤖 Generated with Claude Code