From 30c446fc0eb4d3378e6606258815641b127b1fc3 Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Sat, 16 May 2026 23:02:25 +0200 Subject: [PATCH] docs(16): UI design contract --- .../phases/16-tablo-detail/16-UI-SPEC.md | 125 +++++++++++------- 1 file changed, 77 insertions(+), 48 deletions(-) diff --git a/.planning/phases/16-tablo-detail/16-UI-SPEC.md b/.planning/phases/16-tablo-detail/16-UI-SPEC.md index ef90da1..72b32e2 100644 --- a/.planning/phases/16-tablo-detail/16-UI-SPEC.md +++ b/.planning/phases/16-tablo-detail/16-UI-SPEC.md @@ -40,7 +40,8 @@ Declared values (multiples of 4 only). Token names map to CSS `rem` values used | Token | Value | Usage | |-------|-------|-------| | xs | 4px (0.25rem) | Icon gaps, tight inline spacing | -| sm | 8px (0.5rem) | Compact badges, task-meta gap, column header internal gap | +| sm | 8px (0.5rem) | Compact badges, task-meta gap, column header internal gap, etape group header padding-block | +| sm-plus | 12px (0.75rem) | Tab nav padding-bottom, task row gap, compact list padding (between sm and md; 8px is too tight, 16px is too loose for medium-density list items) | | md | 16px (1rem) | Default element spacing, card padding, row padding | | lg | 24px (1.5rem) | Section heading margin-bottom, dashboard-main gap | | xl | 32px (2rem) | Page-level padding (`dashboard-main`) | @@ -48,9 +49,9 @@ Declared values (multiples of 4 only). Token names map to CSS `rem` values used | 3xl | 64px (4rem) | Not used in this phase | Exceptions: -- Touch targets (Discussion link, Invite button, Delete icon, tab nav items): minimum 44px height — matches existing `min-h-[44px]` pattern in tabs and `td.text-right .borderless-icon-button` at `min-height: 44px`. +- Touch targets (Discussion link, Invite Member button, Delete icon, tab nav items): minimum 44px height — matches existing `min-h-[44px]` pattern in tabs and `td.text-right .borderless-icon-button` at `min-height: 44px`. - Kanban column width: fixed `18rem` (`w-72` equivalent) per existing `KanbanColumn` width. -- Etape group sub-heading: 12px (0.75rem) vertical padding, 16px horizontal — visually subordinate to column header. +- Etape group sub-heading: 8px (0.5rem) vertical padding, 16px horizontal — visually subordinate to column header. Source: `app.css` measured values, CONTEXT.md D-K02, go-backend reference CSS. @@ -61,17 +62,19 @@ Source: `app.css` measured values, CONTEXT.md D-K02, go-backend reference CSS. | Role | Size | Weight | Line Height | Token / Class | |------|------|--------|-------------|---------------| | Body | 15px (0.95rem) | 400 (normal) | 1.5 | task-body p, task-row text | -| Label | 12px (0.75rem) | 400 (normal) | 1.4 | `.task-meta`, `.project-date-row`, column count badge | -| Heading | 16px (1rem) | 600 (semibold) | 1.2 | Column header `h3` (`.tasks-section-header h3` overridden to 1rem for kanban context; see note) | -| Display | 25.6px (1.6rem) | 600 (semibold) | 1.2 | Section headings `.overview-section-heading h3`, `.tasks-section-header h3` base rule | +| Label | 14px (0.875rem) | 400 (normal) | 1.4 | `.task-meta`, `.project-date-row`, `.tab-nav-item`, `.tablo-metadata-date`, `.task-list-empty`, column count badge, etape labels | +| Heading | 16px (1rem) | 600 (semibold) | 1.2 | Column header `h3` (`.tasks-section-header h3` scoped to 1rem inside kanban; see note) | +| Display | 28px (1.75rem) | 600 (semibold) | 1.2 | Tablo title (`.tablo-title-zone`), section headings `.overview-section-heading h3`, `.tasks-section-header h3` base rule | -Note on column headers: The base `.tasks-section-header h3` rule is 1.6rem (26px) — designed for full-width list views. Inside the 3-column kanban layout the column is 18rem wide, so column header `h3` is scoped with `.kanban-column .tasks-section-header h3 { font-size: 1rem; }` to fit without overflow. The base rule remains unchanged for future list-view use. +Note on column headers: The base `.tasks-section-header h3` rule is 1.75rem — designed for full-width list views. Inside the 3-column kanban layout the column is 18rem wide, so column header `h3` is scoped with `.kanban-column .tasks-section-header h3 { font-size: 1rem; }` to fit without overflow. The base rule remains unchanged for future list-view use. Tablo title (inline-editable `tablo-title-zone`): -- Display size: 24–30px responsive (existing `text-xl md:text-3xl` pattern, replaced with token-based `font-size: 1.75rem`). -- Weight: 700 (bold). +- Display size: 1.75rem (shared with Display tier above — no separate size). +- Weight: 600 (semibold). - Color: `var(--color-text-primary)` default; `var(--color-text-brand)` on hover. +Primary focal point: tablo title + color avatar. + Source: `go-backend/app.css` measured values, existing `tablos.templ` class audit. --- @@ -89,7 +92,7 @@ Accent (`#804eec` / `var(--color-brand-primary)`) reserved for: - Active tab nav indicator (bottom border + text color on active tab) - Tablo title hover color - Task checkbox fill when complete (`var(--color-text-brand-strong)` = #7c3aed — same family) -- Invite button border and text (ghost/outline style) +- Invite Member button border and text (ghost/outline style) - Focus ring on interactive elements (`var(--color-focus-ring)`) No other elements use the brand purple. Discussion link uses neutral text, not accent. @@ -112,8 +115,8 @@ All components already exist in `backend/internal/web/ui/`. No new components ne | Component | Templ call | Usage in this phase | |-----------|-----------|---------------------| | Badge | `@ui.Badge(ui.BadgeProps{Label: ..., Variant: ...})` | Status pill in metadata row; task count in column header | -| Button | `@ui.Button(ui.ButtonProps{Label: "Invite", Variant: ui.ButtonVariantGhost, ...})` | Invite button in header | -| IconButton | `@ui.IconButton(ui.IconButtonProps{...})` | Discussion link, Delete (header), Download + Delete (file rows) | +| Button | `@ui.Button(ui.ButtonProps{Label: "Invite Member", Variant: ui.ButtonVariantGhost, ...})` | Invite Member button in header | +| IconButton | `@ui.IconButton(ui.IconButtonProps{...})` | Discussion link (aria-label="Discussion"), Delete tablo (aria-label="Delete tablo"), Download file (aria-label="Download file"), Delete file (aria-label="Delete file") | | Table | `@ui.Table(ui.TableProps{Head: ..., Body: ...})` | Files section list | | EmptyState | `@ui.EmptyState(ui.EmptyStateProps{Title: ..., Description: ...})` | Files empty state | @@ -131,11 +134,11 @@ Structure: `.project-card-top` wrapping row (already in `app.css`). [ .project-card-top ] Left: [ .project-card-title-row ] [ .project-avatar (color circle, 3rem × 3rem, border-radius 0.85rem) ] - [ .tablo-title-zone (inline-editable h1, font-size 1.75rem, weight 700) ] - Right: [ action controls row, gap 0.75rem ] - [ Discussion: @ui.IconButton ghost/neutral + label "Discussion" ] - [ Invite: @ui.Button ghost variant, label "Invite" ] - [ Delete: @ui.IconButton ghost/danger, trash icon ] + [ .tablo-title-zone (inline-editable h1, font-size 1.75rem, weight 600) ] + Right: [ action controls row, gap 0.75rem (sm-plus) ] + [ Discussion: @ui.IconButton ghost/neutral + label "Discussion", aria-label="Discussion" ] + [ Invite Member: @ui.Button ghost variant, label "Invite Member" ] + [ Delete: @ui.IconButton ghost/danger, trash icon, aria-label="Delete tablo" ] ``` Color avatar: @@ -146,7 +149,7 @@ Color avatar: ### Metadata Row ``` -[ .tablo-metadata-row ] — flex row, flex-wrap, gap 1rem, border-bottom 1px var(--color-border-muted), padding-bottom 1rem, margin-bottom 1rem +[ .tablo-metadata-row ] — flex row, flex-wrap, gap 1rem (md), border-bottom 1px var(--color-border-muted), padding-bottom 1rem (md), margin-bottom 1rem (md) [ created date: calendar icon (1rem) + date string, color var(--color-text-muted), font-size 0.875rem ] [ separator: border-right 1px var(--color-border-muted) — only on md+ breakpoint ] @@ -160,18 +163,18 @@ Progress bar: ### Tab Nav -Structure: flex row, gap 1.5rem, border-bottom 1px `var(--color-border-muted)`, margin-bottom 1.5rem. +Structure: flex row, gap 1.5rem (lg), border-bottom 1px `var(--color-border-muted)`, margin-bottom 1.5rem (lg). Each tab link: -- Inactive: `color: var(--color-text-muted)`, `border-bottom: 2px solid transparent`, font-weight 500, font-size 0.875rem. +- Inactive: `color: var(--color-text-muted)`, `border-bottom: 2px solid transparent`, font-weight 400, font-size 0.875rem. - Active: `color: var(--color-text-brand)`, `border-bottom: 2px solid var(--color-brand-primary)`, font-weight 600. - Hover (inactive): `color: var(--color-text-primary)`. -- All tabs: `min-height: 44px`, `padding-bottom: 0.75rem`, `padding-inline: 0.25rem`. +- All tabs: `min-height: 44px`, `padding-bottom: 0.75rem` (sm-plus / 12px), `padding-inline: 0.25rem` (xs / 4px). - Active state toggled via Go template conditional — CSS class `.is-active` applied to the active ``. CSS class pattern (match Phase 15 `sidebar-nav-item.is-active`): ```css -.tab-nav-item { color: var(--color-text-muted); border-bottom: 2px solid transparent; ... } +.tab-nav-item { color: var(--color-text-muted); border-bottom: 2px solid transparent; font-weight: 400; ... } .tab-nav-item.is-active { color: var(--color-text-brand); border-bottom-color: var(--color-brand-primary); font-weight: 600; } .tab-nav-item:hover:not(.is-active) { color: var(--color-text-primary); } ``` @@ -179,36 +182,36 @@ CSS class pattern (match Phase 15 `sidebar-nav-item.is-active`): ### Kanban Board (Tasks Tab) ``` -[ #kanban-board ] — flex row, gap 1rem, overflow-x auto, padding-bottom 1rem +[ #kanban-board ] — flex row, gap 1rem (md), overflow-x auto, padding-bottom 1rem (md) [ .kanban-column ] — flex-shrink 0, width 18rem (fixed) [ .tasks-section ] — border 1px var(--color-border-subtle), border-radius 1rem, overflow hidden [ .tasks-section-header ] — flex row, justify-between, align-center, - border-bottom 1px var(--color-border-muted), padding 1rem + border-bottom 1px var(--color-border-muted), padding 1rem (md) Left: [ h3 "Todo" font-size 1rem, weight 600 ] + [ @ui.Badge count ] Right: [ .tasks-add-button "Add task" button ] [ .task-list ] — flex column [ .etape-group ] — per etape in column (server-side grouped) - [ .etape-group-header ] — flex row, align-center, gap 0.5rem, - padding 0.5rem 1rem, + [ .etape-group-header ] — flex row, align-center, gap 0.5rem (sm), + padding 0.5rem (sm) 1rem (md), background var(--color-surface-muted), border-bottom 1px var(--color-border-muted) [ color dot: 0.5rem circle, background {etapeColor} inline style ] - [ etape name: font-size 0.75rem, weight 600, color var(--color-text-secondary) ] + [ etape name: font-size 0.875rem (label tier), weight 600, color var(--color-text-secondary) ] [ task rows within group... ] - [ .task-row ] — flex row, align-center, gap 0.75rem, padding 0.9rem 1rem, + [ .task-row ] — flex row, align-center, gap 0.75rem (sm-plus / 12px), padding 1rem (md), border-bottom 1px var(--color-border-muted), hover: background var(--color-surface-neutral-hover) [ .task-check ] — 2rem circle checkbox, border 2px var(--color-border-strong) .is-complete → background + border-color var(--color-text-brand-strong) - [ .task-body ] — flex 1, task title 0.95rem weight 500 + [ .task-body ] — flex 1, task title 0.95rem weight 400 .is-complete → color var(--color-text-faint), text-decoration line-through - [ .task-meta ] — etape label (if applicable), 0.75rem muted + [ .task-meta ] — etape label (if applicable), 0.875rem (label tier), muted [ unassigned group (last, if any unassigned tasks exist) ] [ .etape-group-header with label "No etape", no color dot ] @@ -217,7 +220,7 @@ CSS class pattern (match Phase 15 `sidebar-nav-item.is-active`): Etape group sub-heading for "No etape" / unassigned: same `.etape-group-header` class, omit the color dot element, label text `var(--color-text-muted)` instead of `var(--color-text-secondary)`. -Empty column state: `

No tasks yet

` — `color: var(--color-text-faint)`, `font-size: 0.875rem`, italic, padding 0.75rem 1rem. +Empty column state: `

No tasks yet

` — `color: var(--color-text-faint)`, `font-size: 0.875rem`, italic, padding 0.75rem (sm-plus) 1rem (md). ### Files Section @@ -225,7 +228,7 @@ Empty column state: `

No tasks yet

` — `color: va [ .overview-section ] [ .overview-section-heading ] - Left: [ h3 "Files", font-size 1.6rem, weight 600 ] + Left: [ h3 "Files", font-size 1.75rem, weight 600 ] Right: [ @ui.Button(label "Upload file", Variant ButtonVariantDefault, Tone ButtonToneSolid) ] [ — triggers inline FileUploadForm via HTMX swap ] @@ -235,7 +238,7 @@ Empty column state: `

No tasks yet

` — `color: va [ filename (truncated, max-width 20rem) ] [ human-readable size ] [ formatted date ] - [ actions: @ui.IconButton(ghost/neutral, download icon) + @ui.IconButton(ghost/danger, trash icon) ] + [ actions: @ui.IconButton(ghost/neutral, download icon, aria-label="Download file") + @ui.IconButton(ghost/danger, trash icon, aria-label="Delete file") ] [ @ui.EmptyState (when no files) ] Title: "No files yet" @@ -268,7 +271,7 @@ Source: CONTEXT.md D-F01, D-F02, D-F03, D-F04. | Status pill — In progress | "In progress" | | Status pill — Done | "Done" | | Status pill — Todo | "Todo" | -| Invite button | "Invite" | +| Invite button | "Invite Member" | | Column headers | "Todo" / "In Progress" / "Done" (existing `TaskColumnLabels` map — no change) | | Etape group — unassigned | "No etape" | | Tab nav labels | "Overview" / "Tasks" / "Files" / "Discussion" / "Events" (unchanged) | @@ -346,8 +349,8 @@ All values use `var(--...)` tokens — no hardcoded hex. background: var(--color-surface-muted); border-bottom: 1px solid var(--color-border-muted); display: flex; - gap: 0.5rem; - padding: 0.375rem 1rem; + gap: 0.5rem; /* sm / 8px */ + padding: 0.5rem 1rem; /* sm / 8px vertical, md / 16px horizontal */ } .etape-group-color-dot { @@ -359,7 +362,7 @@ All values use `var(--...)` tokens — no hardcoded hex. .etape-group-label { color: var(--color-text-secondary); - font-size: 0.75rem; + font-size: 0.875rem; /* label / 14px */ font-weight: 600; } @@ -373,12 +376,37 @@ All values use `var(--...)` tokens — no hardcoded hex. font-size: 1rem; } +/* New for Phase 16 — Task row */ +.task-row { + align-items: center; + border-bottom: 1px solid var(--color-border-muted); + display: flex; + gap: 0.75rem; /* sm-plus / 12px */ + padding: 1rem; /* md / 16px */ +} + +.task-row:hover { + background: var(--color-surface-neutral-hover); +} + +/* New for Phase 16 — Task body weight */ +.task-body { + flex: 1; + font-size: 0.95rem; + font-weight: 400; +} + +.task-row.is-complete .task-body p { + color: var(--color-text-faint); + text-decoration: line-through; +} + /* New for Phase 16 — Tab nav */ .tab-nav { border-bottom: 1px solid var(--color-border-muted); display: flex; - gap: 1.5rem; - margin-bottom: 1.5rem; + gap: 1.5rem; /* lg / 24px */ + margin-bottom: 1.5rem; /* lg / 24px */ overflow-x: auto; } @@ -387,12 +415,12 @@ All values use `var(--...)` tokens — no hardcoded hex. color: var(--color-text-muted); display: flex; align-items: center; - gap: 0.5rem; + gap: 0.5rem; /* sm / 8px */ font-size: 0.875rem; - font-weight: 500; + font-weight: 400; min-height: 44px; - padding-bottom: 0.75rem; - padding-inline: 0.25rem; + padding-bottom: 0.75rem; /* sm-plus / 12px */ + padding-inline: 0.25rem; /* xs / 4px */ white-space: nowrap; transition: color 0.2s ease, border-bottom-color 0.2s ease; } @@ -413,9 +441,9 @@ All values use `var(--...)` tokens — no hardcoded hex. border-bottom: 1px solid var(--color-border-muted); display: flex; flex-wrap: wrap; - gap: 1rem; - margin-bottom: 1rem; - padding-bottom: 1rem; + gap: 1rem; /* md / 16px */ + margin-bottom: 1rem; /* md / 16px */ + padding-bottom: 1rem; /* md / 16px */ } .tablo-metadata-date { @@ -423,7 +451,7 @@ All values use `var(--...)` tokens — no hardcoded hex. color: var(--color-text-muted); display: flex; font-size: 0.875rem; - gap: 0.375rem; + gap: 0.5rem; /* sm / 8px */ } .tablo-metadata-date svg { @@ -436,7 +464,7 @@ All values use `var(--...)` tokens — no hardcoded hex. color: var(--color-text-faint); font-size: 0.875rem; font-style: italic; - padding: 0.75rem 1rem; + padding: 0.75rem 1rem; /* sm-plus / 12px vertical, md / 16px horizontal */ } ``` @@ -470,6 +498,7 @@ No shadcn, no third-party registries. All components are in-tree templ files. | `backend/templates/tasks.templ` | Kanban column width, task card class names | | `backend/templates/files.templ` | FileDeleteConfirmFragment zone pattern | | User input | 0 (--auto mode; all decisions from context) | +| Checker revision | 3 blocking fixes: typography consolidated to 4 sizes / 2 weights; spacing values corrected to multiples of 4; 12px (sm-plus) token added to spacing scale | ---