import type { RoutePreviewFrameRecipe } from './previewBootstrap.js'; import type { RoutePreviewArtifactFrameIdentity, RoutePreviewArtifactResolution } from './routePreview.js'; /** * Who fills a canvas first. * * `worker-first`: the capture worker behind the artifact endpoint is asked * for the canvas in batches; the walking supply and the per-screen sweep run * only for what the worker cannot answer. `browser-first`: the walking supply * and sweep fill the canvas inside the editor, and the worker captures only * the frame the designer selects — the behaviour before this module existed. * `auto` picks worker-first when an artifact endpoint is configured. */ export type CaptureSupplyMode = 'worker-first' | 'browser-first'; export interface CaptureSupplyFrame { pageId: string; /** Catalog screen id the capture is stored under. */ canonicalId: string; canvas: string; /** * Screen-space distance from the viewport center; the caller sets it to * Infinity for frames off the active canvas. */ distance: number; fingerprint: string; /** Identity of `recipe` alone; travels with it so a generator can verify what it is asked to capture. */ recipeFingerprint?: string; recipe?: RoutePreviewFrameRecipe; } /** * The wire identity of a supply frame. A recipe never travels without its * fingerprint: a generator validates the recipe it is handed against that * fingerprint, and a request carrying one without the other is refused as * unverifiable, which left every swept frame "not prepared". */ export declare function captureSupplyFrameRequest(frame: CaptureSupplyFrame): RoutePreviewArtifactFrameIdentity; export interface CaptureSupplyPlan { /** Canvas that owned the viewport when this plan was built. */ activeCanvas?: string; /** Chunks of the active canvas, nearest first. */ active: CaptureSupplyFrame[][]; /** Chunks of the other canvases, in the order the frames were given. */ background: CaptureSupplyFrame[][]; } export interface PlanCaptureSupplyOptions { activeCanvas: string; /** * Frames per request. Small enough that the first frames of a canvas * arrive while the rest are still being captured, large enough to keep a * worker's lanes busy. Six is roughly one worker round with two or three * lanes. */ chunkSize?: number; } export declare const CAPTURE_SUPPLY_CHUNK_SIZE = 6; /** Chunks the driver keeps in flight for the active canvas. */ export declare const CAPTURE_SUPPLY_ACTIVE_IN_FLIGHT = 2; /** Chunks the driver keeps in flight for canvases nobody is looking at. */ export declare const CAPTURE_SUPPLY_BACKGROUND_IN_FLIGHT = 1; /** * Turns the frames still owed into an ordered plan of batches. * * Duplicate identities collapse (a variant sharing a capture is one * request), the active canvas sorts by distance so what is visible arrives * first, and every other canvas keeps its own frames together so a walk * through the catalog warms one canvas at a time rather than a little of * each. */ export declare function planCaptureSupply(frames: readonly CaptureSupplyFrame[], options: PlanCaptureSupplyOptions): CaptureSupplyPlan; export interface CaptureSupplyStatus { phase: 'idle' | 'running' | 'done'; /** Chunks waiting to be sent, from the latest plan. */ pending: number; /** Chunks answered since the last cancel, exactly or not. */ completed: number; /** Frames the worker could not answer and that were released to the fallback, since the last cancel. */ released: number; /** Chunks currently at the requester. */ inFlight: number; } export interface CaptureSupplyDriverDeps { /** * Hands one chunk to the batch requester. `null` means no requester is * installed (the endpoint is gone), which ends the run. */ request(frames: readonly CaptureSupplyFrame[], priority: number, options: { retry: boolean; }): Promise | null; /** * Frames the worker answered with a real failure. The caller hands them to * the fallback supply; a `not-prepared` answer is retried here instead. */ release?(frames: readonly CaptureSupplyFrame[], reason: string): void; /** How long to wait before asking again for a chunk the runtime was not ready for. */ notPreparedRetryMs?: readonly number[]; setTimeout?: (callback: () => void, ms: number) => unknown; clearTimeout?: (handle: unknown) => void; log?: (message: string) => void; } export interface CaptureSupplyDriverOptions { activeInFlight?: number; backgroundInFlight?: number; } export interface CaptureSupplyDriver { /** * Adopts a plan. Chunks already at the requester keep going — the batch * requester dedupes identities, so re-planning never doubles a capture — * and pending work is replaced by the new plan's. */ run(plan: CaptureSupplyPlan): void; /** Drops pending work and stops retrying. In-flight requests finish on their own. */ cancel(): void; status(): CaptureSupplyStatus; } /** * The capture supply of one editor runtime: the host's mode choice and the * status channel its drivers publish on. Lives on * `runtime.schedulers.captureSupply`, so two editors on one page never * share a listener set or a mode. */ export declare function createCaptureSupply(): { setCaptureSupplyMode: (mode: CaptureSupplyMode | "auto" | undefined) => void; resolveCaptureSupplyMode: (endpointConfigured: boolean) => CaptureSupplyMode; getCaptureSupplyStatus: () => CaptureSupplyStatus; subscribeCaptureSupply: (listener: () => void) => () => void; createCaptureSupplyDriver: (deps: CaptureSupplyDriverDeps, options?: CaptureSupplyDriverOptions) => CaptureSupplyDriver; }; export type CaptureSupply = ReturnType;