From 5a141665838c797fb4cee2c0b074a26504cfedc1 Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Thu, 14 May 2026 16:53:08 +0200 Subject: [PATCH] docs(01): UI design contract for foundation phase Lock base layout, spacing/typography/color tokens, and the canonical HTMX interaction pattern that later phases inherit. Co-Authored-By: Claude Opus 4.7 (1M context) --- .planning/phases/01-foundation/01-UI-SPEC.md | 298 +++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 .planning/phases/01-foundation/01-UI-SPEC.md diff --git a/.planning/phases/01-foundation/01-UI-SPEC.md b/.planning/phases/01-foundation/01-UI-SPEC.md new file mode 100644 index 0000000..7a92b33 --- /dev/null +++ b/.planning/phases/01-foundation/01-UI-SPEC.md @@ -0,0 +1,298 @@ +--- +phase: 1 +slug: foundation +status: draft +shadcn_initialized: false +preset: none +created: 2026-05-14 +--- + +# Phase 1 — UI Design Contract + +> Visual and interaction contract for the Foundation phase. This phase ships a Walking Skeleton: a single Tailwind-styled root page rendered by templ with one working `hx-get` demo. The tokens declared here are load-bearing for every subsequent phase. shadcn is not applicable — the stack is Go templ + HTMX + Tailwind v4 standalone, with no Node/JS toolchain in `backend/`. + +--- + +## Design System + +| Property | Value | +|----------|-------| +| Tool | none (no shadcn — Go/templ/HTMX stack, no React) | +| Preset | not applicable | +| Component library | none (raw templ components + Tailwind utilities) | +| Icon library | none in Phase 1 (deferred — inline SVG when needed in Phase 3+) | +| Font | System font stack: `ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif` (Tailwind default `font-sans`) | + +**Rationale:** Phase 1 is a Walking Skeleton. Pulling in a webfont or icon library before there is any UI to motivate it adds bytes and bootstrap complexity for zero value. System fonts render instantly with no FOUT and align with the "single binary + static" thesis from CONTEXT D-10 / D-12. + +**Static assets (vendored, served from `/static/`, no CDN — per CONTEXT D-10):** +- `htmx.min.js` (HTMX v2.x) +- `tailwind.css` (compiled by Tailwind standalone CLI v4.x) + +--- + +## Spacing Scale + +Declared values (multiples of 4, mapped 1:1 to Tailwind defaults — no custom spacing tokens in Phase 1): + +| Token | Value | Tailwind utility | Usage | +|-------|-------|------------------|-------| +| xs | 4px | `*-1` | Inline icon gaps, tight inline padding | +| sm | 8px | `*-2` | Compact element gaps, button inner padding-y | +| md | 16px | `*-4` | Default element spacing, button inner padding-x, form field gaps | +| lg | 24px | `*-6` | Section internal padding, card padding | +| xl | 32px | `*-8` | Layout gaps between sections, page header padding-y | +| 2xl | 48px | `*-12` | Major section breaks | +| 3xl | 64px | `*-16` | Page-level vertical rhythm, hero spacing | + +**Container:** Page content is constrained to `max-w-5xl` (1024px) and centered with `mx-auto`. Horizontal page padding: `px-4` on mobile (<640px), `px-6` from `sm:` up. + +**Exceptions:** none. Phase 1 has no touch-target-only elements; the `hx-get` demo button uses `md`/`sm` padding which yields >40px height. + +--- + +## Typography + +Three sizes, two weights — enforced through Tailwind utility classes. + +| Role | Size | Tailwind | Weight | Line Height | +|------|------|----------|--------|-------------| +| Body | 16px | `text-base` | 400 (`font-normal`) | 1.5 (`leading-normal`) | +| Label / small | 14px | `text-sm` | 400 (`font-normal`) | 1.5 (`leading-normal`) | +| Heading (H2 / section) | 20px | `text-xl` | 600 (`font-semibold`) | 1.3 (`leading-snug`) | +| Display (H1 / page title) | 28px | `text-3xl` adjusted via `text-[28px]` or default `text-2xl` (24px) acceptable | 600 (`font-semibold`) | 1.2 (`leading-tight`) | + +**Rule:** Only weights 400 and 600 may appear in Phase 1 markup. No 500, no 700. +**Rule:** Only the four sizes above may appear. No `text-lg`, `text-2xl` etc. unless they match the declared px value above. + +**Heading element mapping:** +- Page title (root page `/`): `

` styled with the Display row. +- Demo section heading: `

` styled with the Heading row. +- Body copy and the timestamp fragment: Body row. +- The `hx-get` button label: Body row, weight 600 allowed for button text only. + +--- + +## Color + +Single palette, light-mode only in Phase 1. Dark mode is deferred (no requirement from FOUND-01..05). All values are Tailwind v4 default palette references to keep the contract checkable from CSS alone. + +| Role | Value | Tailwind class | Usage | +|------|-------|---------------|-------| +| Dominant (60%) | `#ffffff` | `bg-white` | Page background, content surface | +| Secondary (30%) | `#f8fafc` (slate-50) | `bg-slate-50` | Header strip, demo card background, any panel that needs visual separation from the page | +| Foreground primary | `#0f172a` (slate-900) | `text-slate-900` | All headings and body text | +| Foreground muted | `#475569` (slate-600) | `text-slate-600` | Secondary copy, helper text, the timestamp fragment when idle | +| Border | `#e2e8f0` (slate-200) | `border-slate-200` | Card borders, divider lines | +| Accent (10%) | `#2563eb` (blue-600) | `bg-blue-600`, `text-blue-600`, `hover:bg-blue-700` | **Reserved for: the primary `hx-get` demo button only.** Hover state: `blue-700`. Focus ring: `ring-2 ring-blue-600/40 ring-offset-2`. | +| Accent foreground | `#ffffff` | `text-white` | Text on accent backgrounds (button label) | +| Destructive | `#dc2626` (red-600) | `text-red-600`, `bg-red-600` | Not used in Phase 1 (no destructive actions exist). Declared here so Phase 3+ inherits the token. | +| Success indicator | `#16a34a` (green-600) | `text-green-600` | `/healthz` "ok" badge if surfaced visually. Not strictly required for Phase 1 but reserved. | + +**Accent reserved for:** the `hx-get` demo button on the root page. Nothing else in Phase 1 may use `blue-*`. No links, no headings, no underlines in the brand blue. + +**60/30/10 split:** ~60% white surface, ~30% slate-50 strip/card, ~10% accent confined to the single primary button. + +--- + +## Copywriting Contract + +| Element | Copy | +|---------|------| +| Page `` | `Xtablo — Foundation` | +| Page H1 | `Xtablo` | +| Page subtitle (under H1, muted) | `Go + HTMX foundation. Sign-in and the Tablos workflow ship in later phases.` | +| Demo section H2 | `HTMX demo` | +| Demo helper text (above button) | `Click the button to fetch the server time as an HTML fragment.` | +| Primary CTA (the only button) | `Fetch server time` | +| Initial fragment placeholder (`#demo-out` empty state) | `No time fetched yet.` (rendered in `text-slate-600`) | +| Fragment success payload | `<span>{ISO-8601 UTC timestamp}</span>` — e.g. `2026-05-14T14:42:38Z` | +| HTMX failure copy (server 5xx, rendered via `hx-target-error` or fallback) | `Could not reach the server. Refresh the page and try again.` | +| `/healthz` JSON success | `{"status":"ok","db":"ok"}` (locked in CONTEXT D-20) | +| `/healthz` JSON degraded | `{"status":"degraded","db":"down"}` (locked in CONTEXT D-20) | +| Footer (small, muted, optional) | `Phase 1 · Walking skeleton` | + +**Destructive actions:** none in Phase 1. The contract row exists for downstream phases but is intentionally empty here. + +**Tone rules:** +- Friendly, declarative, no marketing voice. +- No emoji. +- No exclamation marks. +- Sentence case for headings (not Title Case). +- Errors describe the problem and the next action ("Refresh the page and try again."), never just "Error". + +--- + +## Base Layout Contract + +The base templ layout (`templates/layout.templ`) is the foundation every later phase extends. It must contain exactly the following structural regions: + +``` +<html lang="en"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>{title} + + + +
+
+ +
+
+
+ { children... } +
+
+ Phase 1 · Walking skeleton +
+ + + +``` + +**Rules:** +- `htmx.min.js` is loaded once, at the bottom of ``, with `defer`. +- `tailwind.css` is loaded in `` so there is no FOUC. +- No inline `