diff --git a/web/nitro-entry/bun-https.mjs b/web/nitro-entry/bun-https.mjs index 736c6039..585f1d0f 100644 --- a/web/nitro-entry/bun-https.mjs +++ b/web/nitro-entry/bun-https.mjs @@ -51,8 +51,11 @@ const tls = const server = Bun.serve({ port: process.env.NITRO_PORT || process.env.PORT || 3000, host: process.env.NITRO_HOST || process.env.HOST, + // Bun defaults this to 10 s, which is SHORTER than the host's 15 s SSE keep-alive comment — so a + // proxied `/api/v1/events` stream (or any other quiet long-lived response) gets cut by us and + // reconnects on a loop. 120 s is comfortably above any keep-alive we forward; still overridable. idleTimeout: - Number.parseInt(process.env.NITRO_BUN_IDLE_TIMEOUT, 10) || undefined, + Number.parseInt(process.env.NITRO_BUN_IDLE_TIMEOUT, 10) || 120, // `tls: undefined` ⇒ plain HTTP (dev); otherwise HTTPS over HTTP/1.1. tls, websocket: import.meta._websocket ? ws.websocket : undefined, diff --git a/web/package.json b/web/package.json index bcc47a24..499bbae1 100644 --- a/web/package.json +++ b/web/package.json @@ -11,6 +11,7 @@ "dev": "vite dev --port 47992", "prebuild": "orval --config orval.config.ts", "build": "vite build", + "postbuild": "node tools/check-i18n.mjs", "start": "bun run .output/server/index.mjs", "api:gen": "orval --config orval.config.ts", "lint": "tsc --noEmit", diff --git a/web/tsconfig.json b/web/tsconfig.json index 656e187e..51189929 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -20,5 +20,12 @@ "@/*": ["./src/*"] } }, - "include": ["src", "server", "vite.config.ts", "orval.config.ts"] + "include": [ + "src", + "server", + ".storybook", + "vite.config.ts", + "vite.storybook.config.ts", + "orval.config.ts" + ] } diff --git a/web/vite.config.ts b/web/vite.config.ts index aeba419d..028e2d7b 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -115,16 +115,63 @@ function pluginUiDevProxy(): Plugin { }; } +/** + * Drop @unom/ui's game-UI sound sprites from the build. + * + * `@unom/ui/button` pulls in `sound/defaults.js`, which resolves two sprite sheets with + * `new URL(…, import.meta.url)` at module scope — a 4.8 MB .wav and a 2.2 MB .mp3. Vite therefore + * emits both into `.output/public/assets/`, where they were ~7 MB of an 8.2 MB asset payload, and + * they ride along into the Windows installer and the .deb. + * + * The console never mounts `UnomProviders`, so no sound player is bundled and not one byte of that + * can ever be played. Stub the two files to an empty URL instead of shipping them. + * + * If the console ever DOES want click sounds, delete this plugin — that is the whole revert. + */ +function dropUnomSoundSprites(): Plugin { + // The module that names them, and the `new URL(, import.meta.url).href` expressions + // inside it. Rewriting the EXPRESSION is what works: Vite emits these assets from its own + // `new URL(…, import.meta.url)` transform, so intercepting the .wav/.mp3 module id never fires. + const DEFAULTS = /@unom[\\/]ui[\\/].*sound[\\/]defaults\.(?:js|mjs)$/; + const SPRITE_URL = + /new URL\(\s*(["'])[^"']*\.(?:wav|mp3)\1\s*,\s*import\.meta\.url\s*\)\.href/g; + return { + name: "punktfunk-drop-unom-sound-sprites", + enforce: "pre", + transform(code, id) { + if (!DEFAULTS.test(id)) return null; + const out = code.replace(SPRITE_URL, '""'); + return out === code ? null : { code: out, map: null }; + }, + }; +} + export default defineConfig({ server: { proxy: { // `secure: false`: the host serves its own self-signed identity cert on loopback. - "/api": { target: MGMT_URL, changeOrigin: true, secure: false }, + "/api": { + target: MGMT_URL, + changeOrigin: true, + secure: false, + // Inject the management bearer, exactly as the deployed BFF does + // (server/routes/api/[...].ts). The host requires a token on every route now, so + // without this `bun run dev` 401s on every call and `apiFetch` bounces the developer + // to /login — where logging in doesn't help, because dev has no login gate at all. + configure(proxy) { + const token = process.env.PUNKTFUNK_MGMT_TOKEN; + if (!token) return; + proxy.on("proxyReq", (proxyReq) => { + proxyReq.setHeader("authorization", `Bearer ${token}`); + }); + }, + }, }, }, plugins: [ // First, so it intercepts /plugin-ui before the SSR catch-all in dev. pluginUiDevProxy(), + dropUnomSoundSprites(), viteTsConfigPaths({ projects: ["./tsconfig.json"] }), tailwindcss(), paraglideVitePlugin({