@vosjs/studio-core 0.41.2 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -210,6 +210,12 @@ interface SpeedParams {
210
210
  scrollMin: number;
211
211
  /** Rate applied to scroll runs. */
212
212
  scrollRate: number;
213
+ /**
214
+ * Rate applied to a scripted `wait` or a hover's dwell in which nothing
215
+ * moved on screen: the flow's own pauses, read from the step timeline
216
+ * with the digest's activity as the witness.
217
+ */
218
+ stillRate: number;
213
219
  }
214
220
  /** Conservative defaults: only stretches nobody wants to watch in real time. */
215
221
  declare const DEFAULT_SPEED_PARAMS: SpeedParams;
@@ -227,6 +233,12 @@ declare function planAutoSpeed(track: readonly CursorEvent[], opts: {
227
233
  params?: Partial<SpeedParams>;
228
234
  /** Per-SOURCE-second motion bins (0..1) when a digest measured them. */
229
235
  activity?: readonly number[] | null;
236
+ /**
237
+ * The step timeline of a scripted take (`meta.steps`): the flow's own
238
+ * pauses become still spans when the activity witness says nothing
239
+ * moved in them.
240
+ */
241
+ steps?: readonly StepSpan[] | null;
230
242
  }): SpeedSpan[];
231
243
  /**
232
244
  * Scroll runs as [start, last] seconds — the same grouping the speed planner
@@ -560,6 +572,14 @@ interface StepSpan {
560
572
  * (a pin then reads the presses inside the step's window).
561
573
  */
562
574
  rect?: Rect;
575
+ /** The page the step left, origin and path only (never a query or a hash). */
576
+ url?: string;
577
+ /**
578
+ * The words of the element the step's `read` named, once the step had
579
+ * settled: trimmed, whitespace collapsed, at most 2 KB. The take's own
580
+ * payload, which a layer quotes instead of replaying the flow.
581
+ */
582
+ text?: string;
563
583
  }
564
584
  /** Everything the capture extension hands off to the studio. */
565
585
  interface RecordingArtifact {
@@ -2599,6 +2619,11 @@ interface PlanOptions {
2599
2619
  typingHold?: number;
2600
2620
  /** level floor for typing spans (a wide field still reads punchier). */
2601
2621
  typingMinLevel?: number;
2622
+ /**
2623
+ * The step timeline of a scripted take (`meta.steps`): one zoom per step
2624
+ * where its presses share a window, never one per press.
2625
+ */
2626
+ steps?: readonly StepSpan[] | null;
2602
2627
  }
2603
2628
  /** One press (or ping) in seconds, with the target element when known. */
2604
2629
  interface Click {
@@ -2636,7 +2661,99 @@ interface PlanTiltOptions {
2636
2661
  */
2637
2662
  declare function planAutoTilt(zoom: readonly ZoomSpan[], segments: Segment[], options: PlanTiltOptions): TiltSpan[];
2638
2663
 
2639
- type MomentKind = 'head' | 'tail' | 'click' | 'typing' | 'scroll' | 'dwell' | 'idle' | 'scene';
2664
+ /**
2665
+ * The digest sees consequences: what a click or a typing run DID to the
2666
+ * page. A press is one frame; the page's answer comes after it, a dialog
2667
+ * opening, a 200 landing, a graph re-laying out, a card vanishing. The
2668
+ * region that changed in the seconds after the step is a moment a cut can
2669
+ * hold on, and a zoom can frame.
2670
+ *
2671
+ * Pure: the browser samples luma grids (the cli's digest page) at the
2672
+ * step's end and a few beats after it; this module finds the changed
2673
+ * region and keeps the sample that changed most. Two things keep it
2674
+ * honest on a page that moves on its own. A sample a beat BEFORE the step
2675
+ * gives each cell its own drift, and only what moved past that drift
2676
+ * counts (`ambientDrift`). And a change is judged by the blocks it FILLS,
2677
+ * not by the cells it touches: a silk or a shimmer flickers cells all over
2678
+ * the frame and fills no block, while a dialog, a recoloured pane or a
2679
+ * status line fills its own.
2680
+ */
2681
+
2682
+ /** A luma grid of the frame at one instant, row-major, `w × h` cells. */
2683
+ interface LumaGrid {
2684
+ w: number;
2685
+ h: number;
2686
+ luma: ArrayLike<number>;
2687
+ }
2688
+ /** What changed between two grids: the largest cluster of blocks that filled, and its box. */
2689
+ interface ChangedRegion {
2690
+ /** The cluster's share of the frame's blocks, 0..1. */
2691
+ fraction: number;
2692
+ /** The cluster's bounding box, normalized frame fractions. */
2693
+ rect: NormRect;
2694
+ }
2695
+ /** The grid the browser samples: coarse enough that a cursor is one cell, a dialog a block. */
2696
+ declare const CHANGE_GRID: {
2697
+ readonly w: 128;
2698
+ readonly h: 72;
2699
+ };
2700
+ /** The floor a change must clear to be named: 2% of the frame's blocks. */
2701
+ declare const CHANGE_FLOOR = 0.02;
2702
+ /** How long after a seed the digest looks for its consequence, source seconds. */
2703
+ declare const CHANGE_OFFSETS: readonly [0.3, 0.8, 1.5, 2.5];
2704
+ /** How long before a seed the ambient sample is taken, source seconds. */
2705
+ declare const CHANGE_LEAD = 0.5;
2706
+ /**
2707
+ * The changed region between two grids, or null when nothing (or fewer
2708
+ * than `minBlocks`) filled. `delta` is the luma step a cell must move by,
2709
+ * the digest's own motion delta. `ambient`, when given, is what each cell
2710
+ * moves by on its own (see `ambientDrift`) and how long after `before`
2711
+ * this grid was taken: a cell counts only when it moved past what its own
2712
+ * drift explains over that time. A block is `block` cells a side (the
2713
+ * grid's sixteenth by default: 16 × 9 blocks over the sampled grid).
2714
+ */
2715
+ declare function changedRegion(before: LumaGrid, after: LumaGrid, delta: number, minBlocks?: number, ambient?: {
2716
+ rate: ArrayLike<number>;
2717
+ dt: number;
2718
+ } | null, block?: number): ChangedRegion | null;
2719
+ /**
2720
+ * How much each cell moves on its own, per second: a page whose background
2721
+ * animates (a silk, a video, a shimmer) changes everywhere every frame, and
2722
+ * the consequence of a press would drown in it. Two grids taken BEFORE the
2723
+ * seed, `lead` seconds apart, give each cell its drift; `changedRegion`
2724
+ * then counts a cell only when it moved past what that drift explains.
2725
+ */
2726
+ declare function ambientDrift(earlier: LumaGrid, before: LumaGrid, lead: number): number[] | null;
2727
+ /** One sample after a seed: when it was taken and what had changed by then. */
2728
+ interface ChangeSample {
2729
+ /** Source seconds after the seed. */
2730
+ dt: number;
2731
+ region: ChangedRegion | null;
2732
+ }
2733
+ /** A consequence the digest will name: a moment's seed and the region it changed. */
2734
+ interface ChangeWitness {
2735
+ /** The seed moment's id (a click or a typing run). */
2736
+ seed: string;
2737
+ /** Source second the seed ended at: the frame the change is measured from. */
2738
+ at: number;
2739
+ /** Source second of the sample that had changed most. */
2740
+ peakAt: number;
2741
+ fraction: number;
2742
+ rect: NormRect;
2743
+ }
2744
+ /**
2745
+ * The witness for one seed: the sample that changed most, when it clears
2746
+ * the floor. A consequence that never clears it is no moment, and a seed
2747
+ * that changed everything (a navigation) is the scene detector's.
2748
+ */
2749
+ declare function pickChange(seed: {
2750
+ id: string;
2751
+ at: number;
2752
+ }, samples: readonly ChangeSample[], floor?: number): ChangeWitness | null;
2753
+
2754
+ type MomentKind = 'head' | 'tail' | 'click' | 'typing'
2755
+ /** What a click or a typing run DID: the region of the page that answered it. */
2756
+ | 'changed' | 'scroll' | 'dwell' | 'idle' | 'scene';
2640
2757
  /** A rect in normalized [0..1] frame fractions (the zoom cx/cy convention). */
2641
2758
  interface NormRect {
2642
2759
  x: number;
@@ -2686,6 +2803,8 @@ interface Moment {
2686
2803
  };
2687
2804
  /** Transcript text over the window, when a transcript was merged. */
2688
2805
  said: string | null;
2806
+ /** A `changed` moment: the click or typing moment it answers. */
2807
+ seed?: string;
2689
2808
  }
2690
2809
  interface DigestPlan {
2691
2810
  zoom: ZoomSpan[];
@@ -2706,6 +2825,11 @@ interface MomentsOptions {
2706
2825
  transcript?: readonly TranscriptSegment[] | null;
2707
2826
  /** Idle threshold (seconds); defaults to the speed planner's. */
2708
2827
  idleMin?: number;
2828
+ /**
2829
+ * What the clicks and typing runs did to the page, measured by the
2830
+ * digest's decode pass (see changes.ts): each becomes a `changed` moment.
2831
+ */
2832
+ changes?: readonly ChangeWitness[];
2709
2833
  }
2710
2834
  /**
2711
2835
  * The three planners, run fresh under the doc's own style — the proposals.
@@ -3292,6 +3416,33 @@ interface DigestTakeFacts {
3292
3416
  /** Masks the script asked for, with how many elements each reached. */
3293
3417
  masks: NonNullable<RecordingMeta['masks']>;
3294
3418
  }
3419
+ /**
3420
+ * One script step as the digest tells it: when it ran in the footage and
3421
+ * in the cut, the page it left and the words its `read` kept, so an agent
3422
+ * places a layer by the table and quotes the take by the text.
3423
+ */
3424
+ interface DigestStep {
3425
+ step: number;
3426
+ id: string | null;
3427
+ do: string;
3428
+ selector: string | null;
3429
+ /** Footage seconds the step occupied. */
3430
+ source: {
3431
+ in: number;
3432
+ out: number;
3433
+ };
3434
+ /** The same window in the cut, null when trimmed away. */
3435
+ output: {
3436
+ in: number;
3437
+ out: number;
3438
+ } | null;
3439
+ skipped: boolean;
3440
+ navigated: boolean;
3441
+ /** The page after the step, origin and path; null on a human recording. */
3442
+ url: string | null;
3443
+ /** The words the step's `read` kept, or null. */
3444
+ text: string | null;
3445
+ }
3295
3446
  interface Digest {
3296
3447
  digestVersion: number;
3297
3448
  take: DigestTakeFacts;
@@ -3306,6 +3457,8 @@ interface Digest {
3306
3457
  crop: string | null;
3307
3458
  box: PxRect | null;
3308
3459
  })[];
3460
+ /** The script's steps (a CLI take; empty for a human recording). */
3461
+ steps: DigestStep[];
3309
3462
  activity: number[] | null;
3310
3463
  plan: DigestPlan;
3311
3464
  doc: {
@@ -5228,4 +5381,4 @@ declare function rejectSpan(rejected: RejectedSpan[] | undefined, lane: Rejected
5228
5381
  anchor?: StepAnchor;
5229
5382
  }, note?: string): RejectedSpan;
5230
5383
 
5231
- export { ASPECT_RATIOS, AUDIO_PLAN_STEP, type AnchorKind, type Anim, type AnimKind, type AnimSide, type AnimStep, type AppliedTemplate, type ApplyTemplateOptions, type AspectRatioOption, type AudioClip, type AudioPlanPoint, type AudioPlanTrack, BACKGROUND_Z, BASE_FRAME_STYLE, type Backdrop, type BackgroundMedia, type BakedText3dAsset, type BakedText3dMaterial, type BrowserBarStyle, type BuildDigestInput, CALLOUT_DEFAULT_BODY_PX, CALLOUT_SHAPES, CALLOUT_TYPE_RATIO, CAMERA_CENTRE_RAMP, CAMERA_COVER_MIN, CAMERA_FAR, CAMERA_NEAR, CAM_CHAIN_GAP, CAM_EASE, CAM_PAN, CAM_PAN_EASE, CAM_RAMP_IN, CAM_RAMP_OUT, CAM_SIZE_MAX, CAM_SIZE_MIN, CAM_SPAN_MIN, CAPTURE_COVERAGE_MIN, CARD_EDGE_OVERDRAW, CARD_ENTER_KINDS, CARD_EXIT_KINDS, CARD_FADE_SECONDS, CARD_FOV, CARD_Z, CHANNEL_SPECS_HASH, CHANNEL_SPECS_VERIFIED, CLICK_FX_INTENSITY, CLICK_FX_PRE, CLICK_HIGHLIGHT_FADE, CLICK_HOLD_MS, CLICK_PAIR_MAX, CLICK_PULSE_DUR, CLICK_RECT_MAX_FRAC, CLICK_RIPPLE_DUR, CLICK_SYNTH_RELEASE, CLUSTER_FILL, CROP_MAX_PX, CROP_MIN_FRAC, CROP_MIN_PX, CROP_PAD, CURSOR_IDLE_EPS_FRAC, CURSOR_IDLE_FADE_IN, CURSOR_IDLE_FADE_OUT, CURSOR_IDLE_HOLD, type CalloutBox, type CalloutClipOptions, type CalloutLook, type CalloutRegister, type CalloutShape, type CalloutWords, type CamBubbleRect, type CamPoseSpan, type CamStyle, type CameraModel, type CaptureNormalization, type CardLayout, type ClickFxStyle, type CopiedLayout, type CopyLayoutOptions, type CopyStyleOptions, type CursorEvent, type CursorFadeKey, type CursorIdleOptions, type CursorStyle, type CursorTrack, DARK_GROUND, DEFAULT_BROWSER_BAR, DEFAULT_CAM_POSE, DEFAULT_CAM_STYLE, DEFAULT_CLICK_FX, DEFAULT_CURSOR_STYLE, DEFAULT_DUCK, DEFAULT_FRAME_STYLE, DEFAULT_SPEED_PARAMS, DEFAULT_TILT_POSE, DEFAULT_ZOOM_LEVEL, DEFAULT_ZOOM_STYLE, DESIGN_FRAME_WIDTH, DESTINATIONS, DIGEST_CROP_MAX, DIGEST_FULL_MAX, DIGEST_VERSION, DOC_SCHEMA_VERSION, DRAG_FIT_LEVEL, DRAG_MIN_S, DRAG_MIN_TRAVEL_FRAC, type Destination, type Digest, type DigestImageRef, type DigestPlan, type DigestTakeFacts, type DocSegment, type Drag, type DuckOptions, END_CARD_FROM, END_CARD_RECEDE, END_CARD_SECONDS, ENTRANCE_SECONDS, EXPORT_RESOLUTION_OPTIONS, EXPORT_SHORT_EDGE, type EndCard, type EnvelopePoint, type ExportResolution, type ExtractClickOptions, FOLLOW_RECENTER, FOLLOW_SAFE_RATIO, FOOTAGE_FRAME_STYLE, FRAME_BORDER_COLOR_DEFAULT, FRAME_BORDER_DEFAULT, FRAME_BORDER_WIDTH_DEFAULT, FREEZE_DEFAULT_SECONDS, FREEZE_SECONDS_MAX, FREEZE_SECONDS_MIN, type FocusBounds, type FollowEvent, type FollowOptions, type FrameEntrance, type FrameGeometry, type FrameStyle, type FreezeExtent, type FreezeSpan, HOLD_DRIFT_MAX, HOUSE_GRADIENT, HOUSE_REGISTER, HTML_LAYER_FOREIGN_FIELDS, HTML_LAYER_MAX_BYTES, HTML_LAYER_SVG_CODE, HTML_LAYER_WARN_BYTES, HTML_STARTERS, type HtmlLayerFace, type HtmlLayerInlineFace, type HtmlLayerPayload, type HtmlLayerProblem, type HtmlLayerProblemCode, type HtmlLayerSource, type HtmlOverlayClip, type HtmlOverlayFont, type HtmlStarter, IDLE_KINDS, type IdleKind, type IngestOptions, LAYER_CARD_SHADOW, LAYOUT_FRAME_FIELDS, LOOK_KINDS, type LayerCard, type LayoutParts, type Look, type LookBrand, type LookKind, type LookPlacement, type LowerProgramOptions, type LoweredAudioClip, type LoweredClick, type LoweredComposition, type LoweredZoomSpan, MEDIA_ENTER_KINDS, MEDIA_EXIT_KINDS, MEDIA_FRAME_KEYS, MEDIA_KEY_PREFIX, MINIMAL_BAR_THEMES, MOTION_DELTA, MOTION_EASE, type Media, type MediaFrame, type MediaFrameKey, type MediaOverlayClip, type MediaSegment, type MicRms, type MinimalBarTheme, type Moment, type MomentKind, type MomentsOptions, type MotionKey, type MotionPose, type MotionPose3D, type MusicBedInput, type NormRect, OBJECT_DEFAULT_SCALE, OVERLAY_FONT_FACES, OVERLAY_LINE_HEIGHT, OVERLAY_MEDIA_DEFAULT_RADIUS, OVERLAY_MEDIA_DEFAULT_WIDTH, OVERLAY_MIN_DURATION, OVERLAY_SCALE_MAX, OVERLAY_SIZE_MAX, OVERLAY_SIZE_MIN, OVERLAY_TRANSITION_DUR, OVERLAY_Z, type ObjectAnimation, type ObjectAsset, type ObjectClip, type ObjectPrimitiveShape, type OverlayClip, type OverlayFontFace, type OverlayKind, type OverlayPin, type OverlayPresetStyle, type OverlayRect, type OverlayTransform, type OverlayTransition, PIN_GAP, PIN_MARGIN, PLATE_GROUND, PLAYBACK_ACTIVITY, PRIMARY_MEDIA, PROGRAM_RETIME, PUMP_REST, type PinCandidate, type PinCarry, type PinMark, type PinPlacement, type PinPlacementInput, type PinReferent, type PinSide, type PinSideResolved, type PlanOptions, type PlanTiltOptions, type PlaneSize, type ProgramAnchorDoc, type ProgramTweenEdit, type ProjectDoc, type PxRect, REJECT_OVERLAP, REST_TILT_ID, RETIRED_ZOOM_STYLES, type RecordingArtifact, type RecordingMeta, type Rect, type RejectedLane, type RejectedSpan, type ResolvedOverlayBox, type ResolvedOverlayStyle, type Rgb, SCENE_MOTION, SCENE_QUIET, SEGMENT_ENTER_KINDS, SEGMENT_EXIT_KINDS, SPEED_RATE_MAX, SPEED_RATE_MIN, SPEED_SPAN_MIN, STAGE_CLIP_PREFIX, STILL_DEFAULT, STUDIO_ENTRY_ID, STYLE_FIELDS, type SegmentExtent, type SmoothOptions, type SmoothPoint, type SpeedParams, type SpeedSpan, type StepAnchor, type StepSpan, type StudioAudioPlan, type StudioDoc, type StudioEntry, type StyleField, type StyledText, TEXT3D_DEPTH_DEFAULT, TEXT3D_DEPTH_MAX, TEXT3D_DEPTH_MIN, TEXT_ENTER_KINDS, TEXT_EXIT_KINDS, TEXT_PRESETS, TILT_AUTO_DEAD_ZONE, TILT_AUTO_MIN, TILT_CHAIN_GAP, TILT_DEG_MAX, TILT_EASE, TILT_INTENSITY_MAX, TILT_PAN, TILT_PAN_EASE, TILT_RAMP_IN, TILT_RAMP_OUT, TILT_SPAN_MIN, TILT_UI_DEG_MAX, TRANSITION_SECONDS, TRANSITION_SECONDS_MAX, TRANSITION_SECONDS_MIN, TRANSITION_SPEED_MULT, type TemplateAnchor, type Text3dMaterial, type TextFxDirection, type TextFxKind, type TextFxSpec, type TextFxUnit, type TextOverlayBox, type TextOverlayClip, type TextOverlayPreset, type TextOverlayStroke, type TiltPersonality, type TiltSpan, type TiltStyleName, type TraceFileKind, type TraceOptions, type TraceResult, type TranscriptSegment, type Transition, type TransitionKind, type TransitionMove, type TransitionOut, type TransitionSpeed, WINDOW_FOCUS_MIN, ZOOM_CHAIN_GAP, ZOOM_EASE, ZOOM_LEVELS, ZOOM_LEVEL_MAX, ZOOM_LEVEL_MIN, ZOOM_PAN, ZOOM_PAN_EASE, ZOOM_RAMP_IN, ZOOM_RAMP_IN_OVERLAP, ZOOM_RAMP_OUT, ZOOM_SPAN_MIN, ZOOM_STYLES, ZOOM_STYLE_OPTIONS, type ZipEntry, type ZoomScreen, type ZoomSpan, type ZoomStyleName, type ZoomStyleParams, type ZoomView, type ZoomWindow, anchorKindOf, anchorSourceDuration, animStep, applyLook, applyTemplate, aspectRatioValue, audioLane, backdropMedia, boldWeightFor, buildDigest, calloutBox, calloutClip, calloutGroundOf, calloutLook, calloutSource, camBubbleRect, camBubbleRectAt, camLane, camMoveLane, camRectFromPose, camRestPose, camTrackFromDoc, cameraCentring, cameraModel, cardEnter, cardExit, cardFields, cardInset, cardPointToScreen, cardPoseTrack, clampCamFrac, clampCamSize, clampFocus, clampFreezeSeconds, clampSpeedRate, clampTiltDeg, clampZoomLevel, clipEnvelope, clipLength, clipsFrom, computeCardLayout, computeMicRms, computePeaks, copyLayout, copyStyle, cropBox, cssFamilies, cssWeights, cursorFromCsv, cursorFromPlaywrightTrace, cursorFromRecords, cursorFromTrace, cursorIdleFade, deltaE, deriveViewportCrop, destinationById, destinationsForChannel, docCardLayout, docFreezes, docMediaList, docOutputDuration, docRestTime, docStillTime, docToCropSpace, docToFullSpace, docTransitions, docZoomTrack, dragsFromTrack, dropTemplate, duckCurve, effectiveSegments, endCardClips, enterKey, enterOf, entranceAt, entranceEnd, entranceSeconds, envelopeAt, envelopeValueAt, exitKey, exitOf, exitSeconds, expandEndCard, expectedFrameSize, exportSizeFor, extractClicks, findStep, focusBounds, focusForViewportCentre, followFocusEvents, frameGeometry, freezeLane, freezeOutputExtents, hexToRgb, hexToRgbTriplet, houseLook, htmlLayerBleed, htmlLayerBleedFor, htmlLayerFaces, htmlLayerKey, htmlLayerLabel, htmlLayerPayload, htmlLayerPictureBox, htmlLayerProblems, htmlLayerSvg, htmlLayerText, htmlLayerUnhostedFamilies, htmlLayerWidth, htmlStarterClip, idleGaps, idleKey, isKeyedOverlay, isLookKind, isLoweredRecording, isMusicBed, isPlayback, isProgramDoc, isRecordingDoc, isRejected, isStageClip, isUnitStep, lastLayerEnd, layerMedia, layoutOf, levelForFocusFraction, liveZoomStyleName, lookFromBrand, lookKindForGround, lowerProgramDoc, lowerStudioDoc, lowerToComposition, marksEmphasis, mediaAtOutput, mediaDuration, mediaFrame, mediaKey, mediaRef, mediaRefId, mediaSource, micLane, migrateHostedDoc, migrateMotion, migrateText, migrateTextClip, migrateZoomStyle, momentsFromDoc, motionTrack, musicBedClip, nextMediaId, normalizeCaptureSpace, objectMotionBase, objectMotionPoseAt, objectsLane, outputDurationOf, outputEnd, outputRangeToSource, overlapFraction, overlayFaceFor, overlayFontFaces, overlayFontString, overlayFxSpec, overlayHit, overlayLines, overlayMotionBase, overlayMotionPoseAt, overlayPinPoseAt, overlayPlainText, overlayRect, overlaysLane, ownFreezes, pageDisplayUrl, parseCssColour, parsePoseId, pickStyle, pinBox, pinCandidates, pinPlacement, pinRectOnScreen, pinReferent, pinReferentAt, placeFreezes, planAutoSpeed, planAutoTilt, planAutoZoom, planForDigest, planeSizeAtDepth, platformBarKind, playwrightTraceEntry, programDuration, programStillTime, projectFromArtifact, pumpFreeChainGap, ratedSegments, recommendedExportResolution, refillAudioBeds, rejectSpan, relativeLuminance, removeSourceRange, removeSpeedInRange, resolveExportSize, resolveOverlayBox, resolveOverlayStyle, resolvePins, resolveText3dAsset, resolveZoomStyle, restLeanOf, restSpansThroughTransitions, rgbToHex, sameMedia, sceneChanges, scrollRuns, segmentOutputExtents, outputLen as segmentOutputLen, segmentStarts, setSpeedInRange, slideSide, smoothCursor, snapRunWeight, spanOutputExtent, speedLane, studioAudioPlan, studioEntry, studioLayerData, styledTextOf, textRunsOf, tiltLane, tiltTrackFromDoc, traceKindOf, transitionMult, transitionSeconds, unionFitLevel, unionRect, videoLane, voiceKey, wellFormedXml, withBackdrop, withFreezes, withHolds, withoutRejected, wrapProgramLength, zipEntries, zipEntryBytes, zoomCoversRect, zoomLane, zoomSpanForRange, zoomTrackFromDoc, zoomView, zoomViewport, zoomWindow };
5384
+ export { ASPECT_RATIOS, AUDIO_PLAN_STEP, type AnchorKind, type Anim, type AnimKind, type AnimSide, type AnimStep, type AppliedTemplate, type ApplyTemplateOptions, type AspectRatioOption, type AudioClip, type AudioPlanPoint, type AudioPlanTrack, BACKGROUND_Z, BASE_FRAME_STYLE, type Backdrop, type BackgroundMedia, type BakedText3dAsset, type BakedText3dMaterial, type BrowserBarStyle, type BuildDigestInput, CALLOUT_DEFAULT_BODY_PX, CALLOUT_SHAPES, CALLOUT_TYPE_RATIO, CAMERA_CENTRE_RAMP, CAMERA_COVER_MIN, CAMERA_FAR, CAMERA_NEAR, CAM_CHAIN_GAP, CAM_EASE, CAM_PAN, CAM_PAN_EASE, CAM_RAMP_IN, CAM_RAMP_OUT, CAM_SIZE_MAX, CAM_SIZE_MIN, CAM_SPAN_MIN, CAPTURE_COVERAGE_MIN, CARD_EDGE_OVERDRAW, CARD_ENTER_KINDS, CARD_EXIT_KINDS, CARD_FADE_SECONDS, CARD_FOV, CARD_Z, CHANGE_FLOOR, CHANGE_GRID, CHANGE_LEAD, CHANGE_OFFSETS, CHANNEL_SPECS_HASH, CHANNEL_SPECS_VERIFIED, CLICK_FX_INTENSITY, CLICK_FX_PRE, CLICK_HIGHLIGHT_FADE, CLICK_HOLD_MS, CLICK_PAIR_MAX, CLICK_PULSE_DUR, CLICK_RECT_MAX_FRAC, CLICK_RIPPLE_DUR, CLICK_SYNTH_RELEASE, CLUSTER_FILL, CROP_MAX_PX, CROP_MIN_FRAC, CROP_MIN_PX, CROP_PAD, CURSOR_IDLE_EPS_FRAC, CURSOR_IDLE_FADE_IN, CURSOR_IDLE_FADE_OUT, CURSOR_IDLE_HOLD, type CalloutBox, type CalloutClipOptions, type CalloutLook, type CalloutRegister, type CalloutShape, type CalloutWords, type CamBubbleRect, type CamPoseSpan, type CamStyle, type CameraModel, type CaptureNormalization, type CardLayout, type ChangeSample, type ChangeWitness, type ChangedRegion, type ClickFxStyle, type CopiedLayout, type CopyLayoutOptions, type CopyStyleOptions, type CursorEvent, type CursorFadeKey, type CursorIdleOptions, type CursorStyle, type CursorTrack, DARK_GROUND, DEFAULT_BROWSER_BAR, DEFAULT_CAM_POSE, DEFAULT_CAM_STYLE, DEFAULT_CLICK_FX, DEFAULT_CURSOR_STYLE, DEFAULT_DUCK, DEFAULT_FRAME_STYLE, DEFAULT_SPEED_PARAMS, DEFAULT_TILT_POSE, DEFAULT_ZOOM_LEVEL, DEFAULT_ZOOM_STYLE, DESIGN_FRAME_WIDTH, DESTINATIONS, DIGEST_CROP_MAX, DIGEST_FULL_MAX, DIGEST_VERSION, DOC_SCHEMA_VERSION, DRAG_FIT_LEVEL, DRAG_MIN_S, DRAG_MIN_TRAVEL_FRAC, type Destination, type Digest, type DigestImageRef, type DigestPlan, type DigestTakeFacts, type DocSegment, type Drag, type DuckOptions, END_CARD_FROM, END_CARD_RECEDE, END_CARD_SECONDS, ENTRANCE_SECONDS, EXPORT_RESOLUTION_OPTIONS, EXPORT_SHORT_EDGE, type EndCard, type EnvelopePoint, type ExportResolution, type ExtractClickOptions, FOLLOW_RECENTER, FOLLOW_SAFE_RATIO, FOOTAGE_FRAME_STYLE, FRAME_BORDER_COLOR_DEFAULT, FRAME_BORDER_DEFAULT, FRAME_BORDER_WIDTH_DEFAULT, FREEZE_DEFAULT_SECONDS, FREEZE_SECONDS_MAX, FREEZE_SECONDS_MIN, type FocusBounds, type FollowEvent, type FollowOptions, type FrameEntrance, type FrameGeometry, type FrameStyle, type FreezeExtent, type FreezeSpan, HOLD_DRIFT_MAX, HOUSE_GRADIENT, HOUSE_REGISTER, HTML_LAYER_FOREIGN_FIELDS, HTML_LAYER_MAX_BYTES, HTML_LAYER_SVG_CODE, HTML_LAYER_WARN_BYTES, HTML_STARTERS, type HtmlLayerFace, type HtmlLayerInlineFace, type HtmlLayerPayload, type HtmlLayerProblem, type HtmlLayerProblemCode, type HtmlLayerSource, type HtmlOverlayClip, type HtmlOverlayFont, type HtmlStarter, IDLE_KINDS, type IdleKind, type IngestOptions, LAYER_CARD_SHADOW, LAYOUT_FRAME_FIELDS, LOOK_KINDS, type LayerCard, type LayoutParts, type Look, type LookBrand, type LookKind, type LookPlacement, type LowerProgramOptions, type LoweredAudioClip, type LoweredClick, type LoweredComposition, type LoweredZoomSpan, type LumaGrid, MEDIA_ENTER_KINDS, MEDIA_EXIT_KINDS, MEDIA_FRAME_KEYS, MEDIA_KEY_PREFIX, MINIMAL_BAR_THEMES, MOTION_DELTA, MOTION_EASE, type Media, type MediaFrame, type MediaFrameKey, type MediaOverlayClip, type MediaSegment, type MicRms, type MinimalBarTheme, type Moment, type MomentKind, type MomentsOptions, type MotionKey, type MotionPose, type MotionPose3D, type MusicBedInput, type NormRect, OBJECT_DEFAULT_SCALE, OVERLAY_FONT_FACES, OVERLAY_LINE_HEIGHT, OVERLAY_MEDIA_DEFAULT_RADIUS, OVERLAY_MEDIA_DEFAULT_WIDTH, OVERLAY_MIN_DURATION, OVERLAY_SCALE_MAX, OVERLAY_SIZE_MAX, OVERLAY_SIZE_MIN, OVERLAY_TRANSITION_DUR, OVERLAY_Z, type ObjectAnimation, type ObjectAsset, type ObjectClip, type ObjectPrimitiveShape, type OverlayClip, type OverlayFontFace, type OverlayKind, type OverlayPin, type OverlayPresetStyle, type OverlayRect, type OverlayTransform, type OverlayTransition, PIN_GAP, PIN_MARGIN, PLATE_GROUND, PLAYBACK_ACTIVITY, PRIMARY_MEDIA, PROGRAM_RETIME, PUMP_REST, type PinCandidate, type PinCarry, type PinMark, type PinPlacement, type PinPlacementInput, type PinReferent, type PinSide, type PinSideResolved, type PlanOptions, type PlanTiltOptions, type PlaneSize, type ProgramAnchorDoc, type ProgramTweenEdit, type ProjectDoc, type PxRect, REJECT_OVERLAP, REST_TILT_ID, RETIRED_ZOOM_STYLES, type RecordingArtifact, type RecordingMeta, type Rect, type RejectedLane, type RejectedSpan, type ResolvedOverlayBox, type ResolvedOverlayStyle, type Rgb, SCENE_MOTION, SCENE_QUIET, SEGMENT_ENTER_KINDS, SEGMENT_EXIT_KINDS, SPEED_RATE_MAX, SPEED_RATE_MIN, SPEED_SPAN_MIN, STAGE_CLIP_PREFIX, STILL_DEFAULT, STUDIO_ENTRY_ID, STYLE_FIELDS, type SegmentExtent, type SmoothOptions, type SmoothPoint, type SpeedParams, type SpeedSpan, type StepAnchor, type StepSpan, type StudioAudioPlan, type StudioDoc, type StudioEntry, type StyleField, type StyledText, TEXT3D_DEPTH_DEFAULT, TEXT3D_DEPTH_MAX, TEXT3D_DEPTH_MIN, TEXT_ENTER_KINDS, TEXT_EXIT_KINDS, TEXT_PRESETS, TILT_AUTO_DEAD_ZONE, TILT_AUTO_MIN, TILT_CHAIN_GAP, TILT_DEG_MAX, TILT_EASE, TILT_INTENSITY_MAX, TILT_PAN, TILT_PAN_EASE, TILT_RAMP_IN, TILT_RAMP_OUT, TILT_SPAN_MIN, TILT_UI_DEG_MAX, TRANSITION_SECONDS, TRANSITION_SECONDS_MAX, TRANSITION_SECONDS_MIN, TRANSITION_SPEED_MULT, type TemplateAnchor, type Text3dMaterial, type TextFxDirection, type TextFxKind, type TextFxSpec, type TextFxUnit, type TextOverlayBox, type TextOverlayClip, type TextOverlayPreset, type TextOverlayStroke, type TiltPersonality, type TiltSpan, type TiltStyleName, type TraceFileKind, type TraceOptions, type TraceResult, type TranscriptSegment, type Transition, type TransitionKind, type TransitionMove, type TransitionOut, type TransitionSpeed, WINDOW_FOCUS_MIN, ZOOM_CHAIN_GAP, ZOOM_EASE, ZOOM_LEVELS, ZOOM_LEVEL_MAX, ZOOM_LEVEL_MIN, ZOOM_PAN, ZOOM_PAN_EASE, ZOOM_RAMP_IN, ZOOM_RAMP_IN_OVERLAP, ZOOM_RAMP_OUT, ZOOM_SPAN_MIN, ZOOM_STYLES, ZOOM_STYLE_OPTIONS, type ZipEntry, type ZoomScreen, type ZoomSpan, type ZoomStyleName, type ZoomStyleParams, type ZoomView, type ZoomWindow, ambientDrift, anchorKindOf, anchorSourceDuration, animStep, applyLook, applyTemplate, aspectRatioValue, audioLane, backdropMedia, boldWeightFor, buildDigest, calloutBox, calloutClip, calloutGroundOf, calloutLook, calloutSource, camBubbleRect, camBubbleRectAt, camLane, camMoveLane, camRectFromPose, camRestPose, camTrackFromDoc, cameraCentring, cameraModel, cardEnter, cardExit, cardFields, cardInset, cardPointToScreen, cardPoseTrack, changedRegion, clampCamFrac, clampCamSize, clampFocus, clampFreezeSeconds, clampSpeedRate, clampTiltDeg, clampZoomLevel, clipEnvelope, clipLength, clipsFrom, computeCardLayout, computeMicRms, computePeaks, copyLayout, copyStyle, cropBox, cssFamilies, cssWeights, cursorFromCsv, cursorFromPlaywrightTrace, cursorFromRecords, cursorFromTrace, cursorIdleFade, deltaE, deriveViewportCrop, destinationById, destinationsForChannel, docCardLayout, docFreezes, docMediaList, docOutputDuration, docRestTime, docStillTime, docToCropSpace, docToFullSpace, docTransitions, docZoomTrack, dragsFromTrack, dropTemplate, duckCurve, effectiveSegments, endCardClips, enterKey, enterOf, entranceAt, entranceEnd, entranceSeconds, envelopeAt, envelopeValueAt, exitKey, exitOf, exitSeconds, expandEndCard, expectedFrameSize, exportSizeFor, extractClicks, findStep, focusBounds, focusForViewportCentre, followFocusEvents, frameGeometry, freezeLane, freezeOutputExtents, hexToRgb, hexToRgbTriplet, houseLook, htmlLayerBleed, htmlLayerBleedFor, htmlLayerFaces, htmlLayerKey, htmlLayerLabel, htmlLayerPayload, htmlLayerPictureBox, htmlLayerProblems, htmlLayerSvg, htmlLayerText, htmlLayerUnhostedFamilies, htmlLayerWidth, htmlStarterClip, idleGaps, idleKey, isKeyedOverlay, isLookKind, isLoweredRecording, isMusicBed, isPlayback, isProgramDoc, isRecordingDoc, isRejected, isStageClip, isUnitStep, lastLayerEnd, layerMedia, layoutOf, levelForFocusFraction, liveZoomStyleName, lookFromBrand, lookKindForGround, lowerProgramDoc, lowerStudioDoc, lowerToComposition, marksEmphasis, mediaAtOutput, mediaDuration, mediaFrame, mediaKey, mediaRef, mediaRefId, mediaSource, micLane, migrateHostedDoc, migrateMotion, migrateText, migrateTextClip, migrateZoomStyle, momentsFromDoc, motionTrack, musicBedClip, nextMediaId, normalizeCaptureSpace, objectMotionBase, objectMotionPoseAt, objectsLane, outputDurationOf, outputEnd, outputRangeToSource, overlapFraction, overlayFaceFor, overlayFontFaces, overlayFontString, overlayFxSpec, overlayHit, overlayLines, overlayMotionBase, overlayMotionPoseAt, overlayPinPoseAt, overlayPlainText, overlayRect, overlaysLane, ownFreezes, pageDisplayUrl, parseCssColour, parsePoseId, pickChange, pickStyle, pinBox, pinCandidates, pinPlacement, pinRectOnScreen, pinReferent, pinReferentAt, placeFreezes, planAutoSpeed, planAutoTilt, planAutoZoom, planForDigest, planeSizeAtDepth, platformBarKind, playwrightTraceEntry, programDuration, programStillTime, projectFromArtifact, pumpFreeChainGap, ratedSegments, recommendedExportResolution, refillAudioBeds, rejectSpan, relativeLuminance, removeSourceRange, removeSpeedInRange, resolveExportSize, resolveOverlayBox, resolveOverlayStyle, resolvePins, resolveText3dAsset, resolveZoomStyle, restLeanOf, restSpansThroughTransitions, rgbToHex, sameMedia, sceneChanges, scrollRuns, segmentOutputExtents, outputLen as segmentOutputLen, segmentStarts, setSpeedInRange, slideSide, smoothCursor, snapRunWeight, spanOutputExtent, speedLane, studioAudioPlan, studioEntry, studioLayerData, styledTextOf, textRunsOf, tiltLane, tiltTrackFromDoc, traceKindOf, transitionMult, transitionSeconds, unionFitLevel, unionRect, videoLane, voiceKey, wellFormedXml, withBackdrop, withFreezes, withHolds, withoutRejected, wrapProgramLength, zipEntries, zipEntryBytes, zoomCoversRect, zoomLane, zoomSpanForRange, zoomTrackFromDoc, zoomView, zoomViewport, zoomWindow };
package/dist/index.js CHANGED
@@ -3403,6 +3403,13 @@ var TYPING_CLICK_ABSORB = 1.5;
3403
3403
  var TYPING_REFOCUS_FRAC = 0.08;
3404
3404
  function groupTrack(track, opts) {
3405
3405
  const { width, height, clusterGap, typingGap, typingZoom, targetFill } = opts;
3406
+ const stepOf = (t) => opts.steps?.find(
3407
+ (st) => st.do !== "wait" && !st.skipped && st.tStart <= t && t <= st.tEnd
3408
+ ) ?? null;
3409
+ const sameStep = (a, b) => {
3410
+ const sa = stepOf(a.t);
3411
+ return sa !== null && sa === stepOf(b.t);
3412
+ };
3406
3413
  const minLevel = opts.minLevel ?? resolveZoomStyle().minLevel;
3407
3414
  const clicks = dedupePresses(
3408
3415
  track.filter((e) => e.type === "down")
@@ -3436,7 +3443,7 @@ function groupTrack(track, opts) {
3436
3443
  for (const c of list) {
3437
3444
  const last = clusters.at(-1);
3438
3445
  const prev = last?.at(-1);
3439
- const joins = last && prev && c.t - prev.t <= clusterGap && (!spatial || unionFitLevel(unionRect([...last, c]), width, height) >= minLevel);
3446
+ const joins = last && prev && (c.t - prev.t <= clusterGap || sameStep(prev, c)) && (!spatial || unionFitLevel(unionRect([...last, c]), width, height) >= minLevel);
3440
3447
  if (joins) last.push(c);
3441
3448
  else clusters.push([c]);
3442
3449
  }
@@ -3515,7 +3522,8 @@ function planAutoZoom(track, options) {
3515
3522
  typingGap,
3516
3523
  typingZoom,
3517
3524
  targetFill,
3518
- minLevel
3525
+ minLevel,
3526
+ steps: options.steps
3519
3527
  });
3520
3528
  const { sessions } = grouped;
3521
3529
  const drags = dragsFromTrack(track, width, height);
@@ -3630,10 +3638,11 @@ function planAutoZoom(track, options) {
3630
3638
  const spans = within([...zSpans, ...kSpans, ...dragSpans]).sort(
3631
3639
  (a, b) => a.in - b.in
3632
3640
  );
3641
+ const firstStep = options.steps?.find((st) => !st.skipped)?.tStart;
3633
3642
  const dwells = dwellSpans(track, width, height, maxLevel, [
3634
3643
  ...spans,
3635
3644
  ...dragReserved
3636
- ]);
3645
+ ]).filter((d) => firstStep === void 0 || d.out > firstStep);
3637
3646
  return within([...spans, ...dwells]).sort((a, b) => a.in - b.in);
3638
3647
  }
3639
3648
  function typingSessions(track, width, height, typingGap) {
@@ -9238,13 +9247,15 @@ var DEFAULT_SPEED_PARAMS = {
9238
9247
  typingMin: 3,
9239
9248
  typingRate: 3,
9240
9249
  scrollMin: 2.5,
9241
- scrollRate: 2
9250
+ scrollRate: 2,
9251
+ stillRate: 2
9242
9252
  };
9243
9253
  var TYPING_GAP = 1.5;
9244
9254
  var SCROLL_GAP = 0.8;
9245
9255
  var IDLE_PAD = 0.6;
9246
9256
  var MIN_SPAN = 1;
9247
9257
  var PLAYBACK_ACTIVITY = 0.1;
9258
+ var STILL_ACTIVITY = 0.02;
9248
9259
  function planAutoSpeed(track, opts) {
9249
9260
  const p = { ...DEFAULT_SPEED_PARAMS, ...opts.params };
9250
9261
  const durS = opts.durationMs / 1e3;
@@ -9263,6 +9274,9 @@ function planAutoSpeed(track, opts) {
9263
9274
  p.scrollMin,
9264
9275
  (start, last) => cands.push({ in: start, out: last, rate: p.scrollRate })
9265
9276
  );
9277
+ for (const [a, b] of stillSteps(opts.steps, opts.activity)) {
9278
+ if (b - a >= MIN_SPAN) cands.push({ in: a, out: b, rate: p.stillRate });
9279
+ }
9266
9280
  for (const [a, b] of idleGaps(evs, durS, p.idleMin)) {
9267
9281
  if (isPlayback(opts.activity, a, b)) continue;
9268
9282
  const start = a + IDLE_PAD;
@@ -9316,6 +9330,29 @@ function idleGaps(track, durationS, idleMin = DEFAULT_SPEED_PARAMS.idleMin) {
9316
9330
  if (durationS - prev >= idleMin) gaps.push([prev, durationS]);
9317
9331
  return gaps;
9318
9332
  }
9333
+ function stillSteps(steps, activity) {
9334
+ if (!steps?.length || !activity?.length) return [];
9335
+ const live = steps.filter((st) => !st.skipped);
9336
+ const doing = live.map((st) => st.do !== "wait");
9337
+ const first = doing.indexOf(true);
9338
+ const last = doing.lastIndexOf(true);
9339
+ const out = [];
9340
+ for (const [i, st] of live.entries()) {
9341
+ if (st.do !== "wait" && st.do !== "hover") continue;
9342
+ if (st.do === "wait" && (first < 0 || i < first || i > last)) continue;
9343
+ if (meanActivity(activity, st.tStart, st.tEnd) >= STILL_ACTIVITY) continue;
9344
+ out.push([st.tStart, st.tEnd]);
9345
+ }
9346
+ return out;
9347
+ }
9348
+ function meanActivity(activity, a, b) {
9349
+ const lo = Math.max(0, Math.floor(a));
9350
+ const hi = Math.min(activity.length, Math.ceil(b));
9351
+ if (hi <= lo) return 0;
9352
+ let sum = 0;
9353
+ for (let i = lo; i < hi; i++) sum += activity[i];
9354
+ return sum / (hi - lo);
9355
+ }
9319
9356
  function isPlayback(activity, a, b) {
9320
9357
  if (!activity?.length) return false;
9321
9358
  const lo = Math.max(0, Math.floor(a));
@@ -9354,12 +9391,14 @@ function planForDigest(doc, activity) {
9354
9391
  width: meta.width,
9355
9392
  height: meta.height,
9356
9393
  style: doc.zoomStyle,
9357
- params: doc.zoomParams
9394
+ params: doc.zoomParams,
9395
+ steps: meta.steps
9358
9396
  });
9359
9397
  const speed = planAutoSpeed(cursor, {
9360
9398
  durationMs: meta.durationMs,
9361
9399
  params: doc.speedParams,
9362
- activity
9400
+ activity,
9401
+ steps: meta.steps
9363
9402
  });
9364
9403
  const style = resolveZoomStyle(doc.zoomStyle, doc.zoomParams);
9365
9404
  const intensity = doc.tiltStyle ?? style.tilt.intensity;
@@ -9399,7 +9438,8 @@ function momentsFromDoc(doc, plan, opts = {}) {
9399
9438
  typingGap: style.typingGap,
9400
9439
  typingZoom: style.typingZoom,
9401
9440
  targetFill: style.targetFill,
9402
- minLevel: style.minLevel
9441
+ minLevel: style.minLevel,
9442
+ steps: meta.steps
9403
9443
  });
9404
9444
  const pressed = (c, surface) => {
9405
9445
  const f = clusterFocus(c, width, height);
@@ -9495,15 +9535,30 @@ function momentsFromDoc(doc, plan, opts = {}) {
9495
9535
  rect: null
9496
9536
  });
9497
9537
  }
9538
+ for (const c of opts.changes ?? []) {
9539
+ if (!(c.at >= 0 && c.at < dur)) continue;
9540
+ const peak = Math.min(dur, Math.max(c.at, c.peakAt));
9541
+ drafts.push({
9542
+ kind: "changed",
9543
+ in: c.at,
9544
+ out: peak,
9545
+ at: peak,
9546
+ focus: { cx: c.rect.x + c.rect.w / 2, cy: c.rect.y + c.rect.h / 2 },
9547
+ rect: c.rect,
9548
+ seedAt: c.at,
9549
+ fraction: c.fraction
9550
+ });
9551
+ }
9498
9552
  const order = {
9499
9553
  head: 0,
9500
9554
  click: 1,
9501
9555
  typing: 2,
9502
- scroll: 3,
9503
- dwell: 4,
9504
- scene: 5,
9505
- idle: 6,
9506
- tail: 7
9556
+ changed: 3,
9557
+ scroll: 4,
9558
+ dwell: 5,
9559
+ scene: 6,
9560
+ idle: 7,
9561
+ tail: 8
9507
9562
  };
9508
9563
  drafts.sort((a, b) => a.in - b.in || order[a.kind] - order[b.kind]);
9509
9564
  const rated = ratedSegments(doc);
@@ -9512,8 +9567,17 @@ function momentsFromDoc(doc, plan, opts = {}) {
9512
9567
  return spanOutputExtent(rated, lo, Math.max(b, lo + 1e-3));
9513
9568
  };
9514
9569
  const covers = (s, d) => d.in === d.out ? s.in <= d.in && d.in < s.out : s.in < d.out && s.out > d.in;
9570
+ const ids = drafts.map((_, i) => `m${String(i + 1).padStart(2, "0")}`);
9571
+ const seedOf = (d) => {
9572
+ if (d.kind !== "changed" || d.seedAt === void 0) return void 0;
9573
+ const j = drafts.findIndex(
9574
+ (x) => x.kind === "click" && x.at === d.seedAt || x.kind === "typing" && x.out === d.seedAt
9575
+ );
9576
+ return j >= 0 ? ids[j] : void 0;
9577
+ };
9515
9578
  return drafts.map((d, i) => {
9516
9579
  const output = outputOf(d.in, d.out);
9580
+ const seed = seedOf(d);
9517
9581
  const outAt = d.at === null ? null : outputOf(d.at, d.at);
9518
9582
  const proposed = {};
9519
9583
  const z = plan.zoom.find((s) => covers(s, d));
@@ -9523,7 +9587,7 @@ function momentsFromDoc(doc, plan, opts = {}) {
9523
9587
  const tl = plan.tilt.find((s) => covers(s, d));
9524
9588
  if (tl) proposed.tilt = tl.id;
9525
9589
  return {
9526
- id: `m${String(i + 1).padStart(2, "0")}`,
9590
+ id: ids[i],
9527
9591
  kind: d.kind,
9528
9592
  source: { in: round10(d.in), out: round10(d.out) },
9529
9593
  output: output ? { in: round10(output.start), out: round10(output.end) } : null,
@@ -9539,9 +9603,10 @@ function momentsFromDoc(doc, plan, opts = {}) {
9539
9603
  ...d.clicks !== void 0 ? { clicks: d.clicks } : {},
9540
9604
  ...d.surface ? { surface: true } : {},
9541
9605
  ...d.pings !== void 0 ? { pings: d.pings } : {},
9542
- activity: activityOf(opts.bins, d),
9606
+ activity: d.kind === "changed" && d.fraction !== void 0 ? round10(d.fraction) : activityOf(opts.bins, d),
9543
9607
  proposed,
9544
- said: saidOver(opts.transcript, d)
9608
+ said: saidOver(opts.transcript, d),
9609
+ ...seed ? { seed } : {}
9545
9610
  };
9546
9611
  });
9547
9612
  }
@@ -9568,6 +9633,105 @@ function round10(v) {
9568
9633
  return Math.round(v * 1e3) / 1e3;
9569
9634
  }
9570
9635
 
9636
+ // src/digest/changes.ts
9637
+ var CHANGE_GRID = { w: 128, h: 72 };
9638
+ var BLOCK_FILL = 0.5;
9639
+ var CHANGE_FLOOR = 0.02;
9640
+ var CHANGE_OFFSETS = [0.3, 0.8, 1.5, 2.5];
9641
+ var CHANGE_LEAD = 0.5;
9642
+ function changedRegion(before, after, delta, minBlocks = 1, ambient = null, block = Math.max(1, Math.round(before.w / 16))) {
9643
+ const { w, h } = before;
9644
+ if (after.w !== w || after.h !== h || w <= 0 || h <= 0) return null;
9645
+ const cols = Math.ceil(w / block);
9646
+ const rows = Math.ceil(h / block);
9647
+ const moved = new Uint16Array(cols * rows);
9648
+ const cells = new Uint16Array(cols * rows);
9649
+ for (let y = 0; y < h; y++) {
9650
+ const by = Math.floor(y / block);
9651
+ for (let x = 0; x < w; x++) {
9652
+ const i = y * w + x;
9653
+ const b = by * cols + Math.floor(x / block);
9654
+ cells[b]++;
9655
+ const drift = ambient ? ambient.rate[i] * ambient.dt : 0;
9656
+ if (Math.abs(after.luma[i] - before.luma[i]) - drift > delta) moved[b]++;
9657
+ }
9658
+ }
9659
+ const filled = new Uint8Array(cols * rows);
9660
+ for (let b = 0; b < filled.length; b++)
9661
+ if (moved[b] >= cells[b] * BLOCK_FILL) filled[b] = 1;
9662
+ const seen = new Uint8Array(cols * rows);
9663
+ let best = null;
9664
+ for (let start = 0; start < filled.length; start++) {
9665
+ if (!filled[start] || seen[start]) continue;
9666
+ const stack = [start];
9667
+ seen[start] = 1;
9668
+ let size = 0;
9669
+ let minX = cols;
9670
+ let minY = rows;
9671
+ let maxX = -1;
9672
+ let maxY = -1;
9673
+ while (stack.length) {
9674
+ const b = stack.pop();
9675
+ size++;
9676
+ const bx = b % cols;
9677
+ const by = Math.floor(b / cols);
9678
+ if (bx < minX) minX = bx;
9679
+ if (bx > maxX) maxX = bx;
9680
+ if (by < minY) minY = by;
9681
+ if (by > maxY) maxY = by;
9682
+ for (const [dx, dy] of [
9683
+ [1, 0],
9684
+ [-1, 0],
9685
+ [0, 1],
9686
+ [0, -1]
9687
+ ]) {
9688
+ const nx = bx + dx;
9689
+ const ny = by + dy;
9690
+ if (nx < 0 || ny < 0 || nx >= cols || ny >= rows) continue;
9691
+ const n = ny * cols + nx;
9692
+ if (filled[n] && !seen[n]) {
9693
+ seen[n] = 1;
9694
+ stack.push(n);
9695
+ }
9696
+ }
9697
+ }
9698
+ if (!best || size > best.size) best = { size, minX, minY, maxX, maxY };
9699
+ }
9700
+ if (!best || best.size < minBlocks) return null;
9701
+ return {
9702
+ fraction: best.size / (cols * rows),
9703
+ rect: {
9704
+ x: best.minX * block / w,
9705
+ y: best.minY * block / h,
9706
+ w: Math.min(w, (best.maxX + 1) * block) / w - best.minX * block / w,
9707
+ h: Math.min(h, (best.maxY + 1) * block) / h - best.minY * block / h
9708
+ }
9709
+ };
9710
+ }
9711
+ function ambientDrift(earlier, before, lead) {
9712
+ const { w, h } = before;
9713
+ if (earlier.w !== w || earlier.h !== h || !(lead > 0)) return null;
9714
+ const rate = new Array(w * h);
9715
+ for (let i = 0; i < rate.length; i++)
9716
+ rate[i] = Math.abs(before.luma[i] - earlier.luma[i]) / lead;
9717
+ return rate;
9718
+ }
9719
+ function pickChange(seed, samples, floor = CHANGE_FLOOR) {
9720
+ let best = null;
9721
+ for (const s of samples) {
9722
+ if (!s.region || s.region.fraction < floor) continue;
9723
+ if (!best || s.region.fraction > (best.region?.fraction ?? 0)) best = s;
9724
+ }
9725
+ if (!best?.region) return null;
9726
+ return {
9727
+ seed: seed.id,
9728
+ at: seed.at,
9729
+ peakAt: seed.at + best.dt,
9730
+ fraction: best.region.fraction,
9731
+ rect: best.region.rect
9732
+ };
9733
+ }
9734
+
9571
9735
  // src/digest/scenes.ts
9572
9736
  var SCENE_MOTION = 0.25;
9573
9737
  var SCENE_QUIET = 0.1;
@@ -10099,6 +10263,7 @@ function buildDigest(input) {
10099
10263
  activity: "fraction of pixels that changed, per SOURCE second"
10100
10264
  },
10101
10265
  moments: withFiles,
10266
+ steps: digestSteps(doc),
10102
10267
  activity: input.bins,
10103
10268
  plan: input.plan,
10104
10269
  doc: {
@@ -10122,6 +10287,26 @@ function buildDigest(input) {
10122
10287
  }
10123
10288
  };
10124
10289
  }
10290
+ function digestSteps(doc) {
10291
+ const steps = doc.source.meta.steps ?? [];
10292
+ if (!steps.length) return [];
10293
+ const rated = ratedSegments(doc);
10294
+ return steps.map((s) => {
10295
+ const out = spanOutputExtent(rated, s.tStart, s.tEnd);
10296
+ return {
10297
+ step: s.step,
10298
+ id: s.id ?? null,
10299
+ do: s.do,
10300
+ selector: s.selector ?? null,
10301
+ source: { in: round11(s.tStart), out: round11(s.tEnd) },
10302
+ output: out ? { in: round11(out.start), out: round11(out.end) } : null,
10303
+ skipped: s.skipped === true,
10304
+ navigated: s.navigated === true,
10305
+ url: s.url ?? null,
10306
+ text: s.text ?? null
10307
+ };
10308
+ });
10309
+ }
10125
10310
  function outputDurationOf(doc) {
10126
10311
  return docOutputDuration(doc);
10127
10312
  }
@@ -11956,6 +12141,10 @@ export {
11956
12141
  CARD_FADE_SECONDS,
11957
12142
  CARD_FOV,
11958
12143
  CARD_Z,
12144
+ CHANGE_FLOOR,
12145
+ CHANGE_GRID,
12146
+ CHANGE_LEAD,
12147
+ CHANGE_OFFSETS,
11959
12148
  CHANNEL_SPECS_HASH,
11960
12149
  CHANNEL_SPECS_VERIFIED,
11961
12150
  CLICK_FX_INTENSITY,
@@ -12105,6 +12294,7 @@ export {
12105
12294
  ZOOM_SPAN_MIN,
12106
12295
  ZOOM_STYLES,
12107
12296
  ZOOM_STYLE_OPTIONS,
12297
+ ambientDrift,
12108
12298
  anchorKindOf,
12109
12299
  anchorSourceDuration,
12110
12300
  animStep,
@@ -12137,6 +12327,7 @@ export {
12137
12327
  cardPoseTrack,
12138
12328
  caretAt,
12139
12329
  caretStops,
12330
+ changedRegion,
12140
12331
  clampCamFrac,
12141
12332
  clampCamSize,
12142
12333
  clampFocus,
@@ -12289,6 +12480,7 @@ export {
12289
12480
  pageDisplayUrl,
12290
12481
  parseCssColour,
12291
12482
  parsePoseId,
12483
+ pickChange,
12292
12484
  pickStyle,
12293
12485
  pinBox,
12294
12486
  pinCandidates,