---
phase: 04-tasks-kanban
plan: "02"
subsystem: backend/tasks
tags: [kanban, htmx, handlers, templ, tdd-green]
dependency_graph:
  requires: [04-tasks-kanban/04-01]
  provides: [kanban-board-ui, task-create, task-delete, task-handlers, task-routes]
  affects: [backend/internal/web, backend/templates, backend/cmd/web]
tech_stack:
  added: []
  patterns: [htmx-oob-swap, chi-route-ordering, templ-components, ownership-guard]
key_files:
  created:
    - backend/internal/web/handlers_tasks.go
    - backend/templates/tasks.templ
  modified:
    - backend/internal/web/router.go
    - backend/cmd/web/main.go
    - backend/templates/tablos.templ
    - backend/templates/layout.templ
    - backend/templates/tasks_forms.go
    - backend/internal/web/handlers_tablos.go
    - backend/internal/web/handlers_tasks_test.go
    - backend/internal/web/handlers_test.go
    - backend/internal/web/handlers_tablos_test.go
    - backend/internal/web/handlers_auth_test.go
    - backend/internal/web/csrf_test.go
decisions:
  - "TaskColumns/TaskColumnLabels moved to templates package to avoid web↔templates import cycle"
  - "TabloDetailPage updated to accept tasks []sqlc.Task; handlers_tablos.go fetches tasks via ListTasksByTablo before rendering"
  - "Non-HTMX validation error in TabloUpdateHandler also fetches tasks for kanban board rendering"
  - "AddTaskTrigger and TaskCard use raw CSS class string for soft-danger button (not ui.Button) per plan spec"
  - "Column labels match tasks_forms.go TaskColumnLabels; test scaffold updated from incorrect capitalization"
metrics:
  duration: ~20min
  completed: "2026-05-15"
  tasks: 3
  files: 12
---

# Phase 4 Plan 02: Kanban Board Vertical Slice (Wave 1) Summary

Vertical slice 1 of the kanban workflow: renders the kanban board on tablo detail page with 4 columns, implements task creation via inline column form with HTMX, and task deletion with inline confirmation. Users can now open a tablo, see 4 columns (To do, In progress, In review, Done), create tasks, and delete them.

## Tasks Completed

| Task | Name | Commit | Files |
|------|------|--------|-------|
| 1 | handlers_tasks.go + router + main.go | 181ae79 | backend/internal/web/handlers_tasks.go, router.go, main.go, 5 test files |
| 2 | tasks.templ + tablos.templ + layout.templ | 889164b | backend/templates/tasks.templ, tablos.templ, layout.templ, tasks_forms.go, handlers_tablos.go |
| 3 | Activate task integration tests | 92ebb5f | backend/internal/web/handlers_tasks_test.go |

## Verification Results

- `go build ./...` exits 0
- `go test ./...` exits 0 (all tests pass or skip correctly)
- `go test ./internal/web/ -run TestTask -v`: 9 SKIP total — 5 skip due to no TEST_DATABASE_URL (activated, awaiting DB), 4 skip with "handlers_tasks not yet implemented" (Plan 03)
- `grep -c 'KanbanBoard' templates/tasks.templ` returns 2
- `grep -c 'TaskCreateHandler' internal/web/router.go` returns 1
- `grep -c 'sortable.min.js' templates/layout.templ` returns 1

## Decisions Made

1. **TaskColumns/TaskColumnLabels moved to templates package** — `handlers_tasks.go` imports `templates`, and `tasks.templ` needs the column definitions. Putting them in `web` package would create a cycle (`templates` → `web` → ... → `templates`). Moving to `templates/tasks_forms.go` breaks the cycle cleanly.

2. **TabloDetailPage signature updated** — Added `tasks []sqlc.Task` parameter. All callers updated: `TabloDetailHandler` fetches tasks via `ListTasksByTablo`; `TabloUpdateHandler` non-HTMX error path also fetches tasks for correct rendering.

3. **Task column header strings corrected** — The Plan 01 test scaffold used `"Todo"`, `"In Progress"`, `"In Review"` which didn't match the plan spec `"To do"`, `"In progress"`, `"In review"`. Updated test to use correct labels from `TaskColumnLabels`.

4. **Integration tests skip on missing TEST_DATABASE_URL** — Tests are activated (no t.Skip), but the test helper `setupTestDB` skips when no database is configured. This is the correct behavior per the existing test pattern.

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 2 - Import cycle prevention] TaskColumns/TaskColumnLabels moved from web to templates package**
- **Found during:** Task 2 (templ generation, go build cycle check)
- **Issue:** `tasks.templ` needs `web.TaskColumns` and `web.TaskColumnLabels`. But `templates` package imports `web/ui` (not `web`), and `web` imports `templates` — adding `web` as a direct import to `templates` would create `web → templates → web` cycle.
- **Fix:** Moved `TaskColumns` and `TaskColumnLabels` to `backend/templates/tasks_forms.go`. Handlers reference `templates.TaskColumns` via the already-existing `templates` import. No structural change — just a different home for the constants.
- **Files modified:** backend/templates/tasks_forms.go, backend/internal/web/handlers_tasks.go
- **Commit:** 181ae79

**2. [Rule 1 - Bug] Column header test strings corrected**
- **Found during:** Task 3 (activating tests)
- **Issue:** Plan 01 scaffold used `"Todo"`, `"In Progress"`, `"In Review"` but TaskColumnLabels defines `"To do"`, `"In progress"`, `"In review"`.
- **Fix:** Updated `TestTasksKanbanRenders` to use the correct label strings.
- **Files modified:** backend/internal/web/handlers_tasks_test.go
- **Commit:** 92ebb5f

**3. [Rule 3 - Router update] All NewRouter call sites updated**
- **Found during:** Task 1 (adding TasksDeps param to NewRouter)
- **Issue:** 5 test helper functions across 4 test files called `NewRouter` without `TasksDeps`.
- **Fix:** Updated all callers: `handlers_test.go`, `handlers_tablos_test.go`, `handlers_auth_test.go`, `csrf_test.go`.
- **Files modified:** 4 test files
- **Commit:** 181ae79

## Known Stubs

- `TaskEditHandler` returns 501 — Plan 03 implements task editing
- `TaskUpdateHandler` returns 501 — Plan 03 implements task update
- `TaskReorderHandler` returns 501 — Plan 03 implements drag-and-drop reorder

These stubs are intentional per the plan spec; routes are registered so route resolution tests work.

## Threat Flags

None — all threat mitigations from the plan's threat register are implemented:
- T-04-03: loadOwnedTabloForTask uses `GetTaskByID(WHERE id=$1 AND tablo_id=$2)` with ownership-verified tablo_id
- T-04-04: title validated non-empty, max 255 chars
- T-04-05: status validated against validTaskStatuses map before DB insert
- T-04-07: TabloDetailPage only renders after loadOwnedTablo passes ownership check

## Self-Check: PASSED

- backend/internal/web/handlers_tasks.go: FOUND
- backend/templates/tasks.templ: FOUND
- backend/internal/web/router.go (TaskCreateHandler): FOUND
- backend/templates/layout.templ (sortable.min.js): FOUND
- backend/templates/tablos.templ (tasks param): FOUND
- Commits 181ae79, 889164b, 92ebb5f: FOUND
