# Xtablo backend Go + HTMX + Postgres. Phase 1: Walking Skeleton. This README is the contract for FOUND-05: a developer with the prerequisites below should be able to clone the repo, follow the Quickstart, and see the HTMX-driven page within ~5 minutes. ## Prerequisites Install these on your dev machine before starting: - **Go** ≥ 1.22 (this project's `go.mod` declares 1.26) - **just** — task runner (`brew install just` on macOS, `cargo install just`, or see ) - **podman** with `podman compose` (preferred per D-11) **or** **docker** with `docker compose` - **curl** - **git** You do **not** need to install `goose`, `templ`, `sqlc`, `air`, the Tailwind CLI, or `htmx.min.js` — `just bootstrap` installs the Go tools into `$GOBIN` and bootstrap-downloads the Tailwind binary and HTMX script into local, gitignored paths. ## Quickstart Clone-to-running-page in ~5 minutes. Run from inside `backend/`. ``` cd backend cp .env.example .env # adjust DATABASE_URL if Postgres is not on localhost:5432 just bootstrap # installs goose/templ/sqlc/air; bootstrap-downloads tailwindcss + htmx.min.js just db-up # starts postgres via podman compose (see fallback below) just migrate up # applies migrations from ./migrations just dev # terminal 1: brings up db, runs generate, then air on :8080 # in a SECOND terminal: just styles-watch # rebuilds static/tailwind.css on .templ / .go changes # open http://localhost:8080 ``` The page should render with a "Fetch server time" button. Clicking it swaps an ISO-8601 timestamp into the page via HTMX. If the page shows "No time fetched yet." and nothing happens on click, see Troubleshooting. `bootstrap` is the slowest step (Go tool installs + two HTTP downloads). It only needs to run once per clone. ## docker compose fallback `compose.yaml` is portable across podman and docker — the service definition is identical. If you don't have podman: - Replace `podman compose` with `docker compose` mentally throughout this README. - The `just db-up` / `just db-down` recipes call `podman compose` directly. Run `docker compose up -d postgres` / `docker compose down` instead, and continue with the rest of the Quickstart unchanged. (Decision D-11.) ## Project layout ``` backend/ cmd/ web/main.go # HTTP server entry point worker/main.go # background worker (skeleton — boot/log/shutdown only) internal/ db/ # pgxpool wiring + sqlc-generated queries web/ # chi router, handlers, middleware, design-system ui/ # custom templ component library (Button, Card, Badge) session/ # placeholder — Phase 2 tablos/ # placeholder — Phase 3 tasks/ # placeholder — Phase 4 files/ # placeholder — Phase 5 migrations/ # goose .sql migrations templates/ # .templ files (layout, index, fragments) static/ htmx.min.js # bootstrap-downloaded by `just bootstrap`; gitignored; no runtime CDN tailwind.css # generated by the Tailwind standalone CLI bin/ # gitignored — tailwindcss CLI binary, etc. .air.toml # air live-reload config .env.example # committed; copy to .env compose.yaml # local Postgres go.mod / go.sum justfile # task runner recipes — the source of truth for commands sqlc.yaml tailwind.input.css README.md ``` 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 (D-10). ## Environment variables `backend/.env` is gitignored; `backend/.env.example` is committed and lists the three keys consumed by `cmd/web` (and `cmd/worker` for `DATABASE_URL`): | Variable | Description | Default | | -------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------- | | `DATABASE_URL` | Postgres DSN used by the web + worker binaries and by `just migrate` | `postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable` | | `PORT` | HTTP port for `cmd/web` | `8080` | | `ENV` | `development` enables slog's text handler; `production` switches to JSON | `development` | ## Common commands Every command in this table is a recipe in `backend/justfile`. | Recipe | What it does | When to use | | ----------------------------------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------- | | `just bootstrap` | Installs Go CLI tools (`goose`, `templ`, `sqlc`, `air`); bootstrap-downloads `bin/tailwindcss` and `static/htmx.min.js` | Once per clone; re-run after deleting `bin/` or `static/htmx.min.js` | | `just db-up` | Starts the local Postgres container | Before `just migrate up` / `just dev` if not already running | | `just db-down` | Stops the local Postgres container | When you're done for the day | | `just migrate up` / `migrate down` / `migrate status` | Applies / reverts / inspects goose migrations against `DATABASE_URL` | After `just db-up`, or any time you change `migrations/` | | `just generate` | One-shot: `templ generate`, `sqlc generate`, Tailwind compile to `static/tailwind.css` | After editing `.templ`, query SQL, or `tailwind.input.css` | | `just styles-watch` | Tailwind standalone CLI in `--watch` mode | In a second terminal alongside `just dev` (D-14) | | `just dev` | Brings up Postgres, runs `just generate`, then runs `air` for Go live-reload on `:8080` | Main dev loop, terminal 1 | | `just test` | `templ generate` then `go test ./...` | Before committing | | `just lint` | `go vet ./...` and `gofmt -l` check | Before committing | | `just build` | Generates assets, then builds `bin/web` and `bin/worker` | Producing release binaries locally | | `just clean` | Removes `bin/`, `tmp/`, `static/htmx.min.js`, `static/tailwind.css`, and `*_templ.go` files | Reset to a fresh-clone state without dropping the Postgres volume | ## Worker (skeleton — Phase 1 only) `cmd/worker` in Phase 1 boots, logs `worker ready`, and idles waiting for a signal. Real job runtime lands in Phase 6 (D-03). To run it manually: ``` DATABASE_URL=postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable \ go run ./cmd/worker ``` Ctrl-C to exit. ## Troubleshooting The three issues most likely to trip you up on a fresh clone: - **"Fresh clone fails to build with `undefined: templates.Index`"** — Templ generates `*_templ.go` files from `.templ` sources, and those generated files are not committed. Run `just generate` (or `just dev`, which calls it) before invoking `go build` directly. (Pitfall 1.) - **"First request to `/healthz` returns 503 right after `just db-up`"** — The Postgres container needs ~5–10 seconds to become healthy after `podman compose up -d` returns. Check `podman compose ps` (or `docker compose ps`) for the `healthy` status, or just wait and retry. Subsequent calls succeed. The 503 during warm-up is correct behavior, not a bug. (Pitfall 2.) - **"Tailwind classes used in `.templ` files don't appear in the compiled CSS"** — Tailwind v4 only scans content paths declared via `@source` in `tailwind.input.css`. Confirm the file contains `@source "../templates/**/*.templ";` (and equivalent globs for `internal/web/**/*.go`). Re-run `just styles-watch` so the watcher picks up the config change. (Pitfall 3.) If something else is wrong and you want a clean slate without dropping the Postgres volume: ``` just clean # removes bin/, tmp/, static/htmx.min.js, static/tailwind.css, *_templ.go just bootstrap # re-download tools and assets just dev # back to a working state ``` Run `just db-down` first if you also want to drop the Postgres container. ## What Phase 1 ships (and doesn't) **Ships:** - Project scaffold (`go.mod`, justfile, `.air.toml`, `tailwind.input.css`, `sqlc.yaml`, `compose.yaml`) - Local Postgres via `compose.yaml` (`pg_isready` healthcheck) - goose migration pipeline (`migrations/0001_init.sql` is a no-op bootstrap) - chi router with `/`, `/healthz`, `/demo/time`, `/static/*` - slog-based structured logging with RequestID middleware - Graceful HTTP shutdown - pgxpool wiring exercised by `/healthz` - templ + HTMX demo (root page + `hx-get` round-trip to a templ fragment) - Custom templ design-system package at `internal/web/ui/` (Button, Card, Badge) - Live-reload dev loop (`just dev` + `just styles-watch`) - `cmd/worker` skeleton (boot, log, idle, shutdown) **Does not ship — deferred:** - Authentication, sessions, users → Phase 2 - Tablos CRUD → Phase 3 - Tasks / kanban → Phase 4 - File uploads + R2/S3 → Phase 5 - Real worker jobs → Phase 6 - Production deploy, Dockerfile, `/readyz` → Phase 7