docs(05): UI design contract for files phase
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7d65cb4d94
commit
e939563a49
1 changed files with 228 additions and 0 deletions
228
.planning/phases/05-files/05-UI-SPEC.md
Normal file
228
.planning/phases/05-files/05-UI-SPEC.md
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
---
|
||||||
|
phase: 5
|
||||||
|
slug: files
|
||||||
|
status: draft
|
||||||
|
shadcn_initialized: false
|
||||||
|
preset: none
|
||||||
|
created: 2026-05-15
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 5 — UI Design Contract
|
||||||
|
|
||||||
|
> Visual and interaction contract for Phase 5: Files (tab navigation + file upload/list/download/delete).
|
||||||
|
> Generated by gsd-ui-researcher. Pre-populated from codebase scan of implemented templates and design system CSS.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Design System
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
|----------|-------|
|
||||||
|
| Tool | none — hand-rolled CSS design system in `backend/internal/web/ui/` |
|
||||||
|
| Preset | not applicable |
|
||||||
|
| Component library | Custom: `ui.Button`, `ui.Card`, `ui.Badge`, `ui.CSRFField` (templ components) |
|
||||||
|
| Icon library | none — text labels only in v1 |
|
||||||
|
| Font | system-ui, -apple-system, "Segoe UI", Roboto, sans-serif (OS stack, no web font loaded) |
|
||||||
|
|
||||||
|
Source: `backend/internal/web/ui/base.css` (verified).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Spacing Scale
|
||||||
|
|
||||||
|
Declared values (multiples of 4 — Tailwind utility classes enforce this):
|
||||||
|
|
||||||
|
| Token | Value | Usage |
|
||||||
|
|-------|-------|-------|
|
||||||
|
| xs | 4px (`gap-1`, `px-1`) | Icon gaps, tight inline padding |
|
||||||
|
| sm | 8px (`px-2`, `py-2`, `gap-2`) | Compact element spacing, badge padding |
|
||||||
|
| md | 16px (`px-4`, `py-4`, `gap-4`) | Default element spacing, main content padding |
|
||||||
|
| lg | 24px (`px-6`, `py-6`, `gap-6`) | Section padding, card interior |
|
||||||
|
| xl | 32px (`mb-8`) | Layout section breaks |
|
||||||
|
| 2xl | 48px — | Major section breaks (not used in Phase 5) |
|
||||||
|
| 3xl | 64px — | Page-level spacing (not used in Phase 5) |
|
||||||
|
|
||||||
|
Exceptions:
|
||||||
|
- Touch targets on delete/download actions: minimum 44px height enforced via `min-height: 44px` on `.ui-button-solid-danger-md` and `.ui-button-soft-neutral-md` (source: `button.css` lines 61, 80).
|
||||||
|
- File input vertical rhythm: `mt-1` (4px) label-to-input gap follows existing form pattern from `tablos.templ`.
|
||||||
|
|
||||||
|
Source: scanned `button.css`, `card.css`, `base.css`, `files.templ`, `tablos.templ`, `layout.templ`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Typography
|
||||||
|
|
||||||
|
| Role | Size | Weight | Line Height |
|
||||||
|
|------|------|--------|-------------|
|
||||||
|
| Body | 16px (`text-base`) | 400 regular | 1.5 (Tailwind default) |
|
||||||
|
| Label / small text | 14px (`text-sm`) | 400 regular | 1.25 |
|
||||||
|
| Small metadata | 12px (`text-xs`) | 400 regular | 1.25 |
|
||||||
|
| Heading | 20px (`text-xl`) | 600 semibold | tight (1.2) |
|
||||||
|
| Display | 28px (`text-[28px]`) | 600 semibold | tight (1.2) |
|
||||||
|
|
||||||
|
All sizes pre-populated from codebase scan:
|
||||||
|
- `text-[28px] font-semibold` — dashboard h1 (`tablos.templ` line 15)
|
||||||
|
- `text-xl font-semibold` — card/form headings (`tablos.templ` lines 73, 110)
|
||||||
|
- `text-sm font-medium` — labels, file row filenames (`files.templ` lines 50, 76)
|
||||||
|
- `text-xs` — metadata (file size, date, badge labels) (`files.templ` line 79)
|
||||||
|
|
||||||
|
Phase 5 introduces no new type sizes. Four distinct sizes in use; two weights (400, 600). Contract is locked.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Color
|
||||||
|
|
||||||
|
| Role | Value | Usage |
|
||||||
|
|------|-------|-------|
|
||||||
|
| Dominant (60%) | `#ffffff` white | Page background, body (`base.css` line 22) |
|
||||||
|
| Secondary (30%) | `#f8fafc` slate-50 / `#f1f5f9` slate-100 | Cards (`.ui-card`), upload form background, header strip, empty-state panel |
|
||||||
|
| Accent (10%) | `#2563eb` blue-600 | Primary action buttons, focus rings, download links |
|
||||||
|
| Destructive | `#b91c1c` red-700 | Delete confirm button (solid danger variant) |
|
||||||
|
|
||||||
|
Accent (`#2563eb`) reserved for:
|
||||||
|
- Primary CTA button fill (`.ui-button-solid-default-md`)
|
||||||
|
- Hyperlink text color (`text-blue-600`) — Download link in `FileListRow`
|
||||||
|
- Focus outline (`outline: 2px solid #2563eb` on `:focus-visible`)
|
||||||
|
|
||||||
|
Supporting semantic colors (not accent, not destructive):
|
||||||
|
- `#334155` slate-700 — body text, form labels, log-out button
|
||||||
|
- `#64748b` slate-500 — secondary metadata text (file size, date)
|
||||||
|
- `#94a3b8` slate-400 — placeholder text, empty state italic copy
|
||||||
|
- `#e2e8f0` slate-200 — borders on cards, file list dividers, input borders
|
||||||
|
- `#fee2e2` red-100 / `#fecaca` red-200 — soft-danger button background (delete trigger before confirm)
|
||||||
|
- `#fef2f2` red-50 / `#fca5a5` red-300 — upload error message panel background/border
|
||||||
|
|
||||||
|
Source: `base.css`, `button.css`, `card.css`, `files.templ` (all lines scanned).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Interaction Contract
|
||||||
|
|
||||||
|
This section documents the HTMX interaction model for Phase 5's two new UI surfaces: the tab bar and the files tab.
|
||||||
|
|
||||||
|
### Tab Bar (new in Phase 5)
|
||||||
|
|
||||||
|
- Three tabs: **Overview** / **Tasks** / **Files** on the tablo detail page.
|
||||||
|
- Active tab: underline or filled indicator distinguishing the selected tab from inactive.
|
||||||
|
- Tab click: `hx-get` to the sub-route + `hx-target="#tab-content"` + `hx-swap="innerHTML"` + `hx-push-url` to update the browser URL.
|
||||||
|
- Direct load (no HTMX): full page rendered with the appropriate tab content embedded in `#tab-content`.
|
||||||
|
- Default tab on `/tablos/{id}`: Overview (per CONTEXT.md Claude's Discretion — Overview is the natural default).
|
||||||
|
- Active tab indicator class: `tab-active` (underline style, `border-b-2 border-blue-600 text-slate-900`) vs inactive (`text-slate-600 hover:text-slate-800`).
|
||||||
|
|
||||||
|
### Upload Form (Files tab)
|
||||||
|
|
||||||
|
- Always visible at the top of the Files tab — not collapsed behind a button (simpler; reduces interaction friction for a core action).
|
||||||
|
- Form: `method="POST"` + `enctype="multipart/form-data"` + `hx-post` + `hx-encoding="multipart/form-data"`.
|
||||||
|
- On success: HTMX swaps `#tab-content` `innerHTML` with the refreshed files list (upload form + updated list).
|
||||||
|
- On size error (> 25 MB): server returns 422 + `UploadErrorFragment`; red error panel renders above the file input.
|
||||||
|
- CSRF token embedded via `@ui.CSRFField(csrfToken)` inside the form.
|
||||||
|
|
||||||
|
### File List
|
||||||
|
|
||||||
|
- Sorted newest-first (server-side, `ORDER BY created_at DESC` in sqlc query).
|
||||||
|
- Each row: filename (truncated if long), human-readable size, upload date, Download link, Delete button.
|
||||||
|
- Download: standard `<a href>` pointing to `GET /tablos/{id}/files/{file_id}/download` — server returns 302 to signed URL (no `target="_blank"` needed; redirect handles the navigation).
|
||||||
|
- Delete trigger: HTMX `hx-get` to delete-confirm route; replaces the `.file-row-zone` with inline confirmation (same pattern as task delete in Phase 4).
|
||||||
|
|
||||||
|
### Delete Confirmation
|
||||||
|
|
||||||
|
- Inline in the file row — replaces the row content via `hx-target="closest .file-row-zone"` + `hx-swap="outerHTML"`.
|
||||||
|
- Confirm button: solid danger (`Yes, delete`) — `POST /tablos/{id}/files/{file_id}/delete`.
|
||||||
|
- Cancel button: soft neutral (`Keep file`) — `hx-get` back to the files list, restoring the original row.
|
||||||
|
- On confirmed delete: server returns `FileRowGone` — empty div with same id, removing the row from the DOM.
|
||||||
|
|
||||||
|
### Loading States
|
||||||
|
|
||||||
|
- Button `htmx-request` class: `.ui-button.htmx-request { opacity: 0.6; pointer-events: none; }` — applied by HTMX automatically during in-flight requests (source: `button.css` lines 24–27).
|
||||||
|
- No spinner or skeleton — opacity feedback is sufficient for v1.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Copywriting Contract
|
||||||
|
|
||||||
|
| Element | Copy |
|
||||||
|
|---------|------|
|
||||||
|
| Primary CTA (upload) | "Upload" |
|
||||||
|
| File input label | "Attach a file" |
|
||||||
|
| Empty state body | "No files attached yet." |
|
||||||
|
| Delete trigger | "Delete" (inline text link, red) |
|
||||||
|
| Delete confirm heading | "Delete file?" |
|
||||||
|
| Delete confirm body | "{filename}" (filename shown truncated for context) + "This cannot be undone." |
|
||||||
|
| Delete confirm CTA | "Yes, delete" |
|
||||||
|
| Delete cancel | "Keep file" |
|
||||||
|
| Error state — file too large | "File too large (max 25 MB)." (shown above the upload form in a red panel) |
|
||||||
|
| Error state — upload failure | "Upload failed. Please try again." (generic server error path) |
|
||||||
|
| Download link | "Download" (inline text link, blue) |
|
||||||
|
|
||||||
|
Source: `files.templ` lines 44–66, 88–95, 107–110, 149–151. All copy is locked from the implemented template — no changes needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Component Inventory
|
||||||
|
|
||||||
|
New templ components introduced in Phase 5 (all in `backend/templates/files.templ`):
|
||||||
|
|
||||||
|
| Component | Description |
|
||||||
|
|-----------|-------------|
|
||||||
|
| `FilesTabFragment` | Upload form + file list; rendered as fragment (HTMX) or embedded in `TabloDetailPage` |
|
||||||
|
| `FileUploadForm` | Multipart form with CSRF field, optional error banner, file input, Upload button |
|
||||||
|
| `FileListRow` | Single file row: filename, size, date, Download link, Delete trigger |
|
||||||
|
| `FileDeleteConfirmFragment` | Inline delete confirmation replacing the file row zone |
|
||||||
|
| `FileListEmpty` | Empty state paragraph: "No files attached yet." |
|
||||||
|
| `FileRowGone` | Empty div with file id for HTMX outerHTML removal on delete |
|
||||||
|
| `UploadErrorFragment` | Full tab re-render with error message set in `FileUploadForm` |
|
||||||
|
|
||||||
|
Modified templ components:
|
||||||
|
|
||||||
|
| Component | Change |
|
||||||
|
|-----------|--------|
|
||||||
|
| `TabloDetailPage` (in `tablos.templ`) | Signature extended: `(user, csrfToken, tablo, tasks, files, activeTab string)`. Tab bar added. `#tab-content` area dispatches on `activeTab`. |
|
||||||
|
|
||||||
|
Reused design system components (unchanged):
|
||||||
|
|
||||||
|
| Component | Usage in Phase 5 |
|
||||||
|
|-----------|-----------------|
|
||||||
|
| `ui.Button` | Upload submit, delete confirm, keep-file cancel |
|
||||||
|
| `ui.Card` | Not used in files tab directly; file list uses a `<ul>` with divide-y border |
|
||||||
|
| `ui.CSRFField` | All POST forms (upload, delete confirm) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Registry Safety
|
||||||
|
|
||||||
|
| Registry | Blocks Used | Safety Gate |
|
||||||
|
|----------|-------------|-------------|
|
||||||
|
| shadcn official | none — project does not use shadcn | not applicable |
|
||||||
|
| third-party registries | none | not applicable |
|
||||||
|
|
||||||
|
No external component registries. All UI components are hand-rolled in `backend/internal/web/ui/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checker Sign-Off
|
||||||
|
|
||||||
|
- [ ] Dimension 1 Copywriting: PASS
|
||||||
|
- [ ] Dimension 2 Visuals: PASS
|
||||||
|
- [ ] Dimension 3 Color: PASS
|
||||||
|
- [ ] Dimension 4 Typography: PASS
|
||||||
|
- [ ] Dimension 5 Spacing: PASS
|
||||||
|
- [ ] Dimension 6 Registry Safety: PASS
|
||||||
|
|
||||||
|
**Approval:** pending
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pre-Population Sources
|
||||||
|
|
||||||
|
| Source | Decisions Used |
|
||||||
|
|--------|---------------|
|
||||||
|
| `backend/internal/web/ui/base.css` | Font stack, body color (#0f172a), background (#ffffff), focus ring (#2563eb) |
|
||||||
|
| `backend/internal/web/ui/button.css` | Button variants (solid-default-md, solid-danger-md, soft-neutral-md, soft-danger-md), min-height 44px touch targets, accent color, destructive color |
|
||||||
|
| `backend/internal/web/ui/card.css` | Card background (#f8fafc), border color (#e2e8f0), border-radius (0.5rem), padding (1.5rem) |
|
||||||
|
| `backend/internal/web/ui/badge.css` | Badge sizes (12px), info/success/danger semantic colors |
|
||||||
|
| `backend/templates/files.templ` | All copywriting strings, component structure, interaction targets, HTMX attributes |
|
||||||
|
| `backend/templates/tablos.templ` | Display heading size (28px), card heading size (20px), spacing patterns |
|
||||||
|
| `backend/templates/layout.templ` | Max container width (max-w-5xl), header/main/footer structure, asset references |
|
||||||
|
| `.planning/phases/05-files/05-CONTEXT.md` | D-07 (3-tab layout), D-08 (HTMX+hx-push-url), D-09 (25 MB limit, friendly error), upload always visible (Claude's discretion), inline delete (Claude's discretion), 302 redirect for download (Claude's discretion) |
|
||||||
|
| `.planning/phases/05-files/05-RESEARCH.md` | Standard stack (no shadcn, custom CSS), HTMX fragment patterns, tab navigation architecture |
|
||||||
|
| User input | 0 — all fields pre-populated from codebase and upstream artifacts |
|
||||||
Loading…
Reference in a new issue