@sebastianwessel/isostate 0.5.0 → 0.6.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.
Files changed (73) hide show
  1. package/dist/animation/animation-engine.d.ts +2 -0
  2. package/dist/animation/animation-engine.d.ts.map +1 -1
  3. package/dist/animation/controller.d.ts +4 -2
  4. package/dist/animation/controller.d.ts.map +1 -1
  5. package/dist/assets/software-architecture/IMAGEGEN-PROMPTS.md +66 -0
  6. package/dist/assets/software-architecture/LICENSE +21 -0
  7. package/dist/assets/software-architecture/NOTICE.md +17 -0
  8. package/dist/assets/software-architecture/README.md +105 -0
  9. package/dist/assets/software-architecture/agentic/AGENT-VISUAL-PROMPTS.md +42 -0
  10. package/dist/assets/software-architecture/agentic/HANDOFF-PROMPTS.md +19 -0
  11. package/dist/assets/software-architecture/agentic/PROCESS-VISUAL-PROMPTS.md +109 -0
  12. package/dist/assets/software-architecture/agentic/agents-sprites.png +0 -0
  13. package/dist/assets/software-architecture/agentic/ai-capabilities-sprites.png +0 -0
  14. package/dist/assets/software-architecture/agentic/ai-to-human-handoff.png +0 -0
  15. package/dist/assets/software-architecture/agentic/channels-sprites.png +0 -0
  16. package/dist/assets/software-architecture/agentic/human-to-ai-handoff.png +0 -0
  17. package/dist/assets/software-architecture/agentic/humans-sprites.png +0 -0
  18. package/dist/assets/software-architecture/agentic/infrastructure-sprites.png +0 -0
  19. package/dist/assets/software-architecture/agentic/lifecycle-sprites.png +0 -0
  20. package/dist/assets/software-architecture/agentic/orchestration-sprites.png +0 -0
  21. package/dist/assets/software-architecture/agentic/providers-sprites.png +0 -0
  22. package/dist/assets/software-architecture/agentic/servicenow-sprites.png +0 -0
  23. package/dist/assets/software-architecture/architecture/architecture-sprites.png +0 -0
  24. package/dist/assets/software-architecture/manifest.json +2095 -0
  25. package/dist/assets/software-architecture/workflow/workflow-process-sprites.png +0 -0
  26. package/dist/browser/isostate.runtime.js +4 -4
  27. package/dist/browser/isostate.runtime.js.map +9 -8
  28. package/dist/chunks/errors-2yA81Vfb.js +23 -0
  29. package/dist/chunks/{errors-DyqEkrm5.js.map → errors-2yA81Vfb.js.map} +1 -1
  30. package/dist/chunks/index-WmsNtuaZ.js +1685 -0
  31. package/dist/chunks/index-WmsNtuaZ.js.map +1 -0
  32. package/dist/chunks/rendering-engine-DLaM1XbU.js +1425 -0
  33. package/dist/chunks/rendering-engine-DLaM1XbU.js.map +1 -0
  34. package/dist/chunks/scene-parser-BNXXIAqj.js +3033 -0
  35. package/dist/chunks/scene-parser-BNXXIAqj.js.map +1 -0
  36. package/dist/chunks/sha256-BkgWUdh1.js +123 -0
  37. package/dist/chunks/sha256-BkgWUdh1.js.map +1 -0
  38. package/dist/dsl/browser.js +3 -3
  39. package/dist/dsl/index.js +3 -3
  40. package/dist/dsl/scene-parser.d.ts.map +1 -1
  41. package/dist/dsl/scene-validator.d.ts.map +1 -1
  42. package/dist/dsl/workflow-effects-validation.d.ts +8 -0
  43. package/dist/dsl/workflow-effects-validation.d.ts.map +1 -0
  44. package/dist/editor-support/adapter.d.ts.map +1 -1
  45. package/dist/editor-support/index.js +347 -394
  46. package/dist/editor-support/index.js.map +1 -1
  47. package/dist/index.d.ts +1 -1
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +356 -424
  50. package/dist/index.js.map +1 -1
  51. package/dist/rendering/animation-css.d.ts.map +1 -1
  52. package/dist/rendering/rendering-engine.d.ts +2 -0
  53. package/dist/rendering/rendering-engine.d.ts.map +1 -1
  54. package/dist/rendering/scene-effects.d.ts +15 -0
  55. package/dist/rendering/scene-effects.d.ts.map +1 -0
  56. package/dist/runtime/index.js +4 -4
  57. package/dist/runtime/interactivity.d.ts.map +1 -1
  58. package/dist/types/asset-registry.d.ts.map +1 -1
  59. package/dist/types/errors.d.ts.map +1 -1
  60. package/dist/types/index.d.ts +1 -1
  61. package/dist/types/index.d.ts.map +1 -1
  62. package/dist/types/node.d.ts +40 -1
  63. package/dist/types/node.d.ts.map +1 -1
  64. package/package.json +9 -8
  65. package/dist/chunks/errors-DyqEkrm5.js +0 -29
  66. package/dist/chunks/index-CB6XEsbY.js +0 -1756
  67. package/dist/chunks/index-CB6XEsbY.js.map +0 -1
  68. package/dist/chunks/rendering-engine-nkqYx0qc.js +0 -1303
  69. package/dist/chunks/rendering-engine-nkqYx0qc.js.map +0 -1
  70. package/dist/chunks/scene-parser-F69XGE0l.js +0 -2743
  71. package/dist/chunks/scene-parser-F69XGE0l.js.map +0 -1
  72. package/dist/chunks/sha256-4JuurR-b.js +0 -69
  73. package/dist/chunks/sha256-4JuurR-b.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,52 +1,42 @@
1
- export { A as AnimationController, a as AnimationEngine, e as easeInCubic, b as easeInOutCubic, c as easeOutCubic, l as linear, m as mountScene, r as resolveEasing } from './chunks/index-CB6XEsbY.js';
2
- import { g as getResolvedProjectionLayout, a as getResolvedViewBox, f as projectToScreen, e as updateElementTransforms, c as getElementState, h as hideElementAfterExit, u as unhideElementOnReadd, d as getConnectorState } from './chunks/rendering-engine-nkqYx0qc.js';
3
- export { A as AssetRegistryImpl, D as DEFAULT_CELL_SIZE, i as applyThemeToElement, j as buildSceneDOM, k as calculateTransform, l as calculateVisualSize, m as composeTheme, n as createAssetRegistry, o as createDefaultRegistry, r as resolveTheme } from './chunks/rendering-engine-nkqYx0qc.js';
4
- import { R as RenderError } from './chunks/errors-DyqEkrm5.js';
5
- export { A as AnimationError, C as ControllerError, P as ParseError, V as ValidationErrorClass } from './chunks/errors-DyqEkrm5.js';
6
- import './chunks/sha256-4JuurR-b.js';
1
+ export { A as AnimationController, a as AnimationEngine, e as easeInCubic, b as easeInOutCubic, c as easeOutCubic, l as linear, m as mountScene, r as resolveEasing } from './chunks/index-WmsNtuaZ.js';
2
+ import { g as getResolvedProjectionLayout, a as getResolvedViewBox, f as projectToScreen, e as updateElementTransforms, b as getElementState, h as hideElementAfterExit, u as unhideElementOnReadd, d as getConnectorState } from './chunks/rendering-engine-DLaM1XbU.js';
3
+ export { A as AssetRegistryImpl, D as DEFAULT_CELL_SIZE, i as applyThemeToElement, j as buildSceneDOM, k as calculateTransform, l as calculateVisualSize, m as composeTheme, n as createAssetRegistry, o as createDefaultRegistry, r as resolveTheme } from './chunks/rendering-engine-DLaM1XbU.js';
4
+ import { R as RenderError } from './chunks/errors-2yA81Vfb.js';
5
+ export { A as AnimationError, C as ControllerError, P as ParseError, V as ValidationErrorClass } from './chunks/errors-2yA81Vfb.js';
6
+ import './chunks/sha256-BkgWUdh1.js';
7
7
 
8
- /** Runtime set of valid entry animation strings */
9
- const ENTRY_ANIMATIONS = new Set([
10
- "fade-in",
11
- "fade-in-grow",
12
- "fall-in",
13
- "rise-from-ground",
14
- "slide-in-left",
15
- "slide-in-right",
16
- "flip-in",
17
- "none",
8
+ const ENTRY_ANIMATIONS = /* @__PURE__ */ new Set([
9
+ "fade-in",
10
+ "fade-in-grow",
11
+ "fall-in",
12
+ "rise-from-ground",
13
+ "slide-in-left",
14
+ "slide-in-right",
15
+ "flip-in",
16
+ "none"
18
17
  ]);
19
- /** Runtime set of valid exit animation strings */
20
- const EXIT_ANIMATIONS = new Set([
21
- "fade-out",
22
- "fade-out-shrink",
23
- "fall-through-ground",
24
- "rise-away",
25
- "slide-out-left",
26
- "slide-out-right",
27
- "flip-out",
28
- "none",
18
+ const EXIT_ANIMATIONS = /* @__PURE__ */ new Set([
19
+ "fade-out",
20
+ "fade-out-shrink",
21
+ "fall-through-ground",
22
+ "rise-away",
23
+ "slide-out-left",
24
+ "slide-out-right",
25
+ "flip-out",
26
+ "none"
29
27
  ]);
30
- /** Runtime set of valid lifecycle status strings */
31
- const LIFECYCLE_STATUSES = new Set(["entering", "present", "exiting", "removed"]);
32
- // ── Type guards ──────────────────────────────────────────────────────────────
33
- /** Narrow a raw string to EntryAnimation. Returns undefined if invalid. */
28
+ const LIFECYCLE_STATUSES = /* @__PURE__ */ new Set(["entering", "present", "exiting", "removed"]);
34
29
  function guardEntryAnimation(v) {
35
- if (typeof v !== "string")
36
- return undefined;
37
- return ENTRY_ANIMATIONS.has(v) ? v : undefined;
30
+ if (typeof v !== "string") return void 0;
31
+ return ENTRY_ANIMATIONS.has(v) ? v : void 0;
38
32
  }
39
- /** Narrow a raw string to ExitAnimation. Returns undefined if invalid. */
40
33
  function guardExitAnimation(v) {
41
- if (typeof v !== "string")
42
- return undefined;
43
- return EXIT_ANIMATIONS.has(v) ? v : undefined;
34
+ if (typeof v !== "string") return void 0;
35
+ return EXIT_ANIMATIONS.has(v) ? v : void 0;
44
36
  }
45
- /** Narrow a raw string to LifecycleStatus. Returns undefined if invalid. */
46
37
  function guardLifecycleStatus(v) {
47
- if (typeof v !== "string")
48
- return undefined;
49
- return LIFECYCLE_STATUSES.has(v) ? v : undefined;
38
+ if (typeof v !== "string") return void 0;
39
+ return LIFECYCLE_STATUSES.has(v) ? v : void 0;
50
40
  }
51
41
 
52
42
  const SVG_NS = "http://www.w3.org/2000/svg";
@@ -55,462 +45,404 @@ const GRID_STROKE = "var(--iso-diag-grid, rgba(37, 99, 235, 0.35))";
55
45
  const TEXT_FILL = "var(--iso-diag-text, #1e3a8a)";
56
46
  const ANCHOR_FILL = "var(--iso-diag-anchor, #dc2626)";
57
47
  const ROUTE_FILL = "var(--iso-diag-route, #059669)";
58
- const registryBySvg = new WeakMap();
59
- /**
60
- * Attach a development-time diagnostics overlay (grid, coordinates, element
61
- * anchors, connector route points, and a scene/progress readout) to a mounted
62
- * scene. See `specs/02-capabilities/diagnostics-overlay.md` for the full
63
- * normative behavior.
64
- */
48
+ const registryBySvg = /* @__PURE__ */ new WeakMap();
65
49
  function attachDiagnosticsOverlay(mounted, options = {}) {
66
- assertMounted(mounted);
67
- const registry = registryBySvg.get(mounted.svg) ?? {};
68
- registryBySvg.set(mounted.svg, registry);
69
- // Replace any previously attached overlay: retire its token so its
70
- // `update()`/`destroy()` become no-ops, then remove its DOM group.
71
- registry.current?.handle.destroy();
72
- const token = Symbol("diagnostics-overlay");
73
- const group = document.createElementNS(SVG_NS, "g");
74
- group.setAttribute(DIAGNOSTICS_ATTR, "");
75
- mounted.svg.appendChild(group);
76
- const render = () => renderOverlay(group, mounted, options);
77
- render();
78
- const unsubscribe = subscribeToLiveUpdates(mounted, render);
79
- const isCurrent = () => registry.current?.token === token;
80
- const handle = {
81
- update() {
82
- if (!isCurrent())
83
- return;
84
- render();
85
- },
86
- destroy() {
87
- if (!isCurrent())
88
- return;
89
- unsubscribe();
90
- group.parentNode?.removeChild(group);
91
- registry.current = undefined;
92
- },
93
- };
94
- registry.current = { token, handle };
95
- return handle;
50
+ assertMounted(mounted);
51
+ const registry = registryBySvg.get(mounted.svg) ?? {};
52
+ registryBySvg.set(mounted.svg, registry);
53
+ registry.current?.handle.destroy();
54
+ const token = /* @__PURE__ */ Symbol("diagnostics-overlay");
55
+ const group = document.createElementNS(SVG_NS, "g");
56
+ group.setAttribute(DIAGNOSTICS_ATTR, "");
57
+ mounted.svg.appendChild(group);
58
+ const render = () => renderOverlay(group, mounted, options);
59
+ render();
60
+ const unsubscribe = subscribeToLiveUpdates(mounted, render);
61
+ const isCurrent = () => registry.current?.token === token;
62
+ const handle = {
63
+ update() {
64
+ if (!isCurrent()) return;
65
+ render();
66
+ },
67
+ destroy() {
68
+ if (!isCurrent()) return;
69
+ unsubscribe();
70
+ group.parentNode?.removeChild(group);
71
+ registry.current = void 0;
72
+ }
73
+ };
74
+ registry.current = { token, handle };
75
+ return handle;
96
76
  }
97
77
  function assertMounted(mounted) {
98
- if (!mounted.svg.isConnected) {
99
- throw new RenderError("MOUNT_DESTROYED", "Cannot attach a diagnostics overlay to a destroyed mount");
100
- }
78
+ if (!mounted.svg.isConnected) {
79
+ throw new RenderError("MOUNT_DESTROYED", "Cannot attach a diagnostics overlay to a destroyed mount");
80
+ }
101
81
  }
102
- /**
103
- * When the mount has a controller, re-render on `progress-change` and
104
- * `camera-change` so the overlay tracks live scroll/camera state. Without a
105
- * controller, consumers call `update()` manually — returns a no-op teardown.
106
- */
107
82
  function subscribeToLiveUpdates(mounted, render) {
108
- const controller = mounted.controller;
109
- if (!controller)
110
- return () => undefined;
111
- const onProgressChange = () => render();
112
- const onCameraChange = () => render();
113
- controller.on("progress-change", onProgressChange);
114
- controller.on("camera-change", onCameraChange);
115
- return () => {
116
- controller.off("progress-change", onProgressChange);
117
- controller.off("camera-change", onCameraChange);
118
- };
119
- }
120
- // ── Rendering ────────────────────────────────────────────────────────────────
83
+ const controller = mounted.controller;
84
+ if (!controller) return () => void 0;
85
+ const onProgressChange = () => render();
86
+ const onCameraChange = () => render();
87
+ controller.on("progress-change", onProgressChange);
88
+ controller.on("camera-change", onCameraChange);
89
+ return () => {
90
+ controller.off("progress-change", onProgressChange);
91
+ controller.off("camera-change", onCameraChange);
92
+ };
93
+ }
121
94
  function renderOverlay(group, mounted, options) {
122
- clearChildren(group);
123
- const bundle = mounted.engine.bundle;
124
- if (!bundle)
125
- return;
126
- const layout = getResolvedProjectionLayout(bundle);
127
- const project = (x, y) => {
128
- const screen = projectToScreen(x, y, layout.cellSize, layout.selectedBounds.minX, layout.selectedBounds.minY, layout.padding.x, layout.padding.y);
129
- return { x: screen.screenX, y: screen.screenY };
130
- };
131
- const showGrid = options.grid ?? true;
132
- const showCoordinates = options.coordinates ?? false;
133
- const showAnchors = options.anchors ?? true;
134
- const showRoutes = options.routes ?? true;
135
- const showReadout = options.readout ?? true;
136
- if (showGrid)
137
- appendGrid(group, bundle, project);
138
- if (showCoordinates)
139
- appendCoordinates(group, bundle, project);
140
- if (showAnchors)
141
- appendAnchors(group, mounted, project);
142
- if (showRoutes)
143
- appendRoutes(group, mounted, project);
144
- if (showReadout)
145
- appendReadout(group, mounted, bundle);
95
+ clearChildren(group);
96
+ const bundle = mounted.engine.bundle;
97
+ if (!bundle) return;
98
+ const layout = getResolvedProjectionLayout(bundle);
99
+ const project = (x, y) => {
100
+ const screen = projectToScreen(
101
+ x,
102
+ y,
103
+ layout.cellSize,
104
+ layout.selectedBounds.minX,
105
+ layout.selectedBounds.minY,
106
+ layout.padding.x,
107
+ layout.padding.y
108
+ );
109
+ return { x: screen.screenX, y: screen.screenY };
110
+ };
111
+ const showGrid = options.grid ?? true;
112
+ const showCoordinates = options.coordinates ?? false;
113
+ const showAnchors = options.anchors ?? true;
114
+ const showRoutes = options.routes ?? true;
115
+ const showReadout = options.readout ?? true;
116
+ if (showGrid) appendGrid(group, bundle, project);
117
+ if (showCoordinates) appendCoordinates(group, bundle, project);
118
+ if (showAnchors) appendAnchors(group, mounted, project);
119
+ if (showRoutes) appendRoutes(group, mounted, project);
120
+ if (showReadout) appendReadout(group, mounted, bundle);
146
121
  }
147
122
  function appendGrid(group, bundle, project) {
148
- const [originX, originY] = bundle.floor.origin;
149
- const [columns, rows] = bundle.floor.size;
150
- for (let x = 0; x <= columns; x++) {
151
- appendGridLine(group, project(originX + x, originY), project(originX + x, originY + rows));
152
- }
153
- for (let y = 0; y <= rows; y++) {
154
- appendGridLine(group, project(originX, originY + y), project(originX + columns, originY + y));
155
- }
123
+ const [originX, originY] = bundle.floor.origin;
124
+ const [columns, rows] = bundle.floor.size;
125
+ for (let x = 0; x <= columns; x++) {
126
+ appendGridLine(group, project(originX + x, originY), project(originX + x, originY + rows));
127
+ }
128
+ for (let y = 0; y <= rows; y++) {
129
+ appendGridLine(group, project(originX, originY + y), project(originX + columns, originY + y));
130
+ }
156
131
  }
157
132
  function appendGridLine(group, start, end) {
158
- const path = document.createElementNS(SVG_NS, "path");
159
- path.setAttribute("d", `M ${start.x} ${start.y} L ${end.x} ${end.y}`);
160
- path.setAttribute("stroke", GRID_STROKE);
161
- path.setAttribute("stroke-width", "1");
162
- path.setAttribute("fill", "none");
163
- path.setAttribute("vector-effect", "non-scaling-stroke");
164
- group.appendChild(path);
133
+ const path = document.createElementNS(SVG_NS, "path");
134
+ path.setAttribute("d", `M ${start.x} ${start.y} L ${end.x} ${end.y}`);
135
+ path.setAttribute("stroke", GRID_STROKE);
136
+ path.setAttribute("stroke-width", "1");
137
+ path.setAttribute("fill", "none");
138
+ path.setAttribute("vector-effect", "non-scaling-stroke");
139
+ group.appendChild(path);
165
140
  }
166
141
  function appendCoordinates(group, bundle, project) {
167
- const [originX, originY] = bundle.floor.origin;
168
- const [columns, rows] = bundle.floor.size;
169
- for (let x = 0; x <= columns; x++) {
170
- for (let y = 0; y <= rows; y++) {
171
- const gridX = originX + x;
172
- const gridY = originY + y;
173
- const point = project(gridX, gridY);
174
- const text = document.createElementNS(SVG_NS, "text");
175
- text.setAttribute("x", String(point.x));
176
- text.setAttribute("y", String(point.y));
177
- text.setAttribute("font-size", "8");
178
- text.setAttribute("fill", TEXT_FILL);
179
- text.textContent = `${gridX},${gridY}`;
180
- group.appendChild(text);
181
- }
142
+ const [originX, originY] = bundle.floor.origin;
143
+ const [columns, rows] = bundle.floor.size;
144
+ for (let x = 0; x <= columns; x++) {
145
+ for (let y = 0; y <= rows; y++) {
146
+ const gridX = originX + x;
147
+ const gridY = originY + y;
148
+ const point = project(gridX, gridY);
149
+ const text = document.createElementNS(SVG_NS, "text");
150
+ text.setAttribute("x", String(point.x));
151
+ text.setAttribute("y", String(point.y));
152
+ text.setAttribute("font-size", "8");
153
+ text.setAttribute("fill", TEXT_FILL);
154
+ text.textContent = `${gridX},${gridY}`;
155
+ group.appendChild(text);
182
156
  }
157
+ }
183
158
  }
184
159
  function appendAnchors(group, mounted, project) {
185
- for (const element of mounted.engine.getFrameUpdates()) {
186
- if (element.lifecycle === "removed")
187
- continue;
188
- // The element's anchor is the node's local transform origin, which the
189
- // renderer always places at `projectToScreen(pos + size, ...)` — see
190
- // `applyElementTransform()` in rendering-engine.ts. No separate anchor
191
- // ratio math is needed here to locate the point itself.
192
- const point = project(element.pos[0] + element.size, element.pos[1] + element.size);
193
- const circle = document.createElementNS(SVG_NS, "circle");
194
- circle.setAttribute("cx", String(point.x));
195
- circle.setAttribute("cy", String(point.y));
196
- circle.setAttribute("r", "3");
197
- circle.setAttribute("fill", ANCHOR_FILL);
198
- group.appendChild(circle);
199
- }
160
+ for (const element of mounted.engine.getFrameUpdates()) {
161
+ if (element.lifecycle === "removed") continue;
162
+ const point = project(element.pos[0] + element.size, element.pos[1] + element.size);
163
+ const circle = document.createElementNS(SVG_NS, "circle");
164
+ circle.setAttribute("cx", String(point.x));
165
+ circle.setAttribute("cy", String(point.y));
166
+ circle.setAttribute("r", "3");
167
+ circle.setAttribute("fill", ANCHOR_FILL);
168
+ group.appendChild(circle);
169
+ }
200
170
  }
201
171
  function appendRoutes(group, mounted, project) {
202
- for (const connector of mounted.engine.getConnectorFrameUpdates()) {
203
- if (connector.lifecycle === "removed")
204
- continue;
205
- for (const [x, y] of connector.route) {
206
- const point = project(x, y);
207
- const rect = document.createElementNS(SVG_NS, "rect");
208
- rect.setAttribute("x", String(point.x - 2));
209
- rect.setAttribute("y", String(point.y - 2));
210
- rect.setAttribute("width", "4");
211
- rect.setAttribute("height", "4");
212
- rect.setAttribute("fill", ROUTE_FILL);
213
- group.appendChild(rect);
214
- }
172
+ for (const connector of mounted.engine.getConnectorFrameUpdates()) {
173
+ if (connector.lifecycle === "removed") continue;
174
+ for (const [x, y] of connector.route) {
175
+ const point = project(x, y);
176
+ const rect = document.createElementNS(SVG_NS, "rect");
177
+ rect.setAttribute("x", String(point.x - 2));
178
+ rect.setAttribute("y", String(point.y - 2));
179
+ rect.setAttribute("width", "4");
180
+ rect.setAttribute("height", "4");
181
+ rect.setAttribute("fill", ROUTE_FILL);
182
+ group.appendChild(rect);
215
183
  }
184
+ }
216
185
  }
217
186
  function appendReadout(group, mounted, bundle) {
218
- const viewBox = mounted.controller ? mounted.controller.getCameraState().viewBox : getResolvedViewBox(bundle);
219
- const progress = mounted.controller ? mounted.controller.getProgress() : mounted.engine.getProgress();
220
- // Resolved from the engine's own progress-to-stop mapping (not
221
- // `controller.currentScene`, which only advances on explicit scene
222
- // navigation and would go stale under scroll-driven progress changes).
223
- const sceneId = mounted.controller ? mounted.engine.getCurrentState()?.id : undefined;
224
- const text = document.createElementNS(SVG_NS, "text");
225
- text.setAttribute("x", String(viewBox.minX + 4));
226
- text.setAttribute("y", String(viewBox.minY + 10));
227
- text.setAttribute("font-size", "10");
228
- text.setAttribute("fill", TEXT_FILL);
229
- text.textContent = sceneId
230
- ? `scene ${sceneId} · progress ${roundProgress(progress)}`
231
- : `progress ${roundProgress(progress)}`;
232
- group.appendChild(text);
187
+ const viewBox = mounted.controller ? mounted.controller.getCameraState().viewBox : getResolvedViewBox(bundle);
188
+ const progress = mounted.controller ? mounted.controller.getProgress() : mounted.engine.getProgress();
189
+ const sceneId = mounted.controller ? mounted.engine.getCurrentState()?.id : void 0;
190
+ const text = document.createElementNS(SVG_NS, "text");
191
+ text.setAttribute("x", String(viewBox.minX + 4));
192
+ text.setAttribute("y", String(viewBox.minY + 10));
193
+ text.setAttribute("font-size", "10");
194
+ text.setAttribute("fill", TEXT_FILL);
195
+ text.textContent = sceneId ? `scene ${sceneId} \xB7 progress ${roundProgress(progress)}` : `progress ${roundProgress(progress)}`;
196
+ group.appendChild(text);
233
197
  }
234
198
  function roundProgress(progress) {
235
- return Math.round(progress * 1000) / 1000;
199
+ return Math.round(progress * 1e3) / 1e3;
236
200
  }
237
201
  function clearChildren(node) {
238
- while (node.firstChild)
239
- node.removeChild(node.firstChild);
202
+ while (node.firstChild) node.removeChild(node.firstChild);
240
203
  }
241
204
 
242
205
  const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
243
206
  const XLINK_NAMESPACE = "http://www.w3.org/1999/xlink";
244
207
  const DIAGNOSTICS_OVERLAY_SELECTOR = "[data-iso-diagnostics]";
245
- /**
246
- * Serialize a mounted scene to a standalone SVG document string.
247
- *
248
- * The exported SVG carries the runtime stylesheet and current visual state
249
- * as-is. Ambient/entry/exit CSS animations are not restarted or awaited by
250
- * the export; the snapshot reflects the un-animated base state at the
251
- * resolved progress.
252
- */
253
208
  async function exportSceneSvg(mounted, options = {}) {
254
- assertNotDestroyed(mounted);
255
- assertValidSnapshotOptions(options);
256
- return withRestoredProgress(mounted, options.progress, async () => {
257
- const clone = prepareClone(mounted.svg, options);
258
- if (options.inlineAssets ?? true) {
259
- await inlineImageAssets(clone);
260
- }
261
- return serializeSvg(clone);
262
- });
209
+ assertNotDestroyed(mounted);
210
+ assertValidSnapshotOptions(options);
211
+ return withRestoredProgress(mounted, options.progress, async () => {
212
+ const clone = prepareClone(mounted.svg, options);
213
+ if (options.inlineAssets ?? true) {
214
+ await inlineImageAssets(clone);
215
+ }
216
+ return serializeSvg(clone);
217
+ });
263
218
  }
264
- /** Rasterize a mounted scene to a PNG blob. */
265
219
  async function exportScenePng(mounted, options = {}) {
266
- assertNotDestroyed(mounted);
267
- assertValidPngSnapshotOptions(options);
268
- const scale = options.scale ?? 2;
269
- return withRestoredProgress(mounted, options.progress, async () => {
270
- const clone = prepareClone(mounted.svg, options);
271
- // PNG export always inlines assets: external hrefs taint or fail canvas rasterization.
272
- await inlineImageAssets(clone);
273
- const svgString = serializeSvg(clone);
274
- const viewBox = readViewBox(clone);
275
- return rasterize(svgString, viewBox, scale);
276
- });
220
+ assertNotDestroyed(mounted);
221
+ assertValidPngSnapshotOptions(options);
222
+ const scale = options.scale ?? 2;
223
+ return withRestoredProgress(mounted, options.progress, async () => {
224
+ const clone = prepareClone(mounted.svg, options);
225
+ await inlineImageAssets(clone);
226
+ const svgString = serializeSvg(clone);
227
+ const viewBox = readViewBox(clone);
228
+ return rasterize(svgString, viewBox, scale);
229
+ });
277
230
  }
278
- // ── Shared step 1-3: destroyed check, option validation, progress restore ──
279
231
  function assertNotDestroyed(mounted) {
280
- if (!mounted.svg.isConnected) {
281
- throw new RenderError("EXPORT_TARGET_DESTROYED", "Cannot export a scene whose mount was destroyed");
282
- }
232
+ if (!mounted.svg.isConnected) {
233
+ throw new RenderError("EXPORT_TARGET_DESTROYED", "Cannot export a scene whose mount was destroyed");
234
+ }
283
235
  }
284
236
  function assertValidSnapshotOptions(options) {
285
- if (options.progress !== undefined && !isValidProgress(options.progress)) {
286
- throw new RenderError("EXPORT_INVALID_OPTIONS", "progress must be a finite number within [0, 1]", {
287
- progress: options.progress,
288
- });
289
- }
237
+ if (options.progress !== void 0 && !isValidProgress(options.progress)) {
238
+ throw new RenderError("EXPORT_INVALID_OPTIONS", "progress must be a finite number within [0, 1]", {
239
+ progress: options.progress
240
+ });
241
+ }
290
242
  }
291
243
  function assertValidPngSnapshotOptions(options) {
292
- assertValidSnapshotOptions(options);
293
- if (options.scale !== undefined && !isValidScale(options.scale)) {
294
- throw new RenderError("EXPORT_INVALID_OPTIONS", "scale must be a finite number greater than 0", {
295
- scale: options.scale,
296
- });
297
- }
298
- if (options.inlineAssets === false) {
299
- throw new RenderError("EXPORT_INVALID_OPTIONS", "exportScenePng() always inlines assets; inlineAssets: false is unsupported for PNG export");
300
- }
244
+ assertValidSnapshotOptions(options);
245
+ if (options.scale !== void 0 && !isValidScale(options.scale)) {
246
+ throw new RenderError("EXPORT_INVALID_OPTIONS", "scale must be a finite number greater than 0", {
247
+ scale: options.scale
248
+ });
249
+ }
250
+ if (options.inlineAssets === false) {
251
+ throw new RenderError(
252
+ "EXPORT_INVALID_OPTIONS",
253
+ "exportScenePng() always inlines assets; inlineAssets: false is unsupported for PNG export"
254
+ );
255
+ }
301
256
  }
302
257
  function isValidProgress(progress) {
303
- return Number.isFinite(progress) && progress >= 0 && progress <= 1;
258
+ return Number.isFinite(progress) && progress >= 0 && progress <= 1;
304
259
  }
305
260
  function isValidScale(scale) {
306
- return Number.isFinite(scale) && scale > 0;
307
- }
308
- /**
309
- * Runs `run` at the requested `progress`, restoring the engine's prior
310
- * progress afterwards on every code path (success or rejection). When
311
- * `progress` is omitted, `run` executes at the scene's current progress and
312
- * no restoration is needed.
313
- */
261
+ return Number.isFinite(scale) && scale > 0;
262
+ }
314
263
  async function withRestoredProgress(mounted, progress, run) {
315
- if (progress === undefined) {
316
- return run();
317
- }
318
- const previousProgress = mounted.engine.getProgress();
319
- try {
320
- mounted.engine.setProgress(progress);
321
- applyControllerFrame(mounted);
322
- return await run();
323
- }
324
- finally {
325
- mounted.engine.setProgress(previousProgress);
326
- applyControllerFrame(mounted);
327
- }
264
+ if (progress === void 0) {
265
+ return run();
266
+ }
267
+ const previousProgress = mounted.engine.getProgress();
268
+ try {
269
+ mounted.engine.setProgress(progress);
270
+ applyControllerFrame(mounted);
271
+ return await run();
272
+ } finally {
273
+ mounted.engine.setProgress(previousProgress);
274
+ applyControllerFrame(mounted);
275
+ }
328
276
  }
329
- /**
330
- * Applies the engine's current frame to the live SVG so DOM state (element
331
- * transforms, generated content, connector routes, and lifecycle visibility)
332
- * matches the engine's resolved progress. Mirrors the non-animated portion of
333
- * `AnimationController`'s frame-apply path: transforms/content via
334
- * `updateElementTransforms()`, plus visibility driven by the resolved
335
- * lifecycle state. CSS entry/exit animation classes are intentionally not
336
- * (re)triggered here, matching the spec's requirement that ambient/entry
337
- * animations are irrelevant to export output.
338
- */
339
277
  function applyControllerFrame(mounted) {
340
- const svg = mounted.svg;
341
- const elementUpdates = mounted.engine.getFrameUpdates().map((update) => ({
342
- id: update.id,
343
- asset: update.asset,
344
- pos: update.pos,
345
- size: update.size,
346
- layer: update.layer,
347
- presence: update.lifecycle,
348
- enter: update.entry,
349
- exit: update.exit,
350
- ambient: update.ambient,
351
- text: update.text,
352
- primitive: update.primitive,
353
- }));
354
- const connectorUpdates = mounted.engine.getConnectorFrameUpdates().map((update) => ({
355
- id: update.id,
356
- route: update.route,
357
- layer: update.layer,
358
- presence: update.lifecycle,
359
- style: update.style,
360
- start: update.start,
361
- end: update.end,
362
- direction: update.direction,
363
- enter: update.entry,
364
- exit: update.exit,
365
- ambient: update.ambient,
366
- }));
367
- updateElementTransforms(svg, elementUpdates, connectorUpdates);
368
- applyElementVisibility(svg, elementUpdates);
369
- applyConnectorVisibility(svg, connectorUpdates);
278
+ const svg = mounted.svg;
279
+ const elementUpdates = mounted.engine.getFrameUpdates().map(
280
+ (update) => ({
281
+ id: update.id,
282
+ asset: update.asset,
283
+ pos: update.pos,
284
+ size: update.size,
285
+ layer: update.layer,
286
+ presence: update.lifecycle,
287
+ enter: update.entry,
288
+ exit: update.exit,
289
+ ambient: update.ambient,
290
+ text: update.text,
291
+ primitive: update.primitive,
292
+ activity: update.activity
293
+ })
294
+ );
295
+ const connectorUpdates = mounted.engine.getConnectorFrameUpdates().map(
296
+ (update) => ({
297
+ id: update.id,
298
+ route: update.route,
299
+ layer: update.layer,
300
+ presence: update.lifecycle,
301
+ style: update.style,
302
+ start: update.start,
303
+ end: update.end,
304
+ direction: update.direction,
305
+ message: update.message,
306
+ enter: update.entry,
307
+ exit: update.exit,
308
+ ambient: update.ambient
309
+ })
310
+ );
311
+ updateElementTransforms(svg, elementUpdates, connectorUpdates);
312
+ applyElementVisibility(svg, elementUpdates);
313
+ applyConnectorVisibility(svg, connectorUpdates);
370
314
  }
371
315
  function applyElementVisibility(svg, elements) {
372
- for (const element of elements) {
373
- const state = getElementState(svg, element.id);
374
- if (!state)
375
- continue;
376
- if (element.presence === "removed") {
377
- state.isHidden = true;
378
- hideElementAfterExit(state.node);
379
- }
380
- else if (state.isHidden) {
381
- state.isHidden = false;
382
- unhideElementOnReadd(state.node);
383
- }
316
+ for (const element of elements) {
317
+ const state = getElementState(svg, element.id);
318
+ if (!state) continue;
319
+ if (element.presence === "removed") {
320
+ state.isHidden = true;
321
+ hideElementAfterExit(state.node);
322
+ } else if (state.isHidden) {
323
+ state.isHidden = false;
324
+ unhideElementOnReadd(state.node);
384
325
  }
326
+ }
385
327
  }
386
328
  function applyConnectorVisibility(svg, connectors) {
387
- for (const connector of connectors) {
388
- const state = getConnectorState(svg, connector.id);
389
- if (!state)
390
- continue;
391
- if (connector.presence === "removed") {
392
- state.isHidden = true;
393
- hideElementAfterExit(state.node);
394
- }
395
- else if (state.isHidden) {
396
- state.isHidden = false;
397
- unhideElementOnReadd(state.node);
398
- }
329
+ for (const connector of connectors) {
330
+ const state = getConnectorState(svg, connector.id);
331
+ if (!state) continue;
332
+ if (connector.presence === "removed") {
333
+ state.isHidden = true;
334
+ hideElementAfterExit(state.node);
335
+ } else if (state.isHidden) {
336
+ state.isHidden = false;
337
+ unhideElementOnReadd(state.node);
399
338
  }
339
+ }
400
340
  }
401
- // ── Step 4-5: clone and prepare ────────────────────────────────────────────
402
341
  function prepareClone(svg, options) {
403
- const clone = svg.cloneNode(true);
404
- const viewBox = readViewBox(clone);
405
- clone.setAttribute("width", String(viewBox.width));
406
- clone.setAttribute("height", String(viewBox.height));
407
- clone.setAttribute("xmlns", SVG_NAMESPACE);
408
- clone.setAttribute("xmlns:xlink", XLINK_NAMESPACE);
409
- if (options.background) {
410
- const rect = document.createElementNS(SVG_NAMESPACE, "rect");
411
- rect.setAttribute("x", String(viewBox.minX));
412
- rect.setAttribute("y", String(viewBox.minY));
413
- rect.setAttribute("width", String(viewBox.width));
414
- rect.setAttribute("height", String(viewBox.height));
415
- rect.setAttribute("fill", options.background);
416
- insertAsFirstChild(clone, rect);
417
- }
418
- const diagnosticsOverlay = clone.querySelector(DIAGNOSTICS_OVERLAY_SELECTOR);
419
- diagnosticsOverlay?.parentNode?.removeChild(diagnosticsOverlay);
420
- return clone;
342
+ const clone = svg.cloneNode(true);
343
+ const viewBox = readViewBox(clone);
344
+ clone.setAttribute("width", String(viewBox.width));
345
+ clone.setAttribute("height", String(viewBox.height));
346
+ clone.setAttribute("xmlns", SVG_NAMESPACE);
347
+ clone.setAttribute("xmlns:xlink", XLINK_NAMESPACE);
348
+ if (options.background) {
349
+ const rect = document.createElementNS(SVG_NAMESPACE, "rect");
350
+ rect.setAttribute("x", String(viewBox.minX));
351
+ rect.setAttribute("y", String(viewBox.minY));
352
+ rect.setAttribute("width", String(viewBox.width));
353
+ rect.setAttribute("height", String(viewBox.height));
354
+ rect.setAttribute("fill", options.background);
355
+ insertAsFirstChild(clone, rect);
356
+ }
357
+ const diagnosticsOverlay = clone.querySelector(DIAGNOSTICS_OVERLAY_SELECTOR);
358
+ diagnosticsOverlay?.parentNode?.removeChild(diagnosticsOverlay);
359
+ return clone;
421
360
  }
422
361
  function insertAsFirstChild(parent, child) {
423
- if (parent.firstChild) {
424
- parent.insertBefore(child, parent.firstChild);
425
- }
426
- else {
427
- parent.appendChild(child);
428
- }
362
+ if (parent.firstChild) {
363
+ parent.insertBefore(child, parent.firstChild);
364
+ } else {
365
+ parent.appendChild(child);
366
+ }
429
367
  }
430
368
  function readViewBox(svg) {
431
- const raw = svg.getAttribute("viewBox");
432
- const parts = (raw ?? "").trim().split(/\s+/).map(Number);
433
- if (parts.length !== 4 || parts.some((part) => !Number.isFinite(part))) {
434
- return { minX: 0, minY: 0, width: 0, height: 0 };
435
- }
436
- const [minX, minY, width, height] = parts;
437
- return { minX, minY, width, height };
369
+ const raw = svg.getAttribute("viewBox");
370
+ const parts = (raw ?? "").trim().split(/\s+/).map(Number);
371
+ if (parts.length !== 4 || parts.some((part) => !Number.isFinite(part))) {
372
+ return { minX: 0, minY: 0, width: 0, height: 0 };
373
+ }
374
+ const [minX, minY, width, height] = parts;
375
+ return { minX, minY, width, height };
438
376
  }
439
- // ── Step 6: asset inlining ──────────────────────────────────────────────────
440
377
  async function inlineImageAssets(clone) {
441
- const images = [...clone.querySelectorAll("image")];
442
- await Promise.all(images.map((image) => inlineImageHref(image)));
378
+ const images = [...clone.querySelectorAll("image")];
379
+ await Promise.all(images.map((image) => inlineImageHref(image)));
443
380
  }
444
381
  async function inlineImageHref(image) {
445
- const href = image.getAttribute("href") ?? image.getAttributeNS(XLINK_NAMESPACE, "href");
446
- if (!href || href.startsWith("data:"))
447
- return;
448
- const dataUri = await fetchAsDataUri(href);
449
- image.setAttribute("href", dataUri);
450
- image.setAttributeNS(XLINK_NAMESPACE, "href", dataUri);
382
+ const href = image.getAttribute("href") ?? image.getAttributeNS(XLINK_NAMESPACE, "href");
383
+ if (!href || href.startsWith("data:")) return;
384
+ const dataUri = await fetchAsDataUri(href);
385
+ image.setAttribute("href", dataUri);
386
+ image.setAttributeNS(XLINK_NAMESPACE, "href", dataUri);
451
387
  }
452
388
  async function fetchAsDataUri(url) {
453
- let response;
454
- try {
455
- response = await fetch(url);
456
- }
457
- catch {
458
- throw new RenderError("EXPORT_ASSET_FETCH_FAILED", `Failed to fetch asset for inlining: ${url}`, { url });
459
- }
460
- if (!response.ok) {
461
- throw new RenderError("EXPORT_ASSET_FETCH_FAILED", `Failed to fetch asset for inlining: ${url}`, { url });
462
- }
463
- const contentType = response.headers.get("content-type") ?? "application/octet-stream";
464
- const buffer = await response.arrayBuffer();
465
- return `data:${contentType};base64,${arrayBufferToBase64(buffer)}`;
389
+ let response;
390
+ try {
391
+ response = await fetch(url);
392
+ } catch {
393
+ throw new RenderError("EXPORT_ASSET_FETCH_FAILED", `Failed to fetch asset for inlining: ${url}`, { url });
394
+ }
395
+ if (!response.ok) {
396
+ throw new RenderError("EXPORT_ASSET_FETCH_FAILED", `Failed to fetch asset for inlining: ${url}`, { url });
397
+ }
398
+ const contentType = response.headers.get("content-type") ?? "application/octet-stream";
399
+ const buffer = await response.arrayBuffer();
400
+ return `data:${contentType};base64,${arrayBufferToBase64(buffer)}`;
466
401
  }
467
402
  function arrayBufferToBase64(buffer) {
468
- let binary = "";
469
- const bytes = new Uint8Array(buffer);
470
- for (const byte of bytes)
471
- binary += String.fromCharCode(byte);
472
- return btoa(binary);
403
+ let binary = "";
404
+ const bytes = new Uint8Array(buffer);
405
+ for (const byte of bytes) binary += String.fromCharCode(byte);
406
+ return btoa(binary);
473
407
  }
474
- // ── Step 7: SVG serialization ───────────────────────────────────────────────
475
408
  function serializeSvg(clone) {
476
- const serializer = new XMLSerializer();
477
- return `<?xml version="1.0" encoding="UTF-8"?>\n${serializer.serializeToString(clone)}`;
409
+ const serializer = new XMLSerializer();
410
+ return `<?xml version="1.0" encoding="UTF-8"?>
411
+ ${serializer.serializeToString(clone)}`;
478
412
  }
479
- // ── Step 8: PNG rasterization ────────────────────────────────────────────────
480
413
  async function rasterize(svgString, viewBox, scale) {
481
- const width = Math.ceil(viewBox.width * scale);
482
- const height = Math.ceil(viewBox.height * scale);
483
- const svgBlob = new Blob([svgString], { type: "image/svg+xml" });
484
- const objectUrl = URL.createObjectURL(svgBlob);
485
- try {
486
- const image = await loadImage(objectUrl);
487
- const canvas = document.createElement("canvas");
488
- canvas.width = width;
489
- canvas.height = height;
490
- const context = canvas.getContext("2d");
491
- if (!context) {
492
- throw new RenderError("EXPORT_RASTERIZE_FAILED", "Canvas 2D context is unavailable");
493
- }
494
- context.drawImage(image, 0, 0, width, height);
495
- const blob = await new Promise((resolve) => {
496
- canvas.toBlob(resolve, "image/png");
497
- });
498
- if (!blob) {
499
- throw new RenderError("EXPORT_RASTERIZE_FAILED", "Canvas failed to encode PNG output");
500
- }
501
- return blob;
414
+ const width = Math.ceil(viewBox.width * scale);
415
+ const height = Math.ceil(viewBox.height * scale);
416
+ const svgBlob = new Blob([svgString], { type: "image/svg+xml" });
417
+ const objectUrl = URL.createObjectURL(svgBlob);
418
+ try {
419
+ const image = await loadImage(objectUrl);
420
+ const canvas = document.createElement("canvas");
421
+ canvas.width = width;
422
+ canvas.height = height;
423
+ const context = canvas.getContext("2d");
424
+ if (!context) {
425
+ throw new RenderError("EXPORT_RASTERIZE_FAILED", "Canvas 2D context is unavailable");
502
426
  }
503
- finally {
504
- URL.revokeObjectURL(objectUrl);
427
+ context.drawImage(image, 0, 0, width, height);
428
+ const blob = await new Promise((resolve) => {
429
+ canvas.toBlob(resolve, "image/png");
430
+ });
431
+ if (!blob) {
432
+ throw new RenderError("EXPORT_RASTERIZE_FAILED", "Canvas failed to encode PNG output");
505
433
  }
434
+ return blob;
435
+ } finally {
436
+ URL.revokeObjectURL(objectUrl);
437
+ }
506
438
  }
507
439
  function loadImage(src) {
508
- return new Promise((resolve, reject) => {
509
- const image = new Image();
510
- image.onload = () => resolve(image);
511
- image.onerror = () => reject(new RenderError("EXPORT_RASTERIZE_FAILED", "Failed to load rasterization source image"));
512
- image.src = src;
513
- });
440
+ return new Promise((resolve, reject) => {
441
+ const image = new Image();
442
+ image.onload = () => resolve(image);
443
+ image.onerror = () => reject(new RenderError("EXPORT_RASTERIZE_FAILED", "Failed to load rasterization source image"));
444
+ image.src = src;
445
+ });
514
446
  }
515
447
 
516
448
  export { RenderError, attachDiagnosticsOverlay, exportScenePng, exportSceneSvg, guardEntryAnimation, guardExitAnimation, guardLifecycleStatus, projectToScreen };