@tandryukha/aidemo 0.14.1 → 0.15.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.
package/src/types.ts CHANGED
@@ -133,6 +133,15 @@ export const ActionSchema = z.discriminatedUnion("op", [
133
133
  ...BaseAction,
134
134
  op: z.literal("hover"),
135
135
  target: TargetSchema,
136
+ /**
137
+ * Where the pointer parks relative to the element. Default "center" — but
138
+ * on a small chip or a number the narration is about, the cursor then
139
+ * COVERS the thing it points at (issue #50). "edge" (or a side) parks it
140
+ * just outside the element's box, still close enough to read as pointing.
141
+ */
142
+ anchor: z.enum(["center", "edge", "top", "bottom", "left", "right"]).optional(),
143
+ /** Extra nudge from the anchor point, CSS px. */
144
+ offset: z.object({ x: z.number(), y: z.number() }).optional(),
136
145
  }),
137
146
  z.object({
138
147
  ...BaseAction,
@@ -376,6 +385,35 @@ export const ActionSchema = z.discriminatedUnion("op", [
376
385
  }),
377
386
  /** Browser back (history), with the same readiness wait as `goto`. */
378
387
  z.object({ ...BaseAction, op: z.literal("back") }),
388
+ /**
389
+ * Arm a one-shot handler for the NEXT native dialog (`alert`, `confirm`,
390
+ * `prompt`, `beforeunload`) so the action that triggers it actually goes
391
+ * through (issue #54). Playwright auto-DISMISSES dialogs when nothing is
392
+ * listening, so without this a filmed click on an "Archive this?" button
393
+ * looks like it worked while the handler never ran — a false outcome on
394
+ * camera. Put the `dialog` action immediately BEFORE the click that opens it.
395
+ *
396
+ * The dialog itself is chrome, not page content, so it is not visible in the
397
+ * take; `holdMs` only paces the beat before answering.
398
+ */
399
+ z.object({
400
+ ...BaseAction,
401
+ op: z.literal("dialog"),
402
+ /** Answer the dialog with OK (default) or Cancel. */
403
+ action: z.enum(["accept", "dismiss"]).default("accept"),
404
+ /** Text typed into a `prompt()` before accepting. */
405
+ promptText: z.string().optional(),
406
+ /** Wait this long before answering, ms. Default 0. */
407
+ holdMs: z.number().min(0).max(5000).optional(),
408
+ /** How long to wait for the dialog to appear before warning, ms. Default 5000. */
409
+ timeoutMs: z.number().optional(),
410
+ /**
411
+ * Set false when the dialog is not guaranteed to open — the take then only
412
+ * notes that none came. Default true: a `dialog` whose dialog never fired
413
+ * fails the take, because the filmed action silently did nothing.
414
+ */
415
+ required: z.boolean().optional(),
416
+ }),
379
417
  ]);
380
418
  export type Action = z.infer<typeof ActionSchema>;
381
419
 
@@ -449,6 +487,22 @@ export const CaptionsConfigSchema = z.object({
449
487
  color: z.string().optional(),
450
488
  /** Pill/bar background (CSS). Default rgba(12,14,22,.66). */
451
489
  background: z.string().optional(),
490
+ /**
491
+ * Cue segmentation (storyboard level only — per-scene overrides just move
492
+ * the strip). Defaults follow broadcast practice: up to 2 lines of ~38
493
+ * characters, 1–6 s per cue, broken at punctuation and conjunctions rather
494
+ * than at a hard word count (issue #48).
495
+ */
496
+ /** Max words per cue. Default 12 (≈2 lines). */
497
+ maxWords: z.number().int().min(2).max(30).optional(),
498
+ /** Max cue length, ms. Default 6000. */
499
+ maxCueMs: z.number().min(500).max(12000).optional(),
500
+ /** Cues shorter than this are merged into a neighbour where they fit, ms. Default 1000. */
501
+ minCueMs: z.number().min(0).max(4000).optional(),
502
+ /** Lines a cue may occupy. Default 2. */
503
+ maxLines: z.number().int().min(1).max(3).optional(),
504
+ /** Characters per line used to size a cue. Default 38. */
505
+ maxCharsPerLine: z.number().int().min(16).max(80).optional(),
452
506
  });
453
507
  export type CaptionsConfig = z.infer<typeof CaptionsConfigSchema>;
454
508
 
@@ -458,6 +512,12 @@ export const AttentionConfigSchema = z.object({
458
512
  color: z.string().optional(),
459
513
  /** Draw a click ring where every click lands (compose-time cursor only). */
460
514
  clicks: z.boolean().optional(),
515
+ /**
516
+ * Move the cursor out of the way before a highlight/spotlight/callout/focus
517
+ * beat, so the pointer never sits on top of the value being showcased
518
+ * (issue #50). Opt-in; the cursor glides to just outside the target's box.
519
+ */
520
+ cursorClear: z.boolean().optional(),
461
521
  });
462
522
  export type AttentionConfig = z.infer<typeof AttentionConfigSchema>;
463
523
 
@@ -491,6 +551,12 @@ export const FrameSchema = z.object({
491
551
  safeTop: z.union([z.boolean(), z.number().min(0).max(120)]).optional(),
492
552
  /** Text in the chrome's address pill (url wins over title). */
493
553
  title: z.string().optional(),
554
+ /**
555
+ * Address-pill text. `"auto"` tracks the page: each scene's pill shows the
556
+ * host of the URL that scene is actually on (from the take's `goto`s), so a
557
+ * demo that moves between hosts stops printing one fixed address over all of
558
+ * them (issue #52). Any other string is used verbatim for the whole video.
559
+ */
494
560
  url: z.string().optional(),
495
561
  });
496
562
  export type Frame = z.infer<typeof FrameSchema>;
@@ -534,6 +600,20 @@ export const AutoIdleSchema = z.object({
534
600
  });
535
601
  export type AutoIdle = z.infer<typeof AutoIdleSchema>;
536
602
 
603
+ /**
604
+ * Scene-to-scene transition (opt-in). When present, compose crossfades the
605
+ * video across every scene boundary instead of hard-cutting. The overlap is
606
+ * stolen from each scene's own frozen tail so the timeline does NOT shrink —
607
+ * total duration and per-scene narration alignment are preserved exactly.
608
+ * Omit the key for the default hard cuts (unchanged stream-copy concat).
609
+ */
610
+ export const TransitionSchema = z.object({
611
+ type: z.literal("crossfade"),
612
+ /** Crossfade length, ms. Default 400. */
613
+ durationMs: z.number().default(400),
614
+ });
615
+ export type Transition = z.infer<typeof TransitionSchema>;
616
+
537
617
  export const SceneSchema = z.object({
538
618
  id: z.string(),
539
619
  /** Chapter title for this beat (`output.chapters`); defaults to the scene id. */
@@ -565,6 +645,14 @@ export const SceneSchema = z.object({
565
645
  autoIdle: z.boolean().optional(),
566
646
  /** Caption placement for this scene (overrides top-level `captions.position`). */
567
647
  captions: CaptionsConfigSchema.optional(),
648
+ /**
649
+ * Override the storyboard `transition` at the boundary ENTERING this scene:
650
+ * `false` hard-cuts into it, an object changes the crossfade. Use it when a
651
+ * dissolve would ghost — e.g. this scene opens on a new page still painting
652
+ * (issue #49). Boundaries that cross a navigation hard-cut automatically;
653
+ * this is the manual override.
654
+ */
655
+ transition: z.union([z.literal(false), TransitionSchema]).optional(),
568
656
  /** Blur these regions while this scene records (adds to top-level `redact`). */
569
657
  redact: z.array(RedactSchema).optional(),
570
658
  /** Hide these selectors for this scene (adds to top-level `hide`). */
@@ -656,6 +744,16 @@ export const CursorConfigSchema = z.object({
656
744
  style: z.enum(["arrow", "dot"]).optional(),
657
745
  /** Dot style only: fill color. Default rgba(255,90,95,.85). */
658
746
  color: z.string().optional(),
747
+ /**
748
+ * Outline drawn around the pointer so it stays findable on brand-coloured
749
+ * UI (a gold dot on a gold chip disappears — issue #50). CSS color; default
750
+ * rgba(255,255,255,.9) for "dot", the arrow's dark stroke otherwise.
751
+ */
752
+ outline: z.string().optional(),
753
+ /** Outline thickness in cursor units (the pointer is 24 wide). Default 2. */
754
+ outlineWidth: z.number().min(0).max(6).optional(),
755
+ /** Soft contrasting halo behind the pointer — findable on any background. */
756
+ halo: z.boolean().optional(),
659
757
  });
660
758
  export type CursorConfig = z.infer<typeof CursorConfigSchema>;
661
759
 
@@ -686,19 +784,6 @@ export const CardSchema = z.object({
686
784
  });
687
785
  export type Card = z.infer<typeof CardSchema>;
688
786
 
689
- /**
690
- * Scene-to-scene transition (opt-in). When present, compose crossfades the
691
- * video across every scene boundary instead of hard-cutting. The overlap is
692
- * stolen from each scene's own frozen tail so the timeline does NOT shrink —
693
- * total duration and per-scene narration alignment are preserved exactly.
694
- * Omit the key for the default hard cuts (unchanged stream-copy concat).
695
- */
696
- export const TransitionSchema = z.object({
697
- type: z.literal("crossfade"),
698
- /** Crossfade length, ms. Default 400. */
699
- durationMs: z.number().default(400),
700
- });
701
- export type Transition = z.infer<typeof TransitionSchema>;
702
787
 
703
788
  /**
704
789
  * Master loudness target for the final muxed audio (ffmpeg `loudnorm`). Lands
@@ -747,8 +832,22 @@ export const OutputSchema = z
747
832
  preset: OutputPresetSchema.optional(),
748
833
  /** Write MP4 chapter markers, one per scene (scene `title` or id). Default false. */
749
834
  chapters: z.boolean().optional(),
750
- /** Also extract output/poster.png (first content frame after the intro). Default false. */
751
- poster: z.boolean().optional(),
835
+ /**
836
+ * Also extract output/poster.png. `true` picks the first frame after the
837
+ * intro card that actually shows CONTENT (a blank white load frame is
838
+ * skipped — issue #47); a number picks the frame at that ms of the final
839
+ * video instead. Default false.
840
+ */
841
+ poster: z.union([z.boolean(), z.number().min(0)]).optional(),
842
+ /**
843
+ * Drop the browser's blank pre-paint at the very start of the video, so
844
+ * the demo opens on the product instead of on ~0.5 s of white under the
845
+ * first caption (issue #47). At most `blankOpenCapMs` of the leading flat
846
+ * span is kept. Default false (unchanged opening).
847
+ */
848
+ trimLeadingBlank: z.boolean().optional(),
849
+ /** With `trimLeadingBlank`: leading blank kept, ms. Default 100. */
850
+ blankOpenCapMs: z.number().min(0).max(2000).optional(),
752
851
  /**
753
852
  * Also export output/walkthrough/ (index.html + guide.md + per-scene
754
853
  * frames + captions) after every render. Default false; `aidemo
@@ -759,9 +858,12 @@ export const OutputSchema = z
759
858
  height: z.number().optional(),
760
859
  /**
761
860
  * "contain" (default) = scale to fit + pad the remainder (letterbox bars).
762
- * "cover" = scale to fill + center-crop the overflow (no bars).
861
+ * "cover" = scale to fill + center-crop the overflow (no bars) — the
862
+ * edge-to-edge answer when the capture's aspect differs from the
863
+ * preset's (issue #58). Set explicitly it also wins over a `preset`'s
864
+ * own fit; leave it out to take the preset's.
763
865
  */
764
- fit: z.enum(["contain", "cover"]).default("contain"),
866
+ fit: z.enum(["contain", "cover"]).optional(),
765
867
  /** Pad color for "contain" (ffmpeg color syntax, e.g. black, 0x1a1a1a). Default black. */
766
868
  background: z.string().optional(),
767
869
  /**
@@ -797,6 +899,20 @@ export const HoldSchema = z.object({
797
899
  driftScale: z.number().min(1).max(1.5).default(1.06),
798
900
  /** Take the held frame this many ms before the segment end. Default 0. */
799
901
  backoffMs: z.number().min(0).max(2000).default(0),
902
+ /**
903
+ * Shortest hold that still drifts, ms. Default 2000: a 6-px push over a
904
+ * second reads as a bob — a jitter bug — not as a dwell, so short holds
905
+ * freeze instead (issue #57).
906
+ */
907
+ minDriftMs: z.number().min(0).max(20000).default(2000),
908
+ /**
909
+ * Drift even when a window `frame.chrome` is composited around the video.
910
+ * Default false: the drift zooms the captured PAGE while the drawn browser
911
+ * chrome stays put, so the eye — locked on the static window edge and the
912
+ * sticky header — sees the page sliding inside its frame, i.e. a scroll
913
+ * glitch. Such scenes freeze instead unless you set this (issue #57).
914
+ */
915
+ driftInFrame: z.boolean().default(false),
800
916
  });
801
917
  export type HoldConfig = z.infer<typeof HoldSchema>;
802
918
 
@@ -891,9 +1007,27 @@ export const StoryboardSchema = z.object({
891
1007
  */
892
1008
  params: z.record(z.string(), z.string()).optional(),
893
1009
  targetLengthSeconds: z.number().optional(),
894
- /** Recording viewport / video size. Default 1280x720. */
1010
+ /**
1011
+ * Recording viewport (CSS px) and, with `deviceScaleFactor`, the raw video
1012
+ * size. Default 1280x720.
1013
+ */
895
1014
  video: z
896
- .object({ width: z.number(), height: z.number() })
1015
+ .object({
1016
+ width: z.number(),
1017
+ height: z.number(),
1018
+ /**
1019
+ * Record the video at `width*n x height*n` pixels while the PAGE still
1020
+ * lays out at `width x height` CSS px — the standard fix for "the
1021
+ * product is unreadable" (issue #51). A desktop app recorded at a
1022
+ * 1920-px viewport renders its 1200-px content column tiny; record at
1023
+ * 1280–1440 CSS px with `deviceScaleFactor: 2` instead and let
1024
+ * `output.preset` scale the 2x raw down to 1080p: the UI is a third
1025
+ * bigger on screen and stays crisp. Default 1 (unchanged output).
1026
+ * Built-in (Playwright) capture only — native/OBS capture records real
1027
+ * screen pixels.
1028
+ */
1029
+ deviceScaleFactor: z.number().min(1).max(3).optional(),
1030
+ })
897
1031
  .default({ width: 1280, height: 720 }),
898
1032
  /** Named frames used by actions, mapping name -> iframe selector. */
899
1033
  frames: z.record(z.string(), z.string()).default({}),
@@ -1165,6 +1299,8 @@ export const ComposeReportSchema = z.object({
1165
1299
  hold: z.string(),
1166
1300
  /** output/poster.png when `output.poster` is set. */
1167
1301
  poster: z.string().optional(),
1302
+ /** Ms of blank (contentless) video at the very start of the final cut. */
1303
+ blankOpenMs: z.number().optional(),
1168
1304
  warnings: z.array(ComposeWarningSchema),
1169
1305
  });
1170
1306
  export type ComposeReport = z.infer<typeof ComposeReportSchema>;