diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a770113 --- /dev/null +++ b/.env.example @@ -0,0 +1,13 @@ +# Local dev overrides for vite.config.ts proxy targets. +# Copy to .env.local (gitignored, auto-loaded by Vite) and edit to point +# at your local services. Both have sensible defaults matching the dev +# compose stack — only override when you need to. + +# Directus REST + GraphQL + business-plane WebSocket host. +# Defaults to http://localhost:8055 (the dev compose default). +# The WS proxy target is derived by swapping http(s):// for ws(s)://. +VITE_DEV_DIRECTUS_URL=http://localhost:8055 + +# Processor live-position WebSocket. Defaults to ws://localhost:8081 +# (the LIVE_WS_PORT default in the processor's Phase 1.5 task spec). +VITE_DEV_PROCESSOR_WS_URL=ws://localhost:8081 diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 0944703..14957d8 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -50,8 +50,8 @@ These rules govern every task. Any deviation must be discussed and documented as | # | Task | Status | Landed in | | ---- | ------------------------------------------------------------------------------------------------------------ | ------ | --------- | | 1.1 | Project scaffold (Vite + React + TS) | 🟩 | (manual) | -| 1.2 | [Stack rounding-out (Tailwind + shadcn/ui + deps + Prettier)](./phase-1-foundation/02-stack-rounding-out.md) | 🟩 | local (uncommitted) | -| 1.3 | [Vite dev proxy + path aliases + tsconfig hardening](./phase-1-foundation/03-vite-dev-proxy.md) | ⬜ | — | +| 1.2 | [Stack rounding-out (Tailwind + shadcn/ui + deps + Prettier)](./phase-1-foundation/02-stack-rounding-out.md) | 🟩 | `9918418` | +| 1.3 | [Vite dev proxy + path aliases + tsconfig hardening](./phase-1-foundation/03-vite-dev-proxy.md) | 🟩 | `PENDING_SHA` | | 1.4 | [Runtime config endpoint](./phase-1-foundation/04-runtime-config.md) | ⬜ | — | | 1.5 | [Directus auth client (cookie mode + refresh)](./phase-1-foundation/05-directus-auth-client.md) | ⬜ | — | | 1.6 | [Login page](./phase-1-foundation/06-login-page.md) | ⬜ | — | diff --git a/.planning/phase-1-foundation/02-stack-rounding-out.md b/.planning/phase-1-foundation/02-stack-rounding-out.md index 4177021..e20c77c 100644 --- a/.planning/phase-1-foundation/02-stack-rounding-out.md +++ b/.planning/phase-1-foundation/02-stack-rounding-out.md @@ -107,4 +107,4 @@ Stack rounded out: Tailwind 4 via `@tailwindcss/vite`, shadcn/ui (slate base / n **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.) +Landed in `9918418`. diff --git a/.planning/phase-1-foundation/03-vite-dev-proxy.md b/.planning/phase-1-foundation/03-vite-dev-proxy.md index 4aec658..3408836 100644 --- a/.planning/phase-1-foundation/03-vite-dev-proxy.md +++ b/.planning/phase-1-foundation/03-vite-dev-proxy.md @@ -132,4 +132,18 @@ One gotcha: the proxy's `target` for a WS route must use a `ws://` or `wss://` s ## Done -(Filled in when the task lands.) +`vite.config.ts` switched to the function form, reads env via `loadEnv(mode, process.cwd(), 'VITE_')`, exposes three proxy routes: + +- `/api/*` → `${VITE_DEV_DIRECTUS_URL}/*` (default `http://localhost:8055`), strips the `/api` prefix. +- `/ws-business` → derived `ws://...` from the Directus URL, rewrites to `/websocket`. +- `/ws-live` → `${VITE_DEV_PROCESSOR_WS_URL}` (default `ws://localhost:8081`), strips the `/ws-live` prefix. + +`tsconfig.app.json` gains `noUncheckedIndexedAccess: true` and `noImplicitOverride: true`. The `@/*` path alias was already set up in 1.2 (deviation noted there). + +`.env.example` (committed) documents the two proxy override env vars; `.env.local` (already gitignored via the existing `*.local` pattern) is the developer's actual values file. README "Local dev" section rewritten with the proxy table and override instructions. + +**Deviation from spec:** the spec called for `.env.dev.example` / `.env.dev.local` but Vite's mode for `pnpm dev` is `development` (not `dev`), so mode-specific files would be `.env.development.local` and Vite wouldn't auto-load `.env.dev.local` at all. Switched to the standard Vite convention `.env.example` (committed docs) + `.env.local` (auto-loaded in any mode, gitignored). Cleaner and matches Vite's actual behaviour. + +**Smoke check:** `pnpm typecheck`, `pnpm lint`, `pnpm format:check`, `pnpm build` all green. Stricter tsconfig didn't surface any existing issues (current code is small). + +Landed in `PENDING_SHA`. diff --git a/README.md b/README.md index e49fdc8..b1a9be6 100644 --- a/README.md +++ b/README.md @@ -24,16 +24,16 @@ Implementation planning lives in `.planning/` — see `.planning/ROADMAP.md`. ## Common scripts -| Command | Purpose | -| -------------------- | --------------------------------------------- | -| `pnpm dev` | Start the Vite dev server on `:5173`. | -| `pnpm build` | Type-check + production build into `dist/`. | -| `pnpm preview` | Serve the built `dist/` for smoke testing. | -| `pnpm typecheck` | Type-check only (no build). | -| `pnpm lint` | ESLint over the repo. | -| `pnpm format` | Prettier auto-format. | -| `pnpm format:check` | Prettier check only (CI gate). | -| `pnpm test` | Test runner (Phase 3 wires Vitest). | +| Command | Purpose | +| ------------------- | ------------------------------------------- | +| `pnpm dev` | Start the Vite dev server on `:5173`. | +| `pnpm build` | Type-check + production build into `dist/`. | +| `pnpm preview` | Serve the built `dist/` for smoke testing. | +| `pnpm typecheck` | Type-check only (no build). | +| `pnpm lint` | ESLint over the repo. | +| `pnpm format` | Prettier auto-format. | +| `pnpm format:check` | Prettier check only (CI gate). | +| `pnpm test` | Test runner (Phase 3 wires Vitest). | ## Adding a shadcn primitive @@ -45,7 +45,17 @@ Components land in `src/ui/primitives/` per `components.json`'s alias config. Ed ## Local dev -The Vite dev proxy (Phase 1.3) routes `/api`, `/ws-business`, `/ws-live` to the appropriate local services so the SPA dev experience matches the stage/prod reverse-proxy topology under one origin. Until 1.3 lands, the SPA runs standalone. +The Vite dev server (`pnpm dev` on `:5173`) proxies three path namespaces to local services so everything runs same-origin — the same topology stage/prod gets via Traefik, no CORS workarounds needed. + +| Path | Forwards to | Notes | +| -------------- | ------------------------------------ | ----------------------------------------------------------------------------- | +| `/api/*` | `${VITE_DEV_DIRECTUS_URL}/*` | Directus REST + GraphQL. Default `http://localhost:8055`. | +| `/ws-business` | `${VITE_DEV_DIRECTUS_URL}/websocket` | Directus business-plane WebSocket (auto-derived ws:// scheme). | +| `/ws-live` | `${VITE_DEV_PROCESSOR_WS_URL}/` | Processor live-position WebSocket. Default `ws://localhost:8081` (Phase 1.5). | + +Override per-developer by copying `.env.example` to `.env.local` and editing — Vite auto-loads `.env.local` in any mode and the values flow into `vite.config.ts` via `loadEnv`. + +If the dev port `5173` collides with another Vite app, run with `VITE_PORT=5174 pnpm dev` (or pin a different port in `vite.config.ts`). ## CI diff --git a/tsconfig.app.json b/tsconfig.app.json index 6f0a63d..165b33d 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -24,7 +24,9 @@ "noUnusedLocals": true, "noUnusedParameters": true, "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true }, "include": ["src"] } diff --git a/vite.config.ts b/vite.config.ts index e2a4292..257f916 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,14 +1,49 @@ import path from 'node:path'; -import { defineConfig } from 'vite'; +import { defineConfig, loadEnv } from 'vite'; import react from '@vitejs/plugin-react'; import tailwindcss from '@tailwindcss/vite'; // https://vite.dev/config/ -export default defineConfig({ - plugins: [react(), tailwindcss()], - resolve: { - alias: { - '@': path.resolve(__dirname, './src'), +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'; + // The WS proxy target needs the ws:// scheme; derive from the http(s) URL. + const directusWsUrl = directusUrl.replace(/^http(s?):\/\//, 'ws$1://'); + + return { + plugins: [react(), tailwindcss()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, }, - }, + server: { + port: 5173, + proxy: { + // Directus REST + GraphQL. Strip /api prefix; SPA hits relative URLs. + '/api': { + target: directusUrl, + changeOrigin: true, + rewrite: (p) => p.replace(/^\/api/, ''), + }, + // Directus business-plane WebSocket. Strip /ws-business; rewrite to + // Directus's actual /websocket path. + '/ws-business': { + target: directusWsUrl, + ws: true, + changeOrigin: true, + rewrite: (p) => p.replace(/^\/ws-business/, '/websocket'), + }, + // Processor live-position WebSocket. Strip /ws-live; Processor binds + // bare (no path prefix) per the Phase 1.5 task spec. + '/ws-live': { + target: processorWsUrl, + ws: true, + changeOrigin: true, + rewrite: (p) => p.replace(/^\/ws-live/, ''), + }, + }, + }, + }; });