26e059fc20
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).
136 lines
7.0 KiB
Markdown
136 lines
7.0 KiB
Markdown
# Task 1.3 — Vite dev proxy + path aliases + tsconfig hardening
|
|
|
|
**Phase:** 1 — Foundation
|
|
**Status:** ⬜ Not started
|
|
**Depends on:** 1.2
|
|
**Wiki refs:** `docs/wiki/entities/react-spa.md` §Auth pattern
|
|
|
|
## Goal
|
|
|
|
Configure Vite's dev server to proxy `/api`, `/ws-business`, `/ws-live`, and `/config.json` to the appropriate local services so the SPA dev experience matches the stage/prod reverse-proxy topology. Same-origin in dev means cookies flow correctly, identical to stage/prod where Traefik does the routing. Also: add path aliases (`@/`) so imports stay short, and tighten `tsconfig.json` for stricter type safety.
|
|
|
|
After this task, `pnpm dev` against a locally-running Directus + (eventually) Processor lets the SPA exercise the real auth flow without CORS workarounds.
|
|
|
|
## Deliverables
|
|
|
|
- `vite.config.ts` updated:
|
|
- `server.proxy` configured for:
|
|
- `/api` → `http://localhost:8055` (Directus REST/GraphQL).
|
|
- `/ws-business` → `ws://localhost:8055/websocket` (Directus business-plane WS), with `ws: true` and `rewrite` to strip `/ws-business`.
|
|
- `/ws-live` → `ws://localhost:8081` (Processor live WS, once Phase 1.5 lands), with `ws: true` and `rewrite` to strip `/ws-live`.
|
|
- Don't proxy `/config.json` — it's served from `public/` by Vite directly in dev.
|
|
- `resolve.alias` for `@/` → `src/`.
|
|
- `server.port` pinned to `5173` (Vite default; mention in README so anyone running other Vite apps avoids the conflict).
|
|
- `tsconfig.app.json` updated:
|
|
- `compilerOptions.baseUrl` to `.`, `paths` to `{ "@/*": ["src/*"] }`.
|
|
- Add `noUncheckedIndexedAccess: true` (matches `tcp-ingestion` / `processor` strictness).
|
|
- Add `noImplicitOverride: true`.
|
|
- `verbatimModuleSyntax: true` is fine if already on; otherwise leave for now.
|
|
- `README.md` updated: a "local dev" section listing the assumed local services + how to override the proxy targets via env vars (next bullet).
|
|
- Optional: env-var override for proxy targets:
|
|
- `VITE_DEV_DIRECTUS_URL` (default `http://localhost:8055`).
|
|
- `VITE_DEV_PROCESSOR_WS_URL` (default `ws://localhost:8081`).
|
|
- Read in `vite.config.ts` via `loadEnv`.
|
|
- Document in `.env.dev.example` (committed) and `.env.dev.local` (gitignored).
|
|
|
|
## Specification
|
|
|
|
### Why proxy in dev instead of CORS
|
|
|
|
CORS-with-credentials requires:
|
|
|
|
1. The browser sending `withCredentials: true` on every request.
|
|
2. The server responding with `Access-Control-Allow-Origin: <exact-origin>` (not `*`) and `Access-Control-Allow-Credentials: true`.
|
|
3. Cookies being scoped correctly (`SameSite=None; Secure` for cross-site — which means HTTPS even in dev).
|
|
|
|
This works but it's three places that must agree, and `SameSite=None; Secure` requires localhost-with-TLS in dev (extra setup). The dev proxy collapses all of it into "everything is one origin," matching stage/prod's reverse-proxy topology. **Pick the dev proxy.**
|
|
|
|
### Proxy config shape
|
|
|
|
```ts
|
|
// vite.config.ts
|
|
import { defineConfig, loadEnv } from 'vite';
|
|
import react from '@vitejs/plugin-react';
|
|
import tailwindcss from '@tailwindcss/vite';
|
|
import path from 'node:path';
|
|
|
|
export default defineConfig(({ mode }) => {
|
|
const env = loadEnv(mode, process.cwd(), 'VITE_');
|
|
const directusUrl = env.VITE_DEV_DIRECTUS_URL ?? 'http://localhost:8055';
|
|
const processorWsUrl = env.VITE_DEV_PROCESSOR_WS_URL ?? 'ws://localhost:8081';
|
|
|
|
return {
|
|
plugins: [react(), tailwindcss()],
|
|
resolve: {
|
|
alias: { '@': path.resolve(__dirname, './src') },
|
|
},
|
|
server: {
|
|
port: 5173,
|
|
proxy: {
|
|
'/api': {
|
|
target: directusUrl,
|
|
changeOrigin: true,
|
|
rewrite: (p) => p.replace(/^\/api/, ''),
|
|
},
|
|
'/ws-business': {
|
|
target: directusUrl,
|
|
ws: true,
|
|
changeOrigin: true,
|
|
rewrite: (p) => p.replace(/^\/ws-business/, '/websocket'),
|
|
},
|
|
'/ws-live': {
|
|
target: processorWsUrl,
|
|
ws: true,
|
|
changeOrigin: true,
|
|
rewrite: (p) => p.replace(/^\/ws-live/, ''),
|
|
},
|
|
},
|
|
},
|
|
};
|
|
});
|
|
```
|
|
|
|
The path namespacing (`/api`, `/ws-business`, `/ws-live`) is what the SPA code calls. The proxy strips the namespace and forwards. Stage/prod's Traefik does the same routing under the public domain — same paths, same code, no env branching in the SPA.
|
|
|
|
### Path alias
|
|
|
|
`@/` is the convention shadcn/ui assumes (its `components.json` config wires `@/components/...`, `@/lib/utils`). Aligning Vite + tsconfig with it means imports look like:
|
|
|
|
```ts
|
|
import { Button } from '@/ui/primitives/button';
|
|
import { useAuthStore } from '@/auth/store';
|
|
```
|
|
|
|
instead of `../../../ui/primitives/button`. Don't add other top-level aliases — `@/` for `src/` is enough.
|
|
|
|
### Why `noUncheckedIndexedAccess`
|
|
|
|
Without it, `arr[0]` is typed as `T` even when the array might be empty. With it, `arr[0]` is typed `T | undefined`, forcing a check. Catches a real class of bugs in map-position code (where "first device" assumptions are easy to make and easy to get wrong when the array is empty during reconnect).
|
|
|
|
The processor and tcp-ingestion both have this on. Match.
|
|
|
|
### Vite proxy and WebSocket behaviour
|
|
|
|
The `ws: true` flag on the proxy entry tells Vite (which uses `http-proxy-middleware`) to forward the upgrade request. Cookies attached to the upgrade carry through transparently — that's the whole point of running same-origin in dev.
|
|
|
|
One gotcha: the proxy's `target` for a WS route must use a `ws://` or `wss://` scheme, _not_ `http://`. If you accidentally use `http://`, the upgrade fails silently and the SPA gets a connection error. Document in the README.
|
|
|
|
## Acceptance criteria
|
|
|
|
- [ ] `pnpm dev` starts at `http://localhost:5173`; the SPA loads.
|
|
- [ ] With Directus running locally on `:8055`, `fetch('/api/server/info')` from the browser console returns Directus's server info — proxy is forwarding.
|
|
- [ ] WebSocket smoke test: `new WebSocket('ws://localhost:5173/ws-business')` connects (returns code 1006 if Directus's WS endpoint is locked down without auth, but the upgrade itself succeeds — that's the test).
|
|
- [ ] `import { foo } from '@/auth/foo'` resolves both at type-check time and at runtime.
|
|
- [ ] `pnpm typecheck` is stricter — adding `arr[0].whatever` without a check should now fail.
|
|
- [ ] No CORS errors in the browser console when calling `/api/...` from `http://localhost:5173`.
|
|
|
|
## Risks / open questions
|
|
|
|
- **Directus's WS path is `/websocket`, not `/ws`.** Confirmed by reading Directus 11.x source. The proxy `rewrite` rule reflects this.
|
|
- **Processor WS path is unsettled.** [[processor-ws-contract]] uses `/processor/ws` as illustrative; the actual path will be set by the proxy config in stage/prod (`trm/deploy`). For dev we assume the Processor binds to `:8081` with no path prefix, and the proxy adds the `/ws-live` prefix on the SPA side. Reconcile if Phase 1.5 lands with a different path convention.
|
|
- **Multiple Vite apps on one machine.** Pinning to `:5173` collides if another Vite app is running. Document the override (`VITE_PORT=5174 pnpm dev` works as a one-off; permanent change needs the config edit).
|
|
|
|
## Done
|
|
|
|
(Filled in when the task lands.)
|