# Walking Skeleton — Xtablo Go+HTMX Rewrite

**Phase:** 1
**Generated:** 2026-05-14

## Capability Proven End-to-End

A developer can clone the repo, run `just bootstrap`, `podman compose up -d`, `just migrate up`, and `just dev`, then load `http://localhost:8080/` in a browser, click "Fetch server time", and see an HTML fragment returned by the Go server — exercising templ rendering, chi routing, HTMX swap, static asset serving, slog logging, and a `/healthz` endpoint that calls `pgxpool.Ping` against the goose-migrated local Postgres.

## Architectural Decisions

| Decision | Choice | Rationale |
|---|---|---|
| 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 (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) |
| Query generation | `sqlc-dev/sqlc` v1.31.1 (configured; no queries yet) | Type-safe SQL without ORM (foundation for Phase 2+) |
| Logging | `log/slog` stdlib — JSON in prod, text in dev | Stdlib, no external dep (D-17) |
| Request IDs | `github.com/google/uuid` v1.6.0 via custom middleware | RFC 4122 UUIDs threaded through `context.Context` to slog (D-18) |
| Live reload | `github.com/air-verse/air` v1.65.1 | Watches `.go` + `.templ`; pre_cmd runs `templ generate` |
| Local Postgres | `podman compose` with `postgres:16-alpine` at `backend/compose.yaml` | Developer machine standard (D-11) |
| Local task runner | `just` | Standard across the repo |
| Deployment target | Single VPS / single container (deferred to Phase 7) | Locked in PROJECT.md |
| Auth | Server-managed sessions, HTTP-only cookies (Phase 2) | "No JWT, no third-party auth" |
| Directory layout | Two-binary `cmd/web` + `cmd/worker`, shared `internal/` (D-01, D-02, D-03) | See below |
| Design-system | Custom templ component package at `backend/internal/web/ui/` (Button, Card, Badge in Phase 1) | UI-SPEC contract; mirrors `go-backend/internal/web/ui/` enum surface |

### Directory layout (locked)

```
backend/
  cmd/
    web/main.go
    worker/main.go
  internal/
    db/                  (pgxpool wiring + sqlc-generated queries)
      doc.go
      pool.go
    web/
      router.go
      handlers.go
      middleware.go
      handlers_test.go
      ui/                (custom templ design-system)
        tokens.go
        variants.go
        helpers.go
        base.css
        button.templ
        button.css
        card.templ
        card.css
        badge.templ
        badge.css
        ui_test.go
    session/doc.go       (placeholder — Phase 2)
    tablos/doc.go        (placeholder — Phase 3)
    tasks/doc.go         (placeholder — Phase 4)
    files/doc.go         (placeholder — Phase 5)
  migrations/
    0001_init.sql        (no-op bootstrap; real schema lands Phase 2)
  templates/
    layout.templ
    index.templ
    fragments.templ
  static/
    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
  .env.example
  .gitignore
  compose.yaml
  go.mod / go.sum
  justfile
  sqlc.yaml
  tailwind.input.css
  README.md
```

## Stack Touched in Phase 1

- [x] Project scaffold (`go mod init backend`, justfile, `.air.toml`, `tailwind.input.css`, `sqlc.yaml`, `compose.yaml`)
- [x] Routing — chi router with `/`, `/healthz`, `/demo/time`, `/static/*`
- [x] Database — `pgxpool.New(DATABASE_URL)` + `pool.Ping(ctx)` exercised by `/healthz`; one no-op `goose up` migration applied
- [x] UI — `index.templ` rendered via `internal/web/ui` design-system (Button + Card), HTMX `hx-get` round-trip to `/demo/time` returning a templ fragment
- [x] Deployment — local-run only in Phase 1 (Phase 7 owns container/VPS deploy). `just dev` is the documented full-stack run command.

## Out of Scope (Deferred to Later Slices)

- Authentication, sessions, users (Phase 2)
- Tablos CRUD (Phase 3)
- Tasks / kanban board (Phase 4)
- File uploads + S3/R2 (Phase 5)
- Real background jobs (Phase 6 — `cmd/worker` in Phase 1 is boot/log/shutdown only)
- Production deploy, Dockerfile, `/readyz` (Phase 7)
- Dark mode, icon library, web fonts (UI-SPEC defers these)
- Form components (`Input`, `Textarea`, `Select`, `FormField`) — extend `internal/web/ui` in Phase 2
- Modal / Table / EmptyState components — extend `internal/web/ui` in Phase 3
- CSRF tokens, rate limiting (Phase 2)
- Embedded `goose` library call from app startup (Phase 7)
- CI configuration (no DEPLOY-XX requirement in Phase 1)

## Subsequent Slice Plan

Each later phase adds one vertical slice on top of this skeleton without altering its architectural decisions:

- Phase 2: User can sign up, log in (email + password, argon2/bcrypt), receive a session cookie, and stay logged in. Extends `internal/web/ui` with `Input`, `FormField`. Adds `users` + `sessions` tables.
- Phase 3: Authenticated user can list / create / view / edit / delete tablos. Extends `internal/web/ui` with `Modal`, `Table`, `EmptyState`, `IconButton`.
- Phase 4: User can run a kanban board inside a tablo (create/edit/move/reorder/delete tasks). Drag-and-drop or button-based reorder TBD.
- Phase 5: User can attach files, list, download (signed URL), delete — backed by R2/S3.
- Phase 6: `cmd/worker` runs a real job queue (river/asynq/pg_notify TBD) against the same Postgres.
- Phase 7: Both binaries ship in a single multi-stage Docker image, deployed to a single VPS / Cloud Run-style host with migrations applied on deploy.
