From fa934ccdc64f972f745647ecf210dd00c31cf626 Mon Sep 17 00:00:00 2001 From: Sylvain Duchesne Date: Sun, 16 Aug 2026 16:27:00 +0200 Subject: [PATCH] Production serves from source, so it must fetch its config like everything else MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The deployed application could never sign anybody in. `loadRuntimeConfig` skipped fetching `/festipod-config.json` under `NODE_ENV=production`, reasoning that a production build has the value inlined by `build.ts`'s `define`. This project's production does not build: the container copies the sources and runs `bun run start` (= `NODE_ENV=production bun src/index.ts`), serving from `src/` exactly as dev does. Nothing serves `dist/` at all. So the one step that could supply a wallet was skipped, `ensureIdentity()` threw for want of one, and the endpoint sat there — served, and never asked. The condition tested the wrong thing. "Was the value inlined?" is a question the global itself answers; "am I in production?" only ever stood in for it, and the stand-in was false on the very path that matters. Verified on the served bundle rather than on the endpoint: under `NODE_ENV=production` it now carries the fetch, where it carried none. The same combination is what `hooks.ts` spawns for the @e2e app server, so that layer could not sign in either. --- .project/concepts/app-architecture/_debt.md | 7 ++++++ src/app/frontend.tsx | 27 +++++++++++++++------ 2 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 .project/concepts/app-architecture/_debt.md diff --git a/.project/concepts/app-architecture/_debt.md b/.project/concepts/app-architecture/_debt.md new file mode 100644 index 0000000..a8b7499 --- /dev/null +++ b/.project/concepts/app-architecture/_debt.md @@ -0,0 +1,7 @@ +# Doc-debt — app-architecture + +> Presence of a block = doc to update. Processed → delete the block; no blocks left → delete this file. +> One block = one "big change": `why` + `files` + `verify` (leaves to review). + +## Raw markers (consolidate into blocks, then delete) +- TOUCHED src/app/frontend.tsx @2026-08-16 (session 0b064e8b-1717-421f-a20e-a4318ad217b1) diff --git a/src/app/frontend.tsx b/src/app/frontend.tsx index 615b195..c8c9d87 100644 --- a/src/app/frontend.tsx +++ b/src/app/frontend.tsx @@ -4,19 +4,32 @@ * * It is included in `src/index.html`. * - * Before loading the app tree it pulls the RUNTIME shared-wallet config (dev - * server + `bun run start`, which serve from src/ and so miss build.ts's - * compile-time `define`), sets the global, then dynamically imports `App` so - * `sharedWallet.ts` reads the value on evaluation. In a build.ts bundle the - * password is already inlined via `define`, so this step is skipped (NODE_ENV). + * Before loading the app tree it pulls the RUNTIME shared-wallet config, sets + * the global, then dynamically imports `App` so `sharedWallet.ts` reads the + * value on evaluation. A `build.ts` bundle already carries the value inlined by + * `define`, and then there is nothing to fetch. + * + * WHICH ONE APPLIES IS NOT `NODE_ENV`. This used to skip the fetch under + * `NODE_ENV=production`, on the reasoning "production means built". This + * project's production does NOT build: the container copies the sources and + * runs `bun run start` (= `NODE_ENV=production bun src/index.ts`), serving from + * src/ exactly as dev does. Nothing ever serves `dist/`. So the deployed app + * skipped the only step that could give it a wallet, `ensureIdentity()` threw + * for want of one, and it could never sign anybody in — while `/festipod-config.json` + * sat there, served and unasked. + * + * The question is therefore "was the value inlined?", never "am I in + * production?" — ask the global itself. */ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; -/** Fetch the runtime shared-wallet config and set the global (dev/start only). */ +/** Fetch the runtime shared-wallet config and set the global, unless it is already there. */ async function loadRuntimeConfig(): Promise { - if (process.env.NODE_ENV === "production") return; // build.ts define provides it + // Already inlined by `build.ts`'s `define` → nothing to fetch. Bracket access, + // so that same `define` (which rewrites the dotted global) leaves this read alone. + if ((globalThis as Record)["__FESTIPOD_SHARED_WALLET_PASSWORD__"] != null) return; try { const res = await fetch("/festipod-config.json"); if (!res.ok) return;