Skip to content
140 changes: 140 additions & 0 deletions configurator/IA-REDESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Configurator IA — Basic / Advanced / Power tiers

Status: **implemented** (phases 1–5; see git history on this file's branch).
Companion docs: [`ROADMAP.md`](ROADMAP.md), [`../docs/architecture.md`](../docs/architecture.md).

The panel DX follows the conventions that make the best framework
configurators approachable — a flat home list of clearly named categories,
inline plain-language copy explaining *what each option does and whether you
should touch it*, and the dangerous controls folded away — adapted to
SLASHED's buildless model: there is no save/regenerate cycle, every edit is
a live `--sf-*` override with instant preview and a copy-paste CSS export.
That is our structural advantage and the panel is built to make it felt.

---

## 1. Problem statement (pre-restructure)

The configurator was complete (every token reachable — 841 at the time of
writing) but chaotic as a
first-run experience:

- **Quick Knobs opened every Basic panel.** `--sf-space-scale` drives 45
tokens, `--sf-shadow-strength` 14, `--sf-contrast-threshold` 11. Atomic
power tools — not "first thing a new user should drag" — sat *above* the
essentials, in Basic mode.
- **Basic mode was a filter, not a curation.** It hid rows but still spoke
raw token names, showed engine internals, and rendered 12 sidebar domains,
most of which a typical project never opens (Motion, Effects, Misc).
- **No guidance on "change vs leave".** Nothing said that ~11 colors + two
fluid generators + a handful of layout/border/shadow values are the entire
per-project surface, and everything else has good defaults.
- **Generators baked output.** The scale generators wrote per-step `clamp()`
results instead of the engine scalars, orphaning the live fluid engine.

## 2. Design principles

1. **Basic = the per-project checklist.** Only options that almost every
project changes from default. Everything else has a good default and is
*reachable*, not *visible*.
2. **Progressive disclosure in three tiers**:
- **Basic** — curated, friendly-labelled forms.
- **Advanced** — the full token catalogue, grouped, raw names visible.
- **Power** — global multipliers: rendered inside Advanced but visually
fenced (collapsed `⚡ Power knobs` group at the *bottom* of the panel,
amber warning styling, per-domain `powerIntro` copy, "drives N").
3. **Friendly labels in Basic, raw tokens in Advanced.** Basic says "Content
width", not `--sf-container-default`; the ⓘ popover reveals the token
name (click-to-copy), description, default and reach, so users graduate
naturally.
4. **Defaults are a feature.** Every control shows the framework default and
a one-click reset; style-preset rows always include the framework-default
look, and presets null what they don't set so they can never mask each
other.
5. **Write engine inputs, not baked outputs.** Generators set the source
scalars (`--sf-text-base-min`, ratios, the shared viewport range) so the
buildless fluid engine stays live downstream — a few numbers in the
export, never `clamp()` walls.
6. **Educate in place.** Intro copy per panel, "drives N" everywhere, docs
links in the panel footer, and the Home checklist as orientation.

## 3. The tier model (as shipped)

| Surface | What | Where it renders |
|---|---|---|
| Basic | 11 brand/status colors + ~25 curated controls (`src/lib/basics.js`) + generators + style presets | Basic forms; same tokens also appear in Advanced |
| Advanced | every public token | Advanced grouped catalogue |
| Power | global multipliers (`KNOBS_BY_DOMAIN`) | collapsed group at the bottom of Advanced panels only |

The mode toggle stays global (`B`/`A`); Basic search scopes to the curated
surface and reports "N more matches in Advanced" with a one-click jump that
preserves the query.

## 4. Basic mode contents

Sidebar in Basic: **Home + six domains + Themes**. Motion, Effects, WCAG,
Misc, Cheatsheet appear only in Advanced.

- **Home** — setup-checklist landing: one row per domain with intro copy,
per-domain customised counts, "start here" pointer and an Export CSS
shortcut into the output drawer.
- **Colors** — the 11 brand/status pair rows (light value, auto-derived dark
with optional override): base, neutral, primary, secondary, tertiary,
action, success, warning, error, info, danger.
- **Typography** — Body/Heading/Code font, body line height, heading weight;
the fluid **type generator** (+ display tab; display reuses the type
ratios, so its ratio selects are read-only).
- **Spacing** — the fluid **space generator** plus section padding, content
rhythm, gutter, component padding.
- **Layout** — content/reading/narrow/wide widths, header height, touch
target.
- **Borders** — **Corner style** preset row (Sharp / Subtle / Rounded /
Pill) + radius s/m/l, border width, divider width, border color.
- **Shadows** — **Shadow style** preset row (None / Subtle / Soft / Strong)
+ the four elevation steps.
- **Themes** — preset gallery + saved slots (the friendliest entry point
into the override model).

## 5. Keeping it in lockstep with the framework

The curation is configurator-side data (`basics.js`, `stylePresets.js`,
`fluidEngine.js`, `KNOBS_BY_DOMAIN`), so the framework needs no changes —
and the test suite is the sync tripwire:

- `tests/basics.test.js` — every curated control exists in the catalogue
with a non-empty default; labels unique; `domain.essentials` derives from
`basics.js` so the card and the search surface can't drift.
- `tests/fluid-engine.test.js` — every engine scalar exists and its
hardcoded default equals the live catalogue value; patches clear baked
per-step overrides and never touch the shared viewport on reset.
- `tests/style-presets.test.js` — every patch key exists; values survive
the sanitizer; exactly one all-null default preset per list; uniform key
sets so presets can't leave leftovers.
- `tests/themes.test.js` — knob default parity, including the
encode/decode round-trip for `--sf-shadow-strength`.

The upgrade ritual after a framework release: `npm run sync && npm test` —
a vanished token or drifted default fails CI loudly instead of rendering a
dead control.

## 6. Deliberate non-goals

- **A save/build cycle** — buildless live preview is the point; the header
"N customised — Export CSS" affordance maps the conventional save mental
model onto our export step.
- **Feature on/off toggles that change shipped CSS** — bundle composition
is a loading-time decision (`optional/*.css`), documented, not a
configurator switch.
- **Page-builder / CMS integration panels** — out of scope for a framework
configurator.
- **Separate light/dark scheme pages** — `light-dark()` + auto-derivation
collapse this; the pair-per-row UI covers both modes at once.

## 7. Possible follow-ups

- Read-only derived swatch strip per color family (show what one brand color
buys: hover/tint/shade ramp).
- Promote the curation metadata (`surface`) into `docs/api-index.json` once
it stabilises, so docs and configurator share one source of truth.
- The roadmap items in [`ROADMAP.md`](ROADMAP.md) (dependency-graph hover,
preset builder, shareable URLs…).
30 changes: 27 additions & 3 deletions configurator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,22 @@ step for consumers — open it, tweak tokens, copy the CSS.

## What it does

- Lists all `--sf-*` tokens exposed by the framework, grouped by category and
source section, with tier badges (`public` / `advanced` / `internal`),
descriptions, alias info, and the framework default shown as a placeholder.
- **Basic mode is a per-project checklist**: a Home landing screen plus six
curated domains (Colors, Typography, Spacing, Layout, Borders, Shadows)
and the Themes tool. Curated controls use friendly labels with help text;
an ⓘ popover reveals the raw token name, description, default and reach.
- **Advanced mode** lists all `--sf-*` tokens exposed by the framework,
grouped by category and source section, with tier badges (`public` /
`advanced` / `internal`), descriptions, alias info, and the framework
default shown as a placeholder. The global multipliers (e.g.
`--sf-space-scale`, which drives 45 tokens) live in a collapsed
**Power knobs** group at the bottom of each panel.
- **Fluid scale generators write the live engine scalars** (base / ratio /
shared viewport range) instead of baking per-step `clamp()` expressions —
a few numbers in your export, and the engine stays live for later tweaks.
- **One-click style presets** on the Basic borders/shadows panels (Corner
style: Sharp / Subtle / Rounded / Pill · Shadow style: None / Subtle /
Soft / Strong), each applied as a single undo step.
- Smart editors per token: color picker / swatch for colors, numeric and
length inputs where appropriate, text otherwise.
- Search across names, descriptions, values and namespaces; filter by tier or
Expand Down Expand Up @@ -38,6 +51,17 @@ So whenever a token is added, renamed, retiered, or re-valued in the CSS and
the docs are regenerated, the panel picks it up on the next build with zero
manual edits.

The hand-curated surfaces (`src/lib/basics.js`, `src/lib/stylePresets.js`,
`src/lib/fluidEngine.js`, the knob registry) are pinned to the catalogue by
the test suite: after any framework release the upgrade ritual is

```bash
npm run sync && npm test
```

A vanished token, a renamed knob, or a drifted default fails CI loudly
instead of rendering a dead control.

## Local development

```bash
Expand Down
27 changes: 22 additions & 5 deletions configurator/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,30 @@ intentional scope of the configurator legible.

---

## Shipped (Basic/Advanced/Power IA restructure)

- **Basic = per-project checklist**: a Home landing screen plus six curated
domains (Colors, Typography, Spacing, Layout, Borders, Shadows) and the
Themes tool. Curated controls carry friendly labels, help text and an ⓘ
popover revealing the raw token (see `src/lib/basics.js` — validated
against the catalogue by `tests/basics.test.js`).
- **Power knobs** — the global multipliers (`--sf-space-scale` drives 45
tokens, `--sf-shadow-strength` 14, `--sf-motion-scale` 13…) moved out of
Basic into a collapsed, warning-styled group at the bottom of each
Advanced panel.
- **Scalar-writing scale generators** — Apply writes the framework's live
fluid-engine scalars (base/ratio/shared viewport, `src/lib/fluidEngine.js`)
instead of baking per-step `clamp()` expressions.
- **Style preset rows** — one-click Corner style and Shadow style looks on
the Basic borders/shadows panels (`src/lib/stylePresets.js`), each a
single undo step.
- Basic search scopes to the curated surface and offers an
"N more matches in Advanced" jump that preserves the query.

## Shipped (PR #303)

- Categorised UI: every one of the **841 framework tokens** is reachable, no
catch-all bucket. Sidebar nav, basic ↔ advanced global toggle, search.
- **Quick Knobs** — each domain panel opens with sliders for the global
multipliers it owns (`--sf-space-scale` drives 45 tokens,
`--sf-shadow-strength` drives 14, `--sf-motion-scale` drives 13…).
- **Theme presets** + save/load custom slots in `localStorage`.
- **Undo / Redo** with `Ctrl+Z` / `Ctrl+Shift+Z`, 50-step history, single-step
bulk operations.
Expand Down Expand Up @@ -48,8 +65,8 @@ TokenRow reads to render a glow. Needs Chromium-only API (`computedStyleMap`)
or a fallback that scans `getComputedStyle` for known `--sf-*` properties.

### 2. Utility-class copy on relevant tokens *(½–1 day, blocked on framework)*
Automatic.css has "copy `.text-l`" / "copy `.bg-primary`" buttons next to
every value. Whether SLASHED ships utility classes alongside the tokens is a
Some framework configurators offer "copy `.text-l`" / "copy `.bg-primary`"
buttons next to every value. Whether SLASHED ships utility classes alongside the tokens is a
framework question — if yes, the configurator could add a "📋 utility"
button per row that copies the equivalent class name.

Expand Down
24 changes: 20 additions & 4 deletions configurator/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* State lives in the shared `ui` store; this component just wires the
* active domain to its panel.
*/
import { DOMAIN_BY_ID } from './lib/domains.js';
import { DOMAINS, DOMAIN_BY_ID, BASIC_DOMAIN_IDS } from './lib/domains.js';
import { ui, undo, redo, overrides } from './lib/store.svelte.js';
import { setProbeContext } from './lib/probeHost.js';
import Header from './components/Header.svelte';
Expand All @@ -30,10 +30,22 @@
import WcagPanel from './components/WcagPanel.svelte';
import ThemeGallery from './components/ThemeGallery.svelte';
import Cheatsheet from './components/Cheatsheet.svelte';
import Home from './components/Home.svelte';

const home = $derived(ui.mode === 'basic' && ui.domain === 'home');
const domain = $derived(DOMAIN_BY_ID.get(ui.domain) ?? DOMAIN_BY_ID.get('colors'));
const tool = $derived(domain?.tool ?? '');

// Keep the active domain valid for the current mode: Home exists only in
// Basic, and Basic hides the non-checklist domains (Motion, Effects, …).
$effect(() => {
if (ui.mode === 'advanced' && ui.domain === 'home') {
ui.domain = 'colors';
} else if (ui.mode === 'basic' && ui.domain !== 'home' && !BASIC_DOMAIN_IDS.includes(ui.domain)) {
ui.domain = 'home';
}
});

// Keep the contrast-probe host in sync with the user's cascade so every
// ContrastBadge resolves `var(...)`, `light-dark()` and `oklch(from ...)`
// expressions correctly. setProbeContext is internally idempotent.
Expand Down Expand Up @@ -77,8 +89,10 @@
} else if (e.key === 'a' || e.key === 'A') {
ui.mode = 'advanced';
} else if (e.key === '[' || e.key === ']') {
// Cycle non-tool domains.
const ids = ['colors', 'typography', 'spacing', 'layout', 'borders', 'shadows', 'motion', 'effects', 'wcag', 'themes', 'misc', 'cheatsheet'];
// Cycle the domains visible in the current mode.
const ids = ui.mode === 'basic'
? ['home', ...BASIC_DOMAIN_IDS]
: DOMAINS.map((d) => d.id);
const i = ids.indexOf(ui.domain);
if (i !== -1) {
const next = (i + (e.key === ']' ? 1 : -1) + ids.length) % ids.length;
Expand All @@ -96,7 +110,9 @@
<Sidebar />

<main class="main" aria-label="Configurator main">
{#if tool === 'wcag'}
{#if home}
<Home />
{:else if tool === 'wcag'}
<WcagPanel />
{:else if tool === 'themes'}
<ThemeGallery />
Expand Down
Loading