Const At Module Load Breaks Runtime Injection

What Happened
After a deploy that finally reached production, the oil dashboard disappeared from alejandro-gutierrez.com/projects/oil. The page rendered a header, a description, and a GitHub link, but the Monte Carlo dashboard was gone. The user asked: “where did the model go??”
The Astro island placeholder was present in the HTML. The React bundle loaded without a 404. The bundle silently crashed during hydration. Every visual element that depended on the oil engine vanished, while the Astro page shell stayed intact.
Root Cause
Two repos share one engine. The oil repo at ~/Documents/oil/quant-engine.js is the authoritative source. The public-lab fork at ~/Documents/public-lab/src/components/oil/quant-engine.ts uses a TypeScript wrapper that does runtime data injection instead of static imports. The fork wrapper declares let trafficData, priceData, ... at module scope and populates them inside an exported initEngine(data) function that gets called with R2-fetched JSON.
The sync script scripts/sync-public-lab.sh preserves the wrapper (lines 1 through 129) and copies the engine body from the oil repo over the body in the public-lab fork.
The oil repo had recently been refactored (commit 41bdac2, “move wti-daily-history import to top of engine”). In that refactor, the body declarations changed from:
let TRAF, PRC, POLY, M, PD, FROZEN, FWD, PK, ANALOGUES, RH, RH_MONTH, MARKET;
// ... initEngine populates these at runtime
to:
const TRAF = trafficData.entries;
const PRC = priceData.entries;
const M = { pre: constantsData.pre, ... };
// ... etc
This works in the oil repo because the oil repo uses static import statements for data and those imports resolve before the const declarations execute. It does not work in the public-lab fork because trafficData is set to undefined at module-load time and populated later by initEngine. The const block ran at load, evaluated undefined.entries, threw a TypeError, and crashed the bundle before hydration could render anything.
No visible error. The Astro island stayed a placeholder.
How to Avoid
-
Hardened the sync script with a Python post-processor that rewrites on every sync:
const PARAM_CORRELATIONS = corrData.pairs;tolet PARAM_CORRELATIONS = {};- the TRAF/PRC/POLY/M/PD/FROZEN/FWD/PK/ANALOGUES/RH/RH_MONTH/MARKET const block to a single
letdeclaration const DURS = durData.scenarios;tolet DURS;
Idempotent. Re-running sync preserves the fix.
-
When forking an engine that is used in two environments (static-import, runtime-injection), identify the top-level-state seam explicitly. Any declaration that reads injected data at module-load time is a seam break. Code review the oil repo’s
quant-engine.jswith an eye for this specific pattern. -
When React hydration fails silently and the Astro island stays empty, the first hypothesis should be “module-load-time error in the bundle.” Check dev-tools console even when the visible HTML looks fine.
-
Prefer
let X; // populated by initEnginewith a comment naming the seam overconst X = source.field;when the module is shared between environments.
How the Public-Lab Fork Works
The fork has three responsibilities that the oil repo does not:
- Fetch JSON from R2 at runtime, not at build time.
- Expose an
initEngine(data)boundary for the React component to call after fetch resolves. - Tolerate a seven-wrapper-file structure that the sync script overwrites only partially.
Any oil-repo change that moves state earlier in module execution breaks the third responsibility. The sync-script patch is the mechanism that keeps the two in sync without requiring the oil repo to know about the fork.
Related
- projects/oil/_index : parent project
- wrangler-deploy-custom-domain-pinned : companion pitfall from the same incident
- r2-browser-heuristic-cache-stale-data : third pitfall from the same incident
- electron-stale-bundle-version-drift : adjacent “stale bundle” class on a different project