diff --git a/.planning/phases/16-tablo-detail/16-RESEARCH.md b/.planning/phases/16-tablo-detail/16-RESEARCH.md
new file mode 100644
index 0000000..649ec3d
--- /dev/null
+++ b/.planning/phases/16-tablo-detail/16-RESEARCH.md
@@ -0,0 +1,772 @@
+# Phase 16: Tablo Detail - Research
+
+**Researched:** 2026-05-16
+**Domain:** Go templ templates, CSS design system, HTMX fragment wiring
+**Confidence:** HIGH
+
+## Summary
+
+Phase 16 is a pure visual restyling phase — no new routes, no new data models, no handler
+signature changes. The work touches four templ files and one CSS file:
+`backend/templates/tablos.templ`, `backend/templates/tasks.templ`,
+`backend/templates/etapes.templ`, `backend/templates/files.templ`, and
+`backend/internal/web/ui/app.css`.
+
+All design system components (`@ui.Badge`, `@ui.Button`, `@ui.IconButton`, `@ui.Table`,
+`@ui.EmptyState`) are already present in `backend/internal/web/ui/`. The CSS classes to be
+ported (`.tasks-section`, `.tasks-section-header`, `.task-row`, `.task-check`, `.task-body`,
+`.task-meta`, `.tasks-add-button`, `.task-list`, `.project-progress-track`,
+`.project-progress-bar`) are verbatim in `go-backend/internal/web/ui/app.css` and need to be
+appended to `backend/internal/web/ui/app.css`. New classes needed for this phase
+(`.kanban-column`, `.etape-group`, `.etape-group-header`, `.etape-group-color-dot`,
+`.etape-group-label`, `.tab-nav`, `.tab-nav-item`, `.tablo-metadata-row`,
+`.tablo-metadata-date`, `.task-list-empty`) are fully specified in the UI-SPEC.
+
+The single structural Go-level change is etape grouping inside `KanbanColumn`: a
+`groupTasksByEtape` helper (parallel to the existing `groupTasksByStatus`) produces per-etape
+task slices for server-side rendering inside each column, eliminating the need for
+`EtapeStrip` in the tasks tab.
+
+**Primary recommendation:** Implement in four incremental waves: (1) CSS additions to
+`app.css`, (2) header + metadata + tab nav + overview tab in `tablos.templ`, (3) kanban
+restyling + etape grouping in `tasks.templ` and `etapes.templ` (EtapeStrip removal), (4)
+files section restyling in `files.templ`. Each wave can be verified independently with the
+existing test suite.
+
+---
+
+
+## User Constraints (from CONTEXT.md)
+
+### Locked Decisions
+
+**Header (D-H01 through D-H06)**
+- Header uses `.project-card-top` layout (already in `app.css`): tablo color circle avatar
+ (left) + inline-editable title zone + action controls (right)
+- Action controls: Discussion link button, Invite button, Delete icon button — all restyled
+ with `ui.Button` / `ui.IconButton`; hardcoded `#804EEC` replaced with design tokens
+- Tablo color avatar: `.project-avatar` pattern with inline `background-color` style;
+ falls back to `var(--color-project-fallback)` when empty
+- Metadata row (Created date, Status pill, Progress bar): kept, restyled with design tokens;
+ status pill uses `@ui.Badge(...)`
+- Description zone moves from persistent header into the Overview tab content
+- Tab nav bar: replace hardcoded color classes with `.tab-nav` / `.tab-nav-item.is-active`
+ design token CSS; preserve all 5 existing tabs and their HTMX behavior
+
+**Kanban (D-K01 through D-K04)**
+- Horizontal 3-column layout stays; Sortable.js wiring unchanged
+- Column headers use `.tasks-section-header` CSS: status label + task count badge + Add task
+ button on right
+- Task cards use row style matching `.tasks-section` layout: checkbox + title + meta
+- `KanbanBoard`, `KanbanColumn`, `TaskCard` restyled in-place; no endpoint/handler changes
+
+**Etapes (D-E01 through D-E05)**
+- `EtapeStrip` removed from the tasks tab UI
+- Tasks grouped by etape inside each kanban column (server-side grouping in `KanbanColumn`)
+- Etape group headings always expanded, no collapse toggle
+- Grouping computed server-side; unassigned tasks rendered last under "No etape" label
+- `EtapeFilter` type retained for handler compatibility; filter defaults to "all" permanently
+
+**Files (D-F01 through D-F04)**
+- Files section header: `.overview-section-heading` with "Files" label + upload trigger right
+- File list: `@ui.Table(...)` with columns Filename / Size / Uploaded / Actions
+- Each file row: Download `@ui.IconButton` + Delete `@ui.IconButton`; existing
+ `FileDeleteConfirmFragment` preserved intact
+- Empty state: `@ui.EmptyState(...)` replaces `FileListEmpty()`
+
+### Claude's Discretion
+- Exact sub-heading style for etape groups inside kanban columns
+- Whether `EtapeFilter` param is fully removed from all handlers or just hidden from UI
+ (recommend keeping the type for handler compatibility, defaulting to "show all")
+- Exact badge token for the "In progress" status pill (BadgeVariantPrimary or
+ BadgeVariantNeutral per design token alignment)
+- Tab nav active state CSS — whether to use `.is-active` class or inline conditional classes
+ (match Phase 15 sidebar-nav-item pattern)
+
+### Deferred Ideas (OUT OF SCOPE)
+- Collapse toggle for etape groups
+- Mobile-responsive kanban
+- Etape management UI from within the tablo detail
+- Task priority/assignee display in task rows
+- Files preview pane or thumbnail generation
+
+
+---
+
+
+## Phase Requirements
+
+| ID | Description | Research Support |
+|----|-------------|------------------|
+| DETAIL-01 | Tablo detail header area matches the project-card-top design | `.project-card-top` already in `app.css` (Section 15); `project-avatar` and `project-card-title-row` already defined; `@ui.IconButton` and `@ui.Button` ready |
+| DETAIL-02 | Task kanban board uses the tasks-section design (section header, task rows, add control) | `.tasks-section*` and `.task-*` CSS in `go-backend/app.css` lines 1253–1348; `KanbanBoard` / `KanbanColumn` / `TaskCard` components identified for in-place restyling |
+| DETAIL-03 | Etapes section is visually consistent with the task section | Server-side grouping required; `groupTasksByStatus` pattern (lines 12–18 of `tasks.templ`) is the model for `groupTasksByEtape`; `EtapeStrip` call in `TasksTabFragment` + `TaskCardGone` + `TaskCardOOB` needs removal |
+| DETAIL-04 | Files section uses the table component | `@ui.Table` exists in `backend/internal/web/ui/table.templ`; `@ui.EmptyState` exists; `FileListRow` and `FilesTabFragment` are the targets; `FileDeleteConfirmFragment` preserved unchanged |
+
+
+---
+
+## Architectural Responsibility Map
+
+| Capability | Primary Tier | Secondary Tier | Rationale |
+|------------|-------------|----------------|-----------|
+| Header layout restyling | Frontend Server (templ) | CSS | Go template renders HTML; CSS controls visual |
+| Tab nav active state | Frontend Server (templ) | CSS | Active tab computed server-side (Go `activeTab` param) |
+| Etape grouping | Frontend Server (templ/Go) | — | D-E04: grouping is server-side in `KanbanColumn`; pure Go logic |
+| CSS class additions | CDN / Static (CSS file) | — | `app.css` is a static asset served by the Go embed |
+| Task kanban restyling | Frontend Server (templ) | CSS | Template structure + CSS classes |
+| File table restyling | Frontend Server (templ) | CSS | `@ui.Table` component already handles structure |
+
+---
+
+## Standard Stack
+
+### Core (already in `backend/`)
+
+| Library | Version | Purpose | Why Standard |
+|---------|---------|---------|--------------|
+| `github.com/a-h/templ` | v0.3.1020 (pinned) | Type-safe Go HTML templates | Project standard; all UI is templ |
+| Custom CSS + design tokens | — | Visual styling via `var(--...)` tokens | Established in Phase 13; no Tailwind utility classes in new CSS |
+| HTMX | v2 (pinned in justfile) | Server-driven interactivity | Project constraint; no JS framework |
+| Sortable.js | v1.15.7 (pinned) | Drag-and-drop kanban | Already wired in `KanbanBoard`; must not be changed |
+
+### Supporting UI Components (all already exist in `backend/internal/web/ui/`)
+
+| Component | Templ function | Phase 13 status |
+|-----------|---------------|-----------------|
+| Badge | `@ui.Badge(ui.BadgeProps{...})` | Complete |
+| Button | `@ui.Button(ui.ButtonProps{...})` | Complete |
+| IconButton | `@ui.IconButton(ui.IconButtonProps{...})` | Complete |
+| Table | `@ui.Table(ui.TableProps{Head: ..., Body: ...})` | Complete |
+| EmptyState | `@ui.EmptyState(ui.EmptyStateProps{...})` | Complete |
+
+**Key gap discovered:** The `UIIcon` switch in `icon_button.templ` supports: `plus`,
+`grid3x3`, `list`, `filter`, `search`, `calendar`, `pencil`, `trash`. It does NOT have a
+`download` icon or a `chat`/`message` icon. The `IconButton` component's `default` case
+renders the icon name as raw text. [VERIFIED: read `icon_button.templ` lines 19–73]
+
+**Action required:** Add `download` and `chat` (or `message-circle`) cases to the `UIIcon`
+switch before using `@ui.IconButton` for the Discussion link and file Download buttons.
+These are SVG additions only — no logic change.
+
+---
+
+## Architecture Patterns
+
+### System Architecture Diagram
+
+```
+Browser GET /tablos/{id}
+ |
+ v
+TabloDetailPage (tablos.templ)
+ ├── AppLayout (layout.templ) — sidebar, shell
+ ├── Header zone ──── .project-card-top
+ │ ├── .project-card-title-row: .project-avatar + .tablo-title-zone
+ │ └── action controls: IconButton(chat) + Button(invite) + IconButton(trash)
+ ├── Metadata row ──── .tablo-metadata-row
+ │ ├── .tablo-metadata-date + @ui.Badge(status) + progress track/bar
+ ├── Tab nav ───────── .tab-nav
+ │ └── .tab-nav-item[.is-active] × 5 tabs
+ │ hx-get → /tablos/{id}/{tab} → #tab-content innerHTML
+ └── #tab-content
+ ├── overview → TabloOverviewTabFragment (desc zone moved here)
+ ├── tasks → TasksTabFragment
+ │ └── KanbanBoard → KanbanColumn × 3
+ │ └── .tasks-section
+ │ ├── .tasks-section-header (h3 + badge + add button)
+ │ └── .task-list
+ │ └── .etape-group × N [server-side grouped]
+ │ ├── .etape-group-header (dot + name)
+ │ └── .task-row × M (checkbox + body + meta)
+ ├── files → FilesTabFragment
+ │ ├── .overview-section-heading (h3 + upload button)
+ │ ├── @ui.Table (filename / size / date / actions)
+ │ └── @ui.EmptyState (when no files)
+ ├── discussion → DiscussionTabFragment (unchanged)
+ └── events → EventsTabFragment (unchanged)
+```
+
+### Recommended Project Structure
+
+No new directories. All changes are in-place edits to existing files:
+
+```
+backend/
+├── internal/web/ui/
+│ ├── app.css ← append CSS sections (tasks, etape-group, tab-nav, metadata, progress)
+│ └── icon_button.templ ← add "download" and "chat" icon cases to UIIcon switch
+└── templates/
+ ├── tablos.templ ← restyle TabloDetailPage header, tab nav, TabloOverviewTabFragment
+ ├── tasks.templ ← restyle KanbanBoard, KanbanColumn, TaskCard; add etape grouping
+ ├── etapes.templ ← remove EtapeStrip (keep EtapeEdit/Create/DeleteConfirmFragment)
+ └── files.templ ← restyle FilesTabFragment with @ui.Table + @ui.EmptyState
+```
+
+### Pattern 1: Server-Side Etape Grouping in KanbanColumn
+
+**What:** Mirror `groupTasksByStatus` with a `groupTasksByEtape` helper that returns an
+ordered structure for rendering inside a column.
+
+**When to use:** Called from within the `KanbanColumn` templ component via a Go expression.
+
+```go
+// Source: [VERIFIED: tasks.templ lines 12-18 — groupTasksByStatus is the model]
+
+// EtapeGroup holds tasks for a single etape label within a kanban column.
+type EtapeGroup struct {
+ EtapeID string // "" for unassigned
+ EtapeTitle string // "No etape" for unassigned
+ EtapeColor string // "" for unassigned
+ Tasks []sqlc.Task
+}
+
+// groupTasksByEtape groups a task slice into ordered EtapeGroup slices.
+// Preserves etape declaration order; unassigned tasks always appear last.
+func groupTasksByEtape(tasks []sqlc.Task, etapes []sqlc.Etape) []EtapeGroup {
+ // Build index: etapeID → etape for O(1) lookup
+ // Iterate etapes in order to build groups
+ // Append unassigned group at end if any tasks have nil EtapeID
+}
+```
+
+The `KanbanColumn` signature gains an `etapes []sqlc.Etape` parameter. Since
+`KanbanColumn` is called from `KanbanBoard`, `KanbanBoard` also receives `etapes` and
+passes it through. `KanbanBoard` already receives tasks and filter — adding etapes matches
+the existing `TasksTabFragment` signature pattern.
+
+**KanbanBoard current signature:**
+```go
+// Source: [VERIFIED: tasks.templ line 23]
+templ KanbanBoard(tabloID uuid.UUID, csrfToken string, tasks []sqlc.Task, filter EtapeFilter)
+```
+
+**Updated signature:**
+```go
+templ KanbanBoard(tabloID uuid.UUID, csrfToken string, tasks []sqlc.Task, filter EtapeFilter, etapes []sqlc.Etape)
+```
+
+All `KanbanBoard` call sites must be updated:
+- `tablos.templ` line 413: `@KanbanBoard(tablo.ID, csrfToken, tasks, filter)` [VERIFIED]
+- `handlers_tasks.go` lines 589, 639: `templates.KanbanBoard(tablo.ID, csrf.Token(r), tasks, filter)` [VERIFIED]
+
+### Pattern 2: Tab Nav Active State via CSS Class
+
+**What:** Replace the inline conditional class strings (hardcoded `#804EEC` hex) with
+`.tab-nav-item` + `.is-active` modifier. Match the Phase 15 `sidebar-nav-item.is-active`
+pattern already in `app.css`.
+
+```templ
+// Source: [VERIFIED: tablos.templ lines 306-310, 321-325 — current pattern]
+// Current (hardcoded hex):
+if activeTab == "overview" || activeTab == "" {
+ class="flex items-center gap-2 pb-3 px-1 text-sm font-semibold ... text-[#804EEC] border-[#804EEC]"
+} else {
+ class="flex items-center gap-2 pb-3 px-1 text-sm font-semibold ... text-[#667085] hover:text-gray-900"
+}
+
+// New (design token classes):
+if activeTab == "overview" || activeTab == "" {
+ class="tab-nav-item is-active"
+} else {
+ class="tab-nav-item"
+}
+```
+
+### Pattern 3: Project Avatar in Header Context
+
+The `.project-avatar` CSS class with inline `background-color` style is already implemented
+in `TabloProjectCard` (Phase 15) and the CSS is in `app.css`. Reuse verbatim for the detail
+header.
+
+```templ
+// Source: [VERIFIED: tablos.templ lines 104-107 — Phase 15 pattern]
+if card.Tablo.Color.Valid && card.Tablo.Color.String != "" {
+
+ { string([]rune(card.Tablo.Title)[0:1]) }
+
+} else {
+
+}
+```
+
+Note: The UI-SPEC requires the first character of the title to appear inside the avatar as
+text. The current `TabloProjectCard` implementation shows an empty avatar (no character).
+Phase 16 should render the first character per the UI-SPEC.
+
+### Pattern 4: EtapeStrip Removal and OOB Impact
+
+`EtapeStrip` is called from three sites [VERIFIED]:
+
+1. `tablos.templ` `TasksTabFragment` line 412: `@EtapeStrip(tablo.ID, etapes, counts, filter, csrfToken, false)`
+2. `tasks.templ` `TaskCardGone` line 386: `@EtapeStrip(tabloID, etapes, counts, filter, csrfToken, true)` (OOB)
+3. `tasks.templ` `TaskCardOOB` line 398: `@EtapeStrip(tabloID, etapes, counts, filter, csrfToken, true)` (OOB)
+
+After removal:
+- Site 1: Simply delete the `@EtapeStrip(...)` call from `TasksTabFragment`. Also remove
+ the `#etape-strip` div from the tasks tab since it no longer exists.
+- Sites 2 and 3: Remove the `@EtapeStrip(...)` OOB call. Since EtapeStrip no longer renders
+ the `id="etape-strip"` element, the OOB swap target won't exist — removing the call is
+ safe and required.
+- The `EtapeStrip` templ function itself should be **retained** in `etapes.templ` but not
+ called from anywhere. The `etapes []sqlc.Etape`, `counts EtapeTaskCounts`, and
+ `filter EtapeFilter` parameters continue flowing through handlers and `TasksTabFragment`
+ unchanged (they feed `KanbanBoard` for etape grouping).
+
+### Anti-Patterns to Avoid
+
+- **Changing handler signatures:** `TabloTasksTabHandler`, `TaskCreateHandler`,
+ `TaskDeleteHandler`, `TaskReorderHandler`, etc. must not change. All 19 task tests and
+ 9 file tests verify handler behavior — any signature change breaks them.
+- **Removing `EtapeFilter` from handler flow:** The filter is still parsed in
+ `handlers_tasks.go` `loadTasksTabData` and passed through templates. Only the UI element
+ that generated the filter value (`EtapeStrip`) is removed. The filter param stays "all"
+ because nothing sets it anymore.
+- **Using Tailwind utility classes in new CSS:** All new CSS in `app.css` must use
+ `var(--...)` tokens. Tailwind is compiled separately — adding `tw-` utility classes to
+ `.templ` files risks class name collisions (Tailwind uses `@layer` and purging).
+- **Modifying `FileDeleteConfirmFragment`:** This fragment's HTMX `hx-target` points to
+ `.file-row-zone`. The new `@ui.Table`-based layout must preserve `
` elements with
+ `class="file-row-zone"` and `id="file-{id}"` so the existing delete confirm fragment's
+ outerHTML swap continues to work.
+- **Nesting OOB elements:** `TaskCardOOB` and `TaskCardGone` use `hx-swap-oob` — these
+ must remain top-level siblings, never nested inside the primary swap target.
+
+---
+
+## Don't Hand-Roll
+
+| Problem | Don't Build | Use Instead | Why |
+|---------|-------------|-------------|-----|
+| Status badge / count badge | Custom `` with inline styles | `@ui.Badge(...)` | Phase 13 established component; CSS already defined |
+| Action buttons (Discussion, Invite, Delete) | Raw `