From c3b470a1a7bdc9de43ecb016af8256e39f46f813 Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Sun, 17 May 2026 16:22:30 +0200 Subject: [PATCH] docs(19): create phase 19 tablo list revamp plans (3 plans, 3 waves) Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .planning/ROADMAP.md | 8 +- .../phases/19-tablo-list-revamp/19-01-PLAN.md | 338 ++++++++++++++++ .../phases/19-tablo-list-revamp/19-02-PLAN.md | 370 ++++++++++++++++++ .../phases/19-tablo-list-revamp/19-03-PLAN.md | 337 ++++++++++++++++ 4 files changed, 1052 insertions(+), 1 deletion(-) create mode 100644 .planning/phases/19-tablo-list-revamp/19-01-PLAN.md create mode 100644 .planning/phases/19-tablo-list-revamp/19-02-PLAN.md create mode 100644 .planning/phases/19-tablo-list-revamp/19-03-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index abea957..ed3070c 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -70,12 +70,18 @@ Plans: ### Phase 19: Tablo List Revamp **Goal:** Restyle the tablos page with revamped cards, real progress data, list/card toggle, and status field. **Requirements:** LIST-01, LIST-02, LIST-03 +**Plans:** 3 plans **Success criteria:** 1. Tablo cards display with updated Figma layout including a progress bar showing real task completion % 2. User can switch between card grid and list view; selection persists for the session 3. Tablos have an active/archived status field in DB; a status indicator is visible on cards and list rows 4. DB migration for status field is reversible +Plans: +- [ ] 19-01-PLAN.md — DB migration 0010, sqlc regen with status column, batch progress query, handler wiring +- [ ] 19-02-PLAN.md — Revamped TabloProjectCard template (badge, initial, progress bar) + list row CSS +- [ ] 19-03-PLAN.md — View toggle button + inline JS + tests for LIST-01/02/03 + ### Phase 20: Tablo Detail & Kanban Restyle **Goal:** Restyle the tablo detail page and kanban board to match Figma. **Requirements:** DETAIL-01, TASK-01 @@ -125,7 +131,7 @@ Plans: | 16. Tablo Detail | v3.0 | 4/4 | Complete | 2026-05-17 | | 17. Chat & Planning | v3.0 | 2/2 | Complete | 2026-05-17 | | 18. App Shell & Navigation | v4.0 | 0/3 | Pending | — | -| 19. Tablo List Revamp | v4.0 | — | Pending | — | +| 19. Tablo List Revamp | v4.0 | 0/3 | Pending | — | | 20. Tablo Detail & Kanban | v4.0 | — | Pending | — | | 21. Task Grid & Roadmap Views | v4.0 | — | Pending | — | | 22. Calendar Rework | v4.0 | — | Pending | — | diff --git a/.planning/phases/19-tablo-list-revamp/19-01-PLAN.md b/.planning/phases/19-tablo-list-revamp/19-01-PLAN.md new file mode 100644 index 0000000..5dbb31e --- /dev/null +++ b/.planning/phases/19-tablo-list-revamp/19-01-PLAN.md @@ -0,0 +1,338 @@ +--- +phase: 19-tablo-list-revamp +plan: "01" +type: execute +wave: 1 +depends_on: [] +files_modified: + - backend/migrations/0010_tablo_status.sql + - backend/internal/db/queries/tablos.sql + - backend/internal/db/sqlc/models.go # regenerated by sqlc generate + - backend/internal/db/sqlc/query.sql.go # regenerated by sqlc generate + - backend/templates/discussion_forms.go + - backend/internal/web/handlers_tablos.go +autonomous: true +requirements: [LIST-01, LIST-03] + +must_haves: + truths: + - "GET / renders without 500 after migration runs" + - "TabloCardView carries a Progress int field (0–100) populated by the handler" + - "sqlc.Tablo struct has a Status string field after sqlc regeneration" + - "TabloCardsFromUnreadRows maps Status from the query row into the Tablo struct" + - "Handler calls ListTabloProgressByIDs once per page load (batch, not N+1)" + artifacts: + - path: "backend/migrations/0010_tablo_status.sql" + provides: "Reversible goose migration adding status column to tablos" + contains: "+goose Up" + - path: "backend/internal/db/queries/tablos.sql" + provides: "Updated tablo queries with status column + new ListTabloProgressByIDs query" + contains: "ListTabloProgressByIDs" + - path: "backend/templates/discussion_forms.go" + provides: "TabloCardView with Progress int field" + contains: "Progress int" + - path: "backend/internal/web/handlers_tablos.go" + provides: "Handler wiring progress batch query" + contains: "ListTabloProgressByIDs" + key_links: + - from: "backend/internal/web/handlers_tablos.go (TablosListHandler)" + to: "backend/internal/db/sqlc/query.sql.go (ListTabloProgressByIDs)" + via: "deps.Queries.ListTabloProgressByIDs(ctx, tabloIDs)" + pattern: "ListTabloProgressByIDs" + - from: "backend/templates/discussion_forms.go (TabloCardsFromUnreadRows)" + to: "sqlc.Tablo.Status" + via: "row.Status mapped into Tablo struct literal" + pattern: "Status.*row.Status" +--- + + +Add the `status` column to the tablos table, regenerate sqlc types, add a batch progress query, and wire progress data into the handler before any template changes. + +Purpose: Establishes the data layer that Plans 02 and 03 render. The app must build and all existing tests must pass at the end of this plan. +Output: Migration file, updated SQL queries, regenerated sqlc files, enriched TabloCardView, and handler progress wiring. + + + +@/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 + + + +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/ROADMAP.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/19-tablo-list-revamp/19-CONTEXT.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/19-tablo-list-revamp/19-RESEARCH.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/18-app-shell-navigation/18-03-SUMMARY.md + + + + + +From backend/templates/discussion_forms.go (current — will be modified): +```go +type TabloCardView struct { + Tablo sqlc.Tablo + DiscussionUnreadCount int64 +} + +// TabloCardsFromUnreadRows — builds TabloCardView slice from query rows +// Must be updated to include Status from the row and Progress field (zero-initialized here) +func TabloCardsFromUnreadRows(rows []sqlc.ListTablosByUserWithDiscussionUnreadRow) []TabloCardView + +// TabloCardFromTablo — also needs Progress default of 0 (already implicit via zero-value) +func TabloCardFromTablo(tablo sqlc.Tablo) TabloCardView +``` + +From backend/internal/db/sqlc/models.go (current — will gain Status after regen): +```go +type Tablo struct { + ID uuid.UUID + UserID uuid.UUID + Title string + Description pgtype.Text + Color pgtype.Text + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz + // Status string ← added by migration + sqlc regen +} +``` + +From backend/internal/web/handlers_tablos.go (TablosListHandler — will be extended): +```go +type TablosDeps struct { + Queries *sqlc.Queries +} + +func TablosListHandler(deps TablosDeps) http.HandlerFunc { + // Currently calls: + // tabloRows, err := deps.Queries.ListTablosByUserWithDiscussionUnread(ctx, user.ID) + // cardViews := templates.TabloCardsFromUnreadRows(tabloRows) + // Must also call ListTabloProgressByIDs and populate cardViews[i].Progress +} +``` + +From backend/internal/db/queries/tablos.sql (current — all explicit column lists): +```sql +-- Every SELECT and RETURNING in this file enumerates columns explicitly. +-- After migration: add `status` to EVERY column list and GROUP BY. +-- ListTablosByUserWithDiscussionUnread also has GROUP BY — add status there too. +``` + +From backend/sqlc.yaml (no uuid[] override defined): +```yaml +overrides: + - db_type: "citext" + go_type: "string" + - db_type: "uuid" + go_type: + import: "github.com/google/uuid" + type: "UUID" +# No array override for uuid[] exists. +# For ListTabloProgressByIDs with ANY(@tablo_ids::uuid[]): +# sqlc with pgx/v5 generates []uuid.UUID for the parameter (pgx handles it natively). +# Verify after `sqlc generate` that the generated param type compiles — if it does not, +# add an override for "uuid[]" → github.com/google/uuid UUID with is_array: true, +# OR rewrite the query to use unnest($1::uuid[]) to avoid the array param. +``` + + + + + + Task 1: Migration, SQL query updates, and sqlc regeneration + + backend/migrations/0010_tablo_status.sql + backend/internal/db/queries/tablos.sql + backend/internal/db/sqlc/models.go + backend/internal/db/sqlc/query.sql.go + + + 1. Create backend/migrations/0010_tablo_status.sql with goose annotations (per D-07): + + -- +goose Up + ALTER TABLE tablos + ADD COLUMN status text NOT NULL DEFAULT 'active' + CHECK (status IN ('active', 'archived')); + + -- +goose Down + ALTER TABLE tablos DROP COLUMN status; + + 2. Run the migration against the local dev database: + cd backend && just migrate up + + 3. Update backend/internal/db/queries/tablos.sql — add `status` to EVERY explicit + column list and RETURNING clause. Affected queries: + + a. ListTablosByUser: add `status` after `updated_at` in SELECT list. + + b. ListTablosByUserWithDiscussionUnread: add `tablos.status` to the SELECT + column list AND to the GROUP BY clause (it is already grouped by all + non-aggregated tablo columns; add `tablos.status` after `tablos.updated_at` + in both SELECT and GROUP BY). + + c. GetTabloByID: add `status` after `updated_at`. + + d. InsertTablo: add `status` after `updated_at` in the RETURNING clause. + Do NOT add status to the INSERT column list — it has a DB DEFAULT. + + e. UpdateTablo: add `status` after `updated_at` in the RETURNING clause. + Do NOT add status to the SET clause — Phase 19 does not update status. + + 4. Add the new progress batch query to the end of tablos.sql: + + -- name: ListTabloProgressByIDs :many + -- Batch aggregation: one query for all tablos on the dashboard (D-06). + -- Counts all tasks regardless of etape assignment. + SELECT + tablo_id, + COUNT(*) FILTER (WHERE status = 'done')::int AS done_tasks, + COUNT(*)::int AS total_tasks + FROM tasks + WHERE tablo_id = ANY(@tablo_ids::uuid[]) + GROUP BY tablo_id; + + 5. Run sqlc generate: + cd backend && sqlc generate + + After generation, verify that backend/internal/db/sqlc/models.go now has + `Status string` on the Tablo struct, and that the generated Go code for + ListTabloProgressByIDs compiles (check the parameter type — it should be + []uuid.UUID with pgx/v5 native handling). If the file fails to compile due + to the uuid[] array type, see the fallback in the interfaces section: add a + sqlc.yaml override for uuid[] OR rewrite using unnest. + + 6. Run go build ./... from backend/ to confirm everything compiles before + proceeding to Task 2. + + + cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && go build ./... 2>&1 + + + - backend/migrations/0010_tablo_status.sql exists with +goose Up and +goose Down + - backend/internal/db/sqlc/models.go contains `Status string` on Tablo struct + - backend/internal/db/sqlc/query.sql.go contains ListTabloProgressByIDs function + - `go build ./...` exits 0 + + + + + Task 2: Enrich TabloCardView and wire progress in handler + + backend/templates/discussion_forms.go + backend/internal/web/handlers_tablos.go + + + In backend/templates/discussion_forms.go: + + 1. Add `Progress int` field to TabloCardView: + type TabloCardView struct { + Tablo sqlc.Tablo + DiscussionUnreadCount int64 + Progress int // 0–100; 0 when no tasks (D-05) + } + + 2. Update TabloCardsFromUnreadRows to include Status in the Tablo struct + literal. After sqlc regen, ListTablosByUserWithDiscussionUnreadRow will have + a Status field. Map it: + Tablo: sqlc.Tablo{ + ID: row.ID, + UserID: row.UserID, + Title: row.Title, + Description: row.Description, + Color: row.Color, + CreatedAt: row.CreatedAt, + UpdatedAt: row.UpdatedAt, + Status: row.Status, // ← added + }, + + In backend/internal/web/handlers_tablos.go (TablosListHandler): + + 3. After building cardViews, extract tablo IDs and call the progress batch query: + + tabloIDs := make([]uuid.UUID, len(cardViews)) + for i, cv := range cardViews { + tabloIDs[i] = cv.Tablo.ID + } + + progressRows, err := deps.Queries.ListTabloProgressByIDs(ctx, tabloIDs) + if err != nil { + slog.Default().Error("tablos list: progress query failed", "user_id", user.ID, "err", err) + // non-fatal: proceed with Progress = 0 for all cards + progressRows = nil + } + + progressMap := make(map[uuid.UUID]int, len(progressRows)) + for _, p := range progressRows { + if p.TotalTasks > 0 { + progressMap[p.TabloID] = int(p.DoneTasks * 100 / p.TotalTasks) + } + } + for i := range cardViews { + cardViews[i].Progress = progressMap[cardViews[i].Tablo.ID] + } + + The "uuid" import is already present in handlers_tablos.go. Confirm it is in + scope (it is used for uuid.UUID in other handlers). + + 4. Ensure the import block in discussion_forms.go still compiles — no new + imports are required (sqlc and uuid are already imported). + + + cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && go build ./... && go test ./... 2>&1 + + + - TabloCardView has Progress int field + - TabloCardsFromUnreadRows maps row.Status into Tablo.Status + - TablosListHandler calls ListTabloProgressByIDs and populates cardViews[i].Progress + - `go build ./...` exits 0 + - `go test ./...` passes (no new failures — existing tests may skip due to no test DB) + + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| DB migration → application | Schema change must be reversible; DOWN migration must not lose data | +| Handler → DB (progress query) | Parameterized batch query; no SQL injection surface | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-19-01-01 | Tampering | tablos.status column | mitigate | DB CHECK constraint `IN ('active', 'archived')` prevents invalid values at write time; application never sets status in Phase 19 (DEFAULT only) | +| T-19-01-02 | Information Disclosure | ListTabloProgressByIDs | accept | Query is scoped to tablo_ids from the authenticated user's own tablos; ownership enforced upstream in TablosListHandler via user_id filter in ListTablosByUserWithDiscussionUnread | +| T-19-01-03 | Denial of Service | Migration 0010 | accept | ALTER TABLE ADD COLUMN with DEFAULT on a small user table; no table lock risk for expected data volume | + + + +After both tasks complete: + +```bash +cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend +go build ./... +go test ./... +grep -c "ListTabloProgressByIDs" internal/db/sqlc/query.sql.go +grep -c "Progress int" templates/discussion_forms.go +grep -c "ListTabloProgressByIDs" internal/web/handlers_tablos.go +grep -c "status" internal/db/sqlc/models.go +``` + +Expected: all greps return 1 or more; go build exits 0. + + + +- `backend/migrations/0010_tablo_status.sql` exists with both +goose Up and +goose Down blocks +- `grep -c "Status string" backend/internal/db/sqlc/models.go` returns 1 +- `grep -c "ListTabloProgressByIDs" backend/internal/db/sqlc/query.sql.go` returns 1 +- `grep -c "Progress int" backend/templates/discussion_forms.go` returns 1 +- `grep -c "ListTabloProgressByIDs" backend/internal/web/handlers_tablos.go` returns 1 +- `cd backend && go build ./...` exits 0 +- `cd backend && go test ./...` has no new failures + + + +After completion, create `/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/19-tablo-list-revamp/19-01-SUMMARY.md` following the summary template. + diff --git a/.planning/phases/19-tablo-list-revamp/19-02-PLAN.md b/.planning/phases/19-tablo-list-revamp/19-02-PLAN.md new file mode 100644 index 0000000..4e7a6eb --- /dev/null +++ b/.planning/phases/19-tablo-list-revamp/19-02-PLAN.md @@ -0,0 +1,370 @@ +--- +phase: 19-tablo-list-revamp +plan: "02" +type: execute +wave: 2 +depends_on: ["19-01"] +files_modified: + - backend/templates/tablos.templ + - backend/internal/web/ui/app.css + - backend/static/tailwind.css # regenerated by tailwindcss CLI + - backend/templates/tablos_templ.go # regenerated by templ generate +autonomous: true +requirements: [LIST-01, LIST-03] + +must_haves: + truths: + - "TabloProjectCard shows a status badge (top-left) and delete button (top-right)" + - "TabloProjectCard shows the tablo initial letter inside the colored avatar circle" + - "TabloProjectCard shows a progress bar filled to card.Progress % with label 'Progression: X%'" + - "Each tablo card renders both a .project-card element and a .tablo-list-row sibling" + - "app.css contains .tablo-list-row, .project-card-progress-row, and [data-view='list'] CSS rules" + - "go build and templ generate both exit 0 after changes" + artifacts: + - path: "backend/templates/tablos.templ" + provides: "Revamped TabloProjectCard matching production screenshot" + contains: "project-card-progress-row" + - path: "backend/internal/web/ui/app.css" + provides: "Progress row CSS + list row CSS + data-view toggle rules" + contains: "tablo-list-row" + - path: "backend/static/tailwind.css" + provides: "Regenerated Tailwind output" + contains: "tablo-list-row" + key_links: + - from: "TabloProjectCard template" + to: "card.Progress (int, 0–100)" + via: "inline style width: X% on .project-progress-bar" + pattern: "project-progress-bar" + - from: "TabloProjectCard template" + to: "card.Tablo.Status (string)" + via: "ui.Badge label" + pattern: "ui.Badge" + - from: ".project-card-progress-row CSS" + to: ".project-progress-track / .project-progress-bar" + via: "nested child selectors in app.css §20" + pattern: "project-progress-track" +--- + + +Rebuild `TabloProjectCard` to match the production screenshot design — status badge, colored avatar initial, title, date, progress bar — and add both the new card CSS and the dual-element list row foundation. + +Purpose: Delivers LIST-01 and the visual part of LIST-03. Plan 03 adds the toggle button that switches between the two elements rendered here. +Output: Revamped `TabloProjectCard` template + new CSS classes in app.css + regenerated tailwind.css. + + + +@/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 + + + +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/ROADMAP.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/19-tablo-list-revamp/19-CONTEXT.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/19-tablo-list-revamp/19-RESEARCH.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/18-app-shell-navigation/18-03-SUMMARY.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/19-tablo-list-revamp/19-01-SUMMARY.md + +Read before making visual decisions: +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/screenshots/Homepage.png +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/screenshots/ssidebar-header.png + + + + + +From backend/templates/discussion_forms.go (after Plan 01): +```go +type TabloCardView struct { + Tablo sqlc.Tablo // .ID, .Title, .Color (pgtype.Text), .CreatedAt (pgtype.Timestamptz), .Status (string) + DiscussionUnreadCount int64 + Progress int // 0–100 +} +``` + +From backend/internal/web/ui/variants.go: +```go +type BadgeVariant string +const ( + BadgeVariantInfo BadgeVariant = "info" + BadgeVariantWarning BadgeVariant = "warning" + BadgeVariantSuccess BadgeVariant = "success" + BadgeVariantDanger BadgeVariant = "danger" + BadgeVariantPrimary BadgeVariant = "primary" // ← use for "Active" status badge +) +``` + +From backend/internal/web/ui/badge.templ: +```go +type BadgeProps struct { + Label string + Variant BadgeVariant +} +templ Badge(props BadgeProps) { ... } +// Usage: @ui.Badge(ui.BadgeProps{Label: "Active", Variant: ui.BadgeVariantPrimary}) +``` + +From backend/templates/tablos.templ (current TabloProjectCard — root element is
): +```go +// Current root:
+// HTMX OOB: TabloCardWithOOBFormClear calls TabloCard (NOT TabloProjectCard) for new tablo insertion. +// TabloProjectCard is rendered in the dashboard grid only. +// Safe to change its root element to a wrapper article with class="tablo-card-wrapper". +``` + +From backend/internal/web/ui/app.css (existing CSS — do NOT remove or rename): +```css +/* §14 */ .project-grid — 3-column grid, gap 1.25rem +/* §15 */ .project-card — card shell (border, radius 1rem, padding 1rem) +/* §16 */ .project-card-top — flex space-between, margin-bottom 1rem +/* §17 */ .project-card-title-row — flex, gap 0.75rem, margin-bottom 1rem +/* §17 */ .project-avatar — 3rem×3rem colored circle, background: var(--project-color, fallback) +/* §18 */ .project-date-row — flex, muted color, font-size 0.875rem, margin-bottom 1rem +/* §20 */ .project-progress-track — muted bg, border-radius 999px, height 0.5rem +/* §20 */ .project-progress-bar — background: var(--project-color, fallback), border-radius 999px, height 100% +``` + +Progress bar color note (Pitfall 3 from RESEARCH.md): +- .project-progress-bar uses var(--project-color, var(--color-project-fallback)). +- Per D-11: use var(--color-accent) for the progress bar on cards. Add a new class + .project-card-progress-bar that overrides background to var(--color-accent) instead + of var(--project-color). Use this class (not .project-progress-bar) on dashboard cards. +- Alternatively, set style="--project-color: var(--color-accent)" on the .project-card + wrapper. Either approach is acceptable. + +Initial letter pattern (from TabloDetailPage): +```go +// Confirmed pattern for avatar initial: +if card.Tablo.Color.Valid && card.Tablo.Color.String != "" { + + if len(card.Tablo.Title) > 0 { + { string([]rune(card.Tablo.Title)[0:1]) } + } + +} else { + + if len(card.Tablo.Title) > 0 { + { string([]rune(card.Tablo.Title)[0:1]) } + } + +} +``` + +String import for Itoa (already in discussion_forms.go — NOT in tablos.templ yet): +```go +// tablos.templ needs: import "strconv" for strconv.Itoa(card.Progress) +// Add "strconv" to the import block in tablos.templ. +// strings.Title is deprecated — use strings.ToUpper(card.Tablo.Status[:1]) + +// card.Tablo.Status[1:] for title-casing, OR just pass "Active" as the badge label +// directly since Phase 19 only shows 'active' status. Recommended: title-case helper. +``` + +Tailwind rebuild command: +```bash +cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend +./bin/tailwindcss -i tailwind.input.css -o static/tailwind.css --minify +``` + + + + + + Task 1: Rebuild TabloProjectCard template with dual card+row output + + backend/templates/tablos.templ + + + Read the production screenshot at screenshots/ssidebar-header.png before editing. + The target layout (per D-10): status badge top-left, delete icon top-right, colored + avatar circle with initial letter, title, creation date with calendar icon, + "Progression: X%" label + progress bar. + + Rewrite the TabloProjectCard component in backend/templates/tablos.templ. + + 1. Add "strconv" and "strings" to the import block at the top of tablos.templ. + + 2. Replace the current TabloProjectCard body. The new structure: + + a. Outer wrapper article (same id, new class "tablo-card-wrapper") containing + both the .project-card div and the .tablo-list-row div as siblings. + This satisfies the dual-element approach (see RESEARCH.md pitfall 4) so that + the HTMX OOB fragment remains a single root element. + + b. Inside .project-card: + - .project-card-top: left side = status badge, right side = delete icon button + - .project-card-title-row: avatar circle (with initial letter), title h4 + - .project-date-row: calendar SVG icon + formatted date + - .project-card-progress-row: "Progression: X%" span + .project-progress-track + containing .project-card-progress-bar (see Pitfall 3 note in interfaces) + + c. Inside .tablo-list-row (hidden by default via CSS, shown in list view): + - Status badge + - Title span + - Date span + - Progress label "X%" + - Delete icon button + (Full table-row layout is styled in Plan 03's CSS additions. This task + renders the HTML structure; Plan 03 finalizes the toggle mechanism.) + + For the status badge label: title-case the Status string. Since strings.Title is + deprecated, use a local helper in the template: + { strings.ToUpper(card.Tablo.Status[:1]) + card.Tablo.Status[1:] } + Guard against empty string: if len(card.Tablo.Status) > 0. + + For the progress bar: use class "project-card-progress-bar" (a new CSS class + defined in Task 2 that uses var(--color-accent) instead of var(--project-color)). + Set inline style for width: { strconv.Itoa(card.Progress) + "%" } + + For the delete icon button: keep the existing @ui.IconButton call with hx-get, + hx-target, hx-swap identical to the current implementation. + + The edit-title pencil icon button from the current implementation can be removed + per the production screenshot — the design shows only the delete button top-right. + + Do NOT rename or remove any other templ components in this file. + + 3. Run templ generate: + cd backend && templ generate ./... + + 4. Run go build ./... to confirm the file compiles. + + + cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && templ generate ./... && go build ./... 2>&1 + + + - TabloProjectCard renders an outer article.tablo-card-wrapper containing .project-card and .tablo-list-row siblings + - .project-card-top has status badge on left and delete button on right + - .project-card-title-row has avatar with initial letter + - .project-card-progress-row has "Progression: X%" label and .project-card-progress-bar + - `templ generate` exits 0 + - `go build ./...` exits 0 + + + + + Task 2: Add progress-row and list-row CSS to app.css, rebuild Tailwind + + backend/internal/web/ui/app.css + backend/static/tailwind.css + + + Edit backend/internal/web/ui/app.css only (never edit static/tailwind.css directly). + + Append a new CSS section after Section 20 (progress track/bar). Call it + "Section 20b — Dashboard card progress row and list-row": + + 1. .project-card-progress-row — wrapper for the progress label + bar on cards: + display: flex; flex-direction: column; gap: 0.35rem; + + 2. .project-card-progress-label — "Progression: X%" text: + color: var(--color-text-muted); font-size: 0.8rem; + + 3. .project-card-progress-bar — accent-colored bar for dashboard cards (overrides + the project-color var used in .project-progress-bar): + background: var(--color-accent); + border-radius: 999px; + height: 100%; + + 4. .tablo-card-wrapper — transparent grid item wrapper (so the grid layout still + works when each slot contains a wrapper instead of a bare .project-card): + display: contents; + /* display:contents makes the wrapper invisible to layout — children + (.project-card and .tablo-list-row) participate in the grid directly */ + + 5. .tablo-list-row — row layout for list view (hidden by default): + display: none; + align-items: center; + gap: 1rem; + padding: 0.75rem 1rem; + border: 1px solid var(--color-border-subtle); + border-radius: 0.75rem; + background: var(--color-surface-default); + + 6. .tablo-list-row-title — flex 1, font-weight 500 + + 7. .tablo-list-row-meta — muted color, font-size 0.875rem, flex items gap 0.5rem + + 8. [data-view="list"] toggle rules (append at the end of the new section): + + #tablos-list[data-view="list"] { + display: flex; + flex-direction: column; + gap: 0.5rem; + } + + #tablos-list[data-view="list"] .project-card { + display: none; + } + + #tablos-list[data-view="list"] .tablo-list-row { + display: flex; + } + + Note on display:contents and list view: when data-view="list", the + #tablos-list switches from grid to flex-column. The .tablo-card-wrapper + wrappers (display:contents) are transparent to both layouts, so the switch + works without overriding the wrapper. + + After editing app.css, rebuild Tailwind: + cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend + ./bin/tailwindcss -i tailwind.input.css -o static/tailwind.css --minify + + + cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && grep -c "tablo-list-row" internal/web/ui/app.css && grep -c "project-card-progress-row" internal/web/ui/app.css && grep -c "tablo-list-row" static/tailwind.css + + + - app.css contains .tablo-card-wrapper (display: contents), .tablo-list-row, .project-card-progress-row, .project-card-progress-bar, and [data-view="list"] toggle rules + - static/tailwind.css is regenerated and contains "tablo-list-row" + - `go build ./...` still exits 0 after CSS changes + + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| Template → DB value (Status) | Status string from DB is rendered as badge label; templ auto-escapes | +| Template → Progress int | Integer 0–100 from handler; rendered as inline style width and as text | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-19-02-01 | Information Disclosure | Status badge label | accept | Status value comes only from the authenticated user's own tablos; no cross-user leakage | +| T-19-02-02 | Tampering | Progress inline style | accept | Progress is server-computed int (0–100); rendered as `width: X%` via strconv.Itoa — no user input in path | +| T-19-02-03 | Elevation of Privilege | Delete button HTMX target | accept | hx-get /tablos/{id}/delete-confirm requires server-side ownership check (loadOwnedTablo); client-side rendering does not bypass this | + + + +After both tasks complete: + +```bash +cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend +templ generate ./... +go build ./... +go test ./internal/web/... -run TestTablosDashboard_ProjectCards -v +grep -c "project-card-progress-row" internal/web/ui/app.css +grep -c "tablo-list-row" internal/web/ui/app.css +grep -c "tablo-card-wrapper" internal/web/ui/app.css +grep -c "tablo-list-row" static/tailwind.css +``` + +Expected: all greps return 1+; build exits 0; test passes or skips (no test DB). + + + +- `grep -c "project-card-progress-row" backend/internal/web/ui/app.css` returns 1 +- `grep -c "tablo-list-row" backend/internal/web/ui/app.css` returns 1 +- `grep -c "tablo-card-wrapper" backend/internal/web/ui/app.css` returns 1 +- `grep -c "tablo-list-row" backend/static/tailwind.css` returns 1 +- `grep -c "project-card-progress-row" backend/templates/tablos.templ` returns 1 +- `grep -c "ui.Badge" backend/templates/tablos.templ` returns 1 (status badge) +- `cd backend && go build ./...` exits 0 +- Visual: GET / shows cards with status badge, avatar initial, progress bar filled to correct % + + + +After completion, create `/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/19-tablo-list-revamp/19-02-SUMMARY.md` following the summary template. + diff --git a/.planning/phases/19-tablo-list-revamp/19-03-PLAN.md b/.planning/phases/19-tablo-list-revamp/19-03-PLAN.md new file mode 100644 index 0000000..5f5522d --- /dev/null +++ b/.planning/phases/19-tablo-list-revamp/19-03-PLAN.md @@ -0,0 +1,337 @@ +--- +phase: 19-tablo-list-revamp +plan: "03" +type: execute +wave: 3 +depends_on: ["19-01", "19-02"] +files_modified: + - backend/templates/tablos.templ + - backend/internal/web/ui/app.css + - backend/static/tailwind.css # regenerated by tailwindcss CLI + - backend/templates/tablos_templ.go # regenerated by templ generate + - backend/internal/web/handlers_tablos_test.go +autonomous: true +requirements: [LIST-01, LIST-02, LIST-03] + +must_haves: + truths: + - "GET / renders a view-toggle button in the .overview-section-heading" + - "The toggle button click sets data-view='list' on #tablos-list and back to 'grid'" + - "In grid view, .project-card elements are visible and .tablo-list-row elements are hidden" + - "In list view, .tablo-list-row elements are visible and .project-card elements are hidden" + - "View resets to grid on page reload (no persistence per D-03)" + - "Test assertions for LIST-01/LIST-02/LIST-03 pass (or skip, not fail)" + artifacts: + - path: "backend/templates/tablos.templ" + provides: "View toggle button in TablosDashboard with inline onclick JS" + contains: "view-toggle-btn" + - path: "backend/internal/web/handlers_tablos_test.go" + provides: "Tests for progress bar, toggle button, and status badge presence" + contains: "TestTablosDashboard_ProgressBar" + - path: "backend/static/tailwind.css" + provides: "Rebuilt tailwind output containing view-toggle-btn" + contains: "view-toggle-btn" + key_links: + - from: "Toggle button onclick JS" + to: "#tablos-list element's data-view attribute" + via: "document.getElementById('tablos-list').dataset.view" + pattern: "dataset.view" + - from: "[data-view='list'] CSS rule" + to: ".tablo-list-row visibility" + via: "display: flex toggle in app.css" + pattern: "data-view.*list" +--- + + +Add the list/grid view toggle button to `TablosDashboard`, wire the inline JS, finalize list row CSS, and add automated test coverage for LIST-01/02/03. + +Purpose: Completes the user-visible toggle feature (LIST-02) and adds test coverage for all three LIST requirements. The app ships Phase 19 after this plan. +Output: Toggle button in template, view-toggle-btn CSS, automated tests, rebuilt tailwind.css. + + + +@/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 + + + +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/ROADMAP.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/19-tablo-list-revamp/19-CONTEXT.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/19-tablo-list-revamp/19-RESEARCH.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/19-tablo-list-revamp/19-01-SUMMARY.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/19-tablo-list-revamp/19-02-SUMMARY.md + +Read before making visual decisions: +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/screenshots/ssidebar-header.png + + + + + +From backend/templates/tablos.templ (after Plan 02): +```go +// TablosDashboard current .overview-section-heading block: +//
+//

Your Tablos

+// @ui.Button(...) // "New tablo" button +//
+//
...
+// +// Must add: toggle button BEFORE "New tablo" button, inside a flex wrapper. +// #tablos-list must gain data-view="grid" attribute. +``` + +Toggle button HTML pattern (per D-03, D-04 from CONTEXT.md): +```html + + +``` + +In templ syntax, multiline onclick via templ.Attributes: +```go +@ui.IconButton(ui.IconButtonProps{ + Label: "Toggle list view", + Icon: "list", // check if "list" icon exists in ui package + Variant: ui.IconButtonVariantNeutral, + Tone: ui.IconButtonToneGhost, + Type: "button", + Attrs: templ.Attributes{ + "onclick": "var el=document.getElementById('tablos-list');el.dataset.view=el.dataset.view==='list'?'grid':'list'", + }, +}) +// If "list" icon is not in the ui icon set, use a plain + @ui.Button(...) // existing "New tablo" button, unchanged + + + Note: Use a plain