---
phase: 20-tablo-detail-kanban-restyle
verified: 2026-05-18T16:05:00Z
status: human_needed
score: 19/19 must-haves verified
overrides_applied: 0
human_verification:
  - test: "Navigate to a tablo detail page in the browser"
    expected: "Header shows tablo name in large (1.75rem) font with colored avatar, metadata row (owner name, calendar icon with —, status badge, progress bar in purple), and HTMX tab bar with 'Tâches' tab active"
    why_human: "CSS rendering and visual layout cannot be verified by grep; color-brand-primary token resolution depends on browser rendering"
  - test: "Drag a task card within a kanban column"
    expected: "Drag handle (⠿) appears on card hover; card becomes draggable; dropping the card triggers the hidden reorder form submission via Sortable.js onEnd"
    why_human: "Sortable.js runtime behavior, DOM event wiring, and opacity transitions require a real browser"
  - test: "Click a tab other than Tâches (e.g. Fichiers)"
    expected: "HTMX swaps #tab-content with 'Cette section arrive bientôt.' fragment; URL updates via hx-push-url"
    why_human: "HTMX navigation and URL push behavior require a running server and browser"
  - test: "Check global /tasks page after this phase"
    expected: ".task-row cards on the global tasks page still render correctly (no CSS regression from .task-card additions); .task-list now shows gap between tasks"
    why_human: "CSS cascade collision (.task-card vs .task-row) can only be confirmed visually in the browser"
---

# Phase 20: Tablo Detail + Kanban Restyle Verification Report

**Phase Goal:** Restyle the tablo detail page and kanban board to match Figma.
**Verified:** 2026-05-18T16:05:00Z
**Status:** human_needed
**Re-verification:** No — initial verification

## Goal Achievement

### Observable Truths

| # | Truth | Status | Evidence |
|---|-------|--------|----------|
| 1 | GET /tablos/{tabloID} returns 200 with the tablo name in the HTML response body | VERIFIED | `TestGetTabloDetailPage_Returns200` passes; handler reads tablo name from DB and passes it to `NewTabloDetailViewModel`; templ component renders it in `<h1 class="tablo-detail-title">` |
| 2 | Accessing a tablo owned by a different user returns 404 | VERIFIED | `TestGetTabloDetailPage_Returns404` passes; `findTabloByID` filters by `OwnerID` from session, not request params |
| 3 | Accessing with an invalid UUID returns 400 | VERIFIED | `TestGetTabloDetailPage_Returns400` passes; `uuid.Parse` + `http.Error(400)` in handler |
| 4 | Progress is computed as doneTasks/totalTasks*100 (integer), not from tablo.Status | VERIFIED | `computeTabloProgress` in `tablo_detail_view.go` counts non-etape tasks with `Status==StatusDone`; `TestComputeTabloProgress_*` all pass |
| 5 | TabloDetailViewModel groups tasks into 4 slices: Todo, InProgress, InReview, Done | VERIFIED | `NewTabloDetailViewModel` builds 4 `TabloDetailColumnView` in exact order; `TestNewTabloDetailViewModel_GroupsTasksByStatus` passes; `TestTabloDetailKanbanColumns` verifies all 4 data-status values in response body |
| 6 | TabloDetailViewModel.Etapes lists etapes (tasks with IsEtape=true) for the tablo, with task count per etape | VERIFIED | `NewTabloDetailViewModel` filters `task.IsEtape` and counts children by `ParentTaskID`; `TestNewTabloDetailViewModel_EtapesPopulated` passes |
| 7 | Rendered HTML from GET /tablos/{id} contains substring `initTabloDetailSortable` | VERIFIED | `TestGetTabloDetailPage_ContainsSortableScript` passes; `TabloDetailSortableScript` emits the function name via `templ.Raw` |
| 8 | Tablo detail page renders a header with tablo name as h1 (font-size 1.75rem) and a metadata row | VERIFIED | `tablo_detail.templ` line 28: `<h1 class="tablo-detail-title">{ vm.TabloName }</h1>`; `app.css` line 1990: `font-size: 1.75rem` with `font-weight: 600` |
| 9 | Tab bar renders Overview, Tasks, Files, Discussion, Events tabs; Tasks tab is active with class tab-nav-item--active | VERIFIED | `TabloDetailTabBar` in `tablo_detail.templ` lines 59-89: 5 tabs, Tâches anchor has `class="tab-nav-item tab-nav-item--active"` |
| 10 | Tab links use hx-get and hx-push-url=true targeting #tab-content — not plain href anchors | VERIFIED | All tab anchors use `hx-get`, `hx-target="#tab-content"`, `hx-push-url="true"` with `templ.SafeURL`; no plain href |
| 11 | Kanban board renders exactly 4 columns in a .tablo-kanban-board flex container | VERIFIED | `TabloDetailKanbanBoard` iterates `vm.Columns` (always 4); container uses `class="tablo-kanban-board"`; `app.css` line 2038 defines flex layout |
| 12 | Each column uses class tablo-kanban-column with data-status attribute set to the column ID | VERIFIED | `TabloDetailKanbanColumn`: `class="tablo-kanban-column" data-status={ col.ID }` |
| 13 | Each column contains a hidden reorder form with id reorder-form-{status} for Sortable.js onEnd | VERIFIED | Lines 118-124 in `tablo_detail.templ`: `id={ "reorder-form-" + col.ID }` with `style="display:none"` |
| 14 | Each task card uses class task-card and carries data-task-id | VERIFIED | `TabloDetailTaskCard`: `class="task-card" data-task-id={ task.ID }` |
| 15 | Drag handle element uses class task-drag-handle and is a child of .task-card | VERIFIED | Line 133: `<span class="task-drag-handle" aria-hidden="true">⠿</span>` inside `task-card-top-row` inside `task-card` |
| 16 | Empty column renders a .tablo-kanban-empty element with text 'Aucune tâche' | VERIFIED | Line 111: `<div class="tablo-kanban-empty">Aucune tâche</div>` when `len(col.Tasks) == 0` |
| 17 | Etapes section renders below the kanban board listing each etape name and its task count | VERIFIED | `TabloDetailEtapesSection` in `tablo_detail.templ` lines 154-166; conditional render in `TabloDetailPage` when `len(vm.Etapes) > 0` |
| 18 | app.css contains all required tablo detail CSS rules with correct values | VERIFIED | All 12 CSS must-haves confirmed by grep: `.tablo-detail-header` (line 1959), `.tablo-detail-title` (1988, 1.75rem/600), `.tablo-metadata-row` (1996, flex, gap 24px), `.tablo-kanban-board` (2038, flex, gap 16px, overflow-x auto), `.tablo-kanban-column` (2046, 18rem, 0.75rem radius), `.task-card` (2095, flex-column, 8px gap), `.task-drag-handle` (2119, opacity 0), `.task-card:hover .task-drag-handle` (2128, opacity 1), `.task-card-delete` (2144, opacity 0), `.task-card:hover .task-card-delete` (2150, opacity 1), `.tablo-progress-bar` (2019, `background: var(--color-brand-primary)`), `.tablo-files-table-wrapper` (2199, border-radius 12px), `.task-list` (1283, gap 8px + padding 8px), `.tablo-etapes-section` (2154), `.tablo-etape-row` (2177) |
| 19 | Full test suite remains green after all changes | VERIFIED | `go test ./... -count=1` — all 5 test packages pass (handlers, views, ui, ui/catalog, root) |

**Score:** 19/19 truths verified

### Required Artifacts

| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `go-backend/internal/web/views/tablo_detail_view.go` | TabloDetailViewModel + column/etape views + computeTabloProgress | VERIFIED | All 4 structs, NewTabloDetailViewModel, computeTabloProgress — no stub function remains |
| `go-backend/internal/web/views/tablo_detail.templ` | TabloDetailPage + all 7 sub-components | VERIFIED | 8 templ components defined; tablo_detail_templ.go generated |
| `go-backend/internal/web/handlers/tablo_detail.go` | GetTabloDetailPage handler + tabloDetailRepository interface | VERIFIED | Handler with full auth/ownership/task flow; tabloDetailRepository interface defined |
| `go-backend/internal/web/handlers/tablo_detail_tab.go` | GetTabloDetailTab handler | VERIFIED | Auth + tab slug switch; tasks tab returns kanban fragment; other tabs return "coming soon" |
| `go-backend/internal/web/handlers/tablo_detail_test.go` | Handler integration tests | VERIFIED | 6 test functions all pass |
| `go-backend/internal/web/views/tablo_detail_view_test.go` | View model unit tests | VERIFIED | 7 test functions all pass |
| `go-backend/router.go` | GET /tablos/{tabloID} + GET /tablos/{tabloID}/{tab} routes | VERIFIED | Lines 45-46; both routes registered before edit/post/delete routes |
| `go-backend/internal/web/ui/app.css` | All tablo detail + kanban CSS rules | VERIFIED | 37 new CSS blocks added in "Tablo Detail Page" section; .task-list updated |

### Key Link Verification

| From | To | Via | Status | Details |
|------|----|-----|--------|---------|
| `router.go` | `handlers/tablo_detail.go` | `authHandler.GetTabloDetailPage()` | WIRED | Line 45: `mux.Get("/tablos/{tabloID}", authHandler.GetTabloDetailPage())` |
| `router.go` | `handlers/tablo_detail_tab.go` | `authHandler.GetTabloDetailTab()` | WIRED | Line 46: `mux.Get("/tablos/{tabloID}/{tab}", authHandler.GetTabloDetailTab())` |
| `handlers/tablo_detail.go` | `tabloDetailRepository` interface | type assertion `h.repo.(tabloDetailRepository)` | WIRED | Lines 48-52; used in handler + tab handler |
| `views/tablo_detail.templ` | `views/tablo_detail_view.go` | `TabloDetailViewModel` struct fields | WIRED | templ components reference all ViewModel fields; `tablo_detail_templ.go` generated |
| `.tablo-kanban-board .sortable-column` | `POST /tablos/{id}/tasks/reorder` | Sortable.js onEnd → `#reorder-form-{status}` submit | WIRED (partial — reorder POST endpoint not yet implemented) | Form structure present; `TabloDetailSortableScript` calls `form.requestSubmit()`; POST route for reorder is a future concern (no TASK-01 requirement for it) |
| `.tablo-tab-bar a[hx-get]` | `GET /tablos/{tabloID}/{tab}` | HTMX hx-get + hx-push-url=true | WIRED | All 5 tabs use `hx-get` + `hx-push-url="true"` + `hx-target="#tab-content"` |

### Requirements Coverage

| Requirement | Phase | Description | Status | Evidence |
|-------------|-------|-------------|--------|----------|
| DETAIL-01 | Phase 20 | Tablo detail page (header, tasks section, etapes section, files table) is restyled to match Figma | SATISFIED | Full templ component with header, kanban (tasks section), etapes section, and CSS for files table wrapper |
| TASK-01 | Phase 20 | Kanban board columns, task cards, and drag-and-drop are restyled to match Figma | SATISFIED | `tablo_detail.templ`: 4 kanban columns with `.tablo-kanban-column`, `.task-card`, `.task-drag-handle`, Sortable.js init script; all CSS rules present |

Both DETAIL-01 and TASK-01 are fully addressed. No orphaned requirements for Phase 20.

### Anti-Patterns Found

| File | Line | Pattern | Severity | Impact |
|------|------|---------|----------|--------|
| None | — | — | — | — |

No `TBD`, `FIXME`, `XXX`, `TODO`, or `PLACEHOLDER` markers found in any phase-modified file. No empty implementations. No hardcoded empty data passed to rendering paths.

### Human Verification Required

#### 1. Tablo Detail Page Visual Layout

**Test:** Navigate to an authenticated tablo detail page (e.g. `/tablos/{uuid}`) in the browser.
**Expected:** Header shows tablo name in large font (1.75rem) with colored rounded-square avatar, owner name, calendar icon with "—" (no due date yet), status badge, purple progress bar. Tab bar shows 5 tabs with "Tâches" underlined/active.
**Why human:** CSS rendering and token resolution (`--color-brand-primary`) cannot be verified by file inspection alone.

#### 2. Drag-and-Drop Kanban

**Test:** Hover over a task card and drag it within or between kanban columns.
**Expected:** Drag handle (⠿) becomes visible on hover; card is draggable via Sortable.js; dropping a card triggers form submission (network request to `/tablos/{id}/tasks/reorder?status=…`).
**Why human:** Sortable.js runtime initialization, DOM event binding, and opacity CSS transitions require a real browser.

#### 3. HTMX Tab Navigation

**Test:** Click a tab other than "Tâches" (e.g. "Fichiers").
**Expected:** `#tab-content` swaps to "Cette section arrive bientôt." fragment; browser URL updates to `/tablos/{id}/files` via push-state.
**Why human:** HTMX swapping and URL push behavior require a running server and browser with JavaScript.

#### 4. Global Tasks Page CSS Regression Check

**Test:** Navigate to `/tasks` (the global tasks kanban, not a tablo detail page).
**Expected:** Task rows still render correctly using `.task-row` styles; `.task-list` now shows `gap: 8px` between items (intentional improvement); no visual breakage from the new `.task-card` rules.
**Why human:** CSS cascade interaction between `.task-card` (new) and `.task-row` (existing) can only be confirmed visually in the browser.

---

_Verified: 2026-05-18T16:05:00Z_
_Verifier: Claude (gsd-verifier)_
