@plaintake/scenario 1.23.0 → 1.30.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.
@@ -15,6 +15,13 @@ export declare const ValidationResultSchema: z.ZodObject<{
15
15
  session: "session";
16
16
  }>>;
17
17
  sha256: z.ZodString;
18
+ terminal: z.ZodDefault<z.ZodNullable<z.ZodObject<{
19
+ cols: z.ZodNumber;
20
+ rows: z.ZodNumber;
21
+ shell: z.ZodString;
22
+ command: z.ZodOptional<z.ZodArray<z.ZodString>>;
23
+ browser: z.ZodOptional<z.ZodLiteral<true>>;
24
+ }, z.core.$strip>>>;
18
25
  }, z.core.$strip>>;
19
26
  schema: z.ZodLiteral<"agent-demo.result/v1">;
20
27
  kind: z.ZodLiteral<"validate">;
@@ -48,6 +55,34 @@ export declare const RunCommandResultSchema: z.ZodObject<{
48
55
  bytes: z.ZodNumber;
49
56
  }, z.core.$strip>>;
50
57
  archivedPath: z.ZodOptional<z.ZodString>;
58
+ baseline: z.ZodOptional<z.ZodObject<{
59
+ path: z.ZodString;
60
+ status: z.ZodEnum<{
61
+ match: "match";
62
+ drift: "drift";
63
+ "scenario-changed": "scenario-changed";
64
+ absent: "absent";
65
+ skipped: "skipped";
66
+ updated: "updated";
67
+ }>;
68
+ differences: z.ZodArray<z.ZodObject<{
69
+ category: z.ZodEnum<{
70
+ step: "step";
71
+ assertion: "assertion";
72
+ "target-name": "target-name";
73
+ "target-role": "target-role";
74
+ "target-bounds": "target-bounds";
75
+ timing: "timing";
76
+ caption: "caption";
77
+ actor: "actor";
78
+ }>;
79
+ severity: z.ZodEnum<{
80
+ fail: "fail";
81
+ report: "report";
82
+ }>;
83
+ detail: z.ZodString;
84
+ }, z.core.$strip>>;
85
+ }, z.core.$strip>>;
51
86
  schema: z.ZodLiteral<"agent-demo.result/v1">;
52
87
  kind: z.ZodLiteral<"run">;
53
88
  ok: z.ZodBoolean;
@@ -72,6 +107,34 @@ export declare const CheckCommandResultSchema: z.ZodObject<{
72
107
  cueId: z.ZodString;
73
108
  detail: z.ZodString;
74
109
  }, z.core.$strip>>;
110
+ baseline: z.ZodOptional<z.ZodObject<{
111
+ path: z.ZodString;
112
+ status: z.ZodEnum<{
113
+ match: "match";
114
+ drift: "drift";
115
+ "scenario-changed": "scenario-changed";
116
+ absent: "absent";
117
+ skipped: "skipped";
118
+ updated: "updated";
119
+ }>;
120
+ differences: z.ZodArray<z.ZodObject<{
121
+ category: z.ZodEnum<{
122
+ step: "step";
123
+ assertion: "assertion";
124
+ "target-name": "target-name";
125
+ "target-role": "target-role";
126
+ "target-bounds": "target-bounds";
127
+ timing: "timing";
128
+ caption: "caption";
129
+ actor: "actor";
130
+ }>;
131
+ severity: z.ZodEnum<{
132
+ fail: "fail";
133
+ report: "report";
134
+ }>;
135
+ detail: z.ZodString;
136
+ }, z.core.$strip>>;
137
+ }, z.core.$strip>>;
75
138
  schema: z.ZodLiteral<"agent-demo.result/v1">;
76
139
  kind: z.ZodLiteral<"check">;
77
140
  ok: z.ZodBoolean;
@@ -244,12 +307,18 @@ export declare const DiffCommandResultSchema: z.ZodObject<{
244
307
  identical: z.ZodBoolean;
245
308
  differences: z.ZodArray<z.ZodObject<{
246
309
  category: z.ZodEnum<{
247
- assertion: "assertion";
248
- target: "target";
249
- actor: "actor";
250
310
  step: "step";
311
+ assertion: "assertion";
312
+ "target-name": "target-name";
313
+ "target-role": "target-role";
314
+ "target-bounds": "target-bounds";
251
315
  timing: "timing";
252
316
  caption: "caption";
317
+ actor: "actor";
318
+ }>;
319
+ severity: z.ZodEnum<{
320
+ fail: "fail";
321
+ report: "report";
253
322
  }>;
254
323
  detail: z.ZodString;
255
324
  }, z.core.$strip>>;
@@ -461,11 +530,10 @@ export declare const DoctorResultSchema: z.ZodObject<{
461
530
  voices: z.ZodArray<z.ZodString>;
462
531
  notes: z.ZodArray<z.ZodString>;
463
532
  }, z.core.$strip>;
464
- plainmotion: z.ZodObject<{
465
- installed: z.ZodBoolean;
466
- version: z.ZodOptional<z.ZodString>;
467
- note: z.ZodOptional<z.ZodString>;
468
- }, z.core.$strip>;
533
+ terminal: z.ZodOptional<z.ZodObject<{
534
+ ready: z.ZodBoolean;
535
+ notes: z.ZodArray<z.ZodString>;
536
+ }, z.core.$strip>>;
469
537
  schema: z.ZodLiteral<"agent-demo.result/v1">;
470
538
  kind: z.ZodLiteral<"doctor">;
471
539
  ok: z.ZodBoolean;
@@ -533,6 +601,7 @@ export declare const PublishResultSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
533
601
  "hash-mismatch": "hash-mismatch";
534
602
  "length-mismatch": "length-mismatch";
535
603
  conflict: "conflict";
604
+ "taken-down": "taken-down";
536
605
  }>;
537
606
  schema: z.ZodLiteral<"agent-demo.result/v1">;
538
607
  kind: z.ZodLiteral<"publish">;
@@ -542,6 +611,7 @@ export declare const PublishResultSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
542
611
  videoId: z.ZodString;
543
612
  url: z.ZodString;
544
613
  uploaded: z.ZodBoolean;
614
+ restored: z.ZodDefault<z.ZodBoolean>;
545
615
  remembered: z.ZodOptional<z.ZodBoolean>;
546
616
  expiresAt: z.ZodOptional<z.ZodString>;
547
617
  captions: z.ZodBoolean;
@@ -551,6 +621,44 @@ export declare const PublishResultSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
551
621
  kind: z.ZodLiteral<"publish">;
552
622
  problems: z.ZodArray<z.ZodString>;
553
623
  }, z.core.$strip>], "ok">;
624
+ /**
625
+ * A share taken down, or not. The service tombstones: the link answers 410 at once and
626
+ * the bytes stay until `purgeAfter`, so publishing the same bundle again before then
627
+ * restores it. Discriminated on `ok` for the same reason as `PublishResultSchema`.
628
+ */
629
+ export declare const UnpublishResultSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
630
+ ok: z.ZodLiteral<false>;
631
+ reason: z.ZodEnum<{
632
+ "not-found": "not-found";
633
+ "unexpected-response": "unexpected-response";
634
+ "no-endpoint": "no-endpoint";
635
+ "no-key": "no-key";
636
+ "invalid-store": "invalid-store";
637
+ "not-a-bundle": "not-a-bundle";
638
+ unauthorized: "unauthorized";
639
+ unreachable: "unreachable";
640
+ "invalid-target": "invalid-target";
641
+ "endpoint-mismatch": "endpoint-mismatch";
642
+ forbidden: "forbidden";
643
+ }>;
644
+ schema: z.ZodLiteral<"agent-demo.result/v1">;
645
+ kind: z.ZodLiteral<"unpublish">;
646
+ problems: z.ZodArray<z.ZodString>;
647
+ }, z.core.$strip>, z.ZodObject<{
648
+ ok: z.ZodLiteral<true>;
649
+ videoId: z.ZodString;
650
+ url: z.ZodString;
651
+ deletedAt: z.ZodString;
652
+ deletedBy: z.ZodEnum<{
653
+ producer: "producer";
654
+ admin: "admin";
655
+ expired: "expired";
656
+ }>;
657
+ purgeAfter: z.ZodString;
658
+ schema: z.ZodLiteral<"agent-demo.result/v1">;
659
+ kind: z.ZodLiteral<"unpublish">;
660
+ problems: z.ZodArray<z.ZodString>;
661
+ }, z.core.$strip>], "ok">;
554
662
  export type ArtifactRef = z.infer<typeof ArtifactRefSchema>;
555
663
  export type ValidationResult = z.infer<typeof ValidationResultSchema>;
556
664
  export type RunCommandResult = z.infer<typeof RunCommandResultSchema>;
@@ -568,7 +676,8 @@ export type DoctorResult = z.infer<typeof DoctorResultSchema>;
568
676
  export type ActivateResult = z.infer<typeof ActivateResultSchema>;
569
677
  export type LicenceResult = z.infer<typeof LicenceResultSchema>;
570
678
  export type PublishResult = z.infer<typeof PublishResultSchema>;
571
- export type DemoResult = ValidationResult | RunCommandResult | CheckCommandResult | RenderCommandResult | VerificationReport | WarmCommandResult | DiffCommandResult | CompareCommandResult | InitResult | PruneCommandResult | ImportCommandResult | InspectResult | DoctorResult | ActivateResult | LicenceResult | PublishResult;
679
+ export type UnpublishResult = z.infer<typeof UnpublishResultSchema>;
680
+ export type DemoResult = ValidationResult | RunCommandResult | CheckCommandResult | RenderCommandResult | VerificationReport | WarmCommandResult | DiffCommandResult | CompareCommandResult | InitResult | PruneCommandResult | ImportCommandResult | InspectResult | DoctorResult | ActivateResult | LicenceResult | PublishResult | UnpublishResult;
572
681
  /**
573
682
  * Renders a filesystem location for display in a result, relative to `root` when it
574
683
  * lies inside it.
@@ -23,6 +23,22 @@ export declare const ScenarioIntroSchema: z.ZodObject<{
23
23
  durationMs: z.ZodOptional<z.ZodNumber>;
24
24
  }, z.core.$strip>;
25
25
  export type ScenarioIntro = z.infer<typeof ScenarioIntroSchema>;
26
+ /**
27
+ * The hook: a line or two of text over the top band of a portrait video for its first seconds
28
+ * (`HookSchema` in the render plan says what it is and is not). A string, or `{ text,
29
+ * durationMs }` to hold it for other than the default 3 s.
30
+ *
31
+ * `text` is author-chosen and never generated. A `\n` in it is a line break the author chose;
32
+ * otherwise a long hook is balanced onto two lines at the hook's measured width. Whether every
33
+ * character can be drawn, and whether it fits on two lines, depends on the bundled fonts, which
34
+ * this package cannot see — `validate` checks both (`hookProblems`, `apps/cli`), before a browser
35
+ * opens, and `run` refuses with exit 2 on the same grounds.
36
+ */
37
+ export declare const ScenarioHookSchema: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
38
+ text: z.ZodString;
39
+ durationMs: z.ZodOptional<z.ZodNumber>;
40
+ }, z.core.$strip>]>;
41
+ export type ScenarioHook = z.infer<typeof ScenarioHookSchema>;
26
42
  /**
27
43
  * Per-scenario camera framing, for the demos whose pages the defaults do not suit.
28
44
  *
@@ -78,6 +94,28 @@ export declare const ScenarioSpeechSchema: z.ZodObject<{
78
94
  voices: z.ZodOptional<z.ZodArray<z.ZodString>>;
79
95
  }, z.core.$strip>;
80
96
  export type ScenarioSpeech = z.infer<typeof ScenarioSpeechSchema>;
97
+ export declare const TerminalMetaSchema: z.ZodObject<{
98
+ shell: z.ZodDefault<z.ZodEnum<{
99
+ bash: "bash";
100
+ zsh: "zsh";
101
+ sh: "sh";
102
+ }>>;
103
+ command: z.ZodOptional<z.ZodArray<z.ZodString>>;
104
+ cols: z.ZodDefault<z.ZodNumber>;
105
+ rows: z.ZodDefault<z.ZodNumber>;
106
+ cwd: z.ZodOptional<z.ZodString>;
107
+ env: z.ZodDefault<z.ZodArray<z.ZodString>>;
108
+ secrets: z.ZodDefault<z.ZodArray<z.ZodString>>;
109
+ browser: z.ZodOptional<z.ZodBoolean>;
110
+ label: z.ZodOptional<z.ZodString>;
111
+ transition: z.ZodOptional<z.ZodEnum<{
112
+ cut: "cut";
113
+ card: "card";
114
+ }>>;
115
+ }, z.core.$strip>;
116
+ export type TerminalMeta = z.infer<typeof TerminalMetaSchema>;
117
+ /** What an author writes: every defaulted field (`shell`, `cols`, `rows`, `env`, `secrets`) optional. */
118
+ export type TerminalMetaInput = z.input<typeof TerminalMetaSchema>;
81
119
  export declare const ScenarioMetaSchema: z.ZodObject<{
82
120
  schema: z.ZodLiteral<"agent-demo.scenario/v1">;
83
121
  id: z.ZodString;
@@ -88,6 +126,7 @@ export declare const ScenarioMetaSchema: z.ZodObject<{
88
126
  height: z.ZodLiteral<1080>;
89
127
  deviceScaleFactor: z.ZodLiteral<1>;
90
128
  }, z.core.$strip>;
129
+ uiScale: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<1.5>, z.ZodLiteral<2>]>>;
91
130
  locale: z.ZodLiteral<"en-US">;
92
131
  timezoneId: z.ZodLiteral<"UTC">;
93
132
  colorScheme: z.ZodLiteral<"light">;
@@ -104,6 +143,10 @@ export declare const ScenarioMetaSchema: z.ZodObject<{
104
143
  narration: z.ZodOptional<z.ZodString>;
105
144
  durationMs: z.ZodOptional<z.ZodNumber>;
106
145
  }, z.core.$strip>>;
146
+ hook: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
147
+ text: z.ZodString;
148
+ durationMs: z.ZodOptional<z.ZodNumber>;
149
+ }, z.core.$strip>]>>;
107
150
  camera: z.ZodOptional<z.ZodObject<{
108
151
  maxZoom: z.ZodOptional<z.ZodNumber>;
109
152
  margin: z.ZodOptional<z.ZodNumber>;
@@ -114,6 +157,25 @@ export declare const ScenarioMetaSchema: z.ZodObject<{
114
157
  speed: z.ZodOptional<z.ZodNumber>;
115
158
  voices: z.ZodOptional<z.ZodArray<z.ZodString>>;
116
159
  }, z.core.$strip>>;
160
+ terminal: z.ZodOptional<z.ZodObject<{
161
+ shell: z.ZodDefault<z.ZodEnum<{
162
+ bash: "bash";
163
+ zsh: "zsh";
164
+ sh: "sh";
165
+ }>>;
166
+ command: z.ZodOptional<z.ZodArray<z.ZodString>>;
167
+ cols: z.ZodDefault<z.ZodNumber>;
168
+ rows: z.ZodDefault<z.ZodNumber>;
169
+ cwd: z.ZodOptional<z.ZodString>;
170
+ env: z.ZodDefault<z.ZodArray<z.ZodString>>;
171
+ secrets: z.ZodDefault<z.ZodArray<z.ZodString>>;
172
+ browser: z.ZodOptional<z.ZodBoolean>;
173
+ label: z.ZodOptional<z.ZodString>;
174
+ transition: z.ZodOptional<z.ZodEnum<{
175
+ cut: "cut";
176
+ card: "card";
177
+ }>>;
178
+ }, z.core.$strip>>;
117
179
  pronunciations: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
118
180
  }, z.core.$strip>;
119
181
  /** Which phase, if any, hands the browser over. Read by the recorder and the surfaces. */
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ScenarioMeta } from './schema/index.js';
1
+ import type { ScenarioMeta, TerminalMetaInput } from './schema/index.js';
2
2
  import type { BrowserContextOptions, Locator, Page } from 'playwright-core';
3
3
  /**
4
4
  * What kind of interaction `run()` performs, declared rather than observed. Like
@@ -217,10 +217,10 @@ export type ExplainConnection = {
217
217
  to: string;
218
218
  label?: string;
219
219
  };
220
- /** One side of a `comparison` explain scene: what this alternative is called, and its points. */
220
+ /** One side of a `comparison` explain scene: what this alternative is called, and two to six short items. */
221
221
  export type ExplainColumn = {
222
222
  heading: string;
223
- points: string[];
223
+ items: string[];
224
224
  };
225
225
  /** A named group of imported svg parts a `diagram` scene's cue can target as one thing. */
226
226
  export type ExplainRegion = {
@@ -228,16 +228,16 @@ export type ExplainRegion = {
228
228
  of: string[];
229
229
  };
230
230
  /**
231
- * The five motion-graphics scenes an explain cut-away can be, mirroring PlainMotion's scene
232
- * registry prop-for-prop (`title`/`process`/`comparison`/`diagram`/`recap`). PlainTake does
233
- * not draw these — it compiles each one through the `plainmotion` CLI and splices the frozen
234
- * result into the recording — so this union is a structural mirror of PlainMotion's own
235
- * schemas, and the deep validation (character caps, list bounds, phrase resolution) belongs
236
- * to PlainMotion at run time, not to this package's types. What `validate` checks without a
231
+ * The five motion-graphics scenes an explain cut-away can be, mirroring the scene registry in
232
+ * `@plaintake/motion` prop-for-prop (`title`/`process`/`comparison`/`diagram`/`recap`). This
233
+ * package is types only — each scene is compiled and drawn after capture and spliced into the
234
+ * recording as a frozen segment — so this union is a structural mirror of those scene schemas,
235
+ * and the deep validation (character caps, list bounds, phrase resolution) belongs to the
236
+ * compile at run time, not to this package's types. What `validate` checks without a
237
237
  * browser is the shape around the scene: id, narration, a known `type`.
238
238
  *
239
239
  * Geometry is deliberately absent. Where a pixel appears is the scene layout's decision,
240
- * exactly as it is in a `plainmotion.yaml`; an author says what exists and what the
240
+ * exactly as it is in any motion-graphics scene; an author says what exists and what the
241
241
  * narration points at, and nothing about inches.
242
242
  */
243
243
  export type ExplainScene = {
@@ -273,7 +273,7 @@ export type ExplainScene = {
273
273
  items: string[];
274
274
  };
275
275
  /**
276
- * What a cue does to the viewer's attention. The same vocabulary PlainMotion defines —
276
+ * What a cue does to the viewer's attention. The same vocabulary the explain compile defines —
277
277
  * deliberately small, and deliberately not easing curves or tween internals: an author says
278
278
  * what should happen; how that is expressed is the renderer's job.
279
279
  */
@@ -281,7 +281,7 @@ export type ExplainCueAction = 'reveal' | 'hide' | 'emphasize' | 'deemphasize' |
281
281
  /**
282
282
  * When a cue fires: bound to words in the narration, resolved against the timings the speech
283
283
  * model measures. Rewriting a sentence moves the animation with it instead of silently
284
- * desynchronising — which is why there is no numeric `at` offset here the way PlainMotion's
284
+ * desynchronising — which is why there is no numeric `at` offset here the way the compile's
285
285
  * own cue schema allows one. An explain scene always speaks (its narration is the scene's
286
286
  * clock), so a phrase is the only anchor that survives a rewording, and an offset that could
287
287
  * not is not offered.
@@ -296,22 +296,23 @@ export type ExplainCue = {
296
296
  };
297
297
  /**
298
298
  * A full-frame motion-graphics cut-away in the middle of a browser demo — "explain while
299
- * demoing". `demo.explain()` stops the screencast, splices in a PlainMotion-rendered scene
299
+ * demoing". `demo.explain()` stops the screencast, splices in a rendered motion-graphics scene
300
300
  * (`title`/`process`/`comparison`/`diagram`/`recap`), and resumes the recording on the same
301
301
  * page: the cut-away is a boundary on the timeline, the same mechanism a multi-actor `turn`
302
302
  * uses, so nothing about the recording's determinism or re-renderability changes. The scene
303
303
  * is compiled and rendered after capture finishes, from a generated one-scene project — the
304
304
  * only inputs that matter are the ones right here.
305
305
  *
306
- * Free on every tier, like every authoring verb. Requires the `plainmotion` CLI on `PATH`
307
- * at record time (`plaintake doctor` reports whether it is there); a bundle that carries an
308
- * explain segment re-renders without it, the same way it re-renders without a browser.
306
+ * Free on every tier, like every authoring verb. Always narrated — the narration is the
307
+ * scene's clock — so it needs the voice model installed at record time even when speech is
308
+ * off (`plaintake doctor` reports whether it is there); a bundle that carries an explain
309
+ * segment re-renders without it, the same way it re-renders without a browser.
309
310
  */
310
311
  export type DemoExplain = {
311
312
  /**
312
313
  * Stable, authored — the same rule every other id here follows. It becomes the directory
313
- * the frozen artifacts live in (`explain/<id>/segment.mp4` and its plainmotion plan) and
314
- * the generated scene's own id, so it must read as plainmotion ids do: lower-case
314
+ * the frozen artifacts live in (`explain/<id>/segment.mp4` and its frozen plan) and
315
+ * the generated scene's own id, so it must read as scene ids do: lower-case
315
316
  * alphanumeric and hyphens, starting alphanumeric. Checked at run time, with the offending
316
317
  * id named.
317
318
  */
@@ -320,7 +321,7 @@ export type DemoExplain = {
320
321
  title: string;
321
322
  /**
322
323
  * What the scene says. The source of truth for both the audio and the caption — there is
323
- * no separate caption field, for the same reason PlainMotion has none: two strings that
324
+ * no separate caption field, for the same reason a motion-graphics scene has none: two strings that
324
325
  * are supposed to say the same thing will eventually disagree, and the one the viewer
325
326
  * hears is the one that matters. The measured speech is also the scene's duration: the
326
327
  * cut-away lasts exactly as long as it takes to say this, plus its lead-in and lead-out.
@@ -387,6 +388,53 @@ export type ActorStepMeta = {
387
388
  */
388
389
  paddingPx?: number;
389
390
  };
391
+ /**
392
+ * `ActorStepMeta` for a `TermHandle` verb, plus the one thing a terminal step can mean that a
393
+ * browser step cannot: `expectExit: true` says the terminal process exiting during this step is
394
+ * the point of it (a `quit`, an `exit`), not a capture failure.
395
+ */
396
+ export type TermStepMeta = ActorStepMeta & {
397
+ expectExit?: boolean;
398
+ };
399
+ /** A rectangle of terminal cells: zero-based `row`/`col` of its top-left cell, size in cells. */
400
+ export type TermCells = {
401
+ row: number;
402
+ col: number;
403
+ width: number;
404
+ height: number;
405
+ };
406
+ /**
407
+ * The live terminal a scenario declaring `terminal` drives, handed to `run()` as `term`. Each
408
+ * verb records `step.start`/`step.finish` through the same bracket `Actor` verbs use, so
409
+ * captions, narration, cursor, camera, highlight and chapters work unchanged.
410
+ */
411
+ export type TermHandle = {
412
+ /**
413
+ * The terminal as an actor, to turn back to it with `demo.turn(term.actor)`. Only with
414
+ * `terminal.browser: true`; reading it otherwise throws (exit 2).
415
+ */
416
+ readonly actor: Actor;
417
+ /** Types `line`, then Enter. Records a step whose target is the prompt line. */
418
+ run(line: string, meta?: TermStepMeta): Promise<void>;
419
+ /** Types at a fixed per-character delay — part of the timeline, never wall-clock driven. */
420
+ type(text: string, meta?: TermStepMeta): Promise<void>;
421
+ /** `'Enter'`, `'Ctrl+C'`, or a space-separated sequence such as `'Ctrl+B c'`. */
422
+ press(keys: string, meta?: TermStepMeta): Promise<void>;
423
+ /** Same bounds and timeout semantics as `demo.waitFor`. */
424
+ waitForText(pattern: RegExp | string, opts?: {
425
+ timeoutMs?: number;
426
+ id?: string;
427
+ title?: string;
428
+ }): Promise<void>;
429
+ /** A locator over the first match on the visible screen. */
430
+ getByText(pattern: RegExp | string): Locator;
431
+ /** A locator over a rectangle of cells, refused when it is outside the grid. */
432
+ cells(r: TermCells): Locator;
433
+ /** The visible screen, one line per row. */
434
+ screenText(): Promise<string>;
435
+ /** Reuses `DemoAssertion` verbatim, recorded against the default actor like `demo.assert`. */
436
+ assert(assertion: DemoAssertion): Promise<void>;
437
+ };
390
438
  /**
391
439
  * A named participant in a multi-actor demo: its own Playwright `BrowserContext`, its own
392
440
  * page, and a handle of verbs that each perform a Playwright action *and* record the
@@ -539,20 +587,14 @@ export interface DemoContext extends PreflightContext {
539
587
  * `opts.card`, when given, plays a full-frame transition card between the two actors'
540
588
  * segments — the `lines` it names, an optional spoken `narration`, held for `durationMs`.
541
589
  *
542
- * Omitting `opts.card` is allowed, but still draws a card: `RenderPlanSchema.transitionCards`
543
- * requires exactly one entry per hand-off with non-empty `lines`, so a card-less turn's card
544
- * is synthesised at render time as `Now: <label>`, naming the incoming actor by the `label`
545
- * it was given to `demo.actor()`, held for the same default duration an explicit card with no
546
- * `durationMs` of its own gets. The persistent corner badge names the active actor throughout
547
- * either way; `opts.card` only ever changes whether that hand-off also gets a full-frame beat
548
- * of its own, and what it says.
590
+ * `opts.card: false` is a hard cut: no card and no gap. With no `opts.card` the scenario
591
+ * default applies: a cut for a `terminal.browser` scenario unless `terminal.transition` is
592
+ * `'card'`; otherwise the card is synthesised at render time as `Now: <label>`, naming the
593
+ * incoming actor by the `label` it was given to `demo.actor()`, held for the default
594
+ * duration. The persistent corner badge names the active actor throughout either way.
549
595
  */
550
596
  turn(actor: Actor, opts?: {
551
- card?: {
552
- lines: string[];
553
- narration?: string;
554
- durationMs?: number;
555
- };
597
+ card?: TurnCard | false;
556
598
  }): Promise<void>;
557
599
  /**
558
600
  * Cuts away from the browser recording to a full-frame motion-graphics scene — a diagram
@@ -561,10 +603,10 @@ export interface DemoContext extends PreflightContext {
561
603
  * a `turn` does minus the actor switch, so the cut-away occupies its own slot on the
562
604
  * timeline and nothing it covers is ever captured.
563
605
  *
564
- * The scene is not drawn live: it is compiled and rendered by the `plainmotion` CLI after
565
- * capture finishes, from the request's own fields, and spliced in at render time as a
566
- * frozen 1920×1080@30 segment — which is what keeps the bundle re-renderable without
567
- * `plainmotion`, without a browser, and byte-identically. Its narration is spoken by the
606
+ * The scene is not drawn live: it is compiled and rendered in process after capture
607
+ * finishes, from the request's own fields, and spliced in at render time as a frozen
608
+ * 1920×1080@30 segment — which is what keeps the bundle re-renderable without a voice
609
+ * model, without a browser, and byte-identically. Its narration is spoken by the
568
610
  * same engine as the rest of the recording (`--speech on`), lands in the same caption
569
611
  * track, and is the scene's clock: the cut-away lasts exactly as long as it takes to say.
570
612
  *
@@ -574,10 +616,25 @@ export interface DemoContext extends PreflightContext {
574
616
  */
575
617
  explain(request: DemoExplain): Promise<void>;
576
618
  }
619
+ /** An explicit transition card for `demo.turn`. */
620
+ export type TurnCard = {
621
+ lines: string[];
622
+ narration?: string;
623
+ durationMs?: number;
624
+ };
625
+ /**
626
+ * `term` is present only when the scenario declares `terminal`; a scenario without one is
627
+ * handed no `term` key at all.
628
+ *
629
+ * `page` is the terminal page in a terminal scenario. `baseURL` is the web target (`--base-url`
630
+ * or the fixture) when `terminal.browser` is set; without it, in a terminal scenario, it is the
631
+ * terminal page's URL.
632
+ */
577
633
  export type DemoRunArgs = {
578
634
  page: Page;
579
635
  demo: DemoContext;
580
636
  baseURL: string;
637
+ term?: TermHandle;
581
638
  };
582
639
  /** Same property names as `DemoRunArgs`, so moving code between phases changes nothing else. */
583
640
  export type PreflightArgs = {
@@ -625,7 +682,8 @@ export type DemoDefinition = ScenarioMeta & {
625
682
  * field is one edit here instead of two that can drift apart.
626
683
  */
627
684
  type Defaulted = 'language' | 'allowedConsoleErrors' | 'handoff' | 'handoffTimeoutMs';
628
- export type DemoDefinitionInput = Omit<ScenarioMeta, Defaulted> & Partial<Pick<ScenarioMeta, Defaulted>> & {
685
+ export type DemoDefinitionInput = Omit<ScenarioMeta, Defaulted | 'terminal'> & Partial<Pick<ScenarioMeta, Defaulted>> & {
686
+ terminal?: TerminalMetaInput;
629
687
  run(args: DemoRunArgs): Promise<void>;
630
688
  } & PreflightHook & WarmupHook;
631
689
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plaintake/scenario",
3
- "version": "1.23.0",
3
+ "version": "1.30.0",
4
4
  "description": "Authoring SDK for PlainTake demo scenarios: defineDemo and the scenario DSL types. Install for editor autocomplete; the PlainTake binary ships a runtime fallback.",
5
5
  "license": "MIT",
6
6
  "type": "module",