xtablo-source/.planning/phases/20-tablo-detail-kanban-restyle/20-UI-SPEC.md
Arthur Belleville 9312a65ed9
docs(20): UI design contract for tablo detail & kanban restyle
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-18 15:08:48 +02:00

18 KiB
Raw Blame History

phase slug status shadcn_initialized preset created
20 tablo-detail-kanban-restyle draft false none 2026-05-18

Phase 20 — UI Design Contract

Tablo Detail & Kanban Restyle

Visual and interaction contract for the tablo detail page header, tab bar, kanban board, task cards, and files table. Generated by gsd-ui-researcher, verified by gsd-ui-checker.


Design System

Property Value
Tool none — custom CSS custom properties
Preset not applicable
Component library none (templ components + CSS tokens in backend/internal/web/ui/)
Icon library Lucide SVG inline — stroke-width 2, 16×16 in tabs, 20×20 in actions
Font ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif

Source: backend/internal/web/ui/base.css — all tokens already defined, no additions required for this phase.


Spacing Scale

Declared values (multiples of 4):

Token Value Usage
xs 4px Pill padding vertical, icon-to-label gap
sm 8px Badge gap, drag-handle gap, task-meta item gap
md 16px Column padding, task row padding-inline
lg 24px Page horizontal padding, header padding-top
xl 32px Section break between header and tab bar
2xl 48px — (unused in this phase)
3xl 64px — (unused in this phase)

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)
  • Kanban gap between columns: 16px (gap-4)
  • Tab nav padding-bottom: 12px per tab item (existing .tab-nav-item — keep)

Source: existing app.css measurements, confirmed against Figma screenshots.


Typography

Role Size Weight Line Height
Page title (tablo name h1) 1.75rem (28px) 700 1.2
Tab label 0.875rem (14px) 400 inactive / 600 active 1.4
Column header h3 1rem (16px) 600 1.3
Task card title 0.875rem (14px) 500 1.4
Metadata / caption 0.75rem (12px) 400 1.4

Notes:

  • 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.
  • No custom font loading — system stack only.

Color

Role Value Usage
Dominant (60%) var(--color-surface-default) = #ffffff Page background, kanban column bodies, task cards
Secondary (30%) var(--color-surface-muted) = #f3f4f6 Column header background, etape group headers, progress track
Accent (10%) var(--color-brand-primary) = #804eec Active tab underline, active tab text, progress bar fill, "Create Task" primary CTA
Destructive var(--color-status-danger-foreground) = #dc2626 Delete task icon button hover only

Accent reserved for:

  1. Active tab bar underline (border-bottom-color)
  2. Active tab text color
  3. Progress bar fill (background)
  4. Primary CTA button (Create Task, Upload File)
  5. "Add task" link text in column header

Status badge colors (locked from sketch-findings):

  • Active/In Progress: background #ecfdf3, text #16a34avar(--color-status-success-soft-bg) / var(--color-status-success-foreground)
  • Archived/Not Started: background var(--color-surface-muted), text var(--color-text-muted)
  • 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)

Priority pill colors (locked from sketch-findings):

  • High: background #fef2f2, text #dc2626
  • Medium: background #fff4e2, text #db9729
  • Low: background #ecfdf3, text #16a34a

Surfaces: Tablo Detail Header

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).

Header layout

[ avatar-48 ] [ tablo-name h1 ]                    [ member stack ] [ Invite button ]
  Owner: [avatar-24] Name  |  Due Date: YYYY-MM-DD  |  Status: [badge]  |  Progress: [bar] NN%

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 name h1: font-size: 1.75rem, font-weight: 700, 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).

  • Owner segment: 24×24 avatar circle + owner display name at 0.875rem
  • Due Date segment: calendar icon 16×16 + formatted date string at 0.875rem (show "—" if null)
  • Status segment: status badge pill
  • Progress segment: [bar-track] [NN%] — track width 120px min, fill uses var(--color-brand-primary) (not project color), percentage at 0.875rem font-weight 600

Progress bar dimensions: height 5px, border-radius: 9999px, track var(--color-surface-muted), fill var(--color-brand-primary).

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):

  • "Group Chat" — icon-button, neutral ghost variant, existing ui.IconButton
  • "Invite" — outline button with person-plus icon, neutral soft variant

Surfaces: Tab Bar

The tab bar is the sticky navigation below the header. Current implementation: Overview | Tasks | Files | Discussion | Events.

Figma tabs (Board.png): Board | List | Gantt | Calendar | Files — these are task-view tabs within the tasks section of the detail page, not the outer tab bar. The outer tab bar stays as-is: Overview | Tasks | Files | Discussion | Events. The inner task view switcher (Board/List/Gantt/Calendar) is Phase 21 scope — do not add a task view switcher in Phase 20.

Tab bar spec (unchanged pattern, tokens confirmed)

Property Value
Container border-bottom: 1px solid var(--color-border-muted)
Item height 44px touch target (existing)
Gap between items 24px (existing gap: 1.5rem)
Inactive color var(--color-text-muted)
Active color var(--color-text-brand) = #804eec
Active underline border-bottom: 2px solid var(--color-brand-primary)
Active font-weight 600
Hover color var(--color-text-primary)
Icon size 16×16

No changes required to .tab-nav or .tab-nav-item CSS rules from the existing code.


Surfaces: Kanban Board

Board container

display: flex; gap: 16px; overflow-x: auto; padding-bottom: 16px

No background panel behind the board — columns sit directly on the page surface.

Kanban column

Property Current Target (Phase 20)
Width 18rem (288px) 18rem — unchanged
Background var(--color-surface-default) white var(--color-surface-default) white
Border 1px solid var(--color-border-subtle) 1px solid var(--color-border-default)
Border-radius 16px (1rem) 12px (0.75rem) — match card radius from sketch-findings
Overflow hidden hidden
Column gap (board-level) 16px 16px — unchanged

Column header

Property Value
Background var(--color-surface-muted) = #f3f4f6
Padding 12px 16px
Layout display: flex; align-items: center; justify-content: space-between
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
"+ Add" trigger color: var(--color-text-brand), 0.875rem, weight 500, no border, ghost button

Column header color mapping (status → header accent):

  • Not Started: no color accent — neutral header
  • To Do: no color accent — neutral header
  • In Progress: no color accent — neutral header
  • Done: no color accent — neutral header

The Figma design uses minimal headers. Do NOT add colored left-border or colored header background per column — keep all column headers the same muted surface.

Task cards

Current state: .task-row inside .task-card-zone — horizontal row with drag handle, checkbox, title, delete button.

Figma target (Board.png): Cards are white boxes with:

  • File/type icon or colored initial (top-left of card)
  • Task title at 0.875rem, weight 500
  • Row of meta: priority pill + due date + assignee avatars + subtask count
  • Drag handle visible on hover only (3-dot vertical handle)
  • 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)

Card CSS contract:

.task-card {
  background: var(--color-surface-default);
  border: 1px solid var(--color-border-default);
  border-radius: 8px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
  transition: box-shadow 0.12s ease, border-color 0.12s ease;
}

.task-card:hover {
  border-color: var(--color-border-strong);
  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
}

Card layout (column direction):

  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

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: 0opacity: 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 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.

Between cards: gap: 8px on .task-list (currently display: flex; flex-direction: column). Change from 0 gap to 8px.

Empty column state

When a column has no tasks:

Property Value
Copy "No tasks yet"
Style color: var(--color-text-faint), font-size: 0.875rem, padding: 24px 16px, text-align: center
Icon None — text only

Surfaces: Files Table

Figma (Files.png): Clean table with columns: Files (icon + name + size), Upload By (avatar + name), Upload Date. Actions column right-aligned with "..." overflow menu.

The existing ui.Table component and FilesTabFragment are functionally correct. Restyle only:

Property Current Target
Table wrapper ui.Table default border border: 1px solid var(--color-border-default); border-radius: 12px; overflow: hidden
Header row background browser default var(--color-surface-muted)
Header cell text default 0.75rem, weight 600, uppercase, letter-spacing: 0.04em, var(--color-text-muted)
Body row hover none background: var(--color-surface-subtle)
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 name plain text 0.875rem, weight 500, var(--color-text-primary)
File size plain text 0.75rem, var(--color-text-muted), below file name
Uploader name only 24×24 avatar circle + name at 0.875rem
Date plain text 0.875rem, var(--color-text-secondary)
Actions delete button "..." icon button, ghost neutral, shows delete option

For Phase 20: the existing delete action stays as a direct icon button. The "..." pattern is deferred — keep the current delete icon button but apply the updated row hover.


Surfaces: Task Create / Edit Modal

Figma (Create new task.png): Full modal overlay with:

  • Large title input ("Write task name") at top, 1.25rem placeholder
  • Description textarea below title
  • Metadata fields in a 2-column form grid: Status, Project, Assignee, Due date, Priority, Attachment
  • Cancel + "Create Task" (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:

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)
Title input border: none, border-bottom: 1px solid var(--color-border-default), font-size: 1rem, font-weight: 500, padding: 4px 0, width: 100%
Submit button var(--color-brand-primary) fill, white text — existing solid variant
Cancel/Discard neutral ghost — existing neutral soft variant

Copywriting Contract

Element Copy
Primary CTA — create task "Create Task"
Primary CTA — upload file "Upload File"
Secondary CTA — invite member "Invite"
Empty column state "No tasks yet"
Empty files state heading "No files yet"
Empty files state body "Upload your first file to get started."
Task delete confirmation "Delete this task?" with "Delete" (danger) and "Cancel"
Tablo delete confirmation "Delete this tablo?" with "Delete" (danger) and "Cancel"
Progress label "{N}% complete" — shown inline as text next to progress bar
Due date absent "—" (em dash, not "N/A" or blank)
Status label — active "Active"
Status label — archived "Archived"
Status label — in progress "In Progress"
Status label — not started "Not Started"
Status label — in review "In Review"
Status label — done "Done"

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:

  • "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)

Interaction Contracts

Drag-and-drop (TASK-01 continuity requirement)

Sortable.js initialization on DOMContentLoaded and htmx:afterSettle is PRESERVED verbatim. Do not change:

  • .sortable-column class on task list containers
  • data-status attribute on column containers
  • data-task-id attribute on task cards
  • #reorder-form hidden form structure
  • Sortable handle: ".task-drag-handle" and draggable: ".task-card-zone" options

Only the visual CSS of .task-drag-handle changes (opacity 0 at rest → 1 on card hover).

Inline task edit

HTMX swap pattern unchanged:

  • Click card → hx-get on .task-cardouterHTML swap of .task-card-zone
  • Form submit → hx-postouterHTML swap of .task-card-zone
  • Discard → hx-get /show → restore TaskCard

Tab navigation

HTMX push-url + #tab-content innerHTML swap is PRESERVED. No changes to routing.

Inline title edit

Click on h1.tablo-title-zonehx-get /edit-titleouterHTML swap. Preserved.

Progress bar update

Progress bar on header shows 0% until a real tasks query drives it. Phase 20 wires the progress value from the existing EtapeTaskCounts data already available in TabloDetailPage. The handler already receives task data — compute doneTasks / totalTasks * 100 and inject as inline style width: {N}%.


Component Delta — What Changes vs Current Code

Component File Change
Tablo header tablos.templ TabloDetailPage Add owner row, due-date field, progress bar wired to real %, member avatar stack
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)
Column border-radius app.css .tasks-section Change 1rem0.75rem
Task card layout app.css .task-row Change row → column-flex card layout; add hover shadow + border-color
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
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 header table.css or inline var(--color-surface-muted) background, uppercase caption style

Registry Safety

Registry Blocks Used Safety Gate
shadcn official none not applicable — no shadcn
third-party none not applicable

No external registries. All components are templ + custom CSS.


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