@patterkit/runtime 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -33,6 +33,17 @@ version number always means the same runtime behaviour. This package is versione
33
33
 
34
34
  ## [Unreleased]
35
35
 
36
+ ## [0.17.0] - 2026-10-05
37
+
38
+ ### Fixed
39
+
40
+ - **`openFlow` at an address that does not resolve changes nothing.** It threw, but only after closing
41
+ any flow already open under that name and putting a broken one in its place. It now throws before
42
+ anything changes. The block must be in the scene you named, as `goto` has always required: a block
43
+ from another scene used to open there, and now throws. Held across all four runtimes by the
44
+ conformance corpus, which now also pins a choice step whole (its `groupId` and each option's
45
+ `prompt`), as this runtime already gave it.
46
+
36
47
  ## [0.16.0] - 2026-10-04
37
48
 
38
49
  ### Added
package/dist/index.cjs CHANGED
@@ -332,11 +332,14 @@ var Engine = class _Engine {
332
332
  *
333
333
  * Contrast {@link runFlow}, which REUSES a flow of the same name instead of replacing it - that is the
334
334
  * call to reach for when you want a speaker's variation state to carry on.
335
+ *
336
+ * THROWS on an address that does not resolve (an unknown scene, or a block that is not in the named
337
+ * scene), before anything changes: nothing is opened, and a flow already open under `id` carries on
338
+ * exactly as it was.
335
339
  */
336
340
  openFlow(id, opts = {}) {
337
341
  this.assertExternalScopes();
338
- const sceneId = this.resolveSceneRef(opts.scene);
339
- const blockId = this.resolveBlockRef(sceneId, opts.block);
342
+ const { sceneId, blockId } = this.resolveOpenAddress(opts.scene, opts.block);
340
343
  const prior = this.flowsById.get(id);
341
344
  const journal = this.host.journal;
342
345
  if (journal && prior && !prior.isClosed) throw new Error(`openFlow would replace the open flow '${id}', which can't be undone while a checkpoint is open`);
@@ -397,6 +400,28 @@ var Engine = class _Engine {
397
400
  }
398
401
  }
399
402
  }
403
+ /**
404
+ * `openFlow`'s address as internal ids, or a throw when it does not resolve. With a scene, the block is
405
+ * scene-scoped exactly as {@link Flow.goto} resolves it (a gameId address in that scene, or the internal
406
+ * id of a block in that scene), so a block from another scene does not resolve. With no scene, the block
407
+ * is an internal id from any scene. Neither: the bundle's first scene. The same rule on every runtime.
408
+ */
409
+ resolveOpenAddress(scene, block) {
410
+ if (scene !== void 0) {
411
+ const sceneId = this.resolveSceneRef(scene);
412
+ if (!this.host.bundle.scenes[sceneId]) throw new Error(`unknown scene: ${scene}`);
413
+ if (block === void 0) return { sceneId };
414
+ const blockId = this.blockGameIdToId.get(sceneId)?.get(block) ?? (this.host.blockIndex.get(block)?.sceneId === sceneId ? block : void 0);
415
+ if (blockId === void 0) throw new Error(`unknown block: ${block}`);
416
+ return { sceneId, blockId };
417
+ }
418
+ if (block !== void 0) {
419
+ if (!this.host.blockIndex.has(block)) throw new Error(`unknown block: ${block}`);
420
+ return { blockId: block };
421
+ }
422
+ if (Object.keys(this.host.bundle.scenes).length === 0) throw new Error("no scenes in bundle");
423
+ return {};
424
+ }
400
425
  /** Resolve a scene reference (a gameId address OR an internal id) to its internal id. */
401
426
  resolveSceneRef(ref) {
402
427
  if (ref == null) return void 0;