@plaintake/scenario 1.10.0 → 1.12.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/index.js CHANGED
@@ -443,7 +443,32 @@ var HighlightRectSchema = z5.object({
443
443
  height: z5.number().int().positive(),
444
444
  startMs: z5.number().int().nonnegative(),
445
445
  endMs: z5.number().int().positive(),
446
- label: z5.string().min(1).optional()
446
+ label: z5.string().min(1).optional(),
447
+ /**
448
+ * The rect the spotlight glides *from*, when this window's cutout should animate rather
449
+ * than cut straight to the held `x`/`y`/`width`/`height` — the pre-reveal position a
450
+ * step's `target` measured before a scroll-causing `reveal` settled it into place.
451
+ * Present only together with `settleMs`: a glide needs both where it starts and when it
452
+ * finishes, and one without the other cannot be drawn.
453
+ *
454
+ * Absent means exactly today's behaviour — a single static rect held for the window's
455
+ * whole span — so every plan frozen before this field existed, and every window a future
456
+ * derivation decides not to glide, is unaffected by construction.
457
+ */
458
+ from: z5.object({
459
+ x: z5.number().int().min(0),
460
+ y: z5.number().int().min(0),
461
+ width: z5.number().int().positive(),
462
+ height: z5.number().int().positive()
463
+ }).optional(),
464
+ /**
465
+ * The absolute video-ms at which the glide from `from` completes and the spotlight
466
+ * settles into the held rect — not a duration, the same convention `startMs`/`endMs`
467
+ * and `CameraShot.enterMs`/`holdFromMs` already carry. Strictly after `startMs` (there
468
+ * has to be real time left to glide across) and no later than `endMs` (the window
469
+ * cannot settle after it has already closed).
470
+ */
471
+ settleMs: z5.number().int().positive().optional()
447
472
  }).refine(({ x, y, width, height }) => x + width <= 1920 && y + height <= 1080, {
448
473
  // Unlike CameraShotSchema's crop window, a highlight rect is drawn by libass, not fed to
449
474
  // `crop` — there is no yuv420p even-pixel rule to enforce — but it still has to fit the
@@ -451,6 +476,27 @@ var HighlightRectSchema = z5.object({
451
476
  // meant to sit inside.
452
477
  message: "the rect must stay inside the 1920x1080 frame",
453
478
  path: ["x"]
479
+ }).refine(({ from, settleMs }) => from === void 0 === (settleMs === void 0), {
480
+ // A glide needs both where it starts (`from`) and when it finishes (`settleMs`) — one
481
+ // without the other cannot be drawn, so the pair is refused rather than tolerated as a
482
+ // half-declared capability.
483
+ message: "from and settleMs must be present together or both absent",
484
+ path: ["from"]
485
+ }).refine(
486
+ ({ from }) => from === void 0 || from.x + from.width <= 1920 && from.y + from.height <= 1080,
487
+ {
488
+ // The same 1920x1080 containment the held rect enforces above, applied to the rect the
489
+ // spotlight glides from — an off-canvas starting point is no more drawable than an
490
+ // off-canvas held one.
491
+ message: "from must stay inside the 1920x1080 frame",
492
+ path: ["from"]
493
+ }
494
+ ).refine(({ startMs, endMs, settleMs }) => settleMs === void 0 || settleMs > startMs && settleMs <= endMs, {
495
+ // settleMs is absolute video-ms, like startMs/endMs themselves — the glide needs real
496
+ // room to run (strictly after startMs) and cannot settle after its own window has
497
+ // already closed (no later than endMs).
498
+ message: "settleMs must be strictly after startMs and no later than endMs",
499
+ path: ["settleMs"]
454
500
  });
455
501
  var HighlightSchema = z5.object({
456
502
  assPath: z5.literal("captions/highlight.ass"),
@@ -314,6 +314,13 @@ export declare const HighlightRectSchema: z.ZodObject<{
314
314
  startMs: z.ZodNumber;
315
315
  endMs: z.ZodNumber;
316
316
  label: z.ZodOptional<z.ZodString>;
317
+ from: z.ZodOptional<z.ZodObject<{
318
+ x: z.ZodNumber;
319
+ y: z.ZodNumber;
320
+ width: z.ZodNumber;
321
+ height: z.ZodNumber;
322
+ }, z.core.$strip>>;
323
+ settleMs: z.ZodOptional<z.ZodNumber>;
317
324
  }, z.core.$strip>;
318
325
  /**
319
326
  * The highlight track. A rect **list**, ordered and non-overlapping on the same discipline
@@ -332,6 +339,13 @@ export declare const HighlightSchema: z.ZodObject<{
332
339
  startMs: z.ZodNumber;
333
340
  endMs: z.ZodNumber;
334
341
  label: z.ZodOptional<z.ZodString>;
342
+ from: z.ZodOptional<z.ZodObject<{
343
+ x: z.ZodNumber;
344
+ y: z.ZodNumber;
345
+ width: z.ZodNumber;
346
+ height: z.ZodNumber;
347
+ }, z.core.$strip>>;
348
+ settleMs: z.ZodOptional<z.ZodNumber>;
335
349
  }, z.core.$strip>>;
336
350
  }, z.core.$strip>;
337
351
  export type Highlight = z.infer<typeof HighlightSchema>;
@@ -688,6 +702,13 @@ export declare const RenderPlanSchema: z.ZodObject<{
688
702
  startMs: z.ZodNumber;
689
703
  endMs: z.ZodNumber;
690
704
  label: z.ZodOptional<z.ZodString>;
705
+ from: z.ZodOptional<z.ZodObject<{
706
+ x: z.ZodNumber;
707
+ y: z.ZodNumber;
708
+ width: z.ZodNumber;
709
+ height: z.ZodNumber;
710
+ }, z.core.$strip>>;
711
+ settleMs: z.ZodOptional<z.ZodNumber>;
691
712
  }, z.core.$strip>>;
692
713
  }, z.core.$strip>>;
693
714
  speech: z.ZodOptional<z.ZodObject<{
package/dist/types.d.ts CHANGED
@@ -49,6 +49,63 @@ export type DemoStep = {
49
49
  voice?: string;
50
50
  /** Extra presentation hold after the action completes, in whole milliseconds. */
51
51
  holdMs?: number;
52
+ /**
53
+ * Scrolled into view after `run()` completes and before the hold — the opposite ordering
54
+ * from `target`, which is measured *before* `run()`. Exists for content the action itself
55
+ * reveals below the fold (a response panel that renders after a click, say): without it,
56
+ * an author either remembers to append a separate `Actor.scroll()` step for what is
57
+ * conceptually part of this one action, or the recording holds on an off-screen result.
58
+ *
59
+ * Independent of `target`: the element acted on and the element revealed are often not
60
+ * the same one, and a step may declare `reveal` with none at all.
61
+ *
62
+ * Not best-effort like `target`'s measurement — a `reveal` locator that never appears
63
+ * fails the step normally, the same as `run()` itself or `Actor.scroll`'s own unguarded
64
+ * `scrollIntoViewIfNeeded()`, because unlike `target` (inert metadata) this performs a
65
+ * real action with a real effect on what capture sees.
66
+ *
67
+ * `target` (if declared) is re-measured, best-effort, once this scroll settles: the
68
+ * settled rect and a real "revealed at" timestamp both ride on `step.finish`, alongside
69
+ * the pre-scroll rect `step.start` already carried. Camera zoom and `highlight` both
70
+ * prefer that settled rect over the stale one once it is written, so declaring the same
71
+ * locator as both `target` and `reveal` is the supported way to have them frame the
72
+ * revealed content rather than the position it was scrolled away from —
73
+ * `Actor.scroll`'s own locator form takes the mirror-image approach of scrolling in
74
+ * `prepare`, *before* measuring, for the same reason (see its comment); `reveal` cannot
75
+ * do that since the scroll it causes is itself a consequence of `run()`. Camera zoom
76
+ * additionally anchors a shot's settle time to the *later* of the click and the reveal
77
+ * completing, rather than the click alone, so a `run()` that keeps the page busy after
78
+ * the click (rendering a response panel, say) does not leave the camera's pan finished
79
+ * before the captured frames actually show the revealed content. That push has a limit,
80
+ * though: if this step's reveal is slow AND the next step follows in quick succession,
81
+ * the settle time is pulled earlier so it cannot land after the next step's own arrival —
82
+ * preserving step order rather than exactly matching when the reveal finished, diagnosed
83
+ * with `camera.compressed` when it binds.
84
+ *
85
+ * The scroll itself is a real, live glide (`smoothScrollIntoView` in `recorder-playwright`),
86
+ * not an instant jump — a genuine rAF-driven ease over the capture's own variable-frame-rate
87
+ * screencast, the one piece of on-screen motion in a plaintake recording that is not
88
+ * synthesised at render time from frozen numbers. See that function's doc comment for why a
89
+ * live capture can show it without touching the byte-reproducibility guarantee, which has
90
+ * only ever covered re-rendering an already-frozen bundle.
91
+ *
92
+ * Two narrower caveats remain, both because they cannot be *re*-measured the way `target`
93
+ * is: (a) the cursor arrow itself still marks the pre-scroll click position and does not
94
+ * chase the settled rect, so a large reveal-scroll can leave the arrow outside a
95
+ * tightly-zoomed shot for the rest of the hold; (b) on a `click`-action step specifically,
96
+ * `highlight`'s spotlight window is a brief pulse bracketing the click, with no structural
97
+ * room for a glide to read as intentional motion rather than a timing glitch — it still
98
+ * settles on the target rect instantly, a property of that window's own timing rather than
99
+ * a gap left to close later. Non-click actions (`type`/`point`/`scroll`) are different:
100
+ * their window already spans to the step's own end, so when the pre-reveal and settled
101
+ * rects genuinely differ, the spotlight glides between them at render time — timed to when
102
+ * the reveal actually finished, not the window's own edges — falling back to the same
103
+ * instant settle when there isn't genuinely enough of that window left to glide across,
104
+ * or when the pre-reveal position itself was never usable (no area, or entirely off-frame).
105
+ *
106
+ * Also: if `run()` throws, this scroll never executes — the step fails before reaching it.
107
+ */
108
+ reveal?: Locator;
52
109
  run: () => Promise<unknown>;
53
110
  };
54
111
  export type DemoAssertion = {
@@ -304,8 +361,25 @@ export type ActorStepMeta = {
304
361
  highlight?: boolean | {
305
362
  label?: string;
306
363
  };
364
+ /** See `DemoStep.reveal`. */
365
+ reveal?: Locator;
307
366
  /** See `DemoStep.voice` — one verb-call override, ahead of this actor's own `voice`. */
308
367
  voice?: string;
368
+ /**
369
+ * Alignment `Actor.scroll`'s locator form settles the viewport on, mirroring
370
+ * `Element.scrollIntoView`'s own `block`. Only `scroll` reads this — every other verb
371
+ * has no notion of where the viewport ends up after it runs, so this is silently ignored
372
+ * elsewhere. Absent means `'center'`, the same default a bare `scrollIntoView()` call has.
373
+ */
374
+ block?: 'start' | 'center' | 'end' | 'nearest';
375
+ /**
376
+ * Minimum gap, in pixels, `Actor.scroll`'s locator form leaves between the target and the
377
+ * edge `block` settles it against — breathing room so the target doesn't land flush
378
+ * against the viewport edge. Only meaningful alongside `block: 'start'` or `'end'`, which
379
+ * name an edge; ignored for `'center'`/`'nearest'` and by every verb but `scroll`. Absent
380
+ * means 0, flush with the edge.
381
+ */
382
+ paddingPx?: number;
309
383
  };
310
384
  /**
311
385
  * A named participant in a multi-actor demo: its own Playwright `BrowserContext`, its own
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plaintake/scenario",
3
- "version": "1.10.0",
3
+ "version": "1.12.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",