Skip to content

feat(configurator): add Components tab for .sf-btn/.sf-card, fix preview mirror - #547

Merged
jackgranatowski merged 2 commits into
mainfrom
claude/configurator-components-panel
Jul 6, 2026
Merged

feat(configurator): add Components tab for .sf-btn/.sf-card, fix preview mirror#547
jackgranatowski merged 2 commits into
mainfrom
claude/configurator-components-panel

Conversation

@jackgranatowski

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #544 (.sf-btn/.sf-card shipped) and #545 (utility classes + shadow xs/xl + link a11y): the configurator's data layer was already fully synced (auto-generated), but its UI never caught up — only 5 of the 22 --sf-btn-*/--sf-card-* tokens had controls, scattered under the wrong categories, and the global preview rendered a hand-copied .pv-btn/.pv-card mirror instead of the real classes.

  • New "Components" tab (ComponentsPanel.svelte) — every --sf-btn-*/--sf-card-* token as a slider/button-group, grouped by component, each with its own live preview using the real .sf-btn/.sf-card classes (not a mirror). Variant/style/size are preview-only pickers — SLASHED's button variants reuse the same 3 rule-local custom property names across every colour family, so a global override can't target one variant independently without a CSS architecture change (discussed and deliberately deferred, see conversation).
  • Wired the new domain everywhere it needs to exist: sidebar nav, routing, label maps (including a duplicate in CommandPalette.svelte and a third duplicate pattern list in HomePanel.svelte that would've double-counted badges if left alone), and domain-patterns.json (ordered before borders/shadows so their looser substrings don't steal btn/card tokens first).
  • Removed the 5 btn/card rows BordersPanel.svelte was already duplicating.
  • Fixed the global PreviewPanel mirror: swapped .pv-btn/.pv-card for the real classes in all three demo templates — the iframe already loads the full compiled bundle, so the mirror's isolation rationale was stale. This required splitting every pv-card sf-stack combo into a card + inner wrapper, since the real .sf-card sets display:block in @layer slashed.components, which outranks .sf-stack's display:flex in the earlier slashed.layout layer regardless of specificity.
  • Effects panel: text-shadow presets extended to xs/xl (was s/m/l only); added a drop-shadow section (previously absent entirely), using the real .sf-drop-shadow-* utility classes for preview.
  • Typography panel: added --sf-link-external-label next to the existing --sf-link-external-marker control.

Test plan

  • npm run build (configurator) — clean, 0 errors
  • npx svelte-check — 0 errors, 0 warnings across 4008 files
  • npx vitest run tests/ — 90/90 passed (including curation.test.js)
  • npx vitest run tests-components/ — 23/23 passed
  • Manual verification via Chromium against the built preview server:
    • All 15 sidebar nav destinations present, including "Components"
    • Components tab: Button + Card sections expand, live preview renders real .sf-btn/.sf-card, sliders update it live (radius, padding, etc.), variant/style/size pickers and card modifier checkboxes work
    • Global PreviewPanel: verified the cascade-layer fix directly — .sf-card computes display: block, the inner .sf-stack wrapper correctly computes display: flex; flex-direction: column in both the Marketing and Stylescape templates, light and dark
    • Effects: 5 text-shadow rows + new drop-shadow section with 5 working SVG previews
    • Typography: new "External label" field renders and functions
  • tests-e2e/ (Playwright) — couldn't run via npx playwright test in this sandbox (missing chrome-headless-shell binary, pre-existing environment limitation unrelated to this change); the equivalent assertions were verified manually as above using the full Chromium binary directly

🤖 Generated with Claude Code

https://claude.ai/code/session_01NwTgVgva9FeiQjddgVaC4H


Generated by Claude Code

…iew mirror

- New ComponentsPanel.svelte: sliders/button-groups for all 22 --sf-btn-*/
  --sf-card-* tokens, grouped by component, each with its own live preview
  using the real .sf-btn/.sf-card classes (not a hand-copied mirror).
  Variant/style/size are preview-only pickers, not overrides — SLASHED's
  .sf-btn variants reuse the same 3 rule-local custom property names across
  every colour family, so a global override can't target one variant
  independently without a CSS architecture change (out of scope here).
- Wire the new "Components" domain everywhere it needs to exist: sidebar
  nav, DomainPanel routing, App/CommandPalette label maps, Home screen tile
  + its own separate override-count patterns, and domain-patterns.json
  (inserted before "borders"/"shadows" so their looser "radius"/"shadow"
  substrings don't steal btn/card tokens first-match-wins).
- Remove the 5 btn/card rows BordersPanel.svelte was already duplicating
  under "Component shape" (renamed to "Field shape", now field-only).
- main.ts: load optional/tokens.components.css + components.css so the new
  panel's preview can use real classes in the host document, the same way
  every other panel's inline preview already reads live token overrides.
- PreviewPanel.svelte: replace the .pv-btn/.pv-card mirror with the real
  .sf-btn/.sf-card classes in all three demo templates — the iframe already
  loads the full compiled bundle, so the mirror's "stays isolated from
  whichever bundle is loaded" rationale no longer applied. Required
  splitting every "pv-card sf-stack" combo into a card + inner wrapper,
  since the real .sf-card sets display:block in @layer slashed.components,
  which outranks .sf-stack's display:flex in the earlier slashed.layout
  layer regardless of specificity — same failure mode the old unlayered
  .pv-card avoided differently (by never setting display at all).
- EffectsPanel.svelte: extend text-shadow presets to xs/xl (was s/m/l only)
  and add a drop-shadow section (previously absent entirely), using the
  real .sf-drop-shadow-* utility classes for the preview swatches.
- TypographyPanel.svelte: add --sf-link-external-label next to the existing
  --sf-link-external-marker control, mirroring its (unquoted) value handling
  for consistency rather than introducing a different convention.
- tests-e2e/shell.spec.js: add 'Components' to the expected nav labels.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NwTgVgva9FeiQjddgVaC4H
@coderabbitai

coderabbitai Bot commented Jul 6, 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: 23 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: 92be72b6-fef0-4742-b191-8cb004cdcb2e

📥 Commits

Reviewing files that changed from the base of the PR and between e14727f and d70f23d.

📒 Files selected for processing (13)
  • configurator/src/App.svelte
  • configurator/src/components/CommandPalette.svelte
  • configurator/src/components/DomainPanel.svelte
  • configurator/src/components/panels/BordersPanel.svelte
  • configurator/src/components/panels/ComponentsPanel.svelte
  • configurator/src/components/panels/EffectsPanel.svelte
  • configurator/src/components/panels/HomePanel.svelte
  • configurator/src/components/panels/TypographyPanel.svelte
  • configurator/src/components/shell/PreviewPanel.svelte
  • configurator/src/components/shell/SidebarNav.svelte
  • configurator/src/data/domain-patterns.json
  • configurator/src/main.ts
  • configurator/tests-e2e/shell.spec.js
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/configurator-components-panel

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

Add configurator Components domain for .sf-btn/.sf-card and use real classes in preview

✨ Enhancement 🐞 Bug fix 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add a new Components tab with full controls and live previews for .sf-btn/.sf-card tokens.
• Fix PreviewPanel demos to render real .sf-btn/.sf-card instead of mirrored pv-* styles.
• Extend Effects (text-/drop-shadow) and Typography (external link label) token controls.
Diagram

graph TD
  A["SidebarNav"] --> B["App state (domain)"] --> C["DomainPanel router"] --> D["ComponentsPanel"]
  B --> E["PreviewPanel iframe"]
  F[["domain-patterns.json"]] --> B
  G[("optional/components.css")] --> D --> E
  H["CommandPalette + HomePanel"] --> B

  subgraph Legend
    direction LR
    _ui["UI component"] ~~~ _cfg[["Config/data"]] ~~~ _asset[("CSS asset")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Make ComponentsPanel data-driven from token registry metadata
  • ➕ Avoids hand-maintaining token lists and button-group presets
  • ➕ Reduces future drift when new component tokens ship
  • ➖ Requires richer token metadata (grouping/presets) or new conventions
  • ➖ More refactor risk than this targeted follow-up PR
2. Keep pv-* mirrored component styles but auto-generate them from framework CSS
  • ➕ Preserves preview isolation goals while staying in sync
  • ➕ Avoids cascade-layer interactions with layout utilities
  • ➖ Non-trivial build step / parsing pipeline
  • ➖ Still duplicates component semantics and can lag behind shipped CSS

Recommendation: Current approach (use real .sf-btn/.sf-card in both ComponentsPanel and PreviewPanel) is the best near-term fix because it eliminates duplication and ensures previews match shipped behavior. A data-driven panel could be a future iteration, but would require broader conventions/metadata changes beyond the scope of this follow-up.

Files changed (13) +658 / -97

Enhancement (8) +561 / -11
App.svelteRegister Components domain label in top-level domain map +2/-2

Register Components domain label in top-level domain map

• Adds the "components" entry to DOMAIN_LABELS so the app can display the new tab name consistently.

configurator/src/App.svelte

CommandPalette.svelteAdd Components to command palette domain labels +1/-1

Add Components to command palette domain labels

• Extends the palette's DOMAIN_LABELS so token search results can navigate to the new Components domain.

configurator/src/components/CommandPalette.svelte

DomainPanel.svelteRoute components domain to new ComponentsPanel +3/-0

Route components domain to new ComponentsPanel

• Imports ComponentsPanel and adds a domain switch case to render it when domain === "components".

configurator/src/components/DomainPanel.svelte

ComponentsPanel.svelteNew Components tab with full .sf-btn/.sf-card token controls and previews +461/-0

New Components tab with full .sf-btn/.sf-card token controls and previews

• Introduces a dedicated panel that exposes all button/card tokens as sliders and preset pickers, with local-only preview controls for variants/sizes/modifiers. Live previews render real .sf-btn/.sf-card markup so overrides are validated against actual framework CSS.

configurator/src/components/panels/ComponentsPanel.svelte

EffectsPanel.svelteAdd xs/xl text-shadow tokens and new drop-shadow section with previews +68/-3

Add xs/xl text-shadow tokens and new drop-shadow section with previews

• Expands text-shadow controls to include xs and xl. Adds drop-shadow token inputs plus a visual preview using real .sf-drop-shadow-* utility classes.

configurator/src/components/panels/EffectsPanel.svelte

HomePanel.svelteExpose Components tile and fix per-domain override counting patterns +6/-4

Expose Components tile and fix per-domain override counting patterns

• Adds a new "Components" domain card on the home screen and updates DOMAIN_MATCH so btn/card patterns are counted under components (and removed from borders).

configurator/src/components/panels/HomePanel.svelte

TypographyPanel.svelteAdd --sf-link-external-label input control +18/-0

Add --sf-link-external-label input control

• Adds a new text field for the external link screen-reader label token alongside the existing external marker control.

configurator/src/components/panels/TypographyPanel.svelte

SidebarNav.svelteAdd Components destination to sidebar navigation +2/-1

Add Components destination to sidebar navigation

• Adds a Components nav item (with icon) so the new domain is reachable from the main sidebar.

configurator/src/components/shell/SidebarNav.svelte

Bug fix (1) +80 / -73
PreviewPanel.svelteUse real .sf-btn/.sf-card in demo templates and drop pv-* mirrors +80/-73

Use real .sf-btn/.sf-card in demo templates and drop pv-* mirrors

• Removes the mirrored pv-btn/pv-card component CSS and updates all demo templates to render the real framework classes. Adjusts card compositions by adding inner .sf-stack wrappers to avoid cascade-layer display conflicts.

configurator/src/components/shell/PreviewPanel.svelte

Refactor (1) +7 / -12
BordersPanel.svelteRemove btn/card token controls; rename section to Field shape +7/-12

Remove btn/card token controls; rename section to Field shape

• Deletes the duplicated button/card token rows from Borders and keeps only field-related shape tokens. Renames the UI toggle from "Component shape" to "Field shape" accordingly.

configurator/src/components/panels/BordersPanel.svelte

Tests (1) +1 / -1
shell.spec.jsUpdate nav e2e expectations to include Components +1/-1

Update nav e2e expectations to include Components

• Extends the NAV_LABELS list so Playwright shell tests assert the new sidebar destination is present.

configurator/tests-e2e/shell.spec.js

Other (2) +9 / -0
domain-patterns.jsonAdd components domain patterns before borders/shadows +1/-0

Add components domain patterns before borders/shadows

• Introduces a "components" pattern list (btn-/card-) and positions it before borders/shadows to avoid substring misclassification in first-match domainOf() resolution.

configurator/src/data/domain-patterns.json

main.tsLoad framework component CSS to support real .sf-btn/.sf-card previews +8/-0

Load framework component CSS to support real .sf-btn/.sf-card previews

• Imports optional/tokens.components.css and optional/components.css so the configurator host document can render real component markup in panels and previews without requiring reset/base.

configurator/src/main.ts

@qodo-code-review

qodo-code-review Bot commented Jul 6, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Context used
✅ Compliance rules (platform): 12 rules

Grey Divider


Action required

1. Trim breaks shadow typing ✓ Resolved 🐞 Bug ≡ Correctness
Description
EffectsPanel trims the input value on every oninput before persisting it, which prevents typing
space-separated CSS values (e.g., after typing drop-shadow(0␠, the space is removed immediately).
This makes the new --sf-drop-shadow-* editors (and the text-shadow editors) effectively unusable
unless users paste complete values.
Code

configurator/src/components/panels/EffectsPanel.svelte[R340-344]

+              oninput={(e) => {
+                const v = (e.target as HTMLInputElement).value;
+                const trimmed = v.trim();
+                trimmed ? onSet(t.token, trimmed) : onReset(t.token);
+              }}
Relevance

⭐⭐⭐ High

Same trim-oninput shadow bug was accepted and fixed in EffectsPanel in PR #460.

PR-#460

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The oninput handlers for both text-shadow and the newly added drop-shadow inputs compute `trimmed
= v.trim() and persist trimmed`, which removes spaces as the user types. This matches the
previously accepted bug pattern for shadow inputs.

configurator/src/components/panels/EffectsPanel.svelte[288-346]
PR-#460

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

## Issue description
`EffectsPanel.svelte` trims user input (`v.trim()`) on every keystroke for drop-shadow (and text-shadow) token inputs. This blocks entering space-separated CSS values interactively because the moment the user types a space, it becomes a trailing space and gets removed immediately when the controlled input re-renders.

## Issue Context
This is the same failure mode as the previously accepted trim-on-input bug pattern for shadow inputs.

## Fix Focus Areas
- configurator/src/components/panels/EffectsPanel.svelte[292-296]
- configurator/src/components/panels/EffectsPanel.svelte[340-344]

## Implementation notes
- Keep the raw input value when calling `onSet` (do not trim).
- Use `trim()` only to decide between `onSet` vs `onReset` (e.g., `if (v.trim()) onSet(token, v); else onReset(token);`).
- Optionally trim on `onblur` if you want normalized storage without breaking typing.

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



Remediation recommended

2. Home badges miscount overrides ✓ Resolved 🐞 Bug ≡ Correctness
Description
HomePanel computes per-domain badge counts via substring matching, so component overrides like
--sf-btn-radius/--sf-card-radius are also counted under Borders because Borders includes the
pattern "radius". This makes the Home screen badge counts disagree with the canonical sidebar/App
logic (which uses domainOf()), and can mislead users about where overrides belong.
Code

configurator/src/components/panels/HomePanel.svelte[R40-52]

    typography: ["text", "font", "leading", "tracking", "h1", "h2", "h3", "h4", "h5", "h6", "body"],
    spacing: ["space", "gap", "gutter", "section"],
    layout: ["container", "grid", "sidebar", "bento", "reel", "cluster", "stack", "frame"],
-    borders: ["radius", "border", "divider", "btn-", "field-"],
+    borders: ["radius", "border", "divider", "field-"],
    shadows: ["shadow"],
    motion: ["motion", "duration", "ease", "animation", "transition"],
    effects: ["blur", "opacity", "scrollbar", "scrim", "backdrop"],
    macros: ["flow", "prose", "aspect", "scroll-shadow", "line-clamp"],
    misc: ["z-"],
+    components: ["btn-", "card-"],
  };

  function domainCount(id: string): number {
Relevance

⭐⭐⭐ High

Team previously accepted replacing substring domain matching with domainOf() to avoid
overlap/miscounts (PR #468).

PR-#468

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
HomePanel counts Borders overrides by checking for the substring radius, while the new Components
panel explicitly edits --sf-btn-radius/--sf-card-radius (which both include radius).
App.svelte and domainOf classify tokens via ordered domain-patterns (components before borders), so
HomePanel’s substring counting can’t stay consistent.

configurator/src/components/panels/HomePanel.svelte[37-56]
configurator/src/components/panels/ComponentsPanel.svelte[36-51]
configurator/src/App.svelte[25-70]
configurator/src/lib/domains.ts[6-13]
configurator/src/data/domain-patterns.json[8-16]
PR-#468

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

## Issue description
`HomePanel.svelte` uses `DOMAIN_MATCH` substring lists and `k.includes(p)` to count overrides per domain. After introducing the new `components` domain (btn/card), component tokens still contain generic substrings like `radius`, causing them to be counted under `borders` as well.

## Issue Context
The rest of the app treats `domainOf(tokenName)` + ordered `domain-patterns.json` as the single source of truth for classification (sidebar badges, per-domain reset scope). Home should match that to avoid inconsistent counts.

## Fix Focus Areas
- configurator/src/components/panels/HomePanel.svelte[37-56]

## Implementation notes
- Import `domainOf` and implement `domainCount(id)` as:
 - `Object.keys(overrides).filter(k => domainOf(k) === id).length`
- Alternatively, precompute an `overridesByDomain` map once (same as `App.svelte`) and read counts from it.

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


3. Duplicate component tokens import ✓ Resolved 🐞 Bug ➹ Performance
Description
main.ts imports both optional/tokens.components.css and optional/components.css, but
optional/components.css already @imports ./tokens.components.css. This likely duplicates the
same token-layer CSS in the final bundle, increasing CSS size and parse work.
Code

configurator/src/main.ts[R18-25]

+// .sf-btn / .sf-card only (the rest of optional/components.css is still
+// commented out upstream) — BEM class selectors, disjoint from the Studio
+// shell's own Tailwind utility classes, so safe to load alongside chrome
+// without needing core/reset.css or core/base.css first. Enables real
+// .sf-btn / .sf-card markup in ComponentsPanel's in-panel live preview and
+// in PreviewPanel's demo templates.
+import '@framework-css/optional/tokens.components.css';
+import '@framework-css/optional/components.css';
Relevance

⭐⭐ Medium

No historical evidence found on redundant CSS imports/deduping; unclear if team will change bundling
behavior.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The optional components stylesheet explicitly imports the tokens file; importing both from main.ts
will pull in tokens.components.css twice unless the bundler deduplicates it.

configurator/src/main.ts[18-26]
optional/components.css[11-14]

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

## Issue description
`configurator/src/main.ts` imports `@framework-css/optional/tokens.components.css` and then imports `@framework-css/optional/components.css`, which itself `@import`s `./tokens.components.css`. This is likely a redundant double-include.

## Issue Context
Even if the build pipeline deduplicates in some cases, relying on that makes CSS composition harder to reason about and can regress depending on tooling changes.

## Fix Focus Areas
- configurator/src/main.ts[18-25]
- optional/components.css[11-13]

## Implementation notes
- Prefer importing only `@framework-css/optional/components.css` (since it already brings its tokens), and remove the direct `tokens.components.css` import from `main.ts`.
- Alternatively, remove the `@import` from `optional/components.css` and keep the explicit imports (but that’s likely worse for consumers of the optional bundle).

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


Grey Divider

Qodo Logo

Comment thread configurator/src/components/panels/EffectsPanel.svelte
Comment thread configurator/src/components/panels/HomePanel.svelte Outdated
Comment thread configurator/src/main.ts
- EffectsPanel.svelte: stop trimming the text-shadow/drop-shadow inputs'
  value before persisting it on every keystroke — trim() was only meant to
  decide onSet vs onReset, but was also applied to the stored value, so a
  space typed mid-value (e.g. "drop-shadow(0 ") was stripped immediately by
  the controlled input's re-render, making space-separated CSS effectively
  impossible to type interactively. Affected both the new drop-shadow inputs
  and the pre-existing text-shadow inputs (same bug, same fix).
- HomePanel.svelte: replace the panel's own independent-per-domain substring
  matching (DOMAIN_MATCH/domainCount) with the same domainOf() classification
  everything else in the app already uses. The substring lists checked each
  domain in isolation, so a token like --sf-btn-radius matched both
  "components" (via "btn-") and "borders" (via the pre-existing generic
  "radius" pattern) at the same time, double-counting it on the Home
  screen's tile badges. domainOf() is ordered/first-match-wins, so this
  can't happen again for any future domain addition either.
- main.ts: drop the redundant explicit tokens.components.css import —
  optional/components.css already @imports it, so importing both risked
  shipping the token layer twice in the bundle. Confirmed via build output
  (CSS bundle shrank from 170.31kB to 169.17kB after removing it).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NwTgVgva9FeiQjddgVaC4H
@jackgranatowski
jackgranatowski merged commit 3dd3abf into main Jul 6, 2026
13 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.

2 participants