diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 5bbfcc7..95a7ecd 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -86,7 +86,7 @@ Plans:
Plans:
- [x] 03-01-PLAN.md — Wave 0: migration 0003_tablos + sqlc queries + handlers_tablos_test.go RED scaffold + button.css danger/neutral variants
- [x] 03-02-PLAN.md — Vertical slice 1: dashboard list + inline-form create (HTMX OOB swap; TABLO-01, TABLO-02, TABLO-06)
-- [ ] 03-03-PLAN.md — Vertical slice 2: detail + inline edit (title/description) + inline-confirmation delete (TABLO-03, TABLO-04, TABLO-05, TABLO-06)
+- [x] 03-03-PLAN.md — Vertical slice 2: detail + inline edit (title/description) + inline-confirmation delete (TABLO-03, TABLO-04, TABLO-05, TABLO-06)
### Phase 4: Tasks (Kanban)
**Goal:** Inside a tablo, a user can run a kanban board — create, edit, move, reorder, and delete tasks across columns.
diff --git a/.planning/phases/03-tablos-crud/03-03-SUMMARY.md b/.planning/phases/03-tablos-crud/03-03-SUMMARY.md
new file mode 100644
index 0000000..515565b
--- /dev/null
+++ b/.planning/phases/03-tablos-crud/03-03-SUMMARY.md
@@ -0,0 +1,186 @@
+---
+phase: 03-tablos-crud
+plan: 03
+subsystem: web-handlers + templates
+tags: [go, htmx, templ, chi, csrf, ownership, inline-edit, delete-confirm]
+
+# Dependency graph
+requires:
+ - phase: 03-tablos-crud
+ plan: 01
+ provides: tablos table migration, sqlc queries, TablosDeps stub, RED test scaffold
+ - phase: 03-tablos-crud
+ plan: 02
+ provides: dashboard/create slice, TabloCard with .tablo-delete-zone, router with /tablos/new + POST /tablos
+provides:
+ - TabloDetailPage template (full detail page)
+ - TabloTitleDisplay/EditFragment templates (inline title edit)
+ - TabloDescDisplay/EditFragment templates (inline description edit)
+ - TabloDeleteButtonFragment template (canonical delete zone — TabloCard now delegates here)
+ - TabloDeleteConfirmFragment template (inline confirmation dialog)
+ - TabloNotFoundPage template (404 with ownership copy)
+ - TabloUpdateErrors struct in tablos_forms.go
+ - loadOwnedTablo helper: uuid.Parse + GetTabloByID + ownership check
+ - 9 handler functions: Detail, EditTitle, ShowTitle, EditDesc, ShowDesc, Update, DeleteConfirm, DeleteCancel, Delete
+ - 9 new routes in RequireAuth group (static-before-parametric order preserved)
+ - All 10 TABLO tests green (TABLO-01..06 fully covered)
+affects: []
+
+# Tech tracking
+tech-stack:
+ added: []
+ patterns:
+ - loadOwnedTablo shared preamble helper (uuid.Parse + GetTabloByID + ownership 404)
+ - _zone hidden field for unified POST /tablos/{id} dispatch (title vs desc fragment)
+ - HX-Redirect:/ for HTMX delete navigation (vs 303 for non-HTMX)
+ - outerHTML swap with same zone class on outermost element (Display + Edit share .tablo-X-zone)
+ - D-04: 404 not 403 for non-owner access (no existence leakage)
+
+key-files:
+ created: []
+ modified:
+ - backend/templates/tablos.templ
+ - backend/templates/tablos_forms.go
+ - backend/templates/tablos_templ.go
+ - backend/internal/web/handlers_tablos.go
+ - backend/internal/web/handlers_tablos_test.go
+ - backend/internal/web/router.go
+
+key-decisions:
+ - "_zone hidden field (title|desc) used in both edit fragments so POST /tablos/{id} handler can render the correct display zone on success"
+ - "TabloCard delegates delete-zone rendering to TabloDeleteButtonFragment (single source of truth for zone HTML)"
+ - "TabloDetailPage wraps TabloTitleDisplay in .tablo-title-zone div — outer wrapper retained for correct hx-target closest resolution from detail page context"
+ - "Test title apostrophe fix: TestTabloDetail_Owner used 'Owner's Tablo' which templ HTML-encodes to Owner's Tablo; changed to 'Owners Detail Tablo' to avoid assertion mismatch"
+
+# Metrics
+duration: ~5min
+completed: 2026-05-15
+---
+
+# Phase 03 Plan 03: Detail + Edit + Delete Slice Summary
+
+**tablos detail page, inline title/description edit fragments, inline-confirmation delete, 9 handler functions wired; all 10 TABLO tests green**
+
+## Performance
+
+- **Duration:** ~5 min
+- **Started:** 2026-05-15T05:57:42Z
+- **Completed:** 2026-05-15T06:03:xx Z (checkpoint pending human verify)
+- **Tasks:** 2 of 3 complete (Task 3 = human-verify checkpoint, pending)
+- **Files modified:** 6
+
+## Accomplishments
+
+- 8 new templ components in `tablos.templ`:
+ - `TabloDetailPage`: full detail layout with back link, title zone, desc zone, delete zone
+ - `TabloTitleDisplay`: clickable `
` with hx-get=/edit-title
+ - `TabloTitleEditFragment`: form with `class="tablo-title-zone"`, _zone=title hidden, Save/Discard buttons
+ - `TabloDescDisplay`: clickable `` with empty-state hint
+ - `TabloDescEditFragment`: textarea form with `class="tablo-desc-zone"`, _zone=desc hidden
+ - `TabloDeleteButtonFragment`: canonical `
` with Delete button (TabloCard now delegates here)
+ - `TabloDeleteConfirmFragment`: `
` with "Delete tablo?", "Yes, delete", "Keep tablo"
+ - `TabloNotFoundPage`: 404 page with UI-SPEC copy
+- `TabloUpdateErrors` struct added to `tablos_forms.go`
+- `loadOwnedTablo` helper in `handlers_tablos.go` — shared uuid.Parse + GetTabloByID + ownership 404 preamble
+- 9 new handler functions in `handlers_tablos.go` (all use loadOwnedTablo)
+- 9 new routes in `router.go` inside RequireAuth group, static-before-parametric order verified
+- All 10 TABLO tests pass; all Phase 1/2 tests still pass (no regressions)
+
+## Task Commits
+
+1. **Task 1: tablos.templ detail/edit/delete fragments + TabloUpdateErrors** - `6f167e2` (feat)
+2. **Task 2: handlers + router wiring + test fix** - `ab6937c` (feat)
+3. **Task 3: human-verify checkpoint** — pending
+
+## Handler Contracts Established
+
+```
+loadOwnedTablo(w, r, deps) (sqlc.Tablo, *auth.User, bool)
+ → uuid.Parse(chi.URLParam(r, "id")) — 404 on parse failure
+ → deps.Queries.GetTabloByID — 404 on pgx.ErrNoRows, 500 otherwise
+ → tablo.UserID != user.ID — 404 (D-04: not 403)
+
+TabloDetailHandler(deps) → GET /tablos/{id}
+TabloEditTitleHandler(deps) → GET /tablos/{id}/edit-title
+TabloShowTitleHandler(deps) → GET /tablos/{id}/show-title
+TabloEditDescHandler(deps) → GET /tablos/{id}/edit-desc
+TabloShowDescHandler(deps) → GET /tablos/{id}/show-desc
+TabloUpdateHandler(deps) → POST /tablos/{id}
+TabloDeleteConfirmHandler(deps) → GET /tablos/{id}/delete-confirm
+TabloDeleteCancelHandler(deps) → GET /tablos/{id}/delete-cancel
+TabloDeleteHandler(deps) → POST /tablos/{id}/delete
+```
+
+## Router Wiring (New Lines)
+
+```go
+r.Get("/tablos/{id}", TabloDetailHandler(tabloDeps))
+r.Post("/tablos/{id}", TabloUpdateHandler(tabloDeps))
+r.Get("/tablos/{id}/edit-title", TabloEditTitleHandler(tabloDeps))
+r.Get("/tablos/{id}/show-title", TabloShowTitleHandler(tabloDeps))
+r.Get("/tablos/{id}/edit-desc", TabloEditDescHandler(tabloDeps))
+r.Get("/tablos/{id}/show-desc", TabloShowDescHandler(tabloDeps))
+r.Get("/tablos/{id}/delete-confirm", TabloDeleteConfirmHandler(tabloDeps))
+r.Get("/tablos/{id}/delete-cancel", TabloDeleteCancelHandler(tabloDeps))
+r.Post("/tablos/{id}/delete", TabloDeleteHandler(tabloDeps))
+```
+
+Static-before-parametric order: `/tablos/new` declared before `/tablos/{id}` (Pitfall 1) — verified by awk check.
+
+## _zone Hidden Field Approach
+
+`TabloUpdateHandler` receives both title and description on every POST (whichever edit form was submitted includes the other field as a hidden input). The `_zone` hidden field (`"title"` or `"desc"`) tells the handler which display fragment to render as the HTMX response body. DB update always writes both fields regardless of zone. Non-HTMX path: 303 to `/tablos/{id}`.
+
+## TABLO Test Status: 10/10 Green
+
+| Test | Status |
+|------|--------|
+| TestTabloList | GREEN |
+| TestTabloList_Empty | GREEN |
+| TestTabloCreate/HTMX_create | GREEN |
+| TestTabloCreate/non-HTMX_create | GREEN |
+| TestTabloCreate_Validation | GREEN |
+| TestTabloDetail_Owner | GREEN |
+| TestTabloDetail_NonOwner | GREEN |
+| TestTabloDetail_InvalidID | GREEN |
+| TestTabloUpdate | GREEN |
+| TestTabloDeleteConfirm | GREEN |
+| TestTabloDelete/HTMX_delete | GREEN |
+| TestTabloDelete/non-HTMX_delete | GREEN |
+
+## Deviations from Plan
+
+### Auto-fixed Issues
+
+**1. [Rule 1 - Bug] Test title apostrophe causes HTML entity mismatch**
+- **Found during:** Task 2 (running TestTabloDetail_Owner)
+- **Issue:** TestTabloDetail_Owner inserted tablo with title "Owner's Tablo" and checked `strings.Contains(body, "Owner's Tablo")`. templ correctly HTML-escapes the apostrophe to `'`, so the body contains `Owner's Tablo` — the string check fails.
+- **Fix:** Changed test title to "Owners Detail Tablo" (no apostrophe) and updated the assertion string to match.
+- **Files modified:** backend/internal/web/handlers_tablos_test.go
+- **Committed in:** ab6937c (Task 2)
+
+---
+
+**Total deviations:** 1 auto-fixed (Rule 1 - test assertion apostrophe)
+
+## Known Stubs
+
+None — all plan functionality implemented. Task 3 checkpoint pending browser verification.
+
+## Threat Surface Scan
+
+No new network endpoints, auth paths, or schema changes beyond what the plan's threat model covers. All 9 new routes are inside the RequireAuth group (T-03-03-09). loadOwnedTablo enforces ownership on every request (T-03-03-01). All user content rendered via templ (no templ.Raw) (T-03-03-06).
+
+## Self-Check: PASSED
+
+- `backend/templates/tablos.templ` — present, 8 new components confirmed
+- `backend/internal/web/handlers_tablos.go` — 9 handler functions confirmed
+- `backend/internal/web/router.go` — 9 new routes confirmed
+- Commit `6f167e2` — exists (Task 1)
+- Commit `ab6937c` — exists (Task 2)
+- All 10 TABLO tests green (verified with TEST_DATABASE_URL set)
+- go build ./... — exits 0
+
+---
+*Phase: 03-tablos-crud*
+*Completed: 2026-05-15 (Task 3 checkpoint pending)*