go-htmx-gsd #1
6 changed files with 84 additions and 42 deletions
|
|
@ -3,7 +3,7 @@ gsd_state_version: 1.0
|
|||
milestone: v1.0
|
||||
milestone_name: milestone
|
||||
status: unknown
|
||||
last_updated: "2026-05-14T15:18:59.172Z"
|
||||
last_updated: "2026-05-14T15:41:00.506Z"
|
||||
progress:
|
||||
total_phases: 7
|
||||
completed_phases: 0
|
||||
|
|
|
|||
|
|
@ -35,10 +35,12 @@ tags:
|
|||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Running `just bootstrap` from a fresh clone installs goose, templ, sqlc, air CLIs and downloads the Tailwind standalone binary + htmx.min.js into backend/bin and backend/static"
|
||||
- "Running `just bootstrap` from a fresh clone installs goose, templ, sqlc, air CLIs and bootstrap-downloads the Tailwind standalone binary + htmx.min.js into backend/bin and backend/static (these assets are NOT committed; they are gitignored and reproduced by `just bootstrap`)"
|
||||
- "Running `just db-up` (or `podman compose up -d postgres`) starts a healthy Postgres 16 container reachable at localhost:5432"
|
||||
- "Running `just migrate up` against the running Postgres applies migration 0001_init.sql cleanly and `just migrate status` shows it applied"
|
||||
- "Running `just --list` shows recipes for at least: bootstrap, dev, db-up, db-down, migrate, generate, styles-watch, test, lint, build"
|
||||
- "Running `just --list` shows recipes for at least: bootstrap, dev, db-up, db-down, migrate, generate, styles-watch, test, lint, build, clean"
|
||||
- "Phase-1 caveat: the `generate`, `test`, `build`, and `dev` recipes are scaffolded in Plan 01-01 but only become runnable after Plans 01-02 and 01-03 land their consumers (templ files, ui/*.css, cmd/web/main.go, cmd/worker/main.go). The justfile parses and `just --list` enumerates them in Plan 01-01; their end-to-end execution is covered by Plan 01-03's checkpoint."
|
||||
- "CDN policy (clarified): no runtime CDN references in served HTML/CSS/JS or in any committed source file consumed at request time; bootstrap-time download URLs (Tailwind GitHub release, HTMX from unpkg) appear ONLY inside `backend/justfile` and are the single authoritative source for the pinned asset versions."
|
||||
- "Tailwind input CSS declares @source globs that include backend/templates/**/*.templ and backend/internal/web/**/*.{templ,go} so JIT does not purge classes used only in templ files"
|
||||
- ".env.example documents DATABASE_URL, PORT, ENV and .env is gitignored"
|
||||
- "D-01: Two-binary cmd/web + cmd/worker layout with shared internal/ packages"
|
||||
|
|
@ -164,17 +166,22 @@ Output: `backend/` exists with a valid Go module, all directory placeholders, wo
|
|||
- .planning/phases/01-foundation/01-RESEARCH.md (Standard Stack section — pinned versions are authoritative)
|
||||
- .planning/phases/01-foundation/SKELETON.md (locked versions table)
|
||||
</read_first>
|
||||
<action>From repo root, create `backend/`, then `cd backend && go mod init backend`. Pin the following runtime deps with `go get` at the exact versions from RESEARCH.md Standard Stack: `github.com/go-chi/chi/v5@v5.2.5`, `github.com/a-h/templ@v0.3.1020`, `github.com/jackc/pgx/v5@v5.9.2`, `github.com/pressly/goose/v3@v3.27.1`, `github.com/google/uuid@v1.6.0`. Do NOT use any version other than those listed. Run `go mod tidy` so `go.sum` is populated. The Go directive should match the developer's installed Go (1.22 minimum; existing `go-backend/go.mod` uses 1.26 — use the same `go` line if available, otherwise `1.22`). Do not import or fetch `air`, `sqlc`, `templ` CLI, `goose` CLI as Go module deps in `go.mod` — those are installed via `go install` from the `just bootstrap` recipe (Task 6).</action>
|
||||
<action>From repo root, create `backend/`, then `cd backend && go mod init backend`. Pin the following runtime deps with `go get` at the exact versions from RESEARCH.md Standard Stack: `github.com/go-chi/chi/v5@v5.2.5`, `github.com/a-h/templ@v0.3.1020`, `github.com/jackc/pgx/v5@v5.9.2`, `github.com/pressly/goose/v3@v3.27.1`, `github.com/google/uuid@v1.6.0`. Do NOT use any version other than those listed.
|
||||
|
||||
**Do NOT run `go mod tidy` in this task.** Because no Go source file imports these packages yet in Plan 01-01, `go mod tidy` would aggressively strip the `require` lines we just pinned (Codex review concern #1). `go get` writes the pins into `go.mod` and `go.sum` directly; that is sufficient for Plan 01-01's purposes. `go mod tidy` becomes safe to run only after Plan 01-03 lands the consumer code in `internal/db/pool.go`, `internal/web/{router,handlers,middleware,slog}.go`, and `cmd/{web,worker}/main.go` — Plan 01-03's `just generate` / `just build` workflow will execute it implicitly, and at that point all five deps have real importers so `tidy` is a no-op on the require list.
|
||||
|
||||
The Go directive should match the developer's installed Go (1.22 minimum; existing `go-backend/go.mod` uses 1.26 — use the same `go` line if available, otherwise `1.22`). Do not import or fetch `air`, `sqlc`, `templ` CLI, `goose` CLI as Go module deps in `go.mod` — those are installed via `go install` from the `just bootstrap` recipe (Task 5).</action>
|
||||
<verify>
|
||||
<automated>cd backend && go mod verify && grep -q 'github.com/go-chi/chi/v5 v5.2.5' go.mod && grep -q 'github.com/a-h/templ v0.3.1020' go.mod && grep -q 'github.com/jackc/pgx/v5 v5.9.2' go.mod && grep -q 'github.com/pressly/goose/v3 v3.27.1' go.mod && grep -q 'github.com/google/uuid v1.6.0' go.mod</automated>
|
||||
<automated>cd backend && test -f go.mod && test -f go.sum && grep -q '^module backend$' go.mod && grep -q 'github.com/go-chi/chi/v5 v5.2.5' go.mod && grep -q 'github.com/a-h/templ v0.3.1020' go.mod && grep -q 'github.com/jackc/pgx/v5 v5.9.2' go.mod && grep -q 'github.com/pressly/goose/v3 v3.27.1' go.mod && grep -q 'github.com/google/uuid v1.6.0' go.mod</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `backend/go.mod` declares `module backend`
|
||||
- All five runtime deps pinned at the versions above (verified by grep, not just presence)
|
||||
- `go mod verify` exits 0
|
||||
- `go.sum` is populated and committed
|
||||
- All five runtime deps pinned at the exact versions above (verified by grep on the literal `name version` line)
|
||||
- `go.sum` exists and is non-empty (populated by `go get`)
|
||||
- `go mod tidy` is NOT run in this task (Codex concern #1: would strip the require lines because no Go source imports them yet)
|
||||
- `go mod verify` is NOT asserted here because it can interact badly with un-imported requires on some Go versions; Plan 01-03 reasserts it after real importers land
|
||||
</acceptance_criteria>
|
||||
<done>`backend/go.mod` and `backend/go.sum` exist, module is `backend`, all five deps pinned at locked versions, `go mod verify` passes.</done>
|
||||
<done>`backend/go.mod` and `backend/go.sum` exist, module is `backend`, all five deps pinned at locked versions; tidying deferred to Plan 01-03 where real imports exist.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
|
|
@ -204,7 +211,7 @@ Output: `backend/` exists with a valid Go module, all directory placeholders, wo
|
|||
- .planning/phases/01-foundation/01-RESEARCH.md (Code Examples section — compose.yaml and .env.example are verbatim references; D-15, D-20)
|
||||
- go-backend/compose.yaml (existing healthy reference; strip dev seed mounts as the research note instructs)
|
||||
</read_first>
|
||||
<action>Write `backend/compose.yaml` based on the verbatim block in RESEARCH.md Code Examples. Required: service name `postgres`, image `postgres:16-alpine`, container name `xtablo-backend-postgres`, env `POSTGRES_DB=xtablo`, `POSTGRES_USER=xtablo`, `POSTGRES_PASSWORD=xtablo`, port mapping `5432:5432`, named volume `postgres_data`, `pg_isready -U xtablo -d xtablo` healthcheck (interval 5s, timeout 5s, retries 10), `restart: unless-stopped`. Do NOT include the seed-mount volume from `go-backend/compose.yaml` — Phase 1 has nothing to seed. Write `backend/.env.example` with exactly the three keys locked in D-15: `DATABASE_URL=postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable`, `PORT=8080`, `ENV=development`. Add a brief comment line above each key. Write `backend/.gitignore` covering: `bin/` (Tailwind binary + go-installed CLIs may live here), `tmp/` (air rebuild output), `.env`, `.env.local`, `static/tailwind.css` (generated), `static/htmx.min.js` (vendored on bootstrap, not committed), `*_templ.go` (templ-generated; per RESEARCH Pitfall 1 these are never committed), `internal/db/sqlc/*.go` (generated, except keep `.gitkeep`). Write `backend/migrations/0001_init.sql` exactly per RESEARCH.md Code Examples — goose annotations `-- +goose Up` and `-- +goose Down`, each section containing `SELECT 1;`. The file MUST start with `-- +goose Up` on the first non-comment line (goose parser requirement).</action>
|
||||
<action>Write `backend/compose.yaml` based on the verbatim block in RESEARCH.md Code Examples. Required: service name `postgres`, image `postgres:16-alpine`, container name `xtablo-backend-postgres`, env `POSTGRES_DB=xtablo`, `POSTGRES_USER=xtablo`, `POSTGRES_PASSWORD=xtablo`, port mapping `5432:5432`, named volume `postgres_data`, `pg_isready -U xtablo -d xtablo` healthcheck (interval 5s, timeout 5s, retries 10), `restart: unless-stopped`. Do NOT include the seed-mount volume from `go-backend/compose.yaml` — Phase 1 has nothing to seed. Write `backend/.env.example` with exactly the three keys locked in D-15: `DATABASE_URL=postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable`, `PORT=8080`, `ENV=development`. Add a brief comment line above each key. Write `backend/.gitignore` covering: `bin/` (Tailwind binary + go-installed CLIs may live here), `tmp/` (air rebuild output), `.env`, `.env.local`, `static/tailwind.css` (generated), `static/htmx.min.js` (bootstrap-downloaded by `just bootstrap`, never committed), `*_templ.go` (templ-generated; per RESEARCH Pitfall 1 these are never committed), `internal/db/sqlc/*.go` (generated, except keep `.gitkeep`). Write `backend/migrations/0001_init.sql` exactly per RESEARCH.md Code Examples — goose annotations `-- +goose Up` and `-- +goose Down`, each section containing `SELECT 1;`. The file MUST start with `-- +goose Up` on the first non-comment line (goose parser requirement).</action>
|
||||
<verify>
|
||||
<automated>cd backend && grep -q 'postgres:16-alpine' compose.yaml && grep -q 'pg_isready' compose.yaml && grep -q '^DATABASE_URL=' .env.example && grep -q '^PORT=' .env.example && grep -q '^ENV=' .env.example && grep -q '^\.env$' .gitignore && grep -q 'tailwind.css' .gitignore && grep -q 'htmx.min.js' .gitignore && grep -q '_templ\.go' .gitignore && grep -q '\-\- +goose Up' migrations/0001_init.sql && grep -q '\-\- +goose Down' migrations/0001_init.sql</automated>
|
||||
</verify>
|
||||
|
|
@ -251,7 +258,7 @@ Output: `backend/` exists with a valid Go module, all directory placeholders, wo
|
|||
<action>Write `backend/justfile` modeled on the RESEARCH Code Examples block. Required at minimum, these recipes (names exact):
|
||||
|
||||
- `default` → `@just --list`
|
||||
- `bootstrap` → mkdir bin, then `go install` the four CLI tools at the exact RESEARCH-pinned versions: `github.com/pressly/goose/v3/cmd/goose@v3.27.1`, `github.com/a-h/templ/cmd/templ@v0.3.1020`, `github.com/sqlc-dev/sqlc/cmd/sqlc@v1.31.1`, `github.com/air-verse/air@v1.65.1`. Then `curl -sSL` the Tailwind v4 standalone binary into `./bin/tailwindcss` (use the platform-detection pattern `tailwindcss-$(uname -s | tr A-Z a-z)-$(uname -m)`) and `chmod +x`. Then `curl -sSL` the HTMX v2.x `htmx.min.js` from `unpkg.com/htmx.org@2/dist/htmx.min.js` into `./static/htmx.min.js` (vendored once at bootstrap; subsequently committed-via-gitignore semantics — per CONTEXT D-10 we vendor, never CDN). Pin the Tailwind version in a recipe-local variable at the top of the justfile (e.g., `tailwind_version := "v4.0.0"` — planner: use the latest 4.x stable available at bootstrap time; document the version in a comment).
|
||||
- `bootstrap` → mkdir bin, then `go install` the four CLI tools at the exact RESEARCH-pinned versions: `github.com/pressly/goose/v3/cmd/goose@v3.27.1`, `github.com/a-h/templ/cmd/templ@v0.3.1020`, `github.com/sqlc-dev/sqlc/cmd/sqlc@v1.31.1`, `github.com/air-verse/air@v1.65.1`. Then bootstrap-download the Tailwind v4 standalone binary into `./bin/tailwindcss` using an **explicit OS/arch mapping** (Codex concern #2 — the `uname -s`/`uname -m` outputs do NOT match GitHub release asset naming; `darwin` vs `macos`, `x86_64` vs `x64`, `aarch64`/`arm64` vs `arm64`). The recipe must compute the asset name with a shell case on `uname -s` and `uname -m` resolving to one of: `tailwindcss-macos-x64`, `tailwindcss-macos-arm64`, `tailwindcss-linux-x64`, `tailwindcss-linux-arm64` (verify these against `https://github.com/tailwindlabs/tailwindcss/releases/latest` at implementation time — the four-name set above is current as of Tailwind v4.0). Use a recipe-local `tailwind_version` variable (e.g., `tailwind_version := "v4.0.0"` — use the latest 4.x stable available at bootstrap time and document in a comment). After download, `chmod +x bin/tailwindcss`. Then bootstrap-download the HTMX v2.x `htmx.min.js` from `unpkg.com/htmx.org@2/dist/htmx.min.js` into `./static/htmx.min.js` — this asset is bootstrap-downloaded (not committed; `.gitignore` excludes it), and the justfile is the single authoritative source for the HTMX version. Per CONTEXT D-10 the runtime page MUST NOT reference any CDN; the bootstrap-time `unpkg.com` URL inside the justfile is the explicit allowed exception.
|
||||
- `db-up` → `podman compose up -d postgres`
|
||||
- `db-down` → `podman compose down`
|
||||
- `migrate cmd="status"` → invokes `goose` with `GOOSE_DRIVER=postgres`, `GOOSE_DBSTRING` set to the dev DSN, `GOOSE_MIGRATION_DIR=migrations`, passing `{{cmd}}` (so `just migrate up`, `just migrate down`, `just migrate status` all work)
|
||||
|
|
@ -261,17 +268,20 @@ Output: `backend/` exists with a valid Go module, all directory placeholders, wo
|
|||
- `test` → runs `just generate` then `go test ./...`
|
||||
- `lint` → runs `go vet ./...` and `gofmt -l .` (failing if any file would be reformatted)
|
||||
- `build` → runs `just generate`, then `go build -o bin/web ./cmd/web`, then `go build -o bin/worker ./cmd/worker`
|
||||
- `clean` → removes bootstrap-downloaded and generated artifacts: `rm -rf bin/ tmp/ static/htmx.min.js static/tailwind.css` and `find . -name '*_templ.go' -delete`. Does NOT remove the Postgres volume — call `just db-down` first if a full reset is needed. (Codex concern #10 — README Method B references `just clean`; provide a real recipe.)
|
||||
|
||||
Document at top of justfile in a comment block: project name, podman/docker portability note (CONTEXT D-11 — `compose.yaml` works under either; README will spell out the alternative `docker compose` invocation), and the two-terminal `dev` + `styles-watch` workflow. Do NOT add a recipe to download `goose`/`templ`/`sqlc`/`air` outside of `bootstrap` — keep one install path.
|
||||
|
||||
Note: `build` will fail until Plan 03 ships `cmd/web/main.go` and `cmd/worker/main.go`. That is expected and acceptable for Plan 01's verification — we only assert the justfile parses and `just --list` enumerates the required recipes.</action>
|
||||
<verify>
|
||||
<automated>cd backend && just --list 2>/dev/null | grep -E '^\s+(bootstrap|db-up|db-down|migrate|generate|styles-watch|dev|test|lint|build)\b' | wc -l | awk '$1>=9{exit 0} {exit 1}'</automated>
|
||||
<automated>cd backend && just --list 2>/dev/null | grep -E '^\s+(bootstrap|db-up|db-down|migrate|generate|styles-watch|dev|test|lint|build|clean)\b' | wc -l | awk '$1>=10{exit 0} {exit 1}'</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `just --list` enumerates at least: bootstrap, db-up, db-down, migrate, generate, styles-watch, dev, test, lint, build
|
||||
- `just --list` enumerates at least: bootstrap, db-up, db-down, migrate, generate, styles-watch, dev, test, lint, build, clean
|
||||
- `bootstrap` recipe installs goose v3.27.1, templ v0.3.1020, sqlc v1.31.1, air v1.65.1 at exact pinned versions (verifiable by grep on the justfile)
|
||||
- `bootstrap` recipe downloads htmx.min.js into static/ from unpkg.com (NOT a CDN reference at runtime — D-10)
|
||||
- `bootstrap` recipe resolves the Tailwind asset name via an explicit OS/arch case — `darwin→macos`, `linux→linux`, `x86_64→x64`, `arm64`/`aarch64→arm64` — and downloads one of the four documented asset names (Codex concern #2). Grep test: `grep -E 'tailwindcss-(macos|linux)-(x64|arm64)' backend/justfile` returns ≥ 1 match.
|
||||
- `bootstrap` recipe bootstrap-downloads htmx.min.js into static/ from unpkg.com (this is a bootstrap-time exception to D-10's no-CDN rule; D-10 forbids runtime CDN references — the served HTML/CSS/JS must reference only `/static/htmx.min.js`)
|
||||
- `clean` recipe exists and removes `bin/`, `tmp/`, `static/htmx.min.js`, `static/tailwind.css`, and `*_templ.go` files
|
||||
- `migrate` recipe sets GOOSE_DRIVER, GOOSE_DBSTRING, GOOSE_MIGRATION_DIR
|
||||
- `dev` recipe runs `db-up`, `generate`, then `air`
|
||||
- No pnpm / npm / node references anywhere in the justfile (D-12)
|
||||
|
|
@ -316,7 +326,7 @@ Output: `backend/` exists with a valid Go module, all directory placeholders, wo
|
|||
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|-------------|-----------------|
|
||||
| T-01-01 | T (Tampering) | `static/htmx.min.js` vendored via curl from unpkg | accept | Phase 1 scope: vendoring from a public CDN is an explicit decision (D-10 forbids runtime CDN, not bootstrap-time fetch). Subresource integrity hashes deferred to Phase 7 along with deploy hardening. Rationale: low-value local-dev attack surface; CSS/HTMX assets do not handle secrets in Phase 1. |
|
||||
| T-01-01 | T (Tampering) | `static/htmx.min.js` bootstrap-downloaded via curl from unpkg | accept | Phase 1 scope: vendoring from a public CDN is an explicit decision (D-10 forbids runtime CDN, not bootstrap-time fetch). Subresource integrity hashes deferred to Phase 7 along with deploy hardening. Rationale: low-value local-dev attack surface; CSS/HTMX assets do not handle secrets in Phase 1. |
|
||||
| T-01-02 | T (Tampering) | `bin/tailwindcss` binary downloaded via curl | accept | Same rationale as T-01-01. Tailwind is local-dev tooling only; not shipped in any production image in Phase 1. Hash pinning revisited at Phase 7 (deploy) where reproducible CI bootstrap matters. |
|
||||
| T-01-03 | I (Information disclosure) | `.env` containing dev DSN | mitigate | `.gitignore` excludes `.env`; only `.env.example` (with placeholder/dev-only credentials) is committed. Dev DSN uses the literal `xtablo:xtablo` credential set, which is harmless for local-only bound containers. |
|
||||
| T-01-04 | I (Information disclosure) | `goose_db_version` table | accept | Standard goose metadata table; contains only migration version numbers and timestamps. No user data. |
|
||||
|
|
@ -338,7 +348,9 @@ Output: `backend/` exists with a valid Go module, all directory placeholders, wo
|
|||
4. `just db-up` starts a healthy postgres:16-alpine container reachable at localhost:5432
|
||||
5. `just migrate up` applies the no-op bootstrap migration; `just migrate status` shows it as Applied
|
||||
6. No Node/npm/pnpm artifacts anywhere in `backend/` (D-12)
|
||||
7. No CDN references in any committed file (D-10)
|
||||
7. No **runtime** CDN references in any served HTML/CSS/JS (D-10 clarified): bootstrap-time download URLs inside `backend/justfile` (Tailwind GitHub release URL, HTMX unpkg URL) are the explicit allowed exception; templates and committed CSS reference only `/static/*` paths
|
||||
8. Tailwind asset name is resolved via explicit OS/arch mapping (`darwin→macos`, `x86_64→x64`, `arm64`/`aarch64→arm64`) — never via raw `uname` interpolation (Codex concern #2)
|
||||
9. `go mod tidy` is NOT executed in Plan 01-01 (Codex concern #1) — pinning is via `go get` only; tidy lands implicitly in Plan 01-03 when consumers import the deps
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ files_modified:
|
|||
- backend/internal/web/ui/ui_test.go
|
||||
- backend/internal/web/handlers_test.go
|
||||
- backend/internal/db/pool_test.go
|
||||
# Tests in handlers_test.go and pool_test.go carry the `//go:build red_gate` build tag during Plan 01-02 so a stray `go test ./...` does NOT fail with `undefined: web.NewRouter` / `db.NewPool` errors before Plan 01-03 lands the implementations (Codex concern #3). Plan 01-03 removes the build tag from both files as part of the GREEN step.
|
||||
autonomous: true
|
||||
requirements:
|
||||
- FOUND-01
|
||||
|
|
@ -33,7 +34,8 @@ tags:
|
|||
|
||||
must_haves:
|
||||
truths:
|
||||
- "After this plan lands, `go test ./internal/...` runs and the listed handler tests FAIL with 'undefined: <handler>' or equivalent — proving the test scaffold targets the right functions/signatures before Plan 03 implements them (RED step of MVP)"
|
||||
- "After this plan lands, `go test -tags=red_gate ./internal/web/... ./internal/db/...` FAILS with `undefined: web.NewRouter` / `db.NewPool` (or equivalent) — proving the RED test scaffold targets the exact symbols Plan 01-03 will implement. Without the `red_gate` tag, `go test ./...` SUCCEEDS (the red-gated files are excluded from default compilation), so the intentional RED state does not leak into normal `go test ./...` invocations (Codex concern #3)."
|
||||
- "Verification in Plan 01-02 is package-scoped (`go test ./internal/web/ui/...` for the GREEN ui smoke tests; `go test -tags=red_gate ./internal/web/...` for the RED gate). `go test ./...` without `-tags=red_gate` MUST pass at the end of Plan 01-02 because the red-gated test files are excluded from default compilation."
|
||||
- "The `internal/web/ui` design-system package compiles standalone and its `ui_test.go` smoke tests PASS — each shipped variant of Button, Card, Badge renders without panicking and emits the expected root CSS class"
|
||||
- "The UI component package mirrors the enum surface of `go-backend/internal/web/ui/` for Size, ButtonVariant, ButtonTone, BadgeVariant — future phases extend these enums' CSS without redefining them"
|
||||
- "Test files declare the exact handler function signatures and templ component names that Plan 03 will implement, so Plan 03 is a fill-in not an exploration"
|
||||
|
|
@ -72,11 +74,11 @@ must_haves:
|
|||
provides: Smoke tests rendering each Phase-1 variant; asserts root CSS class appears in output
|
||||
contains: "TestButton"
|
||||
- path: backend/internal/web/handlers_test.go
|
||||
provides: Failing tests for healthz OK + down, index hx-get presence, demo/time fragment, RequestID header, slog handler switch
|
||||
contains: "TestHealthz_OK"
|
||||
provides: Failing tests for healthz OK + down, index hx-get presence, demo/time fragment, RequestID header, slog handler switch — gated behind `//go:build red_gate` until Plan 01-03 lands the handlers
|
||||
contains: "//go:build red_gate"
|
||||
- path: backend/internal/db/pool_test.go
|
||||
provides: pgxpool integration test that SKIPS when DATABASE_URL is unset
|
||||
contains: "t.Skip"
|
||||
provides: pgxpool integration test that SKIPS when DATABASE_URL is unset; gated behind `//go:build red_gate` until Plan 01-03 lands `db.NewPool`
|
||||
contains: "//go:build red_gate"
|
||||
key_links:
|
||||
- from: backend/internal/web/handlers_test.go
|
||||
to: backend/internal/web/{router.go,handlers.go,middleware.go} (Plan 03)
|
||||
|
|
@ -201,9 +203,11 @@ type BadgeProps struct {
|
|||
|
||||
// Templ components — match these names exactly
|
||||
templ Button(props ButtonProps)
|
||||
templ Card(attrs templ.Attributes) // children via templ children
|
||||
templ Card(attrs templ.Attributes) // accepts children via templ's child-content syntax — verify exact syntax against the pinned templ v0.3.1020 docs before locking the rendered HTML test assertions (Codex concern #8)
|
||||
templ Badge(props BadgeProps)
|
||||
```
|
||||
|
||||
**templ syntax verification (Codex concern #8):** Before authoring `card.templ` and its smoke test in Task 2 / Task 3, the executor MUST run `templ version` to confirm v0.3.1020 is installed and consult `https://templ.guide/syntax-and-usage/template-composition` (or the equivalent docs version) to confirm the exact child-content syntax — early templ versions used `{ children... }` inside the templ body, later versions evolved the spelling. The Task 2 acceptance below asserts ONLY that the rendered `<section class="ui-card">...</section>` wraps the literal child content; the exact templ source syntax is the executor's call within that contract.
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
|
|
@ -270,7 +274,7 @@ templ Badge(props BadgeProps)
|
|||
<action>
|
||||
Write `button.templ`: declare `templ Button(props ButtonProps)`. Inside, compute `class` from `ui.ButtonClass(NormalizedButtonVariant(props.Variant), NormalizedButtonTone(props.Tone), NormalizedSize(props.Size))`. Render a `<button>` element with `type={props.Type or "button"}`, `class={class}`, and the pass-through `{props.Attrs...}` spread (templ attribute spread). The button body is `{props.Label}`. Do NOT inline Tailwind utility classes inside the `<button>` markup — the CSS rules live in `button.css`.
|
||||
|
||||
Write `button.css`: declare `.ui-button` as a base rule (display, font, focus-ring per UI-SPEC), and `.ui-button-solid-default-md` with the EXACT Tailwind-equivalent values from UI-SPEC §HTMX Interaction Pattern: `display: inline-flex; align-items: center; border-radius: 0.375rem; background-color: #2563eb; padding: 0.5rem 1rem; font-size: 1rem; font-weight: 600; color: #ffffff;` and `&:hover { background-color: #1d4ed8; }` and the focus-ring spec from UI-SPEC. Add the `htmx-request:opacity-60 htmx-request:pointer-events-none` behavior either via Tailwind variants (kept as utility classes on the `<button>`) or via plain CSS `.htmx-request { opacity: 0.6; pointer-events: none; }` — UI-SPEC's markup example uses Tailwind variants on the element, but since this contract centralizes classes in `button.css`, prefer the plain-CSS `.htmx-request` rule scoped under `.ui-button`. Document the choice in a one-line CSS comment.
|
||||
Write `button.css`: declare `.ui-button` as a base rule (display, font, focus-ring per UI-SPEC), and `.ui-button-solid-default-md` with the EXACT Tailwind-equivalent values from UI-SPEC §HTMX Interaction Pattern: `display: inline-flex; align-items: center; border-radius: 0.375rem; background-color: #2563eb; padding: 0.5rem 1rem; font-size: 1rem; font-weight: 600; color: #ffffff;`. Hover state MUST be expressed as a **non-nested selector** `.ui-button-solid-default-md:hover { background-color: #1d4ed8; }` — do NOT use CSS nesting (`&:hover`) (Codex concern #7 — Tailwind v4 standalone's CSS pipeline does not reliably support nested selectors in plain-CSS component layer files, and explicit selectors work under every Tailwind version). Same rule applies to focus-ring: write `.ui-button-solid-default-md:focus-visible { ... }` as a top-level rule. Add the `htmx-request` loading behavior as a plain-CSS rule `.ui-button.htmx-request { opacity: 0.6; pointer-events: none; }` (also non-nested). Document the no-nesting choice in a one-line CSS comment at the top of `button.css`.
|
||||
|
||||
Write `card.templ`: declare `templ Card(attrs templ.Attributes)` accepting children. Render `<section class="ui-card" {attrs...}>{ children... }</section>`.
|
||||
|
||||
|
|
@ -362,21 +366,34 @@ templ Badge(props BadgeProps)
|
|||
<action>
|
||||
Write both test files. Stub types live in the same file as the tests that use them (no separate helpers file in Phase 1).
|
||||
|
||||
**Red-gate isolation (Codex concern #3).** Both files MUST start with the build tag line:
|
||||
|
||||
```
|
||||
//go:build red_gate
|
||||
```
|
||||
|
||||
followed by a blank line, then the `package web` / `package db` clause. This tag ensures:
|
||||
- Default `go test ./...` and `go build ./...` invocations EXCLUDE these files — so `undefined: web.NewRouter` / `undefined: db.NewPool` errors do NOT leak into normal CI or developer workflows during the Plan 01-02 → 01-03 transition.
|
||||
- Explicit `go test -tags=red_gate ./internal/web/... ./internal/db/...` INCLUDES the files and fails with the expected `undefined:` errors — that is the proof that Plan 01-02's RED gate is wired against the right symbols.
|
||||
- Plan 01-03's GREEN slice removes both `//go:build red_gate` lines as part of its work (the implementations exist; the gate is no longer needed). Plan 01-03's verify runs `go test ./...` without the tag and expects every test green.
|
||||
|
||||
Required imports (handlers_test.go): `bytes`, `context`, `encoding/json`, `errors`, `net/http`, `net/http/httptest`, `regexp`, `strings`, `testing`. Use Go's stdlib `regexp` for the UUID + timestamp patterns.
|
||||
|
||||
These tests reference `web.NewRouter`, `web.HealthzHandler`, `web.NewSlogHandler`, `web.Pinger` interface, all of which Plan 03 introduces. The test file's `package web` declaration ensures it compiles as part of the package — if those symbols don't exist, `go test ./internal/web/` will fail with `undefined: NewRouter` etc. That is the desired RED state at end of Plan 02.
|
||||
These tests reference `web.NewRouter`, `web.HealthzHandler`, `web.NewSlogHandler`, `web.Pinger` interface, all of which Plan 03 introduces. With the build tag in place, the symbols don't need to exist yet under default compilation. Under `-tags=red_gate` they will fail with `undefined: NewRouter` — that is the desired RED state.
|
||||
|
||||
Do NOT add `// +build` constraints. Do NOT skip the failing tests with `t.Skip` — they must remain RED until Plan 03.
|
||||
Do NOT use `// +build` (legacy syntax) — use only the new `//go:build red_gate` form (Go 1.17+). Do NOT skip the failing tests with `t.Skip` — they must remain RED under the `red_gate` tag until Plan 01-03 removes the tag.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd backend && test -f internal/web/handlers_test.go && test -f internal/db/pool_test.go && grep -c '^func Test' internal/web/handlers_test.go | awk '$1>=6{exit 0} {exit 1}' && grep -q 'TestPool_Connects' internal/db/pool_test.go && grep -q 't.Skip' internal/db/pool_test.go && (cd internal/web && ! go test -count=1 -run 'TestHealthz_OK' . 2>&1 | grep -q '^ok')</automated>
|
||||
<automated>cd backend && test -f internal/web/handlers_test.go && test -f internal/db/pool_test.go && grep -q '^//go:build red_gate' internal/web/handlers_test.go && grep -q '^//go:build red_gate' internal/db/pool_test.go && grep -c '^func Test' internal/web/handlers_test.go | awk '$1>=6{exit 0} {exit 1}' && grep -q 'TestPool_Connects' internal/db/pool_test.go && grep -q 't.Skip' internal/db/pool_test.go && go test ./internal/web/ui/... -count=1 && (! go test -tags=red_gate ./internal/web/... 2>&1 | grep -q '^ok\s')</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `handlers_test.go` contains ≥6 `Test*` functions covering the behaviors listed
|
||||
- `pool_test.go` contains `TestPool_Connects` with a `t.Skip` branch for missing `DATABASE_URL`
|
||||
- Running `go test ./internal/web/` exits NON-ZERO at end of Plan 02 (handlers don't exist yet — this is the RED gate)
|
||||
- Running `go test ./internal/db/` exits 0 (skips cleanly when `DATABASE_URL` unset) — the db package compiles because Plan 03 will add `pool.go`; until then this test file references `db.NewPool` which won't exist either. The verify command above only requires the FILE to exist with `t.Skip` content — Plan 03's verify proves the test runs green.
|
||||
- Failure messages are informative (`undefined: web.NewRouter`) — this exact error is what Plan 03's executor will see and fix
|
||||
- Both test files start with `//go:build red_gate` on line 1 (Codex concern #3)
|
||||
- `go test ./...` (no tag) at the end of Plan 01-02 exits 0 — the red-gated files are excluded from default compilation
|
||||
- `go test -tags=red_gate ./internal/web/...` at the end of Plan 01-02 exits NON-ZERO with `undefined: web.NewRouter` (or similar) — proves the RED gate targets the right symbols
|
||||
- `go test ./internal/web/ui/...` exits 0 — the ui smoke tests are GREEN
|
||||
- Failure messages under `-tags=red_gate` are informative (`undefined: web.NewRouter`) — this exact error is what Plan 01-03's executor will see and fix
|
||||
</acceptance_criteria>
|
||||
<done>Failing tests are committed. Plan 03 inherits a concrete, behavior-specified target list — no exploration needed.</done>
|
||||
</task>
|
||||
|
|
@ -401,12 +418,13 @@ templ Badge(props BadgeProps)
|
|||
|
||||
<verification>
|
||||
- `cd backend && go build ./internal/web/ui/` succeeds
|
||||
- `cd backend && templ generate && go test ./internal/web/ui/ -count=1` exits 0 (all ui smoke tests GREEN)
|
||||
- `cd backend && go test ./internal/web/` exits NON-ZERO with `undefined: NewRouter` (or similar) — confirms RED gate is set for Plan 03
|
||||
- `cd backend && go test ./internal/db/` exits 0 (TestPool_Connects skips cleanly) OR exits non-zero with `undefined: db.NewPool` — either is acceptable here since Plan 03 lands `pool.go`
|
||||
- `cd backend && templ generate && go test ./internal/web/ui/... -count=1` exits 0 (all ui smoke tests GREEN — package-scoped)
|
||||
- `cd backend && go test ./... -count=1` exits 0 — the red-gated test files are excluded from default compilation (Codex concern #3); no leak into `go test ./...`
|
||||
- `cd backend && go test -tags=red_gate ./internal/web/... ./internal/db/... -count=1` exits NON-ZERO with `undefined: web.NewRouter` / `undefined: db.NewPool` — confirms the RED gate is set for Plan 01-03
|
||||
- All ten files under `internal/web/ui/` exist
|
||||
- All three test files exist and contain the required `Test*` functions
|
||||
- Both red-gated test files exist, start with `//go:build red_gate`, and contain the required `Test*` functions
|
||||
- `internal/web/ui/ui_test.go` covers Button (default+attrs), Card, Badge (info+success+zero-value)
|
||||
- `button.css` declares non-nested `.ui-button-solid-default-md:hover` and `:focus-visible` selectors (Codex concern #7)
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
|
|
@ -415,7 +433,8 @@ templ Badge(props BadgeProps)
|
|||
3. UI package CSS files are pickable by Tailwind input CSS imports (already wired in Plan 01)
|
||||
4. `internal/web/handlers_test.go` declares the exact test names from VALIDATION.md and targets the exact handler signatures Plan 03 will implement
|
||||
5. `internal/db/pool_test.go` declares `TestPool_Connects` with a graceful skip when `DATABASE_URL` is unset
|
||||
6. RED gate established: `go test ./internal/web/` fails because production handlers don't exist yet — this is intentional and is the target Plan 03 fills in
|
||||
6. RED gate established via `//go:build red_gate` (Codex concern #3): `go test -tags=red_gate ./internal/web/... ./internal/db/...` fails with `undefined: web.NewRouter` / `db.NewPool` — this is intentional and is the target Plan 01-03 fills in. Default `go test ./...` succeeds because the gated files are excluded.
|
||||
7. Component CSS uses non-nested selectors only (Codex concern #7): `.ui-button-solid-default-md:hover` and `:focus-visible` are top-level rules, not `&:hover` nested inside the base selector
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ tags:
|
|||
|
||||
must_haves:
|
||||
truths:
|
||||
- "After this plan lands, every test in `backend/internal/web/handlers_test.go` and `backend/internal/db/pool_test.go` is GREEN (the RED gate from Plan 02 is now turned over)"
|
||||
- "After this plan lands, every test in `backend/internal/web/handlers_test.go` and `backend/internal/db/pool_test.go` is GREEN under default `go test ./...` — the `//go:build red_gate` tags placed in Plan 01-02 are REMOVED in this plan's Task 1 (Codex concern #3). After removal, `go test ./...` (no tag) runs the full suite green."
|
||||
- "`go mod tidy` runs cleanly at the start of Task 1 (Codex concern #1 — deferred from Plan 01-01) and does NOT strip any of the five pinned runtime deps because real importers now exist in `internal/db/pool.go`, `internal/web/{router,handlers,middleware}.go`, and `cmd/{web,worker}/main.go`."
|
||||
- "`go build ./cmd/web ./cmd/worker` succeeds — both binaries compile"
|
||||
- "Running `./tmp/web` (or `go run ./cmd/web`) with `DATABASE_URL` pointing at the compose-managed Postgres serves `/` (200, HTML), `/healthz` (200, JSON with db:ok), `/demo/time` (200, HTML fragment), and `/static/*` (file server)"
|
||||
- "The root page is rendered via `internal/web/ui` components — handlers do NOT inline Tailwind classes — and contains the canonical `hx-get` button declared in UI-SPEC"
|
||||
|
|
@ -172,6 +173,11 @@ func NewPool(ctx context.Context, dsn string) (*pgxpool.Pool, error)
|
|||
- `web.SlogLoggerMiddleware(logger)` is a factory returning a `func(http.Handler) http.Handler` middleware that logs one structured line per request (method, path, status, duration, request_id) — uses `chi/v5/middleware.WrapResponseWriter` to capture status.
|
||||
</behavior>
|
||||
<action>
|
||||
**Pre-step (Codex concerns #1 and #3).** Before writing any new code, perform two cleanup steps:
|
||||
|
||||
1. **Remove the `//go:build red_gate` line** (and the blank line immediately after it) from BOTH `backend/internal/web/handlers_test.go` and `backend/internal/db/pool_test.go`. Plan 01-02 placed these tags so the failing tests would not leak into default `go test ./...` runs; this plan turns them GREEN, so the gate is no longer needed.
|
||||
2. **Now that consumer code is about to exist**, the runtime deps pinned in Plan 01-01 will have real importers in this plan. After Task 1's files are written, run `cd backend && go mod tidy` — it should be a no-op on the require list (Codex concern #1: tidy was deferred to here precisely so this is safe).
|
||||
|
||||
Write `internal/db/pool.go` per RESEARCH Pattern 1 exactly. Use `github.com/jackc/pgx/v5/pgxpool`. The function MUST NOT call `pool.Ping` (Pitfall 2 — lazy is correct).
|
||||
|
||||
Write `internal/web/slog.go`: declare `NewSlogHandler(env string, w io.Writer) slog.Handler`. Use `slog.HandlerOptions{Level: slog.LevelInfo}`. Switch by `env == "production"` per RESEARCH Pattern 3. Do NOT call `slog.SetDefault` here — that's `cmd/web`'s job. The function returns the handler, callers decide what to do with it.
|
||||
|
|
@ -185,9 +191,11 @@ func NewPool(ctx context.Context, dsn string) (*pgxpool.Pool, error)
|
|||
All three files: package clause matches directory (`package db` for pool.go; `package web` for the other two).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd backend && templ generate && go build ./internal/db/ ./internal/web/ && go test ./internal/web/ -run 'TestRequestID_HeaderSet|TestSlog_HandlerSwitch' -count=1</automated>
|
||||
<automated>cd backend && ! grep -q '^//go:build red_gate' internal/web/handlers_test.go && ! grep -q '^//go:build red_gate' internal/db/pool_test.go && go mod tidy && templ generate && go build ./internal/db/ ./internal/web/ && go test ./internal/web/ -run 'TestRequestID_HeaderSet|TestSlog_HandlerSwitch' -count=1</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `//go:build red_gate` line is removed from both `internal/web/handlers_test.go` and `internal/db/pool_test.go` (Codex concern #3 — RED gate retired)
|
||||
- `go mod tidy` runs cleanly and produces no removal of the five pinned runtime deps (Codex concern #1 — deferred tidy from Plan 01-01 is safe here)
|
||||
- `go build ./internal/db/ ./internal/web/` succeeds (no missing symbols from Plan 02 tests)
|
||||
- `TestRequestID_HeaderSet` passes: response carries a UUIDv4 in `X-Request-ID`
|
||||
- `TestSlog_HandlerSwitch` passes: production handler emits JSON, dev handler emits text
|
||||
|
|
@ -387,7 +395,7 @@ func NewPool(ctx context.Context, dsn string) (*pgxpool.Pool, error)
|
|||
- `cd backend && go build ./cmd/web ./cmd/worker` produces `bin/web` (if `just build`) or compiles cleanly via `go build`
|
||||
- Checkpoint Task 5 walks the full Walking Skeleton: dev loop, browser interaction, graceful shutdown, worker boot
|
||||
- No `chi/v5/middleware.Logger` import anywhere (Pitfall 6): `grep -r 'middleware.Logger' backend/` returns empty
|
||||
- No CDN reference anywhere: `grep -r 'unpkg.com\|cdn\.' backend/internal backend/templates backend/cmd` returns empty (justfile's bootstrap-time curl is the only allowed exception, lives in Plan 01)
|
||||
- No runtime CDN reference anywhere in served code (Codex concerns #4 and #5): `grep -r 'unpkg.com\|cdn\.' backend/internal backend/templates backend/cmd backend/static/htmx.min.js` returns empty in the `internal/`, `templates/`, and `cmd/` paths. The justfile's bootstrap-time download URLs (Tailwind GitHub release URL, HTMX unpkg URL) are the explicit allowed exceptions and live ONLY in `backend/justfile`.
|
||||
- No raw `<button class="bg-blue-`anywhere in templates/: `grep -r 'class="bg-blue-' backend/templates/` returns empty (pages consume `ui.Button`, never inline)
|
||||
</verification>
|
||||
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ Output: One file (`backend/README.md`) and a successful clean-clone walkthrough.
|
|||
6. **Environment variables.** Table of the three keys from `.env.example`: `DATABASE_URL`, `PORT`, `ENV` — with one-line descriptions and default values. Note `.env` is gitignored; `.env.example` is committed.
|
||||
|
||||
7. **Common commands.** Table mapping recipe → what it does → when to use it:
|
||||
- `just bootstrap` — install dev tools + vendored assets (one-time per clone)
|
||||
- `just bootstrap` — install dev tools + bootstrap-downloaded assets (one-time per clone)
|
||||
- `just db-up` / `just db-down` — start/stop local Postgres
|
||||
- `just migrate up` / `migrate down` / `migrate status` — apply / revert / inspect migrations
|
||||
- `just generate` — runs `templ generate`, `sqlc generate`, Tailwind compile (one-shot)
|
||||
|
|
@ -131,7 +131,10 @@ Output: One file (`backend/README.md`) and a successful clean-clone walkthrough.
|
|||
|
||||
Hard constraints:
|
||||
- Keep the file under ~300 lines. README is a quickstart, not an architecture doc.
|
||||
- Every command shown must match a recipe that actually exists in `backend/justfile` — no aspirational commands.
|
||||
- Every command shown must match a recipe that actually exists in `backend/justfile` — no aspirational commands (Codex concern #6 — `generate`, `test`, `build`, `dev` are scaffolded in Plan 01-01 but become end-to-end runnable only after Plan 01-03; the README quickstart sequence is the order in which they first become useful and is correct as written).
|
||||
- Use "bootstrap-downloaded" wording (NOT "vendored") for `bin/tailwindcss` and `static/htmx.min.js` — both are reproduced by `just bootstrap` from pinned URLs in the justfile and are gitignored, not committed (Codex concern #4).
|
||||
- When discussing HTMX hosting, clarify: "HTMX is served from `/static/htmx.min.js` at runtime — no CDN. The justfile's bootstrap-time `unpkg.com` URL is the single authoritative version pin." (Codex concern #5).
|
||||
- `just clean` IS a real recipe (added in Plan 01-01 per Codex concern #10); Method B in the onboarding checkpoint references it.
|
||||
- No emoji. No marketing voice (UI-SPEC tone rules — "friendly, declarative, no marketing").
|
||||
- No screenshots in Phase 1. Plain text + code blocks only.
|
||||
</action>
|
||||
|
|
@ -172,7 +175,7 @@ Output: One file (`backend/README.md`) and a successful clean-clone walkthrough.
|
|||
8. Stop here. Do not commit to the sibling clone. `cd ..` and `rm -rf xtablo-source-onboarding-test`.
|
||||
|
||||
**Method B — In-place dry run (acceptable fallback):**
|
||||
1. From a clean working tree in `backend/`, run `just clean` (or manually delete `bin/`, `tmp/`, `static/tailwind.css`, `static/htmx.min.js`, `*_templ.go`).
|
||||
1. From a clean working tree in `backend/`, run `just clean` (the recipe was added to the justfile in Plan 01-01 per Codex concern #10; it removes `bin/`, `tmp/`, `static/tailwind.css`, `static/htmx.min.js`, and `*_templ.go` files — leaves the Postgres volume intact, run `just db-down` separately if a DB reset is needed).
|
||||
2. Open `README.md` and follow the Quickstart literally.
|
||||
3. Same time + ambiguity checks as Method A.
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ A developer can clone the repo, run `just bootstrap`, `podman compose up -d`, `j
|
|||
| Language / runtime | Go ≥ 1.22 (developer machine has 1.26) | No JS framework / no BaaS thesis; chi v5 requires 1.22 |
|
||||
| HTTP router | `github.com/go-chi/chi/v5` v5.2.5 | Idiomatic, standard middleware set (CONTEXT D-08) |
|
||||
| HTML templating | `github.com/a-h/templ` v0.3.1020 | Compile-time type checking; first-class HTMX fragment story (D-07) |
|
||||
| Client interactivity | HTMX v2.x (vendored at `static/htmx.min.js`) | "No JS framework" — HTMX swaps server-rendered fragments (D-10) |
|
||||
| Client interactivity | HTMX v2.x (bootstrap-downloaded to `static/htmx.min.js` by `just bootstrap`; gitignored, never committed) | "No JS framework" — HTMX swaps server-rendered fragments (D-10) |
|
||||
| CSS | Tailwind v4 standalone CLI binary in `./bin/tailwindcss` | Avoids any Node toolchain in `backend/` (D-12) |
|
||||
| DB driver | `github.com/jackc/pgx/v5` + `pgxpool` v5.9.2 | sqlc-recommended, richer pg types (D-16) |
|
||||
| Migrations | `github.com/pressly/goose/v3` v3.27.1 (CLI in Phase 1) | Embeddable library, one `.sql` per migration, sqlc-compatible (D-04, D-05) |
|
||||
|
|
@ -68,7 +68,7 @@ backend/
|
|||
index.templ
|
||||
fragments.templ
|
||||
static/
|
||||
htmx.min.js (vendored, not CDN)
|
||||
htmx.min.js (bootstrap-downloaded by `just bootstrap`; gitignored; no runtime CDN)
|
||||
tailwind.css (generated by Tailwind standalone CLI)
|
||||
bin/ (gitignored — tailwindcss, air, etc.)
|
||||
.air.toml
|
||||
|
|
|
|||
Loading…
Reference in a new issue