import type { FileSystemRouteIdentity } from "@opengeni/sdk"; import { type ClientOverride } from "../provider.js"; /** * The wake-on-edit state machine. This hook owns the logic and the UI renders * its result. States are explicit and exhaustive: * * viewing-cold — cold snapshot open, no local edits (editable, cloud). * buffering — local edits buffered; the box is not warm and no warm is * yet in flight (the host has not attached). * warming — edits buffered; the box is actively coming up (the host * attached a viewer in response to `wantsWarm`). * flushing — the box is warm; the guarded write is in flight. * flushed — the buffer was written to the live box. * conflict — the live file diverged from the captured base between * capture and flush; NO write was issued (C2). * readonly-offline — a self-hosted machine that is offline; read-only, no wake. */ export type WorkspaceEditState = "viewing-cold" | "buffering" | "warming" | "flushing" | "flushed" | "conflict" | "readonly-offline"; export type WorkspaceEditConflict = { path: string; /** The capture-served content the edit was based on. */ base: string; /** The live content found on the box at flush time (what would be overwritten). */ live: string; }; export type UseWorkspaceEditOptions = ClientOverride & { /** The file being edited (workspace-relative). null disables the machine. */ path?: string | null | undefined; /** The capture-served content loaded into the editor — the flush base (C2). */ baseContent?: string | null | undefined; /** Exact capability identity for the selected filesystem route. A route/root * change discards the buffered edit and makes stale flushes fail retryably. */ route?: FileSystemRouteIdentity | undefined; /** `capabilities.liveness` — "warm" | "draining" | "cold". Drives the flush. */ liveness?: string | undefined; /** True while the host is actively warming the box (attach in flight, not warm * yet) — lifts `buffering` → `warming`. The host sets this after acting on * `wantsWarm` (e.g. flipping `attachFiles` on `useSessionCapabilities`). */ warming?: boolean | undefined; /** A self-hosted machine that is OFFLINE: read-only, no remote wake possible. */ offline?: boolean | undefined; /** Called once when the first cold edit needs the box warmed — the host wires * this to its liveness primitive (flip `attachFiles`/`attachViewer`). */ onWarmRequested?: (() => void) | undefined; }; export type UseWorkspaceEditResult = { state: WorkspaceEditState; /** The editor should be read-only (self-hosted offline). */ readOnly: boolean; /** The current buffered content (null = no local edit yet). */ buffer: string | null; /** The host should warm the box (flip its attach primitive). Latches true from * the first cold edit until the buffer flushes or is discarded. */ wantsWarm: boolean; /** Record an edit from the editor (idempotent per identical content). */ edit: (content: string) => void; /** The conflict detail when `state === "conflict"`. */ conflict: WorkspaceEditConflict | null; /** Flush the buffer regardless of the base guard (the user chose "overwrite" * from the conflict bar). Writes last-writer-wins. */ overwrite: () => Promise; /** Drop the local buffer and return to viewing the snapshot. */ discard: () => void; error: Error | null; }; /** * Cold-editing without a spinner: the editor opens instantly from the capture, * the first keystroke buffers locally AND signals the host to warm the box, and on * warm the buffer flushes IF the live file still matches the captured base — else a * non-blocking conflict is surfaced and nothing is overwritten. * * The base guard compares the live content at flush time against `baseContent` * (the exact bytes the editor loaded). This is equivalent to the sha256 compare * the design specifies — M1 leaves `baseHash` null, so the loaded content IS the * authoritative base — and avoids async Web Crypto in the render path. */ export declare function useWorkspaceEdit(sessionId: string | null | undefined, options?: UseWorkspaceEditOptions): UseWorkspaceEditResult;