From b6a706699ba4f174c6742da254c7083200649a7a Mon Sep 17 00:00:00 2001 From: PJ Date: Mon, 1 Jun 2026 16:37:30 +0530 Subject: [PATCH] refactor(spec): installRuntime accepts a lazy root resolver The web bundle imports the runtime before the spec, so the action root on globalThis.actions only exists after the spec evaluates. Accept a function form so the goja and web hosts resolve the root per tick. --- pkg/spec/src/runtime-entry.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/spec/src/runtime-entry.ts b/pkg/spec/src/runtime-entry.ts index 04ef307..f5f2c67 100644 --- a/pkg/spec/src/runtime-entry.ts +++ b/pkg/spec/src/runtime-entry.ts @@ -102,18 +102,22 @@ export function serializeAction(action: ActionDescriptor | null): SerializedActi } // installRuntime defines the next-action and extractor globals for one engine. -// root is the generator the spec assigned (globalThis.actions); evaluateExtractors -// is the engine's snapshot of the spec's extract() handles. +// root is the generator the spec assigned; pass a function when the spec runs +// AFTER this call (the web bundle imports the runtime before the spec, so the +// root only exists on globalThis.actions once the spec has evaluated). +// evaluateExtractors is the engine's snapshot of the spec's extract() handles. export function installRuntime( host: Host, - root: GeneratorNode | null, + root: GeneratorNode | null | (() => GeneratorNode | null), evaluateExtractors: () => Record, ): void { const rng = new Pcg(host.seedHi(), host.seedLo()); + const resolveRoot = typeof root === "function" ? root : () => root; defineLockedGlobal("__sanderlingExtractors__", () => evaluateExtractors()); defineLockedGlobal("__sanderlingNextAction__", () => { - if (!root) return null; - return serializeAction(nextAction(root, rng, host)); + const current = resolveRoot(); + if (!current) return null; + return serializeAction(nextAction(current, rng, host)); }); }