diff --git a/.project/concepts/tech-stack/knowledge_build-pipeline.md b/.project/concepts/tech-stack/knowledge_build-pipeline.md
index 0d7f20f..a5f56cb 100644
--- a/.project/concepts/tech-stack/knowledge_build-pipeline.md
+++ b/.project/concepts/tech-stack/knowledge_build-pipeline.md
@@ -14,7 +14,13 @@ Le serveur sert `src/index.html`, qui charge `src/app/frontend.tsx` (voir `app-a
## Détails de `build.ts` et du serveur
- `build.ts` scanne `src/**/*.html` comme entrypoints (aujourd'hui un seul : `src/index.html`), `target: 'browser'`, minify + sourcemap linked, plugin `bun-plugin-tailwind`. Ajouter un 2e `.html` créerait un 2e bundle.
-- `src/index.ts` (`Bun.serve`) sert : `/reports/cucumber` (rapport HTML), des stubs `/api/hello*`, et un **catch-all `/*` → `src/index.html`** (routing SPA, doit rester en dernier). HMR si `NODE_ENV !== 'production'`, port via `PORT`.
+- `src/index.ts` (`Bun.serve`) sert : `/reports/cucumber` (rapport HTML), des stubs `/api/hello*`, `/festipod-config.json` + `/shared-wallet.ngw` (config runtime, voir ci-dessous), et un **catch-all `/*` → `src/index.html`** (routing SPA, doit rester en dernier). HMR si `NODE_ENV !== 'production'`, port via `PORT`.
+
+## Globals de build vs config runtime (piège du wallet partagé)
+
+`build.ts` injecte des **globals à la compilation** via `define` (p. ex. `__FESTIPOD_SHARED_WALLET_PASSWORD__` depuis `FESTIPOD_SHARED_WALLET_PASSWORD`, `__FESTIPOD_ACCESS_GATE_DISABLED__`). **Piège** : le serveur `src/index.ts` (utilisé par `bun run dev` ET `bun run start`) bundle `index.html` via l'import HTML de Bun, qui **n'applique aucun `define`** — ni `bun --define` ni `process.env` ne s'y propagent (vérifié). Donc une variable d'env passée à `bun run dev` n'atteint pas le bundle frontend par ce chemin.
+
+Pour ces chemins servis depuis `src/`, la config passe donc au **runtime** : `src/index.ts` expose `/festipod-config.json` (lu depuis l'env), et l'entrée `src/app/frontend.tsx` la **fetch d'abord**, pose le global, **puis importe l'app dynamiquement** (`await import('./App')`) — ainsi `sharedWallet.ts` lit la valeur à son évaluation. Dans un bundle `build.ts` la valeur est déjà inline par `define`, donc le fetch est court-circuité (`NODE_ENV === 'production'`). Conséquence pratique : pour voir le flux « portefeuille partagé » en dev, lancer `FESTIPOD_SHARED_WALLET_PASSWORD=1 bun run dev` (+ `FESTIPOD_SHARED_WALLET_FILE=<.ngw>` pour un vrai téléchargement).
## Le harness de test est buildé à part
diff --git a/src/app/frontend.tsx b/src/app/frontend.tsx
index 446e60e..29a466e 100644
--- a/src/app/frontend.tsx
+++ b/src/app/frontend.tsx
@@ -3,24 +3,54 @@
* element and renders the App component to the DOM.
*
* 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).
*/
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
-import { App } from "./App";
-const elem = document.getElementById("root")!;
-const app = (
-
-
-
-);
-
-if (import.meta.hot) {
- // With hot module reloading, `import.meta.hot.data` is persisted.
- const root = (import.meta.hot.data.root ??= createRoot(elem));
- root.render(app);
-} else {
- // The hot module reloading API is not available in production.
- createRoot(elem).render(app);
+/** Fetch the runtime shared-wallet config and set the global (dev/start only). */
+async function loadRuntimeConfig(): Promise {
+ if (process.env.NODE_ENV === "production") return; // build.ts define provides it
+ try {
+ const res = await fetch("/festipod-config.json");
+ if (!res.ok) return;
+ const cfg = (await res.json()) as { sharedWalletPassword?: string };
+ // Bracket access so build.ts's `define` (which matches the dotted global)
+ // never rewrites this assignment. Only set when the env actually carries one.
+ const g = globalThis as Record;
+ if (cfg.sharedWalletPassword && g["__FESTIPOD_SHARED_WALLET_PASSWORD__"] == null) {
+ g["__FESTIPOD_SHARED_WALLET_PASSWORD__"] = cfg.sharedWalletPassword;
+ }
+ } catch {
+ // No runtime config endpoint (static build) → rely on the compile-time define.
+ }
}
+
+async function main(): Promise {
+ await loadRuntimeConfig();
+ // Dynamic import AFTER the global is set, so sharedWallet.ts reads it on eval.
+ const { App } = await import("./App");
+ const elem = document.getElementById("root")!;
+ const app = (
+
+
+
+ );
+
+ if (import.meta.hot) {
+ // With hot module reloading, `import.meta.hot.data` is persisted.
+ const root = (import.meta.hot.data.root ??= createRoot(elem));
+ root.render(app);
+ } else {
+ // The hot module reloading API is not available in production.
+ createRoot(elem).render(app);
+ }
+}
+
+void main();
diff --git a/src/index.ts b/src/index.ts
index 7351a3f..e58c516 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -41,6 +41,25 @@ const server = serve({
});
},
+ // Shared-wallet config, exposed at RUNTIME for the dev server + `bun run start`
+ // (both serve from src/, so they miss build.ts's compile-time `define`). The app
+ // entry (frontend.tsx) fetches this before it loads the app tree, so
+ // `sharedWallet.ts` sees the password. Empty env → '' → no shared wallet.
+ "/festipod-config.json": () =>
+ Response.json({
+ sharedWalletPassword: process.env.FESTIPOD_SHARED_WALLET_PASSWORD ?? "",
+ }),
+
+ // The shared wallet file (download target of the access barrier), when configured.
+ "/shared-wallet.ngw": async () => {
+ const p = process.env.FESTIPOD_SHARED_WALLET_FILE;
+ if (p) {
+ const file = Bun.file(p);
+ if (await file.exists()) return new Response(file);
+ }
+ return new Response("No shared wallet file configured.", { status: 404 });
+ },
+
// Serve index.html for all unmatched routes (must be last)
"/*": index,
},
diff --git a/src/modules/auth/screens/AccessGateScreen.tsx b/src/modules/auth/screens/AccessGateScreen.tsx
index d35789e..ddfce2f 100644
--- a/src/modules/auth/screens/AccessGateScreen.tsx
+++ b/src/modules/auth/screens/AccessGateScreen.tsx
@@ -99,7 +99,7 @@ export function AccessGateScreen({ status, error, onEnter }: AccessGateScreenPro
Festipod
Espace de test
- {hasSharedWallet() ? (
+ {hasSharedWallet() && status !== 'connected' ? (
<>
Première connexion sur cet appareil ?
Chargez le portefeuille partagé, une seule fois.