/** * Editor API — Batch save queue + session auth * Queues translation edits and flushes them in a single batch API call. * Uses editor session JWTs (es_ prefixed) for authentication with auto-renewal. */ import type { CapturedSnapshot, SnapshotTextEntry } from './snapshot-capture.js'; export interface QueuedTranslation { key: string; locale: string; namespace: string; text: string; styles: Record; } export interface SnapshotListItem { snapshotUuid: string; pageUrl: string; title: string; capturedBy: string; createdAt: string; expiresAt: string; textEntryCount: number; status: string; } export interface SnapshotDetail { snapshotUuid: string; pageUrl: string; title: string; html: string; textEntries: SnapshotTextEntry[]; viewport: { width: number; height: number; devicePixelRatio: number; }; createdAt: string; expiresAt: string; } export interface TargetStatus { text: string; rowState: string; isConfirmed: boolean; /** APPROVED for delivery — NOT the same as actually served. This is the label * AND the action of the PM's Mark/Unmark toggle, so it must keep meaning * approval. For "would GET /translations serve this?", read willDeliver. */ isReady: boolean; /** Whether GET /translations would ACTUALLY serve this key right now. * Server-derived from the delivery axes (approved-or-locked, non-empty * target, translatable source, live target) — a sibling fact to isReady, * not a restatement: an APPROVED row with an EMPTY target is approved and * still not delivered. Optional for backward compatibility — a backend * predating the field omits it, leaving this undefined (fetchStatus returns * the parsed body unmodified, so absence survives as undefined rather than * false). Callers must fall back to isReady, not to false. */ willDeliver?: boolean; isLocked: boolean; /** Whether the calling editor session can confirm/reject this row. * Server-computed via can_user_edit_row() — reflects PM/admin override, * postreview, and per-stage assignment. False for rows past the user's * stage (e.g. translator looking at a row they've already pushed forward). * Optional for backward compatibility with older backend versions. */ canEdit?: boolean; /** Current workflow stage index. Optional for backward compatibility. */ stageIndex?: number; } /** One stage in the project's workflow. */ export interface WorkflowStageInfo { index: number; name: string; role: string | null; /** True for the Postreview stage — rows at/past it cannot Advance. */ isPostreview: boolean; } /** Workflow context for the calling editor session. Cached per locale with a * TTL (see fetchWorkflowInfo) — the workflow can change mid-session and can * differ per locale via a per-target override. */ export interface EditorWorkflowInfo { stages: WorkflowStageInfo[]; /** Stage indices the user is assigned to via UserTargetLink. Empty for PM/admin. */ userAssignedStages: number[]; isPmAdmin: boolean; workflowMode: 'per_row' | 'per_file'; /** Number of non-Postreview (work) stages — the server's advance boundary: * a row with stageIndex >= this is in postreview and cannot Advance. * 0 also covers a project with no workflow configured at all. */ workStageCount: number; } /** Whether a row sits at/past the workflow's postreview boundary — the same * check the server's advance endpoint enforces ("Row is already past the * final stage"). False when workflow info or the row's stage is unknown: * the sidebar then keeps its old behavior and lets the server decide. */ export declare function isRowAtPostreview(info: EditorWorkflowInfo | null, stageIndex: number | undefined): boolean; /** Per-key outcome for a batch item the server refused to write (per-row * authorization). The batch analogue of the dashboard save's 423 "locked" / * 403 "forbidden" rejection with a row snapshot. */ export interface DeniedRow { key: string; /** "locked" (row locked, PM-only), "forbidden" (not assigned to this row/stage), * or "busy" (target's MT fill in flight — transient, the edit is retried). */ reason: 'locked' | 'forbidden' | 'busy' | string; /** Server's current translation text (null if the row doesn't exist yet). */ current_text: string | null; } export type StatusCallback = (status: 'unsaved' | 'saving' | 'saved' | 'error', message?: string) => void; export declare class EditorAPI { private queue; private apiBase; private sessionToken; private projectId; private flushInterval; private renewalInterval; private onStatus; private onFlushed; private onSessionExpired; private workflowInfoCache; private static readonly WORKFLOW_INFO_TTL_MS; /** Whether this session may lazily create rows for keys with no target row * yet. Mirrors the server's create gate (creation is stage-0 work — PM/admin * or stage-0 assignees); refreshed from targets/status `can_create`. * Default true: older backends don't send it and don't gate creates. */ canCreateMissing: boolean; /** True while this locale's target has an MT fill queued/running (refreshed * from targets/status `target_busy`). canEdit stays the permission truth * during the window; stage transitions would 423 until it clears, and * saves are denied as "busy" and auto-retried by the flush loop. */ targetBusy: boolean; constructor(apiBase: string, sessionToken: string, projectId: string); setStatusCallback(cb: StatusCallback): void; setFlushedCallback(cb: (keys: string[]) => void): void; setSessionExpiredCallback(cb: () => void): void; /** * Start auto-flush (30s interval + visibilitychange) and session renewal (60s check) */ startAutoFlush(): void; /** * Stop auto-flush and flush remaining items */ stopAndFlush(): Promise; /** * Queue a translation for saving */ queueSave(item: QueuedTranslation): void; /** * Flush all queued translations to the server */ flush(): Promise; private busyRetryTimer; /** * Re-flush edits refused with reason "busy" (target MT fill in flight). * One pipeline sweep (~30 s worst case) clears the fill; a single pending * timer is enough — each retry re-schedules itself while busy rows remain. */ private scheduleBusyRetry; private statusAbort; /** * Fetch translation status for a batch of keys (includes unconfirmed rows). * Cancels any in-flight status request. Caps at 200 keys per request. */ fetchStatus(keys: string[], locale: string, namespace: string): Promise>; get pendingCount(): number; /** * Fetch workflow context (stages, user's stage assignments, PM/admin flag, * advance boundary) for the EFFECTIVE workflow of `locale` — the server * resolves the per-target override (`target ?? project`) when a locale is * given. Cached per locale with a 5-min TTL so a mid-session workflow * change from the dashboard settings is picked up without a page reload. * Returns null if the fetch fails; callers should hide the strip in that case. */ fetchWorkflowInfo(locale?: string): Promise; /** * Check if session is within 5min of expiry, and renew if so. * * The threshold is intentionally generous because background tabs throttle * setInterval to once-per-minute (Chrome) or even less aggressively, and * a sleeping laptop pauses timers entirely. Renewing early gives us * headroom for those cases — the renewal interval still runs before the * 15min server-side TTL elapses even if it fires a minute or two late. */ private maybeRenewSession; /** * Attempt one renewal and signal whether the token actually changed. * Used by the 401-retry path in API calls. */ private tryRenew; private renewSession; private getSessionExp; /** * Authenticated fetch with one-shot 401 retry. If the request comes back * 401, we attempt a session renewal and replay the request once with the * new token. If renewal fails (token past the server's 2min grace), the * caller still gets a 401 and falls through to handleSessionExpired. * * Always passes `init.signal` straight through so abortable callers * (e.g. fetchStatus) still cancel correctly. */ private authFetch; private handleSessionExpired; private handleVisibility; /** * Upload a captured snapshot. Returns the created snapshot's UUID so the * caller can build a share URL (`?__lr_snapshot={uuid}`). */ createSnapshot(snapshot: CapturedSnapshot): Promise<{ snapshotUuid: string; } | null>; /** * Re-capture an existing snapshot. Server appends a new version and keeps * the same `snapshotUuid`. Returns the new version number on success. */ recaptureSnapshot(snapshotUuid: string, snapshot: CapturedSnapshot): Promise<{ snapshotUuid: string; version: number; } | null>; /** * Fetch a single snapshot by UUID. Returns null on auth failure or 404. */ getSnapshot(snapshotUuid: string): Promise; /** * List all snapshots the current editor session can see. */ listSnapshots(): Promise; /** * Delete (soft) a snapshot by UUID. */ deleteSnapshot(snapshotUuid: string): Promise; /** * Workflow transitions (Phase 3). Per-key endpoints on the integrations API * that mirror the dashboard's row-level transitions but are addressable by * composite editor key (locale:namespace:auto_xxx). * * Returns the parsed JSON on success, or null on failure (caller usually * shows a toast / re-fetches status). */ transitionTarget(composite: string, action: 'confirm' | 'unconfirm' | 'reject' | 'advance' | 'mark-ready' | 'unmark-ready'): Promise | null>; } //# sourceMappingURL=editor-api.d.ts.map