# 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: `
test
` 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`.