# CLAUDE.md — Fleet Kiosk App The **kiosk SPA** for the diskless fleet: a cyber sci-fi spaceship monitoring/control terminal (Svelte 5 + Tailwind v4 + Vite) shown fullscreen by the diskless workstation's kiosk session (weston + Chromium). Built to a static bundle, served by a tiny nginx, and reverse-proxied at **`/kiosk/`** by the boot serving stack. This is an **independent project**, consumed by the fleet boot repo (`diskless-workstation`) as a **git submodule** at `serving/kiosk-app/`. It builds and publishes its own container image; the serving stack pulls it. Keep this repo focused on the app — the OS-side kiosk *runtime* (weston, Chromium lockdown policy, the `kiosk-session` script, the systemd unit) lives in the **parent** repo under `files/kiosk/`, not here. ## Build & run ```bash npm install npm run dev # http://localhost:5173/kiosk/ (Vite dev server) npm run build # -> dist/ (static bundle, base path /kiosk/) npm run preview # serve the built bundle locally # container (same as CI does): docker build -t fleet-kiosk:dev . docker run --rm -p 8088:80 fleet-kiosk:dev # http://localhost:8088/ ``` CI (`.gitea/workflows/ci.yaml`) builds the Dockerfile (which runs `npm ci` + `npm run build`) and pushes `git.bitasylum.org/audiobuzz/fleet-kiosk` — branch-aware tags, `:latest` only on `main`. Needs repo secrets `PACKAGE_USER` / `PACKAGE_TOKEN`. ## Hard contracts with the consuming fleet (do not break these) These are the integration invariants the parent serving stack + the kiosk display depend on. Changing one means a coordinated change in the parent repo. 1. **Base path is `/kiosk/`.** `vite.config.js` sets `base: '/kiosk/'` because the main serving nginx reverse-proxies `/kiosk/` to this container and strips the prefix. Emitted asset URLs must be absolute under `/kiosk/`. If this ever changes, the parent's `http/default.conf` proxy + the `kiosk_url` Packer default must change with it. 2. **Hash-based routing only** (`src/lib/router.svelte.js`, `#/power`, `#/nav`…). Deep links stay client-side so the proxy needs **no SPA-fallback rewrite**. Don't switch to history/path routing without adding that rewrite upstream. 3. **Fully self-contained — no CDNs, no web fonts, no external calls.** The boot LAN is air-gapped and the stack's dnsmasq **sinkholes** call-home (and the in-image Chromium policy blocks it). Bundle every asset; inline fonts. A request that escapes to the internet will silently fail on a real host. 4. **Static / read-only by nature.** The display is a fixed kiosk (no keyboard nav, locked-down Chromium). Telemetry is dummy/simulated (`src/lib/data.js`, `live.svelte.js`, `clock.svelte.js`) — there is no backend. If a real data feed is ever added it must be reachable on the boot LAN and fail gracefully offline. 5. **Reproducible bundle.** Keep `package-lock.json` committed; the Dockerfile uses `npm ci`. Don't introduce build-time network fetches beyond npm deps. ## Layout ``` Dockerfile 2-stage: Vite build (node:alpine) -> nginx:alpine static serve nginx.conf serves the built assets at the container root (proxied as /kiosk/) vite.config.js base '/kiosk/', self-contained bundle settings src/App.svelte shell: navbar + routed page src/lib/ Navbar, Panel, Radar, Stat, Bar, Tag, StatusBar + router/clock/data helpers src/pages/ Dashboard, Power, Navigation, LifeSupport, Comms .gitea/workflows/ CI: build + push the fleet-kiosk image ``` ## Conventions - Svelte 5 runes; Tailwind v4 via `@tailwindcss/vite`. - Keep components small and self-contained; shared sim helpers in `src/lib/`. - Target `es2022` (modern Chromium kiosk — no legacy transpile needed). - Versioning: bump `package.json` `version` per release; the image tag tracks it on `main` (plus branch-name+sha tags for dev branches).