236 lines
16 KiB
Markdown
236 lines
16 KiB
Markdown
|
|
---
|
|||
|
|
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"
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
<objective>
|
|||
|
|
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.
|
|||
|
|
</objective>
|
|||
|
|
|
|||
|
|
<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>
|
|||
|
|
|
|||
|
|
<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
|
|||
|
|
|
|||
|
|
<interfaces>
|
|||
|
|
<!-- Critical contracts and current patterns the executor must know. -->
|
|||
|
|
|
|||
|
|
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 <a> 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
|
|||
|
|
</interfaces>
|
|||
|
|
</context>
|
|||
|
|
|
|||
|
|
<tasks>
|
|||
|
|
|
|||
|
|
<task type="auto">
|
|||
|
|
<name>Task 1: Restyle TabloDetailPage header, metadata row, and tab nav</name>
|
|||
|
|
<files>backend/templates/tablos.templ</files>
|
|||
|
|
<read_first>
|
|||
|
|
- 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)
|
|||
|
|
</read_first>
|
|||
|
|
<action>
|
|||
|
|
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`.
|
|||
|
|
</action>
|
|||
|
|
<verify>
|
|||
|
|
<automated>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</automated>
|
|||
|
|
</verify>
|
|||
|
|
<acceptance_criteria>
|
|||
|
|
- `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)
|
|||
|
|
</acceptance_criteria>
|
|||
|
|
<done>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.</done>
|
|||
|
|
</task>
|
|||
|
|
|
|||
|
|
<task type="auto">
|
|||
|
|
<name>Task 2: Remove EtapeStrip call from TasksTabFragment and update KanbanBoard call site</name>
|
|||
|
|
<files>backend/templates/tablos.templ</files>
|
|||
|
|
<read_first>
|
|||
|
|
- 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)
|
|||
|
|
</read_first>
|
|||
|
|
<action>
|
|||
|
|
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`.
|
|||
|
|
</action>
|
|||
|
|
<verify>
|
|||
|
|
<automated>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</automated>
|
|||
|
|
</verify>
|
|||
|
|
<acceptance_criteria>
|
|||
|
|
- `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)
|
|||
|
|
</acceptance_criteria>
|
|||
|
|
<done>EtapeStrip is not called from TasksTabFragment; KanbanBoard call is updated to pass etapes.</done>
|
|||
|
|
</task>
|
|||
|
|
|
|||
|
|
</tasks>
|
|||
|
|
|
|||
|
|
<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>
|
|||
|
|
|
|||
|
|
<verification>
|
|||
|
|
```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.
|
|||
|
|
</verification>
|
|||
|
|
|
|||
|
|
<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>
|
|||
|
|
|
|||
|
|
<output>
|
|||
|
|
After completion, create `.planning/phases/16-tablo-detail/16-02-SUMMARY.md` using the summary template.
|
|||
|
|
</output>
|