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
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,34 @@ jobs:
# so an explicit build step here would just build twice.
- run: npx playwright install --with-deps chromium firefox webkit
- run: npm test

configurator:
name: Configurator tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 22
cache: npm
# Root deps first: the configurator's sync script reads docs/api-index.json
# via tooling generated at the repo root.
- run: npm ci
- run: npm ci
working-directory: configurator
# Unit suite — the sync tripwire: curated Basic controls, presets, fluid
# engine scalars and knob defaults all pinned to the live catalogue.
- run: npm test
working-directory: configurator
# Type/diagnostics gate (kept at 0 errors / 0 warnings).
- run: npm run check
working-directory: configurator
# Browser regression suite (test:e2e prebuilds; Playwright manages the
# preview server via its webServer option). Chromium only — cross-engine
# CSS coverage lives in the root Regression tests job.
- run: npx playwright install --with-deps chromium
working-directory: configurator
- run: npm run test:e2e
working-directory: configurator
19 changes: 19 additions & 0 deletions configurator/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,25 @@
name="description"
content="Configure every SLASHED CSS framework token and generate ready-to-paste override CSS."
/>
<meta name="theme-color" content="#16181d" />
<!-- Inline SVG favicon (the framework's "/" mark) — keeps the app a
single self-contained bundle with no binary assets. -->
<link
rel="icon"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%2316181d'/%3E%3Ctext x='16' y='24' font-family='monospace' font-size='24' font-weight='800' fill='%234f8cff' text-anchor='middle'%3E/%3C/text%3E%3C/svg%3E"
/>
<meta property="og:type" content="website" />
<meta property="og:title" content="SLASHED Configurator" />
<meta
property="og:description"
content="Configure every SLASHED CSS framework token and generate ready-to-paste override CSS. Buildless: live preview, instant export."
/>
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="SLASHED Configurator" />
<meta
name="twitter:description"
content="Configure every SLASHED CSS framework token and generate ready-to-paste override CSS."
/>
</head>
<body>
<div id="app"></div>
Expand Down
64 changes: 64 additions & 0 deletions configurator/package-lock.json

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

5 changes: 4 additions & 1 deletion configurator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./jsconfig.json",
"test": "node --test tests/*.test.js"
"test": "node --test tests/*.test.js",
"pretest:e2e": "npm run build",
"test:e2e": "playwright test"
},
"devDependencies": {
"@playwright/test": "^1.60.0",
"@sveltejs/vite-plugin-svelte": "^7.1.2",
"svelte": "^5.56.3",
"svelte-check": "^4.6.0",
Expand Down
32 changes: 32 additions & 0 deletions configurator/playwright.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Playwright config for the configurator's end-to-end regression suite.
*
* These specs pin the behaviors that unit tests can't reach (real cascade,
* localStorage, keyboard, viewport transitions) — most of them are direct
* regressions for bugs found during the IA-restructure QA sweeps.
*
* `npm run test:e2e` builds first (pretest:e2e) and Playwright then manages
* the preview server itself via `webServer`. Chromium-only on purpose: the
* suite tests the configurator app, not the framework's cross-engine CSS
* (that lives in the root /tests Playwright suite).
*/
import { defineConfig } from '@playwright/test';

export default defineConfig({
testDir: './tests-e2e',
timeout: 30_000,
workers: 1,
retries: process.env.CI ? 1 : 0,
reporter: process.env.CI ? 'line' : 'list',
use: {
baseURL: 'http://localhost:4173',
headless: true,
viewport: { width: 1600, height: 1000 },
},
webServer: {
command: 'npm run preview -- --port 4173 --strictPort',
url: 'http://localhost:4173',
reuseExistingServer: !process.env.CI,
timeout: 30_000,
},
});
22 changes: 21 additions & 1 deletion configurator/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/
import { DOMAINS, DOMAIN_BY_ID, BASIC_DOMAIN_IDS } from './lib/domains.js';
import { ui, undo, redo, overrides } from './lib/store.svelte.js';
import { UI_STORAGE_KEY } from './lib/uiState.js';
import { setProbeContext } from './lib/probeHost.js';
import Header from './components/Header.svelte';
import Sidebar from './components/Sidebar.svelte';
Expand Down Expand Up @@ -55,6 +56,17 @@
setProbeContext({ overrides, theme: ui.previewTheme });
});

// 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({ mode: ui.mode, domain: ui.domain, outputMode: ui.outputMode });
try {
localStorage.setItem(UI_STORAGE_KEY, snapshot);
} catch {
/* quota / private mode — non-essential, ignore */
}
});

// On narrow viewports the preview is a slide-over overlay, so it must start
// closed — opening a full-width scrim on first paint would bury the panel.
// The media query is tracked live: shrinking the window dismisses the
Expand Down Expand Up @@ -98,7 +110,15 @@
}
}
}
if (e.key === '/') {
if (e.key === 'Escape') {
// Dismiss the slide-over preview overlay (narrow viewports only — the
// desktop pane is a persistent layout region, not a dialog). The search
// box's own Escape (clear query) takes precedence when it has focus.
const inInput = e.target instanceof HTMLElement && e.target.tagName === 'INPUT';
if (!inInput && ui.previewOpen && window.matchMedia('(max-width: 1100px)').matches) {
ui.previewOpen = false;
}
} else if (e.key === '/') {
e.preventDefault();
document.querySelector('#cfg-search')?.focus();
} else if (e.key === 'b' || e.key === 'B') {
Expand Down
9 changes: 5 additions & 4 deletions configurator/src/components/Cheatsheet.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* filtered in-memory so switching filters is instant.
*/
import { allTokens } from '../lib/model.js';
import { copyText, COPY_FEEDBACK_MS } from '../lib/clipboard.js';

const PAGE_SIZE = 60;

Expand Down Expand Up @@ -63,11 +64,10 @@
// ── Copy ─────────────────────────────────────────────────────────────────────
let copied = $state(null);
async function copyName(name) {
try {
await navigator.clipboard.writeText(name);
if (await copyText(name)) {
copied = name;
setTimeout(() => (copied = null), 1200);
} catch { /* silent */ }
setTimeout(() => (copied = null), COPY_FEEDBACK_MS);
}
}

// ── Tier colour ──────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -360,6 +360,7 @@
color: var(--cfg-text-muted);
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
word-break: break-all;
Expand Down
44 changes: 22 additions & 22 deletions configurator/src/components/DomainPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,16 @@
}
</script>

{#snippet cardHead(title, count, hint = '')}
<header class="panel__card-head">
<span class="panel__card-title">{title}</span>
<span class="panel__card-count">{count}</span>
{#if hint}
<span class="panel__card-hint">{hint}</span>
{/if}
</header>
{/snippet}

<section class="panel">
<header class="panel__head">
<div class="panel__title-wrap">
Expand Down Expand Up @@ -185,10 +195,7 @@
{:else}
{#if basicSearch.shown.length}
<section class="cfg-card panel__card">
<header class="panel__card-head">
<span class="panel__card-title">Search results</span>
<span class="panel__card-count">{basicSearch.shown.length}</span>
</header>
{@render cardHead('Search results', basicSearch.shown.length)}
<div class="panel__card-rows">
{#each basicSearch.shown as token (token.name)}
<TokenRow {token} />
Expand Down Expand Up @@ -225,13 +232,11 @@
{#if domain.brandColors}
<!-- Brand colors domain: light/dark pair rows for every brand color -->
<section class="cfg-card panel__card">
<header class="panel__card-head">
<span class="panel__card-title">Brand &amp; status colors</span>
<span class="panel__card-count">{BRAND_COLOR_KEYS.length}</span>
{#if !advanced}
<span class="panel__card-hint">Set light-mode values — dark mode is auto-derived. Click the dark swatch to pin a custom value.</span>
{/if}
</header>
{@render cardHead(
'Brand & status colors',
BRAND_COLOR_KEYS.length,
advanced ? '' : 'Set light-mode values — dark mode is auto-derived. Click the dark swatch to pin a custom value.'
)}
<div class="panel__card-rows">
{#each BRAND_COLOR_KEYS as { key, label } (key)}
<BrandColorRow colorKey={key} {label} />
Expand All @@ -242,10 +247,7 @@
<!-- Curated Basic forms: friendly labels, help text, ⓘ raw-token info. -->
{#each basicGroups as group (group.title)}
<section class="cfg-card panel__card">
<header class="panel__card-head">
<span class="panel__card-title">{group.title}</span>
<span class="panel__card-count">{group.controls.length}</span>
</header>
{@render cardHead(group.title, group.controls.length)}
<div class="panel__card-rows">
{#each group.controls as c (c.token)}
<TokenRow token={c.tokenObj} label={c.label} help={c.help} showRawInfo />
Expand All @@ -255,13 +257,11 @@
{/each}
{:else if essentials.length}
<section class="cfg-card panel__card">
<header class="panel__card-head">
<span class="panel__card-title">Essentials</span>
<span class="panel__card-count">{essentials.length}</span>
{#if !advanced}
<span class="panel__card-hint">Curated for most projects · switch to Advanced (A) for the full {domainTokens.length}-token catalogue.</span>
{/if}
</header>
{@render cardHead(
'Essentials',
essentials.length,
advanced ? '' : `Curated for most projects · switch to Advanced (A) for the full ${domainTokens.length}-token catalogue.`
)}
<div class="panel__card-rows">
{#each essentials as token (token.name)}
<TokenRow {token} />
Expand Down
4 changes: 3 additions & 1 deletion configurator/src/components/Home.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
<span class="home__icon" aria-hidden="true">{d.icon}</span>
<span class="home__body">
<span class="home__name">{d.label}</span>
<span class="home__blurb">{d.intro ?? d.blurb}</span>
<!-- Tools (Themes) carry no intro — blurb is their one-liner. -->
<span class="home__blurb">{d.tool ? d.blurb : d.intro}</span>
</span>
<span class="home__status">
{#if d.tool}
Expand Down Expand Up @@ -141,6 +142,7 @@
color: var(--cfg-text-muted);
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
Expand Down
Loading