go-htmx-gsd #1
2 changed files with 231 additions and 0 deletions
152
.planning/phases/01-foundation/01-CONTEXT.md
Normal file
152
.planning/phases/01-foundation/01-CONTEXT.md
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
# Phase 1: Foundation - Context
|
||||
|
||||
**Gathered:** 2026-05-14
|
||||
**Status:** Ready for planning
|
||||
|
||||
<domain>
|
||||
## 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).
|
||||
|
||||
</domain>
|
||||
|
||||
<decisions>
|
||||
## 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/<domain>` 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.
|
||||
|
||||
</decisions>
|
||||
|
||||
<canonical_refs>
|
||||
## 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
|
||||
|
||||
</canonical_refs>
|
||||
|
||||
<code_context>
|
||||
## 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).
|
||||
|
||||
</code_context>
|
||||
|
||||
<specifics>
|
||||
## 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.
|
||||
|
||||
</specifics>
|
||||
|
||||
<deferred>
|
||||
## 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.
|
||||
|
||||
</deferred>
|
||||
|
||||
---
|
||||
|
||||
*Phase: 1-Foundation*
|
||||
*Context gathered: 2026-05-14*
|
||||
79
.planning/phases/01-foundation/01-DISCUSSION-LOG.md
Normal file
79
.planning/phases/01-foundation/01-DISCUSSION-LOG.md
Normal file
|
|
@ -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/<feature>/{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.
|
||||
Loading…
Reference in a new issue