diff --git a/.planning/phases/04-tasks-kanban/04-UI-SPEC.md b/.planning/phases/04-tasks-kanban/04-UI-SPEC.md new file mode 100644 index 0000000..1bc7d8f --- /dev/null +++ b/.planning/phases/04-tasks-kanban/04-UI-SPEC.md @@ -0,0 +1,337 @@ +--- +phase: 4 +slug: tasks-kanban +status: draft +shadcn_initialized: false +preset: none +created: 2026-05-15 +--- + +# Phase 4 — UI Design Contract: Tasks (Kanban) + +> Visual and interaction contract for Phase 4. Generated by gsd-ui-researcher. +> Downstream consumers: gsd-ui-checker, gsd-planner, gsd-executor, gsd-ui-auditor. + +--- + +## Design System + +| Property | Value | Source | +|----------|-------|--------| +| Tool | none (hand-rolled CSS + Tailwind v4) | codebase — `backend/internal/web/ui/` | +| Preset | not applicable | no shadcn | +| Component library | Custom (`ui.Card`, `ui.Button`, `ui.Badge`, `ui.CSRFField`) | codebase — `backend/internal/web/ui/` | +| Icon library | none (text labels + drag handle via CSS `⠿` or plain `::before` dots) | CONTEXT.md — Claude's discretion | +| Font | system-ui, -apple-system, "Segoe UI", Roboto, sans-serif | codebase — `base.css` | + +shadcn gate: Not applicable — project is Go/HTMX, not React/Next.js/Vite. No `components.json` expected. + +--- + +## Spacing Scale + +Declared values (multiples of 4 only): + +| Token | Value | Usage | +|-------|-------|-------| +| xs | 4px | Icon gaps, drag-handle padding, color swatch gutter | +| sm | 8px | Task card internal padding (top/bottom), badge gap | +| md | 16px | Task card internal padding (left/right), form field gaps, column internal spacing | +| lg | 24px | Card padding (matches existing `.ui-card` padding: 1.5rem = 24px) | +| xl | 32px | Kanban board top margin below tablo header | +| 2xl | 48px | Empty state vertical padding | +| 3xl | 64px | Not used in this phase | + +Exceptions: +- Drag handle touch target: minimum 44px height on `.task-drag-handle` (matches Phase 3 danger/neutral button `min-height: 44px`) +- Column width: fixed `w-72` (288px = 18rem) — not on the 8-point scale but matches RESEARCH.md KanbanColumn templ example; consistent with CONTEXT.md D-10 + +--- + +## Typography + +All sizes derived from the existing codebase. No new type sizes introduced. + +| Role | Size | Weight | Line Height | Source | +|------|------|--------|-------------|--------| +| Body | 16px (1rem) | 400 | 1.5 | codebase — base.css, tablos.templ `text-base` | +| Label / small | 14px (0.875rem) | 500 | 1.25 | codebase — tablos.templ `text-sm font-medium` for field labels | +| Card heading | 20px (1.25rem) | 600 | 1.375 | codebase — tablos.templ `text-xl font-semibold leading-snug` | +| Page heading | 28px (1.75rem) | 600 | 1.2 | codebase — tablos.templ `text-[28px] font-semibold leading-tight` | + +Weights in use: 400 (regular) and 600 (semibold). No other weights. Consistent with Phase 3. + +Column header typography: 14px / weight 600 / line-height 1.25 — maps to existing `text-sm font-semibold text-slate-700` pattern from RESEARCH.md KanbanColumn templ example. + +--- + +## Color + +All hex values extracted directly from codebase CSS files (`base.css`, `button.css`, `card.css`, `badge.css`). + +| Role | Value | Usage | +|------|-------|-------| +| Dominant (60%) | `#ffffff` (white) | Page background (`bg-white` in Layout body) | +| Secondary (30%) | `#f8fafc` (slate-50) | Card backgrounds (`.ui-card` background, header `bg-slate-50`) | +| Accent (10%) | `#2563eb` (blue-600) | Reserved: primary action buttons, focus rings, link text hover | +| Destructive | `#b91c1c` (red-700) | Destructive actions only: delete confirm button, delete trigger | + +Accent reserved for: +1. "Add task" submit button (primary CTA per column) +2. "Save changes" button on task edit form +3. Focus ring on all interactive elements (`outline: 2px solid #2563eb`) +4. "View" link text on tablo cards (existing pattern: `text-blue-600`) + +Supporting surface colors (from existing codebase, not new decisions): +- Border: `#e2e8f0` (slate-200) — card borders, form input borders +- Muted text: `#64748b` (slate-500) and `#94a3b8` (slate-400) — placeholder, secondary copy +- Body text: `#0f172a` (slate-900) — primary text on white +- Secondary text: `#334155` (slate-700) and `#475569` (slate-600) — labels, descriptions + +Column header backgrounds: `#f1f5f9` (slate-100) — a light tinted band above each column's task list. Distinct from card white surface to visually anchor the column. Uses existing `bg-slate-100` Tailwind class (no new color). + +Task card (display state): white `#ffffff` background with `border: 1px solid #e2e8f0`. Matches `.ui-card` exactly — use `ui.Card` directly. + +Task card (drag ghost): `#f1f5f9` (slate-100) — Sortable.js `ghostClass: "bg-slate-100"` per RESEARCH.md Pattern 3. + +--- + +## Component Inventory + +All components used in this phase and their source: + +| Component | Type | Source | Notes | +|-----------|------|--------|-------| +| `ui.Card` | existing | `backend/internal/web/ui/card.templ` | Task card display state; use `templ.Attributes` for `data-task-id`, drag class | +| `ui.Button` (solid default md) | existing | `backend/internal/web/ui/button.templ` | "Add task" submit, "Save changes" on edit form | +| `ui.Button` (soft neutral md) | existing | `backend/internal/web/ui/button.templ` | "Cancel" on add-task form, "Discard changes" on edit form | +| `ui.Button` (solid danger md) | existing | `backend/internal/web/ui/button.templ` | "Yes, delete" in task delete confirmation | +| `ui.Button` (soft danger md) | new variant needed | `backend/internal/web/ui/button.css` | Task delete trigger button — same `.ui-button-soft-danger-md` class pattern as Phase 3's `soft-neutral`; add CSS rule | +| `ui.Badge` (info) | existing | `backend/internal/web/ui/badge.templ` | Task count per column header: `BadgeVariantInfo`, e.g. "3" | +| `ui.CSRFField` | existing | `backend/internal/web/ui/csrf_field.templ` | All forms including hidden reorder form | +| `KanbanBoard` | new templ component | `backend/templates/tasks.templ` | Wraps all 4 columns + hidden reorder form | +| `KanbanColumn` | new templ component | `backend/templates/tasks.templ` | Single column: header + sortable list + add-task slot | +| `TaskCard` | new templ component | `backend/templates/tasks.templ` | Display state; carries `.task-card` + `data-task-id` | +| `TaskEditFragment` | new templ component | `backend/templates/tasks.templ` | Inline edit form (title + description); carries `.task-card-zone` | +| `TaskCreateForm` | new templ component | `backend/templates/tasks.templ` | Inline add-task form at column bottom (title only) | +| `TaskDeleteConfirmFragment` | new templ component | `backend/templates/tasks.templ` | Inline confirmation: "Delete task?" + confirm/cancel | + +New CSS rule needed (add to `backend/internal/web/ui/button.css`): + +```css +.ui-button-soft-danger-md { + display: inline-flex; + align-items: center; + border-radius: 0.375rem; + background-color: #fee2e2; + padding: 0.5rem 1rem; + font-size: 1rem; + font-weight: 600; + color: #b91c1c; + border: 1px solid #fecaca; + min-height: 44px; +} +.ui-button-soft-danger-md:hover { + background-color: #fecaca; +} +.ui-button-soft-danger-md:focus-visible { + outline: 2px solid #b91c1c; + outline-offset: 2px; +} +``` + +--- + +## Interaction Contracts + +### § 1 — Kanban Board (TASK-01) + +**Route:** `GET /tablos/{id}` (extension of Phase 3 tablo detail page) + +**Layout:** Tablo title zone (Phase 3) + description zone (Phase 3) + delete zone (Phase 3) → then below: `
No tasks yet
` inside the sortable div, so the column still accepts drops (min-h-16 ensures drop zone height) + +### § 2 — Task Create (TASK-02) + +**Routes:** +- `GET /tablos/{id}/tasks/new?status={status}` — returns `TaskCreateForm` fragment +- `POST /tablos/{id}/tasks` — creates task, returns `TaskCard` fragment + OOB reset of add-task slot + +**Trigger:** `+ Add task` text button at bottom of each column. Rendered as ``. + +**Form layout:** +``` +[Title input — full width, required, placeholder "Task title"] +[Save (solid default md)] [Cancel (soft neutral md, restores trigger button without server round-trip via hx-get)] +``` + +Title: `` + +**On submit (HTMX):** New `TaskCard` appended to `#column-{status}` via `hx-target="#column-{status}" hx-swap="beforeend"`. Add-task slot reset to trigger button via OOB swap on `#add-task-slot-{status}`. + +**On cancel:** `hx-get="/tablos/{id}/tasks/cancel-new?status={status}"` returns `AddTaskTrigger` fragment — restores the `+ Add task` button without a POST. + +**Validation error:** Inline `@FieldError(errs.Title)` below title input (same pattern as Phase 3 `FieldError`). Form stays open with error copy inline. + +### § 3 — Task Edit (TASK-03) + +**Routes:** +- `GET /tablos/{id}/tasks/{task_id}/edit` — returns `TaskEditFragment` +- `POST /tablos/{id}/tasks/{task_id}` — saves, returns `TaskCard` + +**Trigger:** Clicking anywhere on a `TaskCard` (not the delete button, not the drag handle). `hx-get` on the card body `