Skip to content

feat(css-trace): inspect CSS rules affecting a DOM element#4

Merged
JonasJesus42 merged 3 commits into
mainfrom
feat/css-trace-command
May 26, 2026
Merged

feat(css-trace): inspect CSS rules affecting a DOM element#4
JonasJesus42 merged 3 commits into
mainfrom
feat/css-trace-command

Conversation

@JonasJesus42
Copy link
Copy Markdown
Contributor

@JonasJesus42 JonasJesus42 commented May 18, 2026

Summary

New parity css-trace command that uses Chrome DevTools Protocol (CSS.getMatchedStylesForNode) to enumerate every CSS rule contributing to a target element, grouped by stylesheet source. Two modes:

  • Single URL: parity css-trace --url <url> --selector <sel> — lists all matched rules + computed styles.
  • Compare: parity css-trace --prod <url> --cand <url> --selector <sel> — traces both and diffs computed style values.

--filter <prop1,prop2,...> scopes the output to specific properties.

Why

Real bug encountered debugging the Bagaggio TanStack migration: the delivery drawer hung short of the viewport's right edge on desktop. Root cause was daisyUI v5's :root { scrollbar-gutter: var(--page-scroll-gutter, unset) } paired with the drawer-toggle rule flipping --page-scroll-gutter to stable when any drawer opens — reserving ~15px on the right that the position: fixed; width: 100% drawer couldn't reach.

Finding this required grep -h scrollbar-gutter node_modules/daisyui/**/*.css to enumerate library rules. parity css-trace --url <site> --selector html --filter scrollbar-gutter would have surfaced the rule and its source in seconds.

Example

$ parity css-trace --url http://localhost:5173 --selector html --filter scrollbar-gutter

── RESULT ────────────────────────────────────────────
URL: http://localhost:5173
Selector: html

  Computed:
    scrollbar-gutter: auto

  Rules (ordered by CDP — most specific last):
    stylesheet#... (/*! tailwindcss v4.2.4 ...)
      html {
        scrollbar-gutter: auto !important;
      }
    stylesheet#... (/*! tailwindcss v4.2.4 ...)
      (no selector text) {
        scrollbar-gutter: var(--page-scroll-gutter, unset);
      }

Implementation notes

  • Uses Playwright's page.context().newCDPSession(page) — no extra deps.
  • CSS.getStyleSheetText previews the first ~100 chars of each stylesheet to disambiguate sources (daisyUI vs app.css vs Tailwind utilities) since CDP doesn't return the URL.
  • Strips trailing !important baked into CDP property values and infers the flag, so output doesn't render !important !important.
  • :where(...) / anonymous-selector rules print as (no selector text) instead of an empty {.
  • --viewport mobile|tablet|desktop reuses the existing presets so viewport-dependent rules (media queries, viewport-units) line up with the regular run flow.

Test plan

  • bun run check passes
  • Single-URL mode runs against localhost and lists matched rules with source attribution
  • Compare mode against two URLs prints prod/cand sides + computed-style diff
  • Try on the live Bagaggio repro (drawer not flush-right) before the fix lands — confirms the daisyUI scrollbar-gutter rule shows up

🤖 Generated with Claude Code


Summary by cubic

Adds a new parity css-trace command to inspect which CSS rules affect a DOM element and optionally diff computed styles between two URLs. Now also traces inherited rules and marks their ancestor level for clearer debugging.

  • New Features

    • Modes: Single URL and Compare; shows computed styles and matched rules grouped by stylesheet.
    • Inheritance: includes rules from ancestors and labels them as "↑ inherited from ancestor (N)".
    • Filters & output: --filter to limit properties, --json for machine-readable output; --viewport presets and --settle <ms> to wait after load.
    • CDP: Uses Chrome DevTools via playwright; strips !important duplicates and prints anonymous selectors as (no selector text).
  • Bug Fixes

    • CLI validation: enforce --url is mutually exclusive with --prod/--cand; require both for comparison; clear error messages.
    • Replaced unused template literals with strings to satisfy Biome and restore CI.

Written for commit afe2a9a. Summary will update on new commits. Review in cubic

Adds `parity css-trace` — a CLI command that uses Chrome DevTools
Protocol (CSS.getMatchedStylesForNode) to enumerate every CSS rule
contributing to a target element, grouped by stylesheet source.

Two modes:
1. Single URL — `--url ... --selector ...` prints all matched rules,
   their source stylesheet, and the computed style for the selected
   properties.
2. Comparison — `--prod ... --cand ... --selector ...` traces both
   sides and emits a diff of computed-style values.

Use cases:
- Visual regression debugging: when a property's computed value
  differs between Fresh prod and the TanStack migration, instantly
  see which library / file is responsible.
- Library-introduced gotchas: e.g. daisyUI v5 sets
  `scrollbar-gutter: var(--page-scroll-gutter, unset)` on :root and
  toggles `--page-scroll-gutter: stable` when a drawer/modal opens,
  reserving ~15px on the right edge — `css-trace --url ... --selector
  html --filter scrollbar-gutter` surfaces that rule immediately,
  saving the usual `grep node_modules/daisyui/**/*.css` round trip.

Implementation notes:
- Filter prop (`--filter scrollbar-gutter,position,...`) trims output
  to the properties of interest in both `computed` and the matched
  rules.
- `--viewport mobile|tablet|desktop` reuses the same presets as the
  main `run` command, so observed differences in vw-dependent rules
  (media queries, viewport-units) line up with the regular checks.
- Strips trailing `!important` baked into CDP property values and
  derives the flag from either source, so output doesn't render
  `!important !important`.
- `:where(...)` / anonymous-selector rules now print as
  `(no selector text)` instead of an empty `{`.
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/cli.ts">

<violation number="1" location="src/cli.ts:205">
P2: Documented mutual-exclusion constraint between --url and --prod/--cand is not enforced in the action handler. Add a validation check that exits with a clear error message if conflicting options are provided, and that requires at least one URL mode.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/commands/css-trace.ts Outdated
Comment thread src/cli.ts
.description(
"Inspect which CSS rules (from which stylesheets) are affecting a DOM element. Single URL mode lists every matched rule; --prod + --cand mode diffs computed styles between Fresh and TanStack sides.",
)
.option("--url <url>", "Single URL to inspect (mutually exclusive with --prod/--cand)")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Documented mutual-exclusion constraint between --url and --prod/--cand is not enforced in the action handler. Add a validation check that exits with a clear error message if conflicting options are provided, and that requires at least one URL mode.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/cli.ts, line 205:

<comment>Documented mutual-exclusion constraint between --url and --prod/--cand is not enforced in the action handler. Add a validation check that exits with a clear error message if conflicting options are provided, and that requires at least one URL mode.</comment>

<file context>
@@ -196,6 +197,37 @@ program
+  .description(
+    "Inspect which CSS rules (from which stylesheets) are affecting a DOM element. Single URL mode lists every matched rule; --prod + --cand mode diffs computed styles between Fresh and TanStack sides.",
+  )
+  .option("--url <url>", "Single URL to inspect (mutually exclusive with --prod/--cand)")
+  .option("--prod <url>", "Production URL (for comparison mode)")
+  .option("--cand <url>", "Candidate URL (for comparison mode)")
</file context>

…ings

Biome lint/style/noUnusedTemplateLiteral was failing CI on:

- css-trace.ts:209  `✖ Element not found` → "✖ Element not found"
- css-trace.ts:231  `      }`              → "      }"
- css-trace.ts:256  `\n── DIFF ...` → "\n── DIFF ..."

None of the three had interpolation or special-character handling
that would require a template literal. Unblocks the css-trace
PR's CI run.
…rod/--cand mutex

Two P2 findings from the cubic AI reviewer:

1. css-trace was iterating only `matchedCSSRules` and silently dropping
   `inherited[].matchedCSSRules`. CSS inheritance carries properties
   like color, font-*, line-height, visibility from ancestors to the
   target element, so any computed value that propagates from a
   wrapper (typography, theme tokens applied to <html>/<body>) was
   missing from the trace output — "why does this <span> have
   color: red but no rule shows it?" had no answer in the report.

   Fix: extract a shared `buildRule` helper, then walk `inherited`
   (CDP returns it as an array indexed by ancestor distance, 0 =
   parent, 1 = grandparent, …) and tag each rule with
   `inheritedFromDistance`. `printResult` shows that as
   `↑ inherited from ancestor (N)` next to the source URL so the
   reader can tell direct matches from inherited ones at a glance.

2. `--url` was documented as mutually exclusive with `--prod` /
   `--cand` but the handler didn't enforce it — passing `--url`
   together with `--prod` would silently pick the comparison branch
   (because `isCompare = !!(opts.prod && opts.cand)` was checked
   first), making `--url` look broken.

   Fix: explicit validation up front — error if `--url` is combined
   with either `--prod` or `--cand`, error if comparison mode
   passes only one of `--prod`/`--cand`, error if no mode given
   at all. Each branch emits a clear chalk.red message.

Identified by cubic.
@JonasJesus42 JonasJesus42 merged commit 1e323fa into main May 26, 2026
2 checks passed
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.

1 participant