Phase 0: backend + frontend scaffold, single-binary build
Gadfly review (reusable) / review (pull_request) Successful in 10m8s
Adversarial Review (Gadfly) / review (pull_request) Successful in 10m9s

Implements the Pansy v1 scaffold (epic #20, phase 0).

#1 Backend: Go module (pure-Go, builds with CGO_ENABLED=0), env config,
   gin server with slog + recovery + /api/v1/healthz, modernc SQLite store
   with WAL/busy_timeout/foreign_keys pragmas, embedded numbered-migration
   runner, full initial schema (users, sessions, gardens, garden_shares,
   garden_objects, plants, plantings), domain structs + sentinel errors,
   graceful shutdown.

#2 Frontend: Vite + React 19 + TS (strict) + Tailwind 4 + TanStack
   Router/Query, typed /api/v1 fetch wrapper (ApiError carries status +
   body so later issues can read 409 conflict rows), dev proxy /api -> :8080,
   responsive app shell with stub pages for all five routes.

#3 Single binary: //go:embed of the web build with a committed placeholder,
   SPA fallback (deep links, immutable asset caching, JSON 404 for unmatched
   /api and missing assets), Makefile (web/build/dev/test), README quickstart
   + env var table.

Verified: go build/vet/test clean (CGO off); binary migrates idempotently and
serves healthz; web tsc + build clean; integrated binary serves the SPA, deep
links, and correct cache headers; app mounts and navigates all five routes at
desktop and 375px widths.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01JdQpdYYsTgtkJBxbcpAszi
This commit is contained in:
2026-07-18 14:52:05 -04:00
co-authored by Claude Opus 4.8
parent 76d18da542
commit 91da9ff945
36 changed files with 4578 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
import { Link, Outlet } from '@tanstack/react-router'
import { cn } from '@/lib/cn'
const navLinks = [
{ to: '/gardens', label: 'Gardens' },
{ to: '/plants', label: 'Plants' },
] as const
/** Top-level chrome: a sticky nav bar plus the routed page in an <Outlet>. */
export function AppShell() {
return (
<div className="flex min-h-full flex-col">
<header className="sticky top-0 z-10 border-b border-border bg-surface/90 backdrop-blur">
<nav className="mx-auto flex max-w-5xl items-center gap-4 px-4 py-3">
<Link to="/gardens" className="text-lg font-semibold text-accent-strong">
🌱 pansy
</Link>
<div className="flex flex-1 items-center gap-1">
{navLinks.map((l) => (
<Link
key={l.to}
to={l.to}
className={cn(
'rounded-md px-3 py-1.5 text-sm font-medium text-muted transition-colors hover:bg-border/60 hover:text-fg',
)}
activeProps={{ className: 'bg-border/60 text-fg' }}
>
{l.label}
</Link>
))}
</div>
<Link
to="/login"
className="rounded-md px-3 py-1.5 text-sm font-medium text-muted transition-colors hover:text-fg"
>
Sign in
</Link>
</nav>
</header>
<main className="mx-auto w-full max-w-5xl flex-1 px-4 py-6">
<Outlet />
</main>
</div>
)
}