diff --git a/.planning/phases/13-design-system-foundation/13-RESEARCH.md b/.planning/phases/13-design-system-foundation/13-RESEARCH.md
new file mode 100644
index 0000000..72fbfb9
--- /dev/null
+++ b/.planning/phases/13-design-system-foundation/13-RESEARCH.md
@@ -0,0 +1,717 @@
+# Phase 13: Design System Foundation - Research
+
+**Researched:** 2026-05-16
+**Domain:** Go + templ CSS design system port (tokens, component CSS, variant enums, dev catalog)
+**Confidence:** HIGH
+
+## Summary
+
+Phase 13 is a CSS and Go enum porting task. All eleven templ component files already exist in `backend/internal/web/ui/`. No new templ components are being authored from scratch; the work is:
+
+1. Replace `backend/internal/web/ui/base.css` (28-line stub) with the 223-line token vocabulary from `go-backend/internal/web/ui/base.css` verbatim.
+2. Add ten missing CSS files (`modal.css`, `input.css`, `textarea.css`, `select.css`, `table.css`, `empty-state.css`, `icon-button.css`, `form-field.css`, `spacing.css`) ported from go-backend with page-level selectors stripped.
+3. Update `backend/tailwind.input.css` to import all thirteen CSS files.
+4. **Migrate `ButtonClass()` from the compound-class pattern** (`ui-button-solid-default-md`) **to go-backend's multi-class pattern** (`ui-button ui-button-solid ui-button-default ui-button-md`) — and update all call sites in templates that use the old compound class directly as string literals.
+5. Add `ButtonVariantGhost` and `BadgeVariantPrimary` to `variants.go` (with normalizer updates).
+6. Align Props structs with go-backend (Button needs `Icon string`, Card needs `Header/Body/Footer templ.Component` instead of children, several components need `Disabled bool`).
+7. Add helper functions for components that currently lack them (`InputClass`, `TextareaClass`, `ModalClass`, etc.) — go-backend keeps these unexported; backend uses exported names.
+8. Port `select_helpers.go` from go-backend (select component has significant helper logic).
+9. Add a `UIIcon` templ function and a `SpaceX`/`SpaceY` component with `SpacingStep` enum.
+10. Create `backend/internal/web/ui/catalog/` with the single-page `catalog.templ` + handler registered under `//go:build catalog`.
+11. Extend `ui_test.go` to cover all eleven component types.
+
+The critical migration risk is the **button class pattern change**: existing templates (`planning.templ`, `tasks.templ`, `events.templ`, `etapes.templ`) use compound class strings like `ui-button-solid-default-md` as raw HTML attribute values — these are NOT routed through `ButtonClass()`, so replacing `button.css` will silently break those templates unless the old compound selectors are kept or the templates are migrated. The plan must make this explicit.
+
+**Primary recommendation:** Adopt go-backend's multi-class pattern in Phase 13. Replace `ButtonClass()` output, replace `button.css` entirely with go-backend's multi-class selectors, AND migrate all hardcoded compound class strings in templates to the new multi-class format. Do this in one atomic wave.
+
+---
+
+
+## User Constraints (from CONTEXT.md)
+
+### Locked Decisions
+
+**Token Values**
+- D-T01: Port `go-backend/internal/web/ui/base.css` token values verbatim — no adjustments to brand palette, typography, or spacing scale before Phase 13 lands
+- D-T02: Port ALL tokens — full 223-line vocabulary — not just the subset referenced by current component CSS files
+- D-T03: Replace `backend/internal/web/ui/base.css` entirely (do not merge); go-backend version already includes the same box-sizing/reset foundation
+- D-T04: Phase 13 ships the token vocabulary as CSS custom properties; wiring existing component CSS to consume `var(--…)` tokens is Phase 14–17 work — component CSS may still use hardcoded values in Phase 13
+
+**app.css Scope**
+- D-A01: `app.css` (the 1896-line shell) is deferred to Phases 14–17; Phase 13 ships component CSS only
+- D-A02: `backend/tailwind.input.css` imports `base.css` + all component CSS files; no app shell import in Phase 13
+- D-A03: When porting component CSS, strip page-level selectors (`body`, `:root` overrides, `.app-layout`); component CSS stays scoped to `.ui-*` class selectors only
+- D-A04: Catalog page uses a hand-built fake shell (sidebar column + content area) for visual context, even though the real `app.css` shell is not yet in backend
+
+**Catalog Route**
+- D-C01: Catalog route is guarded by a Go build tag (`//go:build catalog`); production binary never includes the route — zero attack surface
+- D-C02: Catalog page layout: sidebar/anchor nav with section links (`#buttons`, `#badges`, `#cards`, etc.) matching the fake shell layout
+- D-C03: Each component section shows rendered variants + the templ call used to produce each variant (rendered + source, not rendered-only)
+- D-C04: Static HTML only — no JavaScript interaction; all variants pre-rendered at page load
+
+**Component API**
+- D-CA01: Add `ButtonVariantGhost` to the `ButtonVariant` enum
+- D-CA02: Add `BadgeVariantPrimary` to the `BadgeVariant` enum
+- D-CA03: Review and align each component's Go-side Props struct with the go-backend equivalent — fill in missing fields
+- D-CA04: Extend `ui_test.go` to cover class-generation logic for all 9+ component types
+
+### Claude's Discretion
+- Exact migration of CSS variable names: if go-backend component CSS uses slightly different class names (`.ui-button` vs `.btn`), match go-backend exactly
+- Exact fake-shell HTML in the catalog: should mimic the sidebar+main-content layout visible in go-backend's catalog but is not bound to pixel-perfect reproduction
+- Order of anchor sections in the catalog sidebar: alphabetical by component name (per UI-SPEC)
+- Exact test coverage depth: at minimum one test per component type for the happy path; error/edge cases at planner discretion
+
+### Deferred Ideas (OUT OF SCOPE)
+- `app.css` shell port (sidebar layout, topbar, nav shell, content area) — deferred to Phases 14–17
+- Wiring component CSS to consume `var(--…)` tokens — deferred to Phases 14–17
+- Dark mode tokens / `prefers-color-scheme` counterparts — Future requirement DARK-01
+- Responsive sidebar and mobile layout — Future requirements RESP-01..03
+
+
+
+## Phase Requirements
+
+| ID | Description | Research Support |
+|----|-------------|------------------|
+| DS-01 | CSS design tokens (colors, spacing, typography, shadows, gradients) defined in `backend/internal/web/ui/base.css` matching the go-backend token vocabulary | Replace 28-line stub with go-backend's 223-line `:root` block verbatim |
+| DS-02 | Button component with primary, secondary, ghost, and danger variants | Add `ButtonVariantGhost` to variants.go; port go-backend button.css multi-class pattern; update existing templates |
+| DS-03 | Input, Textarea, and Select form field components | Port input.css, textarea.css, select.css; add select_helpers.go; align Props structs with go-backend |
+| DS-04 | Card component used across tablo list, detail, and content views | Align Card Props struct (Header/Body/Footer templ.Component); port go-backend card.css |
+| DS-05 | Badge component supports semantic tones (primary, warning, success, danger) | Add `BadgeVariantPrimary` to variants.go; port go-backend badge.css (pill shape differs from current) |
+| DS-06 | Modal component available for create/edit dialogs | Port go-backend modal.css (no modal CSS currently in backend); add ModalProps templ |
+| DS-07 | Empty-state component available for zero-data views | Port go-backend empty-state.css; add EmptyStateProps templ |
+| DS-08 | Table component available for list views | Port go-backend table.css; add TableProps templ |
+| DS-09 | Icon-button component replaces inline icon-button patterns | Add IconButtonVariant/Tone enums; port icon-button.css; add UIIcon helper; add SpaceX/SpaceY |
+
+
+---
+
+## Architectural Responsibility Map
+
+| Capability | Primary Tier | Secondary Tier | Rationale |
+|------------|-------------|----------------|-----------|
+| CSS token vocabulary | Static / Build | — | Compiled into `static/tailwind.css` by Tailwind CLI at `just generate` |
+| Component CSS rules | Static / Build | — | Same pipeline as tokens |
+| Go Props structs + variant enums | Backend Server | — | Compiled into the Go binary |
+| templ components (HTML structure) | Backend Server | — | Rendered server-side, no client JS for structure |
+| Select open/close toggle JS | Browser | — | Minimal inline script in select.templ manages `is-open` class; no framework |
+| Catalog route handler | Backend Server | — | Build-tag gated; registered in router only under `-tags catalog` |
+| Tailwind CSS compilation | Build toolchain | — | `./bin/tailwindcss -i tailwind.input.css -o static/tailwind.css` |
+
+---
+
+## Standard Stack
+
+### Core (already in backend)
+
+| Tool | Version | Purpose | Status |
+|------|---------|---------|--------|
+| Go templ | v0.3.1020 | Server-side HTML component authoring | Pinned in justfile |
+| Tailwind CSS v4 standalone | v4.0.0 | CSS compilation from `.templ` + `.go` source scanning | Pinned in justfile |
+| chi router | v5.2.5 | HTTP routing; catalog route registered with build tag | In go.mod |
+
+### No New Dependencies
+
+Phase 13 adds zero new Go modules or npm packages. All work is:
+- CSS file creation/replacement
+- Go source file edits (`variants.go`, Props structs)
+- New Go files (`select_helpers.go`, `catalog/catalog.templ`, `catalog/catalog.go`, `catalog/examples.go`)
+
+[VERIFIED: codebase inspection] — no missing toolchain dependencies
+
+---
+
+## Architecture Patterns
+
+### System Architecture Diagram
+
+```
+backend/tailwind.input.css
+ └── @import base.css [223-line token vocabulary — DS-01]
+ └── @import button.css [multi-class pattern — DS-02]
+ └── @import badge.css [DS-05]
+ └── @import card.css [DS-04]
+ └── @import input.css [DS-03 — NEW]
+ └── @import textarea.css [DS-03 — NEW]
+ └── @import select.css [DS-03 — NEW]
+ └── @import modal.css [DS-06 — NEW]
+ └── @import empty-state.css [DS-07 — NEW]
+ └── @import table.css [DS-08 — NEW]
+ └── @import icon-button.css [DS-09 — NEW]
+ └── @import form-field.css [DS-03 wrapper — NEW]
+ └── @import spacing.css [DS-09 utility — NEW]
+ |
+ v
+ ./bin/tailwindcss → static/tailwind.css
+ |
+ v
+ Go template
+
+backend/internal/web/ui/
+ variants.go [ButtonVariant, BadgeVariant, IconButtonVariant, SpacingStep enums]
+ helpers.go [mergeAttrs — unchanged]
+ tokens.go [semantic token constants — minor additions]
+ button.templ [ButtonProps — add Icon field]
+ badge.templ [BadgeProps — unchanged API, no CSS class change needed]
+ card.templ [CardProps — BREAKING: switch from children to Header/Body/Footer fields]
+ input.templ [InputProps — add Disabled, Required fields]
+ textarea.templ [TextareaProps — add Disabled, Required fields — NEW FILE]
+ select.templ [SelectProps — complex: multi-value, placeholder — NEW FILE]
+ select_helpers.go [selectPlaceholder, selectOptionSelected, etc. — NEW FILE]
+ modal.templ [ModalProps — Title/Body/Actions — NEW FILE]
+ empty_state.templ [EmptyStateProps — NEW FILE]
+ table.templ [TableProps — Head/Body — NEW FILE]
+ icon_button.templ [IconButtonProps + UIIcon helper — NEW FILE]
+ space.templ [SpaceX/SpaceY — NEW FILE]
+ form_field.templ [FormFieldProps — NEW FILE]
+ ui_test.go [extended to cover all 11+ components]
+
+ catalog/
+ catalog.templ [single-page layout with sidebar + anchor nav — NEW]
+ catalog.go [//go:build catalog handler + router registration — NEW]
+ examples.go [typed Example structs, one per component variant — NEW]
+```
+
+### Multi-Class Button Pattern (Critical Migration)
+
+[VERIFIED: codebase inspection] The current backend generates compound classes:
+
+```go
+// Current backend/internal/web/ui/variants.go
+func ButtonClass(variant ButtonVariant, tone ButtonTone, size Size) string {
+ return "ui-button ui-button-" + string(t) + "-" + string(v) + "-" + string(s)
+ // Produces: "ui-button ui-button-solid-default-md"
+}
+```
+
+The go-backend generates separate classes:
+
+```go
+// go-backend/internal/web/ui/variants.go
+func buttonClass(variant ButtonVariant, tone ButtonTone, size Size) string {
+ return "ui-button ui-button-" + string(normalizedButtonTone(tone)) +
+ " ui-button-" + string(normalizedButtonVariant(variant)) +
+ " ui-button-" + string(normalizedSize(size))
+ // Produces: "ui-button ui-button-solid ui-button-default ui-button-md"
+}
+```
+
+Go-backend's `button.css` uses compound CSS selectors that match the space-separated output:
+
+```css
+/* go-backend pattern */
+.ui-button-solid.ui-button-default {
+ background: var(--color-brand-primary);
+}
+.ui-button-solid.ui-button-default:hover {
+ background: var(--color-brand-primary-hover);
+}
+```
+
+**Migration scope for template hardcodes:** These files use the OLD compound class pattern directly as HTML attribute strings (not through `ButtonClass()`):
+
+- `backend/templates/planning.templ` — uses `ui-button-soft-neutral-md`, `ui-button-solid-default-md`
+- `backend/templates/tasks.templ` — uses `ui-button-soft-neutral-md`, `ui-button-soft-danger-md`
+- `backend/templates/events.templ` — uses `ui-button-soft-neutral-md`
+- `backend/templates/etapes.templ` — uses `ui-button-soft-neutral-md`, `ui-button-soft-danger-md`
+
+[VERIFIED: codebase grep] Each occurrence must be updated to the multi-class pattern:
+- `ui-button-solid-default-md` → `ui-button ui-button-solid ui-button-default ui-button-md`
+- `ui-button-soft-neutral-md` → `ui-button ui-button-soft ui-button-neutral ui-button-md`
+- `ui-button-soft-danger-md` → `ui-button ui-button-soft ui-button-danger ui-button-md`
+
+Also: existing tests in `ui_test.go` assert the compound string `"ui-button ui-button-solid-default-md"` — these must be updated to the multi-class assertion.
+
+### Card Props API Change (Breaking)
+
+[VERIFIED: codebase inspection] The current backend `Card` accepts children via `templ.WithChildren`:
+
+```go
+// Current backend — Card(attrs templ.Attributes) — children via context
+templ Card(attrs templ.Attributes) {
+
+ { children... }
+
+}
+// Called as: @ui.Card(nil) {
content
}
+```
+
+The go-backend uses typed `Header/Body/Footer templ.Component` fields:
+
+```go
+// go-backend — Card(props CardProps)
+type CardProps struct {
+ Header templ.Component
+ Body templ.Component
+ Footer templ.Component
+}
+// Called as: @ui.Card(ui.CardProps{Body: myBodyComponent})
+```
+
+**Call-site impact:** The existing test `TestCard_RendersChildren` uses `templ.WithChildren` and asserts `` block directly in the templ component. This is the approved pattern for the select (per CONTEXT.md interaction contract: "JS adds `is-open` class"). The script uses `window.__uiSelectInitAll` to avoid double-registration across HTMX swaps and re-initializes on `htmx:afterSwap` events.
+
+The backend `select.templ` must include this same inline script verbatim — it is part of the component contract, not an external JS file.
+
+### Ghost Button Variant — New CSS Required
+
+[VERIFIED: codebase inspection] Go-backend's `button.css` does NOT include a ghost variant. `ButtonVariantGhost` is being ADDED (D-CA01). The ghost CSS rules must be authored following the multi-class pattern:
+
+```css
+/* New ghost rules to add to button.css */
+.ui-button-ghost {
+ background: transparent;
+ color: var(--color-brand-primary);
+}
+.ui-button-ghost:hover {
+ background: var(--color-surface-brand-soft);
+}
+.ui-button-ghost:focus-visible {
+ box-shadow: 0 0 0 3px var(--color-focus-ring);
+ outline: none;
+}
+```
+
+Ghost does not interact with tone (solid/soft) — it is its own variant class. UI-SPEC confirms: `ui-button-ghost` is a standalone class, not combined with a tone class.
+
+### Badge Primary Variant — New CSS Required
+
+[VERIFIED: codebase inspection] Go-backend's `badge.css` does not include `ui-badge-primary`. Per UI-SPEC:
+
+```css
+.ui-badge-primary {
+ background: var(--color-surface-brand-soft);
+ border-color: rgba(128, 78, 236, 0.3); /* --color-brand-primary at 30% opacity */
+ color: var(--color-text-brand);
+}
+```
+
+### textarea.css — Missing from go-backend base.css
+
+[VERIFIED: codebase inspection] Go-backend's `textarea.css` is a short file. Textarea does not have a separate CSS class helper — it uses a static `class="ui-textarea"` in the templ. The CSS mirrors input styling with `min-height: 7rem` and `resize: vertical`.
+
+---
+
+## Don't Hand-Roll
+
+| Problem | Don't Build | Use Instead | Why |
+|---------|-------------|-------------|-----|
+| Select dropdown open/close | Custom JS state machine | Inline script in select.templ (go-backend pattern) | Already solved with HTMX re-init support |
+| Icon SVG delivery | Icon font, external CDN | Inline SVG in UIIcon templ switch | Zero-dependency, go-backend pattern |
+| Component token wiring | Replace all hex values with var() | Leave hardcoded in Phase 13 (D-T04) | Token wiring is Phase 14–17 work |
+| Catalog HTML generation | Static site generator | Live HTTP handler (-tags catalog) | Simpler, server-rendered |
+
+---
+
+## Common Pitfalls
+
+### Pitfall 1: Button Class Pattern Mismatch — Silent Visual Break
+**What goes wrong:** Port go-backend's `button.css` (which uses `.ui-button-solid.ui-button-default { }` selectors) without updating the old compound class strings in templates. The buttons render with no styling.
+**Why it happens:** Templates hardcode `class="ui-button ui-button-soft-neutral-md"` directly as attribute strings — these bypass `ButtonClass()` and are invisible to the Go type system.
+**How to avoid:** Grep ALL `.templ` files for `ui-button-` before replacing button.css. Update each hardcoded string to the multi-class equivalent.
+**Warning signs:** `just generate` succeeds but buttons appear unstyled in the browser.
+
+### Pitfall 2: Existing Tests Assert Old Compound Class
+**What goes wrong:** `ui_test.go` has `wantClass := "ui-button ui-button-solid-default-md"` — this will fail after `ButtonClass()` is updated.
+**Why it happens:** Tests were written against the old pattern.
+**How to avoid:** Update test assertions alongside `ButtonClass()` in the same commit.
+
+### Pitfall 3: Card API Change Breaks Test
+**What goes wrong:** `TestCard_RendersChildren` uses `templ.WithChildren(context.Background(), child)` — this breaks when Card switches from `{ children... }` to typed `Header/Body/Footer` fields.
+**Why it happens:** The go-backend Card API is structurally different.
+**How to avoid:** Rewrite the card test to use `CardProps{Body: child}` when migrating the templ.
+
+### Pitfall 4: Build Tag File Pair — Linker Error Without Stub
+**What goes wrong:** `router.go` calls `RegisterCatalogRoute(r)` unconditionally. When built without `-tags catalog`, the function is undefined → linker error.
+**Why it happens:** Go requires all referenced symbols to be defined in the build graph.
+**How to avoid:** Provide a `catalog_stub.go` with `//go:build !catalog` that defines `RegisterCatalogRoute` as a no-op. Both files must be in the same package.
+
+### Pitfall 5: templ Generate Overwrites Hand-Edited `*_templ.go`
+**What goes wrong:** Editing a `*_templ.go` file directly — `just generate` (which runs `templ generate`) will overwrite it on the next run.
+**Why it happens:** `*_templ.go` files are machine-generated from `.templ` source.
+**How to avoid:** Only edit `.templ` files. Run `just generate` after every `.templ` change. Never touch `*_templ.go`.
+
+### Pitfall 6: select.templ Inline Script and HTMX Re-Init
+**What goes wrong:** Omitting the `htmx:afterSwap` listener from the select's inline script means selects inside HTMX-swapped fragments do not initialize.
+**Why it happens:** Inline scripts run once on page load; HTMX replaces DOM without re-running scripts.
+**How to avoid:** Port the full `__uiSelectInitAll` + `htmx:afterSwap` listener block verbatim from go-backend.
+
+### Pitfall 7: Modal Rendered Without Backdrop in Catalog
+**What goes wrong:** Catalog renders `ui-modal-backdrop` with `position: fixed; inset: 0` — this overlays the entire catalog page.
+**Why it happens:** The backdrop class uses fixed positioning for actual use; in the catalog it obscures everything.
+**How to avoid:** In the catalog modal section, render only `ui-modal-panel` directly (skip the backdrop wrapper), or render the modal in a relative-positioned container with an override. UI-SPEC says: "Modal section renders an open modal panel (without the backdrop toggle)".
+
+### Pitfall 8: Missing `ui-button-ghost` CSS for NormalizedButtonVariant
+**What goes wrong:** `NormalizedButtonVariant` defaults unknown variants to `ButtonVariantDefault`. If `ButtonVariantGhost` is not added to the switch, ghost falls back to default (silent).
+**Why it happens:** The normalizer exhaustively lists known variants.
+**How to avoid:** Add `case ButtonVariantGhost: return variant` to `NormalizedButtonVariant`.
+
+---
+
+## Code Examples
+
+### ButtonClass Multi-Class Output (Updated)
+
+```go
+// Source: go-backend/internal/web/ui/variants.go (verified)
+// Updated backend pattern to match:
+func ButtonClass(variant ButtonVariant, tone ButtonTone, size Size) string {
+ v := NormalizedButtonVariant(variant)
+ t := NormalizedButtonTone(tone)
+ s := NormalizedSize(size)
+ return "ui-button ui-button-" + string(t) + " ui-button-" + string(v) + " ui-button-" + string(s)
+}
+// Output: "ui-button ui-button-solid ui-button-default ui-button-md"
+```
+
+### Ghost Variant in NormalizedButtonVariant
+
+```go
+// Add to variants.go normalizer:
+func NormalizedButtonVariant(variant ButtonVariant) ButtonVariant {
+ switch variant {
+ case ButtonVariantNeutral, ButtonVariantWarning, ButtonVariantSuccess, ButtonVariantDanger, ButtonVariantGhost:
+ return variant
+ default:
+ return ButtonVariantDefault
+ }
+}
+```
+
+### IconButtonClass (New — from go-backend)
+
+```go
+// Source: go-backend/internal/web/ui/variants.go (verified)
+func IconButtonClass(variant IconButtonVariant, tone IconButtonTone) string {
+ normalizedVariant := NormalizedIconButtonVariant(variant)
+ switch NormalizedIconButtonTone(tone) {
+ case IconButtonToneGhost:
+ return "borderless-icon-button ui-icon-button-ghost ui-icon-button-" + string(normalizedVariant)
+ default:
+ return "ui-icon-button ui-icon-button-solid ui-icon-button-" + string(normalizedVariant)
+ }
+}
+```
+
+### SpaceX/SpaceY Class Functions (New)
+
+```go
+// Source: go-backend/internal/web/ui/variants.go (verified)
+func SpaceXClass(step SpacingStep) string {
+ return "ui-space-x ui-space-x-" + string(NormalizedSpacingStep(step))
+}
+func SpaceYClass(step SpacingStep) string {
+ return "ui-space-y ui-space-y-" + string(NormalizedSpacingStep(step))
+}
+```
+
+### Build Tag File Pair for Catalog Route
+
+```go
+// File: backend/internal/web/catalog_route_catalog.go
+//go:build catalog
+package web
+
+import "github.com/go-chi/chi/v5"
+
+func RegisterCatalogRoute(r chi.Router) {
+ r.Get("/ui-catalog", catalogPageHandler())
+}
+```
+
+```go
+// File: backend/internal/web/catalog_route_stub.go
+//go:build !catalog
+package web
+
+import "github.com/go-chi/chi/v5"
+
+func RegisterCatalogRoute(r chi.Router) {} // no-op in production builds
+```
+
+### tailwind.input.css Final State
+
+```css
+/* Source: 13-UI-SPEC.md CSS File Manifest (verified) */
+@import "tailwindcss";
+
+@source "./templates/**/*.templ";
+@source "./internal/web/**/*.templ";
+@source "./internal/web/**/*.go";
+
+@import "./internal/web/ui/base.css";
+@import "./internal/web/ui/button.css";
+@import "./internal/web/ui/badge.css";
+@import "./internal/web/ui/card.css";
+@import "./internal/web/ui/input.css";
+@import "./internal/web/ui/textarea.css";
+@import "./internal/web/ui/select.css";
+@import "./internal/web/ui/modal.css";
+@import "./internal/web/ui/empty-state.css";
+@import "./internal/web/ui/table.css";
+@import "./internal/web/ui/icon-button.css";
+@import "./internal/web/ui/form-field.css";
+@import "./internal/web/ui/spacing.css";
+```
+
+### Catalog Handler Pattern
+
+```go
+// Source: go-backend/cmd/designsystem/main.go (adapted for live HTTP)
+// The catalog handler renders the single-page catalog via templ.
+// Fake shell uses Tailwind utility classes — no import of real app.css.
+```
+
+---
+
+## Component Delta Table
+
+This table shows exactly what changes are needed per component — the planner can turn each row into one or more tasks.
+
+| Component | CSS Status | Props Status | Variants | Helpers | Test Coverage |
+|-----------|-----------|--------------|----------|---------|---------------|
+| Button | REPLACE (multi-class migration) | Add `Icon string`, `Disabled bool` | Add `Ghost` | Update `ButtonClass()` normalizer | Update existing tests |
+| Badge | REPLACE (pill shape, token-based colors) | No change needed | Add `Primary` | Update `BadgeClass()` normalizer | Extend existing tests |
+| Card | REPLACE (token-based, add header/footer sections) | BREAKING: replace children with `Header/Body/Footer templ.Component` | — | Add `CardClass()` → returns `"ui-card"` | Rewrite existing test |
+| Input | NEW FILE (port from go-backend) | Add `Disabled bool`, `Required bool` | — | Add `InputClass(disabled bool)` | New test |
+| Textarea | NEW FILE (port from go-backend) | New file: add `Disabled bool`, `Required bool` | — | Add `TextareaClass()` | New test |
+| Select | NEW FILE (port from go-backend) | New file: complex (Values []string, Multiple bool, Placeholder) | — | New `select_helpers.go` (port verbatim) | New test |
+| Modal | NEW FILE (port from go-backend) | New file: `Title/Body/Actions templ.Component` | — | Add `ModalClass()` | New test |
+| Empty State | NEW FILE (port from go-backend) | New file: `Icon templ.Component` (not string) | — | Add `EmptyStateClass()` | New test |
+| Table | NEW FILE (port from go-backend) | New file: `Head/Body templ.Component` | — | Add `TableClass()` | New test |
+| Icon Button | NEW FILE (port from go-backend) | New file: `IconButtonProps` + `UIIcon(kind string)` | `IconButtonVariant`, `IconButtonTone` | Add `IconButtonClass()` | New test |
+| Form Field | NEW FILE (port from go-backend) | New file: `Label/Hint/Error/ForID` | — | Add `FormFieldClass()` | New test |
+| Space X/Y | NEW FILE (port from go-backend) | New file: `SpaceProps{Size SpacingStep}` | `SpacingStep` enum | Add `SpaceXClass()`, `SpaceYClass()` | New test |
+
+---
+
+## Props Struct Alignment Detail
+
+### Button — add `Icon string` and `Disabled bool`
+
+Current `backend` ButtonProps is missing `Icon string` (go-backend has it). `Disabled bool` is mentioned in UI-SPEC but go-backend does NOT have it — go-backend relies on `Attrs` pass-through for `disabled`. **Do not add `Disabled bool` to Button** — use `Attrs: templ.Attributes{"disabled": true}` for now. Only add `Icon string`.
+
+[VERIFIED: go-backend/internal/web/ui/button.templ — no Disabled field in go-backend]
+
+### Input — `Disabled bool` and `Required bool`
+
+Go-backend `InputProps` does NOT have `Disabled bool` or `Required bool` — these are passed via `Attrs`. UI-SPEC defines them as explicit fields. Since this is Claude's discretion for Props alignment (D-CA03), and the UI-SPEC is authoritative for the backend design contract, add them as explicit fields in the backend Props with corresponding `if props.Disabled { disabled }` conditionals in the templ.
+
+### EmptyState — `Icon templ.Component` not `Icon string`
+
+Go-backend's `EmptyStateProps.Icon` is `templ.Component` (a rendered component, not an icon name string). This means callers pass `ui.UIIcon("grid3x3")` directly. The backend port must match: `Icon templ.Component`.
+
+### Card — Children to Typed Fields (Breaking)
+
+Current backend uses `{ children... }` with `templ.WithChildren`. Go-backend uses `Header/Body/Footer templ.Component` struct fields. The switch is breaking at the call site. Since no production template currently calls `Card` with content (it was introduced in Phase 1 but not yet used in pages), the only impact is the test `TestCard_RendersChildren` which must be rewritten.
+
+---
+
+## File Creation Order (Recommended Wave Structure)
+
+The planner should structure waves to ensure CSS is available before catalog tests run:
+
+**Wave 1 — Token and enum foundation (no visual output yet)**
+- Replace `base.css` with go-backend version (D-T01/T02/T03)
+- Update `variants.go`: add Ghost/Primary variants, add IconButtonVariant/Tone, SpacingStep enums
+- Update `tokens.go`: no substantive changes needed (existing constants are compatible)
+
+**Wave 2 — Migrate existing components to go-backend APIs**
+- Update `button.css` (multi-class selectors) + `ButtonClass()` (multi-class output)
+- Update compound class strings in templates (planning.templ, tasks.templ, events.templ, etapes.templ)
+- Update `badge.css` (pill shape, add primary variant)
+- Update `card.templ` (Props API migration) + `card.css` (token-based)
+- Update `ui_test.go` for Button (multi-class assertions) and Card (typed Props)
+
+**Wave 3 — Port new CSS + templ files**
+- Add `input.css`, `input.templ` (update existing stub)
+- Add `textarea.css`, `textarea.templ` (new file)
+- Add `modal.css`, `modal.templ` (new file)
+- Add `select.css`, `select.templ`, `select_helpers.go` (new files — complex)
+- Add `empty-state.css`, `empty_state.templ` (new file)
+- Add `table.css`, `table.templ` (new file)
+- Add `icon-button.css`, `icon_button.templ` (new file — includes UIIcon)
+- Add `form-field.css`, `form_field.templ` (new file)
+- Add `spacing.css`, `space.templ` (new file)
+- Update `tailwind.input.css` with all new @import entries
+- Extend `ui_test.go` for all new components
+
+**Wave 4 — Catalog**
+- Create `backend/internal/web/ui/catalog/catalog.templ`
+- Create `backend/internal/web/ui/catalog/examples.go`
+- Create `backend/internal/web/catalog_route_catalog.go` (build tag: catalog)
+- Create `backend/internal/web/catalog_route_stub.go` (build tag: !catalog)
+- Wire `RegisterCatalogRoute(r)` into `NewRouter` in `router.go`
+- Add justfile target: `catalog: just generate && go run -tags catalog ./cmd/web`
+- Manual visual sign-off checkpoint before Phase 14
+
+---
+
+## Validation Architecture
+
+### Test Framework
+
+| Property | Value |
+|----------|-------|
+| Framework | Go's built-in testing + testify (none — stdlib only) |
+| Config file | none (go test ./...) |
+| Quick run command | `go test ./internal/web/ui/...` |
+| Full suite command | `just test` (runs `just generate && go test ./...`) |
+
+### Phase Requirements → Test Map
+
+| Req ID | Behavior | Test Type | Automated Command | File Exists? |
+|--------|----------|-----------|-------------------|-------------|
+| DS-01 | Token custom properties present in base.css | Manual (visual) + file content check | `grep -c 'color-brand-primary' backend/internal/web/ui/base.css` | ✅ (replace) |
+| DS-02 | ButtonClass ghost variant emits `ui-button-ghost` | Unit | `go test ./internal/web/ui/... -run TestButton` | ✅ (extend) |
+| DS-03 | Input/Textarea/Select render correct class attributes | Unit | `go test ./internal/web/ui/... -run "TestInput\|TestTextarea\|TestSelect"` | ❌ Wave 3 |
+| DS-04 | Card renders ui-card-header/body/footer sections | Unit | `go test ./internal/web/ui/... -run TestCard` | ✅ (rewrite) |
+| DS-05 | BadgeClass primary variant emits `ui-badge-primary` | Unit | `go test ./internal/web/ui/... -run TestBadge` | ✅ (extend) |
+| DS-06 | Modal renders ui-modal-backdrop and ui-modal-panel | Unit | `go test ./internal/web/ui/... -run TestModal` | ❌ Wave 3 |
+| DS-07 | EmptyState renders ui-empty-state with title | Unit | `go test ./internal/web/ui/... -run TestEmptyState` | ❌ Wave 3 |
+| DS-08 | Table renders ui-table-shell wrapping ui-table | Unit | `go test ./internal/web/ui/... -run TestTable` | ❌ Wave 3 |
+| DS-09 | IconButton ghost neutral emits borderless-icon-button | Unit | `go test ./internal/web/ui/... -run TestIconButton` | ❌ Wave 3 |
+
+### Wave 0 Gaps (tests needed before implementation)
+
+- [ ] `backend/internal/web/ui/ui_test.go` — rewrite `TestCard_RendersChildren` → `TestCard_RendersTypedRegions`
+- [ ] New test functions for all 8 components added in Wave 3 (see UI-SPEC test coverage table)
+- Framework already installed — no setup needed
+
+### Sampling Rate
+- **Per commit:** `go test ./internal/web/ui/...`
+- **Per wave merge:** `just test` (includes templ generate)
+- **Phase gate:** `just test` green + manual catalog visual sign-off at `/ui-catalog`
+
+---
+
+## Open Questions
+
+1. **Ghost button — which size rules apply?**
+ - What we know: `ButtonVariantGhost` is a new variant not in go-backend's button.css. The size rules (`ui-button-sm`, `ui-button-md`, `ui-button-lg`) are defined as standalone classes in go-backend and apply to ALL variants.
+ - What's unclear: Should ghost buttons have padding/sizing from the size classes, or use a custom minimal padding?
+ - Recommendation: Inherit the standard size classes (`.ui-button-md { padding: 0.7rem 1rem; }`). Ghost variant only overrides background/color, not sizing.
+
+2. **Catalog route vs. router.go signature**
+ - What we know: `NewRouter(...)` in `router.go` has a fixed signature with many named deps. Adding catalog registration changes `router.go`.
+ - What's unclear: The build-tag stub approach keeps the function call in `router.go` unconditional — this is safe because the stub satisfies the symbol.
+ - Recommendation: Use the two-file build-tag approach (catalog + !catalog stub) with an unconditional call in `router.go`. Zero diff to router.go signature.
+
+3. **`auth-provider-*` CSS in button.css — keep or remove?**
+ - What we know: Current `button.css` has `auth-provider-button`, `auth-provider-separator`, etc. appended from Phase 8. Go-backend does not have these.
+ - What's unclear: Phase 13 replaces button.css entirely — these auth selectors would be lost.
+ - Recommendation: Move auth-provider CSS to a separate `auth.css` file BEFORE replacing button.css. Import it in `tailwind.input.css`. This is a prerequisite sub-task in Wave 2.
+
+---
+
+## Assumptions Log
+
+| # | Claim | Section | Risk if Wrong |
+|---|-------|---------|---------------|
+| A1 | Ghost button inherits size class padding from `.ui-button-sm/md/lg` | Component Delta Table | Ghost renders with wrong sizing if size classes don't apply |
+| A2 | No templates currently call `Card` with children in production pages | Card Props section | Card change breaks rendered pages (not just tests) |
+| A3 | `auth-provider-*` CSS from Phase 8 must be preserved | Open Question 3 | Login page loses styling if auth CSS is dropped |
+
+---
+
+## Environment Availability
+
+| Dependency | Required By | Available | Version | Fallback |
+|------------|------------|-----------|---------|----------|
+| Go | Build | ✓ | 1.26.0 (go.mod) | — |
+| templ CLI | `just generate` | ✓ | v0.3.1020 (justfile) | `go install github.com/a-h/templ/cmd/templ@v0.3.1020` |
+| tailwindcss standalone | `just generate` | ✓ | v4.0.0 (justfile) | `just bootstrap` |
+| just | Task runner | ✓ | system | Run commands manually |
+
+[VERIFIED: justfile inspection] — toolchain versions pinned.
+
+---
+
+## Security Domain
+
+> `security_enforcement` not set to false — section required.
+
+### Applicable ASVS Categories
+
+| ASVS Category | Applies | Standard Control |
+|---------------|---------|-----------------|
+| V2 Authentication | no | Phase 13 is CSS only — no auth changes |
+| V3 Session Management | no | No session changes |
+| V4 Access Control | yes — catalog route | Build tag `//go:build catalog` ensures catalog is never in production binary |
+| V5 Input Validation | no | Component CSS and static Props structs, no user input validated |
+| V6 Cryptography | no | No crypto operations |
+
+### Known Threat Patterns
+
+| Pattern | STRIDE | Standard Mitigation |
+|---------|--------|---------------------|
+| Catalog route exposed in production | Information Disclosure | `//go:build catalog` + `//go:build !catalog` stub; production binary built without `-tags catalog` |
+| XSS via templ text content | Tampering | templ auto-escapes `{ variable }` interpolations — no raw HTML injection via Props |
+
+---
+
+## Sources
+
+### Primary (HIGH confidence — verified by codebase inspection)
+
+- `backend/internal/web/ui/` — all existing files read directly
+- `go-backend/internal/web/ui/` — all source CSS and templ files read directly
+- `go-backend/internal/web/ui/catalog/` — catalog.templ, examples.go, catalog_test.go, pages.go read directly
+- `backend/tailwind.input.css` — current import manifest verified
+- `backend/internal/web/router.go` — router structure verified
+- `backend/justfile` — build targets verified (tailwind_version = v4.0.0)
+- `backend/templates/*.templ` — compound class hardcodes verified by grep
+
+### Secondary (HIGH confidence — from project planning artifacts)
+
+- `.planning/phases/13-design-system-foundation/13-CONTEXT.md` — locked decisions
+- `.planning/phases/13-design-system-foundation/13-UI-SPEC.md` — visual contract
+- `.planning/REQUIREMENTS.md` — DS-01 through DS-09
+
+---
+
+## Metadata
+
+**Confidence breakdown:**
+- Standard stack: HIGH — verified by direct codebase inspection
+- Architecture: HIGH — both source and target codebases fully read
+- Pitfalls: HIGH — compound class migration risk verified by grep, card API break verified by test inspection
+- Props struct alignment: HIGH — all templ files in both repos read
+
+**Research date:** 2026-05-16
+**Valid until:** 2026-06-16 (stable — no external dependencies, pure codebase-internal work)