@plaintake/scenario 1.24.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.
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.24.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",