Skip to content

Improve WP admin panel: configurator-parity CSS export - #24

Merged
jackgranatowski merged 2 commits into
mainfrom
improve-admin-panel-configurator-parity
Jun 9, 2026
Merged

Improve WP admin panel: configurator-parity CSS export#24
jackgranatowski merged 2 commits into
mainfrom
improve-admin-panel-configurator-parity

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

Brings several proven patterns from the framework's standalone configurator (SLASHED/configurator) into the WordPress plugin's Bricks admin-app Svelte panel, without disturbing its REST/section architecture.

  • CSS-injection guard — ported sanitizeValue() from configurator/src/lib/css.js and applied it to every raw, user-entered value that gets interpolated into generated CSS (export.js) and into the live-preview inline-style string (LivePreview.svelte). A pasted Advanced color/value containing ;, {, } or comment delimiters can no longer break out of the declaration block or the style attribute. (PHP still sanitizes on save; this protects the purely client-side Download/Copy/preview paths.)
  • Output-framing togglegenerateExportCSS() now accepts { mode: 'layer' | 'root', banner, version }. @layer slashed.overrides stays the default; :root is offered for consumers without cascade layers — mirroring the configurator's OutputPanel.
  • Copy CSS + banner — the Export/Import tab gains a @layer/:root segmented control, a Copy CSS button with copied/blocked feedback, a live CSS preview, and a generated-by banner comment stamped with the framework/plugin version.
  • Store — added ui.outputMode (session-only).

Defaults are unchanged for LivePreview and SaveBar, so existing behaviour is preserved.

What was tested

  • node --test tests/*.test.js80 pass (incl. new tests/export-css.test.js, 15 tests covering sanitizeValue and the new generateExportCSS options/injection guard).
  • node scripts/check-admin-app.jsOK (no cssVar / default drift vs. the framework).
  • vite build of the admin-app — succeeds; regenerated tracked assets/admin-app/app.js + app.css are included.

Notes / limitations

  • The only build warning is pre-existing and unrelated (FontFamilyField.svelte state_referenced_locally).
  • CSS import round-trip (parsing an override sheet back into the store) was intentionally not ported: the plugin stores semantic keys (e.g. size_m_min/max behind clamp()), so reverse-mapping CSS would be lossy. JSON export/import remains the portable path.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added standalone CSS export with framing options (choose between @layer or :root output format).
    • Added copy-to-clipboard functionality for generated CSS.
    • Added live code preview of exported CSS in the Export/Import tab.
  • Bug Fixes

    • Improved sanitization of user-provided token values in exports and live previews to prevent formatting issues.
  • Tests

    • Added comprehensive test coverage for CSS export generation and value sanitization.

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>
@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 52 minutes and 58 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: 8a3fb278-e896-4b6b-92f6-21736f576f99

📥 Commits

Reviewing files that changed from the base of the PR and between 139776d and 5c782a5.

📒 Files selected for processing (3)
  • SLASHED-for-WP/integrations/bricks/admin-app/src/components/ExportImportTab.svelte
  • SLASHED-for-WP/integrations/bricks/admin-app/src/components/LivePreview.svelte
  • SLASHED-for-WP/integrations/bricks/assets/admin-app/app.js
📝 Walkthrough

Walkthrough

This PR adds CSS injection protection through value sanitization, introduces configurable CSS output framing (layer-wrapped or bare :root), and enables clipboard export with a new UI toggle. The changes span the export library, component UI, state management, live preview integration, and comprehensive test coverage.

Changes

CSS Export Sanitization and Framing Control

Layer / File(s) Summary
Sanitization utility and CSS generation with options
src/lib/export.js
sanitizeValue() strips CSS-breaking constructs from user inputs; generateExportCSS() now accepts opts (mode, banner, version) to support both @layer slashed.overrides and bare :root output framing.
Sanitization applied across token type generation
src/lib/export.js
Color, typography, spacing, radius, shadow, and motion token value emission all route through sanitizeValue() to prevent injection.
Output mode reactive state
src/lib/stores.svelte.js
New ui.outputMode state (default 'layer') controls CSS framing selection as session-only UI preference.
Export tab UI with clipboard and framing controls
src/components/ExportImportTab.svelte
Derives bannerVersion metadata, generates CSS with options, adds copyCSS() clipboard flow with transient states, renders framing toggle to mutate ui.outputMode, wires download/copy buttons, and styles standalone CSS section with code preview.
Live preview inline CSS sanitization
src/components/LivePreview.svelte
Applies sanitizeValue() to colors and typography values before inserting into inline custom properties.
Comprehensive test coverage
tests/export-css.test.js
Tests sanitizeValue() edge cases, generateExportCSS() framing modes and banner generation, injection safety, and full content coverage across all token types.
Generated admin app stylesheet
assets/admin-app/app.css
Regenerated minified styles for new framing toggle and code preview UI elements.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 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 clearly summarizes the main change: improving the WordPress admin panel's CSS export to match the configurator's capabilities, including sanitization and output-framing options.
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 improve-admin-panel-configurator-parity

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.

@jackgranatowski

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 1

🧹 Nitpick comments (1)
SLASHED-for-WP/integrations/bricks/admin-app/src/components/ExportImportTab.svelte (1)

180-180: ⚡ Quick win

Consider announcing the "Copied" feedback to screen readers.

The transient "Copied ✓" text change provides visual feedback but is not announced to assistive technologies. Adding aria-live="polite" to the button or a wrapper element would improve accessibility for screen reader users.

♻️ Proposed improvement
        <button
          type="button"
          class="btn btn--secondary"
          onclick={copyCSS}
          disabled={!canExportCSS}
          title={canExportCSS ? 'Copy the generated CSS to the clipboard' : 'No overrides set yet'}
+         aria-live="polite"
        >
          {copied ? 'Copied ✓' : 'Copy CSS'}
        </button>
🤖 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/ExportImportTab.svelte`
at line 180, The "Copied ✓" transient text in ExportImportTab.svelte isn't
announced to screen readers; update the button (or its wrapper element that
renders {copied ? 'Copied ✓' : 'Copy CSS'}) to include an aria-live attribute
(e.g., aria-live="polite") or role="status" so changes to the copied state are
announced; ensure the element that contains the {copied} text (in the same
component where the copied boolean is used) has the aria-live attribute and
remains in the DOM for the duration of the feedback.
🤖 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 43-53: The setTimeout in copyCSS can fire after the component
unmounts; fix it by storing the timeout ID (e.g., let copyTimeoutId) when you
call setTimeout in copyCSS and clear any previous timeout before creating a new
one, then clearTimeout(copyTimeoutId) in a Svelte onDestroy/cleanup effect so
you never update copied after unmount. Ensure you reference the existing copyCSS
function and the copied state variable when implementing the timeout tracking
and cleanup.

---

Nitpick comments:
In
`@SLASHED-for-WP/integrations/bricks/admin-app/src/components/ExportImportTab.svelte`:
- Line 180: The "Copied ✓" transient text in ExportImportTab.svelte isn't
announced to screen readers; update the button (or its wrapper element that
renders {copied ? 'Copied ✓' : 'Copy CSS'}) to include an aria-live attribute
(e.g., aria-live="polite") or role="status" so changes to the copied state are
announced; ensure the element that contains the {copied} text (in the same
component where the copied boolean is used) has the aria-live attribute and
remains in the DOM for the duration of the feedback.
🪄 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: 852c9290-4f1d-4216-b625-8552a743d0b4

📥 Commits

Reviewing files that changed from the base of the PR and between 1145efb and 139776d.

📒 Files selected for processing (7)
  • SLASHED-for-WP/integrations/bricks/admin-app/src/components/ExportImportTab.svelte
  • SLASHED-for-WP/integrations/bricks/admin-app/src/components/LivePreview.svelte
  • SLASHED-for-WP/integrations/bricks/admin-app/src/lib/export.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

Comment on lines +43 to +53
async function copyCSS() {
if (!exportCSS) return;
copyError = '';
try {
await navigator.clipboard.writeText(exportCSS);
copied = true;
setTimeout(() => (copied = false), 1400);
} catch {
copyError = 'Clipboard blocked — select the CSS below and copy manually.';
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clean up the timeout on component unmount.

The setTimeout callback on line 49 could execute after the component unmounts, which may cause a state update on an unmounted component. In Svelte 5, you should store the timeout ID and clear it in an effect cleanup.

🛡️ Proposed fix to clear timeout on unmount
  async function copyCSS() {
    if (!exportCSS) return;
    copyError = '';
    try {
      await navigator.clipboard.writeText(exportCSS);
      copied = true;
-     setTimeout(() => (copied = false), 1400);
+     const tid = setTimeout(() => (copied = false), 1400);
+     // Clean up if component unmounts before timeout fires
+     $effect(() => () => clearTimeout(tid));
    } catch {
      copyError = 'Clipboard blocked — select the CSS below and copy manually.';
    }
  }

Alternatively, track the timeout ID in component state and clear it in an onDestroy lifecycle hook.

🤖 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/ExportImportTab.svelte`
around lines 43 - 53, The setTimeout in copyCSS can fire after the component
unmounts; fix it by storing the timeout ID (e.g., let copyTimeoutId) when you
call setTimeout in copyCSS and clear any previous timeout before creating a new
one, then clearTimeout(copyTimeoutId) in a Svelte onDestroy/cleanup effect so
you never update copied after unmount. Ensure you reference the existing copyCSS
function and the copied state variable when implementing the timeout tracking
and cleanup.

- 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.
@jackgranatowski
jackgranatowski merged commit abc250d 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