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

7.5 KiB

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)

{
  "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.