---
phase: 01-foundation
plan: 02
type: execute
wave: 2
depends_on: ["01-01"]
files_modified:
  - backend/internal/web/ui/tokens.go
  - backend/internal/web/ui/variants.go
  - backend/internal/web/ui/helpers.go
  - backend/internal/web/ui/base.css
  - backend/internal/web/ui/button.templ
  - backend/internal/web/ui/button.css
  - backend/internal/web/ui/card.templ
  - backend/internal/web/ui/card.css
  - backend/internal/web/ui/badge.templ
  - backend/internal/web/ui/badge.css
  - backend/internal/web/ui/ui_test.go
  - backend/internal/web/handlers_test.go
  - backend/internal/db/pool_test.go
  # Tests in handlers_test.go and pool_test.go carry the `//go:build red_gate` build tag during Plan 01-02 so a stray `go test ./...` does NOT fail with `undefined: web.NewRouter` / `db.NewPool` errors before Plan 01-03 lands the implementations (Codex concern #3). Plan 01-03 removes the build tag from both files as part of the GREEN step.
autonomous: true
requirements:
  - FOUND-01
  - FOUND-02
  - FOUND-03
  - FOUND-04
tags:
  - go
  - testing
  - templ
  - design-system
  - htmx

must_haves:
  truths:
    - "After this plan lands, `go test -tags=red_gate ./internal/web/... ./internal/db/...` FAILS with `undefined: web.NewRouter` / `db.NewPool` (or equivalent) — proving the RED test scaffold targets the exact symbols Plan 01-03 will implement. Without the `red_gate` tag, `go test ./...` SUCCEEDS (the red-gated files are excluded from default compilation), so the intentional RED state does not leak into normal `go test ./...` invocations (Codex concern #3)."
    - "Verification in Plan 01-02 is package-scoped (`go test ./internal/web/ui/...` for the GREEN ui smoke tests; `go test -tags=red_gate ./internal/web/...` for the RED gate). `go test ./...` without `-tags=red_gate` MUST pass at the end of Plan 01-02 because the red-gated test files are excluded from default compilation."
    - "The `internal/web/ui` design-system package compiles standalone and its `ui_test.go` smoke tests PASS — each shipped variant of Button, Card, Badge renders without panicking and emits the expected root CSS class"
    - "The UI component package mirrors the enum surface of `go-backend/internal/web/ui/` for Size, ButtonVariant, ButtonTone, BadgeVariant — future phases extend these enums' CSS without redefining them"
    - "Test files declare the exact handler function signatures and templ component names that Plan 03 will implement, so Plan 03 is a fill-in not an exploration"
    - "D-07: templ (a-h/templ) used as type-safe HTML templating; UI library components are templ functions returning templ.Component"
    - "D-09: templ generate runs as part of the dev loop via just generate alongside sqlc generate; *_templ.go artifacts are gitignored"
    - "D-20: /healthz response shape locked — 200 with status:ok db:ok when pinger succeeds; 503 with status:degraded db:down otherwise — asserted by TestHealthz_OK and TestHealthz_Down"
  artifacts:
    - path: backend/internal/web/ui/variants.go
      provides: Size, ButtonVariant, ButtonTone, BadgeVariant enums + class-builder functions + normalizers
      contains: "ButtonVariantDefault"
    - path: backend/internal/web/ui/tokens.go
      provides: Semantic token constants (e.g. TokenPrimary, TokenDanger) declared but minimally used in Phase 1
      contains: "package ui"
    - path: backend/internal/web/ui/button.templ
      provides: Button templ component accepting ButtonProps{Label, Variant, Tone, Size, Type, Attrs}
      contains: "ButtonProps"
    - path: backend/internal/web/ui/button.css
      provides: CSS rules for `.ui-button-solid-default-md` and the base `.ui-button` class
      contains: ".ui-button"
    - path: backend/internal/web/ui/card.templ
      provides: Card templ component accepting children (slate-50 panel + slate-200 border)
      contains: "templ Card"
    - path: backend/internal/web/ui/card.css
      provides: CSS rules for `.ui-card`
      contains: ".ui-card"
    - path: backend/internal/web/ui/badge.templ
      provides: Badge templ component accepting Label + Variant (info / success / warning / danger)
      contains: "BadgeProps"
    - path: backend/internal/web/ui/badge.css
      provides: CSS rules for `.ui-badge-{info,success,danger}` (warning declared in enum but no CSS in Phase 1)
      contains: ".ui-badge"
    - path: backend/internal/web/ui/base.css
      provides: Resets, focus-ring base, html/body defaults imported into tailwind.input.css
      contains: "html"
    - path: backend/internal/web/ui/ui_test.go
      provides: Smoke tests rendering each Phase-1 variant; asserts root CSS class appears in output
      contains: "TestButton"
    - path: backend/internal/web/handlers_test.go
      provides: Failing tests for healthz OK + down, index hx-get presence, demo/time fragment, RequestID header, slog handler switch — gated behind `//go:build red_gate` until Plan 01-03 lands the handlers
      contains: "//go:build red_gate"
    - path: backend/internal/db/pool_test.go
      provides: pgxpool integration test that SKIPS when DATABASE_URL is unset; gated behind `//go:build red_gate` until Plan 01-03 lands `db.NewPool`
      contains: "//go:build red_gate"
  key_links:
    - from: backend/internal/web/handlers_test.go
      to: backend/internal/web/{router.go,handlers.go,middleware.go} (Plan 03)
      via: tests reference exact function names — NewRouter, healthzHandler, indexHandler, demoTimeHandler, RequestIDMiddleware
      pattern: "NewRouter|healthzHandler|indexHandler|demoTimeHandler"
    - from: backend/internal/web/ui/button.templ
      to: backend/internal/web/ui/button.css
      via: emitted root class `ui-button-solid-default-md`
      pattern: "ui-button-solid-default-md"
    - from: backend/internal/web/ui/ui_test.go
      to: ButtonProps / CardProps / BadgeProps types declared in this plan
      via: direct constructor calls
      pattern: "ui.ButtonProps"
---

<objective>
Land the RED step of the Walking Skeleton (failing handler + db tests targeting exact function signatures Plan 03 will implement) AND the `internal/web/ui` design-system package (Button / Card / Badge with their CSS files, enum surface, and passing smoke tests).

Purpose: Plan 03's job becomes "make the failing tests pass and render the index page via these components" rather than "design the package, then build the page, then write tests." Test-targeted signatures eliminate exploration; the UI package eliminates inline Tailwind in handlers.

Output: 13 files split between `internal/web/ui/` (10 files, all GREEN — package compiles, its own tests pass) and the failing-test scaffold (`internal/web/handlers_test.go`, `internal/db/pool_test.go`). The handler tests reference yet-unwritten code from Plan 03 — they are intentionally RED right now.
</objective>

<execution_context>
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.claude/get-shit-done/workflows/execute-plan.md
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/phases/01-foundation/01-CONTEXT.md
@.planning/phases/01-foundation/01-RESEARCH.md
@.planning/phases/01-foundation/01-UI-SPEC.md
@.planning/phases/01-foundation/01-VALIDATION.md
@.planning/phases/01-foundation/SKELETON.md
@.planning/phases/01-foundation/01-01-SUMMARY.md

# Reference implementation for the ui package shape (READ carefully — DO NOT copy without re-keying class names)
@go-backend/internal/web/ui/variants.go
@go-backend/internal/web/ui/button.templ
@go-backend/internal/web/ui/card.templ
@go-backend/internal/web/ui/badge.templ

<interfaces>
# Contracts Plan 03 will implement against. Defining them here, in tests, locks Plan 03's API surface.

## Handler signatures (backend/internal/web)

```go
// NewRouter returns a fully configured chi.Router. The Pinger interface allows
// test injection of a stub for /healthz coverage without spinning up Postgres.
type Pinger interface {
    Ping(ctx context.Context) error
}

func NewRouter(pinger Pinger, staticDir string) http.Handler

// Individual handlers exported for direct httptest invocation.
func HealthzHandler(pinger Pinger) http.HandlerFunc
func IndexHandler() http.HandlerFunc       // renders templates.Index via templ
func DemoTimeHandler(now func() time.Time) http.HandlerFunc  // injected clock for deterministic tests

// Middleware
func RequestIDMiddleware(next http.Handler) http.Handler
func SlogLoggerMiddleware(logger *slog.Logger) func(http.Handler) http.Handler

// Slog handler selection (in package web — pure function, no globals)
func NewSlogHandler(env string, w io.Writer) slog.Handler
```

## DB package signatures (backend/internal/db)

```go
func NewPool(ctx context.Context, dsn string) (*pgxpool.Pool, error)
```

## UI package signatures (backend/internal/web/ui)

```go
type Size string
const (
    SizeSM Size = "sm"
    SizeMD Size = "md"
    SizeLG Size = "lg"
)

type ButtonVariant string
const (
    ButtonVariantDefault  ButtonVariant = "default"
    ButtonVariantNeutral  ButtonVariant = "neutral"
    ButtonVariantWarning  ButtonVariant = "warning"
    ButtonVariantSuccess  ButtonVariant = "success"
    ButtonVariantDanger   ButtonVariant = "danger"
)

type ButtonTone string
const (
    ButtonToneSolid ButtonTone = "solid"
    ButtonToneSoft  ButtonTone = "soft"
)

type BadgeVariant string
const (
    BadgeVariantInfo    BadgeVariant = "info"
    BadgeVariantWarning BadgeVariant = "warning"
    BadgeVariantSuccess BadgeVariant = "success"
    BadgeVariantDanger  BadgeVariant = "danger"
)

type ButtonProps struct {
    Label   string
    Variant ButtonVariant
    Tone    ButtonTone
    Size    Size
    Type    string            // "button" / "submit"; defaults to "button" if empty
    Attrs   templ.Attributes  // pass-through for hx-* etc.
}

type BadgeProps struct {
    Label   string
    Variant BadgeVariant
}

// Templ components — match these names exactly
templ Button(props ButtonProps)
templ Card(attrs templ.Attributes)  // accepts children via templ's child-content syntax — verify exact syntax against the pinned templ v0.3.1020 docs before locking the rendered HTML test assertions (Codex concern #8)
templ Badge(props BadgeProps)
```

**templ syntax verification (Codex concern #8):** Before authoring `card.templ` and its smoke test in Task 2 / Task 3, the executor MUST run `templ version` to confirm v0.3.1020 is installed and consult `https://templ.guide/syntax-and-usage/template-composition` (or the equivalent docs version) to confirm the exact child-content syntax — early templ versions used `{ children... }` inside the templ body, later versions evolved the spelling. The Task 2 acceptance below asserts ONLY that the rendered `<section class="ui-card">...</section>` wraps the literal child content; the exact templ source syntax is the executor's call within that contract.
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: UI package enums, helpers, base CSS</name>
  <files>backend/internal/web/ui/tokens.go, backend/internal/web/ui/variants.go, backend/internal/web/ui/helpers.go, backend/internal/web/ui/base.css</files>
  <read_first>
    - .planning/phases/01-foundation/01-UI-SPEC.md (Component Library Contract — enum names and normalizer pattern are non-negotiable)
    - go-backend/internal/web/ui/variants.go (reference shape; mirror the enum/normalizer surface)
  </read_first>
  <behavior>
    - Importing `backend/internal/web/ui` from a Go test compiles successfully
    - `ui.SizeMD`, `ui.ButtonVariantDefault`, `ui.ButtonToneSolid`, `ui.BadgeVariantInfo` are exported constants of the right types
    - `ui.NormalizedSize("")` returns `ui.SizeMD` (zero-value safe default)
    - `ui.NormalizedSize("md")` returns `ui.SizeMD`
    - `ui.NormalizedButtonVariant("")` returns `ui.ButtonVariantDefault`
    - `ui.NormalizedButtonTone("")` returns `ui.ButtonToneSolid`
    - `ui.NormalizedBadgeVariant("")` returns `ui.BadgeVariantInfo`
    - `ui.ButtonClass(ButtonVariantDefault, ButtonToneSolid, SizeMD)` returns `"ui-button ui-button-solid-default-md"`
    - `ui.BadgeClass(BadgeVariantInfo)` returns `"ui-badge ui-badge-info"`
  </behavior>
  <action>
    Write `tokens.go`: declare a minimal set of semantic-token string constants (`TokenPrimary`, `TokenNeutral`, `TokenWarning`, `TokenSuccess`, `TokenDanger`) — these mirror the reference package's surface but are not heavily consumed in Phase 1; they exist so later phases can extend without restructuring. Package clause: `package ui`.

    Write `variants.go`: declare exactly the types and constants from the `<interfaces>` block above (Size, ButtonVariant, ButtonTone, BadgeVariant + their constants). Add `NormalizedSize`, `NormalizedButtonVariant`, `NormalizedButtonTone`, `NormalizedBadgeVariant` functions: each accepts the corresponding type and returns the safe default for zero-value input (per UI-SPEC: "normalizer pattern required for every enum"). Add class-builder functions `ButtonClass(variant ButtonVariant, tone ButtonTone, size Size) string` and `BadgeClass(variant BadgeVariant) string` returning class strings exactly as listed in the behavior block. Defaults applied via normalizers before string assembly.

    Write `helpers.go`: a small `mergeAttrs(base, override templ.Attributes) templ.Attributes` helper (for use inside the templ components in Task 2) that copies base then overlays override keys. Keep it ~10 lines.

    Write `base.css`: minimal global resets and a focus-ring base class. Per UI-SPEC §"Accessibility Floor": ensure `:focus-visible` has a visible ring. Use plain CSS (no `@apply`) so it works under Tailwind v4 standalone with `@source` scanning Go files. Include `html { -webkit-text-size-adjust: 100%; }` and an `*, *::before, *::after { box-sizing: border-box; }` reset. Keep under 30 lines.

    Do NOT use `templ.Raw` anywhere. Do NOT import anything from `cmd/web` or other domain packages — `ui` has no upward dependencies.
  </action>
  <verify>
    <automated>cd backend && go build ./internal/web/ui/ && go vet ./internal/web/ui/</automated>
  </verify>
  <acceptance_criteria>
    - All four files compile under `go build ./internal/web/ui/`
    - `variants.go` declares every enum constant listed in the `<interfaces>` block (verifiable: `grep -c 'ButtonVariant\(Default\|Neutral\|Warning\|Success\|Danger\)' variants.go` ≥ 5)
    - Each enum has a `Normalized*` function returning the safe default for zero value
    - `ButtonClass` produces exactly `"ui-button ui-button-solid-default-md"` for (Default, Solid, MD) — asserted by Task 4's tests
    - `helpers.go` exports `mergeAttrs` (used by Task 2)
  </acceptance_criteria>
  <done>UI enum surface and helpers compile; class strings match the contract in `<interfaces>`.</done>
</task>

<task type="auto" tdd="true">
  <name>Task 2: UI templ components — Button, Card, Badge — and their CSS</name>
  <files>backend/internal/web/ui/button.templ, backend/internal/web/ui/button.css, backend/internal/web/ui/card.templ, backend/internal/web/ui/card.css, backend/internal/web/ui/badge.templ, backend/internal/web/ui/badge.css</files>
  <read_first>
    - .planning/phases/01-foundation/01-UI-SPEC.md (Component Library Contract — exact rendered HTML for Button shown in §HTMX Interaction Pattern; canonical Button usage block)
    - go-backend/internal/web/ui/button.templ (reference shape — do NOT copy class soup verbatim; UI-SPEC defines the Phase 1 class names)
    - go-backend/internal/web/ui/card.templ
    - go-backend/internal/web/ui/badge.templ
  </read_first>
  <behavior>
    - `@ui.Button(ui.ButtonProps{Label: "x", Type: "button"})` rendered to a `bytes.Buffer` produces HTML containing class `ui-button-solid-default-md` and the label "x"
    - `@ui.Button(..., Attrs: templ.Attributes{"hx-get": "/demo/time"})` renders an `hx-get="/demo/time"` attribute on the `<button>` element
    - When `props.Type` is empty, the rendered button has `type="button"`
    - `@ui.Card(...) { <p>hello</p> }` renders HTML containing class `ui-card` and the literal `<p>hello</p>`
    - `@ui.Badge(ui.BadgeProps{Label: "OK", Variant: ui.BadgeVariantSuccess})` renders HTML containing `ui-badge-success` and the literal `OK`
  </behavior>
  <action>
    Write `button.templ`: declare `templ Button(props ButtonProps)`. Inside, compute `class` from `ui.ButtonClass(NormalizedButtonVariant(props.Variant), NormalizedButtonTone(props.Tone), NormalizedSize(props.Size))`. Render a `<button>` element with `type={props.Type or "button"}`, `class={class}`, and the pass-through `{props.Attrs...}` spread (templ attribute spread). The button body is `{props.Label}`. Do NOT inline Tailwind utility classes inside the `<button>` markup — the CSS rules live in `button.css`.

    Write `button.css`: declare `.ui-button` as a base rule (display, font, focus-ring per UI-SPEC), and `.ui-button-solid-default-md` with the EXACT Tailwind-equivalent values from UI-SPEC §HTMX Interaction Pattern: `display: inline-flex; align-items: center; border-radius: 0.375rem; background-color: #2563eb; padding: 0.5rem 1rem; font-size: 1rem; font-weight: 600; color: #ffffff;`. Hover state MUST be expressed as a **non-nested selector** `.ui-button-solid-default-md:hover { background-color: #1d4ed8; }` — do NOT use CSS nesting (`&:hover`) (Codex concern #7 — Tailwind v4 standalone's CSS pipeline does not reliably support nested selectors in plain-CSS component layer files, and explicit selectors work under every Tailwind version). Same rule applies to focus-ring: write `.ui-button-solid-default-md:focus-visible { ... }` as a top-level rule. Add the `htmx-request` loading behavior as a plain-CSS rule `.ui-button.htmx-request { opacity: 0.6; pointer-events: none; }` (also non-nested). Document the no-nesting choice in a one-line CSS comment at the top of `button.css`.

    Write `card.templ`: declare `templ Card(attrs templ.Attributes)` accepting children. Render `<section class="ui-card" {attrs...}>{ children... }</section>`.

    Write `card.css`: `.ui-card` matches the slate-50 panel from UI-SPEC §HTMX Interaction Pattern (`border-radius: 0.5rem; border: 1px solid #e2e8f0; background-color: #f8fafc; padding: 1.5rem;`).

    Write `badge.templ`: declare `templ Badge(props BadgeProps)`. Compute class from `ui.BadgeClass(NormalizedBadgeVariant(props.Variant))`. Render `<span class={class}>{props.Label}</span>`.

    Write `badge.css`: `.ui-badge` base (inline-block, small padding, small font), plus variant rules for `.ui-badge-info` (blue-tinted), `.ui-badge-success` (green-600 fg), `.ui-badge-danger` (red-600 fg). `warning` variant: enum exists in variants.go (Task 1) but Phase 1 ships no CSS rule for it — document in a comment that warning lands when first needed.

    All three `.css` files MUST be referenced from `tailwind.input.css` (Plan 01 already imports them). They are pulled in at CSS build time by the Tailwind standalone CLI.

    Run `templ generate` after writing the `.templ` files so the `*_templ.go` artifacts exist for the test in Task 4 to import. Do NOT commit `*_templ.go` (gitignored).
  </action>
  <verify>
    <automated>cd backend && templ generate && go build ./internal/web/ui/ && grep -q '\.ui-button-solid-default-md' internal/web/ui/button.css && grep -q '\.ui-card' internal/web/ui/card.css && grep -q '\.ui-badge-info' internal/web/ui/badge.css && grep -q '\.ui-badge-success' internal/web/ui/badge.css && grep -q '\.ui-badge-danger' internal/web/ui/badge.css</automated>
  </verify>
  <acceptance_criteria>
    - `templ generate` produces `button_templ.go`, `card_templ.go`, `badge_templ.go` with no errors
    - `go build ./internal/web/ui/` succeeds with the generated files present
    - Each CSS file declares exactly the variants enumerated above (info/success/danger for badge; warning intentionally absent)
    - Button component renders `type="button"` when `Type` field is empty
    - Card component accepts and passes through `templ.Attributes`
    - Pages MUST NOT need to know about the `.ui-*` class names — they consume the components only
  </acceptance_criteria>
  <done>UI primitives compile to Go, CSS files cover the Phase 1 variant set, and component HTML matches UI-SPEC's canonical render.</done>
</task>

<task type="auto" tdd="true">
  <name>Task 3: UI package smoke tests (ui_test.go)</name>
  <files>backend/internal/web/ui/ui_test.go</files>
  <read_first>
    - .planning/phases/01-foundation/01-UI-SPEC.md (Component Library Contract — `ui_test.go` requirements: render each Phase-1 variant, assert root class appears, no panic)
    - backend/internal/web/ui/button.templ (just written in Task 2)
    - backend/internal/web/ui/card.templ
    - backend/internal/web/ui/badge.templ
  </read_first>
  <behavior>
    - `TestButton_DefaultSolidMD` renders `@ui.Button(ui.ButtonProps{Label: "Fetch server time"})` to a buffer, asserts the output contains `class="ui-button ui-button-solid-default-md"` and the literal "Fetch server time" and `type="button"`
    - `TestButton_PassesThroughAttrs` renders a Button with `Attrs: templ.Attributes{"hx-get": "/demo/time", "hx-target": "#demo-out"}`, asserts both attributes appear in output
    - `TestCard_RendersChildren` renders `@ui.Card(nil) { <p>x</p> }`, asserts output contains `<section class="ui-card"` and `<p>x</p>`
    - `TestBadge_InfoVariant` renders `@ui.Badge(BadgeProps{Label: "OK", Variant: BadgeVariantInfo})`, asserts output contains `class="ui-badge ui-badge-info"` and `OK`
    - `TestBadge_SuccessVariant` renders `BadgeVariantSuccess`, asserts `ui-badge-success`
    - `TestBadge_ZeroValueDefaultsToInfo` renders with empty Variant, asserts `ui-badge-info` (normalizer behavior)
    - `TestButtonClass_String` asserts `ui.ButtonClass(ui.ButtonVariantDefault, ui.ButtonToneSolid, ui.SizeMD) == "ui-button ui-button-solid-default-md"`
  </behavior>
  <action>
    Write `ui_test.go` with `package ui` (internal — so it can call unexported helpers if needed; but only public API is required by the behavior list). Use `bytes.Buffer` + `templ.Component.Render(ctx, &buf)` to capture output, then `strings.Contains` for assertions. Each test follows the standard table-driven Go pattern or a simple `t.Run` per case. Use `context.Background()` as the render context.

    Required imports: `bytes`, `context`, `strings`, `testing`, `github.com/a-h/templ`.

    Do NOT use any third-party test library — stdlib `testing` only (RESEARCH §Validation Architecture).
  </action>
  <verify>
    <automated>cd backend && templ generate && go test ./internal/web/ui/ -count=1</automated>
  </verify>
  <acceptance_criteria>
    - `go test ./internal/web/ui/` exits 0 with all listed test names present in `-v` output
    - Each test asserts at minimum: (a) the expected root CSS class name appears in rendered HTML, (b) the expected literal label/content appears
    - `TestButtonClass_String` passes — proves the class-string contract Plan 03 relies on
    - Zero-value-defaults test passes — proves normalizers are wired correctly
  </acceptance_criteria>
  <done>The ui package is fully GREEN: it compiles, generates, and all smoke tests pass. This becomes the design-system foundation for Plan 03's page rendering and every later phase.</done>
</task>

<task type="auto" tdd="true">
  <name>Task 4: Failing handler tests (handlers_test.go) and pgxpool integration test</name>
  <files>backend/internal/web/handlers_test.go, backend/internal/db/pool_test.go</files>
  <read_first>
    - .planning/phases/01-foundation/01-RESEARCH.md (Code Examples §`/healthz` handler; §Pattern 4 RequestID; §Pattern 3 slog handler switch)
    - .planning/phases/01-foundation/01-VALIDATION.md (Per-Task Verification Map — exact test names referenced)
    - .planning/phases/01-foundation/01-UI-SPEC.md (HTMX Interaction Pattern — index page must contain `hx-get="/demo/time"`)
  </read_first>
  <behavior>
    The following tests MUST be authored. They will FAIL during Plan 02's verify (because Plan 03's handlers don't exist yet). That failure is the RED step. Plan 03's GREEN step is making them pass.

    In `internal/web/handlers_test.go` (package `web`):
    - `TestHealthz_OK`: builds a `httptest.NewRecorder` + `httptest.NewRequest("GET", "/healthz", nil)`, calls `web.HealthzHandler(stubPinger{err: nil})`. Asserts `200`, `Content-Type: application/json`, body contains `"status":"ok"` and `"db":"ok"`.
    - `TestHealthz_Down`: same shape but `stubPinger{err: errors.New("conn refused")}`. Asserts `503`, body contains `"status":"degraded"` and `"db":"down"`.
    - `TestIndex_RendersHxGet`: GET `/` through `web.NewRouter(stubPinger{}, "./static")`. Asserts status 200, `Content-Type: text/html`, body contains `hx-get="/demo/time"`, `hx-target="#demo-out"`, `ui-button-solid-default-md` (proving the page consumes the ui package), and the literal label "Fetch server time" (UI-SPEC copywriting contract).
    - `TestDemoTime_Fragment`: GET `/demo/time`. Asserts status 200, `Content-Type: text/html`, body matches ISO-8601 UTC pattern (regex `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`), body does NOT contain `<html` (it's a fragment, not a full page). Plan 03 wires the injected clock; for now this test just calls the production constructor and tolerates real wall-clock — Plan 03 will refine the deterministic-clock injection if needed.
    - `TestRequestID_HeaderSet`: GET `/healthz` through the full router, asserts response header `X-Request-ID` is non-empty AND matches a UUIDv4 regex (`^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`).
    - `TestSlog_HandlerSwitch`: calls `web.NewSlogHandler("production", &buf)` and asserts the returned `slog.Handler` writes JSON (the buf, after `logger.Info("x")`, parses as JSON with `"msg":"x"`). Then calls `web.NewSlogHandler("development", &buf2)` and asserts the buf is NOT valid JSON (`text` handler output).

    Define a `stubPinger` struct local to the test file: a single `err error` field, `Ping(ctx context.Context) error { return s.err }`. This implements the `Pinger` interface Plan 03 declares.

    In `internal/db/pool_test.go` (package `db`):
    - `TestPool_Connects`: reads `DATABASE_URL` from env. If empty: `t.Skip("DATABASE_URL not set — integration test skipped")`. Otherwise calls `db.NewPool(ctx, dsn)`, asserts no error, then `pool.Ping(ctx)` returns nil, then `pool.Close()`.
  </behavior>
  <action>
    Write both test files. Stub types live in the same file as the tests that use them (no separate helpers file in Phase 1).

    **Red-gate isolation (Codex concern #3).** Both files MUST start with the build tag line:

    ```
    //go:build red_gate
    ```

    followed by a blank line, then the `package web` / `package db` clause. This tag ensures:
    - Default `go test ./...` and `go build ./...` invocations EXCLUDE these files — so `undefined: web.NewRouter` / `undefined: db.NewPool` errors do NOT leak into normal CI or developer workflows during the Plan 01-02 → 01-03 transition.
    - Explicit `go test -tags=red_gate ./internal/web/... ./internal/db/...` INCLUDES the files and fails with the expected `undefined:` errors — that is the proof that Plan 01-02's RED gate is wired against the right symbols.
    - Plan 01-03's GREEN slice removes both `//go:build red_gate` lines as part of its work (the implementations exist; the gate is no longer needed). Plan 01-03's verify runs `go test ./...` without the tag and expects every test green.

    Required imports (handlers_test.go): `bytes`, `context`, `encoding/json`, `errors`, `net/http`, `net/http/httptest`, `regexp`, `strings`, `testing`. Use Go's stdlib `regexp` for the UUID + timestamp patterns.

    These tests reference `web.NewRouter`, `web.HealthzHandler`, `web.NewSlogHandler`, `web.Pinger` interface, all of which Plan 03 introduces. With the build tag in place, the symbols don't need to exist yet under default compilation. Under `-tags=red_gate` they will fail with `undefined: NewRouter` — that is the desired RED state.

    Do NOT use `// +build` (legacy syntax) — use only the new `//go:build red_gate` form (Go 1.17+). Do NOT skip the failing tests with `t.Skip` — they must remain RED under the `red_gate` tag until Plan 01-03 removes the tag.
  </action>
  <verify>
    <automated>cd backend && test -f internal/web/handlers_test.go && test -f internal/db/pool_test.go && grep -q '^//go:build red_gate' internal/web/handlers_test.go && grep -q '^//go:build red_gate' internal/db/pool_test.go && grep -c '^func Test' internal/web/handlers_test.go | awk '$1>=6{exit 0} {exit 1}' && grep -q 'TestPool_Connects' internal/db/pool_test.go && grep -q 't.Skip' internal/db/pool_test.go && go test ./internal/web/ui/... -count=1 && (! go test -tags=red_gate ./internal/web/... 2>&1 | grep -q '^ok\s')</automated>
  </verify>
  <acceptance_criteria>
    - `handlers_test.go` contains ≥6 `Test*` functions covering the behaviors listed
    - `pool_test.go` contains `TestPool_Connects` with a `t.Skip` branch for missing `DATABASE_URL`
    - Both test files start with `//go:build red_gate` on line 1 (Codex concern #3)
    - `go test ./...` (no tag) at the end of Plan 01-02 exits 0 — the red-gated files are excluded from default compilation
    - `go test -tags=red_gate ./internal/web/...` at the end of Plan 01-02 exits NON-ZERO with `undefined: web.NewRouter` (or similar) — proves the RED gate targets the right symbols
    - `go test ./internal/web/ui/...` exits 0 — the ui smoke tests are GREEN
    - Failure messages under `-tags=red_gate` are informative (`undefined: web.NewRouter`) — this exact error is what Plan 01-03's executor will see and fix
  </acceptance_criteria>
  <done>Failing tests are committed. Plan 03 inherits a concrete, behavior-specified target list — no exploration needed.</done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| Test process ↔ filesystem | Tests run from local checkout only; no external network |
| `DATABASE_URL` env ↔ pool_test.go | Test reads env var; skips if absent — does NOT default to a hardcoded DSN |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-01-06 | I (Information disclosure) | `pool_test.go` logging | accept | Test logs use `t.Logf` only; never log full DSN. `t.Skip` message says "DATABASE_URL not set" — does not echo the value. |
| T-01-07 | T (Tampering via test fixtures) | `stubPinger` and other in-test stubs | accept | Stubs are package-private to `_test.go` files; not reachable from production code (Go test files are excluded from non-test builds). |
</threat_model>

<verification>
- `cd backend && go build ./internal/web/ui/` succeeds
- `cd backend && templ generate && go test ./internal/web/ui/... -count=1` exits 0 (all ui smoke tests GREEN — package-scoped)
- `cd backend && go test ./... -count=1` exits 0 — the red-gated test files are excluded from default compilation (Codex concern #3); no leak into `go test ./...`
- `cd backend && go test -tags=red_gate ./internal/web/... ./internal/db/... -count=1` exits NON-ZERO with `undefined: web.NewRouter` / `undefined: db.NewPool` — confirms the RED gate is set for Plan 01-03
- All ten files under `internal/web/ui/` exist
- Both red-gated test files exist, start with `//go:build red_gate`, and contain the required `Test*` functions
- `internal/web/ui/ui_test.go` covers Button (default+attrs), Card, Badge (info+success+zero-value)
- `button.css` declares non-nested `.ui-button-solid-default-md:hover` and `:focus-visible` selectors (Codex concern #7)
</verification>

<success_criteria>
1. `internal/web/ui/` package compiles, generates, and tests pass (GREEN)
2. UI package mirrors the enum surface of `go-backend/internal/web/ui/` (Size, ButtonVariant, ButtonTone, BadgeVariant — all values present even if Phase 1 only renders a subset)
3. UI package CSS files are pickable by Tailwind input CSS imports (already wired in Plan 01)
4. `internal/web/handlers_test.go` declares the exact test names from VALIDATION.md and targets the exact handler signatures Plan 03 will implement
5. `internal/db/pool_test.go` declares `TestPool_Connects` with a graceful skip when `DATABASE_URL` is unset
6. RED gate established via `//go:build red_gate` (Codex concern #3): `go test -tags=red_gate ./internal/web/... ./internal/db/...` fails with `undefined: web.NewRouter` / `db.NewPool` — this is intentional and is the target Plan 01-03 fills in. Default `go test ./...` succeeds because the gated files are excluded.
7. Component CSS uses non-nested selectors only (Codex concern #7): `.ui-button-solid-default-md:hover` and `:focus-visible` are top-level rules, not `&:hover` nested inside the base selector
</success_criteria>

<output>
After completion, create `.planning/phases/01-foundation/01-02-SUMMARY.md` documenting:
- Final list of exported symbols in `internal/web/ui/` (enums, props types, components)
- Final list of test names in `handlers_test.go` + `pool_test.go`
- The exact compile/test error message currently produced by `go test ./internal/web/` (this is the RED gate Plan 03 turns GREEN)
- Any deviations from UI-SPEC's Component Library Contract (there should be none)
</output>
