- Five-minute clone-to-page onboarding doc covering prereqs, bootstrap, two-terminal dev workflow, common commands, and troubleshooting. - Documents docker compose fallback for contributors not on podman (D-11). - Documents two-terminal workflow: just dev + just styles-watch (D-14). - Every `just <recipe>` referenced is a real recipe in backend/justfile. - Uses 'bootstrap-downloaded' wording (not 'vendored') for tailwindcss and htmx.min.js per Codex review concern #4. - Clarifies HTMX runtime no-CDN policy; the unpkg URL in justfile is the single authoritative version pin (D-10 / Codex concern #5). - Top 3 pitfalls from RESEARCH surfaced in Troubleshooting. - 195 lines, no emoji, no marketing voice. |
||
|---|---|---|
| .. | ||
| bin | ||
| cmd | ||
| internal | ||
| migrations | ||
| static | ||
| templates | ||
| .air.toml | ||
| .env.example | ||
| .gitignore | ||
| compose.yaml | ||
| go.mod | ||
| go.sum | ||
| justfile | ||
| README.md | ||
| sqlc.yaml | ||
| tailwind.input.css | ||
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.moddeclares 1.26) - just — task runner (
brew install juston macOS,cargo install just, or see https://github.com/casey/just) - podman with
podman compose(preferred per D-11) or docker withdocker 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 composewithdocker composementally throughout this README. - The
just db-up/just db-downrecipes callpodman composedirectly. Rundocker compose up -d postgres/docker compose downinstead, 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.gofiles from.templsources, and those generated files are not committed. Runjust generate(orjust dev, which calls it) before invokinggo builddirectly. (Pitfall 1.) -
"First request to
/healthzreturns 503 right afterjust db-up" — The Postgres container needs ~5–10 seconds to become healthy afterpodman compose up -dreturns. Checkpodman compose ps(ordocker compose ps) for thehealthystatus, 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
.templfiles don't appear in the compiled CSS" — Tailwind v4 only scans content paths declared via@sourceintailwind.input.css. Confirm the file contains@source "../templates/**/*.templ";(and equivalent globs forinternal/web/**/*.go). Re-runjust styles-watchso 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_isreadyhealthcheck) - goose migration pipeline (
migrations/0001_init.sqlis 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-getround-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/workerskeleton (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