---
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"
---

<objective>
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.
</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>
@/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
</context>

<interfaces>
<!-- Key contracts the executor needs. Extracted from codebase. -->

From backend/templates/tablos.templ (after Plan 02):
```go
// TablosDashboard current .overview-section-heading block:
//   <div class="overview-section-heading">
//     <h3>Your Tablos</h3>
//     @ui.Button(...)  // "New tablo" button
//   </div>
//   <div id="tablos-list" class="project-grid">...</div>
//
// 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
<!-- Inline onclick JS: no Alpine.js, no localStorage, resets to grid on reload -->
<button
  type="button"
  class="view-toggle-btn"
  onclick="var el=document.getElementById('tablos-list');
           el.dataset.view = el.dataset.view==='list' ? 'grid' : 'list'"
  aria-label="Toggle list view"
>
  <!-- list-view SVG icon (4 horizontal lines) -->
</button>
```

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 <button> with inline SVG instead.
```

From backend/internal/web/handlers_tablos_test.go (existing test patterns):
```go
// Existing test pattern — all tests follow this shape:
func TestTablosDashboard_X(t *testing.T) {
    pool, cleanup := setupTestDB(t)
    defer cleanup()
    ctx := context.Background()
    q := sqlc.New(pool)
    store := auth.NewStore(q)
    router := newTabloTestRouter(q, store)
    user := preInsertUser(t, ctx, q, "email@example.com", "password")
    // optional: insert tablos via q.InsertTablo(...)
    cookieVal, _, err := store.Create(ctx, user.ID)
    // ... set cookie, make GET / request, check body contains string ...
}
// Tests skip automatically when no test DB is available (setupTestDB fails fast).
// New tests MUST use unique email addresses not used by existing tests.
```

From backend/internal/web/ui — check if "list" icon is available:
```go
// Verify by checking backend/internal/web/ui/icons.go or the icon render function.
// If no "list" icon: use a plain <button type="button" class="view-toggle-btn"> with
// an inline SVG (4 horizontal lines, 16×16 viewBox). Do not add new icons to the ui
// package in this plan — use an inline SVG in tablos.templ instead.
```

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
```
</interfaces>

<tasks>

<task type="auto">
  <name>Task 1: Add view toggle button to TablosDashboard + data-view attribute on container</name>
  <files>
    backend/templates/tablos.templ
    backend/internal/web/ui/app.css
    backend/static/tailwind.css
  </files>
  <action>
    In backend/templates/tablos.templ, modify the TablosDashboard component:

    1. Add data-view="grid" to the #tablos-list div:
       Change: &lt;div id="tablos-list" class="project-grid"&gt;
       To:     &lt;div id="tablos-list" class="project-grid" data-view="grid"&gt;

    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:
         <h3>Your Tablos</h3>
         @ui.Button(...)
       To:
         <h3>Your Tablos</h3>
         <div class="flex items-center gap-2">
           <button type="button" class="view-toggle-btn"
             onclick="var el=document.getElementById('tablos-list');el.dataset.view=el.dataset.view==='list'?'grid':'list'"
             aria-label="Toggle list view">
             <!-- inline SVG: 4 horizontal lines (list icon), 16×16, stroke="currentColor" -->
             <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
                  fill="none" stroke="currentColor" stroke-width="2"
                  stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
               <line x1="8" x2="21" y1="6" y2="6"></line>
               <line x1="8" x2="21" y1="12" y2="12"></line>
               <line x1="8" x2="21" y1="18" y2="18"></line>
               <line x1="3" x2="3.01" y1="6" y2="6"></line>
               <line x1="3" x2="3.01" y1="12" y2="12"></line>
               <line x1="3" x2="3.01" y1="18" y2="18"></line>
             </svg>
           </button>
           @ui.Button(...)  // existing "New tablo" button, unchanged
         </div>

    Note: Use a plain <button> 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 ./...
  </action>
  <verify>
    <automated>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</automated>
  </verify>
  <done>
    - 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
  </done>
</task>

<task type="auto">
  <name>Task 2: Add automated tests for LIST-01, LIST-02, LIST-03</name>
  <files>
    backend/internal/web/handlers_tablos_test.go
  </files>
  <action>
    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.
  </action>
  <verify>
    <automated>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</automated>
  </verify>
  <done>
    - 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
  </done>
</task>

</tasks>

<threat_model>
## 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 |
</threat_model>

<verification>
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).
</verification>

<success_criteria>
- `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
</success_criteria>

<output>
After completion, create `/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/19-tablo-list-revamp/19-03-SUMMARY.md` following the summary template.
</output>
