@scenar/core 0.9.0 → 0.10.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.
@@ -24,6 +24,13 @@ export interface ScenarEmbedHostController {
24
24
  seek(timeMs: number): void;
25
25
  setMuted(muted: boolean): void;
26
26
  setVolume(volume: number): void;
27
+ /**
28
+ * Report the scale factor the host is rendering the iframe at (1 = native),
29
+ * so the player's chrome layer can counter-scale its transport controls
30
+ * back to native pixel size. Sent by hosts running the iframe-as-screen
31
+ * mode; players without the capability ignore it.
32
+ */
33
+ setHostScale(scale: number): void;
27
34
  prefetch(): void;
28
35
  /**
29
36
  * Tell the embed to stop (best effort) and detach the host message listener.
@@ -1 +1 @@
1
- {"version":3,"file":"host-controller.d.ts","sourceRoot":"","sources":["../../src/embed/host-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,gBAAgB,EAGtB,MAAM,eAAe,CAAC;AAEvB;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACtD;AAED,2EAA2E;AAC3E,MAAM,WAAW,yBAAyB;IACxC,IAAI,IAAI,IAAI,CAAC;IACb,KAAK,IAAI,IAAI,CAAC;IACd,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,IAAI,IAAI,CAAC;IACjB;;;;OAIG;IACH,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,qBAAqB,EAC7B,OAAO,GAAE,sBAA2B,GACnC,yBAAyB,CA+B3B"}
1
+ {"version":3,"file":"host-controller.d.ts","sourceRoot":"","sources":["../../src/embed/host-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,gBAAgB,EAGtB,MAAM,eAAe,CAAC;AAEvB;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACtD;AAED,2EAA2E;AAC3E,MAAM,WAAW,yBAAyB;IACxC,IAAI,IAAI,IAAI,CAAC;IACb,KAAK,IAAI,IAAI,CAAC;IACd,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;;;;;OAKG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,IAAI,IAAI,CAAC;IACjB;;;;OAIG;IACH,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,qBAAqB,EAC7B,OAAO,GAAE,sBAA2B,GACnC,yBAAyB,CAgC3B"}
@@ -41,6 +41,7 @@ export function createEmbedHostController(target, options = {}) {
41
41
  seek: (timeMs) => send({ type: "seek", timeMs }),
42
42
  setMuted: (muted) => send({ type: "setMuted", muted }),
43
43
  setVolume: (volume) => send({ type: "setVolume", volume }),
44
+ setHostScale: (scale) => send({ type: "setHostScale", scale }),
44
45
  prefetch: () => send({ type: "prefetch" }),
45
46
  destroy: () => {
46
47
  send({ type: "destroy" });
@@ -1 +1 @@
1
- {"version":3,"file":"host-controller.js","sourceRoot":"","sources":["../../src/embed/host-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,iBAAiB,EACjB,eAAe,GAChB,MAAM,eAAe,CAAC;AAsCvB;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,yBAAyB,CACvC,MAA6B,EAC7B,UAAkC,EAAE;IAEpC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAClC,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAC5B,MAAM,SAAS,GAAG,OAAO,MAAM,KAAK,WAAW,CAAC;IAEhD,MAAM,IAAI,GAAG,CAAC,OAA2B,EAAQ,EAAE;QACjD,MAAM,CAAC,aAAa,EAAE,WAAW,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;IACxE,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,CAAC,KAAmB,EAAQ,EAAE;QAC9C,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,aAAa;YAAE,OAAO;QAClD,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM;YAAE,OAAO;QACpC,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;IACpB,CAAC,CAAC;IAEF,IAAI,SAAS;QAAE,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAE7D,OAAO;QACL,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAClC,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QACpC,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAChD,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;QACtD,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC;QAC1D,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;QAC1C,OAAO,EAAE,GAAG,EAAE;YACZ,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;YAC1B,IAAI,SAAS;gBAAE,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QAClE,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"host-controller.js","sourceRoot":"","sources":["../../src/embed/host-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,iBAAiB,EACjB,eAAe,GAChB,MAAM,eAAe,CAAC;AA6CvB;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,yBAAyB,CACvC,MAA6B,EAC7B,UAAkC,EAAE;IAEpC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAClC,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAC5B,MAAM,SAAS,GAAG,OAAO,MAAM,KAAK,WAAW,CAAC;IAEhD,MAAM,IAAI,GAAG,CAAC,OAA2B,EAAQ,EAAE;QACjD,MAAM,CAAC,aAAa,EAAE,WAAW,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;IACxE,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,CAAC,KAAmB,EAAQ,EAAE;QAC9C,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,aAAa;YAAE,OAAO;QAClD,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM;YAAE,OAAO;QACpC,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;IACpB,CAAC,CAAC;IAEF,IAAI,SAAS;QAAE,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAE7D,OAAO;QACL,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAClC,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QACpC,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAChD,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;QACtD,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC;QAC1D,YAAY,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;QAC9D,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;QAC1C,OAAO,EAAE,GAAG,EAAE;YACZ,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;YAC1B,IAAI,SAAS;gBAAE,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QAClE,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -18,11 +18,30 @@
18
18
  export declare const SCENAR_EMBED_SOURCE = "scenar-embed";
19
19
  /** Protocol version. Bump only on a breaking change to the message shapes. */
20
20
  export declare const SCENAR_EMBED_PROTOCOL_VERSION = 1;
21
+ /**
22
+ * The canonical viewport a packed scenario was authored and bundled at, in CSS
23
+ * pixels — the same numbers `scenar pack` bakes into the bundle and records in
24
+ * `scenario.json`.
25
+ *
26
+ * Carried on `ready` so a host can lay the iframe out at this exact size and
27
+ * scale it as one unit (the iframe-as-screen mode in `@scenar/embed`): the
28
+ * embedded document's media queries then resolve against the canonical width,
29
+ * so responsive components render the same variant they would in a real
30
+ * browser window of that size — instead of the narrow variant the host
31
+ * column's width would select.
32
+ */
33
+ export interface ScenarEmbedViewport {
34
+ readonly widthPx: number;
35
+ readonly heightPx: number;
36
+ }
21
37
  /**
22
38
  * Events the embedded player emits to the host (embed -> host).
23
39
  *
24
40
  * - `ready` — the player has mounted; carries scenario shape the host can use
25
- * to size or label the frame.
41
+ * to size or label the frame. `viewport` (when the bundle knows it) is the
42
+ * canonical size the scenario was packed at — see {@link ScenarEmbedViewport}.
43
+ * Optional for compatibility: bundles packed before the field existed omit
44
+ * it, and hosts must treat its absence as "fit content inside the iframe".
26
45
  * - `resize` — the rendered content height changed (dynamic-height hosts).
27
46
  * - `started` / `paused` / `completed` — transport state transitions.
28
47
  * - `stepchange` — the active step changed.
@@ -35,6 +54,7 @@ export type ScenarEmbedEvent = {
35
54
  readonly type: "ready";
36
55
  readonly totalSteps: number;
37
56
  readonly hasNarration: boolean;
57
+ readonly viewport?: ScenarEmbedViewport;
38
58
  } | {
39
59
  readonly type: "resize";
40
60
  readonly widthPx: number;
@@ -63,8 +83,17 @@ export type ScenarEmbedEvent = {
63
83
  /**
64
84
  * Commands the host may send to the embedded player (host -> embed).
65
85
  *
66
- * State-setting commands (`setMuted`, `setVolume`) are idempotent by design so
67
- * a host never has to track the player's internal state to stay in sync.
86
+ * State-setting commands (`setMuted`, `setVolume`, `setHostScale`) are
87
+ * idempotent by design so a host never has to track the player's internal
88
+ * state to stay in sync.
89
+ *
90
+ * `setHostScale` accompanies the iframe-as-screen mode: when the host lays
91
+ * the iframe out at the canonical viewport and scales it as one unit, every
92
+ * pixel inside — including the player's transport controls — shrinks by that
93
+ * factor. The player's chrome layer counter-scales by `1 / scale` so controls
94
+ * keep rendering at native pixel size (the ViewportChrome contract), which
95
+ * only the host can enable because a cross-origin document cannot observe the
96
+ * transform applied to its own frame.
68
97
  */
69
98
  export type ScenarEmbedCommand = {
70
99
  readonly type: "play";
@@ -79,6 +108,9 @@ export type ScenarEmbedCommand = {
79
108
  } | {
80
109
  readonly type: "setVolume";
81
110
  readonly volume: number;
111
+ } | {
112
+ readonly type: "setHostScale";
113
+ readonly scale: number;
82
114
  } | {
83
115
  readonly type: "prefetch";
84
116
  } | {
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/embed/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,2DAA2D;AAC3D,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAElD,8EAA8E;AAC9E,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAE/C;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAA;CAAE,GACvF;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAChF;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACxF;IACE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE,GAC9B;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GACjC;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GACtD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GAC7B;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAEjC,oEAAoE;AACpE,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,MAAM,EAAE,OAAO,mBAAmB,CAAC;IAC5C,QAAQ,CAAC,CAAC,EAAE,OAAO,6BAA6B,CAAC;CAClD;AAED,qEAAqE;AACrE,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,GAAG,mBAAmB,CAAC;AAE7E,uEAAuE;AACvE,MAAM,MAAM,yBAAyB,GAAG,kBAAkB,GAAG,mBAAmB,CAAC;AAEjF,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,KAAK,EAAE,gBAAgB,GAAG,uBAAuB,CAEhF;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,yBAAyB,CAExF;AAcD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,kBAAkB,GAAG,IAAI,CAkB1E;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,gBAAgB,GAAG,IAAI,CAqCtE"}
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/embed/protocol.ts"],"names":[],"mappings":"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"}
package/embed/protocol.js CHANGED
@@ -59,10 +59,33 @@ export function parseEmbedCommand(data) {
59
59
  return typeof data["muted"] === "boolean" ? { type, muted: data["muted"] } : null;
60
60
  case "setVolume":
61
61
  return isFiniteNumber(data["volume"]) ? { type, volume: data["volume"] } : null;
62
+ case "setHostScale":
63
+ return isFiniteNumber(data["scale"]) && data["scale"] > 0
64
+ ? { type, scale: data["scale"] }
65
+ : null;
62
66
  default:
63
67
  return null;
64
68
  }
65
69
  }
70
+ /**
71
+ * Parse the optional `viewport` field of a `ready` payload. Absent -> the
72
+ * event simply has no viewport (a pre-viewport bundle). Present but malformed
73
+ * -> the whole event is rejected, exactly like a mistyped required field: a
74
+ * half-valid message is a protocol violation, not a degraded mode.
75
+ */
76
+ function parseReadyViewport(value) {
77
+ if (value === undefined)
78
+ return {};
79
+ if (typeof value !== "object" || value === null)
80
+ return null;
81
+ const record = value;
82
+ return isFiniteNumber(record["widthPx"]) &&
83
+ record["widthPx"] > 0 &&
84
+ isFiniteNumber(record["heightPx"]) &&
85
+ record["heightPx"] > 0
86
+ ? { viewport: { widthPx: record["widthPx"], heightPx: record["heightPx"] } }
87
+ : null;
88
+ }
66
89
  /**
67
90
  * Parse an inbound `MessageEvent.data` into a typed event, or return `null`.
68
91
  *
@@ -79,10 +102,20 @@ export function parseEmbedEvent(data) {
79
102
  case "completed":
80
103
  case "audioBlocked":
81
104
  return { type };
82
- case "ready":
83
- return isFiniteNumber(data["totalSteps"]) && typeof data["hasNarration"] === "boolean"
84
- ? { type, totalSteps: data["totalSteps"], hasNarration: data["hasNarration"] }
85
- : null;
105
+ case "ready": {
106
+ if (!isFiniteNumber(data["totalSteps"]) || typeof data["hasNarration"] !== "boolean") {
107
+ return null;
108
+ }
109
+ const parsedViewport = parseReadyViewport(data["viewport"]);
110
+ if (parsedViewport === null)
111
+ return null;
112
+ return {
113
+ type,
114
+ totalSteps: data["totalSteps"],
115
+ hasNarration: data["hasNarration"],
116
+ ...parsedViewport,
117
+ };
118
+ }
86
119
  case "resize":
87
120
  return isFiniteNumber(data["widthPx"]) && isFiniteNumber(data["heightPx"])
88
121
  ? { type, widthPx: data["widthPx"], heightPx: data["heightPx"] }
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/embed/protocol.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/embed/protocol.ts"],"names":[],"mappings":"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"}
package/index.d.ts CHANGED
@@ -11,12 +11,12 @@ export { computeStepTimeline } from "./timeline/compute-step-timeline.js";
11
11
  export { deriveStepFromTime } from "./timeline/derive-step.js";
12
12
  export { getStepDurationMs } from "./timeline/step-duration.js";
13
13
  export type { ScenarioShot } from "./timeline/collect-shots.js";
14
- export { SHOT_NAME_PATTERN, collectScenarioShots } from "./timeline/collect-shots.js";
14
+ export { SHOT_NAME_PATTERN, collectScenarioShots, collectShotNames, } from "./timeline/collect-shots.js";
15
15
  export { CLICK_DELAY_MS, TYPE_CHAR_DELAY_MS, HOVER_HOLD_MS, DRAG_SETTLE_MS, CAMERA_TRANSITION_MS, VIEWPORT_SETTLE_MS, } from "./timing/constants.js";
16
16
  export { findScrollParent, scrollTargetIntoView, scrollTargetIntoViewInstant, } from "./dom/scroll.js";
17
17
  export { CURSOR_TARGET_ATTRIBUTE, SCROLL_TARGET_ATTRIBUTE, HOVER_STATE_ATTRIBUTE, DRAG_STATE_ATTRIBUTE, cursorTargetSelector, scrollTargetSelector, } from "./targeting/data-attributes.js";
18
18
  export { SCENAR_EMBED_SOURCE, SCENAR_EMBED_PROTOCOL_VERSION, frameEmbedEvent, frameEmbedCommand, parseEmbedCommand, parseEmbedEvent, } from "./embed/protocol.js";
19
- export type { ScenarEmbedEvent, ScenarEmbedCommand, ScenarEmbedEventMessage, ScenarEmbedCommandMessage, } from "./embed/protocol.js";
19
+ export type { ScenarEmbedEvent, ScenarEmbedCommand, ScenarEmbedEventMessage, ScenarEmbedCommandMessage, ScenarEmbedViewport, } from "./embed/protocol.js";
20
20
  export { createEmbedHostController } from "./embed/host-controller.js";
21
21
  export type { ScenarEmbedHostController, ScenarEmbedHostTarget, ScenarEmbedHostOptions, } from "./embed/host-controller.js";
22
22
  //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,YAAY,EACV,UAAU,EACV,UAAU,EACV,0BAA0B,GAC3B,MAAM,2BAA2B,CAAC;AAGnC,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAG9E,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,EACvB,UAAU,EACV,4BAA4B,GAC7B,MAAM,yBAAyB,CAAC;AAGjC,YAAY,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAGrE,YAAY,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,YAAY,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGtF,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,2BAA2B,GAC5B,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,gCAAgC,CAAC;AAGxC,OAAO,EACL,mBAAmB,EACnB,6BAA6B,EAC7B,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,GAChB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,gBAAgB,EAChB,kBAAkB,EAClB,uBAAuB,EACvB,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AACvE,YAAY,EACV,yBAAyB,EACzB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,YAAY,EACV,UAAU,EACV,UAAU,EACV,0BAA0B,GAC3B,MAAM,2BAA2B,CAAC;AAGnC,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAG9E,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,EACvB,UAAU,EACV,4BAA4B,GAC7B,MAAM,yBAAyB,CAAC;AAGjC,YAAY,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAGrE,YAAY,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,YAAY,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,2BAA2B,GAC5B,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,gCAAgC,CAAC;AAGxC,OAAO,EACL,mBAAmB,EACnB,6BAA6B,EAC7B,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,GAChB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,gBAAgB,EAChB,kBAAkB,EAClB,uBAAuB,EACvB,yBAAyB,EACzB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AACvE,YAAY,EACV,yBAAyB,EACzB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,4BAA4B,CAAC"}
package/index.js CHANGED
@@ -5,7 +5,7 @@ export { computeCursorPosition } from "./cursor/compute-position.js";
5
5
  export { computeStepTimeline } from "./timeline/compute-step-timeline.js";
6
6
  export { deriveStepFromTime } from "./timeline/derive-step.js";
7
7
  export { getStepDurationMs } from "./timeline/step-duration.js";
8
- export { SHOT_NAME_PATTERN, collectScenarioShots } from "./timeline/collect-shots.js";
8
+ export { SHOT_NAME_PATTERN, collectScenarioShots, collectShotNames, } from "./timeline/collect-shots.js";
9
9
  // Timing constants
10
10
  export { CLICK_DELAY_MS, TYPE_CHAR_DELAY_MS, HOVER_HOLD_MS, DRAG_SETTLE_MS, CAMERA_TRANSITION_MS, VIEWPORT_SETTLE_MS, } from "./timing/constants.js";
11
11
  // DOM scroll utilities
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAC5B,sEAAsE;AAgBtE,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,EACvB,UAAU,EACV,4BAA4B,GAC7B,MAAM,yBAAyB,CAAC;AAIjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAIrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAEhE,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAEtF,mBAAmB;AACnB,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAE/B,uBAAuB;AACvB,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,2BAA2B,GAC5B,MAAM,iBAAiB,CAAC;AAEzB,oCAAoC;AACpC,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,gCAAgC,CAAC;AAExC,0EAA0E;AAC1E,OAAO,EACL,mBAAmB,EACnB,6BAA6B,EAC7B,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,GAChB,MAAM,qBAAqB,CAAC;AAQ7B,uEAAuE;AACvE,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAC5B,sEAAsE;AAgBtE,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,EACvB,UAAU,EACV,4BAA4B,GAC7B,MAAM,yBAAyB,CAAC;AAIjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAIrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAEhE,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,6BAA6B,CAAC;AAErC,mBAAmB;AACnB,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAE/B,uBAAuB;AACvB,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,2BAA2B,GAC5B,MAAM,iBAAiB,CAAC;AAEzB,oCAAoC;AACpC,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,gCAAgC,CAAC;AAExC,0EAA0E;AAC1E,OAAO,EACL,mBAAmB,EACnB,6BAA6B,EAC7B,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,GAChB,MAAM,qBAAqB,CAAC;AAS7B,uEAAuE;AACvE,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scenar/core",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Pure TypeScript types, timing, and utilities for the Scenar scenario engine.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -62,6 +62,12 @@ describe("createEmbedHostController — commands", () => {
62
62
  EMBED_ORIGIN,
63
63
  );
64
64
 
65
+ controller.setHostScale(0.7);
66
+ expect(post).toHaveBeenLastCalledWith(
67
+ expect.objectContaining({ type: "setHostScale", scale: 0.7 }),
68
+ EMBED_ORIGIN,
69
+ );
70
+
65
71
  controller.destroy();
66
72
  });
67
73
  });
@@ -32,6 +32,13 @@ export interface ScenarEmbedHostController {
32
32
  seek(timeMs: number): void;
33
33
  setMuted(muted: boolean): void;
34
34
  setVolume(volume: number): void;
35
+ /**
36
+ * Report the scale factor the host is rendering the iframe at (1 = native),
37
+ * so the player's chrome layer can counter-scale its transport controls
38
+ * back to native pixel size. Sent by hosts running the iframe-as-screen
39
+ * mode; players without the capability ignore it.
40
+ */
41
+ setHostScale(scale: number): void;
35
42
  prefetch(): void;
36
43
  /**
37
44
  * Tell the embed to stop (best effort) and detach the host message listener.
@@ -86,6 +93,7 @@ export function createEmbedHostController(
86
93
  seek: (timeMs) => send({ type: "seek", timeMs }),
87
94
  setMuted: (muted) => send({ type: "setMuted", muted }),
88
95
  setVolume: (volume) => send({ type: "setVolume", volume }),
96
+ setHostScale: (scale) => send({ type: "setHostScale", scale }),
89
97
  prefetch: () => send({ type: "prefetch" }),
90
98
  destroy: () => {
91
99
  send({ type: "destroy" });
@@ -55,6 +55,21 @@ describe("parseEmbedCommand", () => {
55
55
  });
56
56
  });
57
57
 
58
+ it("accepts setHostScale with a positive finite scale", () => {
59
+ expect(parseEmbedCommand(wrap({ type: "setHostScale", scale: 0.7 }))).toEqual({
60
+ type: "setHostScale",
61
+ scale: 0.7,
62
+ });
63
+ });
64
+
65
+ it("rejects setHostScale with a missing, non-finite, or non-positive scale", () => {
66
+ expect(parseEmbedCommand(wrap({ type: "setHostScale" }))).toBeNull();
67
+ expect(parseEmbedCommand(wrap({ type: "setHostScale", scale: "big" }))).toBeNull();
68
+ expect(parseEmbedCommand(wrap({ type: "setHostScale", scale: Number.NaN }))).toBeNull();
69
+ expect(parseEmbedCommand(wrap({ type: "setHostScale", scale: 0 }))).toBeNull();
70
+ expect(parseEmbedCommand(wrap({ type: "setHostScale", scale: -1 }))).toBeNull();
71
+ });
72
+
58
73
  it("rejects a foreign source", () => {
59
74
  expect(parseEmbedCommand({ source: "other-widget", v: 1, type: "play" })).toBeNull();
60
75
  });
@@ -103,6 +118,44 @@ describe("parseEmbedEvent", () => {
103
118
  expect(parseEmbedEvent(wrap({ type: "ready", totalSteps: 3 }))).toBeNull();
104
119
  });
105
120
 
121
+ it("accepts a ready payload carrying a well-formed canonical viewport", () => {
122
+ expect(
123
+ parseEmbedEvent(
124
+ wrap({
125
+ type: "ready",
126
+ totalSteps: 3,
127
+ hasNarration: false,
128
+ viewport: { widthPx: 1440, heightPx: 900 },
129
+ }),
130
+ ),
131
+ ).toEqual({
132
+ type: "ready",
133
+ totalSteps: 3,
134
+ hasNarration: false,
135
+ viewport: { widthPx: 1440, heightPx: 900 },
136
+ });
137
+ });
138
+
139
+ it("still accepts a ready payload without a viewport (pre-viewport bundles)", () => {
140
+ const parsed = parseEmbedEvent(wrap({ type: "ready", totalSteps: 3, hasNarration: false }));
141
+ expect(parsed).toEqual({ type: "ready", totalSteps: 3, hasNarration: false });
142
+ // The field must be genuinely absent, not present-and-undefined — hosts
143
+ // branch on `event.viewport` to decide iframe-as-screen adoption.
144
+ expect(parsed && "viewport" in parsed).toBe(false);
145
+ });
146
+
147
+ it("rejects a ready payload whose viewport is malformed", () => {
148
+ const base = { type: "ready", totalSteps: 3, hasNarration: false };
149
+ expect(parseEmbedEvent(wrap({ ...base, viewport: "1440x900" }))).toBeNull();
150
+ expect(parseEmbedEvent(wrap({ ...base, viewport: { widthPx: 1440 } }))).toBeNull();
151
+ expect(
152
+ parseEmbedEvent(wrap({ ...base, viewport: { widthPx: 0, heightPx: 900 } })),
153
+ ).toBeNull();
154
+ expect(
155
+ parseEmbedEvent(wrap({ ...base, viewport: { widthPx: "1440", heightPx: 900 } })),
156
+ ).toBeNull();
157
+ });
158
+
106
159
  it("rejects foreign source and unknown event types", () => {
107
160
  expect(parseEmbedEvent({ source: "x", v: 1, type: "started" })).toBeNull();
108
161
  expect(parseEmbedEvent(wrap({ type: "imploded" }))).toBeNull();
@@ -21,11 +21,31 @@ export const SCENAR_EMBED_SOURCE = "scenar-embed";
21
21
  /** Protocol version. Bump only on a breaking change to the message shapes. */
22
22
  export const SCENAR_EMBED_PROTOCOL_VERSION = 1;
23
23
 
24
+ /**
25
+ * The canonical viewport a packed scenario was authored and bundled at, in CSS
26
+ * pixels — the same numbers `scenar pack` bakes into the bundle and records in
27
+ * `scenario.json`.
28
+ *
29
+ * Carried on `ready` so a host can lay the iframe out at this exact size and
30
+ * scale it as one unit (the iframe-as-screen mode in `@scenar/embed`): the
31
+ * embedded document's media queries then resolve against the canonical width,
32
+ * so responsive components render the same variant they would in a real
33
+ * browser window of that size — instead of the narrow variant the host
34
+ * column's width would select.
35
+ */
36
+ export interface ScenarEmbedViewport {
37
+ readonly widthPx: number;
38
+ readonly heightPx: number;
39
+ }
40
+
24
41
  /**
25
42
  * Events the embedded player emits to the host (embed -> host).
26
43
  *
27
44
  * - `ready` — the player has mounted; carries scenario shape the host can use
28
- * to size or label the frame.
45
+ * to size or label the frame. `viewport` (when the bundle knows it) is the
46
+ * canonical size the scenario was packed at — see {@link ScenarEmbedViewport}.
47
+ * Optional for compatibility: bundles packed before the field existed omit
48
+ * it, and hosts must treat its absence as "fit content inside the iframe".
29
49
  * - `resize` — the rendered content height changed (dynamic-height hosts).
30
50
  * - `started` / `paused` / `completed` — transport state transitions.
31
51
  * - `stepchange` — the active step changed.
@@ -35,7 +55,12 @@ export const SCENAR_EMBED_PROTOCOL_VERSION = 1;
35
55
  * - `error` — an unrecoverable runtime error, with a human-readable message.
36
56
  */
37
57
  export type ScenarEmbedEvent =
38
- | { readonly type: "ready"; readonly totalSteps: number; readonly hasNarration: boolean }
58
+ | {
59
+ readonly type: "ready";
60
+ readonly totalSteps: number;
61
+ readonly hasNarration: boolean;
62
+ readonly viewport?: ScenarEmbedViewport;
63
+ }
39
64
  | { readonly type: "resize"; readonly widthPx: number; readonly heightPx: number }
40
65
  | { readonly type: "started" }
41
66
  | { readonly type: "paused" }
@@ -53,8 +78,17 @@ export type ScenarEmbedEvent =
53
78
  /**
54
79
  * Commands the host may send to the embedded player (host -> embed).
55
80
  *
56
- * State-setting commands (`setMuted`, `setVolume`) are idempotent by design so
57
- * a host never has to track the player's internal state to stay in sync.
81
+ * State-setting commands (`setMuted`, `setVolume`, `setHostScale`) are
82
+ * idempotent by design so a host never has to track the player's internal
83
+ * state to stay in sync.
84
+ *
85
+ * `setHostScale` accompanies the iframe-as-screen mode: when the host lays
86
+ * the iframe out at the canonical viewport and scales it as one unit, every
87
+ * pixel inside — including the player's transport controls — shrinks by that
88
+ * factor. The player's chrome layer counter-scales by `1 / scale` so controls
89
+ * keep rendering at native pixel size (the ViewportChrome contract), which
90
+ * only the host can enable because a cross-origin document cannot observe the
91
+ * transform applied to its own frame.
58
92
  */
59
93
  export type ScenarEmbedCommand =
60
94
  | { readonly type: "play" }
@@ -62,6 +96,7 @@ export type ScenarEmbedCommand =
62
96
  | { readonly type: "seek"; readonly timeMs: number }
63
97
  | { readonly type: "setMuted"; readonly muted: boolean }
64
98
  | { readonly type: "setVolume"; readonly volume: number }
99
+ | { readonly type: "setHostScale"; readonly scale: number }
65
100
  | { readonly type: "prefetch" }
66
101
  | { readonly type: "destroy" };
67
102
 
@@ -122,11 +157,35 @@ export function parseEmbedCommand(data: unknown): ScenarEmbedCommand | null {
122
157
  return typeof data["muted"] === "boolean" ? { type, muted: data["muted"] } : null;
123
158
  case "setVolume":
124
159
  return isFiniteNumber(data["volume"]) ? { type, volume: data["volume"] } : null;
160
+ case "setHostScale":
161
+ return isFiniteNumber(data["scale"]) && data["scale"] > 0
162
+ ? { type, scale: data["scale"] }
163
+ : null;
125
164
  default:
126
165
  return null;
127
166
  }
128
167
  }
129
168
 
169
+ /**
170
+ * Parse the optional `viewport` field of a `ready` payload. Absent -> the
171
+ * event simply has no viewport (a pre-viewport bundle). Present but malformed
172
+ * -> the whole event is rejected, exactly like a mistyped required field: a
173
+ * half-valid message is a protocol violation, not a degraded mode.
174
+ */
175
+ function parseReadyViewport(
176
+ value: unknown,
177
+ ): { viewport?: ScenarEmbedViewport } | null {
178
+ if (value === undefined) return {};
179
+ if (typeof value !== "object" || value === null) return null;
180
+ const record = value as Record<string, unknown>;
181
+ return isFiniteNumber(record["widthPx"]) &&
182
+ record["widthPx"] > 0 &&
183
+ isFiniteNumber(record["heightPx"]) &&
184
+ record["heightPx"] > 0
185
+ ? { viewport: { widthPx: record["widthPx"], heightPx: record["heightPx"] } }
186
+ : null;
187
+ }
188
+
130
189
  /**
131
190
  * Parse an inbound `MessageEvent.data` into a typed event, or return `null`.
132
191
  *
@@ -142,10 +201,19 @@ export function parseEmbedEvent(data: unknown): ScenarEmbedEvent | null {
142
201
  case "completed":
143
202
  case "audioBlocked":
144
203
  return { type };
145
- case "ready":
146
- return isFiniteNumber(data["totalSteps"]) && typeof data["hasNarration"] === "boolean"
147
- ? { type, totalSteps: data["totalSteps"], hasNarration: data["hasNarration"] }
148
- : null;
204
+ case "ready": {
205
+ if (!isFiniteNumber(data["totalSteps"]) || typeof data["hasNarration"] !== "boolean") {
206
+ return null;
207
+ }
208
+ const parsedViewport = parseReadyViewport(data["viewport"]);
209
+ if (parsedViewport === null) return null;
210
+ return {
211
+ type,
212
+ totalSteps: data["totalSteps"],
213
+ hasNarration: data["hasNarration"],
214
+ ...parsedViewport,
215
+ };
216
+ }
149
217
  case "resize":
150
218
  return isFiniteNumber(data["widthPx"]) && isFiniteNumber(data["heightPx"])
151
219
  ? { type, widthPx: data["widthPx"], heightPx: data["heightPx"] }
package/src/index.ts CHANGED
@@ -32,7 +32,11 @@ export { computeStepTimeline } from "./timeline/compute-step-timeline.js";
32
32
  export { deriveStepFromTime } from "./timeline/derive-step.js";
33
33
  export { getStepDurationMs } from "./timeline/step-duration.js";
34
34
  export type { ScenarioShot } from "./timeline/collect-shots.js";
35
- export { SHOT_NAME_PATTERN, collectScenarioShots } from "./timeline/collect-shots.js";
35
+ export {
36
+ SHOT_NAME_PATTERN,
37
+ collectScenarioShots,
38
+ collectShotNames,
39
+ } from "./timeline/collect-shots.js";
36
40
 
37
41
  // Timing constants
38
42
  export {
@@ -75,6 +79,7 @@ export type {
75
79
  ScenarEmbedCommand,
76
80
  ScenarEmbedEventMessage,
77
81
  ScenarEmbedCommandMessage,
82
+ ScenarEmbedViewport,
78
83
  } from "./embed/protocol.js";
79
84
 
80
85
  // Embed host controller — framework-free driver for an embedded player
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import { computeStepTimeline } from "./compute-step-timeline.js";
3
- import { SHOT_NAME_PATTERN, collectScenarioShots } from "./collect-shots.js";
3
+ import { SHOT_NAME_PATTERN, collectScenarioShots, collectShotNames } from "./collect-shots.js";
4
4
 
5
5
  describe("collectScenarioShots", () => {
6
6
  it("resolves each shot to the settled end of its step", () => {
@@ -76,3 +76,41 @@ describe("collectScenarioShots", () => {
76
76
  ).toThrowError(/steps 0 and 2 both declare shot "opening"/);
77
77
  });
78
78
  });
79
+
80
+ describe("collectShotNames", () => {
81
+ it("returns the declared names in step order, without needing a timeline", () => {
82
+ const steps = [
83
+ { delayMs: 0, shot: "opening" },
84
+ { delayMs: 1000 },
85
+ { delayMs: 2000, shot: "detail-open" },
86
+ ];
87
+ expect(collectShotNames(steps)).toEqual(["opening", "detail-open"]);
88
+ });
89
+
90
+ it("returns an empty list when no step declares a shot", () => {
91
+ expect(collectShotNames([{ delayMs: 0 }, { delayMs: 1000 }])).toEqual([]);
92
+ });
93
+
94
+ it("throws the same validation errors as collectScenarioShots", () => {
95
+ expect(() => collectShotNames([{ delayMs: 0, shot: "Not_Kebab" }])).toThrowError(
96
+ /not kebab-case/,
97
+ );
98
+ expect(() =>
99
+ collectShotNames([
100
+ { delayMs: 0, shot: "opening" },
101
+ { delayMs: 100, shot: "opening" },
102
+ ]),
103
+ ).toThrowError(/steps 0 and 1 both declare shot "opening"/);
104
+ });
105
+
106
+ it("agrees with collectScenarioShots on every valid scenario (shared walk)", () => {
107
+ const steps = [
108
+ { delayMs: 0, shot: "opening" },
109
+ { delayMs: 500 },
110
+ { delayMs: 700, shot: "midway" },
111
+ { delayMs: 300, shot: "finale" },
112
+ ];
113
+ const resolved = collectScenarioShots(steps, computeStepTimeline(steps, null));
114
+ expect(collectShotNames(steps)).toEqual(resolved.map((shot) => shot.name));
115
+ });
116
+ });
@@ -33,24 +33,21 @@ export interface ScenarioShot {
33
33
  export const SHOT_NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
34
34
 
35
35
  /**
36
- * Resolve every `shot`-bearing step to its named capture point, in
37
- * timeline order.
36
+ * Walk the steps and validate every declared `shot`, returning name +
37
+ * declaring index in step order — the single validation pass behind both
38
+ * {@link collectShotNames} and {@link collectScenarioShots}, so a name
39
+ * that passes one can never fail the other.
38
40
  *
39
41
  * Throws on the two authoring mistakes that must never reach a deployed
40
42
  * bundle: a name that cannot be a clean filename/URL segment, and a
41
43
  * duplicate name (shots are addressed by name, never by index — a
42
44
  * collision would silently overwrite a still). Error messages name the
43
45
  * offending step index so the fix is local.
44
- *
45
- * The `timeline` must be computed from the same steps (and the same
46
- * narration manifest) the capture will walk — shot times are meaningless
47
- * against any other timeline.
48
46
  */
49
- export function collectScenarioShots(
47
+ function collectDeclaredShots(
50
48
  steps: readonly StepWithShot[],
51
- timeline: StepTimeline,
52
- ): ScenarioShot[] {
53
- const shots: ScenarioShot[] = [];
49
+ ): Array<{ name: string; stepIndex: number }> {
50
+ const declared: Array<{ name: string; stepIndex: number }> = [];
54
51
  const seen = new Map<string, number>();
55
52
 
56
53
  for (let i = 0; i < steps.length; i++) {
@@ -71,14 +68,47 @@ export function collectScenarioShots(
71
68
  );
72
69
  }
73
70
  seen.set(name, i);
71
+ declared.push({ name, stepIndex: i });
72
+ }
73
+
74
+ return declared;
75
+ }
76
+
77
+ /**
78
+ * The validated shot names declared by a scenario, in step order —
79
+ * everything about a scenario's shots that is knowable without a
80
+ * timeline. `scenar pack` records this list in the bundle's
81
+ * scenario.json so tooling can learn a bundle's shots (or that it has
82
+ * none) without booting a browser.
83
+ *
84
+ * Throws on the same authoring mistakes as {@link collectScenarioShots};
85
+ * see {@link collectDeclaredShots}.
86
+ */
87
+ export function collectShotNames(steps: readonly StepWithShot[]): string[] {
88
+ return collectDeclaredShots(steps).map((shot) => shot.name);
89
+ }
74
90
 
75
- const nextStartMs = timeline.stepStartTimesMs[i + 1];
76
- shots.push({
91
+ /**
92
+ * Resolve every `shot`-bearing step to its named capture point, in
93
+ * timeline order.
94
+ *
95
+ * Validation (and the errors it throws) is shared with
96
+ * {@link collectShotNames}; see {@link collectDeclaredShots}.
97
+ *
98
+ * The `timeline` must be computed from the same steps (and the same
99
+ * narration manifest) the capture will walk — shot times are meaningless
100
+ * against any other timeline.
101
+ */
102
+ export function collectScenarioShots(
103
+ steps: readonly StepWithShot[],
104
+ timeline: StepTimeline,
105
+ ): ScenarioShot[] {
106
+ return collectDeclaredShots(steps).map(({ name, stepIndex }) => {
107
+ const nextStartMs = timeline.stepStartTimesMs[stepIndex + 1];
108
+ return {
77
109
  name,
78
110
  timeMs: (nextStartMs ?? timeline.totalDurationMs) - 1,
79
- stepIndex: i,
80
- });
81
- }
82
-
83
- return shots;
111
+ stepIndex,
112
+ };
113
+ });
84
114
  }
@@ -28,15 +28,23 @@ export interface ScenarioShot {
28
28
  * deploy path contract admits.
29
29
  */
30
30
  export declare const SHOT_NAME_PATTERN: RegExp;
31
+ /**
32
+ * The validated shot names declared by a scenario, in step order —
33
+ * everything about a scenario's shots that is knowable without a
34
+ * timeline. `scenar pack` records this list in the bundle's
35
+ * scenario.json so tooling can learn a bundle's shots (or that it has
36
+ * none) without booting a browser.
37
+ *
38
+ * Throws on the same authoring mistakes as {@link collectScenarioShots};
39
+ * see {@link collectDeclaredShots}.
40
+ */
41
+ export declare function collectShotNames(steps: readonly StepWithShot[]): string[];
31
42
  /**
32
43
  * Resolve every `shot`-bearing step to its named capture point, in
33
44
  * timeline order.
34
45
  *
35
- * Throws on the two authoring mistakes that must never reach a deployed
36
- * bundle: a name that cannot be a clean filename/URL segment, and a
37
- * duplicate name (shots are addressed by name, never by index — a
38
- * collision would silently overwrite a still). Error messages name the
39
- * offending step index so the fix is local.
46
+ * Validation (and the errors it throws) is shared with
47
+ * {@link collectShotNames}; see {@link collectDeclaredShots}.
40
48
  *
41
49
  * The `timeline` must be computed from the same steps (and the same
42
50
  * narration manifest) the capture will walk — shot times are meaningless
@@ -1 +1 @@
1
- {"version":3,"file":"collect-shots.d.ts","sourceRoot":"","sources":["../../src/timeline/collect-shots.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D;;;GAGG;AACH,UAAU,YAAY;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,4EAA4E;AAC5E,MAAM,WAAW,YAAY;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,QAA6B,CAAC;AAE5D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,QAAQ,EAAE,YAAY,GACrB,YAAY,EAAE,CAgChB"}
1
+ {"version":3,"file":"collect-shots.d.ts","sourceRoot":"","sources":["../../src/timeline/collect-shots.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D;;;GAGG;AACH,UAAU,YAAY;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,4EAA4E;AAC5E,MAAM,WAAW,YAAY;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,QAA6B,CAAC;AA4C5D;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,EAAE,CAEzE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,QAAQ,EAAE,YAAY,GACrB,YAAY,EAAE,CAShB"}
@@ -6,21 +6,19 @@
6
6
  */
7
7
  export const SHOT_NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
8
8
  /**
9
- * Resolve every `shot`-bearing step to its named capture point, in
10
- * timeline order.
9
+ * Walk the steps and validate every declared `shot`, returning name +
10
+ * declaring index in step order — the single validation pass behind both
11
+ * {@link collectShotNames} and {@link collectScenarioShots}, so a name
12
+ * that passes one can never fail the other.
11
13
  *
12
14
  * Throws on the two authoring mistakes that must never reach a deployed
13
15
  * bundle: a name that cannot be a clean filename/URL segment, and a
14
16
  * duplicate name (shots are addressed by name, never by index — a
15
17
  * collision would silently overwrite a still). Error messages name the
16
18
  * offending step index so the fix is local.
17
- *
18
- * The `timeline` must be computed from the same steps (and the same
19
- * narration manifest) the capture will walk — shot times are meaningless
20
- * against any other timeline.
21
19
  */
22
- export function collectScenarioShots(steps, timeline) {
23
- const shots = [];
20
+ function collectDeclaredShots(steps) {
21
+ const declared = [];
24
22
  const seen = new Map();
25
23
  for (let i = 0; i < steps.length; i++) {
26
24
  const name = steps[i]?.shot;
@@ -36,13 +34,42 @@ export function collectScenarioShots(steps, timeline) {
36
34
  `shot names must be unique within a scenario`);
37
35
  }
38
36
  seen.set(name, i);
39
- const nextStartMs = timeline.stepStartTimesMs[i + 1];
40
- shots.push({
37
+ declared.push({ name, stepIndex: i });
38
+ }
39
+ return declared;
40
+ }
41
+ /**
42
+ * The validated shot names declared by a scenario, in step order —
43
+ * everything about a scenario's shots that is knowable without a
44
+ * timeline. `scenar pack` records this list in the bundle's
45
+ * scenario.json so tooling can learn a bundle's shots (or that it has
46
+ * none) without booting a browser.
47
+ *
48
+ * Throws on the same authoring mistakes as {@link collectScenarioShots};
49
+ * see {@link collectDeclaredShots}.
50
+ */
51
+ export function collectShotNames(steps) {
52
+ return collectDeclaredShots(steps).map((shot) => shot.name);
53
+ }
54
+ /**
55
+ * Resolve every `shot`-bearing step to its named capture point, in
56
+ * timeline order.
57
+ *
58
+ * Validation (and the errors it throws) is shared with
59
+ * {@link collectShotNames}; see {@link collectDeclaredShots}.
60
+ *
61
+ * The `timeline` must be computed from the same steps (and the same
62
+ * narration manifest) the capture will walk — shot times are meaningless
63
+ * against any other timeline.
64
+ */
65
+ export function collectScenarioShots(steps, timeline) {
66
+ return collectDeclaredShots(steps).map(({ name, stepIndex }) => {
67
+ const nextStartMs = timeline.stepStartTimesMs[stepIndex + 1];
68
+ return {
41
69
  name,
42
70
  timeMs: (nextStartMs ?? timeline.totalDurationMs) - 1,
43
- stepIndex: i,
44
- });
45
- }
46
- return shots;
71
+ stepIndex,
72
+ };
73
+ });
47
74
  }
48
75
  //# sourceMappingURL=collect-shots.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"collect-shots.js","sourceRoot":"","sources":["../../src/timeline/collect-shots.ts"],"names":[],"mappings":"AA0BA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,0BAA0B,CAAC;AAE5D;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAA8B,EAC9B,QAAsB;IAEtB,MAAM,KAAK,GAAmB,EAAE,CAAC;IACjC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;QAC5B,IAAI,IAAI,KAAK,SAAS;YAAE,SAAS;QAEjC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CACb,QAAQ,CAAC,mBAAmB,IAAI,6BAA6B;gBAC3D,aAAa,iBAAiB,4CAA4C,CAC7E,CAAC;QACJ,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClC,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CACb,SAAS,UAAU,QAAQ,CAAC,uBAAuB,IAAI,KAAK;gBAC1D,6CAA6C,CAChD,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAElB,MAAM,WAAW,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACrD,KAAK,CAAC,IAAI,CAAC;YACT,IAAI;YACJ,MAAM,EAAE,CAAC,WAAW,IAAI,QAAQ,CAAC,eAAe,CAAC,GAAG,CAAC;YACrD,SAAS,EAAE,CAAC;SACb,CAAC,CAAC;IACL,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"collect-shots.js","sourceRoot":"","sources":["../../src/timeline/collect-shots.ts"],"names":[],"mappings":"AA0BA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,0BAA0B,CAAC;AAE5D;;;;;;;;;;;GAWG;AACH,SAAS,oBAAoB,CAC3B,KAA8B;IAE9B,MAAM,QAAQ,GAA+C,EAAE,CAAC;IAChE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;QAC5B,IAAI,IAAI,KAAK,SAAS;YAAE,SAAS;QAEjC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CACb,QAAQ,CAAC,mBAAmB,IAAI,6BAA6B;gBAC3D,aAAa,iBAAiB,4CAA4C,CAC7E,CAAC;QACJ,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClC,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CACb,SAAS,UAAU,QAAQ,CAAC,uBAAuB,IAAI,KAAK;gBAC1D,6CAA6C,CAChD,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAClB,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC;IACxC,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAA8B;IAC7D,OAAO,oBAAoB,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC9D,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAA8B,EAC9B,QAAsB;IAEtB,OAAO,oBAAoB,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE;QAC7D,MAAM,WAAW,GAAG,QAAQ,CAAC,gBAAgB,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;QAC7D,OAAO;YACL,IAAI;YACJ,MAAM,EAAE,CAAC,WAAW,IAAI,QAAQ,CAAC,eAAe,CAAC,GAAG,CAAC;YACrD,SAAS;SACV,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}