xtablo-source/.planning/phases/16-tablo-detail/16-02-PLAN.md
Arthur Belleville 965ec5e5ce
docs(16): create phase 16 tablo detail plan — 4 plans, 4 waves
Phase 16 delivers DETAIL-01/02/03/04: header restyling, kanban
tasks-section layout with server-side etape grouping, and files
table component. Ends with a browser verify checkpoint.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-16 23:20:49 +02:00

16 KiB
Raw Blame History


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.

<execution_context> @/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 </execution_context>

@.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 ~103112) — 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 ~306325) 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 ~407420; 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.

<threat_model>

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
</threat_model>
```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.

<success_criteria>

  • 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 </success_criteria>
After completion, create `.planning/phases/16-tablo-detail/16-02-SUMMARY.md` using the summary template.