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
+
+
+
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 %
+
+
+
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
+
+
+
+
+ Task 1: Add view toggle button to TablosDashboard + data-view attribute on container
+
+ backend/templates/tablos.templ
+ backend/internal/web/ui/app.css
+ backend/static/tailwind.css
+
+
+ In backend/templates/tablos.templ, modify the TablosDashboard component:
+
+ 1. Add data-view="grid" to the #tablos-list div:
+ Change: <div id="tablos-list" class="project-grid">
+ To: <div id="tablos-list" class="project-grid" data-view="grid">
+
+ 2. Wrap the heading row content in a flex div so the toggle button and "New tablo"
+ button sit together on the right side:
+ Change the .overview-section-heading inner structure from:
+
+
+ Note: Use a plain with inline SVG rather than ui.IconButton to avoid
+ checking icon availability. The onclick attribute is single-line JavaScript; it
+ is safe for templ's attribute handling.
+
+ In backend/internal/web/ui/app.css, append a new section after Section 20b:
+
+ /* ============================================================
+ Section 20c — View toggle button
+ ============================================================ */
+
+ .view-toggle-btn {
+ align-items: center;
+ background: transparent;
+ border: 1px solid var(--color-border-subtle);
+ border-radius: 0.5rem;
+ color: var(--color-text-secondary);
+ cursor: pointer;
+ display: inline-flex;
+ height: 2rem;
+ justify-content: center;
+ padding: 0;
+ transition: background-color 0.15s ease, color 0.15s ease;
+ width: 2rem;
+ }
+
+ .view-toggle-btn:hover {
+ background: var(--color-surface-neutral-hover);
+ color: var(--color-text-primary);
+ }
+
+ After editing, rebuild Tailwind:
+ cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend
+ ./bin/tailwindcss -i tailwind.input.css -o static/tailwind.css --minify
+
+ Run templ generate and go build to verify:
+ cd backend && templ generate ./... && go build ./...
+
+
+ cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && templ generate ./... && go build ./... && grep -c "view-toggle-btn" internal/web/ui/app.css && grep -c "view-toggle-btn" static/tailwind.css 2>&1
+
+
+ - TablosDashboard has a toggle button with class view-toggle-btn and the inline onclick JS
+ - #tablos-list has data-view="grid" as a static HTML attribute
+ - app.css has .view-toggle-btn CSS block
+ - static/tailwind.css contains "view-toggle-btn"
+ - `templ generate ./...` and `go build ./...` both exit 0
+
+
+
+
+ Task 2: Add automated tests for LIST-01, LIST-02, LIST-03
+
+ backend/internal/web/handlers_tablos_test.go
+
+
+ Add three new test functions to the end of
+ backend/internal/web/handlers_tablos_test.go. Each follows the existing
+ pattern (setupTestDB, preInsertUser, router, GET /, check body).
+
+ Use these unique email addresses (not used by existing tests):
+ - "progressbar@example.com" — LIST-01 test
+ - "viewtoggle@example.com" — LIST-02 test
+ - "statusbadge@example.com" — LIST-03 test
+
+ Test 1 — TestTablosDashboard_ProgressBar (LIST-01):
+ Insert a tablo for the user. GET /. Assert body contains "Progression:".
+ The progress bar label "Progression:" must be present in the rendered HTML.
+ Also assert body contains "project-card-progress-row".
+
+ Test 2 — TestTablosDashboard_ViewToggle (LIST-02):
+ GET /. Assert body contains "view-toggle-btn".
+ Assert body contains `data-view="grid"` (the initial state attribute on #tablos-list).
+
+ Test 3 — TestTablosDashboard_StatusBadge (LIST-03):
+ Insert a tablo. GET /. Assert body contains "Active" (the capitalized badge label
+ rendered from the "active" DB default). Also assert body contains "tablo-list-row"
+ (confirming the dual-element structure is rendered).
+
+ Comment each test with the requirement ID it covers (e.g., // LIST-01: progress bar).
+
+ Do not modify any existing tests. Append only.
+
+ After adding tests, run:
+ cd backend && go test ./internal/web/... -run "TestTablosDashboard" -v
+ Tests will PASS when connected to a test DB, or SKIP/fail-fast when no DB is
+ available. The important thing is that the file compiles and go build ./... exits 0.
+
+
+ cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && go build ./... && grep -c "TestTablosDashboard_ProgressBar\|TestTablosDashboard_ViewToggle\|TestTablosDashboard_StatusBadge" internal/web/handlers_tablos_test.go 2>&1
+
+
+ - handlers_tablos_test.go contains TestTablosDashboard_ProgressBar, TestTablosDashboard_ViewToggle, TestTablosDashboard_StatusBadge
+ - `go build ./...` exits 0 (file compiles)
+ - The three new test functions assert "Progression:", "view-toggle-btn" / data-view, and "Active" / "tablo-list-row" respectively
+
+
+
+
+
+
+## Trust Boundaries
+
+| Boundary | Description |
+|----------|-------------|
+| Browser JS → DOM | Toggle onclick manipulates data-view attribute; no server round-trip |
+| Template attribute | data-view="grid" is a static server-rendered attribute; no user-controlled value |
+
+## STRIDE Threat Register
+
+| Threat ID | Category | Component | Disposition | Mitigation Plan |
+|-----------|----------|-----------|-------------|-----------------|
+| T-19-03-01 | Tampering | Toggle onclick JS | accept | JS only toggles a CSS data-attribute on a client-side DOM node; no data is written to the server; cannot be used to bypass auth or modify data |
+| T-19-03-02 | Information Disclosure | List view rows | accept | Same data as card view; all tablos already visible in grid view; list view exposes no additional data |
+| T-19-03-03 | Spoofing | data-view attribute | accept | Client-side attribute only; server always renders the canonical data; no server state changes |
+
+
+
+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" -v
+grep -c "view-toggle-btn" internal/web/ui/app.css
+grep -c "view-toggle-btn" static/tailwind.css
+grep -c "view-toggle-btn" templates/tablos.templ
+grep -c "TestTablosDashboard_ProgressBar" internal/web/handlers_tablos_test.go
+grep -c "TestTablosDashboard_ViewToggle" internal/web/handlers_tablos_test.go
+grep -c "TestTablosDashboard_StatusBadge" internal/web/handlers_tablos_test.go
+```
+
+Expected: all greps return 1+; go build exits 0; tests pass or skip (not compile-error).
+
+
+
+- `grep -c "view-toggle-btn" backend/templates/tablos.templ` returns 1
+- `grep -c "data-view" backend/templates/tablos.templ` returns 1 (static attribute on #tablos-list)
+- `grep -c "view-toggle-btn" backend/static/tailwind.css` returns 1
+- `grep -c "TestTablosDashboard_ProgressBar" backend/internal/web/handlers_tablos_test.go` returns 1
+- `grep -c "TestTablosDashboard_ViewToggle" backend/internal/web/handlers_tablos_test.go` returns 1
+- `grep -c "TestTablosDashboard_StatusBadge" backend/internal/web/handlers_tablos_test.go` returns 1
+- `cd backend && go build ./...` exits 0
+- Browser: clicking the toggle button hides cards and shows list rows; clicking again reverses; page reload resets to grid
+
+
+