diff --git a/docs/superpowers/plans/2026-05-17-stylesheet-redesign.md b/docs/superpowers/plans/2026-05-17-stylesheet-redesign.md index 924ace5..2ec73e8 100644 --- a/docs/superpowers/plans/2026-05-17-stylesheet-redesign.md +++ b/docs/superpowers/plans/2026-05-17-stylesheet-redesign.md @@ -2,11 +2,11 @@ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. -**Goal:** Replace `l4d2web/static/css/*` (~1,436 LOC, 196 component classes) with a tiered design system: two-tier tokens, `@layer`-ordered cascade, budgeted component classes, five high-leverage macros, an in-app style guide, and the "system is closed" workflow rule enforced via `AGENTS.md`. +**Goal:** Replace the current ~1.4k LOC stylesheet (`l4d2web/static/css/*` + 196 component classes) with a from-scratch design system. New naming, new vocabulary, new structure — no preservation of historical names. Every template's class attributes get rewritten. -**Architecture:** Pure custom CSS (no framework base). Single entry stylesheet `main.css` declares `@layer reset, tokens, elements, layout, components, widgets, utilities;` and `@import`s the rest. Tokens split into `primitives.css` (raw palette) + `semantic.css` (role aliases + dark-mode branch). Component classes are CSS-only by default; macros under `templates/ui/` exist only for five composites where markup correctness is load-bearing (`field`, `modal`, `tabs`, `confirm_form`, `badge_state`). A new public route `/styleguide` is the canonical reference contributors and agents read before producing new UI. +**Architecture:** Pure custom CSS, single entry `main.css`, `@layer reset, tokens, elements, layout, components, widgets, utilities;`. Three-tier tokens (primitives → semantic → component-scoped). Naming convention: **parts use hyphenated child classes** (`.card-header`), **variant modifiers chain on the parent** (`.button.primary`). State on ARIA attributes (`[disabled]`, `[aria-busy]`, `[aria-selected]`, `[aria-invalid]`). Five macros under `templates/ui/` for high-leverage composites. Public `/styleguide` is the canonical reference. -**Tech Stack:** Plain CSS using modern features (`@layer`, `color-mix()`, custom properties). Jinja macros for the five composites. Flask blueprint for `/styleguide`. No Sass, no PostCSS, no bundler. +**Tech Stack:** Plain CSS (`@layer`, `color-mix()`, custom properties). Jinja macros. Flask blueprint. No Sass, no PostCSS, no bundler. **Spec:** `docs/superpowers/specs/2026-05-17-stylesheet-redesign-design.md` @@ -18,61 +18,136 @@ |---|---|---| | Create | `l4d2web/static/css/main.css` | Entry. Declares `@layer` order + `@import`s | | Create | `l4d2web/static/css/reset.css` | Modern reset (~30 LOC) | -| Create | `l4d2web/static/css/tokens/primitives.css` | Raw palette: grays, blue, red/green/amber, spacing, type scale, radii, shadows, motion, fonts | -| Create | `l4d2web/static/css/tokens/semantic.css` | Aliases: `--color-*`, `--space-*`, plus `[data-theme="dark"]` branch | -| Create | `l4d2web/static/css/elements.css` | Bare HTML defaults: `body`, `h1-h6`, `a`, `code`, `kbd`, `pre`, `hr`, form elements, tables | -| Rewrite | `l4d2web/static/css/layout.css` | `.container`, `.stack`, `.stack-h`, section spacing | -| Create | `l4d2web/static/css/components/button.css` | `.btn` + variants/sizes + states | -| Create | `l4d2web/static/css/components/field.css` | `.field` + label / hint / error | -| Create | `l4d2web/static/css/components/table.css` | `.table` | -| Create | `l4d2web/static/css/components/panel.css` | `.panel` + heading / body / footer | -| Create | `l4d2web/static/css/components/modal.css` | `.modal` (styles ``) + header / body / footer / close / `.modal-wide` | -| Create | `l4d2web/static/css/components/tabs.css` | `.tabs` + `.tab[aria-selected]` + `.tab-panel` | -| Create | `l4d2web/static/css/components/badge.css` | `.badge` + semantic + `.state-*` | -| Create | `l4d2web/static/css/components/nav.css` | `.site-header`, `.primary-nav`, `.account-nav`, `.brand` | -| Create | `l4d2web/static/css/components/dropdown.css` | `` helper + `.dropdown` | +| Create | `l4d2web/static/css/widgets/file-tree.css` | `.file-tree`, `.file-tree-item`, `.file-tree-toggle`, `.file-tree-children` | +| Create | `l4d2web/static/css/widgets/overlay-list.css` | `.overlay-list`, `.overlay-list-item`, `.overlay-list-handle`, `.overlay-list-meta` | +| Create | `l4d2web/static/css/widgets/console.css` | `.console`, `.console-line`, `.console-line.cmd`, `.console-line.out`, `.console-input` | +| Create | `l4d2web/static/css/widgets/editor.css` | `.editor` (CodeMirror wrapper) + relocated `--cm-*` tokens | +| Create | `l4d2web/static/css/widgets/logs.css` | Log-viewer styling, retargeted to new semantic tokens | +| Create | `l4d2web/static/css/widgets/server-status.css` | `.server-status`, `.server-status-state`, `.server-status-actions`, `.server-status-meta` | +| Create | `l4d2web/static/css/widgets/player-list.css` | `.player-list`, `.player-card`, `.player-card-avatar`, `.player-card-name`, `.player-card-meta` | +| Create | `l4d2web/static/css/utilities.css` | `.muted`, `.mono`, `.truncate`, `.visually-hidden` | | Create | `l4d2web/templates/ui/_field.html` | `ui.field`, `ui.checkbox`, `ui.select` | -| Create | `l4d2web/templates/ui/_modal.html` | `ui.modal` | +| Create | `l4d2web/templates/ui/_dialog.html` | `ui.dialog` | | Create | `l4d2web/templates/ui/_tabs.html` | `ui.tabs`, `ui.tab_panel` | | Create | `l4d2web/templates/ui/_confirm_form.html` | `ui.confirm_form` | -| Create | `l4d2web/templates/ui/_badge.html` | `ui.badge_state` | -| Modify | `l4d2web/templates/base.html` | Swap 5 `` tags for 1; add inline theme-init script | -| Create | `l4d2web/templates/styleguide.html` | Style guide page with every primitive + do/don't blocks | +| Create | `l4d2web/templates/ui/_tag.html` | `ui.tag`, `ui.lifecycle_tag` | +| Modify | `l4d2web/templates/base.html` | Swap 5 `` tags for 1; rewrite header markup; add inline theme-init script | +| Create | `l4d2web/templates/styleguide.html` | Style guide page + ✅/❌ blocks | | Create | `l4d2web/routes/styleguide_routes.py` | Public route `/styleguide` | -| Modify | `l4d2web/l4d2web/app.py` | Register styleguide blueprint; remove spike registration (commit 7) | -| Modify | `AGENTS.md` | Add "UI work" section codifying the workflow rule | -| Delete | `l4d2web/static/css/components.css` | (commit 7) | -| Delete | `l4d2web/static/css/tokens.css` | (commit 7) | -| Delete | `l4d2web/static/css/logs.css` (root) | (commit 7) — moved to `widgets/` | -| Delete | `l4d2web/static/css/console-autocomplete.css` (root) | (commit 7) — moved to `widgets/` | -| Delete | `l4d2web/static/css/editor.css` (root) | (commit 7) — moved to `widgets/` | -| Delete | `l4d2web/static/css/spike/` | (commit 7) — scaffolding | -| Delete | `l4d2web/templates/spike.html` | (commit 7) | -| Delete | `l4d2web/routes/spike_routes.py` | (commit 7) | -| Delete | `l4d2web/static/vendor/css/` | (commit 7) — only used by spike | +| Modify | `l4d2web/l4d2web/app.py` | Register styleguide blueprint; remove spike registration (Task 11) | +| Modify | `AGENTS.md` | Add "UI work" section codifying naming + workflow | +| Rewrite | All ~25 templates in `l4d2web/templates/` | Class attributes use new vocabulary; macros where applicable | +| Delete | `l4d2web/static/css/components.css` | (Task 11) | +| Delete | `l4d2web/static/css/tokens.css` | (Task 11) | +| Delete | `l4d2web/static/css/logs.css` (root) | (Task 11) — replaced by `widgets/logs.css` | +| Delete | `l4d2web/static/css/console-autocomplete.css` (root) | (Task 11) — folded into `widgets/console.css` | +| Delete | `l4d2web/static/css/editor.css` (root) | (Task 11) — replaced by `widgets/editor.css` | +| Delete | `l4d2web/static/css/spike/` | (Task 11) — scaffolding | +| Delete | `l4d2web/templates/spike.html` | (Task 11) | +| Delete | `l4d2web/routes/spike_routes.py` | (Task 11) | +| Delete | `l4d2web/static/vendor/css/` | (Task 11) — only used by spike | -## Reference: spike artifacts +## Token migration (used in Task 6 when relocating widget CSS) -The pre-validated CSS lives in **`l4d2web/static/css/spike/custom.css`**. It is organized exactly by the `@layer` blocks the production stylesheet uses. Each task below extracts a layer (or part of a layer) into its production file. +The old `tokens.css` and the new `tokens/semantic.css` overlap on color names but differ on spacing, radii, and a few colors. Apply this table when copying any rule out of the old `components.css` or root-level widget files: -Read the spike file as needed: +| Old | New | Notes | +|---|---|---| +| `--color-bg`, `--color-text`, `--color-muted`, `--color-border`, `--color-link`, `--color-primary`, `--color-danger`, `--color-warning`, `--color-success`, `--color-focus`, `--color-surface` | (same) | Unchanged. | +| `--color-surface-muted` | `--color-surface-2` | | +| `--color-border-muted` | `--color-border-soft` | | +| `--color-button-primary` | `--color-primary` | | +| `--color-button-danger` | `--color-danger` | | +| `--color-log-bg` | `--color-surface-2` | | +| `--color-log-text` | `--color-text` | | +| `--space-xs` | `--space-1` | 0.25rem | +| `--space-s` | `--space-2` | 0.5rem | +| `--space-m` | `--space-3` | 0.75rem | +| `--space-l` | `--space-4` | 1rem | +| `--space-xl` | `--space-5` | 1.5rem | +| `--space-2xl` | `--space-6` | 2rem | +| `--radius-base`, `--radius-s` | `--radius-1` | 0.25rem | +| `--radius-m` | `--radius-2` | 0.5rem | +| `--line` | `1px solid var(--color-border)` | Inline-expanded — `--line` is not in the new system | +| `--line-soft` | `1px solid var(--color-border-soft)` | Inline-expanded | +| `--font-mono` | (same) | Unchanged. | -```bash -cat l4d2web/l4d2web/static/css/spike/custom.css | sed -n '/^@layer reset/,/^}/p' -``` +CodeMirror tokens (`--cm-*`, `--syntax-*`, `--editor-rows`) move with the editor widget — see Task 6, Step 4b. -Or open it in an editor and copy the `@layer X { … }` blocks one at a time. +## Class-name migration (used in Task 10 for templates) -The spike file's `@layer` declarations and `@layer X { … }` wrappers stay in the file when copied into the production files; the `@layer` wrapper around each block is what gives the production cascade its ordering. +The new vocabulary is a full rename. The mapping is dense; agents executing Task 10 should treat this as a find-replace table. Old names on the left, new on the right; modifier-style chains are space-separated multi-class additions. -The spike template `l4d2web/templates/spike.html` is the reference for the style-guide page's markup vocabulary (every widget appears there at least once). +| Old class | New class | Notes | +|---|---|---| +| `.btn` | `.button` | All variants follow | +| `.btn-primary` | `.button primary` | Chained modifier — two classes | +| `.btn-secondary` | `.button` | Default (no modifier) is the secondary look | +| `.btn-danger` | `.button danger` | | +| `.btn-outline` | `.button outline` | | +| `.btn-link` | `.button link` | (also subsumes `.link-button` below) | +| `.btn-sm` | `.button small` | | +| `.button-secondary` (old short name) | `.button` | | +| `.danger-outline` | `.button danger outline` | Composes from existing axes | +| `.link-button` | `.button link` | | +| `.button-row` | `.row` | Generic horizontal flex (in layout) | +| `.panel`, `.card` | `.card` | One vocabulary | +| `.panel-heading`, `.card-heading` | `.card-header` | Match HTML semantics | +| `.panel-body` | `.card-body` | | +| `.panel-footer` | `.card-footer` | | +| `.modal` | `.dialog` | Matches `` element | +| `.modal-wide` | `.dialog wide` | Chained modifier | +| `.modal-header` | `.dialog-header` | | +| `.modal-body` | `.dialog-body` | | +| `.modal-footer` | `.dialog-footer` | | +| `.modal-close` | `.dialog-close` | | +| `.badge` | `.tag` | | +| `.badge-success` | `.tag success` | Chained | +| `.badge-warning` | `.tag warning` | | +| `.badge-danger` | `.tag danger` | | +| `.badge-muted` | `.tag muted` | | +| `.state-running` | `.tag success` | Use `ui.lifecycle_tag(state)` instead of hand-picking | +| `.state-stopped` | `.tag muted` | | +| `.state-unknown` | `.tag muted` | | +| `.state-transient` | `.tag warning` | | +| `.state-drift` | `.tag danger` | | +| `.site-header` | `.app-header` | | +| `.site-header-inner` | `.app-header-inner` | | +| `.primary-nav` | `.nav` | | +| `.account-nav` | `.account` | | +| `.brand` | (same) | | +| `.page-heading` | `.heading` | | +| `.page-footer-actions`, `.form-actions-inline`, `.button-row` | `.row` (or `.cluster` for wrap-friendly) | | +| `.sr-only` | `.visually-hidden` | | +| `.overlay-picker` | `.overlay-list` | | +| `.overlay-picker-list` | `.overlay-list` direct child `