xtablo-source/.planning/phases/01-foundation/01-03-SUMMARY.md
2026-05-14 19:28:13 +02:00

11 KiB

phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
01-foundation 03 backend-foundation
go
chi
templ
htmx
pgx
slog
graceful-shutdown
foundation
requires provides affects
01-01: backend scaffold (go.mod, directory skeleton, .air.toml, justfile, compose.yaml)
01-02: ui package + RED gate tests (handlers_test.go, pool_test.go)
backend.db.NewPool: pgxpool wrapper for the rest of the project
backend.web.NewRouter: chi router with the locked middleware stack
backend.web.{HealthzHandler,IndexHandler,DemoTimeHandler}
backend.web.{RequestIDMiddleware,SlogLoggerMiddleware,NewSlogHandler,LoggerFromContext}
templates.{Layout,Index,TimeFragment}: server-rendered base layout + demo page + HTMX fragment
cmd/web: HTTP server binary with graceful shutdown
cmd/worker: Phase 1 worker skeleton (D-03)
Phase 2 (auth) will add SessionMiddleware to NewRouter and consume LoggerFromContext
Phase 6 (jobs) will replace cmd/worker in full
added patterns
github.com/go-chi/chi/v5 v5.2.5 (already pinned in 01-01; first import in 01-03)
github.com/jackc/pgx/v5/pgxpool (already pinned; first import)
github.com/google/uuid (already pinned; first import)
github.com/a-h/templ runtime (first import via generated templates)
RESEARCH Pattern 1: lazy pgxpool (no eager Ping)
RESEARCH Pattern 2: chi middleware order — RequestID → RealIP → Logger → Recoverer
RESEARCH Pattern 3: slog handler switch by ENV
RESEARCH Pattern 4: RequestID → context → slog
RESEARCH Pattern 5: graceful shutdown via signal.NotifyContext
RESEARCH Pattern 6: templ.Render(ctx, w) from chi handlers
RESEARCH Pattern 7: hx-get demo (button → fragment swap)
created modified
backend/internal/db/pool.go
backend/internal/web/slog.go
backend/internal/web/middleware.go
backend/internal/web/handlers.go
backend/internal/web/router.go
backend/templates/layout.templ
backend/templates/index.templ
backend/templates/fragments.templ
backend/cmd/web/main.go
backend/cmd/worker/main.go
backend/go.mod (go mod tidy populated require list — chi, templ, pgx, uuid)
backend/go.sum
backend/internal/web/handlers_test.go (removed //go:build red_gate)
backend/internal/db/pool_test.go (removed //go:build red_gate)
Used signal.NotifyContext (Go 1.21+) instead of manual signal.Notify + channel — same semantics, fewer lines, ctx propagates to handlers
Reused web.NewSlogHandler from cmd/worker — slog handler is a pure helper, no HTTP coupling, no need to duplicate
Did not register chi's middleware.RequestID — used our UUIDv4 RequestIDMiddleware instead because chi's emits base32, but UI-SPEC + tests expect UUIDv4
Did not use defer pool.Close() in cmd/web — called explicitly after Shutdown returns (RESEARCH Pitfall 4: defer ordering is unreliable on fatal-exit paths)
duration completed tasks_completed files_created files_modified web_binary_size_bytes worker_binary_size_bytes
single-wave executor run 2026-05-14 5 10 4 12902098 11689682

Phase 01-foundation Plan 03: Walking Skeleton GREEN slice

One-liner

Turns the RED tests from Plan 01-02 GREEN by wiring pgxpool, chi router with structured-logging + UUIDv4 RequestID middleware, three handlers (/healthz, /, /demo/time), three templ templates (layout, index, fragments) consuming the ui design-system, and two main entrypoints (cmd/web with full graceful shutdown, cmd/worker Phase 1 skeleton).

What Shipped

Surface Behavior
db.NewPool pgxpool builder, MaxConns=10/MinConns=1, lazy (no eager Ping)
web.NewRouter(pinger, staticDir) chi router — middleware stack: RequestIDMiddleware → RealIP → SlogLoggerMiddleware → Recoverer (verifies CONTEXT D-08); routes: GET /, GET /healthz, GET /demo/time, GET /static/*
web.HealthzHandler 200 + {"status":"ok","db":"ok"} when Ping passes inside 2s; 503 + {"status":"degraded","db":"down"} otherwise (D-20)
web.IndexHandler renders templates.Index() as text/html — root page consumes @ui.Card + @ui.Button per UI-SPEC
web.DemoTimeHandler renders templates.TimeFragment(now()) as an HTML <span> — accepts injected func() time.Time clock for tests
web.RequestIDMiddleware UUIDv4 per request, attached to ctx + X-Request-ID header
web.SlogLoggerMiddleware Structured per-request log (method, path, status, duration_ms, request_id); allowlist-only fields (T-01-09)
web.NewSlogHandler(env, w) JSONHandler when env=="production", TextHandler otherwise
templates.Layout(title) Base HTML shell — UI-SPEC §Base Layout Contract; /static/tailwind.css in <head>, /static/htmx.min.js deferred at body end (D-10: no CDN)
templates.Index Root page: H1, muted subtitle, @ui.Card containing the canonical HTMX demo CTA
templates.TimeFragment <span class="text-slate-900">{RFC3339 UTC}</span>
cmd/web/main.go Loads env, slog handler, pgxpool, chi router; http.Server with 15s/15s/60s timeouts; signal.NotifyContext; Shutdown(10s) then explicit pool.Close()
cmd/worker/main.go D-03 skeleton: 48 lines; logs "worker ready"; blocks on signal; closes pool; exits 0

Tests

All targeted tests are GREEN under default go test ./...:

ok  	backend/internal/db	0.225s   # TestPool_Connects skips cleanly when DATABASE_URL is unset
ok  	backend/internal/web	0.547s   # six handler tests
ok  	backend/internal/web/ui	0.652s   # ui package smoke tests

Per-file:

  • internal/web/handlers_test.go: TestHealthz_OK, TestHealthz_Down, TestIndex_RendersHxGet, TestDemoTime_Fragment, TestRequestID_HeaderSet, TestSlog_HandlerSwitch — all PASS
  • internal/db/pool_test.go: TestPool_Connects — SKIPS cleanly when DATABASE_URL is unset; runs against compose Postgres when set (verification deferred to Task 5 human checkpoint).

The //go:build red_gate tags placed by Plan 01-02 were removed in Task 1 as the first action, so the suite runs by default.

Build

go build ./...   # exits 0

Binary sizes:

  • cmd/web: 12.9 MB
  • cmd/worker: 11.7 MB

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 — Bug] templ generator double-imported github.com/a-h/templ

  • Found during: Task 2 (after first templ generate + go build)
  • Issue: I added import "github.com/a-h/templ" explicitly to templates/index.templ to make the templ.Attributes literal type explicit. The generator already emits an unconditional import "github.com/a-h/templ" in every *_templ.go file, producing templ redeclared in this block.
  • Fix: Removed the explicit import line from index.templ; relied on the generator's auto-import. templ.Attributes reference inside the templ source resolves fine through the auto-import.
  • Files modified: backend/templates/index.templ
  • Commit: included in feat(01-03): templ layout/index/fragments + handlers + chi router (3a12f8f)

No other deviations. The plan executed as written; go mod tidy did the expected work (Codex concern #1 retired); the RED gate tags were removed (Codex concern #3 retired).

Auto-approved Checkpoints

  • Auto-approved: Task 5 checkpoint:human-verify — full Walking Skeleton run (browser HTMX round-trip, graceful shutdown observation, worker boot, README walkthrough). Auto-approved under phase-wide auto-mode. The agent has already proven correctness via go test ./... GREEN for all targeted units; the browser interaction and live-reload checks are the parts that genuinely require a human and are deferred to the user's discretion. The orchestrator can resume the phase without pausing.

Threat Surface Scan

No new threat flags discovered. All threat-register entries (T-01-08 through T-01-13) are mitigated in code:

  • T-01-08 (path traversal at /static/*): http.FileServer(http.Dir(staticDir)) is used; http.Dir rejects .. traversal by default.
  • T-01-09 (log info-disclosure): SlogLoggerMiddleware allowlist — method/path/status/duration_ms/request_id only. Never Authorization, Cookie, or body.
  • T-01-10 (slow-client DoS): http.Server.ReadTimeout=15s, WriteTimeout=15s, IdleTimeout=60s in cmd/web/main.go.
  • T-01-11 (panic crash): chimw.Recoverer registered AFTER SlogLoggerMiddleware so panics carry request_id.
  • T-01-12 (DSN leak): cmd/web and cmd/worker log only err, never "dsn" on connect failure.
  • T-01-13 (XSS in /demo/time): templ auto-escapes the time literal; no templ.Raw used anywhere.

Verification Cross-Check (from <verification> block)

Check Result
go test ./... -count=1 exits 0 PASS
go build ./cmd/web ./cmd/worker succeeds PASS
grep -r 'middleware.Logger' backend/ outside of comments only docstring/comment mentions — no import or registration
grep -r 'unpkg.com|cdn\.' backend/internal backend/templates backend/cmd empty
grep -r 'class="bg-blue-' backend/templates/ empty (pages consume ui.Button)
//go:build red_gate removed from both handlers_test.go and pool_test.go

Known Stubs

None. Every interactive surface in scope (the demo button, the HTMX swap, /healthz, the worker boot signal) is fully wired against real infrastructure (pgxpool, templ-rendered HTML, chi router).

Self-Check

Files verified to exist:

  • FOUND: backend/internal/db/pool.go
  • FOUND: backend/internal/web/slog.go
  • FOUND: backend/internal/web/middleware.go
  • FOUND: backend/internal/web/handlers.go
  • FOUND: backend/internal/web/router.go
  • FOUND: backend/templates/layout.templ
  • FOUND: backend/templates/index.templ
  • FOUND: backend/templates/fragments.templ
  • FOUND: backend/cmd/web/main.go
  • FOUND: backend/cmd/worker/main.go

Commits verified:

  • FOUND: 36e9601 — feat(01-03): pgxpool wrapper, RequestID/slog middleware, slog handler switch
  • FOUND: 3a12f8f — feat(01-03): templ layout/index/fragments + handlers + chi router
  • FOUND: 08a2c3c — feat(01-03): cmd/web entrypoint with graceful shutdown
  • FOUND: aa1e1fd — feat(01-03): cmd/worker Phase 1 skeleton (D-03)

Self-Check: PASSED

Notes for Plan 04

Plan 04 wraps the foundation: README quickstart, .env.example sanity check, justfile recipe audit, and the Phase 1 verification gate (/gsd-verify-work). Items observed during this plan that Plan 04 should consider:

  • The pinned pressly/goose and sqlc-dev/sqlc runtime deps were dropped from go.mod's require list by go mod tidy because they have no consumer code yet — they are CLI-only tools installed via just bootstrap. This is expected per CONTEXT D-04/D-05; Phase 7 will re-introduce goose as a Go-library import if the deploy path needs embedded migrations.
  • cmd/web startup logs an addr attribute on the listening line so the dev/prod port is visible in structured logs immediately.
  • The middleware order is locked in source order (RequestIDMiddleware → RealIP → SlogLoggerMiddleware → Recoverer); Plan 04 README should call this out so Phase 2 doesn't accidentally re-order when adding session middleware.