/** * Per-(document, field) persistence layer for the Generate dialog. * * What it stores: * - `brief` — current textarea text + status + the AI-generated prompt and * the doc-content hash that produced it. Lets us skip an LLM * call on every popup open when the doc hasn't changed. * - `run` — the most recent generation: runId, mode, status, outputs. * Lets us restore a generation popup (live or completed) on * reopen, and resume polling if the run is still in flight. * * Industry-standard model: * - Cache is a HINT, server is source of truth. On run-restore, we always * refresh from `client.runs.get()` (or `freestyle.get()`) before trusting * anything other than the runId itself. * - Per-entry schema version. On version mismatch, the entry is dropped * silently — no migrations, no user-visible drama. * - Reads never throw. localStorage may be disabled (Safari private mode), * JSON may be corrupt, the shape may have drifted. Failure → return null. * - Writes are best-effort. Storage quota errors are swallowed. * - GC sweep on plugin init drops entries older than the longest TTL. * * Scope: * `lamina:dialog:{documentId}:{fieldName}` — different fields on the same * doc don't share state. Drafts (`drafts.foo`) and published (`foo`) are * different docs from this layer's POV (intentional V1 simplicity). */ import type { GeneratedOutput } from '../types.js'; declare const SCHEMA_VERSION: 1; /** Older runs are treated as expired at READ time. CDN URLs may be dead. */ export declare const RUN_TTL_MS: number; /** Older brief caches are treated as expired at READ time. */ export declare const BRIEF_TTL_MS: number; /** * Preview-state cache TTL. Preview is the post-Generate-click, pre-dispatch * window where the agent has produced a plan and (sometimes) a form for the * user to fill. We persist this short-term so a user who closes the dialog * mid-form-fill doesn't lose what they typed. Beyond an hour, the doc may * have changed enough that re-running preview is more correct than restoring. */ export declare const PREVIEW_TTL_MS: number; export type CachedBriefStatus = 'placeholder' | 'ai-loading' | 'ai-ready' | 'user-edited' | 'chip-applied'; export interface BriefCache { /** Current textarea text. */ text: string; /** State-machine status when the entry was last written. */ status: CachedBriefStatus; /** * The last AI-generated brief (if any). Independent from `text` because the * user may have edited or chip-replaced it; we still want to know what the * AI produced for the cached `docHash` so we don't refetch unnecessarily. */ aiPrompt: string | null; /** * Hash of canonical doc fields that produced `aiPrompt`. On reopen we * recompute the hash; matching → use cached AI brief, mismatch → refetch. */ docHash: string; /** When this brief entry was last written. Used for BRIEF_TTL_MS check. */ cachedAt: number; } export type CachedRunStatus = 'generating' | 'completed' | 'failed'; export type CachedRunMode = 'app' | 'freestyle'; export interface RunCache { runId: string; mode: CachedRunMode; status: CachedRunStatus; outputs: GeneratedOutput[]; progress: number | null; error: string | null; /** When `autoGenerate` returned. Used for RUN_TTL_MS check. */ startedAt: number; /** What was sent in the brief (for display/regenerate). */ brief: string; appId?: string; numVariants?: number; } /** * Snapshot of the post-preview, pre-dispatch state — the agent has decided * which app to use and the user is mid-form (or mid-confirmation). Persisted * so a user who closes the dialog while filling the form can come back and * pick up where they left off. * * Does NOT persist for runs that auto-dispatched (no form needed) — those * fall straight into the `run` cache below. */ export interface PreviewCache { /** When this preview was captured. Subject to PREVIEW_TTL_MS. */ cachedAt: number; /** * Opaque structured-clone-safe payload of the SDK's PreviewRunResult. * We intentionally store the whole shape so plugin code can rehydrate * `previewResult` without re-running preview-run. */ previewResult: Record; /** What the user has typed/picked into the form so far, keyed by field name. */ editedInputs: Record; } export interface DialogState { v: typeof SCHEMA_VERSION; /** Last write time. Used for GC sweep. */ updatedAt: number; brief: BriefCache | null; run: RunCache | null; /** Pre-dispatch preview snapshot. Lost on dispatch, run completion, or cancel. */ preview: PreviewCache | null; } /** * Test-only: reset the cached storage probe. Lets unit tests inject a fresh * mock storage between runs without restarting the process. */ export declare function __resetStorageCacheForTests(): void; /** * Read the dialog state for a (doc, field). Returns null when: * - docId or fieldName missing * - no entry exists * - entry is corrupt / wrong schema version * - localStorage unavailable * * Never throws. Self-heals corrupt entries (deletes them). */ export declare function readDialogState(docId: string | undefined, fieldName: string | undefined): DialogState | null; /** * Shallow-merge `patch` into the stored entry. Top-level fields (`brief`, * `run`) are replaced wholesale by the patch — pass `{ run: null }` to clear * just the run while keeping the brief, etc. * * No-ops silently when docId/fieldName missing or storage unavailable. */ export declare function patchDialogState(docId: string | undefined, fieldName: string | undefined, patch: { brief?: BriefCache | null; run?: RunCache | null; preview?: PreviewCache | null; }): void; /** * Drop the entry entirely for a single (doc, field). No-ops silently when * args missing. */ export declare function clearDialogState(docId: string | undefined, fieldName: string | undefined): void; /** * Wipe all `lamina:dialog:*` entries — every doc, every field. Intended * for "Clear all cached dialog state" admin / debug actions, not for * routine use. Single calls only — no confirmation handled here. */ export declare function clearAllDialogState(): number; /** * Diagnostics — count of stored entries and their total stringified size. * Useful for surfacing "X cached fields, Y KB" in a UI or for tests. */ export declare function getDialogStateStats(): { count: number; bytes: number; }; /** * Sweep all `lamina:dialog:*` entries; drop ones older than MAX_ENTRY_AGE_MS * or with the wrong schema version. Safe to call repeatedly. Intended to fire * once on plugin init. */ export declare function gcDialogState(): void; export {};