Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 11 additions & 12 deletions configurator/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions configurator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
"test:e2e": "playwright test"
},
"dependencies": {
"fflate": "^0.8.3",
"lucide-svelte": "^0.468.0"
"@lucide/svelte": "^1.23.0",
"fflate": "^0.8.3"
},
"devDependencies": {
"@playwright/test": "^1.60.0",
Expand Down
2 changes: 1 addition & 1 deletion configurator/src/App.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import { onMount, untrack } from 'svelte';
import { SlidersHorizontal, Eye, RotateCcw } from 'lucide-svelte';
import { SlidersHorizontal, Eye, RotateCcw } from '@lucide/svelte';
import type { PreviewTemplate, SlashedToken, ApiIndex } from './types';
import StudioHeader from './components/shell/StudioHeader.svelte';
import SidebarNav from './components/shell/SidebarNav.svelte';
Expand Down
2 changes: 1 addition & 1 deletion configurator/src/components/DomainPanel.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { SlidersHorizontal, List } from 'lucide-svelte';
import { SlidersHorizontal, List } from '@lucide/svelte';
import type { SlashedToken } from '../types';
import { DOMAIN_PATTERNS, domainOf } from '../lib/domains';
import HomePanel from './panels/HomePanel.svelte';
Expand Down
2 changes: 1 addition & 1 deletion configurator/src/components/panels/CheatsheetPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<script lang="ts">
import classesData from '../../data/classes.generated.json';
import tokensData from '../../data/api-index.generated.json';
import { Copy, Check } from 'lucide-svelte';
import { Copy, Check } from '@lucide/svelte';
import type { SlashedClass } from '../../types';

const classes = classesData.classes;
Expand Down
2 changes: 1 addition & 1 deletion configurator/src/components/panels/ExportPanel.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { Check, Copy, Download, Link } from 'lucide-svelte';
import { Check, Copy, Download, Link } from '@lucide/svelte';
import { generateCSS, buildShareUrl } from '../../lib/codec';

let { overrides }: {
Expand Down
2 changes: 1 addition & 1 deletion configurator/src/components/panels/HomePanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import {
Sparkles, Palette, Type, Ruler, Layout, Square, Layers, Zap,
Puzzle, Blocks, SwatchBook, ShieldCheck, Package, BookOpen, Save,
} from 'lucide-svelte';
} from '@lucide/svelte';
import type { SavedSlot } from '../../types';
import { listSavedThemes, saveTheme } from '../../lib/savedThemes';

Expand Down
2 changes: 1 addition & 1 deletion configurator/src/components/panels/ThemesPanel.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { Save, Trash2, Check } from 'lucide-svelte';
import { Save, Trash2, Check } from '@lucide/svelte';
import type { SavedSlot } from '../../types';
import { listSavedThemes, saveTheme, deleteTheme } from '../../lib/savedThemes';

Expand Down
2 changes: 1 addition & 1 deletion configurator/src/components/shell/PreviewPanel.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { Sun, Moon, Smartphone, Tablet, Monitor, RefreshCw, ExternalLink, Columns2 } from 'lucide-svelte';
import { Sun, Moon, Smartphone, Tablet, Monitor, RefreshCw, ExternalLink, Columns2 } from '@lucide/svelte';
import type { PreviewTemplate } from '../../types';
import { generateCSS } from '../../lib/codec';
import { computeDerivedOverrides } from '../../lib/persistence';
Expand Down
2 changes: 1 addition & 1 deletion configurator/src/components/shell/SidebarNav.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import {
Home, Palette, Type, Ruler, Layout, Square, Layers, Zap, Sparkles, Puzzle,
Blocks, SwatchBook, ShieldCheck, Package, BookOpen,
} from 'lucide-svelte';
} from '@lucide/svelte';

let { activeId, onSelect, overridesByDomain = {} }: {
activeId: string;
Expand Down
2 changes: 1 addition & 1 deletion configurator/src/components/shell/StudioHeader.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import { tick } from 'svelte';
import { Undo2, Redo2, Trash2, Share2, FolderOpen, Check, Save, Loader2, AlertTriangle } from 'lucide-svelte';
import { Undo2, Redo2, Trash2, Share2, FolderOpen, Check, Save, Loader2, AlertTriangle } from '@lucide/svelte';

const version = typeof __SLASHED_VERSION__ !== "undefined" ? __SLASHED_VERSION__ : "";

Expand Down
6 changes: 5 additions & 1 deletion configurator/src/lib/persistence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* chrome + preview reflect the current overrides immediately.
*/
import { readShareFromHashIfPresent, encodeOverrides, generateCSS } from "./codec";
import { isStringRecord } from "./savedThemes";

const LS_KEY = "slashed-studio/overrides/v2";

Expand Down Expand Up @@ -216,7 +217,10 @@ export function loadInitialOverrides(): Record<string, string> {
}
const local = localStorage.getItem(LS_KEY);
if (local) {
try { return JSON.parse(local); } catch { /* fall through */ }
try {
const parsed: unknown = JSON.parse(local);
if (isStringRecord(parsed)) return parsed;
} catch { /* fall through */ }
}
return {};
}
Expand Down
8 changes: 8 additions & 0 deletions configurator/src/lib/previewResolver.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ let probeDarkEl: HTMLElement | null = null;
// function is a pure write with no reactive read, so clearing here can't cause loops.
const resolveCache = new Map<string, string>();

// SL-025: deliberately module-level (not created inside a component/store
// factory). Every panel across the whole tree resolves colors against the
// *same* single preview iframe registered via registerPreviewDoc(), so there
// is exactly one meaningful "preview version" for the app to share — scoping
// this to a component would just mean re-deriving/passing it down everywhere
// for no benefit. Wrapped in an object (`.value`) because `$state` on a bare
// module-level primitive isn't itself reactive outside a component context;
// consumers read `previewVersion.value`, never reassign `previewVersion`.
/** Reactive version counter — Svelte runes pick this up via `.value`. */
export const previewVersion = $state({ value: 0 });

Expand Down
2 changes: 1 addition & 1 deletion configurator/src/lib/savedThemes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { SavedSlot } from "../types";

const LS_KEY = "slashed-studio/themes/v1";

function isStringRecord(value: unknown): value is Record<string, string> {
export function isStringRecord(value: unknown): value is Record<string, string> {
return !!value
&& typeof value === "object"
&& !Array.isArray(value)
Expand Down
20 changes: 12 additions & 8 deletions configurator/tests/persistence.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
* Contract: standalone mode reads/writes localStorage + URL hash; WP-embedded
* mode (window.slashedApp present) reads PHP-hydrated overrides and writes via
* REST. Covers both branches of loadInitialOverrides/saveOverrides, including
* the malformed-localStorage edge case flagged by SL-019 (not fixed in this
* pass — the assertions below document the current, unguarded behavior).
* the malformed-localStorage edge cases fixed by SL-019 (shape-guarded via
* savedThemes.ts's isStringRecord()).
*/
import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest';
import registry from '../src/data/token-registry.generated.json';
Expand Down Expand Up @@ -87,13 +87,17 @@ describe('loadInitialOverrides — standalone mode', () => {
expect(loadInitialOverrides()).toEqual({});
});

// SL-019 (not fixed here): loadInitialOverrides' standalone/localStorage path
// trusts JSON.parse's result as Record<string,string> with no shape guard,
// unlike savedThemes.ts's equivalent read path. This test pins today's real
// (unguarded) behavior so a future SL-019 fix has a test to update.
test('SL-019: currently returns non-object JSON as-is instead of {} (documented gap)', () => {
// SL-019: loadInitialOverrides' standalone/localStorage path now reuses
// savedThemes.ts's isStringRecord() shape guard, matching that module's
// equivalent read path, instead of trusting JSON.parse's result outright.
test('SL-019: returns {} for non-object JSON instead of passing it through', () => {
localStorage.setItem(LS_KEY, JSON.stringify(['not', 'an', 'object']));
expect(loadInitialOverrides()).toEqual(['not', 'an', 'object']);
expect(loadInitialOverrides()).toEqual({});
});

test('SL-019: returns {} when a value in the stored record is not a string', () => {
localStorage.setItem(LS_KEY, JSON.stringify({ [t0]: '5rem', [t1]: 42 }));
expect(loadInitialOverrides()).toEqual({});
});
});

Expand Down