---
phase: 20-tablo-detail-kanban-restyle
plan: "02"
subsystem: go-backend/views+handlers
tags: [tablo-detail, kanban, templ, htmx, sortable]
dependency_graph:
  requires:
    - 20-01 (TabloDetailViewModel, GetTabloDetailPage handler, GET /tablos/{tabloID} route)
  provides:
    - TabloDetailPage (real templ component, replaces Plan 01 stub)
    - TabloDetailHeader (header with avatar, title, metadata row)
    - TabloDetailTabBar (HTMX tab navigation with hx-get + hx-push-url)
    - TabloDetailKanbanBoard (4-column flex container)
    - TabloDetailKanbanColumn (column with header, task list, hidden reorder form)
    - TabloDetailTaskCard (task card with drag handle + delete button)
    - TabloDetailEtapesSection (etapes list below kanban)
    - TabloDetailSortableScript (Sortable.js DOMContentLoaded + htmx:afterSettle init)
    - GetTabloDetailTab handler (GET /tablos/{tabloID}/{tab})
    - GET /tablos/{tabloID}/{tab} route
  affects:
    - go-backend/router.go
tech_stack:
  added: []
  patterns:
    - templ component hierarchy (8 sub-components under TabloDetailPage)
    - Hidden reorder form per column (id="reorder-form-{status}") for Sortable.js onEnd
    - HTMX tab navigation (hx-get + hx-push-url="true")
    - Tab handler pattern (tasks tab returns partial; other tabs return "coming soon" fragment)
key_files:
  created:
    - go-backend/internal/web/views/tablo_detail.templ
    - go-backend/internal/web/views/tablo_detail_templ.go
    - go-backend/internal/web/handlers/tablo_detail_tab.go
  modified:
    - go-backend/internal/web/views/tablo_detail_view.go
    - go-backend/router.go
decisions:
  - Tab links use hx-get + hx-push-url="true" targeting #tab-content per UI-SPEC locked interaction contract; plain href anchors explicitly forbidden
  - TabloDetailKanbanBoard is a standalone component; TasksKanbanLayout from tasks.templ is not reused (separate surface per RESEARCH anti-patterns)
  - TabloDetailSortableScript uses templ.Raw to emit raw script block; guard el._sortable prevents double-init after HTMX swap
  - GetTabloDetailTab returns kanban board fragment only for tasks tab; other tabs return hardcoded "coming soon" text (tab slug never interpolated into HTML - T-20-07 mitigation)
  - strconv.Itoa used for integer-to-string conversion in templ (not fmt.Sprintf)
metrics:
  duration: "~15min"
  completed_date: "2026-05-18"
  tasks_completed: 2
  files_created: 3
  files_modified: 2
---

# Phase 20 Plan 02: TabloDetailPage Templ Component + Tab Handler Summary

Full HTML surface for the tablo detail page: templ components for header, HTMX tab bar, kanban board (4 columns each with hidden reorder form), task cards with drag handles, etapes section, and Sortable.js init script. Stub replaced with real templ component; tab route registered.

## What Was Built

**views/tablo_detail.templ**
- `TabloDetailPage(vm TabloDetailViewModel)` — outer wrapper calling all sub-components
- `TabloDetailHeader(vm TabloDetailViewModel)` — header with colored avatar, h1 title, metadata row (owner, due date, status badge, progress bar)
- `TabloDetailTabBar(tabloID string)` — 5-tab nav (Vue d'ensemble, Tâches, Fichiers, Discussion, Événements) using hx-get + hx-target="#tab-content" + hx-push-url="true"; Tâches always active
- `TabloDetailKanbanBoard(columns, tabloID)` — flex container iterating over 4 columns
- `TabloDetailKanbanColumn(col, tabloID)` — column with header (title, count, "+ Ajouter"), task list (.task-list.sortable-column), hidden reorder form id="reorder-form-{col.ID}", create zone
- `TabloDetailTaskCard(task, tabloID)` — article.task-card with .task-drag-handle, .task-card-title, ui.IconButton delete with hx-delete
- `TabloDetailEtapesSection(etapes)` — section.tablo-etapes-section with h2 "Étapes" and ul of li.tablo-etape-row items
- `TabloDetailSortableScript(tabloID)` — raw script block with initTabloDetailSortable() registered on DOMContentLoaded + htmx:afterSettle

**views/tablo_detail_view.go**
- Stub `TabloDetailPage` function (templ.ComponentFunc) removed
- `context`, `io`, `templ` imports removed (no longer needed in .go file)
- All struct types and `NewTabloDetailViewModel` / `computeTabloProgress` functions retained

**handlers/tablo_detail_tab.go**
- `GetTabloDetailTab()` handler for GET /tablos/{tabloID}/{tab}
- Auth check → UUID parse → tab slug switch
- tasks tab: fetches tablo + tasks → builds vm → renders TabloDetailKanbanBoard fragment
- Other tabs: returns hardcoded `<div class="tab-coming-soon">...</div>`

**router.go**
- `mux.Get("/tablos/{tabloID}/{tab}", authHandler.GetTabloDetailTab())` registered after /tablos/{tabloID}

## Security (Threat Model)

| Threat | Mitigation | Status |
|--------|------------|--------|
| T-20-04: task.Title injected into HTML | templ auto-escapes all { expr } interpolations | Mitigated |
| T-20-05: data-status from user input | data-status comes from hardcoded column IDs only | Mitigated |
| T-20-07: {tab} slug reflected into HTML | Tab slug used only as switch key; "coming soon" text hardcoded | Mitigated |

## Test Coverage

All pre-existing handler tests pass with the real templ component:
- `TestGetTabloDetailPage_Returns200` — authenticated + owned tablo → 200 + tablo name in body
- `TestGetTabloDetailPage_Returns404` — unknown tablo → 404
- `TestGetTabloDetailPage_Returns400` — invalid UUID → 400
- `TestGetTabloDetailPage_Unauthenticated` — no cookie → 302 /login
- `TestTabloDetailKanbanColumns` — body contains todo/in_progress/in_review/done data-status values
- `TestGetTabloDetailPage_ContainsSortableScript` — body contains "initTabloDetailSortable"

Full view model tests also pass (TestComputeTabloProgress_*, TestNewTabloDetailViewModel_*).

## Commits

| Hash | Type | Description |
|------|------|-------------|
| 4ae19fa | feat | TabloDetailPage templ component + GetTabloDetailTab handler |
| a60de1f | feat | Replace TabloDetailPage stub with templ component + register tab route |

## Deviations from Plan

None — plan executed exactly as written.

## Known Stubs

None. The TabloDetailPage stub from Plan 01 has been replaced. The "coming soon" tab content in GetTabloDetailTab is intentional scaffolding for Phase 21+ (overview, files, discussion, events), not an unresolved stub.

## Self-Check

Files created:
- go-backend/internal/web/views/tablo_detail.templ — FOUND
- go-backend/internal/web/views/tablo_detail_templ.go — FOUND
- go-backend/internal/web/handlers/tablo_detail_tab.go — FOUND

Commits:
- 4ae19fa — FOUND
- a60de1f — FOUND

just generate — PASSES (tablo_detail_templ.go generated)
go build ./... — PASSES
go test ./... -count=1 — ALL PASS (13 packages)

## Self-Check: PASSED
