Files
pastilhasandClaude Opus 5 c08dab4243 Initial commit: Bun · Hono · React SPA · Postgres/Drizzle template
The starting point for an app with a React frontend. Same server, env,
database and API skeleton as template-htmx — the two differ only in what is
served at / — with a React 19 SPA, Tailwind v4 and shadcn/ui on top.

Carried from a working project rather than designed in the abstract, and
carrying the lessons that cost it time:

- The dev script states NODE_ENV=development rather than inheriting it. React
  resolves its runtime against NODE_ENV: Bun emits jsxDEV() in development
  and React's production jsx-dev-runtime defines jsxDEV as void 0. Inherit
  production while transforming for development and the app dies at first
  paint. Verified: the dev bundle resolves the development runtime and
  contains no void-0 stub.
- Asset URLs in components are relative and <base href="/"> is in the HTML,
  so a path-prefix proxy can rewrite one attribute and every asset follows. A
  test enforces it.
- Routing is in the hash, because the app may be mounted under a prefix it
  only learns at runtime and a hash means the same thing at either mount.
- index.html holds __PUBLIC_URL__ and gen-index.ts substitutes it, because
  Bun's HTML bundler fails the build on a root-relative favicon href.
- APP_ENV failing closed, env files before the ambient environment, the
  database enforced by name, one Postgres client per process — as in the
  htmx template.

The database has no tables. The App is a shell with one tab and no features,
named as a placeholder. Renaming `app` is the first step and the README lists
every place it appears.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 02:02:52 +00:00

52 lines
2.3 KiB
Markdown

# app
A project started from `template-react`. **Rename `app` first** — README step 1.
**Bun · Hono · React 19 · Tailwind v4 · shadcn/ui · Postgres with Drizzle.**
## Read these before writing code
| | |
|---|---|
| `CONVENTIONS.md` | React patterns — feature folders, manager hooks, colocated state — and layout |
| `docs/DATABASE.md` | Drizzle structure, `push` not migrations, `check()` vs open `text` |
| `docs/SURFACES.md` | The JSON API and the app: one process, one query layer |
| `README.md` | Setup, scripts, configuration |
## Rules that are load-bearing
**`APP_ENV`, never `NODE_ENV`.** Must be `production` or `development`; throws when unset. Hosts export
`NODE_ENV=production` into everything they spawn, so anything keyed on it treats development as
production silently. `NODE_ENV` is still set explicitly by both scripts because React's runtime
resolves against it — Bun emits `jsxDEV()` in development and React's production runtime defines it
as `void 0`.
**Env files are read with the ambient environment LAST**: `.env.production.local` (production only)
→ `.env.local` → `.env` → `process.env`.
**Asset URLs in components are relative — never start with `/`.** `<base href="/">` in `index.html`
is what a path-prefix proxy rewrites; a leading slash bypasses it. `asset-urls.test.ts` enforces this.
`fetch('/api/...')` is the exception.
**Routing is in the hash.** The app may be mounted under a prefix it only learns at runtime, and a
hash means the same thing at either mount point. `lib/routes.ts`.
**One feature folder per screen, one manager hook per feature.** State lives in the hook, components
receive the manager. `CONVENTIONS.md`.
**Serializers live in `src/servers/serialize.ts` only.** Its test walks the module's exports for
camelCase keys and for serializers without a case.
## Bun
- `bun <file>` not `node`, `bun test` not jest/vitest, `bun install`, `bunx`
- `Bun.serve()` — not express. `Bun.file` over `node:fs`. `Bun.$` over execa
- Bun loads `.env` automatically; do not add dotenv
- `bun dev` and `bun start` set the env markers. Running `src/server.ts` directly throws, by design
## Before you finish
- `bunx tsc --noEmit` and `bun test`
- Verify claims rather than asserting them — start the server and request the thing
- Commit with a message that says *why*, not what the diff already shows