import type { RoutePreviewArtifactBundle } from './routePreview.js'; import type { DesignScreenCase, DesignScreenInteraction, StoryboardEnvironment } from './designImport.js'; export interface PreviewRecipeIdentity { id: string; recipe: unknown; } export interface PreviewCacheNamespaceOptions { sourceRevision: string; recipes: readonly PreviewRecipeIdentity[]; recipeVersion?: number; scope?: string; } export type StoryboardCaptureRecipeInput = Pick; export interface PreviewArtifactExpectation { namespace: string; sourceRevision: string; } export interface PreviewArtifactFrameRequest { id: string; fingerprint?: string; /** Capture recipe identity without its source dependency digest. */ recipeFingerprint?: string; /** The recipe the fingerprint stands for; travels only where a capture may run. */ recipe?: RoutePreviewFrameRecipe; } export { PREVIEW_CAPTURE_PRIORITY, type PreviewCapturePriority } from '../contract/paths.js'; /** Only this consumer's obsolete queued request for the same frame may be replaced. */ export interface PreviewArtifactLatestRequest { consumerId: string; /** Monotonically increasing within a consumer session; never reused for a different recipe. */ sequence: number; } export interface PreviewArtifactTransportRequest extends PreviewArtifactExpectation { /** Frames to fetch. Omitted asks for the whole bundle, as earlier versions did. */ frames?: readonly PreviewArtifactFrameRequest[]; /** Set false to seed only what exists instead of capturing what is absent. */ generate?: boolean; /** Return only exact/missing/stale classification, without frame payloads. */ resolveOnly?: boolean; /** Where this capture queues among others on the identity; see PREVIEW_CAPTURE_PRIORITY. */ priority?: number; latestRequest?: PreviewArtifactLatestRequest; signal?: AbortSignal; captureBaseUrl?: string; } export type PreviewArtifactTransport = (request: PreviewArtifactTransportRequest) => Promise; export interface PreviewArtifactHttpResponse { readonly ok: boolean; readonly status?: number; readonly statusText?: string; json(): Promise; } /** * A probe reads; it travels as GET with the frame identities in the query. A * capture request carries a recipe per frame, which a query string cannot * hold past a handful of frames, so it travels as a JSON body. */ export type PreviewArtifactFetchInit = { method: 'GET'; headers: { accept: 'application/json'; }; cache: 'no-store'; signal?: AbortSignal; } | { method: 'POST'; headers: { accept: 'application/json'; 'content-type': 'application/json'; }; body: string; cache: 'no-store'; signal?: AbortSignal; }; export type PreviewArtifactFetch = (input: string, init: PreviewArtifactFetchInit) => Promise; export interface PreviewArtifactHttpTransportOptions { endpoint: string; fetcher?: PreviewArtifactFetch; captureBaseUrl?: string; } export type PreviewArtifactValidationReason = 'invalid-bundle' | 'namespace-mismatch' | 'revision-mismatch'; /** Exactness of the frames named by a partial artifact request. */ export interface PreviewArtifactFrameResolution { /** Frames whose source revision or opaque fingerprint matches the request. */ exact: readonly string[]; /** Frames that were not present in the returned artifact. */ missing: readonly string[]; /** Frames that were present but did not match the requested identity. */ stale: readonly string[]; } export declare const PREVIEW_ARTIFACT_FRAME_REQUEST_CHUNK_SIZE = 20; /** Keeps GET-based local transports below common query/header limits. */ export declare function chunkPreviewArtifactFrameRequests(frames: readonly PreviewArtifactFrameRequest[], chunkSize?: number): PreviewArtifactFrameRequest[][]; export type PreviewArtifactValidationResult = { valid: true; artifact: RoutePreviewArtifactBundle; errors: readonly []; } | { valid: false; artifact: null; reason: PreviewArtifactValidationReason; errors: readonly string[]; }; export type PreviewArtifactBootstrapResult = { status: 'accepted'; artifact: RoutePreviewArtifactBundle; /** Present when the caller asked for individual frames. */ frameResolution?: PreviewArtifactFrameResolution; } | { status: 'unavailable'; artifact: null; error?: unknown; } | { status: 'rejected'; artifact: null; reason: PreviewArtifactValidationReason; errors: readonly string[]; }; export type PreviewArtifactFrameProbeResult = { status: 'accepted'; resolution: PreviewArtifactFrameResolution; } | { status: 'unavailable'; resolution: null; error?: unknown; } | { status: 'rejected'; resolution: null; errors: readonly string[]; }; /** * Version of the inert preview payload Pygmalion itself produces. * * Host recipe versions describe host-side capture semantics. Core metadata, * sanitization, or bitmap contracts can change without a host edit, so the * core owns a separate partition and retires incompatible artifacts itself. */ export declare const PYGMALION_PREVIEW_CAPTURE_SCHEMA_VERSION = 4; export declare function createPreviewRecipeFingerprint(recipes: readonly PreviewRecipeIdentity[]): string; /** * Identity of one frame's capture, so freshness is decided per screen. * * The namespace fingerprints the whole catalog, which is why one edited screen * used to invalidate every frame. This value covers a single screen's recipe and, * when a caller passes one, the revision or content digest that screen depends * on — the piece a dependency-closure digest replaces later. */ export declare function createPreviewFrameFingerprint(screen: StoryboardCaptureRecipeInput & { id?: string; }, options?: { dependencyDigest?: string; }): string; export interface PreviewFramePage { canonicalId: string; route: string; previewRoute?: string; width?: number; height?: number; environment?: StoryboardEnvironment; preset?: unknown; desiredState?: unknown; interactions?: unknown; assertions?: unknown; interactiveStateSelections?: Readonly>; previewScrollInteraction?: DesignScreenInteraction; heldPseudoInteractions?: readonly DesignScreenInteraction[]; } /** * What the fingerprint of a frame stands for. * * The fingerprint is a hash, so a generator handed one alone can only capture * what the host declared for that id — a different screen wearing the requested * name. Sending the recipe beside it is what lets a frame held in a declared * state be captured as the state it is actually in. * * The interaction list stays opaque here for the same reason the page's does: * this package transports the host's recipe, it does not read it. */ export interface RoutePreviewFrameRecipe { route?: string; width?: number; height?: number; environment?: StoryboardEnvironment; preset?: unknown; desiredState?: unknown; interactions?: unknown; assertions?: unknown; /** Declaration references used to verify a composed recipe at the capture boundary. */ interactiveStateSelections?: Readonly>; /** Preserved viewport position, verified separately from declared replay steps. */ previewScrollInteraction?: DesignScreenInteraction; } /** * Resolves the page identity a returned frame must seed. * * The catalog page is only the authored starting point. A selected frame may * request another viewport or interaction recipe; storing that response under * the authored key reports the request as exact while the canvas keeps waiting * on a different key. The request is authoritative for every field it carries. */ export declare function resolveRoutePreviewFrameRequestPage(request: PreviewArtifactFrameRequest, authored?: PreviewFramePage): (PreviewFramePage & { fingerprint?: string; }) | null; /** The reproducible capture recipe, kept separate from changing source content. */ export declare function createRoutePreviewFrameRecipeFingerprint(page: PreviewFramePage, baselineEnvironment?: StoryboardEnvironment): string; export declare function createPreviewCacheNamespace({ sourceRevision, recipes, recipeVersion, scope, }: PreviewCacheNamespaceOptions): string; /** * Device pixels captured per CSS pixel. Must mirror * `STORYBOARD_CAPTURE_SCREENSHOT_SCALE` in `storyboard-capture-runtime.mjs` * (browser code cannot import the Node runtime); a shared test pins the pair. */ export declare const STORYBOARD_CAPTURE_RECIPE_SCREENSHOT_SCALE = 2; /** * Returns the portable runtime identity of a resolved storyboard capture. * Descriptive labels and editor-only grouping are intentionally omitted. * The screenshot scale participates so that changing the capture density * retires every artifact captured at the old density. */ export declare function createStoryboardCaptureRecipeIdentity(screen: StoryboardCaptureRecipeInput): unknown; /** * Creates the default same-origin HTTP transport for preview artifacts. * Hosts provide only an endpoint; identity query parameters and safe fetch * semantics remain owned by Pygmalion. * * An adapter over `createViteTransport().preview.artifact`, kept so a host * that assembled its own transport from an endpoint and a fetcher still can. * An absolute endpoint splits into the transport's origin and path. */ export declare function createPreviewArtifactHttpTransport({ endpoint, fetcher, captureBaseUrl, }: PreviewArtifactHttpTransportOptions): PreviewArtifactTransport; /** Resolves frame freshness without downloading or validating snapshot payloads. */ export declare function probePreviewArtifactFrames({ namespace, sourceRevision, transport, signal, frames, }: PreviewArtifactExpectation & { transport: PreviewArtifactTransport; signal?: AbortSignal; frames: readonly PreviewArtifactFrameRequest[]; }): Promise; /** * Probes any number of frames in request-sized chunks. Answers the exact ids of * every chunk the endpoint answered and names the frames of the others, so one * failed chunk does not discard the rest; null only when no chunk was answered. */ export declare function probePreviewArtifactFrameChunks(options: PreviewArtifactExpectation & { transport: PreviewArtifactTransport; signal?: AbortSignal; frames: readonly PreviewArtifactFrameRequest[]; }): Promise<{ exact: string[]; unanswered: string[]; } | null>; export declare function validatePreviewArtifactBundle(value: unknown, expectation?: PreviewArtifactExpectation, options?: { perFrameIdentity?: boolean; }): PreviewArtifactValidationResult; /** * Resolves partial artifact exactness per requested frame. * * A bundle revision is sufficient for v1/v2. Version 3 requires both the source * revision and opaque fingerprint; matching content from another revision stays * useful as last-known evidence but never becomes exact. * This keeps a valid partial response distinct from a current frame response: * an empty or stale subset is valid transport data, but it is not a warm cache. */ export declare function resolvePreviewArtifactFrames(artifact: RoutePreviewArtifactBundle, expectation: PreviewArtifactExpectation, frames: readonly PreviewArtifactFrameRequest[]): PreviewArtifactFrameResolution; export declare function bootstrapPreviewArtifact({ namespace, sourceRevision, transport, signal, frames, generate, priority, latestRequest, }: PreviewArtifactExpectation & { transport: PreviewArtifactTransport; signal?: AbortSignal; frames?: readonly PreviewArtifactFrameRequest[]; generate?: boolean; priority?: number; latestRequest?: PreviewArtifactLatestRequest; }): Promise; /** The route dependency digests the host declared for one editor's previews. */ export declare function createPreviewDependencyDigests(): { setPreviewDependencyDigests: (routes: readonly { path?: string; dependencyDigest?: string; }[] | null) => void; previewDependencyDigest: (route: string) => string | undefined; routePreviewFrameRecipe: (page: PreviewFramePage, baselineEnvironment?: StoryboardEnvironment) => RoutePreviewFrameRecipe; createRoutePreviewFrameFingerprint: (page: PreviewFramePage, previewRevision: string, baselineEnvironment?: StoryboardEnvironment) => string; }; export type PreviewDependencyDigests = ReturnType;