@mengine/medeo-client 0.1.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.
@@ -0,0 +1,2373 @@
1
+ import { InferInputType } from "loro-mirror";
2
+ import { z } from "zod";
3
+ import { LoroDoc, PeerID } from "loro-crdt";
4
+ import { DocState } from "@mengine/sync";
5
+ import { BaseDocStorage, Connection, DocDiff, DocSnapshotRecord, DocStorage, DocStorageOptions, DocUpdate, DocUpdateRecord } from "@mengine/storage";
6
+
7
+ //#region src/client/base64.d.ts
8
+ declare function bytesToBase64(bytes: Uint8Array): string;
9
+ declare function base64ToBytes(base64: string): Uint8Array;
10
+ //#endregion
11
+ //#region src/client/wire.d.ts
12
+ interface MengineDocumentVersion {
13
+ update_seq: number;
14
+ /** base64 `VersionVector.encode` of the server oplog — the sync anchor. */
15
+ server_vv: string;
16
+ /** base64 `encodeFrontiers` of the server oplog heads. */
17
+ frontiers: string;
18
+ }
19
+ interface MengineSnapshotResponse {
20
+ snapshot: string;
21
+ version: MengineDocumentVersion;
22
+ }
23
+ /** Business + causal metadata for one update (audit / rollback anchor). */
24
+ interface MengineUpdateMeta {
25
+ semantic_op: string | null;
26
+ payload: unknown;
27
+ intent: string | null;
28
+ message: string | null;
29
+ parse_error: boolean;
30
+ peer: string;
31
+ counter: number;
32
+ lamport: number;
33
+ timestamp: number;
34
+ frontiers: string;
35
+ }
36
+ /**
37
+ * Response to `GET .../sync?from=<vv b64>`: the ops the caller is missing as one
38
+ * merged Loro update blob (`export({mode:'update', from})`) plus the server's
39
+ * current oplog VV. Loro resolves the causal partial order inside the blob, so
40
+ * there is no per-update envelope; `import` is idempotent, so an already-current
41
+ * caller gets a framing-only blob that applies as a no-op. Mirrors the server's
42
+ * `SyncWireResponse`.
43
+ */
44
+ interface MengineSyncResponse {
45
+ /** base64 merged Loro update blob (empty string when the caller is current). */
46
+ update: string;
47
+ server_vv: string;
48
+ }
49
+ interface MengineAuditEntry extends MengineUpdateMeta {
50
+ update_seq: number;
51
+ }
52
+ interface MengineAuditResponse {
53
+ entries: MengineAuditEntry[];
54
+ }
55
+ interface MenginePushUpdateResponse {
56
+ kind: 'ack' | 'duplicate';
57
+ update_seq: number | null;
58
+ version: MengineDocumentVersion;
59
+ }
60
+ interface MengineRejectedResponse {
61
+ kind: 'rejected';
62
+ code: string;
63
+ message: string;
64
+ server_version: MengineDocumentVersion;
65
+ }
66
+ interface MengineSseUpdateEvent {
67
+ update_seq: number;
68
+ updates: string[];
69
+ meta: MengineUpdateMeta;
70
+ version: MengineDocumentVersion;
71
+ }
72
+ type MenginePushResponse = MenginePushUpdateResponse | MengineRejectedResponse;
73
+ //#endregion
74
+ //#region src/client/http-client.d.ts
75
+ interface MengineHttpClientOptions {
76
+ docId: string;
77
+ httpOrigin: string;
78
+ authToken?: string;
79
+ /**
80
+ * The end-user id sent as the `medeo-user-id` header. Accepts either a static
81
+ * string or a getter evaluated per request. Prefer the getter when the login
82
+ * state hydrates asynchronously (e.g. the browser editor): the client is
83
+ * constructed eagerly but each request reads the latest id, so an id that is
84
+ * not yet ready at construction time is picked up once it settles — no need to
85
+ * defer client/session creation until auth is ready.
86
+ */
87
+ userId?: string | (() => string | undefined);
88
+ fetchImpl?: typeof fetch;
89
+ }
90
+ declare class MengineHttpRequestError extends Error {
91
+ readonly status: number;
92
+ readonly payload: unknown;
93
+ constructor(status: number, payload: unknown);
94
+ }
95
+ declare class MengineHttpClient {
96
+ private readonly options;
97
+ private readonly fetchImpl;
98
+ constructor(options: MengineHttpClientOptions);
99
+ fetchSnapshot(): Promise<MengineSnapshotResponse>;
100
+ bootstrapSnapshot(snapshot: Uint8Array): Promise<MengineSnapshotResponse>;
101
+ /**
102
+ * Loro VV-diff pull: send the caller's oplog `VersionVector.encode` as `from`
103
+ * (omit for a full pull) and receive exactly the updates it is missing plus
104
+ * the server's current VV. Replaces the old integer `after_update_id` cursor.
105
+ */
106
+ sync(fromVV?: Uint8Array): Promise<MengineSyncResponse>;
107
+ /** Audit trail: extracted metadata per accepted update, in log order. */
108
+ audit(): Promise<MengineAuditResponse>;
109
+ pushUpdate(update: Uint8Array, baseVersion?: unknown): Promise<MenginePushUpdateResponse>;
110
+ eventsUrl(): string;
111
+ headers(): Headers;
112
+ fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
113
+ private requestJson;
114
+ private endpoint;
115
+ }
116
+ //#endregion
117
+ //#region src/client/sse.d.ts
118
+ interface MengineEventStreamOptions {
119
+ client: MengineHttpClient;
120
+ signal?: AbortSignal;
121
+ /** Fired once the stream response is established (HTTP ok, body readable). */
122
+ onOpen?(): void;
123
+ onUpdate(event: MengineSseUpdateEvent): void;
124
+ }
125
+ declare function readMengineEventStream(options: MengineEventStreamOptions): Promise<void>;
126
+ //#endregion
127
+ //#region src/document/generated/video-draft-idl.d.ts
128
+ /**
129
+ * @generated by @mengine/idl-codegen — DO NOT EDIT MANUALLY.
130
+ *
131
+ * VideoDraft read-view types extracted from the Smithy IDL
132
+ * (medeo-v2-design-materials). Re-run `vp run @mengine/idl-codegen#sync`.
133
+ * Source IDL commit: 902e82e3fec1e94b36e14ef671de91abd210dc86
134
+ *
135
+ * WHY EVERY FIELD IS `T | undefined`:
136
+ * AWS `smithy-typescript-codegen` widens every structure member to
137
+ * `T | undefined` regardless of the Smithy `@required` trait — it favours
138
+ * deserialization robustness (a peer may omit a field on the wire) over
139
+ * encoding requiredness in the static type. So these generated types are NOT
140
+ * the source of truth for which fields are required: the Smithy `.smithy`
141
+ * model is. Many fields here (e.g. `VideoClipPart.play_in` / `play_out` /
142
+ * `volume` / `origin_media_id`) are `@required` in the contract.
143
+ *
144
+ * Requiredness is restored at the write-time gate, not here: see the part
145
+ * schemas in `../zod-schema.ts`, which re-mark the `@required` fields as
146
+ * mandatory before a VideoDocument is committed to Loro. Do not assume a
147
+ * field is optional just because its type is `| undefined`.
148
+ */
149
+ /**
150
+ * @public
151
+ * @enum
152
+ */
153
+ declare const PartKind$1: {
154
+ readonly BGM: 'bgm';
155
+ readonly CAPTION: 'caption';
156
+ readonly SPEECH: 'speech';
157
+ readonly VIDEO_CLIP: 'video_clip';
158
+ };
159
+ /**
160
+ * @public
161
+ */
162
+ type PartKind$1 = (typeof PartKind$1)[keyof typeof PartKind$1];
163
+ /**
164
+ * @public
165
+ */
166
+ interface VoiceSummary {
167
+ name: string | undefined;
168
+ id: string | undefined;
169
+ }
170
+ /**
171
+ * @public
172
+ */
173
+ interface SpeechPart$1 {
174
+ id: string | undefined;
175
+ duration_ms: number | undefined;
176
+ /**
177
+ * Types of parts that can be contained in tracks
178
+ * @public
179
+ */
180
+ kind: PartKind$1 | undefined;
181
+ audio_script: string | undefined;
182
+ volume: number | undefined;
183
+ audio_storage_key: string | undefined;
184
+ origin_speech_id: string | undefined;
185
+ voice: VoiceSummary | undefined;
186
+ caption_ids: string[] | undefined;
187
+ }
188
+ /**
189
+ * Caption visual style configuration
190
+ * @public
191
+ */
192
+ interface CaptionStyle {
193
+ /**
194
+ * Font ID referencing a font from the font library. Defaults to system font if not provided
195
+ * @public
196
+ */
197
+ font_id?: string | undefined;
198
+ /**
199
+ * Font size in points
200
+ * @public
201
+ */
202
+ font_size?: number | undefined;
203
+ /**
204
+ * Font color as hex string (e.g. "#FFFFFF")
205
+ * @public
206
+ */
207
+ font_color?: string | undefined;
208
+ /**
209
+ * Numeric font weight, e.g. 400 for regular or 700 for bold
210
+ * @public
211
+ */
212
+ font_weight?: number | undefined;
213
+ /**
214
+ * Caption entrance animation preset ID, e.g. "fade", "slideUp", or "none"
215
+ * @public
216
+ */
217
+ entrance_animation?: string | undefined;
218
+ /**
219
+ * Caption entrance animation duration in milliseconds
220
+ * @public
221
+ */
222
+ entrance_animation_duration_ms?: number | undefined;
223
+ /**
224
+ * Caption outline/stroke color as hex string (e.g. "#000000")
225
+ * @public
226
+ */
227
+ stroke_color?: string | undefined;
228
+ /**
229
+ * Caption outline/stroke width in pixels
230
+ * @public
231
+ */
232
+ stroke_width?: number | undefined;
233
+ /**
234
+ * Caption center X position as a percentage (0.0 to 1.0)
235
+ * @public
236
+ */
237
+ position_x?: number | undefined;
238
+ /**
239
+ * Caption center Y position as a percentage (0.0 to 1.0)
240
+ * @public
241
+ */
242
+ position_y?: number | undefined;
243
+ }
244
+ /**
245
+ * @public
246
+ */
247
+ interface CaptionPart$1 {
248
+ id: string | undefined;
249
+ duration_ms: number | undefined;
250
+ /**
251
+ * Types of parts that can be contained in tracks
252
+ * @public
253
+ */
254
+ kind: PartKind$1 | undefined;
255
+ speech_part_id: string | undefined;
256
+ text: string | undefined;
257
+ start_ms: number | undefined;
258
+ /**
259
+ * Caption visual style (font, size, color, weight, animation, outline). Uses default style if not provided
260
+ * @public
261
+ */
262
+ style?: CaptionStyle | undefined;
263
+ }
264
+ /**
265
+ * @public
266
+ * @enum
267
+ */
268
+ declare const AspectRatio: {
269
+ readonly RATIO_16_9: '16:9';
270
+ readonly RATIO_9_16: '9:16';
271
+ };
272
+ /**
273
+ * @public
274
+ */
275
+ type AspectRatio = (typeof AspectRatio)[keyof typeof AspectRatio];
276
+ /**
277
+ * @public
278
+ * @enum
279
+ */
280
+ declare const AssetSource: {
281
+ readonly AI_IMAGES: 'ai_images';
282
+ readonly AI_VIDEOS: 'ai_videos';
283
+ readonly MY_UPLOADED_ASSETS: 'my_uploaded_assets';
284
+ readonly STOCK_VIDEOS: 'stock_videos';
285
+ };
286
+ /**
287
+ * @public
288
+ */
289
+ type AssetSource = (typeof AssetSource)[keyof typeof AssetSource];
290
+ /**
291
+ * An extra aspect ratio variant with independent storage keys for thumbnail,
292
+ * preview video, and highlight video. Stored inside video_settings JSONB.
293
+ * @public
294
+ */
295
+ interface RatioVariant {
296
+ aspect_ratio: string | undefined;
297
+ thumb_storage_key: string | undefined;
298
+ video_storage_key: string | undefined;
299
+ highlight_video_storage_key?: string | undefined;
300
+ }
301
+ /**
302
+ * @public
303
+ */
304
+ interface VideoCreationSettings$1 {
305
+ voice_id?: string | undefined;
306
+ duration_ms?: number | undefined;
307
+ aspect_ratio?: AspectRatio | undefined;
308
+ video_style_id?: string | undefined;
309
+ asset_sources?: AssetSource[] | undefined;
310
+ recipe_id?: string | undefined;
311
+ enable_high_cost_confirm?: boolean | undefined;
312
+ user_confirm_cost_threshold?: number | undefined;
313
+ /**
314
+ * Extra aspect ratio variants with independent storage keys.
315
+ * Each variant carries its own thumbnail, preview video, and optional highlight video.
316
+ * Used when a recipe supports multiple aspect ratios (e.g. both 16:9 and 9:16).
317
+ * @public
318
+ */
319
+ extra_ratios?: RatioVariant[] | undefined;
320
+ }
321
+ /**
322
+ * @public
323
+ */
324
+ interface TrackItem$1 {
325
+ abs_time_position: number | undefined;
326
+ part_id: string | undefined;
327
+ }
328
+ /**
329
+ * @public
330
+ */
331
+ interface Track$1 {
332
+ id: string | undefined;
333
+ /**
334
+ * Types of parts that can be contained in tracks
335
+ * @public
336
+ */
337
+ parts_kind: PartKind$1 | undefined;
338
+ is_hidden: boolean | undefined;
339
+ items: TrackItem$1[] | undefined;
340
+ }
341
+ /**
342
+ * @public
343
+ */
344
+ interface Attachment {
345
+ part_id: string | undefined;
346
+ relative_time_position: number | undefined;
347
+ }
348
+ /**
349
+ * @public
350
+ */
351
+ interface PartAggregation {
352
+ body_part_id: string | undefined;
353
+ attachments: Attachment[] | undefined;
354
+ }
355
+ /**
356
+ * @public
357
+ */
358
+ interface BgmPart$1 {
359
+ id: string | undefined;
360
+ duration_ms: number | undefined;
361
+ /**
362
+ * Types of parts that can be contained in tracks
363
+ * @public
364
+ */
365
+ kind: PartKind$1 | undefined;
366
+ audio_storage_key: string | undefined;
367
+ volume: number | undefined;
368
+ origin_media_id: string | undefined;
369
+ }
370
+ /**
371
+ * @public
372
+ * @enum
373
+ */
374
+ declare const SpeedShiftCategory: {
375
+ /**
376
+ * Variable speed controlled by Bezier keyframes
377
+ */
378
+ readonly CURVE: 'curve';
379
+ /**
380
+ * Constant speed multiplier across the entire clip
381
+ */
382
+ readonly LINEAR: 'linear';
383
+ };
384
+ /**
385
+ * @public
386
+ */
387
+ type SpeedShiftCategory = (typeof SpeedShiftCategory)[keyof typeof SpeedShiftCategory];
388
+ /**
389
+ * @public
390
+ */
391
+ interface TangentHandle {
392
+ x: number | undefined;
393
+ y: number | undefined;
394
+ }
395
+ /**
396
+ * @public
397
+ */
398
+ interface SpeedKeyframe {
399
+ position: number | undefined;
400
+ rate: number | undefined;
401
+ in_tangent?: TangentHandle | undefined;
402
+ out_tangent?: TangentHandle | undefined;
403
+ }
404
+ /**
405
+ * @public
406
+ */
407
+ interface CurveConfig {
408
+ keyframes: SpeedKeyframe[] | undefined;
409
+ }
410
+ /**
411
+ * @public
412
+ */
413
+ interface LinearConfig {
414
+ speed: number | undefined;
415
+ }
416
+ /**
417
+ * @public
418
+ */
419
+ type SpeedShiftConfigUnion = SpeedShiftConfigUnion.CurveMember | SpeedShiftConfigUnion.LinearMember | SpeedShiftConfigUnion.$UnknownMember;
420
+ /**
421
+ * @public
422
+ */
423
+ declare namespace SpeedShiftConfigUnion {
424
+ interface LinearMember {
425
+ linear: LinearConfig;
426
+ curve?: never;
427
+ $unknown?: never;
428
+ }
429
+ interface CurveMember {
430
+ linear?: never;
431
+ curve: CurveConfig;
432
+ $unknown?: never;
433
+ }
434
+ /**
435
+ * @public
436
+ */
437
+ interface $UnknownMember {
438
+ linear?: never;
439
+ curve?: never;
440
+ $unknown: [string, any];
441
+ }
442
+ interface Visitor<T> {
443
+ linear: (value: LinearConfig) => T;
444
+ curve: (value: CurveConfig) => T;
445
+ _: (name: string, value: any) => T;
446
+ }
447
+ const visit: <T>(value: SpeedShiftConfigUnion, visitor: Visitor<T>) => T;
448
+ }
449
+ /**
450
+ * @public
451
+ */
452
+ interface SpeedShift {
453
+ category: SpeedShiftCategory | undefined;
454
+ mode: string | undefined;
455
+ config: SpeedShiftConfigUnion | undefined;
456
+ }
457
+ /**
458
+ * @public
459
+ */
460
+ interface VideoClipPart$1 {
461
+ id: string | undefined;
462
+ duration_ms: number | undefined;
463
+ /**
464
+ * Types of parts that can be contained in tracks
465
+ * @public
466
+ */
467
+ kind: PartKind$1 | undefined;
468
+ play_in: number | undefined;
469
+ play_out: number | undefined;
470
+ volume: number | undefined;
471
+ origin_media_id: string | undefined;
472
+ speed_shift?: SpeedShift | undefined;
473
+ }
474
+ /**
475
+ * @public
476
+ */
477
+ type PartUnion$1 = PartUnion$1.BgmMember | PartUnion$1.CaptionMember | PartUnion$1.SpeechMember | PartUnion$1.Video_clipMember | PartUnion$1.$UnknownMember;
478
+ /**
479
+ * @public
480
+ */
481
+ declare namespace PartUnion$1 {
482
+ interface Video_clipMember {
483
+ video_clip: VideoClipPart$1;
484
+ bgm?: never;
485
+ speech?: never;
486
+ caption?: never;
487
+ $unknown?: never;
488
+ }
489
+ interface BgmMember {
490
+ video_clip?: never;
491
+ bgm: BgmPart$1;
492
+ speech?: never;
493
+ caption?: never;
494
+ $unknown?: never;
495
+ }
496
+ interface SpeechMember {
497
+ video_clip?: never;
498
+ bgm?: never;
499
+ speech: SpeechPart$1;
500
+ caption?: never;
501
+ $unknown?: never;
502
+ }
503
+ interface CaptionMember {
504
+ video_clip?: never;
505
+ bgm?: never;
506
+ speech?: never;
507
+ caption: CaptionPart$1;
508
+ $unknown?: never;
509
+ }
510
+ /**
511
+ * @public
512
+ */
513
+ interface $UnknownMember {
514
+ video_clip?: never;
515
+ bgm?: never;
516
+ speech?: never;
517
+ caption?: never;
518
+ $unknown: [string, any];
519
+ }
520
+ interface Visitor<T> {
521
+ video_clip: (value: VideoClipPart$1) => T;
522
+ bgm: (value: BgmPart$1) => T;
523
+ speech: (value: SpeechPart$1) => T;
524
+ caption: (value: CaptionPart$1) => T;
525
+ _: (name: string, value: any) => T;
526
+ }
527
+ const visit: <T>(value: PartUnion$1, visitor: Visitor<T>) => T;
528
+ }
529
+ /**
530
+ * Timeline configuration for video editing
531
+ * @public
532
+ */
533
+ interface Timeline$1 {
534
+ duration_ms: number | undefined;
535
+ unit_time_ms: number | undefined;
536
+ }
537
+ /**
538
+ * Complete video draft information with timeline and tracks
539
+ * @public
540
+ */
541
+ interface VideoDraft$1 {
542
+ id?: string | undefined;
543
+ project_id: string | undefined;
544
+ owner_id: string | undefined;
545
+ /**
546
+ * for-code-gen: property value may be empty string ("")
547
+ * @public
548
+ */
549
+ thumbnail_storage_key: string | undefined;
550
+ /**
551
+ * Timeline configuration for video editing
552
+ * @public
553
+ */
554
+ timeline?: Timeline$1 | undefined;
555
+ video_creation_settings?: VideoCreationSettings$1 | undefined;
556
+ chat_session_id: string | undefined;
557
+ main_track?: Track$1 | undefined;
558
+ above_main_tracks: Track$1[] | undefined;
559
+ below_main_tracks: Track$1[] | undefined;
560
+ part_aggregations: PartAggregation[] | undefined;
561
+ part_library: Record<string, PartUnion$1> | undefined;
562
+ version: number | undefined;
563
+ }
564
+ //#endregion
565
+ //#region src/document/types.d.ts
566
+ declare const VIDEO_DOCUMENT_SCHEMA_VERSION: 'video-document/v0';
567
+ type VideoDocumentSchemaVersion = typeof VIDEO_DOCUMENT_SCHEMA_VERSION;
568
+ type PartKind = 'video_clip' | 'speech' | 'caption' | 'bgm';
569
+ /**
570
+ * Project-level creation settings. Mirrors the IDL, but keeps the two fields the
571
+ * upstream (Director / FE) sends as an explicit `null` nullable — the IDL models
572
+ * "absent" only as `undefined`, whereas these arrive as `null` to mean
573
+ * "explicitly cleared". Relaxing the type here avoids forcing every caller to
574
+ * map `null` → `undefined`.
575
+ */
576
+ type VideoCreationSettings = Omit<VideoCreationSettings$1, 'video_style_id' | 'duration_ms'> & {
577
+ video_style_id?: string | null | undefined;
578
+ duration_ms?: number | null | undefined;
579
+ };
580
+ /**
581
+ * Video clip part. `duration_ms` is dropped from the authoritative type
582
+ * (reference/17 §5): it is the derived effective length and is re-derived into
583
+ * the read-view by the projection. `play_in` / `play_out` are required (smithy
584
+ * `@required`), so the timeline length is always the trim window over the speed
585
+ * multiplier — no need to store the media's intrinsic length either. `speed_shift`
586
+ * is the IDL type. See `effectiveVideoClipDurationMs`.
587
+ */
588
+ type VideoClipPart = Omit<VideoClipPart$1, 'duration_ms'>;
589
+ /**
590
+ * Speech part. `duration_ms` is dropped from the authoritative type
591
+ * (reference/17 §5) in favour of the engine-only `media_duration_ms`: a speech
592
+ * cannot be trimmed or speed-shifted, so its effective timeline duration equals
593
+ * the TTS audio's intrinsic length. The read-view `duration_ms` is re-derived
594
+ * from it by the projection.
595
+ */
596
+ type SpeechPart = Omit<SpeechPart$1, 'duration_ms'> & {
597
+ media_duration_ms?: number | undefined;
598
+ };
599
+ /**
600
+ * Authoritative caption part. The IDL `CaptionPart` carries `duration_ms` (the
601
+ * effective display length, a derived read-view value); the authoritative store
602
+ * instead keeps `initial_duration_ms` — the caption's length at generation time,
603
+ * parsed from the voiceover, a resource-intrinsic fact that does not change
604
+ * (reference/17 §5; onscreen-caption plan). The effective `duration_ms` is
605
+ * re-derived into the `VideoDraft` read-view by the projection.
606
+ *
607
+ * `speech_part_id` / `start_ms` are kept as resource-intrinsic facts (which
608
+ * speech it was split from, and its offset in that source) — reference/17
609
+ * principle 4.
610
+ */
611
+ type CaptionPart = Omit<CaptionPart$1, 'duration_ms'> & {
612
+ initial_duration_ms?: number | undefined;
613
+ };
614
+ /**
615
+ * Background music. The IDL marks `duration_ms` required (via its part mixin),
616
+ * but a bgm has no authoritative duration — its effective length is always the
617
+ * whole timeline, derived on read (RFC 02 §0b). The engine therefore keeps it
618
+ * optional rather than carrying the IDL's required field into storage.
619
+ */
620
+ type BgmPart = Omit<BgmPart$1, 'duration_ms'> & {
621
+ duration_ms?: number | undefined;
622
+ };
623
+ /**
624
+ * How a `TrackItem`'s start is determined — the authoritative positioning fact
625
+ * (RFC 02 §4, reference/17 §3). The absolute time is never stored; it is solved
626
+ * by the cascade at projection time. The three modes express "where the start
627
+ * comes from": `sequential` follows the track's flow order, `anchored` hangs off
628
+ * another part plus an offset, `absolute` pins to the timeline origin.
629
+ * `anchorPartId` points at another item's `part_id` (a part is placed at most
630
+ * once per lane, so `part_id` is the placement identity).
631
+ *
632
+ * `time_position` carries only the real editing intent (mode + anchor + offset),
633
+ * so it is a single last-writer-wins unit safe to overwrite wholesale. The
634
+ * derived fallback snapshot lives outside it on `TrackItem.fallback_abs_ms`.
635
+ */
636
+ type TrackItemTimePosition = {
637
+ mode: 'sequential';
638
+ } | {
639
+ mode: 'anchored';
640
+ anchorPartId: string;
641
+ offsetMs: number;
642
+ } | {
643
+ mode: 'absolute';
644
+ offsetMs: number;
645
+ };
646
+ /**
647
+ * Authoritative track item: only facts. Absolute time is derived, not stored.
648
+ *
649
+ * `fallback_abs_ms` is the one deliberately-retained derived snapshot in
650
+ * authoritative state: the `relative` item's absolute landing, so that when its
651
+ * anchor is concurrently deleted (orphan, RFC 02 §11.1), the item can be put
652
+ * back on the timeline and re-parented to the nearest video — orphan handling
653
+ * needs an absolute anchor and cannot guess one.
654
+ *
655
+ * It is written by the write-side ops that create or move a `relative` item:
656
+ * each resolves the item's absolute landing from its anchor once (add/change
657
+ * speech from the host clip's start + offset; `moveSpeeches` from the requested
658
+ * start; §9.1 reparent from the preserved abs) and stamps it here. The read-side
659
+ * projection never writes it back — the authoritative model stays write-derived,
660
+ * not read-derived. It is a field of its own (not inside `time_position`) so the
661
+ * two are separate LWW units: a fallback refresh can never clobber a concurrent
662
+ * `offsetMs` edit, since they carry unequal business value. When the anchor's
663
+ * absolute position cannot be resolved at write time it is left undefined. Only
664
+ * meaningful for `anchored` items.
665
+ */
666
+ interface TrackItem {
667
+ part_id: string;
668
+ time_position: TrackItemTimePosition;
669
+ fallback_abs_ms?: number | undefined;
670
+ }
671
+ interface Track {
672
+ id: string | undefined;
673
+ parts_kind: PartKind | undefined;
674
+ is_hidden: boolean | undefined;
675
+ items: TrackItem[] | undefined;
676
+ }
677
+ /**
678
+ * Authoritative part union: a flat discriminated union over the four part kinds.
679
+ * Deliberately *not* the IDL's namespace union (no `$unknown` / `visit`): engine
680
+ * storage rejects unknown kinds, and projection / validation read parts by plain
681
+ * `part.video_clip` / `part.speech` property access, which this shape supports.
682
+ */
683
+ type PartUnion = {
684
+ video_clip: VideoClipPart;
685
+ speech?: never;
686
+ caption?: never;
687
+ bgm?: never;
688
+ } | {
689
+ video_clip?: never;
690
+ speech: SpeechPart;
691
+ caption?: never;
692
+ bgm?: never;
693
+ } | {
694
+ video_clip?: never;
695
+ speech?: never;
696
+ caption: CaptionPart;
697
+ bgm?: never;
698
+ } | {
699
+ video_clip?: never;
700
+ speech?: never;
701
+ caption?: never;
702
+ bgm: BgmPart;
703
+ };
704
+ /**
705
+ * Read-view part union for the derived `VideoDraft` (reference/17 §5/§7). The
706
+ * authoritative parts drop their derived durations; the projection re-injects an
707
+ * effective `duration_ms` for downstream compatibility — for a video clip it is
708
+ * `(play_out - play_in) / speed`, for a speech it is `media_duration_ms`, for a
709
+ * caption it is `initial_duration_ms`, for a bgm it is the timeline total. Each
710
+ * read-view part is therefore the authoritative part plus a derived `duration_ms`.
711
+ */
712
+ type VideoDraftPartUnion = {
713
+ video_clip: VideoClipPart & {
714
+ duration_ms?: number;
715
+ };
716
+ speech?: never;
717
+ caption?: never;
718
+ bgm?: never;
719
+ } | {
720
+ video_clip?: never;
721
+ speech: SpeechPart & {
722
+ duration_ms?: number;
723
+ };
724
+ caption?: never;
725
+ bgm?: never;
726
+ } | {
727
+ video_clip?: never;
728
+ speech?: never;
729
+ caption: CaptionPart & {
730
+ duration_ms?: number;
731
+ };
732
+ bgm?: never;
733
+ } | {
734
+ video_clip?: never;
735
+ speech?: never;
736
+ caption?: never;
737
+ bgm: BgmPart & {
738
+ duration_ms?: number;
739
+ };
740
+ };
741
+ /**
742
+ * Derived `VideoDraft` read-view. The shape follows the IDL verbatim except for
743
+ * `part_library`: the IDL types its parts with the generated namespace
744
+ * `PartUnion`, but the engine's projection emits `VideoDraftPartUnion` — the
745
+ * authoritative parts with the derived `duration_ms` re-injected (reference/17 §7)
746
+ * plus the engine extensions (`media_duration_ms`, the looser `SpeedShift`). This
747
+ * is the intentional superset over the IDL `VideoDraft` (see the extensions note).
748
+ */
749
+ type VideoDraft = Omit<VideoDraft$1, 'part_library' | 'video_creation_settings'> & {
750
+ part_library: Record<string, VideoDraftPartUnion> | undefined;
751
+ video_creation_settings?: VideoCreationSettings | undefined;
752
+ };
753
+ /** Authoritative timeline: `duration_ms` is derived (= longest track), not stored. */
754
+ interface Timeline {
755
+ unit_time_ms: number | undefined;
756
+ }
757
+ /**
758
+ * Project-level scalars that do not participate in track ordering. Grouped under
759
+ * `meta` because the loro-mirror `schema()` root only accepts container schemas,
760
+ * not bare scalars — so these must live inside a map container in storage. The
761
+ * domain type mirrors that storage shape 1:1 (RFC 03 §4) rather than flattening,
762
+ * keeping `VideoDocument` and the loro draft isomorphic and the mapping layer a
763
+ * near-identity. `schema_version` lives here too (it is a scalar fact).
764
+ */
765
+ interface VideoDocumentMeta {
766
+ schema_version: VideoDocumentSchemaVersion;
767
+ draft_id?: string | undefined;
768
+ project_id: VideoDraft['project_id'];
769
+ owner_id: VideoDraft['owner_id'];
770
+ thumbnail_storage_key: VideoDraft['thumbnail_storage_key'];
771
+ chat_session_id: VideoDraft['chat_session_id'];
772
+ video_creation_settings: VideoDraft['video_creation_settings'];
773
+ version: VideoDraft['version'];
774
+ }
775
+ /**
776
+ * Authoritative collaborative document: only facts. Positioning lives on each
777
+ * `TrackItem.time_position`; absolute time, `part_aggregations`, and total
778
+ * duration are derived in the `VideoDraft` projection, not stored here (RFC 02 §6).
779
+ *
780
+ * The shape mirrors the loro storage structure 1:1 (RFC 03 §4): a `meta` map of
781
+ * project scalars, plus a single ordered `tracks` list (reference/17 §4). A
782
+ * track's lane is expressed by its `parts_kind`, not by which container it lives
783
+ * in — the `main` / `above` / `below` three-pane view is reconstructed at
784
+ * projection time. Order is the list order itself, so there is no `lane` /
785
+ * `lane_order` field to diverge under concurrent edits.
786
+ */
787
+ interface VideoDocument {
788
+ meta: VideoDocumentMeta;
789
+ timeline: Timeline | undefined;
790
+ tracks: Track[] | undefined;
791
+ part_library: Record<string, PartUnion> | undefined;
792
+ }
793
+ type VideoDocumentValidationIssueCode = 'invalid_schema' | 'duplicate_track_item_identity' | 'unknown_part_kind' | 'part_kind_mismatch' | 'missing_part_reference' | 'track_kind_mismatch' | 'main_track_non_video_clip' | 'invalid_part_value' | 'invalid_speech_caption_reference' | 'invalid_position_anchor';
794
+ /**
795
+ * Issue severity (RFC 02 §11.1). `error` is a hard reject — the document is not
796
+ * a legal `VideoDocument` and must not be projected. `recoverable` marks a
797
+ * business inconsistency the projection is designed to heal on read (currently
798
+ * only a dangling `anchorPartId`: the orphan condition, recovered via the item's
799
+ * `fallback_abs_ms` snapshot + cascade reassign), so it is surfaced for
800
+ * observability but does NOT block projection. Absent `severity` means `error`.
801
+ */
802
+ type VideoDocumentValidationIssueSeverity = 'error' | 'recoverable';
803
+ interface VideoDocumentValidationIssue {
804
+ code: VideoDocumentValidationIssueCode;
805
+ path: string;
806
+ message: string;
807
+ severity?: VideoDocumentValidationIssueSeverity;
808
+ }
809
+ //#endregion
810
+ //#region src/document/projection.d.ts
811
+ /**
812
+ * Projection between the authoritative `VideoDocument` and the legacy
813
+ * `VideoDraft` read-view (RFC 02 §5/§7). Both directions live here:
814
+ *
815
+ * - `toVideoDocument` ingests a `VideoDraft`, deriving each item's `position`
816
+ * from the legacy absolute layout + aggregations; derived values (abs time,
817
+ * `part_aggregations`, total duration) are dropped.
818
+ * - `fromVideoDocument` solves a `VideoDocument` back into a `VideoDraft` via the
819
+ * timeline-core cascade, re-deriving exactly those values.
820
+ *
821
+ * Business validation lives in `validation.ts`; `fromVideoDocument` asserts a
822
+ * valid document before solving.
823
+ */
824
+ /**
825
+ * Ingest the legacy `VideoDraft` into the authoritative `VideoDocument`,
826
+ * deriving each item's `time_position` from the legacy absolute layout +
827
+ * aggregations (RFC 02 §4/§5). Absolute time, `part_aggregations`, and total
828
+ * duration are dropped — they are re-derived by the projection.
829
+ */
830
+ declare function toVideoDocument(draft: VideoDraft): VideoDocument;
831
+ /** speech/attachment part_id → its host video part_id + relative offset. */
832
+ type SpeechHostMap = Map<string, {
833
+ hostPartId: string;
834
+ offsetMs: number;
835
+ }>;
836
+ /**
837
+ * Build a speech-host map from a part_aggregations list. Used by
838
+ * `toVideoDocument` (legacy VideoDraft → VideoDocument) to recover each speech's
839
+ * host video clip and relative offset. Aggregation items with null ids are
840
+ * skipped (malformed input tolerance).
841
+ */
842
+ declare function buildSpeechHostMap(aggregations: Array<{
843
+ body_part_id?: string | null;
844
+ attachments?: Array<{
845
+ part_id?: string | null;
846
+ relative_time_position?: number | null;
847
+ }> | null;
848
+ }> | null | undefined): SpeechHostMap;
849
+ /** The derived time position + orphan-recovery snapshot for one item. */
850
+ interface DerivedItemPosition {
851
+ timePosition: TrackItemTimePosition;
852
+ /** Present only for `anchored` items (RFC 02 §11.1). */
853
+ fallbackAbsMs?: number;
854
+ }
855
+ /**
856
+ * Derive `time_position` (and `fallbackAbsMs` for anchored items) from a legacy
857
+ * absolute time + pre-built speech-host map + part library. The three-branch
858
+ * rule (RFC 02 §4/§5, reference/17 §3):
859
+ *
860
+ * 1. main-track → `sequential`
861
+ * 2. speech/attachment (part_id in speechHost) → `anchored(host, offsetMs)`
862
+ * 3. caption (part has `speech_part_id`) → `anchored(speech, start_ms)`
863
+ * 4. everything else → `absolute(abs)`
864
+ *
865
+ * `partLibrary` values may be `PartUnion | string | undefined` (draft raw
866
+ * form); only object-typed entries are inspected for `caption`.
867
+ */
868
+ declare function derivePositionFromAbs(partId: string, abs: number, isMain: boolean, speechHost: SpeechHostMap, partLibrary: Record<string, PartUnion | string | undefined>): DerivedItemPosition;
869
+ /**
870
+ * Project the authoritative `VideoDocument` back into the legacy `VideoDraft`
871
+ * read-view, solving each item's absolute position, the `part_aggregations`, and
872
+ * the total duration via the timeline-core cascade.
873
+ */
874
+ declare function fromVideoDocument(document: VideoDocument): VideoDraft;
875
+ //#endregion
876
+ //#region src/document/validation.d.ts
877
+ /**
878
+ * Business-level schema guard for `VideoDocument` (RFC 03 §9). It is the gate
879
+ * that decides whether an arbitrary value is a *legal* `VideoDocument` before it
880
+ * is written into Loro — distinct from two neighbours:
881
+ *
882
+ * - `zod-schema.ts` (`videoDocumentSchema`) checks structure/shape only; this
883
+ * file layers the business rules on top (part-kind match, reference integrity,
884
+ * `position.anchorPartId` targets, value ranges, identity uniqueness).
885
+ * - loro-mirror's own `validateSchema` (run on every `setState`) only checks the
886
+ * storage structure, never these business invariants.
887
+ *
888
+ * Projection (`projection.ts`) calls `assertValidVideoDocument` before solving a
889
+ * document into the legacy `VideoDraft`.
890
+ */
891
+ declare class VideoDocumentValidationError extends Error {
892
+ readonly issues: VideoDocumentValidationIssue[];
893
+ constructor(issues: VideoDocumentValidationIssue[]);
894
+ }
895
+ /**
896
+ * Assert the document is legal enough to project. Only `error`-severity issues
897
+ * hard-reject; `recoverable` ones (dangling anchor / orphan, RFC 02 §11.1) are
898
+ * left for the projection to heal on read and do NOT throw. Use
899
+ * `validateVideoDocument` directly to inspect recoverable issues too.
900
+ */
901
+ declare function assertValidVideoDocument(document: unknown): asserts document is VideoDocument;
902
+ declare function validateVideoDocument(document: unknown): VideoDocumentValidationIssue[];
903
+ //#endregion
904
+ //#region src/document/mirror-schema.d.ts
905
+ declare const videoDocumentMirrorSchema: import("loro-mirror").RootSchemaType<{
906
+ meta: import("loro-mirror").LoroMapSchema<{
907
+ schema_version: /*elided*/any;
908
+ draft_id: /*elided*/any;
909
+ project_id: /*elided*/any;
910
+ owner_id: /*elided*/any;
911
+ thumbnail_storage_key: /*elided*/any;
912
+ chat_session_id: /*elided*/any;
913
+ video_creation_settings: /*elided*/any;
914
+ version: /*elided*/any;
915
+ }> & {
916
+ options: {};
917
+ } & {
918
+ catchall: <C extends import("loro-mirror").SchemaType>(catchallSchema: C) => import("loro-mirror").LoroMapSchemaWithCatchall<{
919
+ schema_version: /*elided*/any;
920
+ draft_id: /*elided*/any;
921
+ project_id: /*elided*/any;
922
+ owner_id: /*elided*/any;
923
+ thumbnail_storage_key: /*elided*/any;
924
+ chat_session_id: /*elided*/any;
925
+ video_creation_settings: /*elided*/any;
926
+ version: /*elided*/any;
927
+ }, C>;
928
+ };
929
+ timeline: import("loro-mirror").LoroMapSchema<{
930
+ unit_time_ms: /*elided*/any;
931
+ }> & {
932
+ options: {};
933
+ } & {
934
+ catchall: <C extends import("loro-mirror").SchemaType>(catchallSchema: C) => import("loro-mirror").LoroMapSchemaWithCatchall<{
935
+ unit_time_ms: /*elided*/any;
936
+ }, C>;
937
+ };
938
+ tracks: import("loro-mirror").LoroMovableListSchema<import("loro-mirror").LoroMapSchema<{
939
+ id: /*elided*/any;
940
+ parts_kind: /*elided*/any;
941
+ is_hidden: /*elided*/any;
942
+ items: /*elided*/any;
943
+ }> & {
944
+ options: {};
945
+ } & {
946
+ catchall: <C extends import("loro-mirror").SchemaType>(catchallSchema: C) => import("loro-mirror").LoroMapSchemaWithCatchall<{
947
+ id: /*elided*/any;
948
+ parts_kind: /*elided*/any;
949
+ is_hidden: /*elided*/any;
950
+ items: /*elided*/any;
951
+ }, C>;
952
+ }> & {
953
+ options: {};
954
+ };
955
+ part_library: import("loro-mirror").LoroMapSchemaWithCatchall<{}, import("loro-mirror").LoroMapSchema<{
956
+ video_clip: /*elided*/any;
957
+ speech: /*elided*/any;
958
+ caption: /*elided*/any;
959
+ bgm: /*elided*/any;
960
+ }> & {
961
+ options: {};
962
+ } & {
963
+ catchall: <C extends import("loro-mirror").SchemaType>(catchallSchema: C) => import("loro-mirror").LoroMapSchemaWithCatchall<{
964
+ video_clip: /*elided*/any;
965
+ speech: /*elided*/any;
966
+ caption: /*elided*/any;
967
+ bgm: /*elided*/any;
968
+ }, C>;
969
+ }> & {
970
+ options: {};
971
+ };
972
+ }> & {
973
+ options: {};
974
+ };
975
+ type VideoDocumentMirrorSchema = typeof videoDocumentMirrorSchema;
976
+ /**
977
+ * The mutable draft an op's `transact` callback edits, inferred from the schema.
978
+ *
979
+ * `InferInputType` is the schema's *input* shape: the declared business fields
980
+ * with the `$cid` container ids (which mirror injects into its output
981
+ * `InferType`) made optional, so ops assign plain objects without supplying
982
+ * `$cid`. Deriving it from the schema keeps draft and storage in lockstep — a
983
+ * schema change is a compile error at every edit site, not a silent drift.
984
+ * Reorders within `items` still diff to real Loro `move` ops because the schema
985
+ * keys items by `part_id`.
986
+ */
987
+ type VideoDocumentDraft = InferInputType<VideoDocumentMirrorSchema>;
988
+ /** Track value as it appears in a draft. Derived from the single `tracks` movable list. */
989
+ type TrackDraft = NonNullable<NonNullable<VideoDocumentDraft['tracks']>[number]>;
990
+ /** A single track item in a draft. */
991
+ type TrackItemDraft = NonNullable<NonNullable<TrackDraft['items']>[number]>;
992
+ //#endregion
993
+ //#region src/editor/schemas/add-speeches.d.ts
994
+ /**
995
+ * Add speeches (and their captions). TTS runs upstream; the stable speech /
996
+ * caption parts arrive materialized (see `speech-assets.ts`). The op writes the
997
+ * parts and each speech's `{ mode:'anchored', anchorPartId, offsetMs }` fact
998
+ * verbatim — no write-time host-picking, no cascade (RFC 02 §4). The projection
999
+ * derives absolute positions on read.
1000
+ */
1001
+ declare const addSpeechesInputSchema: z.ZodObject<{
1002
+ speeches: z.ZodArray<z.ZodObject<{
1003
+ speech_id: z.ZodString;
1004
+ anchor_part_id: z.ZodString;
1005
+ offset_ms: z.ZodNumber;
1006
+ audio_storage_key: z.ZodString;
1007
+ duration_ms: z.ZodNumber;
1008
+ audio_script: z.ZodString;
1009
+ volume: z.ZodNumber;
1010
+ voice: z.ZodObject<{
1011
+ id: z.ZodString;
1012
+ name: z.ZodString;
1013
+ }, z.core.$strip>;
1014
+ origin_speech_id: z.ZodString;
1015
+ caption_ids: z.ZodArray<z.ZodString>;
1016
+ }, z.core.$strip>>;
1017
+ captions: z.ZodArray<z.ZodObject<{
1018
+ caption_id: z.ZodString;
1019
+ speech_part_id: z.ZodString;
1020
+ text: z.ZodString;
1021
+ start_ms: z.ZodNumber;
1022
+ duration_ms: z.ZodNumber;
1023
+ }, z.core.$strip>>;
1024
+ }, z.core.$strip>;
1025
+ type AddSpeechesInput = z.infer<typeof addSpeechesInputSchema>;
1026
+ //#endregion
1027
+ //#region src/editor/schemas/add-video-clips.d.ts
1028
+ /**
1029
+ * Add video clips to a track. Each clip's duration facts are separated so a
1030
+ * single number is never overloaded (RFC 02 / `reference/16` §0b):
1031
+ *
1032
+ * - `media_duration_ms` is the source media's intrinsic full length (a resource
1033
+ * fact, written to the part);
1034
+ * - `play_in` / `play_out` are the optional trim window into that media; when
1035
+ * omitted the whole media is used (`play_in=0`, `play_out=media_duration_ms`).
1036
+ *
1037
+ * The clip's effective timeline duration is derived by the projection from the
1038
+ * trim window and `speed_shift` — it is never an input here.
1039
+ */
1040
+ declare const addVideoClipsInputSchema: z.ZodObject<{
1041
+ clips: z.ZodArray<z.ZodObject<{
1042
+ media_id: z.ZodString;
1043
+ start_ms: z.ZodOptional<z.ZodNumber>;
1044
+ media_duration_ms: z.ZodNumber;
1045
+ play_in: z.ZodOptional<z.ZodNumber>;
1046
+ play_out: z.ZodOptional<z.ZodNumber>;
1047
+ track_id: z.ZodOptional<z.ZodString>;
1048
+ }, z.core.$strip>>;
1049
+ before_clip_id: z.ZodOptional<z.ZodString>;
1050
+ after_clip_id: z.ZodOptional<z.ZodString>;
1051
+ }, z.core.$strip>;
1052
+ type AddVideoClipsInput = z.infer<typeof addVideoClipsInputSchema>;
1053
+ //#endregion
1054
+ //#region src/editor/schemas/adjust-bgm-volume.d.ts
1055
+ declare const adjustBgmVolumeInputSchema: z.ZodObject<{
1056
+ bgm: z.ZodArray<z.ZodObject<{
1057
+ bgm_id: z.ZodString;
1058
+ volume: z.ZodNumber;
1059
+ }, z.core.$strip>>;
1060
+ }, z.core.$strip>;
1061
+ type AdjustBgmVolumeInput = z.infer<typeof adjustBgmVolumeInputSchema>;
1062
+ //#endregion
1063
+ //#region src/editor/schemas/adjust-speech-volume.d.ts
1064
+ declare const adjustSpeechVolumeInputSchema: z.ZodObject<{
1065
+ speeches: z.ZodArray<z.ZodObject<{
1066
+ speech_id: z.ZodString;
1067
+ volume: z.ZodNumber;
1068
+ }, z.core.$strip>>;
1069
+ }, z.core.$strip>;
1070
+ type AdjustSpeechVolumeInput = z.infer<typeof adjustSpeechVolumeInputSchema>;
1071
+ //#endregion
1072
+ //#region src/editor/schemas/adjust-video-clip-duration.d.ts
1073
+ /**
1074
+ * Re-trim existing video clips (the user-facing "adjust duration" gesture is a
1075
+ * trim of the source window). The new `play_in` / `play_out` are the facts; the
1076
+ * effective timeline duration is derived from them and the clip's `speed_shift`,
1077
+ * and the change reflows downstream clips, speeches, and the timeline inside the
1078
+ * op's transaction (no caller-materialized cascade).
1079
+ */
1080
+ declare const adjustVideoClipDurationInputSchema: z.ZodObject<{
1081
+ clips: z.ZodArray<z.ZodObject<{
1082
+ clip_id: z.ZodString;
1083
+ play_in: z.ZodNumber;
1084
+ play_out: z.ZodNumber;
1085
+ }, z.core.$strip>>;
1086
+ }, z.core.$strip>;
1087
+ type AdjustVideoClipDurationInput = z.infer<typeof adjustVideoClipDurationInputSchema>;
1088
+ //#endregion
1089
+ //#region src/editor/schemas/adjust-video-clip-volume.d.ts
1090
+ declare const adjustVideoClipVolumeInputSchema: z.ZodObject<{
1091
+ clips: z.ZodArray<z.ZodObject<{
1092
+ clip_id: z.ZodString;
1093
+ volume: z.ZodNumber;
1094
+ }, z.core.$strip>>;
1095
+ }, z.core.$strip>;
1096
+ type AdjustVideoClipVolumeInput = z.infer<typeof adjustVideoClipVolumeInputSchema>;
1097
+ //#endregion
1098
+ //#region src/editor/schemas/change-speech.d.ts
1099
+ /**
1100
+ * Change a speech's script or voice. Both re-run TTS upstream and return the
1101
+ * regenerated speech / caption parts in the same materialized shape as
1102
+ * `AddSpeeches` (`speech-assets.ts`); the op upserts them by id (the speech part
1103
+ * id is preserved across a re-TTS), re-seats at `start_ms`, and reflows. Old
1104
+ * caption parts no longer owned by the speech are removed via `caption_ids`.
1105
+ */
1106
+ declare const changeSpeechScriptInputSchema: z.ZodObject<{
1107
+ speeches: z.ZodArray<z.ZodObject<{
1108
+ speech_id: z.ZodString;
1109
+ anchor_part_id: z.ZodString;
1110
+ offset_ms: z.ZodNumber;
1111
+ audio_storage_key: z.ZodString;
1112
+ duration_ms: z.ZodNumber;
1113
+ audio_script: z.ZodString;
1114
+ volume: z.ZodNumber;
1115
+ voice: z.ZodObject<{
1116
+ id: z.ZodString;
1117
+ name: z.ZodString;
1118
+ }, z.core.$strip>;
1119
+ origin_speech_id: z.ZodString;
1120
+ caption_ids: z.ZodArray<z.ZodString>;
1121
+ }, z.core.$strip>>;
1122
+ captions: z.ZodArray<z.ZodObject<{
1123
+ caption_id: z.ZodString;
1124
+ speech_part_id: z.ZodString;
1125
+ text: z.ZodString;
1126
+ start_ms: z.ZodNumber;
1127
+ duration_ms: z.ZodNumber;
1128
+ }, z.core.$strip>>;
1129
+ }, z.core.$strip>;
1130
+ declare const changeSpeechVoiceInputSchema: z.ZodObject<{
1131
+ speeches: z.ZodArray<z.ZodObject<{
1132
+ speech_id: z.ZodString;
1133
+ anchor_part_id: z.ZodString;
1134
+ offset_ms: z.ZodNumber;
1135
+ audio_storage_key: z.ZodString;
1136
+ duration_ms: z.ZodNumber;
1137
+ audio_script: z.ZodString;
1138
+ volume: z.ZodNumber;
1139
+ voice: z.ZodObject<{
1140
+ id: z.ZodString;
1141
+ name: z.ZodString;
1142
+ }, z.core.$strip>;
1143
+ origin_speech_id: z.ZodString;
1144
+ caption_ids: z.ZodArray<z.ZodString>;
1145
+ }, z.core.$strip>>;
1146
+ captions: z.ZodArray<z.ZodObject<{
1147
+ caption_id: z.ZodString;
1148
+ speech_part_id: z.ZodString;
1149
+ text: z.ZodString;
1150
+ start_ms: z.ZodNumber;
1151
+ duration_ms: z.ZodNumber;
1152
+ }, z.core.$strip>>;
1153
+ }, z.core.$strip>;
1154
+ type ChangeSpeechScriptInput = z.infer<typeof changeSpeechScriptInputSchema>;
1155
+ type ChangeSpeechVoiceInput = z.infer<typeof changeSpeechVoiceInputSchema>;
1156
+ //#endregion
1157
+ //#region src/editor/schemas/delete-bgm.d.ts
1158
+ /**
1159
+ * Remove the document BGM. Pure document edit: clears the bgm lane and removes
1160
+ * the bgm part. Takes no input (a document holds at most one bgm); an empty
1161
+ * object keeps the op signature uniform with the rest.
1162
+ */
1163
+ declare const deleteBgmInputSchema: z.ZodObject<{}, z.core.$strip>;
1164
+ type DeleteBgmInput = z.infer<typeof deleteBgmInputSchema>;
1165
+ //#endregion
1166
+ //#region src/editor/schemas/delete-speeches.d.ts
1167
+ /**
1168
+ * Delete speeches with their captions. Pure document edit (no side effect): the
1169
+ * op removes each speech part, cascade-deletes the captions it owns (via
1170
+ * `caption_ids` / `speech_part_id`), drops their track items, and reflows.
1171
+ */
1172
+ declare const deleteSpeechesInputSchema: z.ZodObject<{
1173
+ speech_ids: z.ZodArray<z.ZodString>;
1174
+ }, z.core.$strip>;
1175
+ type DeleteSpeechesInput = z.infer<typeof deleteSpeechesInputSchema>;
1176
+ //#endregion
1177
+ //#region src/editor/schemas/delete-video-clips.d.ts
1178
+ /**
1179
+ * How a delete handles the anchored subtree (speeches anchored to a deleted clip,
1180
+ * and their captions) — a delete-op policy, not a data-model field (reference/17
1181
+ * §6). `cascade` (default) removes the subtree; `detach` keeps the direct
1182
+ * anchored children, re-pinning them to `absolute` so they stay on the timeline.
1183
+ */
1184
+ declare const anchoredDeletePolicySchema: z.ZodEnum<{
1185
+ cascade: "cascade";
1186
+ detach: "detach";
1187
+ }>;
1188
+ type AnchoredDeletePolicy = z.infer<typeof anchoredDeletePolicySchema>;
1189
+ declare const deleteVideoClipsInputSchema: z.ZodObject<{
1190
+ clip_ids: z.ZodArray<z.ZodString>;
1191
+ on_anchored: z.ZodOptional<z.ZodEnum<{
1192
+ cascade: "cascade";
1193
+ detach: "detach";
1194
+ }>>;
1195
+ }, z.core.$strip>;
1196
+ type DeleteVideoClipsInput = z.infer<typeof deleteVideoClipsInputSchema>;
1197
+ //#endregion
1198
+ //#region src/editor/schemas/move-speeches.d.ts
1199
+ /**
1200
+ * Move speeches in time. Pure document edit: the op re-seats each speech at its
1201
+ * new absolute `start_ms`; the cascade reassigns it to the host video clip,
1202
+ * resolves overlaps, and reflows. Captions follow their speech.
1203
+ */
1204
+ declare const moveSpeechesInputSchema: z.ZodObject<{
1205
+ speeches: z.ZodArray<z.ZodObject<{
1206
+ speech_id: z.ZodString;
1207
+ new_start_ms: z.ZodNumber;
1208
+ }, z.core.$strip>>;
1209
+ }, z.core.$strip>;
1210
+ type MoveSpeechesInput = z.infer<typeof moveSpeechesInputSchema>;
1211
+ //#endregion
1212
+ //#region src/editor/schemas/move-video-clips.d.ts
1213
+ declare const moveVideoClipsInputSchema: z.ZodObject<{
1214
+ clips: z.ZodArray<z.ZodObject<{
1215
+ clip_id: z.ZodString;
1216
+ new_start_ms: z.ZodNumber;
1217
+ new_track_id: z.ZodOptional<z.ZodString>;
1218
+ }, z.core.$strip>>;
1219
+ }, z.core.$strip>;
1220
+ type MoveVideoClipsInput = z.infer<typeof moveVideoClipsInputSchema>;
1221
+ //#endregion
1222
+ //#region src/editor/schemas/replace-video-clip-content.d.ts
1223
+ /**
1224
+ * Replace the media backing existing video clips. The media import runs upstream
1225
+ * (Director); its stable result — the new media id, intrinsic length, and the
1226
+ * reset trim window — arrives materialized (see
1227
+ * `results/phase-4-side-effect-payload-contract.md` §4). Director resets
1228
+ * `play_in=0` / `play_out=media_duration_ms` and clears `speed_shift` on
1229
+ * replacement. The clip `part_id`s (hence their track items) are unchanged; the
1230
+ * editor reflows the main track from the new effective durations.
1231
+ */
1232
+ declare const replaceVideoClipContentInputSchema: z.ZodObject<{
1233
+ clips: z.ZodArray<z.ZodObject<{
1234
+ clip_id: z.ZodString;
1235
+ origin_media_id: z.ZodString;
1236
+ media_duration_ms: z.ZodNumber;
1237
+ play_in: z.ZodNumber;
1238
+ play_out: z.ZodNumber;
1239
+ volume: z.ZodNumber;
1240
+ }, z.core.$strip>>;
1241
+ }, z.core.$strip>;
1242
+ type ReplaceVideoClipContentInput = z.infer<typeof replaceVideoClipContentInputSchema>;
1243
+ //#endregion
1244
+ //#region src/editor/schemas/set-bgm.d.ts
1245
+ /**
1246
+ * Set the document BGM. The media's stable result (storage key) arrives
1247
+ * materialized from upstream (see
1248
+ * `results/phase-4-side-effect-payload-contract.md` §3). The op upserts the bgm
1249
+ * part and seats it on the bgm lane; its effective length is always the whole
1250
+ * timeline, derived by the projection on read — so there is no `duration_ms`
1251
+ * input or fact (RFC 02 / `reference/16` §0b). A `bgm_id` lets the op replace an
1252
+ * existing bgm part by id.
1253
+ */
1254
+ declare const setBgmInputSchema: z.ZodObject<{
1255
+ bgm_id: z.ZodString;
1256
+ audio_storage_key: z.ZodString;
1257
+ origin_media_id: z.ZodString;
1258
+ volume: z.ZodNumber;
1259
+ }, z.core.$strip>;
1260
+ type SetBgmInput = z.infer<typeof setBgmInputSchema>;
1261
+ //#endregion
1262
+ //#region src/editor/schemas/set-caption-style.d.ts
1263
+ /**
1264
+ * Set the caption visual style. GLOBAL by design: the style applies to every
1265
+ * caption part in the document — it carries NO `caption_id`. This mirrors the FE,
1266
+ * whose caption-style store (`caption-style.ts:persistCaptionStylePatch`) iterates
1267
+ * ALL captions and writes the same normalized style to each; the product has a
1268
+ * single document-wide caption style, not per-caption styling.
1269
+ *
1270
+ * Every field is optional and maps to a `CaptionStyle` attribute (snake_case
1271
+ * IDL). A field present in the input is written to every caption; a field ABSENT
1272
+ * from the input is left untouched on each caption (the editor merges the patch
1273
+ * onto each caption's existing style — this is a value edit, not a full-style
1274
+ * replace, so a partial patch such as "recolor only" does not wipe font size).
1275
+ *
1276
+ * Pure document edit, no cascade — captions keep their positions; only the style
1277
+ * sub-map of each caption part changes.
1278
+ */
1279
+ declare const setCaptionStyleInputSchema: z.ZodObject<{
1280
+ font_id: z.ZodOptional<z.ZodString>;
1281
+ font_size: z.ZodOptional<z.ZodNumber>;
1282
+ font_color: z.ZodOptional<z.ZodString>;
1283
+ font_weight: z.ZodOptional<z.ZodNumber>;
1284
+ entrance_animation: z.ZodOptional<z.ZodString>;
1285
+ entrance_animation_duration_ms: z.ZodOptional<z.ZodNumber>;
1286
+ stroke_color: z.ZodOptional<z.ZodString>;
1287
+ stroke_width: z.ZodOptional<z.ZodNumber>;
1288
+ position_x: z.ZodOptional<z.ZodNumber>;
1289
+ position_y: z.ZodOptional<z.ZodNumber>;
1290
+ }, z.core.$strip>;
1291
+ type SetCaptionStyleInput = z.infer<typeof setCaptionStyleInputSchema>;
1292
+ //#endregion
1293
+ //#region src/editor/schemas/set-caption-visibility.d.ts
1294
+ /**
1295
+ * Toggle caption visibility (the caption track's `is_hidden` flag). Pure
1296
+ * document edit, no cascade — captions keep their positions; only the lane's
1297
+ * hidden flag changes.
1298
+ */
1299
+ declare const setCaptionVisibilityInputSchema: z.ZodObject<{
1300
+ is_hidden: z.ZodBoolean;
1301
+ }, z.core.$strip>;
1302
+ type SetCaptionVisibilityInput = z.infer<typeof setCaptionVisibilityInputSchema>;
1303
+ //#endregion
1304
+ //#region src/editor/schemas/set-video-clip-speed-shift.d.ts
1305
+ /**
1306
+ * Set the playback speed of existing video clips. Per the speed-shift decision
1307
+ * (`reference/16` §0): the op writes only the `speed_shift` fact — it does NOT
1308
+ * store an effective `duration_ms` (projection derives it from the trim window /
1309
+ * speed) and does NOT scale anchored speeches' relative offsets (offsets stay
1310
+ * put; the cascade reflows absolute positions). A `null` speed_shift clears the
1311
+ * speed back to original (1×).
1312
+ */
1313
+ declare const setVideoClipSpeedShiftInputSchema: z.ZodObject<{
1314
+ clips: z.ZodArray<z.ZodObject<{
1315
+ clip_id: z.ZodString;
1316
+ speed_shift: z.ZodNullable<z.ZodObject<{
1317
+ category: z.ZodEnum<{
1318
+ curve: "curve";
1319
+ linear: "linear";
1320
+ }>;
1321
+ mode: z.ZodString;
1322
+ config: z.ZodUnion<readonly [z.ZodObject<{
1323
+ linear: z.ZodObject<{
1324
+ speed: z.ZodNumber;
1325
+ }, z.core.$strip>;
1326
+ }, z.core.$strip>, z.ZodObject<{
1327
+ curve: z.ZodObject<{
1328
+ keyframes: z.ZodArray<z.ZodObject<{
1329
+ position: z.ZodNumber;
1330
+ rate: z.ZodNumber;
1331
+ in_tangent: z.ZodOptional<z.ZodObject<{
1332
+ x: z.ZodNumber;
1333
+ y: z.ZodNumber;
1334
+ }, z.core.$strip>>;
1335
+ out_tangent: z.ZodOptional<z.ZodObject<{
1336
+ x: z.ZodNumber;
1337
+ y: z.ZodNumber;
1338
+ }, z.core.$strip>>;
1339
+ }, z.core.$strip>>;
1340
+ }, z.core.$strip>;
1341
+ }, z.core.$strip>]>;
1342
+ }, z.core.$strip>>;
1343
+ }, z.core.$strip>>;
1344
+ }, z.core.$strip>;
1345
+ type SetVideoClipSpeedShiftInput = z.infer<typeof setVideoClipSpeedShiftInputSchema>;
1346
+ //#endregion
1347
+ //#region src/editor/schemas/shared.d.ts
1348
+ declare const clipIdSchema: z.ZodString;
1349
+ declare const clipIdsSchema: z.ZodArray<z.ZodString>;
1350
+ declare const mediaIdSchema: z.ZodString;
1351
+ declare const speechIdSchema: z.ZodString;
1352
+ declare const timelineMsSchema: z.ZodNumber;
1353
+ declare const positiveMsSchema: z.ZodNumber;
1354
+ declare const volumeSchema: z.ZodNumber;
1355
+ declare const speechIdsSchema: z.ZodArray<z.ZodString>;
1356
+ /**
1357
+ * A clip's playback-speed fact, the only thing `SetVideoClipSpeedShift` writes.
1358
+ * Mirrors the IDL `SpeedShift`: `category` is `linear` | `curve`, and `config`
1359
+ * is a discriminated union — `{ linear: { speed } }` for a constant multiplier
1360
+ * (the multiplier projection reads at `config.linear.speed`) or `{ curve: {
1361
+ * keyframes } }` for a Bezier-controlled variable speed (RFC 02 / `reference/16`
1362
+ * §0). Exactly one of `linear` / `curve` is present.
1363
+ */
1364
+ declare const speedShiftSchema: z.ZodObject<{
1365
+ category: z.ZodEnum<{
1366
+ curve: "curve";
1367
+ linear: "linear";
1368
+ }>;
1369
+ mode: z.ZodString;
1370
+ config: z.ZodUnion<readonly [z.ZodObject<{
1371
+ linear: z.ZodObject<{
1372
+ speed: z.ZodNumber;
1373
+ }, z.core.$strip>;
1374
+ }, z.core.$strip>, z.ZodObject<{
1375
+ curve: z.ZodObject<{
1376
+ keyframes: z.ZodArray<z.ZodObject<{
1377
+ position: z.ZodNumber;
1378
+ rate: z.ZodNumber;
1379
+ in_tangent: z.ZodOptional<z.ZodObject<{
1380
+ x: z.ZodNumber;
1381
+ y: z.ZodNumber;
1382
+ }, z.core.$strip>>;
1383
+ out_tangent: z.ZodOptional<z.ZodObject<{
1384
+ x: z.ZodNumber;
1385
+ y: z.ZodNumber;
1386
+ }, z.core.$strip>>;
1387
+ }, z.core.$strip>>;
1388
+ }, z.core.$strip>;
1389
+ }, z.core.$strip>]>;
1390
+ }, z.core.$strip>;
1391
+ declare const voiceSchema: z.ZodObject<{
1392
+ id: z.ZodString;
1393
+ name: z.ZodString;
1394
+ }, z.core.$strip>;
1395
+ //#endregion
1396
+ //#region src/editor/schemas/speech-assets.d.ts
1397
+ /**
1398
+ * The materialized TTS result shared by `AddSpeeches` / `ChangeSpeechScript` /
1399
+ * `ChangeSpeechVoice` (see `results/phase-4-side-effect-payload-contract.md`
1400
+ * §1/§2). The side effect (TTS/ASR + billing) runs upstream; the op receives the
1401
+ * stable speech + caption parts and writes them as authoritative facts. No
1402
+ * cascade runs on write — the projection derives absolute positions on read.
1403
+ *
1404
+ * Each speech carries the anchoring fact directly (RFC 02 §4): the host video
1405
+ * clip `anchor_part_id` and the `offset_ms` within it. The upstream caller
1406
+ * already knows which clip a speech attaches to, so the op writes
1407
+ * `{ mode:'anchored', anchorPartId, offsetMs }` verbatim — no write-time
1408
+ * host-picking. Captions anchor to their speech via the caption part's
1409
+ * `start_ms` (offset within the speech).
1410
+ */
1411
+ declare const speechAssetSchema: z.ZodObject<{
1412
+ speech_id: z.ZodString;
1413
+ anchor_part_id: z.ZodString;
1414
+ offset_ms: z.ZodNumber;
1415
+ audio_storage_key: z.ZodString;
1416
+ duration_ms: z.ZodNumber;
1417
+ audio_script: z.ZodString;
1418
+ volume: z.ZodNumber;
1419
+ voice: z.ZodObject<{
1420
+ id: z.ZodString;
1421
+ name: z.ZodString;
1422
+ }, z.core.$strip>;
1423
+ origin_speech_id: z.ZodString;
1424
+ caption_ids: z.ZodArray<z.ZodString>;
1425
+ }, z.core.$strip>;
1426
+ declare const captionAssetSchema: z.ZodObject<{
1427
+ caption_id: z.ZodString;
1428
+ speech_part_id: z.ZodString;
1429
+ text: z.ZodString;
1430
+ start_ms: z.ZodNumber;
1431
+ duration_ms: z.ZodNumber;
1432
+ }, z.core.$strip>;
1433
+ /** A materialized speech-subtree write (speeches + their captions). */
1434
+ declare const speechAssetsSchema: z.ZodObject<{
1435
+ speeches: z.ZodArray<z.ZodObject<{
1436
+ speech_id: z.ZodString;
1437
+ anchor_part_id: z.ZodString;
1438
+ offset_ms: z.ZodNumber;
1439
+ audio_storage_key: z.ZodString;
1440
+ duration_ms: z.ZodNumber;
1441
+ audio_script: z.ZodString;
1442
+ volume: z.ZodNumber;
1443
+ voice: z.ZodObject<{
1444
+ id: z.ZodString;
1445
+ name: z.ZodString;
1446
+ }, z.core.$strip>;
1447
+ origin_speech_id: z.ZodString;
1448
+ caption_ids: z.ZodArray<z.ZodString>;
1449
+ }, z.core.$strip>>;
1450
+ captions: z.ZodArray<z.ZodObject<{
1451
+ caption_id: z.ZodString;
1452
+ speech_part_id: z.ZodString;
1453
+ text: z.ZodString;
1454
+ start_ms: z.ZodNumber;
1455
+ duration_ms: z.ZodNumber;
1456
+ }, z.core.$strip>>;
1457
+ }, z.core.$strip>;
1458
+ type SpeechAsset = z.infer<typeof speechAssetSchema>;
1459
+ type CaptionAsset = z.infer<typeof captionAssetSchema>;
1460
+ type SpeechAssets = z.infer<typeof speechAssetsSchema>;
1461
+ declare namespace index_d_exports {
1462
+ export { AddSpeechesInput, AddVideoClipsInput, AdjustBgmVolumeInput, AdjustSpeechVolumeInput, AdjustVideoClipDurationInput, AdjustVideoClipVolumeInput, AnchoredDeletePolicy, CaptionAsset, ChangeSpeechScriptInput, ChangeSpeechVoiceInput, DeleteBgmInput, DeleteSpeechesInput, DeleteVideoClipsInput, MoveSpeechesInput, MoveVideoClipsInput, ReplaceVideoClipContentInput, SetBgmInput, SetCaptionStyleInput, SetCaptionVisibilityInput, SetVideoClipSpeedShiftInput, SpeechAsset, SpeechAssets, addSpeechesInputSchema, addVideoClipsInputSchema, adjustBgmVolumeInputSchema, adjustSpeechVolumeInputSchema, adjustVideoClipDurationInputSchema, adjustVideoClipVolumeInputSchema, anchoredDeletePolicySchema, changeSpeechScriptInputSchema, changeSpeechVoiceInputSchema, clipIdSchema, clipIdsSchema, deleteBgmInputSchema, deleteSpeechesInputSchema, deleteVideoClipsInputSchema, mediaIdSchema, moveSpeechesInputSchema, moveVideoClipsInputSchema, positiveMsSchema, replaceVideoClipContentInputSchema, setBgmInputSchema, setCaptionStyleInputSchema, setCaptionVisibilityInputSchema, setVideoClipSpeedShiftInputSchema, speechAssetsSchema, speechIdSchema, speechIdsSchema, speedShiftSchema, timelineMsSchema, voiceSchema, volumeSchema };
1463
+ }
1464
+ //#endregion
1465
+ //#region src/editor/schema-validator.d.ts
1466
+ interface SnapshotReadable {
1467
+ snapshot(): VideoDocument;
1468
+ }
1469
+ declare class ValidationError extends Error {
1470
+ readonly code: string;
1471
+ readonly context?: Record<string, unknown>;
1472
+ constructor(code: string, message: string, context?: Record<string, unknown>);
1473
+ }
1474
+ declare class SchemaValidator {
1475
+ validateMoveVideoClips(input: MoveVideoClipsInput, doc: SnapshotReadable): void;
1476
+ validateDeleteVideoClips(input: DeleteVideoClipsInput, doc: SnapshotReadable): void;
1477
+ validateAddVideoClips(input: AddVideoClipsInput, doc: SnapshotReadable): void;
1478
+ validateAdjustVideoClipVolume(input: AdjustVideoClipVolumeInput, doc: SnapshotReadable): void;
1479
+ validateSetVideoClipSpeedShift(input: SetVideoClipSpeedShiftInput, doc: SnapshotReadable): void;
1480
+ validateReplaceVideoClipContent(input: ReplaceVideoClipContentInput, doc: SnapshotReadable): void;
1481
+ validateAdjustVideoClipDuration(input: AdjustVideoClipDurationInput, doc: SnapshotReadable): void;
1482
+ validateAdjustSpeechVolume(input: AdjustSpeechVolumeInput, doc: SnapshotReadable): void;
1483
+ validateAdjustBgmVolume(input: AdjustBgmVolumeInput, doc: SnapshotReadable): void;
1484
+ validateAddSpeeches(input: AddSpeechesInput, doc: SnapshotReadable): void;
1485
+ validateChangeSpeechScript(input: ChangeSpeechScriptInput, doc: SnapshotReadable): void;
1486
+ validateChangeSpeechVoice(input: ChangeSpeechVoiceInput, doc: SnapshotReadable): void;
1487
+ validateDeleteSpeeches(input: DeleteSpeechesInput, doc: SnapshotReadable): void;
1488
+ validateMoveSpeeches(input: MoveSpeechesInput, doc: SnapshotReadable): void;
1489
+ validateSetBgm(input: SetBgmInput, _doc: SnapshotReadable): void;
1490
+ validateDeleteBgm(input: DeleteBgmInput, _doc: SnapshotReadable): void;
1491
+ validateSetCaptionVisibility(input: SetCaptionVisibilityInput, _doc: SnapshotReadable): void;
1492
+ validateSetCaptionStyle(input: SetCaptionStyleInput, _doc: SnapshotReadable): void;
1493
+ private parse;
1494
+ private mainTrackIds;
1495
+ /** Every caption a speech declares in `caption_ids` must be supplied in `captions`. */
1496
+ private assertCaptionsOwned;
1497
+ /** Each regenerated speech (re-TTS) must already exist in the document. */
1498
+ private assertSpeechesExist;
1499
+ /**
1500
+ * Each speech's `anchor_part_id` must point at a video clip that already
1501
+ * exists. A relative speech anchored to a missing clip cannot be positioned
1502
+ * by the projection and has no host to recover to (RFC 02 §4/§11.1), so the
1503
+ * write must be rejected rather than landing a dangling reference. (Restores
1504
+ * the dangling-reference guard the old `validateMaterializedPatch` carried;
1505
+ * ADR 0009.)
1506
+ */
1507
+ private assertAnchorsExist;
1508
+ private assertPartKind;
1509
+ }
1510
+ //#endregion
1511
+ //#region src/editor/types.d.ts
1512
+ /**
1513
+ * `SemanticOp` contract surface — the frozen catalogue of business write
1514
+ * operations the collaborative editor recognises.
1515
+ *
1516
+ * This is the single source of truth; `SemanticEditor` derives its method set
1517
+ * (`SemanticOpName`) from `ImplementedSemanticOpKind`, so adding a method
1518
+ * without listing it here (or vice versa) is a type error.
1519
+ *
1520
+ * Mapping of each kind to UI Operation / agent tool / side-effect routing is
1521
+ * maintained in
1522
+ * `docs/projects/medeo-integration/reference/operation-semantic-matrix.md`; the
1523
+ * frozen contract narrative lives in
1524
+ * `docs/projects/medeo-integration/results/phase-4-semantic-op-contract.md`.
1525
+ */
1526
+ /**
1527
+ * Operations implemented by `SemanticEditor`. FROZEN: each entry has a
1528
+ * `SemanticEditor` method, a narrow zod input schema (business intent +
1529
+ * materialized side-effect assets, never a document slice), and validation. The
1530
+ * editor runs the timeline-core cascade in-transaction to derive every position
1531
+ * (ADR 0009 — no caller-materialized layout).
1532
+ *
1533
+ * Effectful ops (TTS for `AddSpeeches` / `ChangeSpeechScript` /
1534
+ * `ChangeSpeechVoice`, media import for `ReplaceVideoClipContent`, BGM for
1535
+ * `SetBgm`) receive their stable side-effect result already materialized — the
1536
+ * editor only writes the document; it never performs the side effect. See the
1537
+ * side-effect payload contract in
1538
+ * `docs/projects/medeo-integration/results/phase-4-side-effect-payload-contract.md`.
1539
+ */
1540
+ type ImplementedSemanticOpKind = 'MoveVideoClips' | 'DeleteVideoClips' | 'AddVideoClips' | 'AdjustVideoClipVolume' | 'SetVideoClipSpeedShift' | 'ReplaceVideoClipContent' | 'AdjustVideoClipDuration' | 'AddSpeeches' | 'DeleteSpeeches' | 'MoveSpeeches' | 'ChangeSpeechScript' | 'ChangeSpeechVoice' | 'AdjustSpeechVolume' | 'SetCaptionVisibility' | 'SetCaptionStyle' | 'SetBgm' | 'DeleteBgm' | 'AdjustBgmVolume';
1541
+ /**
1542
+ * Operations on the roadmap but NOT yet implemented by `SemanticEditor`. Empty:
1543
+ * Phase 4 covers every op with a real entry point. Kept as a named type so the
1544
+ * contract surface stays explicit and re-expanding the roadmap is a typed edit.
1545
+ * To re-add planned kinds, list them here and add them back to the
1546
+ * `SemanticOpKind` union below.
1547
+ */
1548
+ type PlannedSemanticOpKind = never;
1549
+ /**
1550
+ * Every SemanticOp kind. Currently equals `ImplementedSemanticOpKind` because
1551
+ * `PlannedSemanticOpKind` is empty; re-add `| PlannedSemanticOpKind` when the
1552
+ * roadmap is re-expanded.
1553
+ */
1554
+ type SemanticOpKind = ImplementedSemanticOpKind;
1555
+ /** Runtime list of the frozen, implemented kinds (for guards / introspection). */
1556
+ declare const IMPLEMENTED_SEMANTIC_OP_KINDS: readonly ImplementedSemanticOpKind[];
1557
+ declare function isImplementedSemanticOpKind(kind: string): kind is ImplementedSemanticOpKind;
1558
+ //#endregion
1559
+ //#region src/editor/semantic-editor.d.ts
1560
+ interface CommitOptions {
1561
+ intent?: {
1562
+ kind: string;
1563
+ payload: unknown;
1564
+ };
1565
+ }
1566
+ type SemanticOpInput = MoveVideoClipsInput | DeleteVideoClipsInput | AddVideoClipsInput | AdjustVideoClipVolumeInput | AdjustSpeechVolumeInput | AdjustBgmVolumeInput | SetVideoClipSpeedShiftInput | ReplaceVideoClipContentInput | AdjustVideoClipDurationInput | AddSpeechesInput | DeleteSpeechesInput | MoveSpeechesInput | SetCaptionVisibilityInput | SetCaptionStyleInput | SetBgmInput | DeleteBgmInput;
1567
+ type SemanticOpName = ImplementedSemanticOpKind;
1568
+ /** Audit metadata committed alongside an op's writes. */
1569
+ interface TransactAudit {
1570
+ kind: SemanticOpName;
1571
+ payload: unknown;
1572
+ intent?: unknown;
1573
+ }
1574
+ /**
1575
+ * Narrow write surface the `SemanticEditor` drives (ADR 0008 / 0009). It stays
1576
+ * off raw Loro: every op runs its document mutations inside a single `transact`
1577
+ * callback, which the storage adapter applies as one CRDT transaction + commit
1578
+ * carrying the audit message.
1579
+ *
1580
+ * The callback edits a `VideoDocumentDraft` — an immer draft of the whole
1581
+ * document state. A throw inside it discards the draft and never touches Loro,
1582
+ * so partial writes roll back naturally (no `commit`/`rollback` machinery). The
1583
+ * adapter's declarative diff turns the edited draft into minimal CRDT ops,
1584
+ * including real `move`s for reordered track items (keyed by `part_id`), so
1585
+ * per-item identity survives on every lane.
1586
+ *
1587
+ * Each op takes a narrow business input (intent + materialized side-effect
1588
+ * assets, never a document slice); the editor applies the authoritative facts
1589
+ * and runs the timeline-core cascade inside the callback to derive every
1590
+ * position. The caller never materializes a layout (ADR 0009).
1591
+ */
1592
+ interface SemanticDocumentAdapter extends SnapshotReadable {
1593
+ /** True once the document holds real content (a bootstrapped/synced snapshot). */
1594
+ hasContent(): boolean;
1595
+ /**
1596
+ * Apply one op's whole mutation in a single transaction. `edit` mutates the
1597
+ * document draft; the adapter diffs the result and commits once with `audit`.
1598
+ * A throw in `edit` rolls back (the draft is discarded, Loro untouched). An
1599
+ * edit that changes nothing produces no commit (no phantom audit entry).
1600
+ */
1601
+ transact(edit: (draft: VideoDocumentDraft) => void, audit: TransactAudit): void;
1602
+ }
1603
+ declare class SemanticEditor {
1604
+ private readonly doc;
1605
+ private readonly validator;
1606
+ constructor(doc: SemanticDocumentAdapter, validator?: SchemaValidator);
1607
+ moveVideoClips(input: MoveVideoClipsInput, options?: CommitOptions): Promise<void>;
1608
+ deleteVideoClips(input: DeleteVideoClipsInput, options?: CommitOptions): Promise<void>;
1609
+ addVideoClips(input: AddVideoClipsInput, options?: CommitOptions): Promise<void>;
1610
+ adjustVideoClipVolume(input: AdjustVideoClipVolumeInput, options?: CommitOptions): Promise<void>;
1611
+ setVideoClipSpeedShift(input: SetVideoClipSpeedShiftInput, options?: CommitOptions): Promise<void>;
1612
+ adjustSpeechVolume(input: AdjustSpeechVolumeInput, options?: CommitOptions): Promise<void>;
1613
+ adjustBgmVolume(input: AdjustBgmVolumeInput, options?: CommitOptions): Promise<void>;
1614
+ /**
1615
+ * Replace the media backing existing video clips. The new media's stable
1616
+ * result (new media id, intrinsic length, reset trim window) is materialized
1617
+ * upstream; the clip ids and their track items are unchanged. Only the part
1618
+ * facts change — effective duration and downstream positions are derived by
1619
+ * the projection on read.
1620
+ */
1621
+ replaceVideoClipContent(input: ReplaceVideoClipContentInput, options?: CommitOptions): Promise<void>;
1622
+ /**
1623
+ * Re-trim clips. Only the `play_in` / `play_out` facts change; the new
1624
+ * effective duration and the resulting downstream reflow are derived by the
1625
+ * projection on read (anchored speeches follow their host clip automatically).
1626
+ */
1627
+ adjustVideoClipDuration(input: AdjustVideoClipDurationInput, options?: CommitOptions): Promise<void>;
1628
+ /**
1629
+ * Add speeches (and their captions). TTS runs upstream; the materialized
1630
+ * speech / caption parts arrive in `input`, each carrying its host clip
1631
+ * `anchor_part_id` + `offset_ms`. The editor writes the parts and their
1632
+ * `anchored` `time_position` facts verbatim — no write-time host-picking, no
1633
+ * cascade. The projection derives absolute positions on read.
1634
+ */
1635
+ addSpeeches(input: AddSpeechesInput, options?: CommitOptions): Promise<void>;
1636
+ /** Delete speeches with their captions; the subtree is removed (§9.2). Surviving lanes keep their facts. */
1637
+ deleteSpeeches(input: DeleteSpeechesInput, options?: CommitOptions): Promise<void>;
1638
+ /**
1639
+ * Move speeches in time (§9.1). One forward positioning pass: anchor each
1640
+ * speech to the main-track clip its new absolute start lands in and write the
1641
+ * resulting `anchored` `time_position` fact. No cascade — the projection
1642
+ * derives absolute positions on read.
1643
+ */
1644
+ moveSpeeches(input: MoveSpeechesInput, options?: CommitOptions): Promise<void>;
1645
+ /** Change a speech's script. Re-TTS runs upstream; the regenerated parts arrive materialized (no cascade). */
1646
+ changeSpeechScript(input: ChangeSpeechScriptInput, options?: CommitOptions): Promise<void>;
1647
+ /** Change a speech's voice. Re-TTS runs upstream; the regenerated parts arrive materialized (no cascade). */
1648
+ changeSpeechVoice(input: ChangeSpeechVoiceInput, options?: CommitOptions): Promise<void>;
1649
+ /**
1650
+ * Set the document BGM. The media's stable result arrives materialized.
1651
+ *
1652
+ * KNOWN GAP (non-blocking): when the BGM comes from the public library, the
1653
+ * Director must also register project-level stock media ownership. That is a
1654
+ * separate "register-only, no draft write" side effect Director does not yet
1655
+ * expose; until it does, a public-library BGM set here will not auto-appear in
1656
+ * the project media library. The document edit itself is complete and correct.
1657
+ * See `docs/projects/medeo-integration/results/phase-4-op-side-effect-classification.md`.
1658
+ */
1659
+ setBgm(input: SetBgmInput, options?: CommitOptions): Promise<void>;
1660
+ /** Remove the document BGM; clears the bgm lane and removes the part. */
1661
+ deleteBgm(input: DeleteBgmInput, options?: CommitOptions): Promise<void>;
1662
+ /** Toggle caption visibility (caption track `is_hidden`). */
1663
+ setCaptionVisibility(input: SetCaptionVisibilityInput, options?: CommitOptions): Promise<void>;
1664
+ /**
1665
+ * Set the document-wide caption style. GLOBAL (no `caption_id`): the patch is
1666
+ * merged onto EVERY caption part's `style`, mirroring the FE, which applies a
1667
+ * single style to all captions (`caption-style.ts:persistCaptionStylePatch`).
1668
+ *
1669
+ * Merge, not replace: only the fields present in the input overwrite the
1670
+ * caption's existing style; absent fields are carried forward. So a partial
1671
+ * patch ("recolor only") keeps the caption's font size. Pure document edit, no
1672
+ * cascade — positions are untouched.
1673
+ */
1674
+ setCaptionStyle(input: SetCaptionStyleInput, options?: CommitOptions): Promise<void>;
1675
+ /**
1676
+ * Resolve the main-track sequential layout from `source`. Callers inside a
1677
+ * `transact` MUST pass the live `draft` so item order reflects in-progress
1678
+ * mutations; the default `this.doc.snapshot()` is only committed state and is
1679
+ * correct for read-only callers outside a transaction. `readMainTrackItems` /
1680
+ * `readPartDurationMs` accept both an immer draft and a raw snapshot.
1681
+ */
1682
+ private computeMainTrackLayout;
1683
+ private indexForStartMs;
1684
+ private computeAddInsertIndex;
1685
+ }
1686
+ //#endregion
1687
+ //#region src/document/mirror-adapter.d.ts
1688
+ interface MirrorVideoDocumentOptions {
1689
+ peerId?: PeerID;
1690
+ origin?: string;
1691
+ }
1692
+ /**
1693
+ * Storage-layer adapter that backs a `VideoDocument` with `loro-mirror` (ADR
1694
+ * 0008). The mirror holds an in-memory immutable state synced to the `LoroDoc`
1695
+ * by declarative diff, replacing the hand-rolled `@mengine/schema` adapter:
1696
+ *
1697
+ * - `snapshot()` projects the mirror's in-memory state to the read model — no
1698
+ * per-call `toJSON()` FFI rebuild.
1699
+ * - `transact(edit, audit)` runs the whole op in one `mirror.setState` callback:
1700
+ * one diff, one `doc.commit` carrying the audit message. The callback edits an
1701
+ * immer draft, so a throw inside it discards the draft and never touches Loro
1702
+ * (natural rollback) — no `guard` / `rollback` / `openTransaction` machinery.
1703
+ * - mirror's `idSelector` (track items keyed by `part_id`) diffs reorders to
1704
+ * real Loro `move` ops on every lane, so per-item CRDT identity survives on
1705
+ * main and secondary tracks alike.
1706
+ */
1707
+ declare class MirrorVideoDocumentAdapter implements SemanticDocumentAdapter {
1708
+ readonly doc: LoroDoc;
1709
+ private readonly mirror;
1710
+ constructor(doc: LoroDoc);
1711
+ snapshot(): VideoDocument;
1712
+ /**
1713
+ * True once the doc holds real document content. A fresh mirror over an empty
1714
+ * doc still reports defaulted root maps, so probe the stored `schema_version`
1715
+ * (empty until a snapshot is bootstrapped or synced in).
1716
+ */
1717
+ hasContent(): boolean;
1718
+ /**
1719
+ * Apply one op as a single transaction. `edit` mutates the immer draft; mirror
1720
+ * diffs the result and commits once with the audit `message`. A throw in `edit`
1721
+ * discards the draft (Loro untouched). When `edit` produces no change, mirror
1722
+ * skips the commit — matching the prior "empty op leaves no audit" behavior.
1723
+ */
1724
+ transact(edit: (draft: VideoDocumentDraft) => void, audit: TransactAudit): void;
1725
+ }
1726
+ /** Build a fresh Loro doc seeded with `document` through the mirror. */
1727
+ declare function createMirrorVideoDocument(document: VideoDocument, options?: MirrorVideoDocumentOptions): LoroDoc;
1728
+ /** Build a `MirrorVideoDocumentAdapter` over a fresh doc seeded with `document`. */
1729
+ declare function createMirrorVideoDocumentAdapter(document: VideoDocument, options?: MirrorVideoDocumentOptions): MirrorVideoDocumentAdapter;
1730
+ //#endregion
1731
+ //#region src/document/mirror-read.d.ts
1732
+ /**
1733
+ * Project the mirror state (`VideoDocumentDraft`) into the authoritative
1734
+ * `VideoDocument`. The storage shape is isomorphic to the domain shape (RFC 03
1735
+ * §4, reference/17 §4: meta map + a single `tracks` list + part_library), so this
1736
+ * is a near-identity — it reads `time_position` / `fallback_abs_ms` JSON blobs
1737
+ * back into structured values and trims empty strings, nothing more.
1738
+ *
1739
+ * It maps only authoritative facts (RFC 02 §6): `part_id` + `time_position`. The
1740
+ * projection-derived `VideoDraft` read-view (absolute time, `part_aggregations`,
1741
+ * total duration) is solved separately by the cascade, not here.
1742
+ *
1743
+ * It reads in-memory mirror state (O(n) over the document), not a Loro
1744
+ * `toJSON()` FFI rebuild — the cost the prior schema adapter paid on every
1745
+ * `snapshot()`.
1746
+ */
1747
+ declare function readVideoDocumentFromDraft(draft: VideoDocumentDraft): VideoDocument;
1748
+ //#endregion
1749
+ //#region src/document/zod-schema.d.ts
1750
+ declare const partUnionSchema: z.ZodUnion<readonly [z.ZodObject<{
1751
+ video_clip: z.ZodObject<{
1752
+ id: z.ZodOptional<z.ZodString>;
1753
+ kind: z.ZodLiteral<"video_clip">;
1754
+ duration_ms: z.ZodOptional<z.ZodNumber>;
1755
+ play_in: z.ZodNumber;
1756
+ play_out: z.ZodNumber;
1757
+ volume: z.ZodNumber;
1758
+ origin_media_id: z.ZodString;
1759
+ speed_shift: z.ZodOptional<z.ZodObject<{
1760
+ category: z.ZodOptional<z.ZodString>;
1761
+ mode: z.ZodOptional<z.ZodString>;
1762
+ config: z.ZodOptional<z.ZodObject<{
1763
+ linear: z.ZodOptional<z.ZodObject<{
1764
+ speed: z.ZodOptional<z.ZodNumber>;
1765
+ }, z.core.$loose>>;
1766
+ }, z.core.$loose>>;
1767
+ }, z.core.$loose>>;
1768
+ }, z.core.$loose>;
1769
+ }, z.core.$loose>, z.ZodObject<{
1770
+ speech: z.ZodObject<{
1771
+ id: z.ZodOptional<z.ZodString>;
1772
+ kind: z.ZodLiteral<"speech">;
1773
+ media_duration_ms: z.ZodNumber;
1774
+ duration_ms: z.ZodOptional<z.ZodNumber>;
1775
+ audio_script: z.ZodString;
1776
+ volume: z.ZodNumber;
1777
+ audio_storage_key: z.ZodString;
1778
+ origin_speech_id: z.ZodString;
1779
+ voice: z.ZodUnknown;
1780
+ caption_ids: z.ZodArray<z.ZodString>;
1781
+ }, z.core.$loose>;
1782
+ }, z.core.$loose>, z.ZodObject<{
1783
+ caption: z.ZodObject<{
1784
+ id: z.ZodOptional<z.ZodString>;
1785
+ kind: z.ZodLiteral<"caption">;
1786
+ initial_duration_ms: z.ZodNumber;
1787
+ speech_part_id: z.ZodString;
1788
+ text: z.ZodString;
1789
+ start_ms: z.ZodNumber;
1790
+ style: z.ZodOptional<z.ZodObject<{
1791
+ font_id: z.ZodOptional<z.ZodString>;
1792
+ font_size: z.ZodOptional<z.ZodNumber>;
1793
+ font_color: z.ZodOptional<z.ZodString>;
1794
+ font_weight: z.ZodOptional<z.ZodNumber>;
1795
+ entrance_animation: z.ZodOptional<z.ZodString>;
1796
+ entrance_animation_duration_ms: z.ZodOptional<z.ZodNumber>;
1797
+ stroke_color: z.ZodOptional<z.ZodString>;
1798
+ stroke_width: z.ZodOptional<z.ZodNumber>;
1799
+ position_x: z.ZodOptional<z.ZodNumber>;
1800
+ position_y: z.ZodOptional<z.ZodNumber>;
1801
+ }, z.core.$loose>>;
1802
+ }, z.core.$loose>;
1803
+ }, z.core.$loose>, z.ZodObject<{
1804
+ bgm: z.ZodObject<{
1805
+ id: z.ZodOptional<z.ZodString>;
1806
+ kind: z.ZodLiteral<"bgm">;
1807
+ audio_storage_key: z.ZodString;
1808
+ volume: z.ZodNumber;
1809
+ origin_media_id: z.ZodString;
1810
+ }, z.core.$loose>;
1811
+ }, z.core.$loose>]>;
1812
+ declare const videoDocumentSchema: z.ZodObject<{
1813
+ meta: z.ZodObject<{
1814
+ schema_version: z.ZodLiteral<"video-document/v0">;
1815
+ draft_id: z.ZodOptional<z.ZodString>;
1816
+ project_id: z.ZodOptional<z.ZodString>;
1817
+ owner_id: z.ZodOptional<z.ZodString>;
1818
+ thumbnail_storage_key: z.ZodOptional<z.ZodString>;
1819
+ chat_session_id: z.ZodOptional<z.ZodString>;
1820
+ video_creation_settings: z.ZodOptional<z.ZodUnknown>;
1821
+ version: z.ZodOptional<z.ZodNumber>;
1822
+ }, z.core.$loose>;
1823
+ timeline: z.ZodOptional<z.ZodObject<{
1824
+ unit_time_ms: z.ZodOptional<z.ZodNumber>;
1825
+ }, z.core.$loose>>;
1826
+ tracks: z.ZodOptional<z.ZodArray<z.ZodObject<{
1827
+ id: z.ZodOptional<z.ZodString>;
1828
+ parts_kind: z.ZodOptional<z.ZodEnum<{
1829
+ bgm: "bgm";
1830
+ caption: "caption";
1831
+ speech: "speech";
1832
+ video_clip: "video_clip";
1833
+ }>>;
1834
+ is_hidden: z.ZodOptional<z.ZodBoolean>;
1835
+ items: z.ZodOptional<z.ZodArray<z.ZodObject<{
1836
+ part_id: z.ZodString;
1837
+ time_position: z.ZodDiscriminatedUnion<[z.ZodObject<{
1838
+ mode: z.ZodLiteral<"sequential">;
1839
+ }, z.core.$loose>, z.ZodObject<{
1840
+ mode: z.ZodLiteral<"anchored">;
1841
+ anchorPartId: z.ZodString;
1842
+ offsetMs: z.ZodNumber;
1843
+ }, z.core.$loose>, z.ZodObject<{
1844
+ mode: z.ZodLiteral<"absolute">;
1845
+ offsetMs: z.ZodNumber;
1846
+ }, z.core.$loose>], "mode">;
1847
+ fallback_abs_ms: z.ZodOptional<z.ZodNumber>;
1848
+ }, z.core.$loose>>>;
1849
+ }, z.core.$loose>>>;
1850
+ part_library: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
1851
+ video_clip: z.ZodObject<{
1852
+ id: z.ZodOptional<z.ZodString>;
1853
+ kind: z.ZodLiteral<"video_clip">;
1854
+ duration_ms: z.ZodOptional<z.ZodNumber>;
1855
+ play_in: z.ZodNumber;
1856
+ play_out: z.ZodNumber;
1857
+ volume: z.ZodNumber;
1858
+ origin_media_id: z.ZodString;
1859
+ speed_shift: z.ZodOptional<z.ZodObject<{
1860
+ category: z.ZodOptional<z.ZodString>;
1861
+ mode: z.ZodOptional<z.ZodString>;
1862
+ config: z.ZodOptional<z.ZodObject<{
1863
+ linear: z.ZodOptional<z.ZodObject<{
1864
+ speed: z.ZodOptional<z.ZodNumber>;
1865
+ }, z.core.$loose>>;
1866
+ }, z.core.$loose>>;
1867
+ }, z.core.$loose>>;
1868
+ }, z.core.$loose>;
1869
+ }, z.core.$loose>, z.ZodObject<{
1870
+ speech: z.ZodObject<{
1871
+ id: z.ZodOptional<z.ZodString>;
1872
+ kind: z.ZodLiteral<"speech">;
1873
+ media_duration_ms: z.ZodNumber;
1874
+ duration_ms: z.ZodOptional<z.ZodNumber>;
1875
+ audio_script: z.ZodString;
1876
+ volume: z.ZodNumber;
1877
+ audio_storage_key: z.ZodString;
1878
+ origin_speech_id: z.ZodString;
1879
+ voice: z.ZodUnknown;
1880
+ caption_ids: z.ZodArray<z.ZodString>;
1881
+ }, z.core.$loose>;
1882
+ }, z.core.$loose>, z.ZodObject<{
1883
+ caption: z.ZodObject<{
1884
+ id: z.ZodOptional<z.ZodString>;
1885
+ kind: z.ZodLiteral<"caption">;
1886
+ initial_duration_ms: z.ZodNumber;
1887
+ speech_part_id: z.ZodString;
1888
+ text: z.ZodString;
1889
+ start_ms: z.ZodNumber;
1890
+ style: z.ZodOptional<z.ZodObject<{
1891
+ font_id: z.ZodOptional<z.ZodString>;
1892
+ font_size: z.ZodOptional<z.ZodNumber>;
1893
+ font_color: z.ZodOptional<z.ZodString>;
1894
+ font_weight: z.ZodOptional<z.ZodNumber>;
1895
+ entrance_animation: z.ZodOptional<z.ZodString>;
1896
+ entrance_animation_duration_ms: z.ZodOptional<z.ZodNumber>;
1897
+ stroke_color: z.ZodOptional<z.ZodString>;
1898
+ stroke_width: z.ZodOptional<z.ZodNumber>;
1899
+ position_x: z.ZodOptional<z.ZodNumber>;
1900
+ position_y: z.ZodOptional<z.ZodNumber>;
1901
+ }, z.core.$loose>>;
1902
+ }, z.core.$loose>;
1903
+ }, z.core.$loose>, z.ZodObject<{
1904
+ bgm: z.ZodObject<{
1905
+ id: z.ZodOptional<z.ZodString>;
1906
+ kind: z.ZodLiteral<"bgm">;
1907
+ audio_storage_key: z.ZodString;
1908
+ volume: z.ZodNumber;
1909
+ origin_media_id: z.ZodString;
1910
+ }, z.core.$loose>;
1911
+ }, z.core.$loose>]>>>;
1912
+ }, z.core.$loose>;
1913
+ //#endregion
1914
+ //#region src/editor/id-gen.d.ts
1915
+ /**
1916
+ * Part-id generation, aligned with the online ecosystem.
1917
+ *
1918
+ * The authoritative online producers — agent-harness (`@harness/shared`
1919
+ * `genObjId`) and director.v2 (`common/obj_id.py` `gen_obj_id`) — both mint part
1920
+ * ids as `` `${prefix}_${ulid()}` ``, and real captured drafts use exactly that
1921
+ * shape (`clip_…` / `spe_…` / `cap_…` / `bgm_…`, each a 26-char ULID). The engine
1922
+ * previously emitted `vc_<base36 timestamp><6 random>`, a different prefix AND a
1923
+ * different encoding — the sole cross-repo id divergence. This module removes it
1924
+ * by emitting the same `<prefix>_<ULID>` bytes.
1925
+ *
1926
+ * The ULID is generated inline (Crockford Base32, 48-bit time + 80-bit random)
1927
+ * rather than pulling the `ulid` npm package: the randomness class matches the
1928
+ * old generator (both `Math.random`-based) and it keeps `@mengine/medeo-client`
1929
+ * dependency-free for a purely mechanical id string. Part ids only need to be
1930
+ * unique and lexicographically time-sortable, which this satisfies.
1931
+ */
1932
+ /**
1933
+ * Online part-id semantic prefixes. `clip` (video clip) is the only value the
1934
+ * engine currently mints (see `addVideoClips`); the rest are declared so the
1935
+ * type documents the shared vocabulary and guards against reintroducing the old
1936
+ * `vc`/`sp`/`cp`/`bg` names. Speech/caption/bgm ids arrive pre-minted in op
1937
+ * payloads, so the engine never generates them itself.
1938
+ */
1939
+ type PartIdPrefix = 'clip' | 'spe' | 'cap' | 'bgm' | 'ti';
1940
+ declare function generatePartId(prefix: PartIdPrefix): string;
1941
+ //#endregion
1942
+ //#region src/editor/snapshot-utils.d.ts
1943
+ /** Main-track item identity read from a raw snapshot (only `part_id` is needed). */
1944
+ interface MainTrackItemRef {
1945
+ part_id: string | undefined;
1946
+ }
1947
+ declare function isMap(value: unknown): value is Map<unknown, unknown>;
1948
+ declare function snapshotToPlain(value: unknown): unknown;
1949
+ declare function getAt(snapshot: unknown, ...keys: string[]): unknown;
1950
+ declare function readMainTrackItems(snapshot: unknown): MainTrackItemRef[];
1951
+ /**
1952
+ * The effective timeline duration of a part read from a raw snapshot. No part
1953
+ * stores a derived `duration_ms` in authoritative state (reference/17 §5): a
1954
+ * video clip's length is its trim window `play_out - play_in` over the speed
1955
+ * multiplier; a speech's is its intrinsic `media_duration_ms`; a caption's is its
1956
+ * `initial_duration_ms`. Falls back to `fallback` when no source value is
1957
+ * resolvable (e.g. an unknown/raw part).
1958
+ */
1959
+ declare function readPartDurationMs(snapshot: unknown, partId: string, fallback?: number): number;
1960
+ declare function readPart(snapshot: unknown, partId: string): (Record<string, unknown> & {
1961
+ part_kind?: PartKind;
1962
+ }) | null;
1963
+ //#endregion
1964
+ //#region src/session/types.d.ts
1965
+ /**
1966
+ * Local storage contract the runtime depends on. Aliased to the engine
1967
+ * `DocStorage` so browsers can inject `IndexedDBDocStorage` and Node can inject
1968
+ * `MemoryDocStorage` without medeo-client taking a hard dependency on either
1969
+ * concrete implementation.
1970
+ */
1971
+ type DocStorageLike = DocStorage;
1972
+ //#endregion
1973
+ //#region src/session/mengine-doc-session.d.ts
1974
+ interface MengineDocSessionUpdateEvent {
1975
+ source: 'remote' | 'local';
1976
+ snapshot: VideoDocument;
1977
+ }
1978
+ interface MengineDocSessionOptions {
1979
+ docId: string;
1980
+ client: MengineHttpClient;
1981
+ peerId?: PeerID;
1982
+ /**
1983
+ * Local storage peer. Defaults to in-memory so the session works in Node.
1984
+ * Browsers should pass an `IndexedDBDocStorage` for refresh/cross-tab support.
1985
+ */
1986
+ localStorage?: DocStorageLike;
1987
+ sseReconnectDelayMs?: number;
1988
+ }
1989
+ /**
1990
+ * A live editing session for one Medeo document — the single entry point clients
1991
+ * (FE draft driver, Agent, embeds) use to open, edit, and observe a document.
1992
+ *
1993
+ * It assembles the engine sync stack around one Medeo document:
1994
+ *
1995
+ * local DocStorage ─┐
1996
+ * ├─ ClientServerSynchronizer ── DocManager ── LoroDoc
1997
+ * MedeoHttpDocStorage┘ │
1998
+ * (remote = Rust mengine-server) ▼
1999
+ * MirrorVideoDocumentAdapter + SemanticEditor
2000
+ *
2001
+ * `DocManager` owns the `LoroDoc`: local edits committed on the adapter are
2002
+ * picked up via `subscribeLocalUpdates`, saved to local storage, then pushed to
2003
+ * the server by the synchronizer. Remote SSE updates flow server → synchronizer
2004
+ * → local storage → manager → `LoroDoc`. A single `LoroDoc.subscribe` turns any
2005
+ * resulting change into a snapshot event, so callers never track update ids by
2006
+ * hand.
2007
+ *
2008
+ * Replaces the per-consumer hand-written pull/SSE loops that previously lived in
2009
+ * the standalone client session and `MengineDraftDriver` with one verified path.
2010
+ */
2011
+ declare class MengineDocSession {
2012
+ private readonly options;
2013
+ private readonly docId;
2014
+ private readonly local;
2015
+ private readonly server;
2016
+ private readonly synchronizer;
2017
+ private readonly manager;
2018
+ private readonly events;
2019
+ private readonly disposables;
2020
+ private adapterValue;
2021
+ private editorValue;
2022
+ private started;
2023
+ constructor(options: MengineDocSessionOptions);
2024
+ /**
2025
+ * The editor for local edits. Each op method validates, writes, and commits
2026
+ * itself as one SemanticOp (single commit carrying its audit message), so
2027
+ * callers just call `session.editor.someOp(...)` — there is no separate commit
2028
+ * step. The committed change drives DocManager's local-update push.
2029
+ */
2030
+ get editor(): SemanticEditor;
2031
+ /** Current document snapshot (read model). */
2032
+ snapshot(): VideoDocument;
2033
+ /**
2034
+ * Start the sync stack and connect the document.
2035
+ *
2036
+ * Returns once the local snapshot has loaded into the Loro doc so callers can
2037
+ * read an initial snapshot. Remote convergence continues in the background and
2038
+ * surfaces through `subscribe`.
2039
+ */
2040
+ start(): Promise<VideoDocument>;
2041
+ subscribe(cb: (event: MengineDocSessionUpdateEvent) => void): () => void;
2042
+ onStateChange(cb: (state: DocState) => void): () => void;
2043
+ getState(): DocState;
2044
+ destroy(): void;
2045
+ /**
2046
+ * Resolve once the Loro doc holds the document root.
2047
+ *
2048
+ * `DocManager` loads from local storage, which starts empty for a fresh
2049
+ * client; the server snapshot arrives asynchronously via the first sync job.
2050
+ * `loaded` only means the local load ran, so we wait for actual content
2051
+ * (populated schema roots) instead, surfaced by the doc subscription set up
2052
+ * in `start()`.
2053
+ */
2054
+ private waitForContent;
2055
+ private hasContent;
2056
+ }
2057
+ //#endregion
2058
+ //#region src/storage/medeo-http-doc-storage.d.ts
2059
+ interface MedeoHttpDocStorageOptions {
2060
+ docId: string;
2061
+ client: MengineHttpClient;
2062
+ sseReconnectDelayMs?: number;
2063
+ readonlyMode?: boolean;
2064
+ }
2065
+ /**
2066
+ * Adapts the mengine-server HTTP/SSE protocol to the engine `DocStorage`
2067
+ * contract so `ClientServerSynchronizer` can treat it as a remote peer.
2068
+ *
2069
+ * Deliberately thin (mirrors the socket `DocStorage` in the playground): it
2070
+ * forwards live SSE updates and exposes a version-vector diff, and keeps NO
2071
+ * sync state of its own.
2072
+ *
2073
+ * - `getDocDiff(docId, knownVersion)` pulls the server-computed VV-diff via
2074
+ * `GET /sync?from=<vv>` — the synchronizer passes the real `doc.version()`, so
2075
+ * the response carries exactly the ops the doc is missing. `getDoc` (full
2076
+ * `/snapshot`) stays for cold start, when the caller holds no version yet.
2077
+ * - `pushDocUpdate` forwards a Loro update; the server appends it.
2078
+ * - `subscribeDocUpdate` registers a callback for live SSE updates. It does no
2079
+ * catch-up and keeps no cursor: after an SSE drop the connection reports a
2080
+ * status change, and the synchronizer re-runs its cycle to catch up via
2081
+ * `getDocDiff(doc.version())`. `LoroDoc.import` is idempotent (OpId/VV), so
2082
+ * re-forwarded or echoed updates are harmless.
2083
+ *
2084
+ * It is bound to a single `docId` because `MengineHttpClient` is per-document.
2085
+ */
2086
+ declare class MedeoHttpDocStorage implements DocStorage {
2087
+ private readonly options;
2088
+ readonly connection: Connection;
2089
+ private readonly client;
2090
+ private readonly docId;
2091
+ private readonly events;
2092
+ constructor(options: MedeoHttpDocStorageOptions);
2093
+ get isReadonly(): boolean;
2094
+ getDoc(docId: string): Promise<DocSnapshotRecord | null>;
2095
+ getDocDiff(docId: string, knownVersion?: Uint8Array): Promise<DocDiff | null>;
2096
+ pushDocUpdate(update: DocUpdate, _origin: unknown): Promise<void>;
2097
+ deleteDoc(_docId: string): Promise<void>;
2098
+ subscribeDocUpdate(callback: (update: DocUpdate, origin: unknown) => void): () => void;
2099
+ private assertDocId;
2100
+ private emitUpdate;
2101
+ }
2102
+ //#endregion
2103
+ //#region src/storage/memory-doc-storage.d.ts
2104
+ /**
2105
+ * Runtime-neutral local `DocStorage` backed by in-process memory.
2106
+ *
2107
+ * `IndexedDBDocStorage` is the browser-side local storage, but it requires
2108
+ * `indexedDB`/`idb`, which is absent in Node (FE/agent tests, unit tests, SSR).
2109
+ * The mengine runtime injects this implementation as the local peer in those
2110
+ * environments so the same `DocManager` + `ClientServerSynchronizer` wiring
2111
+ * works without a browser. It mirrors the merge-on-read and update-sequence
2112
+ * behavior of `IndexedDBDocStorage` so sync semantics are identical.
2113
+ */
2114
+ declare class MemoryDocStorage extends BaseDocStorage {
2115
+ readonly connection: Connection;
2116
+ private readonly entries;
2117
+ constructor(options?: DocStorageOptions);
2118
+ pushDocUpdate(update: DocUpdate, origin: unknown): Promise<void>;
2119
+ deleteDoc(docId: string): Promise<void>;
2120
+ protected getDocSnapshot(docId: string): Promise<DocSnapshotRecord | null>;
2121
+ protected setDocSnapshot(snapshot: DocSnapshotRecord): Promise<boolean>;
2122
+ protected getDocUpdates(docId: string): Promise<DocUpdateRecord[]>;
2123
+ protected markUpdatesMerged(docId: string, updates: DocUpdateRecord[]): Promise<number>;
2124
+ private entry;
2125
+ private now;
2126
+ }
2127
+ //#endregion
2128
+ //#region src/timeline-core/types.d.ts
2129
+ /** Total document duration when there is no real content (matches FE bgm fallback). */
2130
+ declare const TIMELINE_SKELETON_DURATION_MS = 20000;
2131
+ /**
2132
+ * Plain-JSON working shape the timeline-core cascade functions operate on.
2133
+ *
2134
+ * It is a flat, lane-as-array view of the document — close to agent-harness's
2135
+ * `NormalizedDraftRow` and to the legacy `VideoDraft`, deliberately *not* the
2136
+ * mirror's keyed-track-map storage shape. The editor converts a
2137
+ * `VideoDocumentDraft` into this view, runs the cascade, and writes the result
2138
+ * back (see `timeline-core/index.ts`). Keeping the algorithms on a structured
2139
+ * shape makes them readable and 1:1 comparable with the canonical reference.
2140
+ *
2141
+ * All times are integer milliseconds (ADR 0009 §4): callers normalize on the
2142
+ * way in and the cascade preserves that.
2143
+ */
2144
+ interface TimelineDoc {
2145
+ main_track: TimelineItem[];
2146
+ /** speech lane (below main). */
2147
+ speech_track: TimelineItem[];
2148
+ /** caption lane (above main). */
2149
+ caption_track: TimelineItem[];
2150
+ /** bgm lane (below main). */
2151
+ bgm_track: TimelineItem[];
2152
+ part_library: Record<string, PartUnion>;
2153
+ /** body_part_id -> { attachment part_id -> relative_time_position }. */
2154
+ aggregations: Aggregation[];
2155
+ timeline: {
2156
+ duration_ms: number;
2157
+ unit_time_ms: number;
2158
+ };
2159
+ }
2160
+ /**
2161
+ * Working item the cascade solves over. `part_id` is the placement identity
2162
+ * (unique per lane). `time_position` is the authoritative input read from the
2163
+ * draft; `abs_time_position` is the cascade's solve variable (derived output),
2164
+ * seeded from `time_position` (or `fallback_abs_ms`) on read.
2165
+ *
2166
+ * `fallback_abs_ms` is the authoritative orphan-recovery snapshot carried beside
2167
+ * `time_position` (RFC 02 §11.1). On read it seeds the solve variable for
2168
+ * `anchored` items; on write it is refreshed to the freshly solved absolute
2169
+ * position.
2170
+ */
2171
+ interface TimelineItem {
2172
+ part_id: string;
2173
+ time_position: TrackItemTimePosition;
2174
+ abs_time_position: number;
2175
+ fallback_abs_ms?: number | undefined;
2176
+ }
2177
+ interface Aggregation {
2178
+ body_part_id: string;
2179
+ attachments: Array<{
2180
+ part_id: string;
2181
+ relative_time_position: number;
2182
+ }>;
2183
+ }
2184
+ /** Empty placeholder clip marker: a video_clip part with no backing media. */
2185
+ declare function isEmptyVideoClip(part: PartUnion | undefined): boolean;
2186
+ /** Clamp a duration to a non-negative integer (NaN/Infinity/negative → 0). */
2187
+ declare function safeDurationMs(value: number | undefined): number;
2188
+ declare function partDurationMs(doc: TimelineDoc, partId: string): number;
2189
+ //#endregion
2190
+ //#region src/timeline-core/cascade.d.ts
2191
+ /**
2192
+ * Canonical timeline cascade primitives (ADR 0009).
2193
+ *
2194
+ * Single source of truth for "how an edit's connected regions move": main-track
2195
+ * seamless layout, aggregation position sync + reassignment, total-duration
2196
+ * recompute, speech-overlap resolution, gap filling. Reconciled per ADR 0009 §4:
2197
+ *
2198
+ * - product behavior follows the FE current implementation;
2199
+ * - all times are integer ms — positions/durations are rounded, never floated;
2200
+ * - function decomposition follows agent-harness (the Python-derived structure).
2201
+ *
2202
+ * Every function mutates the `TimelineDoc` in place (the editor runs them inside
2203
+ * one immer `transact`, so in-place edits diff correctly).
2204
+ */
2205
+ /**
2206
+ * Lay the main track out head-to-tail from 0, rewriting each item's
2207
+ * `abs_time_position`. Items whose part is missing from the library are dropped.
2208
+ */
2209
+ declare function arrangeMainTrackSeamlessly(doc: TimelineDoc): void;
2210
+ /**
2211
+ * Sync attached parts' absolute positions from their host:
2212
+ * speech.abs = host_video.abs + relative_time_position
2213
+ * caption.abs = speech.abs + caption.start_ms
2214
+ */
2215
+ declare function syncAggregatedClipsTimePosition(doc: TimelineDoc): void;
2216
+ /**
2217
+ * Reassign each speech to the video clip whose time range contains its start,
2218
+ * rebuilding `aggregations`. A speech before the first clip or after the last
2219
+ * falls back to the first / last clip respectively (FE: see §4 note — FE falls
2220
+ * back to last only; we keep the harness two-sided fallback because a speech
2221
+ * dragged before clip 0 belonging to the last clip is clearly wrong, and the FE
2222
+ * single-sided rule is an acknowledged rough edge). `relative_time_position` is
2223
+ * clamped to a non-negative integer.
2224
+ */
2225
+ declare function reassignSpeechesToVideoClipsByTime(doc: TimelineDoc): void;
2226
+ /**
2227
+ * Recompute `timeline.duration_ms` as the max end (abs + duration) across main,
2228
+ * speech, and caption lanes (BGM does not extend the timeline).
2229
+ *
2230
+ * BGM has no authoritative duration (RFC 02 / `reference/16` §0b): its effective
2231
+ * length is always the timeline total, so it is not written back here — the
2232
+ * projection derives it from `timeline.duration_ms` on read (`partDurationMs`
2233
+ * returns the timeline total for a bgm part). The empty-document 20s skeleton is
2234
+ * applied at that read step, not stored.
2235
+ */
2236
+ declare function recalculateTimelineDuration(doc: TimelineDoc): void;
2237
+ /**
2238
+ * Resolve one speech overlap by shifting the overlapping speech's host video
2239
+ * (and every clip after it) right. Returns true when one overlap was resolved;
2240
+ * callers loop until it returns false. The compared range is the speech merged
2241
+ * with its captions: start = min(speech.start, captions.start) (FE behavior),
2242
+ * end = max(speech.end, captions.end).
2243
+ */
2244
+ declare function resolveSpeechOverlapByShiftingVideos(doc: TimelineDoc): boolean;
2245
+ /**
2246
+ * Resolve speech overlaps for one cascade pass. Mirrors the authoritative FE
2247
+ * `ensureNoOverlappingClips`, which is documented to resolve AT MOST ONE overlap
2248
+ * per cascade and is invoked exactly once at every FE call site — the supported
2249
+ * ops each produce at most one new overlap. It is NOT a fixpoint loop: shifting a
2250
+ * host right also moves every speech anchored to it, so two speeches sharing a
2251
+ * host can never be separated by shifting. Looping to a "fixed point" there does
2252
+ * not converge — it accumulates the same overlap every iteration and pushes the
2253
+ * clip arbitrarily far right (e.g. a sped-up clip landing at ~287k ms instead of
2254
+ * its seamless slot). A single pass matches FE product behavior and terminates.
2255
+ */
2256
+ declare function resolveAllSpeechOverlaps(doc: TimelineDoc): void;
2257
+ /**
2258
+ * Make the main track gapless by adjusting/merging empty placeholder clips or
2259
+ * inserting new ones between real clips. Mirrors the harness four-case rule, but
2260
+ * the merge of two adjacent empty clips keeps the earlier clip (harness Case 4).
2261
+ * Requires `makeEmptyPart` to mint a placeholder part (the editor supplies an
2262
+ * id generator).
2263
+ */
2264
+ declare function fillMainTrackTimeGaps(doc: TimelineDoc, makeEmptyPart: (durationMs: number) => {
2265
+ partId: string;
2266
+ }): void;
2267
+ //#endregion
2268
+ //#region src/timeline-core/entrypoints.d.ts
2269
+ /**
2270
+ * The composed cascade the read-side projection runs (RFC 02 §7): the ordered
2271
+ * step sequence `solveVideoDocument` applies to derive every absolute position,
2272
+ * aggregation, gap filler, and the total duration from the position-only facts.
2273
+ *
2274
+ * `makeEmptyPart` mints placeholder clips for gap filling; the projection
2275
+ * supplies a bounded id generator.
2276
+ */
2277
+ type MakeEmptyPart = (durationMs: number) => {
2278
+ partId: string;
2279
+ };
2280
+ /**
2281
+ * The full solve pipeline: arrange → sync → reassign → resolve-overlap →
2282
+ * fill-gaps → recalc. The single cascade the read-side projection runs; ops
2283
+ * never call it (they write only facts — RFC 02 §7/§10).
2284
+ */
2285
+ declare function cascadeAfterVideoClipChanges(doc: TimelineDoc, makeEmptyPart: MakeEmptyPart): void;
2286
+ //#endregion
2287
+ //#region src/timeline-core/bridge.d.ts
2288
+ /**
2289
+ * Bridge between the authoritative `VideoDocument` (position-only facts) and the
2290
+ * flat `TimelineDoc` the cascade primitives solve over.
2291
+ *
2292
+ * The data flow is single-directional (RFC 02 §7/§10): ops write only facts
2293
+ * (`time_position`, parts) to `VideoDocument`; absolute time, `part_aggregations`,
2294
+ * total duration, and gap fillers are NOT stored — the projection derives them
2295
+ * on read by running the cascade. There is no write-back of solved positions.
2296
+ *
2297
+ * - `solveVideoDocument` seeds a `TimelineDoc` from each item's `time_position`,
2298
+ * runs the full cascade, and returns the derived read-view (abs / aggregations
2299
+ * / duration). It is the single solve shared by the legacy `VideoDraft`
2300
+ * projection.
2301
+ * - `ensureLaneTrack` / `findLaneTrack` locate (or mint) a secondary lane's
2302
+ * track row in the draft, so ops can write authoritative facts onto the right
2303
+ * named container (lane = container).
2304
+ */
2305
+ /** The projection-derived read-view of a `VideoDocument` (RFC 02 §7). */
2306
+ interface SolvedVideoDocument {
2307
+ absByPartId: Map<string, number>;
2308
+ aggregations: PartAggregation[];
2309
+ durationMs: number;
2310
+ partLibrary: Record<string, PartUnion>;
2311
+ }
2312
+ /**
2313
+ * Solve a `VideoDocument` (authoritative, position-only) into its derived
2314
+ * read-view: absolute time per item, `part_aggregations`, and total duration.
2315
+ * This is the read side of the single-directional flow — never written back.
2316
+ */
2317
+ declare function solveVideoDocument(document: VideoDocument): SolvedVideoDocument;
2318
+ /** The named container a secondary lane lives in. */
2319
+ type SecondaryLane = 'speech' | 'caption' | 'bgm';
2320
+ /**
2321
+ * Locate a lane's track row in the single `tracks` list by kind, minting an empty
2322
+ * row in lane-stacking order if absent (reference/17 §4: lane = `parts_kind`).
2323
+ * Ops use this to write authoritative items onto the right lane. The track id
2324
+ * mirrors the seed convention (`<kind>_track`).
2325
+ */
2326
+ declare function ensureLaneTrack(draft: VideoDocumentDraft, kind: SecondaryLane | 'video_clip'): TrackDraft;
2327
+ /** Find a secondary lane's track row without minting it. */
2328
+ declare function findLaneTrack(draft: VideoDocumentDraft, kind: SecondaryLane): TrackDraft | undefined;
2329
+ //#endregion
2330
+ //#region src/timeline-core/locate.d.ts
2331
+ /**
2332
+ * Forward positioning for write-time ops (RFC 02 §9.1) — NOT a cascade.
2333
+ *
2334
+ * The authoritative model stores only `position`; absolute time is derived at
2335
+ * read time by the projection. But two write-time ops legitimately need to turn
2336
+ * an *absolute target* into a `relative` `position` fact:
2337
+ *
2338
+ * - moving / reordering main-track video clips re-parents each affected speech
2339
+ * to the video clip its (unchanged) absolute landing now falls in (§9.1);
2340
+ * - moving a speech places it at a new absolute time, then anchors it to the
2341
+ * host clip it lands in.
2342
+ *
2343
+ * Both need one forward computation: lay out the main track from flow order +
2344
+ * effective durations, find the clip whose range contains the target ms, and
2345
+ * emit `{ mode:'anchored', anchorPartId, offsetMs }`. This reads the current
2346
+ * facts and writes one new fact — it never solves and writes back the whole
2347
+ * layout (that stays the projection's job, read-side only).
2348
+ */
2349
+ interface MainClipRange {
2350
+ partId: string;
2351
+ startMs: number;
2352
+ endMs: number;
2353
+ }
2354
+ /**
2355
+ * The flow-ordered main-track clip ranges (cumulative effective durations from
2356
+ * 0). Empty-media gap fillers are not in authoritative state, so this reflects
2357
+ * only the real clips the draft stores.
2358
+ */
2359
+ declare function mainTrackRanges(draft: VideoDocumentDraft): MainClipRange[];
2360
+ /**
2361
+ * Pick the host video clip an absolute time lands in, with the harness two-sided
2362
+ * fallback: before the first clip → first clip; after the last → last clip.
2363
+ * Returns null only when there is no clip at all (caller leaves the item as-is).
2364
+ */
2365
+ declare function hostForAbsMs(ranges: MainClipRange[], absMs: number): MainClipRange | null;
2366
+ /**
2367
+ * Build an `anchored` time position anchoring `absMs` to the host clip it lands
2368
+ * in (offset clamped to a non-negative integer). Falls back to `absolute` when
2369
+ * there is no host clip. Pair with `fallbackAbsMs = absMs` on the item.
2370
+ */
2371
+ declare function relativePositionForAbs(ranges: MainClipRange[], absMs: number): TrackItemTimePosition;
2372
+ //#endregion
2373
+ export { type Aggregation, type Attachment, type BgmPart, type CaptionPart, type CaptionStyle, type CommitOptions, type DerivedItemPosition, type DocStorageLike, IMPLEMENTED_SEMANTIC_OP_KINDS, type ImplementedSemanticOpKind, type MainClipRange, type MakeEmptyPart, MedeoHttpDocStorage, type MedeoHttpDocStorageOptions, MemoryDocStorage, type MengineAuditEntry, type MengineAuditResponse, MengineDocSession, type MengineDocSessionOptions, type MengineDocSessionUpdateEvent, type MengineDocumentVersion, type MengineEventStreamOptions, MengineHttpClient, type MengineHttpClientOptions, MengineHttpRequestError, type MenginePushResponse, type MenginePushUpdateResponse, type MengineRejectedResponse, type MengineSnapshotResponse, type MengineSseUpdateEvent, type MengineSyncResponse, type MengineUpdateMeta, MirrorVideoDocumentAdapter, type MirrorVideoDocumentOptions, type PartAggregation, type PartKind, type PartUnion, type PlannedSemanticOpKind, SchemaValidator, type SemanticDocumentAdapter, SemanticEditor, type SemanticOpInput, type SemanticOpKind, type SemanticOpName, type SnapshotReadable, type SolvedVideoDocument, type SpeechHostMap, type SpeechPart, type SpeedShift, TIMELINE_SKELETON_DURATION_MS, type Timeline, type TimelineDoc, type TimelineItem, type Track, type TrackDraft, type TrackItem, type TrackItemDraft, type TrackItemTimePosition, type TransactAudit, VIDEO_DOCUMENT_SCHEMA_VERSION, ValidationError, type VideoClipPart, type VideoDocument, type VideoDocumentDraft, type VideoDocumentMirrorSchema, type VideoDocumentSchemaVersion, VideoDocumentValidationError, type VideoDocumentValidationIssue, type VideoDocumentValidationIssueCode, type VideoDraft, type CaptionPart$1 as VideoDraftCaptionPart, type Timeline$1 as VideoDraftTimeline, type Track$1 as VideoDraftTrack, type TrackItem$1 as VideoDraftTrackItem, arrangeMainTrackSeamlessly, assertValidVideoDocument, base64ToBytes, buildSpeechHostMap, bytesToBase64, cascadeAfterVideoClipChanges, createMirrorVideoDocument, createMirrorVideoDocumentAdapter, derivePositionFromAbs, ensureLaneTrack, fillMainTrackTimeGaps, findLaneTrack, fromVideoDocument, generatePartId, getAt, hostForAbsMs, isEmptyVideoClip, isImplementedSemanticOpKind, isMap, mainTrackRanges, partDurationMs, partUnionSchema, readMainTrackItems, readMengineEventStream, readPart, readPartDurationMs, readVideoDocumentFromDraft, reassignSpeechesToVideoClipsByTime, recalculateTimelineDuration, relativePositionForAbs, resolveAllSpeechOverlaps, resolveSpeechOverlapByShiftingVideos, safeDurationMs, index_d_exports as schemas, snapshotToPlain, solveVideoDocument, syncAggregatedClipsTimePosition, toVideoDocument, validateVideoDocument, videoDocumentMirrorSchema, videoDocumentSchema };