Skip to content

Refactor themes & motion UI; replace presets with user-saved themes - #460

Merged
jackgranatowski merged 4 commits into
mainfrom
claude/configurator-ui-improvements-22etpo
Jun 30, 2026
Merged

jackgranatowski merged 4 commits into
mainfrom
claude/configurator-ui-improvements-22etpo

Conversation

@jackgranatowski

@jackgranatowski jackgranatowski commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR replaces the curated theme preset system with a user-driven saved themes feature, and significantly refactors the Motion panel to improve easing/duration editing and preview experience. The Themes panel now lets users save their current configuration as a named theme and re-apply it later (stored in localStorage), rather than applying pre-baked design system presets.

Key Changes

Themes System

  • Removed lib/themes.ts (8 curated preset themes) and PresetTheme type
  • Added lib/savedThemes.ts with localStorage-backed theme persistence:
    • listSavedThemes() — retrieve all saved themes
    • saveTheme(name, overrides) — create a new theme snapshot
    • deleteTheme(id) — remove a saved theme
    • renameTheme(id, name) — update theme name
  • Updated SavedSlot type to replace PresetTheme (same shape, different semantics)
  • Refactored ThemesPanel.svelte:
    • Removed preset grid; added "Save current" input + button
    • Shows active theme when live overrides match a saved snapshot exactly
    • Lists saved themes with delete/rename controls
    • Empty state when no themes saved yet

Motion Panel Overhaul

  • Added live preview section at the top:
    • Animated box that plays on demand
    • Dropdown selectors for easing curve + duration
    • "Play" button to feel the animation in real-time
    • Reflects any edits to easing/duration tokens immediately
  • Reorganized sections with clearer naming:
    • "Durations" (was "Duration overrides") — now includes inline duration preview bars
    • "Easing curves" (was "Easing curves") — fully editable with preview/reset buttons per curve
    • "Stagger" (was "Stagger base") — simplified UI
    • "Advanced" (was "Theme transition" + "Global scale") — collapsed section for less-common knobs
  • Improved easing editing:
    • Each easing token now has a text input accepting any CSS timing function
    • Visual preview (SVG curve) + quick "preview" button to test in live demo
    • Reset button appears only when overridden
  • Updated overshoot easing value from cubic-bezier(0.34, 1.56, 0.64, 1) to linear(0, 0.6 30%, 1.08 55%, 0.98 75%, 1) for smoother behavior
  • Removed comment about animation-delay-N base calculation (was inline documentation)
  • Added helper function easeValue(token) to resolve easing overrides with fallback to defaults

Home Panel Updates

  • Replaced theme preset grid with token domain cards + tools section
  • Added domain count badges showing active overrides per category
  • Reorganized layout:
    • Token domains (colors, typography, spacing, layout, borders, shadows, motion, effects, macros, misc)
    • Tools section (themes, WCAG, install, classes)
  • Added quick-save UI for themes directly on home panel
  • Updated copy to reflect user-driven workflow ("Pick any domain to get started" vs. "Start with a preset")

Spacing & Borders Panels

  • Removed StylePresetCards component and preset UI
  • Spacing: Removed density presets; added space scale preview bars at top
  • Borders: Removed corner style presets; simplified radius section
  • Shadows: Removed elevation presets; added elevation preview grid
  • Effects: Converted text shadow presets to direct token editing

Type System

  • Removed PresetTheme interface
  • Kept SavedSlot (now the primary theme type)
  • Removed StylePreset interface (no longer used)

Cleanup

  • Removed lib/stylePresets.ts (corner, shadow, text-shadow presets)
  • Removed `StylePre

https://claude.ai/code/session_01MW6NcRM3dtJndBz9kgXnL7

Summary by CodeRabbit

  • New Features

    • Added saved themes with local browser storage, including save, apply, rename, and delete actions.
    • Added richer token editing controls across spacing, shadows, motion, typography, and text-shadow settings.
    • Introduced new live previews and “Advanced” sections for several design panels.
  • Bug Fixes

    • Theme application now applies the selected overrides directly, improving consistency when switching styles.
    • Simplified panel interactions and removed several preset-based controls for a cleaner experience.

- De-emphasise power knobs and move them to a collapsed "Advanced" section at
  the end of each category (borders, shadows, spacing, motion, typography).
- Home panel links to every token domain and tool, and surfaces saved themes
  with a quick "save current" action.
- Replace curated theme presets with user-saved themes: a new savedThemes
  store (localStorage) backs save/apply/delete in the Themes panel and Home.
- Remove opinionated style presets (corner style, elevation, density, text
  shadow) so all design options are token-driven; text-shadow edited per token.
- Font-weight pickers offer the full 100-900 numeric scale.
- Reposition previews: shadows elevation and spacing scale previews move to the
  top of their categories; motion duration preview sits inside the durations
  group; borders radius preview stays within the radius group.
- Reorganise the Motion panel into Live preview / Durations / Easing / Stagger /
  Advanced. Easing tokens are now editable and the live demo plays a chosen
  easing + duration so changes are visible.
- Shadow glow now has a working preview bound to --sf-shadow-glow.
- Flow space and other var-based sliders (prose spacing, media radius, scroll
  shadow size) gained raw-input mode for entering variables/expressions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MW6NcRM3dtJndBz9kgXnL7
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 31 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: 1e9d3055-7fbe-494b-bb4a-751ba4e8e6f4

📥 Commits

Reviewing files that changed from the base of the PR and between d17e1ce and 3a7920e.

📒 Files selected for processing (10)
  • configurator/src/App.svelte
  • configurator/src/components/panels/EffectsPanel.svelte
  • configurator/src/components/panels/MotionPanel.svelte
  • configurator/src/components/panels/ShadowsPanel.svelte
  • configurator/src/components/panels/ThemesPanel.svelte
  • configurator/src/components/panels/TypographyPanel.svelte
  • configurator/src/components/shell/PreviewPanel.svelte
  • configurator/src/components/shell/StudioHeader.svelte
  • configurator/src/lib/savedThemes.ts
  • configurator/src/types.ts
📝 Walkthrough

Walkthrough

Removes the curated PresetTheme/StylePreset type system and all preset data (THEME_PRESETS, CORNER_PRESETS, SHADOW_PRESETS, StylePresetCards). Introduces a localStorage-backed saved themes library (savedThemes.ts). Rewires onApplyTheme across all panels to accept Record<string, string> directly, removes onBulkChange from domain panels, and reorganizes domain panel UIs with Advanced collapsibles.

Changes

Preset Removal and Saved Themes Refactor

Layer / File(s) Summary
Type and data removal
configurator/src/types.ts, configurator/src/lib/themes.ts, configurator/src/lib/stylePresets.ts, configurator/src/components/inputs/StylePresetCards.svelte
Removes PresetTheme and StylePreset interfaces, deletes THEME_PRESETS, CORNER_PRESETS, SHADOW_PRESETS, and the StylePresetCards component entirely.
localStorage saved themes CRUD
configurator/src/lib/savedThemes.ts
New module with read/write helpers over localStorage and exported listSavedThemes, saveTheme, deleteTheme, renameTheme functions.
App and DomainPanel contract update
configurator/src/App.svelte, configurator/src/components/DomainPanel.svelte
handleApplyTheme rewritten to accept Record<string, string> and call setOverrides directly; DomainPanel removes onBulkChange passthrough to child panels.
HomePanel and ThemesPanel saved-theme UI
configurator/src/components/panels/HomePanel.svelte, configurator/src/components/panels/ThemesPanel.svelte
HomePanel adds TOKEN_DOMAINS, TOOLS, domainCount, and quick-save; ThemesPanel replaces preset list with save/apply/delete UI backed by savedThemes.ts.
SpacingPanel, BordersPanel, ShadowsPanel refactor
configurator/src/components/panels/SpacingPanel.svelte, configurator/src/components/panels/BordersPanel.svelte, configurator/src/components/panels/ShadowsPanel.svelte
Removes onBulkChange prop and density/corner preset UIs; moves power-knob controls into new Advanced collapsibles; reorganizes ShadowsPanel state and adds glow preview.
EffectsPanel, TypographyPanel, MacrosPanel updates
configurator/src/components/panels/EffectsPanel.svelte, configurator/src/components/panels/TypographyPanel.svelte, configurator/src/components/panels/MacrosPanel.svelte
EffectsPanel replaces text-shadow presets with per-token text inputs; TypographyPanel expands weight options and adds Advanced collapsible for power knobs; MacrosPanel adds raw CSS variable wiring to SliderRows.
MotionPanel reorganization
configurator/src/components/panels/MotionPanel.svelte
Adds a Live preview section with easing/duration selectors, inline duration visualization, collapsible easing-curves editor, and moves global motion scale/transition/scroll-timeline into an Advanced collapsible.
PowerKnobRow label styling
configurator/src/components/inputs/PowerKnobRow.svelte
Removes the lightning ⚡ icon and changes label text color to text-slate-300.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • codeslash-dev/SLASHED#303: Same configurator theme-application redesign — updates App/Domain/Themes flows to work with theme override maps.
  • codeslash-dev/SLASHED#337: Extends saved-themes UI with import/export (saveImportedTheme), directly adjacent to the persistence layer added here.
  • codeslash-dev/SLASHED#423: Adds TEXT_SHADOW_PRESETS to EffectsPanel — the exact construct this PR removes and replaces with per-token inputs.

Suggested labels

codex

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main changes: theme preset replacement with user-saved themes and a broader motion UI refactor.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/configurator-ui-improvements-22etpo

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

Refactor themes & motion UI; replace presets with user-saved themes

✨ Enhancement 🕐 40+ Minutes

Grey Divider

AI Description

• Replaces 8 curated PresetTheme presets with a localStorage-backed user-saved themes system
 (savedThemes.ts) supporting save, delete, and rename operations.
• Overhauls the Motion panel: adds a live preview section (animated box with easing/duration
 selectors + Play button), makes easing tokens directly editable with per-curve text inputs and SVG
 previews, and moves power knobs to a collapsed "Advanced" section.
• Removes StylePresetCards component and all opinionated style presets (corner, density,
 elevation, text-shadow); replaces them with always-visible preview visualizations (space scale bars,
 elevation grid, glow preview) and direct token editing.
• Refactors Home panel to show all 10 token domains with per-domain override count badges, a tools
 section, and a quick-save UI for themes.
• Adds raw-input mode to SliderRow usages in MacrosPanel (flow space, scroll shadow, prose
 spacing) so users can enter CSS variable expressions.
• Expands font-weight picker from 6 options (300–800) to the full 100–900 numeric scale.
• Removes PresetTheme and StylePreset interfaces from types.ts; SavedSlot is now the sole
 theme type.
Diagram

graph TD
    App["App.svelte"] --> DomainPanel["DomainPanel.svelte"]
    DomainPanel --> HomePanel["HomePanel.svelte"]
    DomainPanel --> ThemesPanel["ThemesPanel.svelte"]
    DomainPanel --> MotionPanel["MotionPanel.svelte"]
    DomainPanel --> SpacingPanel["SpacingPanel.svelte"]
    DomainPanel --> BordersPanel["BordersPanel.svelte"]
    DomainPanel --> ShadowsPanel["ShadowsPanel.svelte"]
    DomainPanel --> EffectsPanel["EffectsPanel.svelte"]
    DomainPanel --> MacrosPanel["MacrosPanel.svelte"]
    HomePanel --> savedThemes[("savedThemes.ts")]
    ThemesPanel --> savedThemes
    savedThemes --> LS[("localStorage")]
    OLD_themes["themes.ts ❌"] -. removed .-> HomePanel
    OLD_presets["stylePresets.ts ❌"] -. removed .-> SpacingPanel
    OLD_presets -. removed .-> BordersPanel
    OLD_presets -. removed .-> ShadowsPanel
    subgraph Legend
      direction LR
      _panel["Panel"] ~~~ _store[("Store")] ~~~ _db[("Browser Storage")]
    end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Svelte store with localStorage sync
  • ➕ Reactive — all components auto-update when themes change
  • ➕ Eliminates the pattern of reassigning themes = saveTheme(...) in every caller
  • ➕ Consistent with Svelte idioms
  • ➖ Slightly more boilerplate (writable + subscribe/set wrapper)
  • ➖ Requires converting callers from imperative to reactive style

Recommendation: The PR's localStorage approach is the right call for a browser-only configurator tool — it's zero-dependency, instant, and survives page reloads. The main alternative worth considering is a Svelte store (writable + localStorageEffect) rather than imperative read/write calls, which would give reactive updates across components without manually reassigning the themes state variable after each mutation. The current pattern (each mutation returns the new array and the caller does themes = saveTheme(...)) works but is slightly fragile if a third component ever calls the library directly without updating its local state.

Files changed (17) +698 / -734

Enhancement (7) +557 / -408
ThemesPanel.svelteReplace preset list with user-saved themes UI +121/-53

Replace preset list with user-saved themes UI

• Rewrites the Themes panel to show a save-current form (name input + Save button), a list of user-saved themes with Apply/Delete controls, active-theme highlighting via shallow equality check, and a conditional Reset-all button. Empty state is shown when no themes are saved yet.

configurator/src/components/panels/ThemesPanel.svelte

HomePanel.svelteReplace preset grid with domain cards, tools section, and quick-save +126/-42

Replace preset grid with domain cards, tools section, and quick-save

• Removes the theme preset grid and replaces it with a full list of 10 token domain cards (each with a per-domain override count badge), a tools section (Themes, WCAG, Install, Classes), and a quick-save UI for themes. Imports 'listSavedThemes'/'saveTheme' from the new store.

configurator/src/components/panels/HomePanel.svelte

MotionPanel.svelteOverhaul Motion panel with live preview, editable easings, and Advanced section +194/-211

Overhaul Motion panel with live preview, editable easings, and Advanced section

• Adds a persistent live-preview section at the top with an animated box, easing/duration dropdowns, and a Play button. Makes all easing tokens directly editable via text inputs with SVG curve previews and per-curve preview/reset buttons. Moves global scale, theme transition, and scroll timeline controls into a collapsed Advanced section. Renames sections for clarity and updates the overshoot easing value.

configurator/src/components/panels/MotionPanel.svelte

ShadowsPanel.svelteReplace elevation presets with always-visible preview grid; add glow preview +49/-61

Replace elevation presets with always-visible preview grid; add glow preview

• Removes the 'StylePresetCards' elevation preset row and the collapsible elevation preview. Adds an always-visible elevation preview grid at the top and a glow preview box inside the glow section. Moves power knobs to a collapsed Advanced section. Drops 'onBulkChange' from props.

configurator/src/components/panels/ShadowsPanel.svelte

EffectsPanel.svelteReplace text-shadow presets with direct per-token text inputs +23/-22

Replace text-shadow presets with direct per-token text inputs

• Replaces the three-button text-shadow preset row (None/Subtle/Strong) with individual text inputs for '--sf-text-shadow-s/m/l', each with a reset button. Adds inline documentation about using 'none' to disable.

configurator/src/components/panels/EffectsPanel.svelte

MacrosPanel.svelteAdd raw-input mode to flow space, scroll shadow, and prose spacing sliders +19/-7

Add raw-input mode to flow space, scroll shadow, and prose spacing sliders

• Passes 'rawDefault', 'currentRaw', and 'onRawSet' props to 'SliderRow' for flow space, scroll shadow size, prose spacing tokens, and prose media radius, enabling users to enter CSS variable expressions instead of numeric values.

configurator/src/components/panels/MacrosPanel.svelte

TypographyPanel.svelteExpand font-weight options and move power knobs to Advanced section +25/-12

Expand font-weight options and move power knobs to Advanced section

• Extends 'WEIGHT_OPTIONS' from 6 values (300–800) to the full 9-step scale (100–900). Moves typography power knobs out of the modular scale section into a new collapsed Advanced subsection.

configurator/src/components/panels/TypographyPanel.svelte

Refactor (9) +77 / -326
themes.tsDeleted: 8 curated preset themes +0/-120

Deleted: 8 curated preset themes

• Removed the static 'THEME_PRESETS' array containing 8 opinionated design system presets (Bold, Editorial, Soft Cloud, High Contrast, Neo-Brutalist, Midnight, Sand & Clay, default). This data is no longer shipped with the configurator.

configurator/src/lib/themes.ts

stylePresets.tsDeleted: corner and shadow style presets +0/-17

Deleted: corner and shadow style presets

• Removed 'CORNER_PRESETS' and 'SHADOW_PRESETS' arrays that backed the 'StylePresetCards' component. These preset-based bulk-patch shortcuts are replaced by direct token editing and always-visible previews.

configurator/src/lib/stylePresets.ts

StylePresetCards.svelteDeleted: generic preset card selector component +0/-46

Deleted: generic preset card selector component

• Removed the reusable 'StylePresetCards' component that rendered a row of preset buttons and highlighted the active one. All panels that used it now show direct token controls instead.

configurator/src/components/inputs/StylePresetCards.svelte

types.tsRemove PresetTheme and StylePreset interfaces +0/-15

Remove PresetTheme and StylePreset interfaces

• Deleted the 'PresetTheme' and 'StylePreset' interfaces. 'SavedSlot' is now the sole theme type used throughout the configurator.

configurator/src/types.ts

SpacingPanel.svelteRemove density presets; add always-visible space scale preview +39/-78

Remove density presets; add always-visible space scale preview

• Removes the 'DENSITY_PRESETS' array and its collapsible UI. Adds a space scale preview bar chart at the top of the panel. Moves power knobs to a collapsed Advanced section at the bottom. Drops 'onBulkChange' from the component's props.

configurator/src/components/panels/SpacingPanel.svelte

BordersPanel.svelteRemove corner style presets; move radius scale to Advanced section +28/-36

Remove corner style presets; move radius scale to Advanced section

• Removes the 'StylePresetCards' corner-style preset row. Moves the radius scale power knob into a collapsed Advanced section at the bottom of the panel. Drops 'onBulkChange' from props.

configurator/src/components/panels/BordersPanel.svelte

DomainPanel.svelteUpdate onApplyTheme signature; drop onBulkChange from spacing/borders/shadows +5/-5

Update onApplyTheme signature; drop onBulkChange from spacing/borders/shadows

• Changes 'onApplyTheme' prop type from '(theme: PresetTheme) => void' to '(overrides: Record<string, string>) => void'. Removes 'onBulkChange' from SpacingPanel, BordersPanel, and ShadowsPanel call sites.

configurator/src/components/DomainPanel.svelte

App.svelteSimplify handleApplyTheme to accept plain overrides record +4/-7

Simplify handleApplyTheme to accept plain overrides record

• Removes the 'PresetTheme' import and the special-case 'id === 'default'' branch in 'handleApplyTheme'. The function now simply calls 'setOverrides({ ...themeOverrides })' with the passed override map.

configurator/src/App.svelte

PowerKnobRow.svelteRemove ⚡ badge from power knob label +1/-2

Remove ⚡ badge from power knob label

• Removes the amber '⚡' badge span from the knob label row, de-emphasising power knobs visually to match their new placement inside collapsed Advanced sections.

configurator/src/components/inputs/PowerKnobRow.svelte

Other (1) +64 / -0
savedThemes.tsNew localStorage-backed user-saved themes store +64/-0

New localStorage-backed user-saved themes store

• Introduces 'savedThemes.ts' with four exported functions: 'listSavedThemes', 'saveTheme', 'deleteTheme', and 'renameTheme'. Themes are persisted under the key 'slashed-studio/themes/v1' in localStorage, replacing the old static preset array. Each saved slot gets a timestamp-based unique ID.

configurator/src/lib/savedThemes.ts

@coderabbitai coderabbitai Bot added the codex label Jun 30, 2026
@qodo-code-review

qodo-code-review Bot commented Jun 30, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Context used
✅ Compliance rules (platform): 6 rules

Grey Divider


Action required

1. NaN motion token parsing ✓ Resolved 🐞 Bug ≡ Correctness
Description
MotionPanel and ShadowsPanel derive numeric UI and timing values using parseFloat on override
strings without validating finiteness, so non-numeric CSS expressions like var(...)/calc(...)
can become NaN and break slider controls, transition styling, live preview timing, and demo
timers.
Code

configurator/src/components/panels/MotionPanel.svelte[R56-60]

  function getDuration(token: string, base: number): number {
    const raw = overrides[token];
    if (raw) return parseFloat(raw);
    return Math.round(base * scale);
  }
Relevance

⭐⭐⭐ High

Team previously accepted NaN/finite guards for parseFloat(overrides) sliders (PR #438).

PR-#438

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
In MotionPanel, getDuration() returns parseFloat(raw) directly when an override exists, and
those derived values are then consumed as numbers for slider values, CSS transition duration,
and setTimeout delays; if the override string is not a plain numeric value, parseFloat yields
NaN which propagates into those call sites and breaks rendering/behavior. In ShadowsPanel,
lightness is computed as parseFloat(overrides["--sf-shadow-lightness"]) and passed into
SliderRow, so a non-numeric override similarly produces NaN and can break the slider control;
this matches a previously accepted bug pattern of needing NaN/finite guards around parseFloat for
override-driven UI.

configurator/src/components/panels/MotionPanel.svelte[36-76]
configurator/src/components/panels/ShadowsPanel.svelte[15-51]
PR-#438

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

## Issue description
`MotionPanel.svelte` and `ShadowsPanel.svelte` parse override values with `parseFloat` and then use the results as numbers (slider values, CSS transition duration, and timer delays) without guarding against `NaN`/non-finite results. When overrides contain non-numeric CSS expressions such as `var(...)` or `calc(...)`, `parseFloat` can produce `NaN`, which then propagates into UI controls and timing logic and breaks rendering/behavior.

## Issue Context
Overrides can come from imported configs, URL hash/localStorage, or hand-authored inputs and are not guaranteed to be plain numeric strings; they may include `var(...)`/`calc(...)` expressions or units like `ms`. The fix should ensure all numeric derivations used by sliders, transitions, and timers fall back to sane defaults when parsing does not yield a finite number.

## Fix Focus Areas
- configurator/src/components/panels/MotionPanel.svelte[36-60]
- configurator/src/components/panels/ShadowsPanel.svelte[15-21]

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


2. Invalid saved theme crash ✓ Resolved 🐞 Bug ☼ Reliability
Description
savedThemes.read() casts arbitrary JSON arrays from localStorage to SavedSlot[] without
validating fields, so malformed entries (e.g., missing overrides) can crash ThemesPanel when it
calls Object.keys(theme.overrides) or compares snapshots.
Code

configurator/src/lib/savedThemes.ts[R13-20]

+function read(): SavedSlot[] {
+  if (typeof window === "undefined") return [];
+  try {
+    const raw = localStorage.getItem(LS_KEY);
+    if (!raw) return [];
+    const parsed = JSON.parse(raw);
+    return Array.isArray(parsed) ? (parsed as SavedSlot[]) : [];
+  } catch {
Relevance

⭐⭐ Medium

Only evidence: accepted try/catch around JSON.parse for corrupt localStorage (PR #313); no history
on deep shape validation.

PR-#313

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The storage layer returns unvalidated parsed JSON as SavedSlot[], while ThemesPanel immediately
assumes theme.overrides is a valid object and calls Object.keys(theme.overrides), which will
throw for malformed stored entries.

configurator/src/lib/savedThemes.ts[13-22]
configurator/src/components/panels/ThemesPanel.svelte[12-25]
configurator/src/components/panels/ThemesPanel.svelte[81-83]

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

### Issue description
`savedThemes.read()` only checks that parsed localStorage content is an array, then returns it as `SavedSlot[]`. UI code assumes `theme.overrides` is a valid object and uses `Object.keys(theme.overrides)` and `shallowEq(t.overrides, overrides)`, which will throw if `overrides` is `null`, missing, or not an object.

### Issue Context
localStorage can contain corrupted/legacy data (manual edits, previous experiments, partial writes). The storage layer should be defensive.

### Fix Focus Areas
- configurator/src/lib/savedThemes.ts[13-22]
- configurator/src/components/panels/ThemesPanel.svelte[12-25]
- configurator/src/components/panels/ThemesPanel.svelte[81-83]

### Implementation notes
- Add a runtime validator/normalizer in `read()`:
 - Ensure each item is a plain object.
 - Validate `id/name/icon/blurb` are strings (or provide safe defaults).
 - Validate `overrides` is a plain object; coerce to `{}` if invalid.
 - Filter override entries to `[string -> string]` only.
- Return only validated themes; optionally `write()` back the sanitized list to self-heal.
- Consider guarding UI too (e.g., `Object.keys(theme.overrides ?? {})`) as a second line of defense.

ⓘ 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/MotionPanel.svelte
Comment thread configurator/src/lib/savedThemes.ts

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

🤖 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 `@configurator/src/components/panels/EffectsPanel.svelte`:
- Around line 283-292: The controlled input in EffectsPanel.svelte is trimming
on every keystroke, which breaks multi-word values like space-separated shadows.
Update the oninput handler for the input bound to overrides[t.token] so it reads
the raw HTMLInputElement value, uses a trimmed check only to decide between
onSet and onReset, and stores the untrimmed value when calling onSet; keep any
trimming behavior out of the per-keystroke path, using on blur only if needed.

In `@configurator/src/components/panels/MotionPanel.svelte`:
- Around line 133-150: The easing and duration controls in MotionPanel.svelte
are unlabeled selects, so add accessible names to both dropdowns. Update the
select elements tied to demoEase and demoDuration with distinct aria-labels or
visible labels so assistive technologies can distinguish the easing picker from
the duration picker.

In `@configurator/src/components/panels/ThemesPanel.svelte`:
- Around line 104-108: The delete control in ThemesPanel.svelte is only revealed
via group hover, so it remains hidden for keyboard and touch users. Update the
button styling in the theme row markup so the delete action becomes visible on
focus/focus-within as well, and consider showing it by default for
non-hover/small-screen contexts; keep the change localized to the delete button
around handleDelete(theme.id) and the surrounding group-hover classes.

In `@configurator/src/components/panels/TypographyPanel.svelte`:
- Line 69: The weight picker in TypographyPanel.svelte now exposes weights that
the Google font loader does not request, so custom fonts can fall back to
synthesized styles. Update injectGoogleFont to request the full weight range
used by WEIGHT_OPTIONS, including the lighter and heavier values, and keep the
weight list in sync with the options shown in the panel.

In `@configurator/src/lib/savedThemes.ts`:
- Around line 25-31: The persistence flow in write() is swallowing localStorage
quota/security failures, so save/delete/rename callers still act as if the theme
list was persisted successfully. Update the savedThemes helpers that call
write() to detect and propagate persistence failure from write() instead of
always returning the updated list, and ensure the public save/delete/rename
paths only report success when the localStorage write actually succeeds.
- Around line 13-20: The read() helper in savedThemes.ts is accepting any parsed
array as SavedSlot[] without validating the record shape, which lets malformed
or legacy localStorage entries slip through. Update read() to filter/validate
each parsed item before returning it, ensuring every SavedSlot has the expected
fields including overrides, and discard invalid entries so downstream uses like
Object.keys(theme.overrides) in the saved-themes panels cannot fail.
🪄 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: 938b7143-4d87-4841-aef1-eaf171726cdd

📥 Commits

Reviewing files that changed from the base of the PR and between 58d64be and d17e1ce.

📒 Files selected for processing (17)
  • configurator/src/App.svelte
  • configurator/src/components/DomainPanel.svelte
  • configurator/src/components/inputs/PowerKnobRow.svelte
  • configurator/src/components/inputs/StylePresetCards.svelte
  • configurator/src/components/panels/BordersPanel.svelte
  • configurator/src/components/panels/EffectsPanel.svelte
  • configurator/src/components/panels/HomePanel.svelte
  • configurator/src/components/panels/MacrosPanel.svelte
  • configurator/src/components/panels/MotionPanel.svelte
  • configurator/src/components/panels/ShadowsPanel.svelte
  • configurator/src/components/panels/SpacingPanel.svelte
  • configurator/src/components/panels/ThemesPanel.svelte
  • configurator/src/components/panels/TypographyPanel.svelte
  • configurator/src/lib/savedThemes.ts
  • configurator/src/lib/stylePresets.ts
  • configurator/src/lib/themes.ts
  • configurator/src/types.ts
💤 Files with no reviewable changes (4)
  • configurator/src/lib/stylePresets.ts
  • configurator/src/types.ts
  • configurator/src/lib/themes.ts
  • configurator/src/components/inputs/StylePresetCards.svelte

Comment thread configurator/src/components/panels/EffectsPanel.svelte
Comment thread configurator/src/components/panels/MotionPanel.svelte
Comment thread configurator/src/components/panels/ThemesPanel.svelte
Comment thread configurator/src/components/panels/TypographyPanel.svelte
Comment thread configurator/src/lib/savedThemes.ts
Comment thread configurator/src/lib/savedThemes.ts Outdated
claude added 3 commits June 30, 2026 16:08
- savedThemes: validate SavedSlot shape on read() to guard against
  malformed/legacy localStorage entries crashing ThemesPanel
- savedThemes: write() now returns boolean; save/delete/rename only
  report success when the localStorage write actually succeeds
- MotionPanel/ShadowsPanel: guard parseFloat results with isFinite()
  to prevent NaN propagating into sliders, transitions, and timers
- MotionPanel: add aria-label to easing/duration preview selects
- EffectsPanel: stop trimming on every keystroke so multi-word
  shadow values (e.g. "0 4px 8px ...") can be typed without spaces
  being stripped mid-entry
- TypographyPanel: expand Google Fonts weight request from 300–700
  to 100–900 to match the full WEIGHT_OPTIONS range
- ThemesPanel: make delete button visible on focus and touch (was
  opacity-0 until group hover only, inaccessible to keyboard users)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MW6NcRM3dtJndBz9kgXnL7
On narrow screens the fixed rail + 360px panel + preview split left the
live preview off-screen and overflowed several toolbars. Now:

- Below the md breakpoint the body folds to a single full-width column
  with a Controls ⇄ Preview toggle bar; each view folds the other away
  so both the panel and the preview are fully usable on mobile. Desktop
  keeps the side-by-side layout unchanged.
- Header sheds secondary actions on small screens (share, import, the
  subtitle, and the "customised ·" label) so nothing is clipped.
- Preview toolbar wraps instead of cutting off; the template-tab group
  scrolls horizontally so every template stays reachable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MW6NcRM3dtJndBz9kgXnL7
…board previews

- Restore the Share and Import header buttons on small screens (all
  toolbar actions are now always visible); reclaim the space by
  collapsing only the brand text to the logo below the sm breakpoint,
  which removes no functionality.
- Remove the Docs and Dashboard live-preview templates entirely
  (TEMPLATES entries, DOCS_BODY/DASHBOARD_BODY bodies, BODIES map, and
  the PreviewTemplate union). Marketing, Components and Stylescape
  remain. previewTemplate is in-memory only, so no stored value can
  reference the removed templates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MW6NcRM3dtJndBz9kgXnL7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants