docs(stylesheet): redesign from first principles
Throw away the historical naming. New vocabulary chosen for clarity and agentic-dev predictability: parts use hyphenated child classes (.card-header), variant modifiers chain on the parent (.button.primary), state stays on ARIA attributes. Variants compose via Tier-3 component-scoped tokens (--button-bg etc.) — .button.danger.outline is a real outlined-danger button with no combination rule. Adds toast, spinner, heading, app-header as first-class components. Renames panel→card, modal→dialog, badge→tag; collapses state-* into tag variants via ui.lifecycle_tag. Adds an explicit template-rewrite phase in the migration plan, since every template's class attributes change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
536c3384bf
commit
308fa4eb26
2 changed files with 1332 additions and 692 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -112,7 +112,7 @@ l4d2web/static/css/
|
|||
utilities.css
|
||||
```
|
||||
|
||||
### Tokens — two tiers
|
||||
### Tokens — three tiers
|
||||
|
||||
**Tier 1 (primitives)** in `tokens/primitives.css` is the raw palette: ~30
|
||||
custom properties for the grayscale ramp, brand blue ramp, semantic state
|
||||
|
|
@ -124,18 +124,29 @@ by semantic tokens.
|
|||
**Tier 2 (semantic)** in `tokens/semantic.css` aliases primitives by *role*:
|
||||
`--color-bg`, `--color-surface`, `--color-text`, `--color-muted`,
|
||||
`--color-border`, `--color-primary`, `--color-on-primary`, `--color-danger`,
|
||||
`--color-warning`, `--color-success`, `--color-focus`, plus `--space-s`,
|
||||
`--space-m`, `--space-l`, `--radius-s`, `--shadow-sm`, `--shadow-md`, etc.
|
||||
Dark mode lives here, in a `[data-theme="dark"]` selector — the primitives
|
||||
do not change between modes; the semantic aliases re-point at different
|
||||
primitives. This keeps dark mode in one file instead of duplicated inside
|
||||
a `@media (prefers-color-scheme: dark)` block.
|
||||
`--color-warning`, `--color-success`, `--color-info`, `--color-focus`, plus
|
||||
`--space-1` through `--space-7`, `--radius-1` / `-2` / `-full`, `--shadow-sm`
|
||||
/ `-md` / `-lg`, etc. Dark mode lives here, in a `[data-theme="dark"]`
|
||||
selector — the primitives do not change between modes; the semantic aliases
|
||||
re-point at different primitives. This keeps dark mode in one file instead
|
||||
of duplicated inside a `@media (prefers-color-scheme: dark)` block.
|
||||
|
||||
A rule the agent can follow mechanically: **components reference only
|
||||
semantic tokens (`var(--color-*)`, `var(--space-*)`, etc.). Raw hex codes
|
||||
appear only in `tokens/primitives.css`.** This rule is codified in
|
||||
`AGENTS.md` and is the simplest possible expression of the two-tier
|
||||
contract.
|
||||
**Tier 3 (component-scoped)** lives inside each component's CSS file, never
|
||||
exposed beyond it. Example: `components/button.css` declares
|
||||
`--button-bg`, `--button-fg`, `--button-border` on `.button`, and each variant
|
||||
(`.button.primary`, `.button.danger`) re-points those local properties at
|
||||
semantic tokens. Modifiers like `.button.outline` then read the inherited
|
||||
component-scoped values, which is what lets variants **compose** —
|
||||
`.button.danger.outline` is a real outlined-danger button without a single
|
||||
rule mentioning the combination. Same pattern for `.tag` (`--tag-bg`,
|
||||
`--tag-fg`, `--tag-border`) and `.toast` (`--toast-bg`, etc.).
|
||||
|
||||
A rule the agent can follow mechanically: **components reference Tier 2
|
||||
semantic tokens** (`var(--color-*)`, `var(--space-*)`, …). **Tier 3
|
||||
component-scoped tokens are private** to each component and never appear
|
||||
in other components' CSS. **Raw hex codes appear only in
|
||||
`tokens/primitives.css`.** These three rules are codified in `AGENTS.md`
|
||||
and constitute the entire token contract.
|
||||
|
||||
Dark mode is opted into by setting `data-theme="dark"` on the `<html>`
|
||||
element. The initial value comes from `prefers-color-scheme` via a small
|
||||
|
|
@ -146,47 +157,89 @@ this redesign — the redesign only ensures the mechanism is in place.
|
|||
|
||||
### Components — class-based, CSS-only
|
||||
|
||||
Vocabulary (the components layer in full):
|
||||
**Naming convention** (two rules, no exceptions):
|
||||
|
||||
- **`.btn`** with variants `.btn-primary`, `.btn-secondary`, `.btn-danger`,
|
||||
`.btn-outline`, `.btn-link`, plus `.btn-sm`. States: `[disabled]`,
|
||||
`[aria-busy="true"]` (loading). Loading state shows a spinner via a CSS
|
||||
animation; no extra markup required.
|
||||
- **`.button-row`** — flex+gap container for groups of buttons.
|
||||
- **`.field`** — wrapper containing `.field-label`, `input` / `select` /
|
||||
`textarea`, optional `.field-hint`, optional `.field-error`. Error state
|
||||
uses native `[aria-invalid="true"]` on the input — no separate
|
||||
`.field-input-error` class. Hint/error text is associated via
|
||||
`aria-describedby` (enforced by the macro; see below).
|
||||
- **`.inline-save`** — flex layout for the input + submit pair used several
|
||||
places today (`.inline-save` exists already; conventions preserved).
|
||||
- **`.table`** — bordered, slight zebra optional via `.table-striped`.
|
||||
- **`.panel`** — bordered card with optional `.panel-heading`, `.panel-body`,
|
||||
`.panel-footer`. Subsumes the current `.panel` / `.card` aliases.
|
||||
- **`.modal`** — styles `<dialog>`. Optional `.modal-wide` for the wider
|
||||
variant already used today. Header / body / footer subdivisions. The
|
||||
existing `modals.js` open/close machinery is unchanged.
|
||||
- **`.tabs`** with `.tab` and `.tab-panel`. State is encoded as
|
||||
`[aria-selected="true"]` on the tab — no `.tab-active` class. The existing
|
||||
`tabs.js` reads and writes those ARIA attributes already; no JS changes
|
||||
needed.
|
||||
- **`.badge`** with semantic variants (`.badge-success`, `.badge-warning`,
|
||||
`.badge-danger`, `.badge-muted`) and state variants
|
||||
(`.state-running`, `.state-stopped`, `.state-unknown`, `.state-transient`,
|
||||
`.state-drift`). The state variants exist today; preserved for templating
|
||||
continuity.
|
||||
- **`.site-header`** + **`.primary-nav`** + **`.account-nav`** + **`.brand`**
|
||||
— already present, restyled on the new token base.
|
||||
- **`.dropdown`** — styling around native `<select>` and a small custom
|
||||
menu pattern if needed.
|
||||
1. **Parts of a component use hyphenated child classes.** `.card-header`,
|
||||
`.field-hint`, `.dialog-body`. The part belongs to the parent; the
|
||||
hyphen makes the relationship visible.
|
||||
2. **Variant modifiers chain on the parent class.** `.button.primary`,
|
||||
`.tag.success`, `.button.outline`, `.dialog.wide`. A modifier alone
|
||||
is meaningless — it must be read together with the component class.
|
||||
|
||||
State as ARIA attributes, not modifier classes: `[disabled]`,
|
||||
State stays on ARIA attributes, not modifier classes: `[disabled]`,
|
||||
`[aria-busy="true"]`, `[aria-selected="true"]`, `[aria-invalid="true"]`.
|
||||
This avoids the class-vs-aria drift that produces broken accessibility.
|
||||
|
||||
Variant naming convention is uniform: `<component> <component>-<variant>
|
||||
<component>-<size>`. No exceptions. New variants must be added to the
|
||||
component's file *and* to the style guide entry in the same commit.
|
||||
Vocabulary (the components layer in full):
|
||||
|
||||
- **`.button`** with modifiers `.primary`, `.outline`, `.ghost`, `.danger`,
|
||||
`.link`, `.small`. Modifiers compose: `.button.danger.outline` is an
|
||||
outlined-danger button, no new rule required. States: `[disabled]`,
|
||||
`[aria-busy="true"]` (loading; spinner via CSS animation, no extra markup).
|
||||
- **`.field`** — wrapper containing `.field-label`, `input` / `select` /
|
||||
`textarea`, optional `.field-hint`, optional `.field-error`. Error state
|
||||
uses native `[aria-invalid="true"]` on the input. Hint/error text wired
|
||||
to the input via `aria-describedby` (enforced by the `ui.field` macro).
|
||||
`.field-checkbox` handles the checkbox-with-label layout.
|
||||
- **`.card`** — bordered surface with `.card-header`, `.card-body`,
|
||||
`.card-footer`. Replaces the historically duplicated `.panel` / `.card`
|
||||
pair.
|
||||
- **`.table`** — data table. Modifier `.striped` for zebra rows.
|
||||
- **`.dialog`** — styles `<dialog>`. Parts: `.dialog-header`, `.dialog-body`,
|
||||
`.dialog-footer`, `.dialog-close`. Modifier `.wide` for the wider variant
|
||||
used by editor-style modals. The existing `modals.js` open/close machinery
|
||||
is unchanged; the close button keeps its `data-inline-modal-close`
|
||||
attribute (the JS hook).
|
||||
- **`.tabs`** with parts `.tab` and `.tab-panel`. State is `[aria-selected]`
|
||||
on each `.tab`; the existing `tabs.js` reads/writes the attribute.
|
||||
- **`.tag`** with modifiers `.success`, `.warning`, `.danger`, `.info`,
|
||||
`.muted`. Replaces the current `.badge` vocabulary AND the project-specific
|
||||
`.state-running` / `.state-stopped` / `.state-unknown` / `.state-transient`
|
||||
/ `.state-drift` classes — lifecycle states map to semantic tag variants
|
||||
via the `ui.lifecycle_tag(state)` macro (`"running"` → `.tag.success`,
|
||||
`"stopped"` → `.tag.muted`, `"unknown"` → `.tag.muted`, `"starting"` /
|
||||
`"stopping"` / `"resetting"` etc. → `.tag.warning`, `"drift"` →
|
||||
`.tag.danger`). One vocabulary; one mapping rule.
|
||||
- **`.toast`** (new) — flash-message notification, top-right positioned.
|
||||
Modifiers `.success`, `.warning`, `.danger`. Part `.toast-close`. Fills
|
||||
a real gap — flash messages currently improvise.
|
||||
- **`.spinner`** (new) — standalone loading indicator. Modifier `.small`.
|
||||
Used independently of `[aria-busy]`-on-buttons.
|
||||
- **`.app-header`** with parts `.app-header-inner`, `.brand`, `.nav`,
|
||||
`.account` — the top-of-page banner. Replaces `.site-header` +
|
||||
`.primary-nav` + `.account-nav` (one vocabulary instead of three).
|
||||
- **`.heading`** with part `.heading-actions` — formal page-title pattern
|
||||
(h1 + actions row). Replaces the current ad-hoc `.page-heading`.
|
||||
- **`.dropdown`** — small helper for native `<select>` + any future custom
|
||||
menu pattern.
|
||||
|
||||
**Variant composition pattern** (the mechanism behind chained modifiers):
|
||||
|
||||
Each component declares Tier-3 component-scoped tokens and consumes them.
|
||||
Variants re-point those tokens; modifiers can read inherited values. A
|
||||
trimmed `components/button.css` example:
|
||||
|
||||
```css
|
||||
.button {
|
||||
--button-bg: var(--color-surface);
|
||||
--button-fg: var(--color-text);
|
||||
--button-border: var(--color-border);
|
||||
background: var(--button-bg);
|
||||
color: var(--button-fg);
|
||||
border: 1px solid var(--button-border);
|
||||
/* …padding, radius, transitions… */
|
||||
}
|
||||
.button.primary { --button-bg: var(--color-primary); --button-fg: var(--color-on-primary); --button-border: var(--color-primary); }
|
||||
.button.danger { --button-bg: var(--color-danger); --button-fg: var(--color-on-danger); --button-border: var(--color-danger); }
|
||||
.button.outline { --button-bg: transparent; --button-fg: var(--button-border); }
|
||||
.button.ghost { --button-bg: transparent; --button-fg: var(--color-text); --button-border: transparent; }
|
||||
.button.link { --button-bg: transparent; --button-border: transparent; text-decoration: underline; color: var(--color-link); }
|
||||
.button.small { padding: 0.25rem 0.5rem; font-size: var(--text-sm); }
|
||||
```
|
||||
|
||||
`.button.danger.outline` works because `.outline` is declared *after*
|
||||
`.danger` and reads the inherited `--button-border` (which `.danger`
|
||||
just set). The same pattern is used for `.tag` and `.toast`.
|
||||
|
||||
### Macros — five high-leverage primitives
|
||||
|
||||
|
|
@ -194,13 +247,13 @@ A new directory `l4d2web/templates/ui/` adds Jinja macros for primitives
|
|||
where markup correctness is load-bearing — i.e., where hand-assembling
|
||||
gets accessibility wrong easily:
|
||||
|
||||
| Macro file | Macros | Why |
|
||||
|-------------------------|----------------------------------------------|---------------------------------------------------------------------------------------------------------------|
|
||||
| `ui/_field.html` | `ui.field`, `ui.checkbox`, `ui.select` | Owns `<label for>`/`<input id>` pairing, `aria-describedby` wiring for hint+error, `aria-invalid` on error |
|
||||
| `ui/_modal.html` | `ui.modal` | Owns the `<dialog>` + `modal-header`/`modal-body`/`modal-footer` structure and the close-button + JS hooks |
|
||||
| `ui/_tabs.html` | `ui.tabs`, `ui.tab_panel` | Owns the `role="tablist"` / `role="tab"` / `role="tabpanel"` + `aria-controls` / `aria-selected` wiring |
|
||||
| `ui/_confirm_form.html` | `ui.confirm_form` | Owns CSRF token, POST action, and standard button row for destructive actions |
|
||||
| `ui/_badge.html` | `ui.badge_state(state)` | Maps a server-state string to the correct `state-*` class — encapsulates a real source of inconsistency today |
|
||||
| Macro file | Macros | Why |
|
||||
|-------------------------|----------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `ui/_field.html` | `ui.field`, `ui.checkbox`, `ui.select` | Owns `<label for>`/`<input id>` pairing, `aria-describedby` wiring for hint+error, `aria-invalid` on error |
|
||||
| `ui/_dialog.html` | `ui.dialog` | Owns the `<dialog>` + `.dialog-header`/`.dialog-body`/`.dialog-footer` structure and the `data-inline-modal-close` JS hook |
|
||||
| `ui/_tabs.html` | `ui.tabs`, `ui.tab_panel` | Owns the `role="tablist"` / `role="tab"` / `role="tabpanel"` + `aria-controls` / `aria-selected` wiring |
|
||||
| `ui/_confirm_form.html` | `ui.confirm_form` | Owns CSRF token, POST action, and standard button row for destructive actions |
|
||||
| `ui/_tag.html` | `ui.tag`, `ui.lifecycle_tag(state)` | Maps a server-lifecycle state string (`"running"`, `"starting"`, `"drift"`, …) to the right `.tag.<variant>` — one mapping rule total |
|
||||
|
||||
CSS-only is the default for trivial primitives — `<button class="btn
|
||||
btn-primary">Save</button>` is its own canonical form and does not need a
|
||||
|
|
@ -301,14 +354,19 @@ Before adding any UI markup:
|
|||
with a canonical example.
|
||||
2. If your change needs a widget that isn't in the style guide, add it to
|
||||
the system FIRST:
|
||||
- CSS goes in `components/<name>.css` or `widgets/<name>.css`
|
||||
- Style guide entry with rendered example + source + at least one do/don't
|
||||
- If composite/a11y-bearing, add a macro in `templates/ui/_<name>.html`
|
||||
- CSS goes in `components/<name>.css` (generic) or `widgets/<name>.css` (project-specific)
|
||||
- Style guide entry with rendered example + source + at least one ✅ DO
|
||||
- If composite or a11y-load-bearing, add a macro in `templates/ui/_<name>.html`
|
||||
- Only then use the widget on the page
|
||||
3. Never inline `style="…"` attributes.
|
||||
4. Never invent class names outside the system.
|
||||
5. Use only `var(--color-*)`, `var(--space-*)`, etc. in component CSS —
|
||||
raw hex codes appear only in `tokens/primitives.css`.
|
||||
3. Naming convention (no exceptions):
|
||||
- **Parts of a component use hyphenated child classes** — `.card-header`, `.field-hint`, `.dialog-body`
|
||||
- **Variant modifiers chain on the parent class** — `.button.primary`, `.tag.success`, `.dialog.wide`
|
||||
- **State stays on ARIA attributes**, not modifier classes — `[disabled]`, `[aria-busy="true"]`, `[aria-selected="true"]`, `[aria-invalid="true"]`
|
||||
4. Never inline `style="…"` attributes. Never invent class names outside the system.
|
||||
5. Token usage:
|
||||
- Component CSS references **only** Tier-2 semantic tokens (`var(--color-*)`, `var(--space-*)`, …)
|
||||
- Tier-3 component-scoped tokens (`--button-bg`, `--tag-fg`, etc.) are private to each component and never appear in other components' CSS
|
||||
- Raw hex codes appear **only** in `tokens/primitives.css`
|
||||
```
|
||||
|
||||
## What the spike validated
|
||||
|
|
@ -337,9 +395,14 @@ Findings:
|
|||
writing new component CSS doesn't have to think about specificity at all.
|
||||
|
||||
Pure custom won on the criterion you weighted highest ("bad code feel
|
||||
leads to incoherent styling later"). The spike's `custom.css` is the seed
|
||||
for the production stylesheet; it is not the final form, but the layer
|
||||
ordering, the token shape, and the component vocabulary are validated.
|
||||
leads to incoherent styling later"). The spike validated the
|
||||
**architecture** — `@layer` cascade, color-mix() in production browsers,
|
||||
the light/dark theme switching via `data-theme`, the aesthetic direction.
|
||||
The **vocabulary used in the spike (`.btn`, `.panel`, `.modal`, `.badge`,
|
||||
`.state-*`, …) is NOT carried forward** — the rewrite uses a redesigned
|
||||
vocabulary (`.button` + chained modifiers, `.card`, `.dialog`, `.tag`, …)
|
||||
chosen from first principles for the agentic-dev goal. The spike artifacts
|
||||
get deleted in the cleanup commit (see Migration plan).
|
||||
|
||||
## Migration plan
|
||||
|
||||
|
|
@ -356,32 +419,45 @@ may be visible mid-series, which is fine):
|
|||
`<link rel="stylesheet" href=".../main.css">`. Old `components.css`
|
||||
no longer loads — pages look bare. Intentional and visible.
|
||||
|
||||
3. **Core components** — add `components/button.css`,
|
||||
`components/panel.css`, `components/table.css`, `components/badge.css`,
|
||||
`components/nav.css`. Half the surface starts looking right again.
|
||||
3. **Core components** — add `components/button.css`, `components/card.css`,
|
||||
`components/table.css`, `components/tag.css`, `components/app-header.css`,
|
||||
`components/heading.css`. Half the surface starts looking right again
|
||||
(where templates use the new class names; many still use the old ones
|
||||
until step 7).
|
||||
|
||||
4. **Composite components + macros** — add `components/modal.css`,
|
||||
4. **Composite components + macros** — add `components/dialog.css`,
|
||||
`components/tabs.css`, `components/field.css`, `components/dropdown.css`,
|
||||
plus `templates/ui/_field.html`, `_modal.html`, `_tabs.html`,
|
||||
`_confirm_form.html`, `_badge.html`. Update templates that use these
|
||||
primitives to invoke the macros where appropriate.
|
||||
`components/toast.css`, `components/spinner.css`, plus
|
||||
`templates/ui/_field.html`, `_dialog.html`, `_tabs.html`,
|
||||
`_confirm_form.html`, `_tag.html`. The macros are available for use but
|
||||
templates aren't yet calling them (step 7 does that).
|
||||
|
||||
5. **Project widgets** — move file-tree, overlay-picker,
|
||||
console-autocomplete, editor, logs, live-state into `widgets/<name>.css`.
|
||||
This is largely content-preserving — rename token references to the
|
||||
semantic-token namespace, otherwise the rules carry over.
|
||||
5. **Project widgets** — move file-tree, overlay-picker, console-autocomplete,
|
||||
editor, logs, live-state into `widgets/<name>.css` with renamed class
|
||||
names (`.overlay-picker` → `.overlay-list`, `.file-tree-row` →
|
||||
`.file-tree-item`, etc. — full mapping in the implementation plan).
|
||||
Rename token references to the new semantic-token namespace.
|
||||
|
||||
6. **Style guide** — add `routes/styleguide_routes.py`,
|
||||
`templates/styleguide.html` with every primitive + do/don't blocks,
|
||||
token reference table, dark-mode toggle button. Update `AGENTS.md`
|
||||
with the UI workflow section.
|
||||
`templates/styleguide.html` with every primitive + ✅ DO / ❌ DON'T
|
||||
blocks, token reference table, dark-mode toggle button. Update
|
||||
`AGENTS.md` with the UI workflow section.
|
||||
|
||||
7. **Cleanup** — delete the old `components.css`, the old `tokens.css`,
|
||||
the old root-level `logs.css` / `console-autocomplete.css` / `editor.css`
|
||||
(now under `widgets/`). Delete the spike artifacts in the same commit:
|
||||
`l4d2web/static/css/spike/`, `l4d2web/templates/spike.html`,
|
||||
`l4d2web/routes/spike_routes.py`, `l4d2web/static/vendor/css/`, and the
|
||||
`if spike_enabled(): app.register_blueprint(spike_bp)` block in
|
||||
7. **Template rewrite** — walk every template in `l4d2web/templates/`
|
||||
and update class attributes to the new vocabulary. This is a large but
|
||||
mechanical phase; the implementation plan splits it into reviewable
|
||||
chunks (one per template-family: page templates, partial templates,
|
||||
server-detail's complex cluster). Templates also switch to the new
|
||||
macros (`ui.field`, `ui.dialog`, `ui.lifecycle_tag`, etc.) where
|
||||
applicable. After this step, no template still references the old
|
||||
vocabulary.
|
||||
|
||||
8. **Cleanup** — delete the old `components.css`, the old `tokens.css`,
|
||||
the old root-level `logs.css` / `console-autocomplete.css` /
|
||||
`editor.css` (now under `widgets/`). Delete the spike artifacts in
|
||||
the same commit: `l4d2web/static/css/spike/`, `l4d2web/templates/spike.html`,
|
||||
`l4d2web/routes/spike_routes.py`, `l4d2web/static/vendor/css/`, and
|
||||
the `if spike_enabled(): app.register_blueprint(spike_bp)` block in
|
||||
`app.py` (plus the `spike_routes` import). Run the existing Chromium
|
||||
e2e suite and the pytest suite. Walk the major pages (dashboard,
|
||||
servers, server detail, overlay detail, blueprint detail, profile,
|
||||
|
|
@ -412,20 +488,39 @@ revisit them.
|
|||
- **No build step.** No Sass, no PostCSS, no bundler. Modern CSS
|
||||
(`@layer`, `color-mix()`, custom properties) suffices and matches the
|
||||
project's zero-build philosophy.
|
||||
- **Two-tier tokens, not three.** Three-tier (primitive / semantic /
|
||||
component-scoped, as in Material 3 or Adobe Spectrum) is over-engineered
|
||||
for a project this size. Two tiers buy clean dark mode and unambiguous
|
||||
agent rules without the indirection cost.
|
||||
- **Three-tier tokens** (primitives → semantic → component-scoped). The
|
||||
initial draft proposed two tiers; the third (component-scoped tokens
|
||||
like `--button-bg`) was added when we adopted chained-modifier variants
|
||||
(`.button.danger.outline`), because composing variants cleanly requires
|
||||
each variant to set local CSS custom properties that later modifiers
|
||||
can read. The third tier is *private to each component* — it doesn't
|
||||
bloat the global token namespace.
|
||||
- **Naming convention chosen from first principles.** The old vocabulary
|
||||
(`.btn` / `.btn-primary` / `.panel` / `.modal` / `.badge` / `.state-*` /
|
||||
`.site-header` / `.primary-nav` / `.link-button` / `.danger-outline` /
|
||||
`.sr-only` / `.overlay-picker` / …) was historically grown and
|
||||
inconsistent. The new vocabulary uses two rules: **parts are hyphenated
|
||||
child classes** (`.card-header`), **variants chain on the parent**
|
||||
(`.button.primary`). State stays on ARIA attributes. The agent learns
|
||||
the rules once and predicts the rest.
|
||||
- **`@layer` for cascade.** Underused, well-supported since 2022, exactly
|
||||
fits the "layered design system" mental model. Replaces specificity
|
||||
hacks with declared layer ordering.
|
||||
- **Macros for five high-leverage primitives, CSS-only for the rest.**
|
||||
The macro tier exists where forgetting a piece of markup silently
|
||||
breaks accessibility. Buttons and panels don't qualify; fields and
|
||||
modals do.
|
||||
breaks accessibility. Buttons and tags don't qualify; fields and
|
||||
dialogs do. The fifth, `ui.lifecycle_tag`, encapsulates the
|
||||
state-string → tag-variant mapping in one place.
|
||||
- **Style guide as enforcement, not just documentation.** Tied to a
|
||||
workflow rule ("the system is closed"), referenced in `AGENTS.md`,
|
||||
with do/don't anti-pattern blocks. Most leveraged change for the
|
||||
with ✅ DO / ❌ DON'T blocks. Most leveraged change for the
|
||||
agentic-dev goal.
|
||||
- **Spike artifacts kept until cleanup commit.** They seeded the decision;
|
||||
they go away when the production system lands.
|
||||
- **Template rewrite as a discrete migration phase.** The new vocabulary
|
||||
means every template's class attributes change; the implementation
|
||||
plan dedicates a phase to this mechanical-but-extensive work rather
|
||||
than mixing it into the CSS commits.
|
||||
- **Spike artifacts kept until cleanup commit.** They seeded the
|
||||
architecture decision; they go away when the production system lands.
|
||||
The spike used the *old* vocabulary — only its `@layer` structure +
|
||||
token shape + dark-mode mechanism + aesthetic direction carried
|
||||
forward.
|
||||
|
|
|
|||
Loading…
Reference in a new issue