From c779d2aee1149046ac8d47b8890794b4501d117f Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Thu, 14 May 2026 23:39:48 +0200 Subject: [PATCH] docs(03): UI design contract for Tablos CRUD phase Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .planning/phases/03-tablos-crud/03-UI-SPEC.md | 322 ++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 .planning/phases/03-tablos-crud/03-UI-SPEC.md diff --git a/.planning/phases/03-tablos-crud/03-UI-SPEC.md b/.planning/phases/03-tablos-crud/03-UI-SPEC.md new file mode 100644 index 0000000..0a0227c --- /dev/null +++ b/.planning/phases/03-tablos-crud/03-UI-SPEC.md @@ -0,0 +1,322 @@ +--- +phase: 3 +slug: tablos-crud +status: draft +shadcn_initialized: false +preset: none +created: 2026-05-14 +--- + +# Phase 3 — UI Design Contract + +> Visual and interaction contract for Phase 3: Tablos CRUD. +> Generated by gsd-ui-researcher. Stack is Go + templ + HTMX + Tailwind v4 — no React, no shadcn. + +--- + +## Design System + +| Property | Value | +|----------|-------| +| Tool | none (plain HTML + Tailwind v4) | +| Preset | not applicable | +| Component library | ui package — `backend/internal/web/ui/` (Card, Button, Badge, CSRFField) | +| Icon library | none — Phase 3 uses text labels and Unicode characters only | +| Font | system-ui, -apple-system, "Segoe UI", Roboto, sans-serif (inherited from base.css) | + +shadcn gate: Not applicable. Stack is Go + templ, not React/Next.js/Vite. + +--- + +## Base Layout Contract + +Inherited from Phase 1/2 (`backend/templates/layout.templ`). Do NOT modify the shell. + +| Property | Value | Source | +|----------|-------|--------| +| Container max-width | max-w-5xl | layout.templ — locked | +| Horizontal padding | px-4 sm:px-6 | layout.templ — locked | +| Main content top padding | py-8 | layout.templ — locked | +| Header background | bg-slate-50 border-b border-slate-200 | layout.templ — locked | +| Body background | bg-white | layout.templ (base.css) — locked | +| HTMX script | `/static/htmx.min.js` deferred — no CDN | layout.templ — locked | + +The footer text "Phase 2 · Authentication" must be updated to "Phase 3 · Tablos" in layout.templ. + +--- + +## Spacing Scale + +Declared values (multiples of 4 only): + +| Token | Value | Tailwind class | Usage | +|-------|-------|----------------|-------| +| xs | 4px | gap-1, p-1 | Icon gaps, inline chip padding | +| sm | 8px | gap-2, p-2 | Compact element spacing, badge padding | +| md | 16px | gap-4, p-4 | Default element spacing, card internal gap | +| lg | 24px | gap-6, p-6 | Card padding (matches `.ui-card` 1.5rem) | +| xl | 32px | gap-8, mt-8 | Section breaks within page | +| 2xl | 48px | gap-12 | Major section separators | +| 3xl | 64px | — | Not used in Phase 3 | + +Exceptions: +- Touch-target minimum 44px height for all interactive buttons (per WCAG 2.5.5). Achieved via `py-2 px-4` on `.ui-button-solid-default-md` (already 40px rendered; add `min-h-[44px]` when implementing new danger/neutral button variants for Phase 3). +- Form `space-y-5` (20px) is an existing pattern from auth forms — reuse exactly for tablo create/edit forms. + +--- + +## Typography + +All sizes sourced from existing codebase patterns in `templates/` and `ui/`. + +| Role | Size | Weight | Line Height | Tailwind classes | Usage | +|------|------|--------|-------------|-----------------|-------| +| Display | 28px | 600 (semibold) | 1.2 (tight) | `text-[28px] font-semibold leading-tight` | Dashboard page heading ("Your Tablos") | +| Heading | 20px | 600 (semibold) | 1.3 (snug) | `text-xl font-semibold leading-snug` | Tablo card title, detail page section headings | +| Body | 16px | 400 (regular) | 1.5 | `text-base text-slate-600` | Tablo descriptions, paragraph content | +| Label | 14px | 500 (medium) | 1.4 | `text-sm font-medium text-slate-700` | Form labels, metadata (created date, owner email) | +| Small/muted | 14px | 400 (regular) | 1.4 | `text-sm text-slate-500` | "Signed in as" subtext, timestamps | + +Two declared weights: 400 (regular) and 600 (semibold). The 500 (medium) weight is used only for form labels — this follows the existing auth form pattern (`text-sm font-medium text-slate-700`). + +--- + +## Color + +| Role | Hex | Tailwind | Usage | +|------|-----|----------|-------| +| Dominant surface (60%) | #ffffff | bg-white | Page background, form inputs | +| Secondary surface (30%) | #f8fafc | bg-slate-50 | Cards (`ui-card`), header strip, input focus hover | +| Accent (10%) | #2563eb | bg-blue-600 | Primary CTA button only (see accent reserved list) | +| Destructive | #b91c1c | bg-red-700 / text-red-700 | Delete confirmation button, field error text, general error banner | + +Accent (#2563eb / blue-600) reserved for: +1. Primary action button — "New tablo" (create) and "Save" (edit submit) +2. Focus ring on all interactive elements (`outline: 2px solid #2563eb`) +3. Nothing else — links, secondary actions, and navigation use slate tones + +Borders: slate-200 (`#e2e8f0`) for cards and input default state. slate-300 (`#cbd5e1`) for input borders. + +Text: slate-900 (`#0f172a`) for primary content, slate-700 (`#334155`) for labels, slate-600 (`#475569`) for secondary text, slate-500 (`#64748b`) for muted/metadata text. + +Tablo color field rendering: render as a 10px × 10px filled dot (CSS circle using `inline-block w-2.5 h-2.5 rounded-full`) on the dashboard card, using the stored hex or Tailwind color as inline `background-color` style. If `color` is null or empty, omit the dot entirely. No color picker UI in Phase 3 — accept hex via plain text input. + +--- + +## Component Inventory + +### Existing (reuse without modification) + +| Component | Import | Usage in Phase 3 | +|-----------|--------|-----------------| +| `ui.Card` | `backend/internal/web/ui` | Tablo list card, create form container | +| `ui.Button` (solid/default/md) | `backend/internal/web/ui` | "New tablo" CTA, "Save" on edit, "Create tablo" submit | +| `ui.Badge` | `backend/internal/web/ui` | Optional: status labels if needed | +| `ui.CSRFField` | `backend/internal/web/ui` | All forms (create, edit, delete) — AUTH-06 | +| `templates.FieldError` | `backend/templates` | Inline field validation on create/edit forms | +| `templates.GeneralError` | `backend/templates` | General error banner on form submission failures | +| `templates.Layout` | `backend/templates` | All tablo pages | + +### New (must be implemented in Phase 3) + +| Component | File | Description | +|-----------|------|-------------| +| `templates.TablosDashboard` | `backend/templates/tablos.templ` | Full dashboard page (tablo list + create form slot) | +| `templates.TablosListFragment` | `backend/templates/tablos.templ` | HTMX-swappable list of tablo cards (reused on create/delete) | +| `templates.TabloCard` | `backend/templates/tablos.templ` | Single tablo card (title, description excerpt, color dot, delete button) | +| `templates.TabloCreateFormFragment` | `backend/templates/tablos.templ` | Inline create form (HTMX swap on validation error) | +| `templates.TabloDetailPage` | `backend/templates/tablos.templ` | Tablo detail page with inline edit areas | +| `templates.TabloTitleDisplay` | `backend/templates/tablos.templ` | Display fragment for title (swapped to edit input on click) | +| `templates.TabloTitleEditFragment` | `backend/templates/tablos.templ` | Edit input fragment for title | +| `templates.TabloDescDisplay` | `backend/templates/tablos.templ` | Display fragment for description | +| `templates.TabloDescEditFragment` | `backend/templates/tablos.templ` | Edit textarea fragment for description | +| `templates.TabloDeleteButtonFragment` | `backend/templates/tablos.templ` | Delete button (swaps to confirmation row) | +| `templates.TabloDeleteConfirmFragment` | `backend/templates/tablos.templ` | "Delete tablo? Yes / Cancel" confirmation row | +| `ui.ButtonDanger` (soft/danger/md CSS) | `backend/internal/web/ui/button.css` | Delete-confirm "Yes, delete" button (red variant) | +| `ui.ButtonNeutral` (soft/neutral/md CSS) | `backend/internal/web/ui/button.css` | "Cancel" button (slate/ghost variant) | + +--- + +## Interaction Contracts + +### 1. Dashboard — Tablo List + +- Route: `GET /` +- Template: `templates.TablosDashboard` +- Container: `
` wraps the list of `TabloCard` items. +- Sort: newest-first (from DB `ORDER BY created_at DESC`). +- Empty state: when no tablos exist, render the empty state inside `#tablos-list` (see Copywriting section). +- No HTMX on initial load — full page render. + +### 2. Create Tablo — Inline Form + +- Trigger: "New tablo" button (blue, solid, md) fires `hx-get="/tablos/new"` targeting `#create-form-slot` with `hx-swap="innerHTML"`. +- `#create-form-slot` is an empty `
` above `#tablos-list` in the dashboard. +- Form: `hx-post="/tablos"` → `hx-target="#create-form-slot"` → `hx-swap="innerHTML"`. +- On validation error (422): server returns the form fragment with field errors. Form re-renders in-place. +- On success (200 + HX-Trigger or fragment): server returns an empty `#create-form-slot` (form collapses) AND the new tablo card is prepended to `#tablos-list` using `hx-swap="afterbegin"` on the list, or via `HX-Retarget`/`HX-Reswap` response headers. Preferred approach: use `HX-Retarget: #tablos-list` + `HX-Reswap: afterbegin` response headers from the handler so a single HTMX response both clears the form slot and prepends the card. +- Non-HTMX fallback: standard `POST /tablos` → `303 /` redirect. +- Form fields: Title (required, text input), Description (optional, textarea 3 rows), Color (optional, text input with placeholder "#6366f1 or indigo"). + +### 3. Tablo Detail — View + +- Route: `GET /tablos/{id}` +- Non-owner or unauthenticated: 404 (no 403 per D-04). +- Template: `templates.TabloDetailPage` extending `Layout`. +- Title and description rendered as display fragments with an "Edit" affordance. + +### 4. Edit Tablo — Inline on Detail Page + +- Trigger: Clicking the title or description area fires `hx-get="/tablos/{id}/edit-title"` (or `/edit-desc`) targeting the respective display element with `hx-swap="outerHTML"`. +- Edit fragment renders an `` (title) or `