Files
spa/.planning/phase-1-foundation/02-stack-rounding-out.md
T
julian 3c7033c3f3 feat: task 1.3 vite dev proxy + tsconfig hardening
Same-origin dev proxy via Vite's server.proxy:
- /api/*       -> ${VITE_DEV_DIRECTUS_URL}/*   (Directus REST + GraphQL)
- /ws-business -> ws://.../websocket           (Directus business-plane WS)
- /ws-live     -> ${VITE_DEV_PROCESSOR_WS_URL} (Processor live WS, Phase 1.5)

WS proxy targets derive ws:// scheme from the http(s) Directus URL.
loadEnv reads VITE_DEV_* overrides; sensible localhost defaults.

tsconfig.app.json: noUncheckedIndexedAccess + noImplicitOverride.

.env.example documents the two override vars; .env.local is gitignored
via the existing *.local pattern.

README "Local dev" section: proxy table + override instructions + port
collision workaround.

Deviation: spec called for .env.dev.example/.env.dev.local but Vite's
dev mode is "development" not "dev", so mode-specific files would be
.env.development.* and Vite wouldn't auto-load .env.dev.local. Switched
to the standard Vite .env.example/.env.local convention. Documented in
the task's Done section.

Plus: backfill 1.2 commit SHA (9918418) in its Done section.
2026-05-02 18:42:22 +02:00

111 lines
7.5 KiB
Markdown

# Task 1.2 — Stack rounding-out (Tailwind + shadcn/ui + deps + Prettier)
**Phase:** 1 — Foundation
**Status:** ⬜ Not started
**Depends on:** 1.1 (scaffold)
**Wiki refs:** `docs/wiki/entities/react-spa.md` §Stack
## Goal
Take the bare Vite + React + TS scaffold from 1.1 and round it out into a real app stack: Tailwind CSS, shadcn/ui primitives, TanStack Router + Query, Zustand, `@directus/sdk`, zod, react-hook-form, Prettier. After this task, every dependency the rest of Phase 1 needs is installed and configured; subsequent tasks build features on top.
This is mechanical setup, not feature work. Don't introduce features here — keep the placeholder `App.tsx` returning "SPA" until 1.7 wires the router.
## Deliverables
- **Tailwind CSS 4 via the Vite plugin:**
- `pnpm add -D tailwindcss @tailwindcss/vite`
- `vite.config.ts` updated to include the Tailwind plugin (additive; don't disturb the React plugin).
- `src/styles/globals.css` with `@import "tailwindcss";` (Tailwind 4 syntax — single import, no `@tailwind base/components/utilities` triplet).
- `src/main.tsx` imports `./styles/globals.css`.
- Sanity check: `<div className="bg-red-500 p-4">test</div>` in `App.tsx` renders red.
- **shadcn/ui initialised:**
- `npx shadcn@latest init` — accept the defaults that fit our setup (Tailwind 4, TS, `src/ui/primitives` as the `components` path, `src/lib/utils.ts` for `cn`).
- `components.json` committed.
- Add the bare-minimum primitives we'll need in 1.6 (login page) and 1.7 (routing): `button`, `input`, `label`, `form`, `card`, `alert`. Run `npx shadcn@latest add button input label form card alert` — these populate `src/ui/primitives/` as plain TS files we own and edit.
- **TanStack Router + Query:**
- `pnpm add @tanstack/react-router @tanstack/react-query`
- `pnpm add -D @tanstack/router-plugin @tanstack/router-devtools @tanstack/react-query-devtools`
- Don't wire them yet — that's 1.7. Just install.
- **State + data libs:**
- `pnpm add zustand @directus/sdk zod react-hook-form @hookform/resolvers`
- **Prettier + ESLint integration:**
- `pnpm add -D prettier eslint-config-prettier eslint-plugin-prettier`
- `.prettierrc` with sensible defaults (single quotes, semi: true, printWidth: 100, trailingComma: 'all').
- `.prettierignore` matching `.gitignore` plus `pnpm-lock.yaml`, `dist/`, `public/`.
- `eslint.config.js` extended to include `eslint-config-prettier` last (turns off conflicting rules) and the prettier plugin's recommended config.
- `package.json` `scripts`:
- `"format": "prettier --write ."`
- `"format:check": "prettier --check ."`
- `"typecheck": "tsc -b --noEmit"` (replaces the implicit typecheck inside the existing `build` script for a faster CI gate).
- `README.md` updated with: stack list, common scripts, "how to add a shadcn component."
## Specification
### Why Tailwind 4 (the Vite-plugin path)
Tailwind 4 ships a Vite plugin that does the JIT generation in-process — no PostCSS dance, no `@tailwind` directives, single `@import "tailwindcss";` in the entry CSS. Faster, fewer config files, the modern path. The shadcn/ui CLI auto-detects Tailwind 4 and configures correctly.
### Why a separate `src/lib/` for `cn` and `query-client.ts`
shadcn/ui expects `src/lib/utils.ts` to exist (it imports `cn` from there in every primitive). The `query-client.ts` (in 1.5) lives alongside it — both are framework-glue, not feature code.
### What we don't install yet
- **MapLibre GL JS** — Phase 2.
- **`@mapbox/mapbox-gl-draw`** — deferred until geometry editing in Phase 4.
- **`maplibre-google-maps`** — Phase 2, optional / runtime-config-gated.
- **Vitest + React Testing Library** — Phase 3.
- **i18n libraries** — deferred (English-only for dogfood).
### Prettier ↔ ESLint coexistence
ESLint 10 with the `typescript-eslint` flat config has different opinions from Prettier on quote style and trailing commas. The standard fix is: `eslint-config-prettier` last in the chain (disables the conflicting style rules), `eslint-plugin-prettier` in `recommended` mode (so ESLint reports Prettier diffs as ESLint errors, single source of truth in CI).
Don't try to make ESLint also do formatting — Prettier owns formatting, ESLint owns lint. The two-tool split is the boring-correct setup.
### `package.json` final shape (illustrative)
```json
{
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview",
"lint": "eslint .",
"typecheck": "tsc -b --noEmit",
"format": "prettier --write .",
"format:check": "prettier --check ."
}
}
```
CI (task 1.9) will run `typecheck`, `lint`, `format:check`, `build` in that order.
## Acceptance criteria
- [ ] `pnpm install` completes without warnings about peer-dep conflicts.
- [ ] `pnpm dev` starts and the page shows "SPA" still rendering, plus a Tailwind-styled colour test (e.g. red background) to prove Tailwind works.
- [ ] `pnpm typecheck` clean.
- [ ] `pnpm lint` clean.
- [ ] `pnpm format:check` clean.
- [ ] `pnpm build` produces `dist/` with no warnings.
- [ ] `npx shadcn@latest add card` (or any primitive) works — proves shadcn/ui is configured correctly.
- [ ] `package.json` `dependencies` section includes (at minimum): `@directus/sdk`, `@hookform/resolvers`, `@tanstack/react-query`, `@tanstack/react-router`, `react`, `react-dom`, `react-hook-form`, `zod`, `zustand`. `devDependencies` includes the rest.
## Risks / open questions
- **Tailwind 4 + shadcn/ui compatibility.** shadcn supports Tailwind 4 as of late 2024. If the CLI throws an error during init, fall back to Tailwind 3 + the PostCSS plugin (older but stable). Document the choice in the task's Done section.
- **TanStack Router type inference.** The router type generation is done by the Vite plugin; without it, route-tree types don't propagate. 1.3 pulls in the Vite plugin alongside the proxy config; for now leave the router uninstalled-as-feature.
- **React 19 + libs.** Most ecosystem libs support React 19 by now (early 2026). If any of these has a peer-dep complaint, document and decide between forcing the install or downgrading the lib in question.
## Done
Stack rounded out: Tailwind 4 via `@tailwindcss/vite`, shadcn/ui (slate base / new-york style) with primitives `button`, `input`, `label`, `form`, `card`, `alert` in `src/ui/primitives/`, TanStack Router 1.169 + Query 5.100 (devtools + router-plugin in devDeps, not yet wired — that's 1.7), Zustand 5, `@directus/sdk` 21, zod 4, react-hook-form 7 + `@hookform/resolvers`, `class-variance-authority` + `clsx` + `tailwind-merge` + `lucide-react` (shadcn runtime), Prettier 3 + `eslint-config-prettier` + `eslint-plugin-prettier` integrated last in `eslint.config.js`. Path alias `@/*``./src/*` set up in both `tsconfig.json` and `tsconfig.app.json` (TS 6 deprecates `baseUrl`; paths now resolve relative to the config file). Scripts: `dev`, `build`, `preview`, `lint`, `typecheck`, `format`, `format:check`, `test` (placeholder). README rewritten with stack/scripts/shadcn-add instructions.
**Deviation from spec:** the path alias was originally scoped to task 1.3, but shadcn's `add` CLI needs the alias resolvable to populate primitives at the right path — without it shadcn created a literal `@\` directory. Set up minimally here; 1.3 still owns the rest of the tsconfig hardening and the dev proxy.
**Smoke check:** `pnpm typecheck`, `pnpm lint`, `pnpm format:check`, `pnpm build` all green. `App.tsx` renders a Tailwind-styled centered card with a shadcn `Button` to prove the toolchain works end-to-end. Bundle: 222KB raw / 70KB gzipped.
Landed in `9918418`.