---
phase: 16-tablo-detail
plan: 02
type: execute
wave: 2
depends_on:
- 16-01
files_modified:
- backend/templates/tablos.templ
autonomous: true
requirements:
- DETAIL-01
must_haves:
truths:
- "Tablo detail page header uses .project-card-top layout with color avatar, inline-editable title zone, and action controls"
- "Action controls use @ui.IconButton for Discussion and Delete, @ui.Button for Invite Member — no hardcoded #804EEC hex"
- "Metadata row uses .tablo-metadata-row with created date, @ui.Badge status pill, and .project-progress-track/bar"
- "Tab nav uses .tab-nav / .tab-nav-item / .tab-nav-item.is-active classes replacing hardcoded Tailwind hex classes"
- "Description zone renders inside TabloOverviewTabFragment (not in the persistent header)"
- "EtapeStrip is no longer called from TasksTabFragment; KanbanBoard call passes etapes parameter"
- "All existing tablo handler tests pass unchanged"
artifacts:
- path: backend/templates/tablos.templ
provides: Restyled TabloDetailPage header, tab nav, overview tab, TasksTabFragment EtapeStrip removal
contains: ".project-card-top"
- path: backend/templates/tablos_templ.go
provides: Generated Go from restyled tablos.templ
exports: []
key_links:
- from: backend/templates/tablos.templ TabloDetailPage
to: backend/internal/web/ui/app.css .project-card-top
via: CSS class on div element
pattern: "project-card-top"
- from: backend/templates/tablos.templ TabloOverviewTabFragment
to: TabloDescDisplay / TabloDescEditFragment
via: "@TabloDescDisplay(tablo, csrfToken)" call inside overview tab
pattern: "TabloDescDisplay"
- from: backend/templates/tablos.templ TasksTabFragment
to: KanbanBoard
via: "@KanbanBoard(tablo.ID, csrfToken, tasks, filter, etapes)"
pattern: "KanbanBoard.*etapes"
---
Restyle `TabloDetailPage` header area (project-card-top layout, color avatar, action controls), metadata row, tab nav bar, and overview tab content. Move the description zone from the persistent header into `TabloOverviewTabFragment`. Remove the `@EtapeStrip` call from `TasksTabFragment` and update the `KanbanBoard` call to pass `etapes`. All HTMX wiring, inline-edit zones, delete flow, and tab routing are preserved unchanged.
Purpose: Deliver DETAIL-01 — the tablo detail header matches the project-card-top design.
Output: Restyled `tablos.templ` with no hardcoded hex values in the detail page section.
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.claude/get-shit-done/workflows/execute-plan.md
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.claude/get-shit-done/templates/summary.md
@.planning/ROADMAP.md
@.planning/phases/16-tablo-detail/16-CONTEXT.md
@.planning/phases/16-tablo-detail/16-RESEARCH.md
@.planning/phases/16-tablo-detail/16-PATTERNS.md
@.planning/phases/16-tablo-detail/16-UI-SPEC.md
@.planning/phases/16-tablo-detail/16-01-SUMMARY.md
From backend/templates/tablos.templ (current state — read the file before editing):
TabloDetailPage signature (line ~200):
templ TabloDetailPage(tablo sqlc.Tablo, tasks []sqlc.Task, etapes []sqlc.Etape, counts EtapeTaskCounts, filter EtapeFilter, csrfToken string, activeTab string, user auth.User)
Current header structure uses raw inline Tailwind classes with hardcoded #804EEC hex. Replace with design token classes.
Color avatar pattern from TabloProjectCard (lines ~103–112) — reuse for detail header, adding first-character text:
if tablo.Color.Valid && tablo.Color.String != "" {
span class="project-avatar" style="background-color: {tablo.Color.String}"
if len(tablo.Title) > 0 { string([]rune(tablo.Title)[0:1]) }
} else {
span class="project-avatar"
if len(tablo.Title) > 0 { string([]rune(tablo.Title)[0:1]) }
}
Tab nav active state — current pattern (lines ~306–325) uses inline Tailwind hex classes per-tab. Replace ALL 5 tabs (overview, tasks, files, discussion, events):
if activeTab == "overview" || activeTab == "" { class="tab-nav-item is-active" } else { class="tab-nav-item" }
EtapeStrip call in TasksTabFragment (line ~412): DELETE this call
@EtapeStrip(tablo.ID, etapes, counts, filter, csrfToken, false)
KanbanBoard call in TasksTabFragment (line ~413): UPDATE to add etapes parameter
@KanbanBoard(tablo.ID, csrfToken, tasks, filter)
→ @KanbanBoard(tablo.ID, csrfToken, tasks, filter, etapes)
Delete button in detail header — per Pitfall 6: do NOT modify TabloDeleteButtonFragment (used by dashboard cards).
Create an INLINE IconButton inside a .tablo-delete-zone div in TabloDetailPage header:
div class="tablo-delete-zone"
@ui.IconButton(ui.IconButtonProps{Label: "Delete tablo", Icon: "trash", Variant: ui.IconButtonVariantDanger, Tone: ui.IconButtonToneGhost, Type: "button", Attrs: templ.Attributes{"hx-get": "/tablos/{id}/delete-confirm", "hx-target": "closest .tablo-delete-zone", "hx-swap": "outerHTML"}})
Discussion action in header: @ui.IconButton with Icon "chat", Label "Discussion", linking/navigating to discussion tab (ghost/neutral variant). Since Discussion is navigation, wrap in an tag or use hx-get to load the discussion tab fragment:
a href="/tablos/{id}/discussion" hx-get="/tablos/{id}/discussion" hx-target="#tab-content" hx-swap="innerHTML" hx-push-url="true" class="tab-nav-item" aria-label="Go to Discussion tab"
@ui.IconButton(...Icon: "chat"...) -- OR render the button label directly as link text with chat icon
Invite button: @ui.Button(ui.ButtonProps{Label: "Invite Member", Variant: ui.ButtonVariantGhost, Tone: ui.ButtonToneSolid, Size: ui.SizeMD, Type: "button"})
Metadata row:
div class="tablo-metadata-row"
div class="tablo-metadata-date"
[calendar SVG 1rem aria-hidden]
span "Created"
span { tablo.CreatedAt.Time.Format("Jan 2, 2006") }
@ui.Badge(ui.BadgeProps{Label: "In progress", Variant: ui.BadgeVariantPrimary})
div class="project-progress-track"
div class="project-progress-bar" style="width: 0%;"
Overview tab (TabloOverviewTabFragment): move the tablo-desc-zone call here (currently in persistent header). The component already exists — just relocate the @TabloDescDisplay(tablo, csrfToken) call inside a div class="tablo-desc-zone" inside TabloOverviewTabFragment.
Tab nav wrapper: change the outer div for the tab links to div class="tab-nav" (replaces existing raw flex Tailwind classes).
ui package variant identifiers (from backend/internal/web/ui/variants.go):
ui.ButtonVariantGhost, ui.ButtonVariantDefault, ui.ButtonToneSolid
ui.IconButtonVariantDanger, ui.IconButtonVariantNeutral, ui.IconButtonToneGhost
ui.BadgeVariantPrimary, ui.BadgeVariantInfo
ui.SizeMD
Task 1: Restyle TabloDetailPage header, metadata row, and tab nav
backend/templates/tablos.templ
- backend/templates/tablos.templ (read the FULL file before editing — you need to understand the current header structure, tab nav structure, action control positions, and the persistent desc zone location; the file is ~650 lines)
- backend/internal/web/ui/variants.go (read to confirm exact Go identifiers for variants/tones: ButtonVariantGhost, IconButtonVariantDanger, IconButtonToneGhost, BadgeVariantPrimary, SizeMD — use these exact names)
- .planning/phases/16-tablo-detail/16-PATTERNS.md (section "backend/templates/tablos.templ" — color avatar pattern, tab nav pattern, IconButton for delete, metadata row pattern, description zone relocation)
Edit `backend/templates/tablos.templ` to restyle `TabloDetailPage`. Make the following targeted changes:
1. HEADER — replace the current header div (which uses inline Tailwind flex classes with #804EEC) with a `.project-card-top` layout:
- Outer div: `class="project-card-top"`
- Left: `div class="project-card-title-row"` containing the project-avatar span (with tablo color + first char of title per PATTERNS.md color avatar pattern) followed by the existing `.tablo-title-zone` inline-edit zone (`@TabloTitleDisplay(tablo, csrfToken)` preserved as-is)
- Right: action controls row with gap, containing: (a) Discussion `@ui.IconButton` with `Icon: "chat"`, `Label: "Discussion"`, ghost/neutral variant, wrapped so it navigates to the discussion tab (use hx-get="/tablos/{id}/discussion" hx-target="#tab-content" hx-swap="innerHTML" hx-push-url="true" as Attrs); (b) `@ui.Button` for "Invite Member" with `ButtonVariantGhost`; (c) a `.tablo-delete-zone` div containing the trash `@ui.IconButton` (DO NOT use or modify `@TabloDeleteButtonFragment`)
2. METADATA ROW — replace the current metadata span/div elements (which use hardcoded color classes) with `div class="tablo-metadata-row"` containing: tablo-metadata-date div (calendar SVG + "Created" + formatted date), `@ui.Badge` for "In progress" with `BadgeVariantPrimary`, and the progress track/bar divs using `.project-progress-track` + `.project-progress-bar` CSS classes (per PATTERNS.md metadata row pattern)
3. TAB NAV — change the outer container of the 5 tab links to `div class="tab-nav"`. For each of the 5 tabs (overview, tasks, files, discussion, events): replace the long inline Tailwind class strings (which contain `text-[#804EEC]`, `border-[#804EEC]`, etc.) with the simple conditional: `if activeTab == "{tab}" { class="tab-nav-item is-active" } else { class="tab-nav-item" }`. Preserve all `hx-get`, `hx-target`, `hx-swap`, `hx-push-url`, and `href` attributes on each tab link unchanged.
4. DESCRIPTION ZONE RELOCATION — Remove the `tablo-desc-zone` div (containing `@TabloDescDisplay`) from the persistent header area (above the tab nav). Move it into `TabloOverviewTabFragment` as the first element inside that component. The `TabloDescDisplay` and `TabloDescEditFragment` components themselves are not modified — only the call site location changes.
After editing, run: `just generate` to regenerate `tablos_templ.go`.
grep -c "project-card-top" /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend/templates/tablos.templ && grep -c "tab-nav-item is-active" /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend/templates/tablos.templ && grep -c "tablo-metadata-row" /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend/templates/tablos.templ && grep -c "804EEC" /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend/templates/tablos.templ && cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source && go test ./backend/internal/web/... -run TestTablos -count=1
- `tablos.templ` contains `class="project-card-top"` in TabloDetailPage header
- `tablos.templ` contains `class="tab-nav-item is-active"` (applied conditionally to the active tab)
- `tablos.templ` contains `class="tablo-metadata-row"`
- `tablos.templ` contains `@ui.Badge(ui.BadgeProps{Label: "In progress", Variant: ui.BadgeVariantPrimary})`
- `tablos.templ` contains `.project-progress-track` and `.project-progress-bar` class references
- `grep -c "#804EEC" backend/templates/tablos.templ` returns 0 (all hardcoded hex removed from detail page)
- `tablos.templ` contains `@TabloDescDisplay` call inside `TabloOverviewTabFragment` (not in the persistent header)
- `go test ./backend/internal/web/... -run TestTablos -count=1` passes
- `TabloDeleteButtonFragment` function body is unchanged (grep its content to confirm)
TabloDetailPage header uses project-card-top layout; tab nav uses design token classes; description is in overview tab; no hardcoded hex values remain in the detail page header/nav section.
Task 2: Remove EtapeStrip call from TasksTabFragment and update KanbanBoard call site
backend/templates/tablos.templ
- backend/templates/tablos.templ (already read in Task 1 — find TasksTabFragment, line ~407–420; confirm EtapeStrip and KanbanBoard call positions)
- .planning/phases/16-tablo-detail/16-RESEARCH.md (Pattern 4: EtapeStrip Removal and OOB Impact — explains which EtapeStrip call sites exist in tablos.templ; this task handles Site 1 only; Sites 2 and 3 in tasks.templ are handled in Plan 03)
In `TasksTabFragment` inside `tablos.templ`:
1. DELETE the line: `@EtapeStrip(tablo.ID, etapes, counts, filter, csrfToken, false)` — remove this entire call (per D-E01; the EtapeStrip UI strip is removed). Also remove any surrounding `div id="etape-strip"` wrapper if one exists.
2. UPDATE the KanbanBoard call: change `@KanbanBoard(tablo.ID, csrfToken, tasks, filter)` to `@KanbanBoard(tablo.ID, csrfToken, tasks, filter, etapes)` — adding `etapes` as the 5th argument. Note: the `KanbanBoard` templ signature update happens in Plan 03; this call site change MUST be done simultaneously with that signature change to avoid a compile error. If Plan 03 has not yet run, add a TODO comment next to this line noting the parameter will be added when KanbanBoard signature is updated in Plan 03. The `templ generate && go build ./...` gate in Plan 03 will catch any mismatch.
After editing, run: `just generate` to regenerate `tablos_templ.go`.
grep -c "EtapeStrip" /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend/templates/tablos.templ && grep "KanbanBoard" /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend/templates/tablos.templ
- `grep "EtapeStrip" backend/templates/tablos.templ` returns 0 (the EtapeStrip call is gone from TasksTabFragment)
- `grep "KanbanBoard" backend/templates/tablos.templ` shows the call includes `etapes` as a parameter (or has a TODO comment pending Plan 03)
- `go test ./backend/internal/web/... -run TestTablos -count=1` passes (or compile error from KanbanBoard signature mismatch is expected and noted — will be resolved in Plan 03)
EtapeStrip is not called from TasksTabFragment; KanbanBoard call is updated to pass etapes.
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| Go template → HTML response | tablos.templ renders tablo data from DB into HTML; existing ownership guard (`loadOwnedTablo`) unchanged |
| HTMX GET requests | hx-get endpoints for tab fragments are authenticated routes; no change to auth middleware |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-16-02-01 | Tampering | TabloDetailPage inline-edit zones | accept | tablo-title-zone and tablo-desc-zone HTMX endpoints validate CSRF token and ownership — unchanged; only CSS classes change |
| T-16-02-02 | Spoofing | Discussion/Delete action controls | accept | Auth middleware on all /tablos/{id}/* routes unchanged; restyling does not change route protection |
| T-16-02-03 | Information Disclosure | tablo.Color inline style | accept | tablo.Color is user-supplied but rendered as a CSS value in a style attribute; no script injection possible via `background-color: {value}` — browsers do not execute style values as JS |
```bash
cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source
go test ./backend/internal/web/... -count=1
```
All existing tablo tests pass. No hardcoded #804EEC hex in tablos.templ for the detail page section.
- `tablos.templ` header uses `.project-card-top`, `.project-avatar` (with first char), `.tablo-title-zone`
- All 5 tab nav items use `.tab-nav-item` / `.tab-nav-item.is-active` (no hardcoded hex)
- Description zone is inside `TabloOverviewTabFragment`
- `EtapeStrip` is not called from `TasksTabFragment`
- `KanbanBoard` call passes `etapes`
- `go test ./backend/internal/web/... -count=1` passes