---
phase: 03-tablos-crud
plan: 02
subsystem: web-handlers + templates
tags: [go, htmx, templ, chi, csrf, tablo-crud]

# Dependency graph
requires:
  - phase: 03-tablos-crud
    plan: 01
    provides: tablos table migration, sqlc queries, TablosDeps stub, RED test scaffold, button CSS variants
provides:
  - TablosDashboard template (full page): heading, New-tablo button, create-form slot, card list
  - TablosEmptyState template: "No tablos yet" + CTA with exact UI-SPEC copy
  - TabloCard template: title, optional description/color dot, View link, .tablo-delete-zone wrapper
  - TabloCreateFormFragment template: inline HTMX form with CSRF, field errors, Cancel link
  - TabloCardWithOOBFormClear template: card + top-level OOB div clearing #create-form-slot
  - TabloCreateForm + TabloCreateErrors form types (tablos_forms.go)
  - TablosListHandler — GET / lists user tablos newest-first
  - TablosNewHandler — GET /tablos/new returns create form fragment
  - TablosCreateHandler — POST /tablos validates, inserts, dual-target HTMX swap or 303 redirect
  - router.go: GET / → TablosListHandler, GET /tablos/new, POST /tablos registered
  - layout.templ footer updated: "Phase 3 · Tablos"
  - TestTabloList, TestTabloList_Empty, TestTabloCreate, TestTabloCreate_Validation: GREEN (4/10)
affects: [03-03]

# Tech tracking
tech-stack:
  added: []
  patterns:
    - Dual-target HTMX swap: HX-Retarget + hx-swap-oob sibling in single response (Pattern 4)
    - OOB div as top-level sibling in templ component (Pitfall 5 handled)
    - r.PostFormValue only — gorilla/csrf body consumption guard (Pitfall 2)
    - pgtype.Text{String: s, Valid: s != ""} for nullable insert params
    - renderTabloCreateError helper mirrors renderSignupError pattern

key-files:
  created:
    - backend/templates/tablos.templ
    - backend/templates/tablos_forms.go
  modified:
    - backend/internal/web/handlers_tablos.go
    - backend/internal/web/handlers.go
    - backend/internal/web/router.go
    - backend/templates/layout.templ
    - backend/templates/index.templ

key-decisions:
  - "index_templ.go deleted manually — templ generates broken import when .templ has no components"
  - "IndexHandler removed from handlers.go; auth/csrf imports removed (now unused)"
  - "index.templ reduced to bare package declaration rather than deleted — keeps file discoverable"
  - "Non-HTMX validation error re-fetches tablos and renders full dashboard (no form state threading)"

patterns-established:
  - "Pattern: TabloCardWithOOBFormClear emits OOB div as top-level sibling (Pitfall 5)"
  - "Pattern: dual-target HTMX create swap via HX-Retarget + HX-Reswap headers"
  - "Pattern: nullable pgtype.Text insert via Valid: s != '' guard"

requirements-completed:
  - TABLO-01
  - TABLO-02
  - TABLO-06

# Metrics
duration: ~4min
completed: 2026-05-15
---

# Phase 03 Plan 02: Tablos Dashboard + Create Slice Summary

**tablos.templ dashboard/card/form components + TablosListHandler/NewHandler/CreateHandler wiring 4/10 integration tests green; HTMX dual-target create and non-HTMX 303 fallback both verified**

## Performance

- **Duration:** ~4 min
- **Started:** 2026-05-14T22:16:47Z
- **Completed:** 2026-05-15T06:00:00Z (checkpoint approved)
- **Tasks:** 3 of 3 (all complete including human-verify)
- **Files modified:** 7

## Accomplishments

- `tablos.templ` created with 5 components: TablosDashboard, TablosEmptyState, TabloCard, TabloCreateFormFragment, TabloCardWithOOBFormClear
- `tablos_forms.go` with TabloCreateForm + TabloCreateErrors types (mirrors auth_forms.go)
- `layout.templ` footer updated from "Phase 2 · Authentication" to "Phase 3 · Tablos"
- `handlers_tablos.go` filled from stub to full implementation (List/New/Create handlers)
- `router.go` protected group updated: GET / → TablosListHandler, + GET /tablos/new, POST /tablos
- `handlers.go` IndexHandler removed; `index.templ` deleted; `index_templ.go` deleted
- TestTabloList, TestTabloList_Empty, TestTabloCreate (HTMX + non-HTMX), TestTabloCreate_Validation all PASS
- Phase 1/2 tests (TestSignup/TestLogin/TestLogout/TestCSRF) still pass — no regression
- Task 3 human-verify checkpoint approved: dashboard, HTMX create flow, validation, non-JS fallback, and CSRF all confirmed working in browser

## Task Commits

1. **Task 1: tablos templates + layout footer** - `43ddf25` (feat)
2. **Task 2: tablo handlers + router wiring** - `5db9215` (feat)
3. **Task 3: human-verify checkpoint approved** — fix: deleted retired `index.templ` - `c08da7f` (fix)

## Tests: 4 of 10 Green, 6 Remain RED for Plan 03

| Test | Status |
|------|--------|
| TestTabloList | GREEN |
| TestTabloList_Empty | GREEN |
| TestTabloCreate/HTMX_create | GREEN |
| TestTabloCreate/non-HTMX_create | GREEN |
| TestTabloCreate_Validation | GREEN |
| TestTabloDetail_Owner | RED (Plan 03) |
| TestTabloDetail_NonOwner | RED (Plan 03) |
| TestTabloDetail_InvalidID | RED (Plan 03) |
| TestTabloUpdate | RED (Plan 03) |
| TestTabloDeleteConfirm | RED (Plan 03) |
| TestTabloDelete/HTMX_delete | RED (Plan 03) |
| TestTabloDelete/non-HTMX_delete | RED (Plan 03) |

## Handler Contracts Established

**TablosDeps** (unchanged from Plan 01):
```go
type TablosDeps struct {
    Queries *sqlc.Queries
}
```

**Handler signatures:**
- `TablosListHandler(deps TablosDeps) http.HandlerFunc` — GET /
- `TablosNewHandler(deps TablosDeps) http.HandlerFunc` — GET /tablos/new
- `TablosCreateHandler(deps TablosDeps) http.HandlerFunc` — POST /tablos

**NewRouter signature** (unchanged from Plan 01):
```go
func NewRouter(pinger Pinger, staticDir string, deps AuthDeps, tabloDeps TablosDeps, csrfKey []byte, env string, trustedOrigins ...string) http.Handler
```

## Template Contracts for Plan 03

- `TabloCard` wraps Delete button in `<div class="tablo-delete-zone">` — Plan 03 wires /delete-confirm to swap outerHTML
- `TabloCard` renders "View" link to `/tablos/{id}` — Plan 03 implements that route
- All 5 template components in `tablos.templ` are callable by Plan 03

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 1 - Bug] Deleted stale index_templ.go after emptying index.templ**
- **Found during:** Task 2 (after reducing index.templ to bare package declaration)
- **Issue:** `templ generate` regenerated index_templ.go with `import "github.com/a-h/templ"` even though the file has no templ components, causing `go build` to fail with "imported and not used".
- **Fix:** Deleted `backend/templates/index_templ.go` manually after `just generate`; build passes.
- **Files modified:** backend/templates/index_templ.go (deleted)
- **Committed in:** 5db9215 (Task 2)

## Known Stubs

- `TabloCard` Delete button fires `hx-get="/tablos/{id}/delete-confirm"` — the `/delete-confirm` route does not exist yet (Plan 03 implements it). The button is visible on the dashboard but clicking it returns a 404 until Plan 03.
- `TabloCard` "View" link points to `/tablos/{id}` — route not yet registered (Plan 03). Navigating returns chi's default 404 until Plan 03.

These stubs are intentional per plan design — Plan 02 establishes the zone and link shapes that Plan 03 will wire.

## Checkpoint Outcome

Task 3 (human-verify) approved by user. Manual browser verification confirmed:
1. Dashboard "Your Tablos" heading + tablo cards or empty state — PASS
2. "New tablo" button HTMX fetch of inline form — PASS
3. HTMX create: form clears, card prepends, no full reload — PASS
4. Validation: empty title shows "Title is required." inline — PASS
5. Non-JS POST → 303 redirect to /, tablo in list — PASS
6. CSRF token present in form `_csrf` hidden field — PASS

Additional fix applied during checkpoint: `index.templ` was deleted (reduced to bare package was causing `templ generate` to regenerate a stale `index_templ.go` with an unused import). Deletion resolves the issue cleanly.

## Next Phase Readiness

- Plan 03 can implement TabloDetail, TabloUpdate, TabloDeleteConfirm, TabloDelete, and TabloDeleteCancel handlers and routes.
- TablosDeps available, NewRouter signature stable.
- `.tablo-delete-zone` class on TabloCard wired for Plan 03's delete-confirm OOB swap.

---
*Phase: 03-tablos-crud*
*Completed: 2026-05-15*
