18 KiB
| 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-rowat0.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.875remthroughout task cards and metadata rows (source: sketch-findings0.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:
- Active tab bar underline (
border-bottom-color) - Active tab text color
- Progress bar fill (
background) - Primary CTA button (Create Task, Upload File)
- "Add task" link text in column header
Status badge colors (locked from sketch-findings):
- Active/In Progress: background
#ecfdf3, text#16a34a—var(--color-status-success-soft-bg)/var(--color-status-success-foreground) - Archived/Not Started: background
var(--color-surface-muted), textvar(--color-text-muted) - In Review: background
var(--color-status-warning-soft-bg)=#fff4e2, textvar(--color-status-warning-foreground)=#db9729 - Done: background
var(--color-status-success-soft-bg), textvar(--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 usesvar(--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):
- Row 1:
[drag-handle] [task-title flex-1] [delete-icon — visible on hover] - 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: 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 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-columnclass on task list containersdata-statusattribute on column containersdata-task-idattribute on task cards#reorder-formhidden form structure- Sortable
handle: ".task-drag-handle"anddraggable: ".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-geton.task-card→outerHTMLswap of.task-card-zone - Form submit →
hx-post→outerHTMLswap of.task-card-zone - Discard →
hx-get/show→ restoreTaskCard
Tab navigation
HTMX push-url + #tab-content innerHTML swap is PRESERVED. No changes to routing.
Inline title edit
Click on h1.tablo-title-zone → hx-get /edit-title → outerHTML 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 1rem → 0.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