docs(20): UI design contract

This commit is contained in:
Arthur Belleville 2026-05-18 15:16:38 +02:00
parent 9312a65ed9
commit e37b8c5855
No known key found for this signature in database

View file

@ -35,7 +35,7 @@ Declared values (multiples of 4):
| Token | Value | Usage | | Token | Value | Usage |
|-------|-------|-------| |-------|-------|-------|
| xs | 4px | Pill padding vertical, icon-to-label gap | | xs | 4px | Pill padding vertical, icon-to-label gap |
| sm | 8px | Badge gap, drag-handle gap, task-meta item gap | | sm | 8px | Badge gap, drag-handle gap, task-meta item gap, task card padding-block |
| md | 16px | Column padding, task row padding-inline | | md | 16px | Column padding, task row padding-inline |
| lg | 24px | Page horizontal padding, header padding-top | | lg | 24px | Page horizontal padding, header padding-top |
| xl | 32px | Section break between header and tab bar | | xl | 32px | Section break between header and tab bar |
@ -43,10 +43,10 @@ Declared values (multiples of 4):
| 3xl | 64px | — (unused in this phase) | | 3xl | 64px | — (unused in this phase) |
Exceptions: Exceptions:
- Task row padding-block: 10px (not on the 4-point grid — matches existing `.task-row` at `0.9rem`; keep as-is)
- Column width: 18rem (288px, not grid-aligned — Sortable.js layout constraint; keep as-is) - Column width: 18rem (288px, not grid-aligned — Sortable.js layout constraint; keep as-is)
- Kanban gap between columns: 16px (`gap-4`) - Kanban gap between columns: 16px (`gap-4`)
- Tab nav padding-bottom: 12px per tab item (existing `.tab-nav-item` — keep) - Tab nav padding-bottom: 12px per tab item (existing `.tab-nav-item` — keep)
- Progress bar height: 5px — cosmetic dimension, not a layout spacing value; matches Figma spec.
Source: existing `app.css` measurements, confirmed against Figma screenshots. Source: existing `app.css` measurements, confirmed against Figma screenshots.
@ -56,15 +56,16 @@ Source: existing `app.css` measurements, confirmed against Figma screenshots.
| Role | Size | Weight | Line Height | | Role | Size | Weight | Line Height |
|------|------|--------|-------------| |------|------|--------|-------------|
| Page title (tablo name h1) | 1.75rem (28px) | 700 | 1.2 | | Page title (tablo name h1) | 1.75rem (28px) | 600 | 1.2 |
| Tab label | 0.875rem (14px) | 400 inactive / 600 active | 1.4 | | Tab label | 0.875rem (14px) | 400 inactive / 600 active | 1.4 |
| Column header h3 | 1rem (16px) | 600 | 1.3 | | Column header h3 | 1rem (16px) | 600 | 1.3 |
| Task card title | 0.875rem (14px) | 500 | 1.4 | | Task card title | 0.875rem (14px) | 400 | 1.4 |
| Metadata / caption | 0.75rem (12px) | 400 | 1.4 | | Metadata / caption | 0.75rem (12px) | 400 | 1.4 |
Notes: Notes:
- Body text uses `0.875rem` throughout task cards and metadata rows (source: sketch-findings `0.875rem body/rows`). - Body text uses `0.875rem` throughout task cards and metadata rows (source: sketch-findings `0.875rem body/rows`).
- Priority pill label: `0.625rem (10px)`, weight 600, per locked sketch-findings pattern. - Exactly 2 font weights in use: `400` (body, inactive labels, task card title) and `600` (headings, active tab, emphasis).
- Priority pill is not rendered in Phase 20 — no priority field in the data model yet. Priority typography belongs to Phase 21.
- No custom font loading — system stack only. - No custom font loading — system stack only.
--- ---
@ -91,10 +92,7 @@ Status badge colors (locked from sketch-findings):
- In Review: background `var(--color-status-warning-soft-bg)` = `#fff4e2`, text `var(--color-status-warning-foreground)` = `#db9729` - In Review: background `var(--color-status-warning-soft-bg)` = `#fff4e2`, text `var(--color-status-warning-foreground)` = `#db9729`
- Done: background `var(--color-status-success-soft-bg)`, text `var(--color-status-success-foreground)` - Done: background `var(--color-status-success-soft-bg)`, text `var(--color-status-success-foreground)`
Priority pill colors (locked from sketch-findings): Priority pill colors (deferred to Phase 21 — not rendered in Phase 20).
- High: background `#fef2f2`, text `#dc2626`
- Medium: background `#fff4e2`, text `#db9729`
- Low: background `#ecfdf3`, text `#16a34a`
--- ---
@ -102,6 +100,8 @@ Priority pill colors (locked from sketch-findings):
**Goal:** Match Board.png Figma — tablo name large, owner avatar, due date metadata row, status badge, progress bar + percentage, right-aligned action buttons (Group Chat, Invite). **Goal:** Match Board.png Figma — tablo name large, owner avatar, due date metadata row, status badge, progress bar + percentage, right-aligned action buttons (Group Chat, Invite).
**Primary focal point:** `h1.tablo-title-zone` — the tablo name is the first visual anchor on the detail page.
### Header layout ### Header layout
``` ```
@ -111,9 +111,9 @@ Priority pill colors (locked from sketch-findings):
### Header sub-elements ### Header sub-elements
**Tablo avatar:** 48×48px, `border-radius: 12px`, background = tablo color or `var(--color-project-fallback)`, white bold initial letter at 1.25rem. **Tablo avatar:** 48×48px, `border-radius: 12px`, background = tablo color or `var(--color-project-fallback)`, white bold initial letter at 1rem.
**Tablo name h1:** `font-size: 1.75rem`, `font-weight: 700`, `color: var(--color-text-primary)`, inline-edit on click (existing HTMX pattern preserved). **Tablo name h1:** `font-size: 1.75rem`, `font-weight: 600`, `color: var(--color-text-primary)`, inline-edit on click (existing HTMX pattern preserved).
**Metadata row:** horizontal flex, `gap: 24px`, `padding-block: 16px`, `border-bottom: 1px solid var(--color-border-muted)`. **Metadata row:** horizontal flex, `gap: 24px`, `padding-block: 16px`, `border-bottom: 1px solid var(--color-border-muted)`.
- Owner segment: 24×24 avatar circle + owner display name at 0.875rem - Owner segment: 24×24 avatar circle + owner display name at 0.875rem
@ -126,7 +126,7 @@ Priority pill colors (locked from sketch-findings):
**Member avatar stack (top-right):** up to 3 overlapping 32×32 circles, `-margin-left: 8px` for overlap, `border: 2px solid white`, `border-radius: 50%`. **Member avatar stack (top-right):** up to 3 overlapping 32×32 circles, `-margin-left: 8px` for overlap, `border: 2px solid white`, `border-radius: 50%`.
**Action buttons (top-right):** **Action buttons (top-right):**
- "Group Chat" — icon-button, neutral ghost variant, existing `ui.IconButton` - "Group Chat" — icon-button, neutral ghost variant, existing `ui.IconButton`, `aria-label="Groupe de discussion"`
- "Invite" — outline button with person-plus icon, neutral soft variant - "Invite" — outline button with person-plus icon, neutral soft variant
--- ---
@ -182,8 +182,8 @@ No background panel behind the board — columns sit directly on the page surfac
| Padding | `12px 16px` | | Padding | `12px 16px` |
| Layout | `display: flex; align-items: center; justify-content: space-between` | | Layout | `display: flex; align-items: center; justify-content: space-between` |
| Column title | 0.875rem, weight 600, `var(--color-text-primary)` | | Column title | 0.875rem, weight 600, `var(--color-text-primary)` |
| Task count badge | pill, `var(--color-surface-default)` background, `var(--color-text-secondary)` text, `border: 1px solid var(--color-border-muted)`, `border-radius: 9999px`, `font-size: 0.75rem`, `padding: 1px 8px` | | Task count badge | pill, `var(--color-surface-default)` background, `var(--color-text-secondary)` text, `border: 1px solid var(--color-border-muted)`, `border-radius: 9999px`, `font-size: 0.75rem`, `padding: 0 8px` |
| "+ Add" trigger | `color: var(--color-text-brand)`, 0.875rem, weight 500, no border, ghost button | | "+ Add" trigger | `color: var(--color-text-brand)`, 0.875rem, weight 400, no border, ghost button |
Column header color mapping (status → header accent): Column header color mapping (status → header accent):
- Not Started: no color accent — neutral header - Not Started: no color accent — neutral header
@ -199,8 +199,8 @@ The Figma design uses minimal headers. Do NOT add colored left-border or colored
**Figma target (Board.png):** Cards are white boxes with: **Figma target (Board.png):** Cards are white boxes with:
- File/type icon or colored initial (top-left of card) - File/type icon or colored initial (top-left of card)
- Task title at 0.875rem, weight 500 - Task title at 0.875rem, weight 400
- Row of meta: priority pill + due date + assignee avatars + subtask count - Row of meta: due date + assignee avatars (priority pill deferred to Phase 21)
- Drag handle visible on hover only (3-dot vertical handle) - Drag handle visible on hover only (3-dot vertical handle)
- No visible border on rest state; subtle shadow on hover - No visible border on rest state; subtle shadow on hover
- Delete icon replaced by a "..." overflow menu (for this phase: keep delete icon but move to top-right corner of card, ghost on rest, visible on hover) - Delete icon replaced by a "..." overflow menu (for this phase: keep delete icon but move to top-right corner of card, ghost on rest, visible on hover)
@ -216,7 +216,8 @@ The Figma design uses minimal headers. Do NOT add colored left-border or colored
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
padding: 12px; padding-inline: 12px;
padding-block: 8px;
transition: box-shadow 0.12s ease, border-color 0.12s ease; transition: box-shadow 0.12s ease, border-color 0.12s ease;
} }
@ -228,13 +229,13 @@ The Figma design uses minimal headers. Do NOT add colored left-border or colored
Card layout (column direction): Card layout (column direction):
1. Row 1: `[drag-handle] [task-title flex-1] [delete-icon — visible on hover]` 1. Row 1: `[drag-handle] [task-title flex-1] [delete-icon — visible on hover]`
2. Row 2: `[priority-pill?] [due-date?] [assignee-avatars?]` — all optional; skip row if all empty 2. Row 2: `[due-date?] [assignee-avatars?]` — all optional; skip row if all empty
For Phase 20, the task data model has: title, status, etape assignment. No priority field yet. **Priority pill is not rendered in Phase 20** — the meta row is omitted if there is no meta. Do not add placeholder meta. For Phase 20, the task data model has: title, status, etape assignment. No priority field yet. **Priority pill is not rendered in Phase 20** — the meta row is omitted if there is no meta. Do not add placeholder meta.
**Drag handle:** `⠿` Braille pattern, `color: var(--color-text-faint)`, `font-size: 1rem`, visible only on `.task-card:hover` (use CSS `opacity: 0``opacity: 1` on parent hover, not `display: none`). Transition `0.12s`. **Drag handle:** `⠿` Braille pattern, `color: var(--color-text-faint)`, `font-size: 1rem`, visible only on `.task-card:hover` (use CSS `opacity: 0``opacity: 1` on parent hover, not `display: none`). Transition `0.12s`.
**Task title:** `font-size: 0.875rem`, `font-weight: 500`, `color: var(--color-text-primary)`, single-line ellipsis overflow. **Task title:** `font-size: 0.875rem`, `font-weight: 400`, `color: var(--color-text-primary)`, single-line ellipsis overflow.
**Task card delete icon:** `color: var(--color-text-faint)` at rest, `color: var(--color-status-danger-foreground)` on hover. Opacity 0 at rest, opacity 1 on `.task-card:hover`. Do NOT use `display: none` — opacity transition preserves layout. **Task card delete icon:** `color: var(--color-text-faint)` at rest, `color: var(--color-status-danger-foreground)` on hover. Opacity 0 at rest, opacity 1 on `.task-card:hover`. Do NOT use `display: none` — opacity transition preserves layout.
@ -246,7 +247,7 @@ When a column has no tasks:
| Property | Value | | Property | Value |
|----------|-------| |----------|-------|
| Copy | "No tasks yet" | | Copy | "Aucune tâche" |
| Style | `color: var(--color-text-faint)`, `font-size: 0.875rem`, `padding: 24px 16px`, `text-align: center` | | Style | `color: var(--color-text-faint)`, `font-size: 0.875rem`, `padding: 24px 16px`, `text-align: center` |
| Icon | None — text only | | Icon | None — text only |
@ -266,7 +267,7 @@ The existing `ui.Table` component and `FilesTabFragment` are functionally correc
| Body row hover | none | `background: var(--color-surface-subtle)` | | Body row hover | none | `background: var(--color-surface-subtle)` |
| Body row separator | none explicit | `border-bottom: 1px solid var(--color-border-default)` | | Body row separator | none explicit | `border-bottom: 1px solid var(--color-border-default)` |
| File type icon | none | 32×32px colored icon block (PDF=red `#ef4444`, DOCX=blue `#3b82f6`, other=gray), `border-radius: 6px` | | File type icon | none | 32×32px colored icon block (PDF=red `#ef4444`, DOCX=blue `#3b82f6`, other=gray), `border-radius: 6px` |
| File name | plain text | 0.875rem, weight 500, `var(--color-text-primary)` | | File name | plain text | 0.875rem, weight 400, `var(--color-text-primary)` |
| File size | plain text | 0.75rem, `var(--color-text-muted)`, below file name | | File size | plain text | 0.75rem, `var(--color-text-muted)`, below file name |
| Uploader | name only | 24×24 avatar circle + name at 0.875rem | | Uploader | name only | 24×24 avatar circle + name at 0.875rem |
| Date | plain text | 0.875rem, `var(--color-text-secondary)` | | Date | plain text | 0.875rem, `var(--color-text-secondary)` |
@ -279,17 +280,17 @@ For Phase 20: the existing delete action stays as a direct icon button. The "...
## Surfaces: Task Create / Edit Modal ## Surfaces: Task Create / Edit Modal
**Figma (Create new task.png):** Full modal overlay with: **Figma (Create new task.png):** Full modal overlay with:
- Large title input ("Write task name") at top, 1.25rem placeholder - Large title input ("Write task name") at top, 1rem placeholder
- Description textarea below title - Description textarea below title
- Metadata fields in a 2-column form grid: Status, Project, Assignee, Due date, Priority, Attachment - Metadata fields in a 2-column form grid: Status, Project, Assignee, Due date, Priority, Attachment
- Cancel + "Create Task" (purple filled) buttons at bottom-right - Cancel + "Créer la tâche" (purple filled) buttons at bottom-right
**Phase 20 scope:** The existing `TaskCreateFormFragment` and `TaskEditFragment` are inline forms inside the column, not a modal. Converting to a modal is a larger refactor. In Phase 20, restyle the inline forms only: **Phase 20 scope:** The existing `TaskCreateFormFragment` and `TaskEditFragment` are inline forms inside the column, not a modal. Converting to a modal is a larger refactor. In Phase 20, restyle the inline forms only:
| Property | Target | | Property | Target |
|----------|--------| |----------|--------|
| Form container | `background: var(--color-surface-default)`, `border: 1px solid var(--color-border-default)`, `border-radius: 8px`, `padding: 12px`, `box-shadow: 0 8px 24px rgba(15,23,42,.08)` | | Form container | `background: var(--color-surface-default)`, `border: 1px solid var(--color-border-default)`, `border-radius: 8px`, `padding: 12px`, `box-shadow: 0 8px 24px rgba(15,23,42,.08)` |
| Title input | `border: none`, `border-bottom: 1px solid var(--color-border-default)`, `font-size: 1rem`, `font-weight: 500`, `padding: 4px 0`, `width: 100%` | | Title input | `border: none`, `border-bottom: 1px solid var(--color-border-default)`, `font-size: 1rem`, `font-weight: 400`, `padding: 4px 0`, `width: 100%` |
| Submit button | `var(--color-brand-primary)` fill, white text — existing solid variant | | Submit button | `var(--color-brand-primary)` fill, white text — existing solid variant |
| Cancel/Discard | neutral ghost — existing neutral soft variant | | Cancel/Discard | neutral ghost — existing neutral soft variant |
@ -299,30 +300,26 @@ For Phase 20: the existing delete action stays as a direct icon button. The "...
| Element | Copy | | Element | Copy |
|---------|------| |---------|------|
| Primary CTA — create task | "Create Task" | | Primary CTA — create task | "Créer la tâche" |
| Primary CTA — upload file | "Upload File" | | Primary CTA — upload file | "Téléverser un fichier" |
| Secondary CTA — invite member | "Invite" | | Secondary CTA — invite member | "Inviter" |
| Empty column state | "No tasks yet" | | Empty column state | "Aucune tâche" |
| Empty files state heading | "No files yet" | | Empty files state heading | "Aucun fichier" |
| Empty files state body | "Upload your first file to get started." | | Empty files state body | "Téléversez votre premier fichier pour commencer." |
| Task delete confirmation | "Delete this task?" with "Delete" (danger) and "Cancel" | | Task delete confirmation | "Supprimer cette tâche ?" with "Supprimer" (danger) and "Annuler" |
| Tablo delete confirmation | "Delete this tablo?" with "Delete" (danger) and "Cancel" | | Tablo delete confirmation | "Supprimer ce tablo ?" with "Supprimer" (danger) and "Annuler" |
| Progress label | "{N}% complete" — shown inline as text next to progress bar | | Progress label | "{N}% complete" — shown inline as text next to progress bar |
| Due date absent | "—" (em dash, not "N/A" or blank) | | Due date absent | "—" (em dash, not "N/A" or blank) |
| Status label — active | "Active" | | Status label — active | "Actif" |
| Status label — archived | "Archived" | | Status label — archived | "Archivé" |
| Status label — in progress | "In Progress" | | Status label — in progress | "En cours" |
| Status label — not started | "Not Started" | | Status label — not started | "Non démarré" |
| Status label — in review | "In Review" | | Status label — in review | "En révision" |
| Status label — done | "Done" | | Status label — done | "Terminé" |
| Task create failure | "Impossible de créer la tâche. Réessayez." |
| File upload failure | "Impossible de téléverser le fichier. Réessayez." |
**French localization note:** Existing templates use French labels ("Actif", "Archivé", "Progression", etc.) in some places and English in others. For Phase 20, standardize all tablo detail + kanban strings to French: **Localization:** All tablo detail + kanban strings use French throughout.
- "Créer la tâche" (Create Task)
- "Téléverser un fichier" (Upload File)
- "Inviter" (Invite)
- "Aucune tâche" (No tasks yet)
- "Aucun fichier" (No files yet)
- "Téléversez votre premier fichier pour commencer." (Empty files body)
--- ---
@ -368,12 +365,13 @@ Progress bar on header shows `0%` until a real tasks query drives it. Phase 20 w
| Metadata row | `app.css` `.tablo-metadata-row` | Add owner segment + due-date segment styling | | Metadata row | `app.css` `.tablo-metadata-row` | Add owner segment + due-date segment styling |
| Progress bar | `app.css` `.project-progress-bar` | Change fill color from `var(--project-color)` to `var(--color-brand-primary)` | | Progress bar | `app.css` `.project-progress-bar` | Change fill color from `var(--project-color)` to `var(--color-brand-primary)` |
| Column border-radius | `app.css` `.tasks-section` | Change `1rem``0.75rem` | | Column border-radius | `app.css` `.tasks-section` | Change `1rem``0.75rem` |
| Task card layout | `app.css` `.task-row` | Change row → column-flex card layout; add hover shadow + border-color | | Task card layout | `app.css` `.task-row` | Change row → column-flex card layout; add hover shadow + border-color; padding-block: 8px |
| Drag handle visibility | `app.css` `.task-drag-handle` | Add `opacity: 0; transition: 0.12s` + `.task-card:hover .task-drag-handle { opacity: 1 }` | | Drag handle visibility | `app.css` `.task-drag-handle` | Add `opacity: 0; transition: 0.12s` + `.task-card:hover .task-drag-handle { opacity: 1 }` |
| Delete icon visibility | `app.css` | Add `opacity: 0` at rest + `.task-card:hover` reveals it | | Delete icon visibility | `app.css` | Add `opacity: 0` at rest + `.task-card:hover` reveals it |
| Task list gap | `app.css` `.task-list` | Add `gap: 8px; padding: 8px` | | Task list gap | `app.css` `.task-list` | Add `gap: 8px; padding: 8px` |
| Files table wrapper | `files.templ` `FilesTabFragment` | Add wrapper div with border + border-radius | | Files table wrapper | `files.templ` `FilesTabFragment` | Add wrapper div with border + border-radius |
| Files table header | `table.css` or inline | `var(--color-surface-muted)` background, uppercase caption style | | Files table header | `table.css` or inline | `var(--color-surface-muted)` background, uppercase caption style |
| Group Chat button | `tablos.templ` | Add `aria-label="Groupe de discussion"` to the icon-button |
--- ---