@scenar/cli 0.2.0 → 0.2.1

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.
@@ -9,6 +9,17 @@ import { posix } from "node:path";
9
9
  * @scenar/react theme + styles so the bundle is self-contained, and scopes the
10
10
  * tree with `SCENAR_CLASS` + `DemoViewport` exactly as the authoring surfaces do.
11
11
  *
12
+ * Interactions: the entry wires the full host-side interaction system —
13
+ * `useStepInteractions` + `<Cursor>` + `<ViewportTransformLayer>` — so packed
14
+ * embeds run the same narration-synced cursor moves, clicks, typing, hovers,
15
+ * drags, scrolls, and viewport transitions as the in-app player. (`ScenarioPlayer`
16
+ * itself owns none of this; the host must wire it, per DemoViewport's contract.)
17
+ *
18
+ * Theme: the embed reads `?theme` from its own URL and applies the `dark` class
19
+ * alongside `SCENAR_CLASS` when `theme=dark` (matching the `.scenar.dark` token
20
+ * selector). The default is light, so existing embeds are unaffected; a host can
21
+ * theme-sync simply by framing `…/?theme=dark`.
22
+ *
12
23
  * Pure function, no side effects — the caller writes the result to disk.
13
24
  */
14
25
  export function generateEmbedEntry(input) {
@@ -17,11 +28,24 @@ export function generateEmbedEntry(input) {
17
28
  const renderImport = toPosix(input.renderFilePath).replace(/\.[^.]+$/, "");
18
29
  const lines = [];
19
30
  // --- Imports ---
31
+ // React hooks drive the host-side interaction system (the same one the
32
+ // in-app player wires): step tracking, cursor target, ripple/drag flags, and
33
+ // the viewport transform. Without these, the embed would render content but
34
+ // silently drop every cursor move and mid-step interaction.
35
+ lines.push(`import { useCallback, useRef, useState } from "react";`);
20
36
  lines.push(`import { createRoot } from "react-dom/client";`);
21
- const reactImports = input.hasNarration
22
- ? `ScenarioPlayer, DemoViewport, SCENAR_CLASS, useNarrationManifest`
23
- : `ScenarioPlayer, DemoViewport, SCENAR_CLASS`;
24
- lines.push(`import { ${reactImports} } from "@scenar/react";`);
37
+ const reactImports = [
38
+ "ScenarioPlayer",
39
+ "DemoViewport",
40
+ "SCENAR_CLASS",
41
+ "Cursor",
42
+ "useStepInteractions",
43
+ "ViewportTransformLayer",
44
+ "VIEWPORT_TRANSFORM_IDENTITY",
45
+ ];
46
+ if (input.hasNarration)
47
+ reactImports.push("useNarrationManifest");
48
+ lines.push(`import { ${reactImports.join(", ")} } from "@scenar/react";`);
25
49
  lines.push(`import "@scenar/react/theme.css";`);
26
50
  lines.push(`import "@scenar/react/styles.css";`);
27
51
  lines.push(`import { renderStep } from ${JSON.stringify(renderImport)};`);
@@ -54,26 +78,73 @@ export function generateEmbedEntry(input) {
54
78
  lines.push(``);
55
79
  lines.push(`const _resolveManifestUrl = () => "./narration/manifest.json";`);
56
80
  }
81
+ // --- Theme: opt-in dark via ?theme=dark (default light; backward-compatible) ---
82
+ // Read once at module load from the embed's own URL. `theme=dark` adds the
83
+ // `dark` class next to SCENAR_CLASS so the `.scenar.dark` tokens apply; any
84
+ // other value (including absent) renders light, preserving prior behavior.
85
+ lines.push(``);
86
+ lines.push(`const _theme = (() => {`);
87
+ lines.push(` try { return new URLSearchParams(window.location.search).get("theme"); }`);
88
+ lines.push(` catch { return null; }`);
89
+ lines.push(`})();`);
90
+ lines.push(`const _rootClass = _theme === "dark" ? SCENAR_CLASS + " dark" : SCENAR_CLASS;`);
57
91
  // --- App tree ---
92
+ // The structure mirrors the in-app demo wiring exactly (see DemoViewport's
93
+ // contract and ViewportTransformLayer's "Cursor must be a sibling" invariant):
94
+ // DemoViewport(containerRef)
95
+ // └ ViewportTransformLayer(transform)
96
+ // └ ScenarioPlayer(embed, onStepChange)
97
+ // └ Cursor(target, containerRef) ← sibling, not a child
98
+ // `useStepInteractions` reads the current step's `interactions` and drives the
99
+ // cursor / ripple / drag / viewport state, synced to narration duration.
100
+ const manifestExpr = input.hasNarration ? "_manifest" : "undefined";
58
101
  lines.push(``);
59
102
  lines.push(`function _App() {`);
60
103
  if (input.hasNarration) {
61
104
  lines.push(` const _manifest = useNarrationManifest(${JSON.stringify(input.scenarioId)}, _resolveManifestUrl);`);
62
105
  }
106
+ lines.push(` const _containerRef = useRef<HTMLDivElement>(null);`);
107
+ lines.push(` const [_stepIndex, _setStepIndex] = useState(0);`);
108
+ lines.push(` const [_cursorTarget, _setCursorTarget] = useState<string | undefined>(undefined);`);
109
+ lines.push(` const [_showRipple, _setShowRipple] = useState(true);`);
110
+ lines.push(` const [_dragging, _setDragging] = useState(false);`);
111
+ lines.push(` const [_viewport, _setViewport] = useState(VIEWPORT_TRANSFORM_IDENTITY);`);
112
+ lines.push(``);
113
+ // Reset the cursor when the step changes so a prior step's target never
114
+ // lingers; the new step's interactions re-set it at their scheduled time.
115
+ lines.push(` const _handleStepChange = useCallback((_data: any, index: number) => {`);
116
+ lines.push(` _setStepIndex(index);`);
117
+ lines.push(` _setCursorTarget(undefined);`);
118
+ lines.push(` }, []);`);
119
+ lines.push(``);
120
+ lines.push(` useStepInteractions({`);
121
+ lines.push(` stepIndex: _stepIndex,`);
122
+ lines.push(` narrationManifest: ${manifestExpr},`);
123
+ lines.push(` containerRef: _containerRef,`);
124
+ lines.push(` setCursorTarget: _setCursorTarget,`);
125
+ lines.push(` setShowRipple: _setShowRipple,`);
126
+ lines.push(` setDragging: _setDragging,`);
127
+ lines.push(` setViewportTransform: _setViewport,`);
128
+ lines.push(` steps: _steps,`);
129
+ lines.push(` });`);
130
+ lines.push(``);
63
131
  lines.push(` const _bundle = {`);
64
132
  lines.push(` id: ${JSON.stringify(input.scenarioId)},`);
65
133
  lines.push(` steps: _steps,`);
66
- lines.push(` narrationManifest: ${input.hasNarration ? "_manifest" : "undefined"},`);
134
+ lines.push(` narrationManifest: ${manifestExpr},`);
67
135
  lines.push(` };`);
68
136
  const open = input.providersPath ? `<_Providers>` : ``;
69
137
  const close = input.providersPath ? `</_Providers>` : ``;
70
138
  lines.push(` return (`);
71
- lines.push(` <div className={SCENAR_CLASS}>`);
139
+ lines.push(` <div className={_rootClass}>`);
72
140
  lines.push(` ${open}`);
73
- lines.push(` <DemoViewport canonicalWidth={${input.canonicalWidth}} shellHeight={${input.shellHeight}}>`);
74
- lines.push(` <ScenarioPlayer bundle={_bundle} embed>`);
75
- lines.push(` {(data: any, stepIndex: number) => renderStep(data, stepIndex)}`);
76
- lines.push(` </ScenarioPlayer>`);
141
+ lines.push(` <DemoViewport containerRef={_containerRef} canonicalWidth={${input.canonicalWidth}} shellHeight={${input.shellHeight}}>`);
142
+ lines.push(` <ViewportTransformLayer transform={_viewport}>`);
143
+ lines.push(` <ScenarioPlayer bundle={_bundle} embed onStepChange={_handleStepChange}>`);
144
+ lines.push(` {(data: any, stepIndex: number) => renderStep(data, stepIndex)}`);
145
+ lines.push(` </ScenarioPlayer>`);
146
+ lines.push(` </ViewportTransformLayer>`);
147
+ lines.push(` <Cursor target={_cursorTarget} containerRef={_containerRef} showRipple={_showRipple} isDragging={_dragging} />`);
77
148
  lines.push(` </DemoViewport>`);
78
149
  lines.push(` ${close}`);
79
150
  lines.push(` </div>`);
@@ -1 +1 @@
1
- {"version":3,"file":"generate-embed-entry.js","sourceRoot":"","sources":["../../../src/pack/generate-embed-entry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"generate-embed-entry.js","sourceRoot":"","sources":["../../../src/pack/generate-embed-entry.ts"],"names":[],"mappings":"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"}
@@ -33,6 +33,17 @@ export interface EmbedEntryInput {
33
33
  * @scenar/react theme + styles so the bundle is self-contained, and scopes the
34
34
  * tree with `SCENAR_CLASS` + `DemoViewport` exactly as the authoring surfaces do.
35
35
  *
36
+ * Interactions: the entry wires the full host-side interaction system —
37
+ * `useStepInteractions` + `<Cursor>` + `<ViewportTransformLayer>` — so packed
38
+ * embeds run the same narration-synced cursor moves, clicks, typing, hovers,
39
+ * drags, scrolls, and viewport transitions as the in-app player. (`ScenarioPlayer`
40
+ * itself owns none of this; the host must wire it, per DemoViewport's contract.)
41
+ *
42
+ * Theme: the embed reads `?theme` from its own URL and applies the `dark` class
43
+ * alongside `SCENAR_CLASS` when `theme=dark` (matching the `.scenar.dark` token
44
+ * selector). The default is light, so existing embeds are unaffected; a host can
45
+ * theme-sync simply by framing `…/?theme=dark`.
46
+ *
36
47
  * Pure function, no side effects — the caller writes the result to disk.
37
48
  */
38
49
  export function generateEmbedEntry(input: EmbedEntryInput): string {
@@ -43,11 +54,23 @@ export function generateEmbedEntry(input: EmbedEntryInput): string {
43
54
  const lines: string[] = [];
44
55
 
45
56
  // --- Imports ---
57
+ // React hooks drive the host-side interaction system (the same one the
58
+ // in-app player wires): step tracking, cursor target, ripple/drag flags, and
59
+ // the viewport transform. Without these, the embed would render content but
60
+ // silently drop every cursor move and mid-step interaction.
61
+ lines.push(`import { useCallback, useRef, useState } from "react";`);
46
62
  lines.push(`import { createRoot } from "react-dom/client";`);
47
- const reactImports = input.hasNarration
48
- ? `ScenarioPlayer, DemoViewport, SCENAR_CLASS, useNarrationManifest`
49
- : `ScenarioPlayer, DemoViewport, SCENAR_CLASS`;
50
- lines.push(`import { ${reactImports} } from "@scenar/react";`);
63
+ const reactImports = [
64
+ "ScenarioPlayer",
65
+ "DemoViewport",
66
+ "SCENAR_CLASS",
67
+ "Cursor",
68
+ "useStepInteractions",
69
+ "ViewportTransformLayer",
70
+ "VIEWPORT_TRANSFORM_IDENTITY",
71
+ ];
72
+ if (input.hasNarration) reactImports.push("useNarrationManifest");
73
+ lines.push(`import { ${reactImports.join(", ")} } from "@scenar/react";`);
51
74
  lines.push(`import "@scenar/react/theme.css";`);
52
75
  lines.push(`import "@scenar/react/styles.css";`);
53
76
  lines.push(`import { renderStep } from ${JSON.stringify(renderImport)};`);
@@ -86,26 +109,74 @@ export function generateEmbedEntry(input: EmbedEntryInput): string {
86
109
  lines.push(`const _resolveManifestUrl = () => "./narration/manifest.json";`);
87
110
  }
88
111
 
112
+ // --- Theme: opt-in dark via ?theme=dark (default light; backward-compatible) ---
113
+ // Read once at module load from the embed's own URL. `theme=dark` adds the
114
+ // `dark` class next to SCENAR_CLASS so the `.scenar.dark` tokens apply; any
115
+ // other value (including absent) renders light, preserving prior behavior.
116
+ lines.push(``);
117
+ lines.push(`const _theme = (() => {`);
118
+ lines.push(` try { return new URLSearchParams(window.location.search).get("theme"); }`);
119
+ lines.push(` catch { return null; }`);
120
+ lines.push(`})();`);
121
+ lines.push(`const _rootClass = _theme === "dark" ? SCENAR_CLASS + " dark" : SCENAR_CLASS;`);
122
+
89
123
  // --- App tree ---
124
+ // The structure mirrors the in-app demo wiring exactly (see DemoViewport's
125
+ // contract and ViewportTransformLayer's "Cursor must be a sibling" invariant):
126
+ // DemoViewport(containerRef)
127
+ // └ ViewportTransformLayer(transform)
128
+ // └ ScenarioPlayer(embed, onStepChange)
129
+ // └ Cursor(target, containerRef) ← sibling, not a child
130
+ // `useStepInteractions` reads the current step's `interactions` and drives the
131
+ // cursor / ripple / drag / viewport state, synced to narration duration.
132
+ const manifestExpr = input.hasNarration ? "_manifest" : "undefined";
90
133
  lines.push(``);
91
134
  lines.push(`function _App() {`);
92
135
  if (input.hasNarration) {
93
136
  lines.push(` const _manifest = useNarrationManifest(${JSON.stringify(input.scenarioId)}, _resolveManifestUrl);`);
94
137
  }
138
+ lines.push(` const _containerRef = useRef<HTMLDivElement>(null);`);
139
+ lines.push(` const [_stepIndex, _setStepIndex] = useState(0);`);
140
+ lines.push(` const [_cursorTarget, _setCursorTarget] = useState<string | undefined>(undefined);`);
141
+ lines.push(` const [_showRipple, _setShowRipple] = useState(true);`);
142
+ lines.push(` const [_dragging, _setDragging] = useState(false);`);
143
+ lines.push(` const [_viewport, _setViewport] = useState(VIEWPORT_TRANSFORM_IDENTITY);`);
144
+ lines.push(``);
145
+ // Reset the cursor when the step changes so a prior step's target never
146
+ // lingers; the new step's interactions re-set it at their scheduled time.
147
+ lines.push(` const _handleStepChange = useCallback((_data: any, index: number) => {`);
148
+ lines.push(` _setStepIndex(index);`);
149
+ lines.push(` _setCursorTarget(undefined);`);
150
+ lines.push(` }, []);`);
151
+ lines.push(``);
152
+ lines.push(` useStepInteractions({`);
153
+ lines.push(` stepIndex: _stepIndex,`);
154
+ lines.push(` narrationManifest: ${manifestExpr},`);
155
+ lines.push(` containerRef: _containerRef,`);
156
+ lines.push(` setCursorTarget: _setCursorTarget,`);
157
+ lines.push(` setShowRipple: _setShowRipple,`);
158
+ lines.push(` setDragging: _setDragging,`);
159
+ lines.push(` setViewportTransform: _setViewport,`);
160
+ lines.push(` steps: _steps,`);
161
+ lines.push(` });`);
162
+ lines.push(``);
95
163
  lines.push(` const _bundle = {`);
96
164
  lines.push(` id: ${JSON.stringify(input.scenarioId)},`);
97
165
  lines.push(` steps: _steps,`);
98
- lines.push(` narrationManifest: ${input.hasNarration ? "_manifest" : "undefined"},`);
166
+ lines.push(` narrationManifest: ${manifestExpr},`);
99
167
  lines.push(` };`);
100
168
  const open = input.providersPath ? `<_Providers>` : ``;
101
169
  const close = input.providersPath ? `</_Providers>` : ``;
102
170
  lines.push(` return (`);
103
- lines.push(` <div className={SCENAR_CLASS}>`);
171
+ lines.push(` <div className={_rootClass}>`);
104
172
  lines.push(` ${open}`);
105
- lines.push(` <DemoViewport canonicalWidth={${input.canonicalWidth}} shellHeight={${input.shellHeight}}>`);
106
- lines.push(` <ScenarioPlayer bundle={_bundle} embed>`);
107
- lines.push(` {(data: any, stepIndex: number) => renderStep(data, stepIndex)}`);
108
- lines.push(` </ScenarioPlayer>`);
173
+ lines.push(` <DemoViewport containerRef={_containerRef} canonicalWidth={${input.canonicalWidth}} shellHeight={${input.shellHeight}}>`);
174
+ lines.push(` <ViewportTransformLayer transform={_viewport}>`);
175
+ lines.push(` <ScenarioPlayer bundle={_bundle} embed onStepChange={_handleStepChange}>`);
176
+ lines.push(` {(data: any, stepIndex: number) => renderStep(data, stepIndex)}`);
177
+ lines.push(` </ScenarioPlayer>`);
178
+ lines.push(` </ViewportTransformLayer>`);
179
+ lines.push(` <Cursor target={_cursorTarget} containerRef={_containerRef} showRipple={_showRipple} isDragging={_dragging} />`);
109
180
  lines.push(` </DemoViewport>`);
110
181
  lines.push(` ${close}`);
111
182
  lines.push(` </div>`);