---
phase: 01-foundation
plan: 01
subsystem: backend-scaffold
tags: [go, scaffold, tooling, postgres, tailwind, htmx]
requires: []
provides:
  - backend-go-module
  - backend-directory-skeleton
  - backend-compose-postgres
  - backend-goose-migration-pipeline
  - backend-sqlc-config
  - backend-tailwind-input-css
  - backend-air-live-reload
  - backend-justfile
affects:
  - downstream-plan-01-02-handler-tests-and-ui
  - downstream-plan-01-03-walking-skeleton
tech_stack:
  added:
    - go-1.26
    - chi-v5.2.5
    - templ-v0.3.1020
    - pgx-v5.9.2
    - goose-v3.27.1
    - uuid-v1.6.0
    - sqlc-v1.31.1 (CLI; bootstrap-installed)
    - air-v1.65.1 (CLI; bootstrap-installed)
    - tailwind-standalone-v4.0.0 (binary; bootstrap-downloaded)
    - htmx-v2 (bootstrap-downloaded, pinned major in justfile)
  patterns: []
key_files:
  created:
    - backend/go.mod
    - backend/go.sum
    - backend/compose.yaml
    - backend/.env.example
    - backend/.gitignore
    - backend/.air.toml
    - backend/sqlc.yaml
    - backend/tailwind.input.css
    - backend/justfile
    - backend/migrations/0001_init.sql
    - backend/internal/db/doc.go
    - backend/internal/session/doc.go
    - backend/internal/tablos/doc.go
    - backend/internal/tasks/doc.go
    - backend/internal/files/doc.go
  modified: []
decisions:
  - locked: D-01 two-binary cmd/web + cmd/worker layout reflected in directory plan (cmd/* itself lands in Plan 01-03)
  - locked: D-02 placeholder doc.go files created for db, session, tablos, tasks, files
  - locked: D-04 goose chosen, .sql with +goose Up/Down format
  - locked: D-12 standalone Tailwind binary, no Node toolchain
  - locked: D-13/D-14 air for Go live-reload; Tailwind watch runs separately (two-terminal workflow)
  - locked: D-15 .env.example documents DATABASE_URL/PORT/ENV
  - locked: D-16 pgx/v5 + pgxpool; sqlc emits pgx-compatible code
metrics:
  duration_minutes: ~8
  completed: 2026-05-14
  tasks_completed: 5
  files_created: 18
---

# Phase 01 Plan 01: Backend Scaffold Summary

Stood up the `backend/` Go module skeleton — directory layout, compose Postgres, justfile recipes, sqlc/air/tailwind configs, and a no-op goose bootstrap migration — with zero application code, exactly per the locked CONTEXT D-01..D-20 decisions and revised post-Codex-review plan.

## What Shipped

### Go module + pinned runtime deps (commit `aa90008`)
- `backend/go.mod` declares `module backend` against Go 1.26.1.
- Five runtime deps pinned via `go get` at the exact RESEARCH-Standard-Stack versions: `chi v5.2.5`, `templ v0.3.1020`, `pgx/v5 v5.9.2`, `goose/v3 v3.27.1`, `uuid v1.6.0`.
- `go mod tidy` deliberately NOT executed (Codex concern #1) — tidy would strip the require lines while no source file imports them yet. Tidy lands implicitly in Plan 01-03's `just build` once consumer code exists.

### Directory skeleton + doc.go placeholders (commit `4de9685`)
- `internal/db/doc.go`, `internal/session/doc.go`, `internal/tablos/doc.go`, `internal/tasks/doc.go`, `internal/files/doc.go` — each a one-line comment + bare `package` clause per D-02.
- Empty directories materialized via `.gitkeep`: `internal/db/queries/`, `internal/db/sqlc/`, `templates/`, `migrations/`, `bin/`, `static/`.
- `cmd/web/`, `cmd/worker/`, `internal/web/` deliberately NOT created here — owned by Plan 01-02 (web ui/tests) and Plan 01-03 (entrypoints).
- `go build ./internal/...` compiles cleanly.

### Compose, env, gitignore, bootstrap migration (commit `2fe5b51`)
- `compose.yaml`: `postgres:16-alpine`, container name `xtablo-backend-postgres`, `xtablo:xtablo` credentials, `pg_isready` healthcheck (5s × 10), named volume `postgres_data`. No seed mounts (Phase 1 has nothing to seed).
- `.env.example`: `DATABASE_URL`, `PORT=8080`, `ENV=development` (D-15 exact key set).
- `.gitignore`: excludes `.env`, `bin/`, `tmp/`, generated `static/tailwind.css`, bootstrap-downloaded `static/htmx.min.js`, `*_templ.go` (Pitfall 1), `internal/db/sqlc/*.go`.
- `migrations/0001_init.sql`: goose-formatted no-op with `-- +goose Up` / `-- +goose Down` markers and `SELECT 1;` bodies.

### sqlc, Tailwind input CSS, air (commit `d6085b7`)
- `sqlc.yaml`: `version: "2"`, schema source = `migrations/` (Pitfall 7 alignment), `internal/db/queries/` → `internal/db/sqlc/` (package `sqlc`), `sql_package: "pgx/v5"`.
- `tailwind.input.css`: verbatim per UI-SPEC contract — `@import "tailwindcss"`, three `@source` globs covering `templates/**/*.templ`, `internal/web/**/*.templ`, `internal/web/**/*.go` (Pitfall 3), then `@import` of the four `internal/web/ui/{base,button,card,badge}.css` files (those CSS files land in Plan 01-02).
- `.air.toml`: watches `.go`+`.templ`, excludes `*_templ.go` (Pitfall 5), runs `templ generate && go build -o ./tmp/web ./cmd/web`. Tailwind intentionally NOT wired into air's pre_cmd (RESEARCH Open Q2 — two-terminal workflow).

### justfile (commit `ce22472`)
Recipes: `default`, `bootstrap`, `db-up`, `db-down`, `migrate cmd="status"`, `generate`, `styles-watch`, `dev`, `test`, `lint`, `build`, `clean` — `just --list` enumerates 11 user-facing recipes.

- `bootstrap` installs goose `v3.27.1` / templ `v0.3.1020` / sqlc `v1.31.1` / air `v1.65.1` and downloads Tailwind v4.0.0 standalone + HTMX v2 latest into `bin/` and `static/`.
- Tailwind asset name uses explicit `case` mapping (Codex concern #2): `Darwin→macos`, `Linux→linux`, `x86_64/amd64→x64`, `arm64/aarch64→arm64`, resolving to one of `tailwindcss-{macos,linux}-{x64,arm64}`. The four canonical asset names are documented inline; `grep -E 'tailwindcss-(macos|linux)-(x64|arm64)' backend/justfile` matches.
- `migrate` recipe sets `GOOSE_DRIVER=postgres`, `GOOSE_DBSTRING`, `GOOSE_MIGRATION_DIR=migrations`.
- `clean` recipe removes `bin/`, `tmp/`, `static/htmx.min.js`, `static/tailwind.css`, and `*_templ.go` files (Codex concern #10).
- No `pnpm`/`npm`/`node` references anywhere (D-12).
- Bootstrap-time CDN URLs (Tailwind GitHub releases, HTMX unpkg) are confined to the justfile — D-10 clarified: no **runtime** CDN references in served HTML/CSS/JS.

## Codex Review Concerns — Compliance

| # | Concern | Resolution |
|---|---------|------------|
| 1 | No `go mod tidy` in this plan | Honored — deps pinned via `go get` only; tidy deferred to Plan 01-03. |
| 2 | Tailwind URL via explicit OS/arch case (not raw `uname` interpolation) | Honored — `case` statements + inline doc comment listing the four canonical assets. |
| 3 | "Bootstrap-downloaded" wording (not "vendored") | Honored — used in .gitignore comment, justfile comments, and this SUMMARY. |
| 4 | No runtime CDN references | Honored — the only CDN URLs in `backend/` are inside `justfile` bootstrap. |
| 5 | `generate / test / build / dev` may not be end-to-end runnable yet | Acknowledged — only `just --list` parseability asserted in Plan 01-01. |
| 6 | Real `just clean` recipe | Implemented — removes bin/, tmp/, generated css, htmx, *_templ.go. |

## Deviations from Plan

None — plan executed exactly as written.

## Checkpoint Auto-Approval

⚡ Auto-approved checkpoint: human-verify (Task 6 — `just bootstrap` + `just db-up` + `just migrate up` end-to-end)

Auto-mode was active. Task 6 is a real network-dependent verification (downloads goose/templ/sqlc/air CLIs, downloads Tailwind binary and HTMX, brings up a Postgres container, applies the migration). Auto-approving it means the per-task commits land; the developer is expected to actually run the five-step bootstrap loop the next time they pick the repo up. If any step fails on their machine, that becomes a follow-up bug, not a plan-execution failure. The scaffold is mechanical and the file-level acceptance criteria (every `verify` block in Tasks 1–5) all passed automated grep/build checks before commit.

## Pinned Versions Actually Wired

| Tool | Version | Where pinned |
|------|---------|-------------|
| Go runtime | 1.26.1 | `go.mod` (matches developer's installed toolchain) |
| chi | v5.2.5 | `go.mod` |
| templ runtime | v0.3.1020 | `go.mod` |
| pgx/v5 | v5.9.2 | `go.mod` |
| goose/v3 | v3.27.1 | `go.mod` + `justfile` (CLI install) |
| uuid | v1.6.0 | `go.mod` |
| templ CLI | v0.3.1020 | `justfile` (CLI install) |
| sqlc CLI | v1.31.1 | `justfile` (CLI install) |
| air CLI | v1.65.1 | `justfile` (CLI install) |
| Tailwind standalone | v4.0.0 | `justfile` (download) |
| HTMX | v2 (latest 2.x at bootstrap time) | `justfile` (download) |

## Verification

- `cd backend && test -f go.mod && grep '^module backend$' go.mod` → OK
- `cd backend && go build ./internal/...` → OK (placeholder packages compile)
- `cd backend && just --list` → 11 user recipes enumerated, all required names present
- `grep -E 'tailwindcss-(macos|linux)-(x64|arm64)' backend/justfile` → 2 matches (Codex #2)
- File-level grep checks per each task's `<verify>` block → all pass

## Path Forward

- **Plan 01-02 (Wave 2):** lands `internal/web/handlers_test.go` for `/healthz` OK/Down + index/fragment + RequestID + slog-handler-switch tests, plus the `internal/web/ui/{base,button,card,badge}.css` files referenced by `tailwind.input.css`. After 01-02, `just generate` will partially run (Tailwind step will succeed; templ step still needs Plan 01-03 templ files).
- **Plan 01-03 (Wave 3):** lands `cmd/web/main.go`, `cmd/worker/main.go`, `internal/db/pool.go`, the templ files for the index + demo fragment, the chi router + middleware. After 01-03, `just bootstrap && just db-up && just migrate up && just dev` is a fully runnable walking skeleton.

## Known Stubs

None requiring resolution within this plan — every placeholder (`doc.go` files, `*_templ.go` ignore rule, the four `ui/*.css` `@import` lines, the `cmd/web` reference in `.air.toml`) is wired exactly where the next plans will land their real content; no UI-visible stub flows to a user yet because there is no UI yet.

## Self-Check: PASSED

- `backend/go.mod` exists with `module backend` and five pinned runtime deps — FOUND
- `backend/go.sum` exists, non-empty — FOUND
- `backend/internal/{db,session,tablos,tasks,files}/doc.go` all exist and `go build ./internal/...` passes — FOUND
- `backend/compose.yaml`, `.env.example`, `.gitignore`, `migrations/0001_init.sql` — FOUND
- `backend/sqlc.yaml`, `tailwind.input.css`, `.air.toml` — FOUND
- `backend/justfile` parses; `just --list` shows all 11 required recipes — FOUND
- Commits `aa90008`, `4de9685`, `2fe5b51`, `d6085b7`, `ce22472` present in `git log` — FOUND
