/** * useIncrementalStitcher — React hook driving the live panorama * engine. * * Lifecycle: * 1. Host calls `useARSession().start()` to put the AR session in * tracking mode. (Works for AR-supported devices only — non-AR * fallback comes in a later phase.) * 2. Host calls `start()` from this hook. The native engine * registers itself as the AR session's frame consumer. * 3. Native emits a state event for every ARFrame the engine * processes (~60 Hz, mostly trivially-skipped). The hook * mirrors this into React state so a `` * or any other consumer can render the live panorama + UX hints. * 4. Host calls `finalize(outputPath)` when the user releases the * shutter; resolves with the final panorama path + stats. * 5. Host calls `cancel()` if the user dismisses the capture. */ import { type IncrementalState, type IncrementalStartOptions, type IncrementalFinalizeResult } from './incremental'; export type IncrementalHint = 'slow-down' | 'scene-uniform' | 'alignment-lost' | 'tracking-poor' | null; export interface UseIncrementalStitcherReturn { /** Whether the native engine is registered. False = no fallback wiring. */ isAvailable: boolean; /** True between successful `start()` and `finalize()`/`cancel()`. */ isRunning: boolean; /** Latest state pushed by the native engine, or null pre-start. */ state: IncrementalState | null; /** * perf-3a change 4 (review fix) — accumulated keyframe thumbnail paths * (raw native paths, capture-ordered, de-duped) for the live strip. * Accumulated per RAW accept event inside the subscription (via a * functional setState updater), NOT off the coalesced `state`, so two * accepts landing in one React batch both survive — the collapsed-render * effect the earlier draft used dropped one. Cleared on start/finalize/ * cancel. Consumers normalise to `file://` at render time. */ keyframeThumbnails: string[]; /** * Convenience: which UX hint to show, derived from the latest * state.outcome. null when nothing should be shown (silent * accepts, skips inside the overlap window). */ hint: IncrementalHint; /** * Convenience: 'high' | 'medium' | null based on the last accept. * Drives confidence-ring rendering in the live preview. */ confidenceLevel: 'high' | 'medium' | null; /** Begin a new capture. Throws if the AR session isn't running. */ start: (options?: IncrementalStartOptions) => Promise; /** * End the capture and write the final panorama. When `outputPath` * is omitted or empty, the native side picks a path under the * app's tmp directory and returns it in the result. * * `captureOrientation` (optional) — pass the user's CURRENT * device orientation at finalize time. The engine prefers this * value over the start-time snapshot for the bake-rotation pass, * so cross-orientation captures (user opened screen in portrait, * captured in landscape) bake correctly. Omit to keep the legacy * behaviour (start-time orientation). */ finalize: (outputPath?: string, quality?: number, captureOrientation?: string, /** * 2026-05-22 (audit F2b) — measured cumulative translation * magnitude in METRES from the JS-side IMU translation gate. * Used by the auto-resolver in non-AR mode where the engine has * no pose-driven translation source — without this signal the * auto-resolver always picks `panorama` even for shelf scans. * Omit (or pass 0) when no IMU translation data is available * (e.g. in AR mode the native side has its own pose-driven * translation magnitude and prefers that). */ imuTranslationMetres?: number, /** * 2026-06-16 — the EXPLICIT lens the user selected (`'1x'` | `'0.5x'`). * The reliable zoom signal for the high-level warper tree (`'0.5x'` * ultra-wide → spherical). Omit ⇒ treated as `'1x'`. */ lens?: string) => Promise; /** Abort the capture without producing output. */ cancel: () => Promise; } /** * Sticky-snapshot merge: keep the last-good snapshot fields so the PiP * shows the most recent panorama continuously between accepts (the native * engine emits `panoramaPath` only on accept; reject/skip ticks carry * none — a naive replace would blank the live preview ~60×/s). */ export declare function stickyMergeIncremental(prev: IncrementalState | null, next: IncrementalState): IncrementalState; /** * Classify an event as immediate-flush (must render now) vs coalesced. * Immediate: an ACCEPT (a keyframe thumbnail is present, or the accepted * count went up, or the outcome is an `Accepted*`) or a refine-stage * transition (including the terminal `done`/`error` stages). Everything * else — rejects, skips, overlap %, hints — coalesces. */ export declare function isImmediateIncrementalEvent(next: IncrementalState, prevAcceptedCount: number, prevRefineStage: IncrementalState['refineStage']): boolean; export declare function useIncrementalStitcher(): UseIncrementalStitcherReturn; //# sourceMappingURL=useIncrementalStitcher.d.ts.map