screenci 0.0.76 → 0.0.78

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.
Files changed (184) hide show
  1. package/dist/cli.d.ts +51 -1
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +557 -200
  4. package/dist/cli.js.map +1 -1
  5. package/dist/docs/manifest.d.ts +253 -98
  6. package/dist/docs/manifest.d.ts.map +1 -1
  7. package/dist/docs/manifest.js +92 -32
  8. package/dist/docs/manifest.js.map +1 -1
  9. package/dist/docs/video-sources/camera-and-zooming.screenci.d.ts +2 -0
  10. package/dist/docs/video-sources/camera-and-zooming.screenci.d.ts.map +1 -0
  11. package/dist/docs/video-sources/camera-and-zooming.screenci.js +57 -0
  12. package/dist/docs/video-sources/camera-and-zooming.screenci.js.map +1 -0
  13. package/dist/docs/video-sources/installation.screenci.js +14 -3
  14. package/dist/docs/video-sources/installation.screenci.js.map +1 -1
  15. package/dist/docs/video-sources/languages.screenci.d.ts +2 -0
  16. package/dist/docs/video-sources/languages.screenci.d.ts.map +1 -0
  17. package/dist/docs/video-sources/languages.screenci.js +68 -0
  18. package/dist/docs/video-sources/languages.screenci.js.map +1 -0
  19. package/dist/docs/video-sources/locator-highlight-animated.screenci.js +1 -1
  20. package/dist/docs/video-sources/locator-highlight-animated.screenci.js.map +1 -1
  21. package/dist/docs/video-sources/narration.screenci.d.ts +2 -0
  22. package/dist/docs/video-sources/narration.screenci.d.ts.map +1 -0
  23. package/dist/docs/video-sources/narration.screenci.js +71 -0
  24. package/dist/docs/video-sources/narration.screenci.js.map +1 -0
  25. package/dist/docs/video-sources/public-urls-and-embeds.screenci.d.ts +2 -0
  26. package/dist/docs/video-sources/public-urls-and-embeds.screenci.d.ts.map +1 -0
  27. package/dist/docs/video-sources/public-urls-and-embeds.screenci.js +60 -0
  28. package/dist/docs/video-sources/public-urls-and-embeds.screenci.js.map +1 -0
  29. package/dist/docs/video-sources/speaking-style.screenci.d.ts +2 -0
  30. package/dist/docs/video-sources/speaking-style.screenci.d.ts.map +1 -0
  31. package/dist/docs/video-sources/speaking-style.screenci.js +76 -0
  32. package/dist/docs/video-sources/speaking-style.screenci.js.map +1 -0
  33. package/dist/docs/video-sources/vertical-video.screenci.d.ts +2 -0
  34. package/dist/docs/video-sources/vertical-video.screenci.d.ts.map +1 -0
  35. package/dist/docs/video-sources/vertical-video.screenci.js +58 -0
  36. package/dist/docs/video-sources/vertical-video.screenci.js.map +1 -0
  37. package/dist/docs/videos.d.ts +20 -0
  38. package/dist/docs/videos.d.ts.map +1 -1
  39. package/dist/docs/videos.js +25 -0
  40. package/dist/docs/videos.js.map +1 -1
  41. package/dist/e2e/instrument.e2e.js +8 -0
  42. package/dist/e2e/instrument.e2e.js.map +1 -1
  43. package/dist/e2e/redact.e2e.d.ts +2 -0
  44. package/dist/e2e/redact.e2e.d.ts.map +1 -0
  45. package/dist/e2e/redact.e2e.js +161 -0
  46. package/dist/e2e/redact.e2e.js.map +1 -0
  47. package/dist/index.d.ts +10 -3
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +8 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/src/asset.d.ts +154 -13
  52. package/dist/src/asset.d.ts.map +1 -1
  53. package/dist/src/asset.js +262 -32
  54. package/dist/src/asset.js.map +1 -1
  55. package/dist/src/assetHash.d.ts +27 -0
  56. package/dist/src/assetHash.d.ts.map +1 -0
  57. package/dist/src/assetHash.js +0 -0
  58. package/dist/src/assetHash.js.map +1 -0
  59. package/dist/src/audio.d.ts +8 -5
  60. package/dist/src/audio.d.ts.map +1 -1
  61. package/dist/src/audio.js +56 -31
  62. package/dist/src/audio.js.map +1 -1
  63. package/dist/src/builder.d.ts +132 -32
  64. package/dist/src/builder.d.ts.map +1 -1
  65. package/dist/src/builder.js +66 -20
  66. package/dist/src/builder.js.map +1 -1
  67. package/dist/src/changeFocus.d.ts.map +1 -1
  68. package/dist/src/changeFocus.js +103 -34
  69. package/dist/src/changeFocus.js.map +1 -1
  70. package/dist/src/cue.d.ts +29 -17
  71. package/dist/src/cue.d.ts.map +1 -1
  72. package/dist/src/cue.js +138 -61
  73. package/dist/src/cue.js.map +1 -1
  74. package/dist/src/declare.d.ts +8 -4
  75. package/dist/src/declare.d.ts.map +1 -1
  76. package/dist/src/declare.js +28 -10
  77. package/dist/src/declare.js.map +1 -1
  78. package/dist/src/defaults.d.ts +23 -0
  79. package/dist/src/defaults.d.ts.map +1 -1
  80. package/dist/src/defaults.js +23 -0
  81. package/dist/src/defaults.js.map +1 -1
  82. package/dist/src/events.d.ts +289 -25
  83. package/dist/src/events.d.ts.map +1 -1
  84. package/dist/src/events.js +145 -13
  85. package/dist/src/events.js.map +1 -1
  86. package/dist/src/htmlRasterizer.d.ts +10 -0
  87. package/dist/src/htmlRasterizer.d.ts.map +1 -1
  88. package/dist/src/htmlRasterizer.js +12 -2
  89. package/dist/src/htmlRasterizer.js.map +1 -1
  90. package/dist/src/init.d.ts +2 -0
  91. package/dist/src/init.d.ts.map +1 -1
  92. package/dist/src/init.js +64 -18
  93. package/dist/src/init.js.map +1 -1
  94. package/dist/src/instrument.d.ts.map +1 -1
  95. package/dist/src/instrument.js +25 -7
  96. package/dist/src/instrument.js.map +1 -1
  97. package/dist/src/locales.d.ts.map +1 -1
  98. package/dist/src/locales.js +1 -0
  99. package/dist/src/locales.js.map +1 -1
  100. package/dist/src/localize.d.ts +25 -17
  101. package/dist/src/localize.d.ts.map +1 -1
  102. package/dist/src/localize.js +18 -10
  103. package/dist/src/localize.js.map +1 -1
  104. package/dist/src/localizeRuntime.d.ts +18 -13
  105. package/dist/src/localizeRuntime.d.ts.map +1 -1
  106. package/dist/src/localizeRuntime.js +28 -18
  107. package/dist/src/localizeRuntime.js.map +1 -1
  108. package/dist/src/logger.d.ts +5 -0
  109. package/dist/src/logger.d.ts.map +1 -1
  110. package/dist/src/logger.js +7 -0
  111. package/dist/src/logger.js.map +1 -1
  112. package/dist/src/narrationVisibility.d.ts +19 -0
  113. package/dist/src/narrationVisibility.d.ts.map +1 -0
  114. package/dist/src/narrationVisibility.js +26 -0
  115. package/dist/src/narrationVisibility.js.map +1 -0
  116. package/dist/src/recordingData.d.ts +7 -0
  117. package/dist/src/recordingData.d.ts.map +1 -1
  118. package/dist/src/recordingSize.d.ts +32 -0
  119. package/dist/src/recordingSize.d.ts.map +1 -0
  120. package/dist/src/recordingSize.js +54 -0
  121. package/dist/src/recordingSize.js.map +1 -0
  122. package/dist/src/redact.d.ts +68 -0
  123. package/dist/src/redact.d.ts.map +1 -0
  124. package/dist/src/redact.js +138 -0
  125. package/dist/src/redact.js.map +1 -0
  126. package/dist/src/redactController.d.ts +61 -0
  127. package/dist/src/redactController.d.ts.map +1 -0
  128. package/dist/src/redactController.js +232 -0
  129. package/dist/src/redactController.js.map +1 -0
  130. package/dist/src/runtimeContext.d.ts +32 -0
  131. package/dist/src/runtimeContext.d.ts.map +1 -1
  132. package/dist/src/runtimeContext.js +21 -0
  133. package/dist/src/runtimeContext.js.map +1 -1
  134. package/dist/src/runtimeMode.d.ts +15 -7
  135. package/dist/src/runtimeMode.d.ts.map +1 -1
  136. package/dist/src/runtimeMode.js +17 -6
  137. package/dist/src/runtimeMode.js.map +1 -1
  138. package/dist/src/screenAudio.d.ts +34 -4
  139. package/dist/src/screenAudio.d.ts.map +1 -1
  140. package/dist/src/screenAudio.js +0 -0
  141. package/dist/src/screenAudio.js.map +1 -1
  142. package/dist/src/screenAudioSink.d.ts +12 -0
  143. package/dist/src/screenAudioSink.d.ts.map +1 -1
  144. package/dist/src/screenAudioSink.js +30 -0
  145. package/dist/src/screenAudioSink.js.map +1 -1
  146. package/dist/src/screenshot.d.ts +16 -14
  147. package/dist/src/screenshot.d.ts.map +1 -1
  148. package/dist/src/screenshot.js +11 -11
  149. package/dist/src/screenshot.js.map +1 -1
  150. package/dist/src/sourceTrim.d.ts +29 -0
  151. package/dist/src/sourceTrim.d.ts.map +1 -0
  152. package/dist/src/sourceTrim.js +76 -0
  153. package/dist/src/sourceTrim.js.map +1 -0
  154. package/dist/src/studio.d.ts +71 -3
  155. package/dist/src/studio.d.ts.map +1 -1
  156. package/dist/src/studio.js +19 -1
  157. package/dist/src/studio.js.map +1 -1
  158. package/dist/src/timelineOffset.d.ts +46 -0
  159. package/dist/src/timelineOffset.d.ts.map +1 -0
  160. package/dist/src/timelineOffset.js +66 -0
  161. package/dist/src/timelineOffset.js.map +1 -0
  162. package/dist/src/titleValidation.d.ts +38 -0
  163. package/dist/src/titleValidation.d.ts.map +1 -1
  164. package/dist/src/titleValidation.js +42 -0
  165. package/dist/src/titleValidation.js.map +1 -1
  166. package/dist/src/types.d.ts +85 -0
  167. package/dist/src/types.d.ts.map +1 -1
  168. package/dist/src/types.js +1 -1
  169. package/dist/src/types.js.map +1 -1
  170. package/dist/src/video.d.ts +37 -34
  171. package/dist/src/video.d.ts.map +1 -1
  172. package/dist/src/video.js +99 -48
  173. package/dist/src/video.js.map +1 -1
  174. package/dist/src/voiceSampleAudio.d.ts +34 -0
  175. package/dist/src/voiceSampleAudio.d.ts.map +1 -0
  176. package/dist/src/voiceSampleAudio.js +105 -0
  177. package/dist/src/voiceSampleAudio.js.map +1 -0
  178. package/dist/src/voices.d.ts +2 -2
  179. package/dist/src/voices.d.ts.map +1 -1
  180. package/dist/src/voices.js +8 -3
  181. package/dist/src/voices.js.map +1 -1
  182. package/dist/tsconfig.tsbuildinfo +1 -1
  183. package/package.json +3 -2
  184. package/templates/recordings/assets/logo.png +0 -0
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Shared validation and parsing for the source `crop` rectangle and the
3
+ * `start`/`end` source-trim positions accepted by file overlays, narration video
4
+ * cues, and `selected()` render dependencies.
5
+ *
6
+ * `crop` is a rectangle in the source file's own pixels. `start`/`end` are time
7
+ * strings (`'2s'`, `'0:02'`, `'50%'`): seconds/timecodes resolve to a concrete ms
8
+ * offset into the source, while a percentage stays symbolic (a fraction of the
9
+ * SOURCE duration) because the source length is not known until render time.
10
+ */
11
+ import { type TimelineOffset } from './timelineOffset.js';
12
+ import type { OverlayCrop, SourceTrimPoint } from './events.js';
13
+ /**
14
+ * Validates a {@link OverlayCrop}: every field finite, `x`/`y` non-negative, and
15
+ * `width`/`height` strictly positive. Returns the crop unchanged so callers can
16
+ * thread it inline.
17
+ */
18
+ export declare function validateCrop(label: string, crop: OverlayCrop): OverlayCrop;
19
+ /**
20
+ * Parses and validates the optional `start`/`end` source-trim pair. When both are
21
+ * concrete (ms or percent), `start` must come before `end`; a mixed ms/percent
22
+ * pair cannot be compared here (the source length is unknown) and is deferred to
23
+ * the renderer. Returns the parsed points to thread onto the recorded event.
24
+ */
25
+ export declare function resolveSourceTrim(label: string, start: TimelineOffset | undefined, end: TimelineOffset | undefined): {
26
+ sourceStart?: SourceTrimPoint;
27
+ sourceEnd?: SourceTrimPoint;
28
+ };
29
+ //# sourceMappingURL=sourceTrim.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sourceTrim.d.ts","sourceRoot":"","sources":["../../src/sourceTrim.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAC9E,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAE/D;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,WAAW,CAgB1E;AA2BD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,cAAc,GAAG,SAAS,EACjC,GAAG,EAAE,cAAc,GAAG,SAAS,GAC9B;IAAE,WAAW,CAAC,EAAE,eAAe,CAAC;IAAC,SAAS,CAAC,EAAE,eAAe,CAAA;CAAE,CAiChE"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Shared validation and parsing for the source `crop` rectangle and the
3
+ * `start`/`end` source-trim positions accepted by file overlays, narration video
4
+ * cues, and `selected()` render dependencies.
5
+ *
6
+ * `crop` is a rectangle in the source file's own pixels. `start`/`end` are time
7
+ * strings (`'2s'`, `'0:02'`, `'50%'`): seconds/timecodes resolve to a concrete ms
8
+ * offset into the source, while a percentage stays symbolic (a fraction of the
9
+ * SOURCE duration) because the source length is not known until render time.
10
+ */
11
+ import { parseTimelineOffset } from './timelineOffset.js';
12
+ /**
13
+ * Validates a {@link OverlayCrop}: every field finite, `x`/`y` non-negative, and
14
+ * `width`/`height` strictly positive. Returns the crop unchanged so callers can
15
+ * thread it inline.
16
+ */
17
+ export function validateCrop(label, crop) {
18
+ const { x, y, width, height } = crop;
19
+ const finite = (v) => typeof v === 'number' && Number.isFinite(v);
20
+ if (!finite(x) || !finite(y) || !finite(width) || !finite(height)) {
21
+ throw new Error(`[screenci] ${label} crop must have finite numeric x, y, width, and height (source pixels). Received: ${JSON.stringify(crop)}`);
22
+ }
23
+ if (x < 0 || y < 0) {
24
+ throw new Error(`[screenci] ${label} crop x and y must be >= 0.`);
25
+ }
26
+ if (width <= 0 || height <= 0) {
27
+ throw new Error(`[screenci] ${label} crop width and height must be > 0.`);
28
+ }
29
+ return crop;
30
+ }
31
+ /** Parses a single `start`/`end` time string into a {@link SourceTrimPoint}. */
32
+ function parseSourceTrimPoint(value, label) {
33
+ if (typeof value !== 'string') {
34
+ throw new Error(`[screenci] ${label} must be a time string such as '2s', '0:02', or '50%', got ${typeof value}.`);
35
+ }
36
+ const parsed = parseTimelineOffset(value);
37
+ if (parsed.kind === 'percent') {
38
+ if (parsed.fraction <= 0 || parsed.fraction > 1) {
39
+ throw new Error(`[screenci] ${label} percentage must be greater than 0% and at most 100%. Received: '${value}'.`);
40
+ }
41
+ return { percent: parsed.fraction };
42
+ }
43
+ if (parsed.ms < 0) {
44
+ throw new Error(`[screenci] ${label} must be >= 0. Received: '${value}'.`);
45
+ }
46
+ return { ms: parsed.ms };
47
+ }
48
+ /**
49
+ * Parses and validates the optional `start`/`end` source-trim pair. When both are
50
+ * concrete (ms or percent), `start` must come before `end`; a mixed ms/percent
51
+ * pair cannot be compared here (the source length is unknown) and is deferred to
52
+ * the renderer. Returns the parsed points to thread onto the recorded event.
53
+ */
54
+ export function resolveSourceTrim(label, start, end) {
55
+ const sourceStart = start !== undefined
56
+ ? parseSourceTrimPoint(start, `${label} start`)
57
+ : undefined;
58
+ const sourceEnd = end !== undefined ? parseSourceTrimPoint(end, `${label} end`) : undefined;
59
+ if (sourceStart !== undefined && sourceEnd !== undefined) {
60
+ if ('ms' in sourceStart &&
61
+ 'ms' in sourceEnd &&
62
+ sourceStart.ms >= sourceEnd.ms) {
63
+ throw new Error(`[screenci] ${label} start must be before end (got start ${start}, end ${end}).`);
64
+ }
65
+ if ('percent' in sourceStart &&
66
+ 'percent' in sourceEnd &&
67
+ sourceStart.percent >= sourceEnd.percent) {
68
+ throw new Error(`[screenci] ${label} start must be before end (got start ${start}, end ${end}).`);
69
+ }
70
+ }
71
+ return {
72
+ ...(sourceStart !== undefined && { sourceStart }),
73
+ ...(sourceEnd !== undefined && { sourceEnd }),
74
+ };
75
+ }
76
+ //# sourceMappingURL=sourceTrim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sourceTrim.js","sourceRoot":"","sources":["../../src/sourceTrim.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,78 @@
1
+ import type { ContentMajor, LanguageMajor } from './declare.js';
1
2
  /**
2
- * The studio option groups deferred to the ScreenCI web app at render/record
3
- * time. Set per recording from the `renderOptions`/`recordOptions` `'studio'`
4
- * sentinel; read by the recorder to stamp `metadata.studio`.
3
+ * The studio axes deferred to the ScreenCI web app, read by the recorder to
4
+ * stamp `metadata.studio`. `renderOptions`/`recordOptions` come from the
5
+ * matching `'studio'` sentinel; `languages` is set when the recording's
6
+ * language set is web-owned (`video.languages(studio())`).
5
7
  */
6
8
  export type StudioOptionFlags = {
7
9
  renderOptions: boolean;
8
10
  recordOptions: boolean;
11
+ /** Whether the language set is owned by the web app (`video.languages(studio())`). */
12
+ languages?: boolean;
13
+ };
14
+ /** Brand marking a value produced by {@link studio}. */
15
+ export declare const STUDIO: unique symbol;
16
+ /**
17
+ * A Studio-owned declaration produced by {@link studio}: the content (or, for
18
+ * `languages`, the set) is owned by the ScreenCI web app. `names` lists the
19
+ * declared keys (cue / overlay / audio names, or language codes); `seed`, when
20
+ * present, supplies initial values the web app starts from but may override.
21
+ *
22
+ * `S` carries the seed shape so the consuming feature method validates seed
23
+ * values against its own value type (e.g. narration seeds are checked as
24
+ * narration cues). A blank declaration (`studio([...])`) has `seed` undefined.
25
+ */
26
+ export type StudioMarker<S = unknown> = {
27
+ readonly [STUDIO]: true;
28
+ readonly names: readonly string[];
29
+ readonly seed?: S;
30
+ };
31
+ /** A blank Studio declaration: names only, no seed. */
32
+ export type StudioNames = StudioMarker<undefined> & {
33
+ readonly seed?: undefined;
34
+ /** Phantom guard: keeps the keyless form ({@link StudioPending}) out of feature args. */
35
+ readonly languagesOnly?: never;
36
+ };
37
+ /** A seeded Studio declaration: the web app starts from `seed` but may override it. */
38
+ export type StudioSeeded<S> = StudioMarker<S> & {
39
+ readonly seed: S;
40
+ readonly languagesOnly?: never;
41
+ };
42
+ /**
43
+ * The keyless form, `studio()`: the web app owns the entire set. Only
44
+ * `video.languages(studio())` accepts it (a content feature has no keys to
45
+ * defer, so its keyless form is rejected by {@link FeatureArg}). The
46
+ * `languagesOnly` brand is what makes it unassignable to {@link StudioNames}.
47
+ */
48
+ export type StudioPending = StudioMarker<undefined> & {
49
+ readonly names: readonly [];
50
+ readonly seed?: undefined;
51
+ readonly languagesOnly: true;
52
+ };
53
+ /** Whether a value was produced by {@link studio}. */
54
+ export declare function isStudioMarker(value: unknown): value is StudioMarker;
55
+ /**
56
+ * Declare something Studio-owned: its content is configured in the ScreenCI web
57
+ * app, and code only declares that it exists. Three forms:
58
+ *
59
+ * - `studio(['intro', 'cta'])`: blank names. The keys exist in code (so the test
60
+ * body can reference `narration.intro`), but the web app owns their content.
61
+ * - `studio({ intro: 'Welcome' })`: seeded. The web app starts from these values
62
+ * but owns them: once edited in Studio, the Studio value wins (a seed never
63
+ * clobbers an existing Studio edit).
64
+ * - `studio()`: keyless. Only `video.languages(studio())` accepts it: the web app
65
+ * owns the entire language set. Content features have keys to declare, so their
66
+ * keyless form does not type-check.
67
+ *
68
+ * For `video.languages`, the names are language codes: `studio(['en', 'fi'])`
69
+ * seeds the initial set (web-owned, but starts with those languages).
70
+ */
71
+ export declare function studio(): StudioPending;
72
+ export declare function studio<const N extends readonly string[]>(names: N): StudioNames & {
73
+ readonly names: N;
74
+ };
75
+ export declare function studio<const S extends ContentMajor<unknown> | LanguageMajor<unknown>>(seed: S): StudioSeeded<S> & {
76
+ readonly names: readonly (keyof S & string)[];
9
77
  };
10
78
  //# sourceMappingURL=studio.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"studio.d.ts","sourceRoot":"","sources":["../../src/studio.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,aAAa,EAAE,OAAO,CAAA;IACtB,aAAa,EAAE,OAAO,CAAA;CACvB,CAAA"}
1
+ {"version":3,"file":"studio.d.ts","sourceRoot":"","sources":["../../src/studio.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAE/D;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,aAAa,EAAE,OAAO,CAAA;IACtB,aAAa,EAAE,OAAO,CAAA;IACtB,sFAAsF;IACtF,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AAED,wDAAwD;AACxD,eAAO,MAAM,MAAM,EAAE,OAAO,MAAsC,CAAA;AAElE;;;;;;;;;GASG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,GAAG,OAAO,IAAI;IACtC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;CAClB,CAAA;AAED,uDAAuD;AACvD,MAAM,MAAM,WAAW,GAAG,YAAY,CAAC,SAAS,CAAC,GAAG;IAClD,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAA;IACzB,yFAAyF;IACzF,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,CAAA;CAC/B,CAAA;AAED,uFAAuF;AACvF,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG;IAC9C,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;IAChB,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,CAAA;CAC/B,CAAA;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,YAAY,CAAC,SAAS,CAAC,GAAG;IACpD,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,CAAA;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAA;IACzB,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAA;CAC7B,CAAA;AAED,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAMpE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,MAAM,IAAI,aAAa,CAAA;AACvC,wBAAgB,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,SAAS,MAAM,EAAE,EACtD,KAAK,EAAE,CAAC,GACP,WAAW,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAA;AACtC,wBAAgB,MAAM,CACpB,KAAK,CAAC,CAAC,SAAS,YAAY,CAAC,OAAO,CAAC,GAAG,aAAa,CAAC,OAAO,CAAC,EAC9D,IAAI,EAAE,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,EAAE,CAAA;CAAE,CAAA"}
@@ -1,2 +1,20 @@
1
- export {};
1
+ /** Brand marking a value produced by {@link studio}. */
2
+ export const STUDIO = Symbol.for('screenci.studio');
3
+ /** Whether a value was produced by {@link studio}. */
4
+ export function isStudioMarker(value) {
5
+ return (typeof value === 'object' &&
6
+ value !== null &&
7
+ value[STUDIO] === true);
8
+ }
9
+ export function studio(arg) {
10
+ if (arg === undefined)
11
+ return { [STUDIO]: true, names: [] };
12
+ if (Array.isArray(arg))
13
+ return { [STUDIO]: true, names: [...arg] };
14
+ return {
15
+ [STUDIO]: true,
16
+ names: Object.keys(arg),
17
+ seed: arg,
18
+ };
19
+ }
2
20
  //# sourceMappingURL=studio.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"studio.js","sourceRoot":"","sources":["../../src/studio.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"studio.js","sourceRoot":"","sources":["../../src/studio.ts"],"names":[],"mappings":"AAeA,wDAAwD;AACxD,MAAM,CAAC,MAAM,MAAM,GAAkB,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;AA2ClE,sDAAsD;AACtD,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACb,KAAsC,CAAC,MAAM,CAAC,KAAK,IAAI,CACzD,CAAA;AACH,CAAC;AAyBD,MAAM,UAAU,MAAM,CACpB,GAAiD;IAEjD,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;IAC3D,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,CAAA;IAClE,OAAO;QACL,CAAC,MAAM,CAAC,EAAE,IAAI;QACd,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;QACvB,IAAI,EAAE,GAA4B;KACnC,CAAA;AACH,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Parsing for the string position arguments accepted by the narration and
3
+ * overlay controllers, e.g. `narration.intro('0:02')` or `overlays.tip('56%')`.
4
+ *
5
+ * A controller called with a NUMBER keeps its existing meaning (a relative
6
+ * duration in milliseconds). A controller called with a STRING means an absolute
7
+ * position in the final video: "this cue/overlay window should reach this point."
8
+ * Only strings flow through this module; numbers never do.
9
+ *
10
+ * Absolute positions are resolved against the finished render, so a percentage
11
+ * cannot be turned into milliseconds here (the total length is not known until
12
+ * render time). The parser therefore returns a discriminated union: concrete
13
+ * `'<n>s'`/timecode forms resolve to milliseconds, while `'<n>%'` surfaces a
14
+ * fraction the renderer multiplies by the final total.
15
+ */
16
+ /**
17
+ * A position within the final video. Strings only (numbers are the existing
18
+ * duration path on each controller). Accepts:
19
+ * - `'<n>s'` seconds, e.g. `'2s'` or `'5.51s'`
20
+ * - `'m:ss(.f)'` timecode minutes:seconds, e.g. `'0:05.51'` (= 5.51s)
21
+ * - `'h:mm:ss(.f)'` timecode hours:minutes:seconds, e.g. `'1:02:03.5'`
22
+ * - `'<n>%'` percentage of the total video, e.g. `'56.1%'`
23
+ *
24
+ * The template-literal members give editor hints for the common forms; the
25
+ * trailing `string` keeps timecodes (which the type system cannot express)
26
+ * assignable. Validation happens at runtime in {@link parseTimelineOffset}.
27
+ */
28
+ export type TimelineOffset = `${number}s` | `${number}%` | string;
29
+ /** Result of parsing a {@link TimelineOffset} string. */
30
+ export type ParsedTimelineOffset =
31
+ /** From `'<n>s'` or a timecode: a concrete absolute position in milliseconds. */
32
+ {
33
+ kind: 'absolute';
34
+ ms: number;
35
+ }
36
+ /** From `'<n>%'`: a fraction (0.561 for `'56.1%'`), resolved against the total at render. */
37
+ | {
38
+ kind: 'percent';
39
+ fraction: number;
40
+ };
41
+ /**
42
+ * Parses a string position into a {@link ParsedTimelineOffset}. Throws on any
43
+ * unrecognized or malformed input. Pure: no I/O, no globals.
44
+ */
45
+ export declare function parseTimelineOffset(value: string): ParsedTimelineOffset;
46
+ //# sourceMappingURL=timelineOffset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timelineOffset.d.ts","sourceRoot":"","sources":["../../src/timelineOffset.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,cAAc,GAAG,GAAG,MAAM,GAAG,GAAG,GAAG,MAAM,GAAG,GAAG,MAAM,CAAA;AAEjE,yDAAyD;AACzD,MAAM,MAAM,oBAAoB;AAC9B,iFAAiF;AAC/E;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE;AAClC,6FAA6F;GAC3F;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAA;AAmBzC;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,oBAAoB,CAqCvE"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Parsing for the string position arguments accepted by the narration and
3
+ * overlay controllers, e.g. `narration.intro('0:02')` or `overlays.tip('56%')`.
4
+ *
5
+ * A controller called with a NUMBER keeps its existing meaning (a relative
6
+ * duration in milliseconds). A controller called with a STRING means an absolute
7
+ * position in the final video: "this cue/overlay window should reach this point."
8
+ * Only strings flow through this module; numbers never do.
9
+ *
10
+ * Absolute positions are resolved against the finished render, so a percentage
11
+ * cannot be turned into milliseconds here (the total length is not known until
12
+ * render time). The parser therefore returns a discriminated union: concrete
13
+ * `'<n>s'`/timecode forms resolve to milliseconds, while `'<n>%'` surfaces a
14
+ * fraction the renderer multiplies by the final total.
15
+ */
16
+ const SECONDS_RE = /^(\d+(?:\.\d+)?)s$/;
17
+ const PERCENT_RE = /^(\d+(?:\.\d+)?)%$/;
18
+ const TIMECODE_MIN_RE = /^(\d+):(\d{2}(?:\.\d+)?)$/;
19
+ const TIMECODE_HOUR_RE = /^(\d+):(\d{2}):(\d{2}(?:\.\d+)?)$/;
20
+ function invalidTimecode(value) {
21
+ throw new Error(`invalid timecode '${value}'; expected m:ss(.f) or h:mm:ss(.f) with mm/ss < 60`);
22
+ }
23
+ function invalidPosition(value) {
24
+ throw new Error(`invalid position '${value}'; expected '<n>s', a timecode 'm:ss(.f)'/'h:mm:ss(.f)', or '<n>%'`);
25
+ }
26
+ /**
27
+ * Parses a string position into a {@link ParsedTimelineOffset}. Throws on any
28
+ * unrecognized or malformed input. Pure: no I/O, no globals.
29
+ */
30
+ export function parseTimelineOffset(value) {
31
+ const trimmed = value.trim();
32
+ if (trimmed.length === 0)
33
+ invalidPosition(value);
34
+ const percent = PERCENT_RE.exec(trimmed);
35
+ if (percent !== null) {
36
+ return { kind: 'percent', fraction: Number(percent[1]) / 100 };
37
+ }
38
+ const seconds = SECONDS_RE.exec(trimmed);
39
+ if (seconds !== null) {
40
+ return { kind: 'absolute', ms: Number(seconds[1]) * 1000 };
41
+ }
42
+ const hour = TIMECODE_HOUR_RE.exec(trimmed);
43
+ if (hour !== null) {
44
+ const hours = Number(hour[1]);
45
+ const minutes = Number(hour[2]);
46
+ const secs = Number(hour[3]);
47
+ if (minutes >= 60 || secs >= 60)
48
+ invalidTimecode(value);
49
+ return { kind: 'absolute', ms: (hours * 3600 + minutes * 60 + secs) * 1000 };
50
+ }
51
+ const min = TIMECODE_MIN_RE.exec(trimmed);
52
+ if (min !== null) {
53
+ const minutes = Number(min[1]);
54
+ const secs = Number(min[2]);
55
+ if (secs >= 60)
56
+ invalidTimecode(value);
57
+ return { kind: 'absolute', ms: (minutes * 60 + secs) * 1000 };
58
+ }
59
+ // A bare `h:mm:ss`-shaped string that failed the strict patterns above (for
60
+ // example two-digit seconds were not supplied) is reported as a timecode error
61
+ // so the message points at the right fix.
62
+ if (/^\d+:\d/.test(trimmed))
63
+ invalidTimecode(value);
64
+ invalidPosition(value);
65
+ }
66
+ //# sourceMappingURL=timelineOffset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timelineOffset.js","sourceRoot":"","sources":["../../src/timelineOffset.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAuBH,MAAM,UAAU,GAAG,oBAAoB,CAAA;AACvC,MAAM,UAAU,GAAG,oBAAoB,CAAA;AACvC,MAAM,eAAe,GAAG,2BAA2B,CAAA;AACnD,MAAM,gBAAgB,GAAG,mCAAmC,CAAA;AAE5D,SAAS,eAAe,CAAC,KAAa;IACpC,MAAM,IAAI,KAAK,CACb,qBAAqB,KAAK,qDAAqD,CAChF,CAAA;AACH,CAAC;AAED,SAAS,eAAe,CAAC,KAAa;IACpC,MAAM,IAAI,KAAK,CACb,qBAAqB,KAAK,oEAAoE,CAC/F,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAA;IAC5B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,eAAe,CAAC,KAAK,CAAC,CAAA;IAEhD,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACxC,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,EAAE,CAAA;IAChE,CAAC;IAED,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACxC,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAAA;IAC5D,CAAC;IAED,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC3C,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;QAC7B,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;QAC/B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;QAC5B,IAAI,OAAO,IAAI,EAAE,IAAI,IAAI,IAAI,EAAE;YAAE,eAAe,CAAC,KAAK,CAAC,CAAA;QACvD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,KAAK,GAAG,IAAI,GAAG,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,EAAE,CAAA;IAC9E,CAAC;IAED,MAAM,GAAG,GAAG,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACzC,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACjB,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;QAC9B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;QAC3B,IAAI,IAAI,IAAI,EAAE;YAAE,eAAe,CAAC,KAAK,CAAC,CAAA;QACtC,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,EAAE,CAAA;IAC/D,CAAC;IAED,4EAA4E;IAC5E,+EAA+E;IAC/E,0CAA0C;IAC1C,IAAI,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,eAAe,CAAC,KAAK,CAAC,CAAA;IAEnD,eAAe,CAAC,KAAK,CAAC,CAAA;AACxB,CAAC"}
@@ -1,3 +1,41 @@
1
+ /**
2
+ * A render dependency declared via `selected(name)`: the `from` render embeds the
3
+ * `to` render's output as an overlay. Used for best-effort registration-time
4
+ * warnings; the authoritative existence/one-level/medium checks run on the
5
+ * backend at upload (it sees every file and language).
6
+ */
7
+ export type DependencyReference = {
8
+ /** Name of the dependent video/screenshot (the one declaring `selected`). */
9
+ from: string;
10
+ /** Name referenced via `selected(name)` (the embedded target). */
11
+ to: string;
12
+ };
13
+ /**
14
+ * Videos and screenshots share a single name namespace within a run (both are
15
+ * rows in the same table, addressed by `selected(name)`). Titles must therefore
16
+ * be unique across BOTH mediums, not just within one. {@link findDuplicateTitles}
17
+ * already enforces this because every video and screenshot title is collected
18
+ * into the same list before checking, so a name reused across mediums surfaces as
19
+ * a duplicate.
20
+ */
1
21
  export declare function findDuplicateTitles(titles: readonly string[]): string[];
2
22
  export declare function formatDuplicateTitlesMessage(duplicates: readonly string[]): string;
23
+ /**
24
+ * Returns the dependency references that point at themselves (`from === to`). A
25
+ * render cannot embed its own output, so these are always a mistake.
26
+ */
27
+ export declare function findSelfReferences(references: readonly DependencyReference[]): DependencyReference[];
28
+ /**
29
+ * Returns the dependency references whose target (`to`) is not among the
30
+ * `knownTitles` discovered in the run. This is a best-effort warning: a target
31
+ * may legitimately live in another file not part of this discovery, so the
32
+ * authoritative existence check runs on the backend at upload.
33
+ */
34
+ export declare function findMissingReferences(references: readonly DependencyReference[], knownTitles: readonly string[]): DependencyReference[];
35
+ /**
36
+ * Formats best-effort registration-time warnings for self-references and
37
+ * obviously missing dependency targets. Returns an empty string when there is
38
+ * nothing to warn about, so callers can skip emitting anything.
39
+ */
40
+ export declare function formatDependencyWarnings(selfReferences: readonly DependencyReference[], missingReferences: readonly DependencyReference[]): string;
3
41
  //# sourceMappingURL=titleValidation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"titleValidation.d.ts","sourceRoot":"","sources":["../../src/titleValidation.ts"],"names":[],"mappings":"AAAA,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAUvE;AAED,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,MAAM,CAUR"}
1
+ {"version":3,"file":"titleValidation.d.ts","sourceRoot":"","sources":["../../src/titleValidation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAA;IACZ,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAA;CACX,CAAA;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAUvE;AAED,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,MAAM,CAUR;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,SAAS,mBAAmB,EAAE,GACzC,mBAAmB,EAAE,CAEvB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,SAAS,mBAAmB,EAAE,EAC1C,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,mBAAmB,EAAE,CAGvB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,cAAc,EAAE,SAAS,mBAAmB,EAAE,EAC9C,iBAAiB,EAAE,SAAS,mBAAmB,EAAE,GAChD,MAAM,CAcR"}
@@ -1,3 +1,11 @@
1
+ /**
2
+ * Videos and screenshots share a single name namespace within a run (both are
3
+ * rows in the same table, addressed by `selected(name)`). Titles must therefore
4
+ * be unique across BOTH mediums, not just within one. {@link findDuplicateTitles}
5
+ * already enforces this because every video and screenshot title is collected
6
+ * into the same list before checking, so a name reused across mediums surfaces as
7
+ * a duplicate.
8
+ */
1
9
  export function findDuplicateTitles(titles) {
2
10
  const counts = new Map();
3
11
  for (const title of titles) {
@@ -16,4 +24,38 @@ export function formatDuplicateTitlesMessage(duplicates) {
16
24
  }
17
25
  return lines.join('\n');
18
26
  }
27
+ /**
28
+ * Returns the dependency references that point at themselves (`from === to`). A
29
+ * render cannot embed its own output, so these are always a mistake.
30
+ */
31
+ export function findSelfReferences(references) {
32
+ return references.filter((ref) => ref.from === ref.to);
33
+ }
34
+ /**
35
+ * Returns the dependency references whose target (`to`) is not among the
36
+ * `knownTitles` discovered in the run. This is a best-effort warning: a target
37
+ * may legitimately live in another file not part of this discovery, so the
38
+ * authoritative existence check runs on the backend at upload.
39
+ */
40
+ export function findMissingReferences(references, knownTitles) {
41
+ const known = new Set(knownTitles);
42
+ return references.filter((ref) => !known.has(ref.to));
43
+ }
44
+ /**
45
+ * Formats best-effort registration-time warnings for self-references and
46
+ * obviously missing dependency targets. Returns an empty string when there is
47
+ * nothing to warn about, so callers can skip emitting anything.
48
+ */
49
+ export function formatDependencyWarnings(selfReferences, missingReferences) {
50
+ const lines = [];
51
+ for (const ref of selfReferences) {
52
+ lines.push(` - "${ref.from}" uses selected("${ref.to}"), which references itself. A render cannot embed its own output.`);
53
+ }
54
+ for (const ref of missingReferences) {
55
+ lines.push(` - "${ref.from}" uses selected("${ref.to}"), but no video or screenshot named "${ref.to}" was found in this run.`);
56
+ }
57
+ if (lines.length === 0)
58
+ return '';
59
+ return ['Dependency warnings:', ...lines].join('\n');
60
+ }
19
61
  //# sourceMappingURL=titleValidation.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"titleValidation.js","sourceRoot":"","sources":["../../src/titleValidation.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,mBAAmB,CAAC,MAAyB;IAC3D,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAA;IAExC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IACjD,CAAC;IAED,OAAO,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;SACzB,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC;SAChC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAA;AAC5B,CAAC;AAED,MAAM,UAAU,4BAA4B,CAC1C,UAA6B;IAE7B,MAAM,KAAK,GAAG;QACZ,gEAAgE;KACjE,CAAA;IAED,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,QAAQ,KAAK,GAAG,CAAC,CAAA;IAC9B,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACzB,CAAC"}
1
+ {"version":3,"file":"titleValidation.js","sourceRoot":"","sources":["../../src/titleValidation.ts"],"names":[],"mappings":"AAaA;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAyB;IAC3D,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAA;IAExC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IACjD,CAAC;IAED,OAAO,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;SACzB,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC;SAChC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAA;AAC5B,CAAC;AAED,MAAM,UAAU,4BAA4B,CAC1C,UAA6B;IAE7B,MAAM,KAAK,GAAG;QACZ,gEAAgE;KACjE,CAAA;IAED,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,QAAQ,KAAK,GAAG,CAAC,CAAA;IAC9B,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACzB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,UAA0C;IAE1C,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC,CAAA;AACxD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAA0C,EAC1C,WAA8B;IAE9B,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,CAAA;IAClC,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CACtC,cAA8C,EAC9C,iBAAiD;IAEjD,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,KAAK,MAAM,GAAG,IAAI,cAAc,EAAE,CAAC;QACjC,KAAK,CAAC,IAAI,CACR,QAAQ,GAAG,CAAC,IAAI,oBAAoB,GAAG,CAAC,EAAE,oEAAoE,CAC/G,CAAA;IACH,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,iBAAiB,EAAE,CAAC;QACpC,KAAK,CAAC,IAAI,CACR,QAAQ,GAAG,CAAC,IAAI,oBAAoB,GAAG,CAAC,EAAE,yCAAyC,GAAG,CAAC,EAAE,0BAA0B,CACpH,CAAA;IACH,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IACjC,OAAO,CAAC,sBAAsB,EAAE,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACtD,CAAC"}
@@ -139,6 +139,13 @@ export type RenderOptions = {
139
139
  narration?: {
140
140
  /** 0-1: 1=mask size equals shorter side of output */
141
141
  size?: number;
142
+ /**
143
+ * Narration size when the recording is shrunk via `resizeRecording()`.
144
+ * When set, the narration bubble scales down and tightens into its corner
145
+ * in sync with the recording size transition. Omit to keep a fixed size.
146
+ * 0-1: same units as `size`.
147
+ */
148
+ sizeZoomed?: number;
142
149
  /** 0-1: 0=square, 1=circle */
143
150
  roundness?: number;
144
151
  shape?: 'rounded';
@@ -403,6 +410,35 @@ export type RecordOptions = {
403
410
  * @default 0
404
411
  */
405
412
  captureAudio?: number;
413
+ /**
414
+ * CSS selectors whose matching elements are masked from the very first frame,
415
+ * before any page script runs. Use this for elements that are always secret
416
+ * (API keys, account numbers) so there is no window where they could be
417
+ * captured in the clear.
418
+ *
419
+ * The mask is applied client side in the live DOM, so the obscured pixels
420
+ * never enter `recording.mp4` and are never uploaded. For masking that starts
421
+ * at a specific moment, use the `redact()` helper instead.
422
+ *
423
+ * @example ['.api-key', '[data-sensitive]']
424
+ */
425
+ redact?: string[];
426
+ /**
427
+ * Vertical framing bias (0–1) used when a plain interaction (a `click()`,
428
+ * `fill()`, `scrollIntoViewIfNeeded()`, etc. that is not zooming) scrolls its
429
+ * target into view.
430
+ *
431
+ * `0` reveals the target just inside the top edge, `1` places it dead center,
432
+ * and the default `0.2` frames it gently toward the upper third so
433
+ * already-visible elements are not yanked to the center on every click.
434
+ *
435
+ * This only affects plain scroll reveals. Zooming (`zoomTo`/`autoZoom`) keeps
436
+ * its tight centering, and an explicit per-call `centering` (via
437
+ * `autoZoomOptions` or `scrollIntoViewIfNeeded({ centering })`) always wins.
438
+ *
439
+ * @default 0.2
440
+ */
441
+ scrollCentering?: number;
406
442
  };
407
443
  /**
408
444
  * Encoder preset for the realtime screen capture. See {@link RecordOptions.encoder}.
@@ -490,6 +526,42 @@ export type ScreenCILocatorPostClickMoveOptions = CursorMoveTimingOption & {
490
526
  easing?: Easing;
491
527
  padding?: number;
492
528
  };
529
+ /**
530
+ * How a redacted region is drawn over the page. The mask is an opaque panel
531
+ * applied client side in the live DOM, so the obscured pixels never enter the
532
+ * recording and are never uploaded, and it cannot leak under any renderer.
533
+ *
534
+ * By default the panel is filled with a color sampled from the surface beneath
535
+ * it, so it blends in as a clean block. Set `color` for a fixed fill, or `css`
536
+ * to fully style the panel.
537
+ */
538
+ export type RedactStyle = {
539
+ /**
540
+ * Opaque fill color, e.g. '#111' or 'black'. Omit to sample a color from the
541
+ * surface underneath the element.
542
+ */
543
+ color?: string;
544
+ /** Corner radius of the mask in px (default 12). */
545
+ radius?: number;
546
+ /** CSS box-shadow for the mask, or `false` to disable it. */
547
+ shadow?: string | false;
548
+ /**
549
+ * Extra CSS applied to the mask panel for full custom styling, e.g.
550
+ * `'background: repeating-linear-gradient(45deg,#222 0 6px,#333 6px 12px)'`.
551
+ * Applied last, so it overrides the defaults. Keep the panel opaque, or the
552
+ * content underneath may show through.
553
+ */
554
+ css?: string;
555
+ };
556
+ /** Options for {@link redact} and the per-action `redact` switch. */
557
+ export type RedactOptions = {
558
+ style?: RedactStyle;
559
+ };
560
+ /** Handle returned by a persistent {@link redact} call. */
561
+ export type RedactHandle = {
562
+ /** Remove this mask, revealing the element again. */
563
+ unredact(): Promise<void>;
564
+ };
493
565
  export type ScreenCILocatorFillOptions = ScreenCILocatorClickOptions & {
494
566
  /**
495
567
  * When `true`, forces the pre-type click animation even if the target input
@@ -506,6 +578,13 @@ export type ScreenCILocatorFillOptions = ScreenCILocatorClickOptions & {
506
578
  };
507
579
  /** Hide the cursor while typing; shown again on the next mouse move. */
508
580
  hideMouse?: boolean;
581
+ /**
582
+ * Mask the typed value in the recording so the secret never enters
583
+ * `recording.mp4`. The mask is applied before the first character is typed,
584
+ * so the value is never captured in the clear. Pass `true` for the default
585
+ * mask, or `RedactOptions` to customize it.
586
+ */
587
+ redact?: boolean | RedactOptions;
509
588
  click?: false | ScreenCILocatorClickOptions;
510
589
  postClickMove?: ScreenCILocatorPostClickMoveOptions;
511
590
  autoZoomOptions?: AutoZoomOptions;
@@ -524,6 +603,12 @@ export type ScreenCILocatorPressSequentiallyOptions = Omit<NonNullable<Parameter
524
603
  };
525
604
  /** Hide the cursor while typing; shown again on the next mouse move. */
526
605
  hideMouse?: boolean;
606
+ /**
607
+ * Mask the typed value in the recording so the secret never enters
608
+ * `recording.mp4`. Applied before the first character is typed. Pass `true`
609
+ * for the default mask, or `RedactOptions` to customize it.
610
+ */
611
+ redact?: boolean | RedactOptions;
527
612
  autoZoomOptions?: AutoZoomOptions;
528
613
  };
529
614
  export type ScreenCILocatorCheckOptions = NonNullable<Parameters<Locator['check']>[0]> & CursorMoveOptions & {