diff --git a/configurator/src/App.svelte b/configurator/src/App.svelte
index 661bd5c5..a5f9b769 100644
--- a/configurator/src/App.svelte
+++ b/configurator/src/App.svelte
@@ -151,7 +151,7 @@
// Persist the navigation prefs so a reload restores where the user was.
// Restore (with validation) happens in store.svelte.js via sanitiseUiState.
$effect(() => {
- const snapshot = JSON.stringify({ domain: ui.domain, outputMode: ui.outputMode, uiTheme: ui.uiTheme, bundle: ui.bundle });
+ const snapshot = JSON.stringify({ domain: ui.domain, outputMode: ui.outputMode, uiTheme: ui.uiTheme, bundle: ui.bundle, showTokens: ui.showTokens });
try {
localStorage.setItem(UI_STORAGE_KEY, snapshot);
} catch {
diff --git a/configurator/src/components/Header.svelte b/configurator/src/components/Header.svelte
index 2273248b..d20f6d74 100644
--- a/configurator/src/components/Header.svelte
+++ b/configurator/src/components/Header.svelte
@@ -53,6 +53,7 @@
diff --git a/configurator/src/components/editors/ColorStudio.svelte b/configurator/src/components/editors/ColorStudio.svelte
index 022876da..d768f2f1 100644
--- a/configurator/src/components/editors/ColorStudio.svelte
+++ b/configurator/src/components/editors/ColorStudio.svelte
@@ -5,9 +5,11 @@
import BrandColorRow from '../BrandColorRow.svelte';
import ColorAssignments from '../ColorAssignments.svelte';
import FriendlyControl from '../FriendlyControl.svelte';
+ import { ui } from '../../lib/store.svelte.js';
import ShadeRamp from '../ShadeRamp.svelte';
import StudioFrame from './StudioFrame.svelte';
+ const showToken = $derived(ui.showTokens);
const main = BRAND_COLOR_KEYS.filter((c) => c.group === 'brand');
const status = BRAND_COLOR_KEYS.filter((c) => c.group === 'status');
const tuning = [
@@ -60,7 +62,7 @@
Core brand colors
{#each main as {key,label} (key)}{/each}
Semantic colors
{#each status as {key,label} (key)}{/each}
-
Contrast & palette tuning
{#each tuning as token (token.name)}{/each}
+
Contrast & palette tuning
{#each tuning as token (token.name)}{/each}
Semantic role preview
Generated shade ramp
diff --git a/configurator/src/components/editors/StudioControls.svelte b/configurator/src/components/editors/StudioControls.svelte
index d6de97bb..b318d5bb 100644
--- a/configurator/src/components/editors/StudioControls.svelte
+++ b/configurator/src/components/editors/StudioControls.svelte
@@ -1,9 +1,10 @@
@@ -11,7 +12,7 @@
overrides[token.name] != null).length}>
{#each group.tokens as token (token.name)}
-
+
{/each}
diff --git a/configurator/src/lib/store.svelte.js b/configurator/src/lib/store.svelte.js
index 6dfad727..ed02ee43 100644
--- a/configurator/src/lib/store.svelte.js
+++ b/configurator/src/lib/store.svelte.js
@@ -74,6 +74,8 @@ export const ui = $state({
uiTheme: savedUi.uiTheme ?? 'dark',
/** Selected dist bundle id for the install/setup picker (see lib/bundles.js). */
bundle: savedUi.bundle ?? 'optimal',
+ /** Show raw --sf-* token names in friendly/studio controls. */
+ showTokens: savedUi.showTokens ?? false,
/** Sidebar collapse — for narrow viewports / a focus-mode. */
sidebarOpen: true,
/**
@@ -109,7 +111,7 @@ export const savedThemes = $state(loadSavedThemes());
* Load the persisted UI preferences (domain / output format / theme / bundle),
* validated against the live taxonomy. Persisting happens in App.svelte
* via an $effect over the same fields.
- * @returns {{ domain?: string, outputMode?: 'layer'|'root', uiTheme?: 'light'|'dark', bundle?: string }}
+ * @returns {{ domain?: string, outputMode?: 'layer'|'root', uiTheme?: 'light'|'dark', bundle?: string, showTokens?: boolean }}
*/
function loadUiState() {
if (typeof localStorage === 'undefined') return {};
diff --git a/configurator/src/lib/uiState.js b/configurator/src/lib/uiState.js
index 3e459887..064d96ff 100644
--- a/configurator/src/lib/uiState.js
+++ b/configurator/src/lib/uiState.js
@@ -19,7 +19,7 @@ export const UI_STORAGE_KEY = 'slashed-configurator/ui/v1';
* simply ignored — the flat IA has no complexity mode.
*
* @param {string|null} raw JSON string from localStorage (or null)
- * @returns {{ domain?: string, outputMode?: 'layer'|'root', uiTheme?: 'light'|'dark', bundle?: string }}
+ * @returns {{ domain?: string, outputMode?: 'layer'|'root', uiTheme?: 'light'|'dark', bundle?: string, showTokens?: boolean }}
*/
export function sanitiseUiState(raw) {
const out = {};
@@ -49,5 +49,9 @@ export function sanitiseUiState(raw) {
out.bundle = parsed.bundle;
}
+ if (typeof parsed.showTokens === 'boolean') {
+ out.showTokens = parsed.showTokens;
+ }
+
return out;
}
diff --git a/configurator/tests-components/studios.test.js b/configurator/tests-components/studios.test.js
index e5e10d49..742091d9 100644
--- a/configurator/tests-components/studios.test.js
+++ b/configurator/tests-components/studios.test.js
@@ -18,9 +18,12 @@ import MotionStudio from '../src/components/editors/MotionStudio.svelte';
import EffectsStudio from '../src/components/editors/EffectsStudio.svelte';
import FriendlyControl from '../src/components/FriendlyControl.svelte';
import { tokenByName } from '../src/lib/model.js';
-import { clearAll, overrides } from '../src/lib/store.svelte.js';
+import { clearAll, overrides, ui } from '../src/lib/store.svelte.js';
-beforeEach(() => clearAll());
+beforeEach(() => {
+ clearAll();
+ ui.showTokens = false;
+});
const STUDIOS = [
['Typography Studio', TypographyStudio, ['The quick brown fox', 'Fluid scale']],
@@ -46,12 +49,38 @@ describe('visual studios', () => {
}
test('Typography Studio switches scopes and renders active panel controls', async () => {
+ ui.showTokens = true;
const { getByRole, getByText, getAllByText } = render(TypographyStudio);
await fireEvent.click(getByRole('tab', { name: 'Headings' }));
expect(getAllByText('Heading aliases, line-height, tracking and max-width constraints.').length).toBeGreaterThan(0);
expect(getByText('--sf-h1-size')).toBeInTheDocument();
});
+
+
+ test('Studio token names follow the global show/hide token setting', async () => {
+ const { queryByText, getByText } = render(TypographyStudio);
+
+ expect(queryByText('--sf-font-body')).not.toBeInTheDocument();
+
+ ui.showTokens = true;
+ await tick();
+ expect(getByText('--sf-font-body')).toBeInTheDocument();
+
+ ui.showTokens = false;
+ await tick();
+ expect(queryByText('--sf-font-body')).not.toBeInTheDocument();
+ });
+
+ test('FriendlyControl can still reveal raw token info locally', async () => {
+ const token = tokenByName.get('--sf-heading-text-wrap');
+ const { getByRole, getByText, queryByText } = render(FriendlyControl, { props: { token } });
+
+ expect(queryByText('--sf-heading-text-wrap')).not.toBeInTheDocument();
+ await fireEvent.click(getByRole('button', { name: 'Show token' }));
+ expect(getByText('--sf-heading-text-wrap')).toBeInTheDocument();
+ });
+
test('FriendlyControl renders schema select controls and writes overrides', async () => {
const token = tokenByName.get('--sf-heading-text-wrap');
const { getByLabelText } = render(FriendlyControl, { props: { token, showToken: true } });