diff --git a/.planning/phases/01-foundation/01-CONTEXT.md b/.planning/phases/01-foundation/01-CONTEXT.md new file mode 100644 index 0000000..ba497e8 --- /dev/null +++ b/.planning/phases/01-foundation/01-CONTEXT.md @@ -0,0 +1,152 @@ +# Phase 1: Foundation - Context + +**Gathered:** 2026-05-14 +**Status:** Ready for planning + + +## Phase Boundary + +A fresh `backend/` Go package boots a web server, renders an HTMX-driven base layout styled with Tailwind, and connects to a local Postgres with migrations applied. A new dev can clone the repo, run `compose up -d` + `just dev`, and see the page within ~5 minutes following `backend/README.md`. + +Delivers FOUND-01..05. **Not** in scope: auth, tablos, tasks, files, worker job processing (worker binary scaffold only — no real jobs yet; jobs land in Phase 6), deployment (Phase 7). + + + + +## Implementation Decisions + +### Directory Layout +- **D-01:** Two-binary layout with shared `internal/`. Final shape: + ``` + backend/ + cmd/ + web/main.go + worker/main.go + internal/ + db/ (sqlc-generated queries + pgx pool wiring) + web/ (chi router, handlers, middleware) + session/ (placeholder package — populated in Phase 2) + tablos/ (placeholder — Phase 3) + tasks/ (placeholder — Phase 4) + files/ (placeholder — Phase 5) + migrations/ (goose .sql files) + templates/ (.templ files) + static/ (tailwind.css output, htmx.min.js) + compose.yaml + justfile + .env.example + README.md + ``` +- **D-02:** Phase 1 creates the directory skeleton for all `internal/` packages (empty `doc.go` is fine) so later phases drop files in without restructuring. +- **D-03:** `cmd/worker` in Phase 1 is a minimal binary that boots, connects to Postgres, logs "worker ready", and exits cleanly on signal. Real job runtime is Phase 6. + +### Migrations +- **D-04:** Use **goose** (`pressly/goose`). Reasons: embeddable as a library (can be called from `cmd/web` startup or a small subcommand so we ship migrations inside the Docker image in Phase 7 without a second binary), supports Go-based migrations if ever needed, one `.sql` file per migration with `-- +goose Up/Down` annotations (sqlc reads the same files). +- **D-05:** `just migrate up` / `just migrate down` / `just migrate status` wired via the goose CLI for local dev. Production migration strategy (embed vs CLI) decided in Phase 7. +- **D-06:** Phase 1 includes one trivial bootstrap migration (e.g., `0001_init.sql` creating an empty `schema_migrations` baseline or a no-op) so the migration pipeline is exercised end-to-end. + +### Templating + Router +- **D-07:** **templ** (`a-h/templ`) for HTML. Type-safe, compiled, plays well with HTMX partials (each fragment is a typed func returning `templ.Component`). +- **D-08:** **chi** (`go-chi/chi/v5`) as the HTTP router. Middleware stack: `RequestID → RealIP → Logger (structured) → Recoverer → GracefulShutdown wiring`. +- **D-09:** `templ generate` runs via `just generate` (alongside `sqlc generate`). Dev loop uses `templ generate --watch` or air's reload hook — pick during planning, both acceptable. +- **D-10:** Base layout template renders a Tailwind-styled page with HTMX loaded from `/static/htmx.min.js` (vendored, not CDN). Include one working `hx-get` example to satisfy success criterion 3. + +### Local Dev Stack +- **D-11:** **podman compose** for local Postgres (matches the developer's machine setup; `compose.yaml` at `backend/compose.yaml`). Document `podman compose` commands in the justfile; if a contributor uses docker, the same `compose.yaml` works — call this out in the README. +- **D-12:** **Standalone Tailwind CLI binary** (no Node/pnpm in `backend/`). Binary is downloaded by a `just bootstrap` recipe into `./bin/tailwindcss` (gitignored) — version pinned in the justfile. Keeps the "no JS toolchain" thesis intact. +- **D-13:** **air** (`cosmtrek/air`) for Go live-reload (`just dev`). Watches `.go` + `.templ` files; triggers `templ generate` and rebuild. +- **D-14:** Tailwind in watch mode runs as a separate process (`just styles`) or via air's `pre_cmd`. Planner decides which is cleaner. + +### Configuration & Operational Basics +- **D-15:** Env-driven config via `.env` (loaded from `.env` at dev startup; production injects real env vars). Required keys at minimum: `DATABASE_URL`, `PORT`, `ENV`. Provide `.env.example` in the repo. +- **D-16:** Postgres driver: **pgx/v5** with `pgxpool` for the connection pool. sqlc configured to emit pgx-compatible code (`sqlc.yaml` engine: postgresql, sql_package: pgx/v5). +- **D-17:** Structured logging: `log/slog` (std lib, Go 1.21+) with JSON handler in prod and text handler in dev, switched by `ENV`. +- **D-18:** Request ID middleware attaches a UUID per request and threads it into the slog logger via `context.Context`. +- **D-19:** Graceful shutdown: `cmd/web` traps SIGINT/SIGTERM, calls `http.Server.Shutdown` with a configurable timeout (default 10s), then closes the pgx pool. +- **D-20:** `/healthz` returns 200 with `{"status":"ok","db":"ok"}` only when `db.Ping` succeeds; otherwise 503 with `{"status":"degraded","db":"down"}`. + +### Claude's Discretion +- Concrete chi middleware order (within the above stack) and slog handler configuration details. +- Exact `air.toml` settings, file watch globs, and whether tailwind runs as a separate `just styles` process or air `pre_cmd`. +- Whether goose runs migrations via library call from a `backend migrate` subcommand or pure CLI in Phase 1 (Phase 7 will likely require the library approach; planner can prepare for that). +- Layout/CSS specifics of the demo page — keep minimal but professional; one visible `hx-get` interaction is enough. +- Whether to include a basic `internal/web/handlers_test.go` smoke test now or defer to Phase 2. + + + + +## Canonical References + +**Downstream agents MUST read these before planning or implementing.** + +### Project & Scope +- `.planning/PROJECT.md` — Core value, constraints, out-of-scope list, key decisions (fresh `backend/`, single VPS, no Node toolchain target). +- `.planning/REQUIREMENTS.md` §Foundation — FOUND-01..05 verbatim. +- `.planning/ROADMAP.md` §"Phase 1: Foundation" — Success criteria + user-in-loop callouts. + +### Codebase Maps (legacy JS app — behavioral reference only) +- `.planning/codebase/STACK.md` — Existing stack inventory (used to identify what we are *replacing*, not copying). +- `.planning/codebase/CONVENTIONS.md` — Existing conventions (most do not apply to the new Go backend, but useful for parity decisions). +- `.planning/codebase/CONCERNS.md` — Known pain points in the JS version that motivated the rewrite. + +### Existing Go scaffold (reference, not foundation) +- `go-backend/` — Scratch scaffold. **Not** the foundation per PROJECT.md. Useful as a sanity check for templ/chi/sqlc/pgx wiring patterns and for the `compose.yaml` + `justfile` shape, but the new `backend/` is built fresh. +- `go-backend/justfile` — Reference for podman compose + tailwind + templ + sqlc justfile layout. +- `go-backend/sqlc.yaml` — Reference for sqlc config shape. + +### External tool docs (planner will pull versions during research) +- goose: https://github.com/pressly/goose +- templ: https://templ.guide +- chi: https://github.com/go-chi/chi +- pgx: https://github.com/jackc/pgx +- air: https://github.com/cosmtrek/air +- Tailwind standalone CLI: https://tailwindcss.com/blog/standalone-cli + + + + +## Existing Code Insights + +### Reusable Assets (reference, copy-with-care) +- `go-backend/justfile`: pattern for podman compose + tailwind + templ + sqlc recipes — adapt, do not copy wholesale (it uses pnpm tailwind, which we are dropping). +- `go-backend/compose.yaml` (postgres service): can be lifted to `backend/compose.yaml` essentially as-is. +- `go-backend/sqlc.yaml`: config shape is a good starting point for `backend/sqlc.yaml`. + +### Established Patterns (from go-backend, validating our choices) +- templ + chi + pgx + sqlc is a known-working combination in this developer's hands (go-backend has all four wired). +- podman compose is the developer's local container runtime — confirmed working. + +### Integration Points +- `backend/` is greenfield — no integration points yet. Future phases attach: Phase 2 adds session middleware to the chi stack and `users`/`sessions` tables via goose migrations. + +### What we are deliberately NOT carrying over +- `go-backend/`'s pnpm + tailwindcss npm dependency (replaced by standalone binary). +- Any sqlc-generated code in `go-backend/internal/db/` (greenfield schema; will be regenerated against the new migrations). + + + + +## Specific Ideas + +- Developer explicitly chose **goose over golang-migrate** after weighing embeddability, sqlc-file-layout, and the (small) chance of needing Go-side migrations. +- Developer explicitly chose **two-cmd layout (`cmd/web` + `cmd/worker`)** over single-binary-with-subcommand. Implication: deploy ships two binaries (or two entrypoints from one image — revisit in Phase 7). +- Developer explicitly chose **podman compose + standalone Tailwind binary** — no Node toolchain in `backend/`. This is a load-bearing decision for the "simpler stack" thesis. +- One visible `hx-get` example on the root page is required (success criterion 3). Keep it minimal — a button that swaps in a server-rendered timestamp or counter is enough. + + + + +## Deferred Ideas + +- **Single-binary subcommand layout** (`backend web` / `backend worker`) — rejected for Phase 1; could be revisited in Phase 7 if the two-binary Docker image proves awkward. +- **Embedded goose migrations called from app startup** — Phase 1 uses CLI-only; embedded library call deferred to Phase 7 deploy decision. +- **`internal/web/handlers_test.go` smoke tests** — testing strategy is established in Phase 2 (auth requires real tests). Phase 1 may include a minimal `/healthz` test only. +- **Production logging configuration** (sampling, redaction, log shipping) — Phase 7 concern. +- **`/readyz` endpoint** — listed in Phase 7 success criteria (DEPLOY-04); Phase 1 ships `/healthz` only. + + + +--- + +*Phase: 1-Foundation* +*Context gathered: 2026-05-14* diff --git a/.planning/phases/01-foundation/01-DISCUSSION-LOG.md b/.planning/phases/01-foundation/01-DISCUSSION-LOG.md new file mode 100644 index 0000000..bc82d71 --- /dev/null +++ b/.planning/phases/01-foundation/01-DISCUSSION-LOG.md @@ -0,0 +1,79 @@ +# Phase 1: Foundation - Discussion Log + +> **Audit trail only.** Do not use as input to planning, research, or execution agents. +> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered. + +**Date:** 2026-05-14 +**Phase:** 1-Foundation +**Areas discussed:** Directory layout, Migration tool, Templating + router, Local dev stack + +--- + +## Directory Layout + +| Option | Description | Selected | +|--------|-------------|----------| +| Two cmds, shared internal | `backend/cmd/web` + `backend/cmd/worker` + shared `backend/internal/{db,web,session,tablos,tasks,files,...}`. Standard Go layout. | ✓ | +| Single binary, subcommand | One `backend` binary with `backend web` / `backend worker` subcommands. Smaller deploy artifact. | | +| Feature-first internal | `internal//{handler.go,store.go,templates/}` — each domain owns its own templates + handlers. | | + +**User's choice:** Two cmds, shared internal +**Notes:** Locks in conventional Go layout. Single-binary subcommand left available for Phase 7 reconsideration if the two-binary Docker image gets awkward. + +--- + +## Migration Tool + +| Option | Description | Selected | +|--------|-------------|----------| +| goose | pressly/goose. Embeddable library + CLI. Supports Go-based migrations. One file per migration with `-- +goose Up/Down`. | ✓ | +| golang-migrate | golang-migrate/migrate. CLI-first, SQL-only by default. Wider mindshare. Split `*.up.sql` / `*.down.sql`. | | +| atlas | ariga/atlas. Schema-as-code/declarative. Richer features but heavier learning curve. | | + +**User's choice:** goose +**Notes:** User asked for a direct recommendation; Claude argued for goose on three grounds — embeddability (one binary in Phase 7 deploy), single-file-per-migration aligning with sqlc, and the option for Go migrations if a backfill ever requires it. User accepted ("go for goose"). + +--- + +## Templating + Router + +| Option | Description | Selected | +|--------|-------------|----------| +| templ + chi | templ (type-safe, compiled templates) + chi router. Best HTMX-fragment DX. Matches go-backend's existing pick. | ✓ | +| html/template + net/http 1.22 | Std-lib-only — html/template + Go 1.22 enhanced ServeMux (method-prefixed patterns). Zero external deps. | | +| templ + net/http 1.22 | Keep templ's type safety, drop chi. Hand-roll middleware stacking. | | + +**User's choice:** templ + chi +**Notes:** Best DX for HTMX partials wins over a stricter dep budget. chi's middleware ergonomics and `Route()` subrouters will be load-bearing once auth + tablos routes land. + +--- + +## Local Dev Stack + +| Option | Description | Selected | +|--------|-------------|----------| +| podman + standalone Tailwind | podman compose for Postgres; standalone Tailwind binary (no Node/pnpm in `backend/`). | ✓ | +| docker compose + standalone Tailwind | docker compose for portability; same standalone Tailwind binary. | | +| podman + pnpm Tailwind | Keep go-backend's existing pnpm + tailwindcss npm package. | | + +**User's choice:** podman + standalone Tailwind +**Notes:** "No JS toolchain in `backend/`" is a load-bearing thesis. compose.yaml is portable between podman and docker — README will note both. Tailwind binary version is pinned in the justfile. + +--- + +## Claude's Discretion + +- Concrete chi middleware order within the agreed stack (RequestID → RealIP → Logger → Recoverer → graceful shutdown wiring). +- slog handler details (JSON in prod, text in dev), keyed off `ENV`. +- air configuration (`air.toml` watch globs; whether tailwind runs as a separate `just styles` process or air `pre_cmd`). +- Whether goose runs via library call from a `backend migrate` subcommand or pure CLI in Phase 1 (Phase 7 will likely require the library approach). +- Demo page layout/CSS — keep minimal but professional; the visible `hx-get` interaction is the only hard requirement. +- Whether to ship a minimal `/healthz` smoke test in Phase 1 or defer all testing to Phase 2. + +## Deferred Ideas + +- **Single-binary subcommand layout** (`backend web` / `backend worker`) — rejected for Phase 1; revisit in Phase 7 if two-binary Docker image is awkward. +- **Embedded goose migrations at app startup** — Phase 1 uses CLI; embedded library call deferred to Phase 7. +- **`/readyz` endpoint** — Phase 7 (DEPLOY-04). Phase 1 ships `/healthz` only. +- **Production logging configuration** (sampling, redaction, log shipping) — Phase 7. +- **Full handler test suite** — Phase 2 establishes testing strategy; Phase 1 may include a minimal `/healthz` test only.