Add .planning/ scaffolding: - ROADMAP.md (4 phases, 8 non-negotiable design rules) - phase-1-foundation/ README + 9 task files (1.2-1.10) - phase-2-live-map / phase-3-dogfood-readiness / phase-4-future README placeholders Task 1.2 — stack rounding-out: - Tailwind 4 via @tailwindcss/vite + src/styles/globals.css - shadcn/ui (slate, new-york) primitives in src/ui/primitives/: button, input, label, form, card, alert - TanStack Router 1.169 + Query 5.100 (devtools + plugin in devDeps) - Zustand 5, @directus/sdk 21, zod 4, react-hook-form 7 + resolvers - Prettier 3 + eslint-config-prettier + eslint-plugin-prettier - ESLint override disabling react-refresh/only-export-components for src/ui/primitives/** (intentional dual-exports in shadcn primitives) - Path alias @/* -> ./src/* in tsconfig.json + tsconfig.app.json (TS 6 deprecates baseUrl; paths now resolve relative to config file). Pulled forward from 1.3 because shadcn add CLI needs it resolvable. - Scripts: dev, build, preview, lint, typecheck, format, format:check, test (placeholder) - App.tsx Tailwind smoke test (centred card + shadcn Button) - README.md rewritten with stack/scripts/shadcn-add docs All four gates green: typecheck, lint, format:check, build (222KB / 70KB gz).
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/vitevite.config.tsupdated to include the Tailwind plugin (additive; don't disturb the React plugin).src/styles/globals.csswith@import "tailwindcss";(Tailwind 4 syntax — single import, no@tailwind base/components/utilitiestriplet).src/main.tsximports./styles/globals.css.- Sanity check:
<div className="bg-red-500 p-4">test</div>inApp.tsxrenders red.
- shadcn/ui initialised:
npx shadcn@latest init— accept the defaults that fit our setup (Tailwind 4, TS,src/ui/primitivesas thecomponentspath,src/lib/utils.tsforcn).components.jsoncommitted.- Add the bare-minimum primitives we'll need in 1.6 (login page) and 1.7 (routing):
button,input,label,form,card,alert. Runnpx shadcn@latest add button input label form card alert— these populatesrc/ui/primitives/as plain TS files we own and edit.
- TanStack Router + Query:
pnpm add @tanstack/react-router @tanstack/react-querypnpm 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.prettierrcwith sensible defaults (single quotes, semi: true, printWidth: 100, trailingComma: 'all')..prettierignorematching.gitignorepluspnpm-lock.yaml,dist/,public/.eslint.config.jsextended to includeeslint-config-prettierlast (turns off conflicting rules) and the prettier plugin's recommended config.package.jsonscripts:"format": "prettier --write .""format:check": "prettier --check .""typecheck": "tsc -b --noEmit"(replaces the implicit typecheck inside the existingbuildscript for a faster CI gate).
README.mdupdated 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 installcompletes without warnings about peer-dep conflicts.pnpm devstarts and the page shows "SPA" still rendering, plus a Tailwind-styled colour test (e.g. red background) to prove Tailwind works.pnpm typecheckclean.pnpm lintclean.pnpm format:checkclean.pnpm buildproducesdist/with no warnings.npx shadcn@latest add card(or any primitive) works — proves shadcn/ui is configured correctly.package.jsondependenciessection includes (at minimum):@directus/sdk,@hookform/resolvers,@tanstack/react-query,@tanstack/react-router,react,react-dom,react-hook-form,zod,zustand.devDependenciesincludes 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.
(Commit SHA pending — work landed locally, not yet committed.)