Skip to content

Admin panel: share WCAG/scale/font logic with the configurator + DX polish - #25

Merged
jackgranatowski merged 6 commits into
mainfrom
admin-panel-shared-parity-modules
Jun 9, 2026
Merged

Admin panel: share WCAG/scale/font logic with the configurator + DX polish#25
jackgranatowski merged 6 commits into
mainfrom
admin-panel-shared-parity-modules

Conversation

@jackgranatowski

@jackgranatowski jackgranatowski commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

This pull request was created by @kiro-agent on behalf of @jackgranatowski 👻

Comment with /kiro fix to address specific feedback or /kiro all to address everything.
Learn about Kiro Web


Summary

Extracts the framework-agnostic logic the admin panel shares with the SLASHED configurator into three modules that are byte-for-byte identical across both repos, so the two tools can never drift on the behaviours they have in common. Behaviour of the existing tabs is unchanged.

Companion PR (framework side): codeslash-dev/SLASHEDConfigurator: Accessibility + Scales views and shared parity modules.

Shared logic modules (admin-app/src/lib/)

  • color.js — WCAG maths, rgb↔hsl, pure hslToRgb, resolveToRgb, and the accessible-palette optimizer (now pure — no canvas round-trip).
  • scale.js — named ratios + modular-scale maths + clamp() builder.
  • fonts.js — curated system font stacks + detection.

Component refactor (no behaviour change)

  • WcagTab — drops its local contrast / HSL / optimizer maths in favour of the shared module.
  • ScaleGenerator — uses shared RATIOS + modularValue/round (keeps the WP-specific 12-step list incl. display-* and the display-offset control).
  • FontFamilyField — uses the shared system-stack catalogue + detection (Bricks/Manual modes preserved).

Tests

  • Mirrored shared-color / shared-scale / shared-fonts suites, plus shared-parity — a byte-identity drift guard against the framework checkout (skips cleanly when the framework isn't present).
  • 107 tests pass (node --test tests/*.test.js). check-admin-app reports no cssVar/default drift.

DX polish

  • Branded header mark and a softer body card; tab nav gains a focus-visible outline, an active-tab accent bar and smoother transitions.
  • assets/admin-app/app.js + app.css rebuilt from source.

Known limitations

  • One pre-existing state_referenced_locally build warning in FontFamilyField.svelte is unrelated to this change and left as-is.

Summary by CodeRabbit

  • New Features

    • Added CSS export with live code preview and framing options toggle.
    • Added palette optimizer to suggest accessible color combinations based on WCAG standards.
    • Added support for system font stacks with improved font selection detection.
  • Bug Fixes

    • Implemented value sanitization to prevent user inputs from breaking CSS output.
  • Style

    • Refreshed header design with decorative mark and improved body styling.
    • Enhanced tab navigation with better focus and active states, improved transitions.
  • Tests

    • Added comprehensive unit tests for CSS export, color utilities, and font handling.

kiro-agent and others added 2 commits June 9, 2026 18:25
Port patterns from the framework configurator into the Bricks admin SPA:

- Add sanitizeValue() (from configurator/src/lib/css.js) and apply it to
  every raw, user-entered value interpolated into generated CSS and the
  live-preview inline style. A pasted Advanced value containing ';' or '}'
  can no longer break out of the declaration block / style attribute.
- Extend generateExportCSS() with { mode, banner, version }: a @layer vs
  bare :root output-framing toggle and a generated-by header comment.
  Defaults preserve existing behaviour for LivePreview/SaveBar.
- ExportImportTab: @layer/:root segmented toggle, a Copy CSS button with
  copied/blocked feedback, and a live CSS preview.
- Add ui.outputMode to the store.
- Cover sanitizeValue and generateExportCSS options with unit tests.

Co-authored-by: Jack Granatowski <contact@codeslash.net>
…r + DX polish

Extract the framework-agnostic logic the admin panel shares with the SLASHED
configurator into three modules that are byte-for-byte identical across both
repos, so the two tools can never drift on the behaviours they have in common:

- src/lib/color.js — WCAG maths, rgb<->hsl, pure hslToRgb, resolveToRgb,
  and the accessible-palette optimizer (now pure, no canvas round-trip).
- src/lib/scale.js — named ratios + modular-scale maths + clamp() builder.
- src/lib/fonts.js — curated system font stacks + detection.

Refactor the components to consume them (behaviour unchanged):
- WcagTab: drops its local contrast/HSL/optimizer maths.
- ScaleGenerator: uses shared RATIOS + modularValue/round (keeps the WP step
  list incl. display-* and the display-offset control).
- FontFamilyField: uses the shared system-stack catalogue + detection.

Tests: mirrored color/scale/fonts suites plus a byte-identity drift guard
against the framework checkout (skips when absent). 107 tests pass.

DX polish: branded header mark, softer body card; tab nav focus-visible
outline, active-tab accent and smoother transitions. Admin-app assets rebuilt.
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

More reviews will be available in 51 minutes and 25 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

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.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a458eeb6-5054-4420-9e4d-82d3bc8aa806

📥 Commits

Reviewing files that changed from the base of the PR and between 5efcb8d and 3da9621.

📒 Files selected for processing (5)
  • SLASHED-for-WP/integrations/bricks/admin-app/src/components/ExportImportTab.svelte
  • SLASHED-for-WP/integrations/bricks/admin-app/src/components/FontFamilyField.svelte
  • SLASHED-for-WP/integrations/bricks/admin-app/src/components/LivePreview.svelte
  • SLASHED-for-WP/integrations/bricks/assets/admin-app/app.js
  • tests/shared-parity.test.js
📝 Walkthrough

Walkthrough

This PR consolidates color/WCAG, font stack, and modular-scale utilities into shared library modules; adds CSS value sanitization and extends the export API with framing modes; refactors components to use shared utilities; implements clipboard-based CSS export with @layer/@root toggle; and enhances visual styling with a header mark and improved tab interactions.

Changes

Shared utilities and export enhancement

Layer / File(s) Summary
Color and WCAG utilities
src/lib/color.js, tests/shared-color.test.js
New module exports sRGB linearization, relative luminance, contrast ratio calculation, WCAG level classification for normal and large text, RGB↔HSL conversion (including browser-based CSS value parsing), and accessible palette suggestion. Tests verify contrast symmetry, WCAG thresholds, color-space round-trips, and palette optimization.
System font stack utilities
src/lib/fonts.js, tests/shared-fonts.test.js
New module catalogs OS-native system font stacks as {label, value} pairs with detectSystemStack() for normalized case-insensitive matching and isFontFamilyToken() for token classification. Tests validate catalog completeness, matching behavior, and token type detection.
Modular-scale math utilities
src/lib/scale.js, tests/shared-scale.test.js
New module exports ratio definitions, step catalogs (TEXT_STEPS, SPACE_STEPS), modularValue() and round() for scale computation, computeScale() for min/max ranges, and buildClamp() for fluid CSS expressions. Tests verify base-step relationships, monotonicity, and fluid-range accuracy.
CSS export sanitization and API
src/lib/export.js, tests/export-css.test.js
New sanitizeValue() function strips comment blocks and structural CSS characters to prevent injection; applied across all token-section generators. generateExportCSS() signature expanded to accept opts with mode ('layer' or 'root'), banner, and version for customizable output framing and generated-by headers. Tests cover sanitization edge cases, injection guards, framing modes, and section coverage.
Export UI, state, and sanitized preview
src/components/ExportImportTab.svelte, src/lib/stores.svelte.js, src/components/LivePreview.svelte
New ui.outputMode state ('layer' default) for framing toggle. ExportImportTab adds CSS export card with segmented @layer/:root control, download/copy buttons, and live preview. New copyCSS() writes to clipboard with transient "Copied" state and error display. LivePreview sanitizes inline color and typography values before emitting CSS variables.
Components refactored to use shared utilities
src/components/WcagTab.svelte, src/components/FontFamilyField.svelte, src/components/ScaleGenerator.svelte
WcagTab imports color utilities (resolveToRgb, contrastRatio, wcagLevelLarge, suggestAccessiblePalette) and delegates WCAG math; FontFamilyField imports SYSTEM_STACKS and detectSystemStack() for font detection; ScaleGenerator imports RATIOS, modularValue(), and round() for preview computation. Previous inline implementations removed.
Visual polish and CSS regeneration
src/App.svelte, src/components/TabNav.svelte, src/assets/admin-app/app.css, tests/shared-parity.test.js
Header includes decorative slash mark with new typography/alignment styling. TabNav enhanced with CSS transitions, :focus-visible outline, and active state emphasis (bold font, inset box-shadow). Admin body increases border-radius from 4px to 6px and adds drop shadow. App.css regenerated with all changes. Parity test ensures color.js, fonts.js, and scale.js match framework configurator byte-for-byte.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: extracting shared WCAG/scale/font logic with the configurator and adding DX polish to the admin panel.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch admin-panel-shared-parity-modules

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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
SLASHED-for-WP/integrations/bricks/admin-app/src/components/FontFamilyField.svelte (1)

79-95: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use the same normalized system-stack matcher in switchSource('system').

detectSource() now accepts whitespace/case variants via detectSystemStack, but switchSource('system') still does strict toLowerCase() equality. This mismatch can treat a valid system stack as “no match” and overwrite it with SYSTEM_STACKS[0] when switching tabs.

💡 Suggested patch
 function switchSource(next) {
   source = next;
   // Pre-select sensible first value when switching into a dropdown mode.
   if (next === 'system') {
-    const match = SYSTEM_STACKS.find(s => s.value.toLowerCase() === effectiveValue.toLowerCase());
-    if (!match) writeField(section, fieldKey, SYSTEM_STACKS[0].value);
+    const match = detectSystemStack(effectiveValue);
+    if (!match) {
+      writeField(section, fieldKey, SYSTEM_STACKS[0].value);
+    } else if (match.value !== effectiveValue) {
+      // Canonicalize equivalent values (spacing/case) to the curated string.
+      writeField(section, fieldKey, match.value);
+    }
   } else if (next === 'bricks') {
     const match = bricksFonts.find(f => f.family.toLowerCase() === effectiveValue.toLowerCase());
     if (!match && bricksFonts.length > 0) writeField(section, fieldKey, bricksFonts[0].family);
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@SLASHED-for-WP/integrations/bricks/admin-app/src/components/FontFamilyField.svelte`
around lines 79 - 95, The system-stack matching in switchSource should use the
same normalization as detectSource/detectSystemStack: replace the strict
equality search (SYSTEM_STACKS.find(s => s.value.toLowerCase() ===
effectiveValue.toLowerCase())) with a call to detectSystemStack(effectiveValue)
(or use its returned match) and only call writeField(section, fieldKey,
SYSTEM_STACKS[0].value) when that normalized match is falsy; update switchSource
(and any local variable name) to rely on detectSystemStack so whitespace/case
variants are preserved the same way as detectSource.
🧹 Nitpick comments (1)
SLASHED-for-WP/integrations/bricks/admin-app/src/lib/export.js (1)

452-457: ⚡ Quick win

Sanitize version before interpolating it into the banner comment.

version is currently inserted raw into /* ... */. If bootstrap metadata ever contains */, it can terminate the comment and inject extra CSS into exported/copied output.

Proposed patch
   if (banner) {
-    const v = version ? ` v${version}` : '';
+    const safeVersion = sanitizeValue(version);
+    const v = safeVersion ? ` v${safeVersion}` : '';
     const n = declarations.length;
     css +=
       `/* SLASHED override tokens${v} — generated by the SLASHED for WordPress admin.\n` +
       `   Load this AFTER the SLASHED stylesheet. ${n} declaration${n === 1 ? '' : 's'}. */\n`;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SLASHED-for-WP/integrations/bricks/admin-app/src/lib/export.js` around lines
452 - 457, The banner assembly inserts the raw version into a block comment
(variables: banner, version, css, declarations), which allows a malicious
version like "*/" to terminate the comment and inject CSS; sanitize version
before interpolation by stripping or escaping any closing-comment sequences and
control characters (e.g., remove or replace "*/" and non-printable chars) and
then use that sanitizedVersion when building the banner string so the comment
cannot be prematurely closed or contain unexpected content.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@SLASHED-for-WP/integrations/bricks/admin-app/src/components/ExportImportTab.svelte`:
- Around line 145-159: The segmented control buttons in ExportImportTab.svelte
don't expose pressed state to assistive tech; update each button (the `@layer`
button bound to ui.outputMode === 'layer' and the :root button bound to
ui.outputMode === 'root') to include an aria-pressed attribute that reflects the
current selection (e.g., aria-pressed={ui.outputMode === 'layer'} and
aria-pressed={ui.outputMode === 'root'}) so screen readers can perceive the
binary toggle state.

In
`@SLASHED-for-WP/integrations/bricks/admin-app/src/components/LivePreview.svelte`:
- Around line 133-134: Guard emissions by the sanitized result rather than raw
truthiness: call sanitizeValue(typography.font_body) and
sanitizeValue(typography.font_heading) into local variables, check those
sanitized values are non-empty, and only then push the CSS custom property
strings via pairs.push(`--sf-font-body:${sanitized}`) /
pairs.push(`--sf-font-heading:${sanitized}`). Update the logic that currently
uses if (typography.font_body) / if (typography.font_heading) so it references
the sanitized variables and skips emitting `--sf-font-*:` when
sanitizeValue(...) returns an empty string.

In `@tests/shared-parity.test.js`:
- Around line 21-27: The test currently only checks haveFramework before reading
files, so if pluginLib is missing the readFileSync calls will throw; add a guard
for the plugin path (e.g., compute havePlugin = existsSync(pluginLib) or check
each resolved plugin path) and use that in the test skip condition (for MODULES
loop) so tests are skipped when either the framework or plugin parity path is
absent; update the skip message to reflect missing plugin or framework and
ensure readFileSync is only executed when both haveFramework and havePlugin are
true.

---

Outside diff comments:
In
`@SLASHED-for-WP/integrations/bricks/admin-app/src/components/FontFamilyField.svelte`:
- Around line 79-95: The system-stack matching in switchSource should use the
same normalization as detectSource/detectSystemStack: replace the strict
equality search (SYSTEM_STACKS.find(s => s.value.toLowerCase() ===
effectiveValue.toLowerCase())) with a call to detectSystemStack(effectiveValue)
(or use its returned match) and only call writeField(section, fieldKey,
SYSTEM_STACKS[0].value) when that normalized match is falsy; update switchSource
(and any local variable name) to rely on detectSystemStack so whitespace/case
variants are preserved the same way as detectSource.

---

Nitpick comments:
In `@SLASHED-for-WP/integrations/bricks/admin-app/src/lib/export.js`:
- Around line 452-457: The banner assembly inserts the raw version into a block
comment (variables: banner, version, css, declarations), which allows a
malicious version like "*/" to terminate the comment and inject CSS; sanitize
version before interpolation by stripping or escaping any closing-comment
sequences and control characters (e.g., remove or replace "*/" and non-printable
chars) and then use that sanitizedVersion when building the banner string so the
comment cannot be prematurely closed or contain unexpected content.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 86a15026-5486-4464-8dc9-66a91b9f1cde

📥 Commits

Reviewing files that changed from the base of the PR and between 1145efb and 5efcb8d.

📒 Files selected for processing (19)
  • SLASHED-for-WP/integrations/bricks/admin-app/src/App.svelte
  • SLASHED-for-WP/integrations/bricks/admin-app/src/components/ExportImportTab.svelte
  • SLASHED-for-WP/integrations/bricks/admin-app/src/components/FontFamilyField.svelte
  • SLASHED-for-WP/integrations/bricks/admin-app/src/components/LivePreview.svelte
  • SLASHED-for-WP/integrations/bricks/admin-app/src/components/ScaleGenerator.svelte
  • SLASHED-for-WP/integrations/bricks/admin-app/src/components/TabNav.svelte
  • SLASHED-for-WP/integrations/bricks/admin-app/src/components/WcagTab.svelte
  • SLASHED-for-WP/integrations/bricks/admin-app/src/lib/color.js
  • SLASHED-for-WP/integrations/bricks/admin-app/src/lib/export.js
  • SLASHED-for-WP/integrations/bricks/admin-app/src/lib/fonts.js
  • SLASHED-for-WP/integrations/bricks/admin-app/src/lib/scale.js
  • SLASHED-for-WP/integrations/bricks/admin-app/src/lib/stores.svelte.js
  • SLASHED-for-WP/integrations/bricks/assets/admin-app/app.css
  • SLASHED-for-WP/integrations/bricks/assets/admin-app/app.js
  • tests/export-css.test.js
  • tests/shared-color.test.js
  • tests/shared-fonts.test.js
  • tests/shared-parity.test.js
  • tests/shared-scale.test.js

Comment thread SLASHED-for-WP/integrations/bricks/admin-app/src/components/LivePreview.svelte Outdated
Comment thread tests/shared-parity.test.js Outdated
…ed_locally

Replace the value-seeded `let source = $state(detectSource(...))` with a
`userSource` override (null = auto): `source` is now $derived from the value +
the live Bricks list until the user explicitly picks a tab. Removes the
build warning, drops the manual onMount re-sync, and fixes a latent bug where
a font present only in the freshly-fetched Bricks list stayed misclassified.
Behaviour for the user is unchanged. Asset rebuilt.
- ExportImportTab: expose pressed state on the @layer/:root framing toggle
  (aria-pressed) so screen readers perceive the binary selection.
- LivePreview: gate font-var emission on the sanitized result so a value that
  sanitizes to empty no longer emits a valueless --sf-font-*: declaration.

Admin-app assets rebuilt.
…el-shared-parity-modules

# Conflicts:
#	SLASHED-for-WP/integrations/bricks/assets/admin-app/app.js
Guard `havePlugin` alongside `haveFramework` so a misresolved/absent plugin lib
makes the byte-identity check skip deterministically instead of throwing in
readFileSync. (review feedback)
@jackgranatowski
jackgranatowski merged commit 03f74e6 into main Jun 9, 2026
9 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