wordinweb 0.1.22 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -2
- package/dist/chunk-XGTWJMDG.js +38208 -0
- package/dist/chunk-XGTWJMDG.js.map +1 -0
- package/dist/collab.d.ts +1281 -0
- package/dist/collab.js +4909 -0
- package/dist/collab.js.map +1 -0
- package/dist/index-C2J9egkh.d.ts +4314 -0
- package/dist/index.d.ts +2 -2401
- package/dist/index.js +13 -35778
- package/dist/index.js.map +1 -1
- package/package.json +13 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/collab.tsx","../../collab/src/intents.ts","../../collab/src/transform.ts","../../collab/src/apply.ts","../../collab/src/replica.ts","../../collab/src/bundle.ts","../../collab/src/rebase.ts","../../collab/src/protocol.ts","../../collab/src/media.ts","../../collab/src/e2ee.ts","../../collab/src/connection.ts","../../collab/src/invert.ts","../../collab/src/validate.ts","../../collab/src/session.ts","../../collab/src/hash.ts","../../collab/src/enc-connection.ts","../../collab/src/ws-transport.ts","../../collab/src/liveness.ts","../src/bundle-store.ts"],"sourcesContent":["import { useEffect, useMemo, useRef, useState, createElement, type ReactNode } from \"react\";\nimport type { DocxDocument, EncodedCaret } from \"@wordinweb/core\";\nimport { DocxView, type DocxViewApi } from \"./index.js\";\nimport { DocxToolbar, type ToolbarFeature, type ToolbarMode } from \"./toolbar.js\";\nimport {\n CollabConnection,\n EncryptedCollabConnection,\n stretchShareCode,\n toSuggestions,\n arrivalMode,\n OFFLINE_TAIL_CAP,\n applyIntentScoped,\n resyncScope,\n unionScopes,\n type Scope,\n createWebSocketTransport,\n monitorTransport,\n BundlePersister,\n draftKey,\n supersededKey,\n type ConnectionState,\n type LivenessOptions,\n type BundleStore,\n type ParticipantProfile,\n type RosterEntry,\n type Intent,\n type PresencePosition,\n type UndoOutcome,\n type WriteStatus,\n} from \"@wordinweb/collab/client\";\n\n/**\n * React binding for wordinweb collaboration. Imported from the SEPARATE\n * `wordinweb/collab` entry so that non-collab apps never pull the collab\n * engine into their bundle (plan doc 07: unreachable beats shakeable — a\n * local-only `import { DocxView } from \"wordinweb\"` has no path to this file).\n */\n\nexport interface UseCollabOptions {\n /** WebSocket URL of the collab server (ws://… / wss://…). */\n url: string;\n /** Document id to join (the magic-link id, plan doc 11). */\n docId: string;\n /** This client's stable id (persist per browser/tab). */\n clientId: string;\n /** Optional auth token (JWT minted by the app's backend, plan doc 07). */\n token?: string;\n /** Construct the socket. Defaults to `new WebSocket(url)`; injectable for\n * tests / custom transports. */\n createSocket?: (url: string) => WebSocket;\n /**\n * Bundle persistence + resume (plan doc 12 §4/§5). When set, the hook:\n * resumes from a stored bundle if one exists (replaying pending intents,\n * idempotently) instead of joining cold; persists the confirmed state on a\n * ~1s throttle after every change, plus a best-effort flush on pagehide/\n * hidden; and on an epoch change (someone re-seeded while away) preserves\n * the superseded bundle as a draft (`<docId>#draft-<oldGenesis>`) before\n * adopting the server's state — the fork rule made visible, never a silent\n * merge. Browser apps pass an `IndexedDbBundleStore` (from `wordinweb`);\n * tests inject the in-memory store. Without it, behavior is unchanged\n * (join cold, keep nothing).\n */\n store?: BundleStore;\n /** Claim the identity from an existing live connection (doc 12 §7 \"use\n * here instead\"): set after an `already-open` refusal and remount. */\n takeover?: boolean;\n /**\n * E2EE mode (doc 13): the document master key from the link's `#k=`\n * fragment. Its PRESENCE selects the encrypted connection — mode is\n * derived from the link, never the wire; a plaintext welcome is\n * hard-refused (`mode-downgrade`). */\n docKey?: string;\n /** Share code (doc 13 §7) when the doc has one — stretched client-side\n * and mixed into key derivation + sent as the hello proof. */\n shareCode?: string;\n /** Owner capability token (doc 14 §2.5): held by the seeder (returned by\n * go-live, kept in their bundle), never in the shared link. Presence of\n * a valid token unlocks the admin controls. */\n ownerToken?: string;\n /**\n * HTTP origin of the media relay (plan doc 16 §3), e.g.\n * \"http://localhost:1234\" — the same server the WebSocket points at.\n * Image bytes travel over these routes and NEVER over the sequencer.\n * Without it the room has no media duties and the image toolbar stays\n * inert, so an app that doesn't want media simply omits it.\n */\n httpBase?: string;\n /** Display profile sent at join (doc 14 §2) — self-asserted; persist it in\n * localStorage next to the clientId so identity is stable per browser. */\n profile?: ParticipantProfile;\n /**\n * Heartbeat + reconnect tuning. Defaults suit a real network (15s probe,\n * 10s answer deadline, 6 retries); tests inject short values and a fake\n * clock. `maxRetries` is how many backoff attempts run before the session\n * gives up and reports `lost` — the point at which a human has to act.\n */\n liveness?: LivenessOptions & { maxRetries?: number };\n /**\n * Bound on the offline tail (doc 15 §4.3) — how many intents may be\n * recorded while disconnected before the editor gates read-only with an\n * explicit \"offline limit\" state. Defaults to OFFLINE_TAIL_CAP; injectable\n * so tests can reach the cap without recording thousands of edits.\n */\n offlineTailCap?: number;\n}\n\nexport interface CollabSession {\n /** The live document to render (null until the welcome arrives). */\n doc: DocxDocument | null;\n /** Monotonically increases on every reconciled change — a cheap re-render\n * signal (the doc object may be reloaded by reconciliation). */\n version: number;\n /**\n * Increases only when the RENDERED DOCUMENT changed outside the editor —\n * remote applies, optimistic toolbar ops, reloads, self-heals, media\n * installs. The editor-driven typing path (submit + own echo) leaves it\n * untouched: the editor already mutated AND painted the doc, so repainting\n * again on `version` queued a redundant whole-document relayout per\n * keystroke — invisible on 5 pages, catastrophic past DocxView's\n * background-layout threshold (a 500-page doc relaid out asynchronously\n * behind an inert container, per keystroke). DocxView's `renderSignal`\n * rides THIS; `version` remains the observer/bookkeeping signal.\n */\n renderVersion: number;\n /**\n * Drain the union of the dirty scopes behind `renderVersion` since the last\n * take — what the repaint answering it must relayout. A remote text edit\n * reports its one paragraph, so DocxView relayouts that paragraph\n * incrementally instead of the whole document (the far-page repaint stall);\n * structural or unverifiable intents, doc reloads, and media installs\n * report `doc`, which keeps today's whole-document path. Null means nothing\n * dirty was recorded since the last take (an earlier paint covered it) and\n * the repaint may be skipped. Consumed on the repaint itself so a coalesced\n * repaint sees every batched intent's scope.\n */\n takeRenderScope: () => Scope | null;\n /** Increases only when reconciliation RELOADED the document (a true\n * conflict). The editor re-mounts on this; between reloads it updates in\n * place (no flash for the common non-conflicting edits). */\n docEpoch: number;\n /** True once joined and the snapshot is loaded. */\n ready: boolean;\n /** Submit a local edit the editor ALREADY applied to the live doc\n * (bookkeeping filled by the connection; no local re-apply). */\n submit: (intent: Omit<Intent, \"clientId\" | \"clientSeq\" | \"base\">) => void;\n /** Submit an operation NOT yet applied locally: it is applied optimistically\n * through the same canonical applyIntent code the server runs, so the local\n * result is byte-identical to every replica by construction. Used for\n * toolbar/API commands (insert chart, set link, page layout, ...). */\n submitOp: (intent: Omit<Intent, \"clientId\" | \"clientSeq\" | \"base\">) => void;\n /** Broadcast this client's cursor/selection. */\n setPresence: (pos: PresencePosition | null) => void;\n /** Allocate carried node ids (sub-range format / split / insert). */\n allocIds: (n: number) => number[];\n /** Remote participants' latest cursor/selection positions. */\n presence: Record<string, PresencePosition | null>;\n /** Set if the server refused the connection (e.g. version mismatch). */\n refused: string | null;\n /**\n * WHETHER THIS CLIENT CAN REACH THE SEQUENCER AT ALL.\n *\n * Distinct from `refused`, which means the server answered and said no —\n * this means nobody answered. A refusal ends the session; this is a\n * transport fault that usually heals by itself.\n *\n * - `live` the last heartbeat round trip completed.\n * - `reconnecting` the socket dropped; a backoff retry is scheduled. Most\n * drops pass through here and back to `live` unnoticed,\n * so UI should stay quiet or subtle here.\n * - `lost` retries exhausted. Show something modal: the document is\n * safe but nothing further will sync until a human acts.\n *\n * A NON-LIVE CONNECTION DOES NOT BLOCK WRITES (doc 12 §5: offline editing\n * is first-class). \"I cannot reach the server\" is a different fact from\n * \"the server said no\": nothing typed offline is doomed — it applies\n * locally, is recorded to the offline tail, persists with the bundle, and\n * replays on reconnect (doc 15's arrival ladder). {@link offline} is the\n * state a consumer renders for it; {@link writesBlocked} stays reserved\n * for genuine refusals, where accepting keystrokes would be a lie.\n */\n connection: ConnectionState;\n /**\n * OFFLINE EDITING IS ACTIVE (doc 15 §2 / doc 12 §5): the sequencer is\n * unreachable, a document is in hand, and nothing has refused this\n * client's writes — so edits apply locally and accumulate in the durable\n * offline tail instead of being dropped.\n *\n * - `editsHeld` how many intents the tail currently holds. Show it: the\n * difference between \"editing is paused\" and \"your changes are being\n * kept (N so far)\" is the whole point of the state.\n * - `capped` the tail hit its bound (doc 15 §4.3). Recording has STOPPED\n * and {@link writesBlocked} is raised — loudly, with this flag saying\n * why, never by silently discarding keystrokes. The escape hatches are\n * reconnecting or downloading a copy.\n *\n * Null while the connection is live, and null when a refusal already\n * blocks writes (a viewer's link does not become writable by going\n * through a tunnel). On reconnect the tail replays: silently as ordinary\n * edits when the session is the same epoch, else via {@link arrival}.\n */\n offline: { editsHeld: number; capped: boolean } | null;\n /**\n * Retry the connection immediately, resetting the backoff and the attempt\n * budget. Wired to a \"Try again\" control on the `lost` UI — from `lost`\n * nothing else will ever retry, because exhausting the budget is precisely\n * the statement that automatic recovery has stopped.\n */\n reconnect: () => void;\n /**\n * The server announced it will END this session, and this is the grace\n * period before it does. `inMs` is the measured remainder at the moment the\n * warning arrived — tick a countdown from it locally rather than expecting\n * refreshed messages.\n *\n * - `idle` cancellable: any qualifying activity (an accepted edit, an\n * admin action, a media transfer, a join — never presence)\n * resets the clock and this goes back to null on its own.\n * - `lifetime` NOT cancellable by anything; it counts down to the ending.\n *\n * Null whenever no deadline is approaching — including after ANY session\n * end (kick, refusal), because a countdown that outlived its session would\n * keep promising an ending that already happened.\n */\n sessionWarning: { reason: \"idle\" | \"lifetime\"; inMs: number } | null;\n /** True after an edit was refused under the owner's read-only lock\n * (doc 14 §2.5). NON-FATAL: the live view keeps working.\n *\n * LEGACY/FALLBACK now: sticky until reload or `retryWrites`, because a\n * refusal can only ever say \"blocked\". Prefer {@link writesBlocked}, which\n * reads the server's positive roster status when one is published and only\n * falls back to this against an older server. */\n readOnlyBlocked: boolean;\n /**\n * The server has told this client it is NOT the owner of this document.\n *\n * Holding an `ownerToken` string is not the same as it being valid — a room\n * re-seeded into a new epoch mints a fresh one, leaving the old token\n * truthy in a browser that now has no admin rights. The welcome carries no\n * owner flag, so until an admin op is attempted this is genuinely unknown;\n * a `not-owner` refusal is the authoritative answer.\n *\n * Consumers should HIDE admin controls when this is true rather than\n * disable them: an offered control that cannot work is worse than an absent\n * one, and the refusal it produces is not a session failure.\n */\n notOwner: boolean;\n /**\n * THE SERVER WILL NOT ACCEPT THIS CLIENT'S WRITES. Consumers must render the\n * editor READ-ONLY on this, never merely annotate it: an editable surface\n * over a server that refuses the writes applies every keystroke locally and\n * then loses it, which is silent data loss that looks like it worked.\n *\n * One predicate rather than a per-reason check, so a new blocking state\n * inherits the gate instead of needing to remember it.\n *\n * NOW READS THE SERVER'S ROSTER STATUS (`RosterEntry.write`), which closed\n * the seam this comment used to describe. That matters in both directions:\n * the status is present AT JOIN, so edit 1 is gated rather than applied and\n * then healed away; and it is POSITIVE, so a lift arrives on its own instead\n * of leaving the client in viewer mode until reload. Against a server that\n * publishes no status it falls back to the refusal-derived flag — never to\n * \"allowed\", which would restore both bugs at once.\n *\n * The status also distinguishes the three server conditions the single\n * `read-only` refusal could not (owner lock, per-client demotion, viewer\n * token), so a UI can finally say which one applies rather than inventing a\n * distinction it could not see.\n *\n * A DEAD CONNECTION IS DELIBERATELY NOT A BLOCK — that is the refusal /\n * unreachable split (doc 12 §5). This predicate means \"nothing you type\n * can ever land\"; a dropped socket means \"nothing you type can land YET\",\n * and the offline tail is what keeps the two honest: disconnected edits\n * apply locally, persist durably, and replay on reconnect, so accepting\n * them is not a lie. The one offline condition that does fold in is the\n * tail CAP ({@link offline}.capped): past it an edit genuinely cannot be\n * kept, so the gate closes loudly rather than recording silently stopping.\n * An earlier revision folded every non-live connection in here; that\n * over-reached — it was written for server refusals and extended to\n * transport faults, which made first-class offline editing impossible.\n *\n * A REFUSED SESSION FOLDS IN TOO, by the same rule. A refusal ends the\n * session — no socket, nothing to sequence against — so any document still\n * on screen (e.g. behind a \"session ended\" dialog) must be read-only, and\n * it inherits that from this predicate rather than from a refusal-specific\n * gate someone could forget to compose.\n */\n writesBlocked: boolean;\n /**\n * WHY the server refuses this client's writes, straight from the roster.\n *\n * UNDEFINED means the server published nothing (an older build), NOT that\n * writing is allowed — the same contract as the media limit's null. A\n * consumer must keep its copy generic in that case rather than naming a\n * cause it was never told.\n *\n * The three causes are genuinely different to the person reading the banner:\n * `owner-lock` may lift at any moment, `demoted` is about them specifically,\n * and `viewer-role` is a property of their link that nothing the owner does\n * in-session will change. Telling that last group to wait for the owner is\n * telling them to wait for something that will never happen.\n */\n writeStatus?: WriteStatus;\n /**\n * Optimistically clear the FALLBACK block and let the user try again.\n *\n * BELT AND BRACES now, not the primary escape: where the server publishes a\n * write status, a lift arrives on its own and the editor becomes writable\n * without anyone clicking anything. This remains for the older-server\n * fallback path — where the block is sticky, no lift is announced, and\n * attempting a write is the only way to discover the lock is gone — and as a\n * manual override if a status update is ever missed.\n */\n retryWrites: () => void;\n /**\n * Set when a resume landed in a different epoch than the stored bundle\n * (doc 12 §5 case 2): the session took the server's state; the old copy\n * was saved as a draft. UI copy: \"restored by another participant — your\n * offline copy is saved as a draft.\" Null otherwise.\n */\n epochChanged: { from: string; to: string } | null;\n /** Session roster (doc 14 §2): everyone who joined this session, with\n * connection state — the identity keyspace presence/attribution share. */\n roster: RosterEntry[];\n /** Rename/recolor this participant (server sanitizes + fans out). */\n setProfile: (profile: ParticipantProfile) => void;\n /** Attribution layer 1 (doc 14 §3): recent applied entries\n * {seq, clientId, kind} — join clientId to `roster` for names/colors. */\n activity: { seq: number; clientId: string; kind: string }[];\n /** Owner admin ops (doc 14 §2.5) — no-op unless this connection proved\n * the owner token; the server refuses `not-owner` otherwise. */\n admin: (action:\n | { op: \"kick\"; clientId: string }\n | { op: \"readOnly\"; on: boolean }\n | { op: \"setRole\"; clientId: string; role: \"editor\" | \"viewer\" }) => void;\n /**\n * Offline reconciliation (doc 15 arrival ladder). When a rejoin lands in\n * a DIFFERENT epoch than the tail was recorded against, the recommended\n * mode is `suggest` (replay as tracked changes; ≤ the suggest threshold)\n * or `draft` (large tails). `structural` counts the tail intents that\n * have no suggestion form (splits/merges/tables/format — rebase.ts):\n * they are NOT replayed by `suggest` and survive in the banked draft\n * slot instead — say so, or their absence reads as silent loss. A\n * same-epoch rejoin never surfaces here: its tail replays silently\n * through the ordinary submit path, like a large pending queue.\n * Null when there's nothing to reconcile. `reconcile()` runs the choice.\n */\n arrival: { mode: \"suggest\" | \"draft\"; tailLength: number; structural: number } | null;\n reconcile: (mode: \"suggest\" | \"draft\") => void;\n /** How many times the connection self-healed a drifted optimistic replica\n * (encrypted mode's quiescent mirror check — the B6a class). Telemetry;\n * the heal itself is automatic and already reflected in doc/docEpoch. */\n selfHeals: number;\n /**\n * Submits the connection DROPPED because it was not ready yet (no replica\n * to apply against, no confirmed seq, no key to seal with). Should stay 0:\n * the editor is gated on `ready`, so nothing user-driven can reach that\n * guard. A non-zero value means some caller is submitting before the\n * welcome — which is a silent edit loss, and was the shape of B13's\n * 231-intent gap. Telemetry, deliberately visible rather than swallowed.\n */\n droppedPreReady: number;\n /**\n * Persistence writes that FAILED (quota exceeded, blocked storage, private\n * mode). Non-zero means this browser's stored bundle is stale — and in a\n * zero-custody design that bundle IS the durable copy, so \"saved\" is\n * currently a lie. Consumers should SHOW this, not just log it: the user\n * can still rescue the document with the download button, but only if they\n * are told. Sticky (never decremented): the doubt persists until reload.\n */\n persistErrors: number;\n /**\n * The connect-path storage read exceeded its deadline, so this session\n * joined COLD instead of resuming. Says nothing about whether storage is\n * full — a slow or blocked store looks identical from here, and\n * conflating the two with {@link persistErrors} told one user their\n * storage was full when it held a few megabytes.\n */\n storeSlow: boolean;\n /**\n * Submits whose seal or transport send THREW after the edit was already\n * applied optimistically — i.e. lost edits, counted at the far end of the\n * path from droppedPreReady.\n *\n * The two together are a PARTITION, which is the point: droppedPreReady is\n * \"refused before it was ever applied\", this is \"accepted locally then lost\n * on the way out\". A swarm run that loses N intents can now subtract both\n * from N and see what remains unexplained, instead of a night of guessing —\n * see B13, where exactly that arithmetic was missing.\n */\n sendFailures: number;\n /** Upload image bytes to the relay and get the address fields an\n * insertImage intent must carry (doc 16 §5.1). Null = the relay refused,\n * and the caller must not reserve anything. Always null when the app\n * supplied no `httpBase`. */\n uploadMedia: (bytes: Uint8Array) => Promise<{ blobSha: string; bytesLen: number; iv?: string } | null>;\n /**\n * Largest single upload the relay accepts, in bytes, as published in the\n * welcome — so an oversized file can be refused locally instead of after a\n * full seal-hash-upload round trip.\n *\n * NULL MEANS \"NO PUBLISHED LIMIT\", and the only correct response to it is to\n * skip the check: a server that publishes nothing still enforces its real\n * limit, so a client that invents a default either blocks uploads that\n * would have succeeded or promises a size that will be refused.\n */\n mediaMaxBlobBytes: number | null;\n /**\n * Reverse this participant's last SEQUENCED action (plan doc 03 Phase 8).\n * The inverse is computed locally and submitted as an ORDINARY intent, so\n * it converges, broadcasts, and — if the target has since been deleted or\n * changed — is rejected cleanly on every replica alike.\n *\n * The outcome is reported rather than thrown so the UI can say the right\n * thing: `cannot-undo` means the last action has no inverse yet (a\n * different message from having nothing to undo), and `unavailable` means\n * this connection has no collaborative undo at all — today the plaintext\n * connection, whose authority lives on the server.\n */\n undoLast: () => UndoOutcome;\n}\n\n\n/**\n * Connect to a collab document and track its live state. Returns a\n * CollabSession the app injects into `<DocxView collab={session} />` (the\n * DocxView `collab` prop is typed structurally, so the react package needs no\n * runtime dependency on this module).\n */\n/** Sentinel distinguishing \"storage did not answer\" from \"nothing stored\". */\nconst TIMED_OUT = Symbol(\"store-timeout\");\n\n/**\n * How long a storage read may take before the join gives up on it.\n *\n * A healthy IndexedDB read is single-digit milliseconds, so this is ~1000x\n * headroom and still only two seconds of spinner before the user is connected\n * anyway. Longer would be more patient with a slow disk at the cost of every\n * user with a broken one staring at nothing.\n */\nconst STORE_DEADLINE_MS = 2000;\n\n/**\n * Resolve `p`, or `fallback` if it has not settled within `ms`.\n *\n * Deliberately RESOLVES on timeout rather than rejecting: every caller here is\n * on the connect path, where the right answer to \"storage is not answering\" is\n * to carry on without it, not to abort the connection.\n */\nfunction withDeadline<T>(p: Promise<T>, ms: number, fallback: T): Promise<T> {\n return new Promise<T>((resolve) => {\n let done = false;\n const t = setTimeout(() => { if (!done) { done = true; resolve(fallback); } }, ms);\n void p.then(\n (v) => { if (!done) { done = true; clearTimeout(t); resolve(v); } },\n () => { if (!done) { done = true; clearTimeout(t); resolve(fallback); } },\n );\n });\n}\n\nexport function useCollab(opts: UseCollabOptions): CollabSession {\n const { url, docId, clientId, token, createSocket, store, profile, takeover, docKey, shareCode, ownerToken, httpBase } = opts;\n const connRef = useRef<CollabConnection | null>(null);\n const [doc, setDoc] = useState<DocxDocument | null>(null);\n const [version, setVersion] = useState(0);\n /** Repaint signal (see CollabSession.renderVersion): follows the\n * connection's docVersion, plus local bumps for offline toolbar applies. */\n const [renderVersion, setRenderVersion] = useState(0);\n /** Last connection docVersion folded into renderVersion — the two sources\n * (connection counter, local offline bumps) must not collide. */\n const lastDocVersionRef = useRef(0);\n /** Dirty scope of LOCAL offline applies not yet drained by a repaint —\n * the offline counterpart of the connection's takeRenderScope. */\n const renderScopeRef = useRef<Scope | null>(null);\n const [docEpoch, setDocEpoch] = useState(0);\n const [ready, setReady] = useState(false);\n const [presence, setPresence] = useState<Record<string, PresencePosition | null>>({});\n const [refused, setRefused] = useState<string | null>(null);\n /**\n * Announced endings as ABSOLUTE deadlines, one slot per reason.\n *\n * Not a single slot, because the two deadlines can be pending at once (a\n * room nearing its age cap that also goes quiet), and a single slot loses\n * one of them: the idle warning would overwrite the lifetime warning, and\n * the `cleared` that follows an edit would then take the lifetime countdown\n * down with it — leaving no warning at all for an ending that is still\n * minutes away and cannot be cancelled.\n *\n * Absolute rather than remaining, so the surviving deadline resumes with the\n * time it actually has left rather than the remainder it was announced with.\n */\n const [deadlines, setDeadlines] = useState<{ idle: number | null; lifetime: number | null }>({ idle: null, lifetime: null });\n /** Both slots empty, without re-rendering when they already are. */\n const clearDeadlines = () =>\n setDeadlines((d) => (d.idle === null && d.lifetime === null ? d : { idle: null, lifetime: null }));\n /**\n * The SOONEST pending ending — that is the one the user needs, and when it\n * is the cancellable one, cancelling it reveals the other rather than\n * clearing the screen. `inMs` is measured here and only here: the memo\n * recomputes exactly when a deadline is set or cleared, so the number the\n * consumer starts its countdown from is fresh at the moment it changes, and\n * the object identity is stable in between (a consumer keying an interval on\n * it must not be restarted by an unrelated render).\n */\n const sessionWarning = useMemo<{ reason: \"idle\" | \"lifetime\"; inMs: number } | null>(() => {\n const { idle, lifetime } = deadlines;\n const reason = idle !== null && (lifetime === null || idle <= lifetime) ? \"idle\" : lifetime !== null ? \"lifetime\" : null;\n if (!reason) return null;\n return { reason, inMs: Math.max(0, (reason === \"idle\" ? idle! : lifetime!) - Date.now()) };\n }, [deadlines]);\n /** True after an edit was refused `read-only` (owner lock, doc 14 §2.5).\n * Sticky until reload — there is no lift signal on the wire yet, and the\n * lock only re-manifests per-edit; the banner is advisory while the live\n * view keeps working. */\n const [readOnlyBlocked, setReadOnlyBlocked] = useState(false);\n const [notOwner, setNotOwner] = useState(false);\n const [epochChanged, setEpochChanged] = useState<{ from: string; to: string } | null>(null);\n const [roster, setRoster] = useState<RosterEntry[]>([]);\n const [arrival, setArrival] = useState<{ mode: \"suggest\" | \"draft\"; tailLength: number; structural: number } | null>(null);\n const [selfHeals, setSelfHeals] = useState(0);\n const [droppedPreReady, setDroppedPreReady] = useState(0);\n const [persistErrors, setPersistErrors] = useState(0);\n /** Storage did not answer the connect-path read within its deadline.\n * Distinct from persistErrors: the store may be perfectly healthy and\n * simply slow, or blocked in a way that never resolves. Either way the\n * session joined cold rather than resuming. */\n const [storeSlow, setStoreSlow] = useState(false);\n const [sendFailures, setSendFailures] = useState(0);\n /**\n * THE OFFLINE TAIL (doc 15 §2): intents recorded while the sequencer was\n * unreachable. The ref — not the connection — owns it, because the tail\n * must survive the teardown-and-rebuild cycle every reconnect performs.\n * Entries carry no wire bookkeeping; the replay's ordinary submit path\n * assigns clientId/clientSeq/base when they finally go out.\n */\n const offlineTailRef = useRef<import(\"@wordinweb/collab/client\").DocBundle[\"offlineTail\"]>(undefined);\n /** The genesisId the tail was recorded against (bundle.offlineTailEpoch):\n * same epoch on rejoin ⇒ silent replay; different ⇒ arrival ladder. */\n const offlineEpochRef = useRef<string | null>(null);\n /** Tail length mirrored into state so the UI re-renders as it grows. */\n const [offlineHeld, setOfflineHeld] = useState(0);\n /** The genesis of the state on screen — what a capture stamps the tail\n * with when recording starts. Set on every welcomed onChange. */\n const lastGenesisRef = useRef<string | null>(null);\n /** The live persister, reachable from capture/replay code that outlives\n * any single effect run. Null while no store or between effect runs. */\n const persisterRef = useRef<BundlePersister | null>(null);\n /** The connection whose welcome already resolved the tail (replay started\n * or arrival offered) — the welcome-time decision must run once per\n * connection, not once per broadcast. */\n const tailConnRef = useRef<object | null>(null);\n /** Trailing-throttle timer for the OFFLINE tail writer (below). */\n const offlinePersistTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const [connection, setConnection] = useState<ConnectionState>(\"live\");\n /**\n * Bumped to force the connection effect to tear down and rebuild — the\n * reconnect itself.\n *\n * REBUILDING RATHER THAN RE-HANDSHAKING THE EXISTING CONNECTION is the\n * whole design. The teardown flushes the bundle and the fresh run reads it\n * back, so a reconnect travels the ORDINARY resume path — the one doc 12 §5\n * specifies and the bundle-resume suite already covers — with\n * `sinceSeq = bundle.confirmedSeq` and the pending queue replayed\n * idempotently by (clientId, clientSeq). Swapping a socket underneath a live\n * connection would instead need a second, bespoke rejoin path that no\n * existing test exercises, to re-derive state the bundle already holds.\n */\n const [reconnectNonce, setReconnectNonce] = useState(0);\n /** Backoff + attempt budget, in refs: they must survive the effect teardown\n * that the reconnect itself performs. */\n const backoffRef = useRef(0);\n const attemptRef = useRef(0);\n const retryTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n /**\n * The teardown's bundle flush, awaited by the next effect run before it\n * reads the store.\n *\n * WITHOUT THIS THE RECONNECT RACES ITS OWN SAVE: `flush()` is async and the\n * teardown cannot await it, so the rebuilt connection could call\n * `store.get()` first and resume from a bundle up to one throttle window\n * stale — re-sending confirmed intents and, on a store with nothing written\n * yet, joining COLD when a resume was available.\n */\n const flushDoneRef = useRef<Promise<void>>(Promise.resolve());\n const maxRetries = opts.liveness?.maxRetries ?? 6;\n // Read through a ref so the effect does not list an options object literal\n // (a fresh identity every render) in its deps and reconnect forever — the\n // same hazard the `profile` note below records.\n const livenessRef = useRef(opts.liveness);\n livenessRef.current = opts.liveness;\n\n /** Retry now: clear the budget and rebuild. Used by the `lost` UI and by\n * the wake-up path, both of which mean \"a human is here, try again\". */\n const reconnect = () => {\n if (retryTimerRef.current !== null) clearTimeout(retryTimerRef.current);\n retryTimerRef.current = null;\n backoffRef.current = 0;\n attemptRef.current = 0;\n setConnection(\"reconnecting\");\n setReconnectNonce((n) => n + 1);\n };\n\n /**\n * THE OFFLINE TAIL WRITER — persistence for the window where the\n * BundlePersister cannot write (a rebuilt connection has no welcome yet,\n * so exportBundle is null). Read-modify-write of the STORED bundle,\n * chained on flushDoneRef so it serializes against teardown flushes and\n * the next run's resume read. While a welcomed connection exists the\n * persister carries the tail instead (via its offlineTail getter), so the\n * two writers are never active at once.\n */\n const persistOfflineTail = (): void => {\n if (!store) return;\n flushDoneRef.current = flushDoneRef.current\n .then(async () => {\n const prev = await store.get(docId);\n if (!prev) return; // never persisted: nothing durable to attach to\n const tail = offlineTailRef.current ?? [];\n const next = { ...prev };\n if (tail.length) {\n next.offlineTail = [...tail];\n next.offlineTailEpoch = offlineEpochRef.current ?? undefined;\n } else {\n delete next.offlineTail;\n delete next.offlineTailEpoch;\n }\n await store.put(next);\n })\n .catch((err: unknown) => {\n // Same contract as the persister's onError: in a zero-custody design\n // a failed write means the durable copy is stale — never silent.\n setPersistErrors((n) => n + 1);\n // eslint-disable-next-line no-console\n console.error(\"[wordinweb] offline-tail-persist\", err);\n });\n };\n /** Throttled (trailing, 1s) — the doc 12 §4 discipline: bounds loss to\n * the window without deferring forever under sustained typing. */\n const scheduleOfflinePersist = (): void => {\n if (!store || offlinePersistTimerRef.current !== null) return;\n offlinePersistTimerRef.current = setTimeout(() => {\n offlinePersistTimerRef.current = null;\n persistOfflineTail();\n }, 1000);\n };\n /** Run any armed offline write NOW (pagehide, teardown). */\n const flushOfflinePersist = (): void => {\n if (offlinePersistTimerRef.current === null) return;\n clearTimeout(offlinePersistTimerRef.current);\n offlinePersistTimerRef.current = null;\n persistOfflineTail();\n };\n\n /**\n * Record one offline edit (doc 15 §2): already applied to the live doc by\n * the editor (`preApplied`), or applied here through the SAME canonical\n * applyIntent code every replica runs (the toolbar/API path). The intent\n * then joins the tail, the UI repaints, and the tail is persisted through\n * whichever writer can currently reach the store.\n */\n const captureOffline = (intent: Omit<Intent, \"clientId\" | \"clientSeq\" | \"base\">, preApplied: boolean): void => {\n if (!preApplied) {\n if (!doc) return;\n const ids = doc.enableStableIds();\n const res = applyIntentScoped(doc, ids, intent as Intent);\n if (!res.applied) return; // nothing changed ⇒ nothing to record\n resyncScope(doc, ids, res);\n // Applied HERE, not by the editor — the repaint signal must move,\n // carrying what this apply disturbed.\n renderScopeRef.current = unionScopes(renderScopeRef.current, res);\n setRenderVersion((v) => v + 1);\n }\n const tail = offlineTailRef.current ?? [];\n tail.push(intent as Intent);\n offlineTailRef.current = tail;\n // Stamp the epoch the tail is being recorded against, once per tail.\n offlineEpochRef.current ??= lastGenesisRef.current;\n setOfflineHeld(tail.length);\n setVersion((v) => v + 1); // offline edits fire no onChange — repaint here\n if (connRef.current?.ready) persisterRef.current?.notify();\n else scheduleOfflinePersist();\n };\n\n /**\n * Drain the offline tail through the ordinary submit path, one-in-flight\n * (doc 03's supported discipline — the reason no sibling math is needed).\n * `plain` replays entries as the ordinary edits they were (same-epoch\n * rejoin: to the session this client is indistinguishable from one that\n * was disconnected with a large pending queue — doc 15 §2). `suggest`\n * converts each entry to its tracked-change form first (diverged rejoin,\n * the owner-adopted default); structural entries have no suggestion form\n * and are popped WITHOUT submitting — they survive in the banked draft\n * slot, and `arrival.structural` told the user so.\n *\n * EXACTLY-ONCE across a kill: each step pops the entry from the tail,\n * submits it (it becomes a pending intent with a clientSeq), and flushes\n * the persister — so the stored bundle moves the intent from `offlineTail`\n * to `pending` in one write. A resume then replays it from `pending`,\n * verbatim, deduplicated by (clientId, clientSeq). A hard kill inside the\n * sub-second flush window keeps the ordinary doc 12 §4 RPO bound.\n */\n const runTailReplay = (conn: CollabConnection, kind: \"plain\" | \"suggest\"): void => {\n const author = profile?.name || clientId;\n const date = new Date().toISOString();\n const step = (): void => {\n // Torn down or dropped again: stop. The remainder is persisted and\n // resumes on the next welcome (same epoch now ⇒ silent continuation;\n // suggest remainders re-offer via the arrival ladder).\n if (connRef.current !== conn || !conn.ready) return;\n if (conn.pendingCount > 0) {\n setTimeout(step, 25);\n return;\n }\n const tail = offlineTailRef.current ?? [];\n if (!tail.length) {\n // Done. The stored tail is already erased: the final step's flush\n // (below) wrote the bundle after the last pop emptied the getter.\n offlineTailRef.current = undefined;\n offlineEpochRef.current = null;\n setOfflineHeld(0);\n setArrival(null);\n return;\n }\n const next = tail[0];\n offlineTailRef.current = tail.slice(1);\n setOfflineHeld(tail.length - 1);\n if (kind === \"suggest\") {\n const { suggestions } = toSuggestions([next], author, date);\n if (suggestions.length) conn.submit(suggestions[0]);\n } else {\n conn.submit(next);\n }\n void persisterRef.current?.flush();\n setTimeout(step, 0);\n };\n step();\n };\n\n useEffect(() => {\n // Every dep change here means a DIFFERENT connection, so a deadline\n // announced by the previous one no longer applies to anything.\n clearDeadlines();\n const socket = (createSocket ?? ((u: string) => new WebSocket(u)))(url);\n let disposed = false;\n /** One verdict per socket: close, error and heartbeat-timeout all funnel\n * here and only the first is acted on. */\n let dropHandled = false;\n // Assigned immediately below; held in a `let` so `dropped` can stop it\n // without a temporal-dead-zone reference to a const declared after it.\n let monitor: { stop: () => void; start: () => void; probe: () => void } | null = null;\n const dropped = (): void => {\n if (dropHandled || disposed) return;\n dropHandled = true;\n monitor?.stop();\n // Close it ourselves. A half-open socket is still OPEN as far as the\n // browser is concerned, and leaving it around means the next reconnect\n // adds a second live socket for the same clientId — which the hub's\n // single-live-connection rule would refuse `already-open`.\n try {\n socket.close?.();\n } catch {\n // Already dead; the reconnect below is what matters.\n }\n // Budget exhausted ⇒ stop retrying and say so. Retrying forever would\n // leave a phone in a tunnel burning battery on a socket that will not\n // open, and would never give the user the modal that tells them their\n // work is safe.\n if (attemptRef.current >= maxRetries) {\n setConnection(\"lost\");\n return;\n }\n attemptRef.current += 1;\n // Backoff matching scheduleRateLimitRedrive (enc-connection.ts): 300ms\n // doubling to a 5s ceiling, reset on success.\n backoffRef.current = Math.min(backoffRef.current === 0 ? 300 : backoffRef.current * 2, 5000);\n setConnection(\"reconnecting\");\n retryTimerRef.current = setTimeout(() => {\n retryTimerRef.current = null;\n setReconnectNonce((n) => n + 1);\n }, backoffRef.current);\n };\n // ONCLOSE/ONERROR AS PROPERTIES, not addEventListener: the property form\n // needs no matching removeEventListener in the teardown and cannot\n // double-register if this effect ever re-runs against the same socket.\n socket.onclose = () => dropped();\n socket.onerror = () => dropped();\n // The heartbeat catches what those two cannot — see LivenessMonitor: a\n // suspended phone leaves a half-open socket where NEITHER handler ever\n // fires and readyState still reads OPEN.\n const monitored = monitorTransport(\n createWebSocketTransport(socket),\n () => dropped(),\n livenessRef.current ?? {},\n );\n const transport = monitored.transport;\n monitor = monitored.monitor;\n // Probe the moment the tab wakes. This is the motivating scenario, and\n // waiting up to a full probe interval to discover a socket that died\n // during sleep is precisely the window in which the user types into a\n // void. Registered unconditionally (the persister's own visibilitychange\n // listener below is separate and store-gated).\n const onVisible = (): void => {\n if (typeof document !== \"undefined\" && document.visibilityState !== \"visible\") return;\n monitor?.probe();\n };\n if (typeof document !== \"undefined\") document.addEventListener(\"visibilitychange\", onVisible);\n monitor.start();\n let persister: BundlePersister | null = null; // assigned below iff store\n const callbacks: import(\"@wordinweb/collab/client\").ConnectionCallbacks = {\n onChange: () => {\n const c = connRef.current;\n if (!c) return;\n setDoc(c.doc);\n setReady(c.ready);\n setVersion((v) => v + 1); // signal a re-render on every reconciled change\n // Repaint only when the DOC changed outside the editor (docVersion\n // moved) — an own-echo onChange must not queue a whole-document\n // relayout (see CollabSession.renderVersion).\n const dv = c.docVersion;\n if (dv !== lastDocVersionRef.current) {\n lastDocVersionRef.current = dv;\n setRenderVersion((v) => v + 1);\n }\n setDocEpoch(c.docEpoch); // bumps only on a reload (true conflict)\n persister?.notify(); // throttled bundle write (doc 12 §4)\n // A welcome that LANDED is the only proof a reconnect worked. Reset\n // the budget here rather than when the socket opens: an open socket\n // that is then refused (`already-open`, version mismatch) has not\n // recovered anything, and crediting it would let a refusal loop retry\n // forever without ever reaching `lost`.\n if (c.ready) {\n backoffRef.current = 0;\n attemptRef.current = 0;\n setConnection((s) => (s === \"live\" ? s : \"live\"));\n lastGenesisRef.current = c.genesisId;\n // Resolve the offline tail ONCE per welcomed connection (doc 15\n // arrival ladder). Same epoch as the tail was recorded against ⇒\n // this client is just a rejoiner with a large pending queue:\n // replay silently through the ordinary drained submit path.\n // Different epoch (or a tail from a build that recorded none) ⇒\n // true divergence: offer the choice, never silently bulldoze.\n if (tailConnRef.current !== c) {\n tailConnRef.current = c;\n const tail = offlineTailRef.current ?? [];\n if (tail.length) {\n if (offlineEpochRef.current === c.genesisId) {\n runTailReplay(c, \"plain\");\n } else {\n const structural = toSuggestions(tail, \"\", \"\").dropped.length;\n // diverged=true never yields \"fast-forward\"; narrow for the type.\n const mode = arrivalMode(tail.length, true) === \"draft\" ? \"draft\" as const : \"suggest\" as const;\n setArrival({ mode, tailLength: tail.length, structural });\n }\n }\n }\n }\n },\n onPresence: (participant, pos) =>\n setPresence((prev) => ({ ...prev, [participant]: pos })),\n onRefused: (reason) => {\n // Read-only is a PER-EDIT refusal, not a connection failure (doc 14\n // §2.5): the blocked participant keeps the live view — \"you can keep\n // reading\" — so it must never flip the full-page refused state.\n // The optimistic local edit is dropped by the stuck-pending watchdog\n // (refused resends exhaust the retry budget → drop + heal from the\n // mirror), so the room converges without the victim's edit; typing\n // simply works again once the owner lifts the lock.\n if (reason === \"read-only\") {\n setReadOnlyBlocked(true);\n return;\n }\n // NOT-OWNER is a REJECTED ACTION, not a dead session, and treating it\n // as fatal was a real bug: the admin op bounced, the connection stayed\n // perfectly healthy, and the whole editor was replaced by \"Connection\n // refused: not-owner. Refresh to retry.\" — losing the live document\n // view over a button press that should simply have done nothing.\n //\n // It is reachable without anyone being devious. `ownerToken` is a\n // string a client holds; holding one is not the same as it being\n // VALID. A room re-seeded into a new epoch mints a fresh owner token,\n // so the previous one stays truthy in this browser, keeps rendering\n // the admin controls, and fails the moment it is used.\n //\n // The refusal is also the authoritative answer to a question the\n // client cannot otherwise ask — the welcome carries no owner flag, so\n // \"am I the owner\" is unknowable until an admin op is tried. Recording\n // it lets the UI stop offering controls it now knows will fail.\n if (reason === \"not-owner\") {\n setNotOwner(true);\n return;\n }\n // The session is over, so any countdown toward its ending is now a\n // lie — including the `idle-timeout` / `session-expired` kicks that\n // ARE the deadline arriving. Cleared before the refusal so no render\n // can ever show both.\n clearDeadlines();\n setRefused(reason);\n },\n onSessionWarning: ({ reason, inMs }) => setDeadlines((d) => ({ ...d, [reason]: Date.now() + inMs })),\n // Only ever `idle` — the lifetime deadline cannot be cancelled, so\n // there is deliberately no per-reason branch here to get wrong.\n onSessionWarningCleared: () => setDeadlines((d) => (d.idle === null ? d : { ...d, idle: null })),\n onRoster: (r) => setRoster(r),\n onSelfHeal: () => setSelfHeals((n) => n + 1),\n // A submit that never left the client. Counted, never swallowed.\n onSubmitDropped: () => setDroppedPreReady((n) => n + 1),\n // Async failures with nowhere to return (seal, transport send). Surfaced\n // rather than swallowed — this callback exists because three bugs this\n // session lived inside empty catches. console.error is the floor: an app\n // embedding this should report it properly.\n onError: (info) => {\n // A submit that threw on its way out is a LOST EDIT — counted so the\n // loss accounting partitions (see CollabSession.sendFailures). Other\n // sites are reported but not counted here; they are not edit loss.\n if (info.where === \"enc.submit\") setSendFailures((n) => n + 1);\n // eslint-disable-next-line no-console\n console.error(`[wordinweb] ${info.where}`, info.error);\n },\n onEpochChange: (from, to) => {\n // A recorded offline tail is handled by the welcome-time resolution\n // in onChange (the arrival ladder keys on the tail's RECORDED epoch\n // vs the welcome's, which also covers a reload that lands after\n // this callback already fired in a previous session).\n // Someone re-seeded while we were away (doc 12 §5 case 2): the\n // connection already took the server's state and withheld our\n // old-epoch pending. Preserve the superseded bundle as a DRAFT\n // before the persister overwrites the main slot — this is the\n // \"your offline copy is saved as a draft\" guarantee.\n if (store) {\n void store.get(docId).then((old) => {\n if (old && old.genesisId === from) {\n return store.put({ ...old, docId: draftKey(docId, from) });\n }\n });\n }\n setEpochChanged({ from, to });\n },\n onFastForward: (from, _to) => {\n // Doc 15 fast-forward: nothing was lost, so no draft banner — but\n // the superseded state is BANKED recoverable-not-gone (the\n // fabricated-lineage mitigation: worst case is restorable).\n if (store) {\n void store.get(docId).then((old) => {\n if (old && old.genesisId === from) {\n return store.put({ ...old, docId: supersededKey(docId, from) });\n }\n });\n }\n },\n };\n // Mode from the LINK (doc 13 §6): a docKey selects the encrypted\n // connection; both classes expose the same session surface, so the rest\n // of the hook (and DocxView) is mode-blind. Construction is async only\n // when a share code must be stretched (PBKDF2, once per join).\n const flush = () => {\n void persister?.flush();\n // The offline writer's armed write, if any (it is armed only while\n // the persister cannot export a bundle, so at most one of these two\n // produces a write).\n flushOfflinePersist();\n };\n /**\n * This run is a REJOIN after a detected drop, not a first join — so it\n * must claim the identity the way `resume()` already does.\n *\n * The dropped socket may still be registered server-side: a half-open\n * connection is invisible to the server too, and it holds this clientId\n * under the single-live-connection rule until its own timeout. Without the\n * claim the rejoin is refused `already-open` and the session is stuck\n * behind a socket that is provably dead — the failure this whole path\n * exists to recover from. A reconnect IS that identity's continuation, by\n * definition, which is the same argument resume() makes.\n */\n const rejoining = reconnectNonce > 0;\n void (async () => {\n const stretched = docKey && shareCode ? await stretchShareCode(shareCode, docId) : undefined;\n const codeProof = stretched ? btoa(String.fromCharCode(...stretched)) : undefined;\n if (disposed) return;\n // Media relay origin (doc 16 §3). Absent ⇒ the connection has no media\n // duties at all and images stay a local-only feature, which is exactly\n // how every non-media caller behaves today.\n const mediaOpts = httpBase ? { httpBase } : undefined;\n const conn: CollabConnection = docKey\n ? (new EncryptedCollabConnection(transport, clientId, docKey, callbacks, stretched, undefined, mediaOpts) as unknown as CollabConnection)\n : new CollabConnection(transport, clientId, callbacks, mediaOpts);\n connRef.current = conn;\n if (store) {\n persister = new BundlePersister(conn, store, docId, {\n // The browser's bundle IS the durable copy in a zero-custody design,\n // so a failed write (quota, blocked storage, private mode) means\n // \"saved\" is a lie. Never silent.\n onError: (err) => {\n // Surfaced to the UI as well as the console: a failed write is\n // data-loss-class here, and a console line nobody is watching is\n // the same silence this arc exists to remove.\n setPersistErrors((n) => n + 1);\n // eslint-disable-next-line no-console\n console.error(\"[wordinweb] bundle-persist\", err);\n },\n // The offline tail rides every bundle write (doc 15 §4.3 \"tail\n // hygiene\"): without this, the first post-resume write would\n // ERASE a stored tail the user had not reconciled yet.\n offlineTail: () => {\n const tail = offlineTailRef.current ?? [];\n return tail.length ? { tail: [...tail], epoch: offlineEpochRef.current ?? undefined } : null;\n },\n });\n persisterRef.current = persister;\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"pagehide\", flush);\n document.addEventListener(\"visibilitychange\", flush);\n }\n // A RECONNECT MUST NOT OVERTAKE THE SAVE THAT PRECEDED IT. The\n // previous run's teardown started a flush it could not await; reading\n // the store before that lands would resume from a stale bundle — or,\n // on the very first save, find nothing and join COLD, discarding\n // exactly the state the resume path exists to carry across the drop.\n // BOUNDED, both of them. A wedged IndexedDB — quota exhausted, storage\n // blocked, private mode — does not reject, it simply never settles, and\n // the hello below is sent only after these awaits. The observed failure:\n // the socket opens, no hello is ever sent, the server sees a room with\n // no participants and evicts it on the empty-room grace, and the client\n // spins on \"Connecting…\" forever with nothing in any log to explain it.\n //\n // Losing the resume costs the pending queue and one cold rejoin. Never\n // connecting costs everything, so the deadline resolves rather than\n // throws and the join proceeds without the bundle.\n await withDeadline(flushDoneRef.current, STORE_DEADLINE_MS, undefined);\n if (disposed || connRef.current !== conn) return;\n // Resume if a bundle exists, else join cold. The get() is async;\n // the editor stays !ready (input disabled) until the welcome.\n const read = await withDeadline<Awaited<ReturnType<typeof store.get>> | typeof TIMED_OUT>(\n store.get(docId),\n STORE_DEADLINE_MS,\n TIMED_OUT,\n );\n if (disposed || connRef.current !== conn) return;\n // A timeout is NOT the same as \"no bundle stored\", and conflating them\n // would silently look like the session forgot the user's work. Only the\n // timeout raises the warning; a genuine empty store joins cold quietly,\n // which is the ordinary first-visit path.\n const storeTimedOut = read === TIMED_OUT;\n const bundle = storeTimedOut ? null : (read as Awaited<ReturnType<typeof store.get>>);\n // NOT persistErrors. That counter means \"a write failed\", and the demo\n // renders it as \"storage is full or blocked\" — which is a different\n // claim, and a wrong one for a read that merely took too long. Saying\n // the store is full when it holds a few megabytes sends someone\n // looking in exactly the wrong place; it did.\n if (storeTimedOut) setStoreSlow(true);\n // The stored tail seeds the in-memory one only when memory holds\n // nothing (a fresh page). Within a page's lifetime the ref is always\n // at least as fresh as the store (the store trails by the throttle\n // window), so a reconnect must never clobber it with a stale read.\n if (!offlineTailRef.current?.length) {\n offlineTailRef.current = bundle?.offlineTail;\n offlineEpochRef.current = bundle?.offlineTailEpoch ?? null;\n setOfflineHeld(bundle?.offlineTail?.length ?? 0);\n }\n // resume() already claims takeover for this exact reason (doc 12 §7).\n if (bundle) conn.resume(bundle, token, { profile, codeProof, ownerToken });\n else conn.join(docId, token, { profile, takeover: takeover || rejoining, codeProof, ownerToken });\n } else {\n conn.join(docId, token, { profile, takeover: takeover || rejoining, codeProof });\n }\n })();\n return () => {\n disposed = true;\n monitor?.stop(); // no probe may outlive the socket it was measuring\n if (typeof document !== \"undefined\") document.removeEventListener(\"visibilitychange\", onVisible);\n if (store && typeof window !== \"undefined\") {\n window.removeEventListener(\"pagehide\", flush);\n document.removeEventListener(\"visibilitychange\", flush);\n }\n // The offline writer's armed write, if any, joins the flush handoff\n // (it chains itself onto flushDoneRef; armed only while the persister\n // cannot export, so the two flushes never both produce a write).\n flushOfflinePersist();\n // Published so the NEXT run can await it before reading the store —\n // this is the handoff that makes a reconnect a resume (see above).\n flushDoneRef.current = Promise.all([flushDoneRef.current, persister?.flush() ?? Promise.resolve()]).then(() => {});\n persister?.stop(); // …then detach (no timers left behind)\n if (persisterRef.current === persister) persisterRef.current = null;\n connRef.current = null;\n };\n // Reconnect when the target document or endpoint changes, or when\n // `reconnectNonce` is bumped by a detected drop — the reconnect IS a\n // teardown-and-rebuild, which is what routes it through resume().\n // profile intentionally omitted from deps (an inline object literal would\n // reconnect every render); renames go through setProfile, not re-join.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, docId, clientId, token, createSocket, store, takeover, docKey, shareCode, ownerToken, httpBase, reconnectNonce]);\n\n /** Cancel a pending retry if the hook unmounts between attempts. */\n useEffect(() => () => {\n if (retryTimerRef.current !== null) clearTimeout(retryTimerRef.current);\n retryTimerRef.current = null;\n }, []);\n\n /**\n * This client's own write status from the roster, or undefined when the\n * server publishes none. Read from the roster rather than tracked\n * separately so there is exactly one source for it.\n */\n const myWrite = roster.find((r) => r.clientId === clientId)?.write;\n\n /**\n * THE REFUSAL / UNREACHABLE SPLIT (doc 12 §5). `refusalBlocked` is \"the\n * server said no\" — a dead session, an owner lock, a demotion, a viewer\n * link — and it is the only thing that makes accepting keystrokes a lie.\n * A merely unreachable server is `offlineActive`: edits apply locally and\n * are recorded to the durable tail, EXCEPT past the tail cap, where\n * recording genuinely cannot continue and the gate closes loudly.\n *\n * The refusal check stays live while offline on purpose: a viewer-role\n * link (or a lock this client last heard was on) does not become writable\n * by going through a tunnel — offline editing must not punch a hole in\n * any refusal.\n */\n const refusalBlocked =\n refused !== null || (myWrite !== undefined ? myWrite !== \"allowed\" : readOnlyBlocked);\n const offlineActive = !refusalBlocked && connection !== \"live\" && ready;\n const offlineCapped = offlineActive && offlineHeld >= (opts.offlineTailCap ?? OFFLINE_TAIL_CAP);\n\n return {\n doc,\n version,\n renderVersion,\n // Union of what the connection applied (its replica tracks per-intent\n // scopes) and any local offline applies, drained together at the repaint.\n takeRenderScope: () => {\n const local = renderScopeRef.current;\n renderScopeRef.current = null;\n const conn = connRef.current?.takeRenderScope() ?? null;\n if (local && conn) return unionScopes(local, conn);\n return conn ?? local;\n },\n docEpoch,\n ready,\n // DocxView's editor applies every command to the live doc BEFORE emitting\n // its intent, so the connection must not optimistically re-apply it (that\n // doubled each keystroke: \"Hello\" rendered as \"Hello\" + its reversal, and\n // the corrupted offsets got everything after the first char rejected\n // server-side). Pre-applied semantics track pending + send only.\n //\n // OFFLINE, the same intent is CAPTURED instead (doc 15 §2): the edit is\n // already in the doc, so it only needs recording. Past the cap nothing\n // routes here — writesBlocked has withdrawn the editor — and anything\n // that slips to the connection anyway lands in the loud droppedPreReady\n // backstop rather than vanishing.\n submit: (intent) => {\n if (offlineActive && !offlineCapped) return captureOffline(intent, /*preApplied*/ true);\n connRef.current?.submitPreApplied(intent);\n },\n // Toolbar/API ops: canonical-apply optimistic path (see CollabSession).\n // Offline, the capture applies through the SAME canonical applyIntent\n // code before recording, so the local result stays byte-identical to\n // what every replica will derive when the tail replays.\n submitOp: (intent) => {\n if (offlineActive && !offlineCapped) return captureOffline(intent, /*preApplied*/ false);\n connRef.current?.submit(intent);\n },\n setPresence: (pos) => connRef.current?.setPresence(pos),\n allocIds: (n) => connRef.current?.allocIds(n) ?? [],\n uploadMedia: async (bytes) => (await connRef.current?.uploadMedia(bytes)) ?? null,\n // Read straight off the connection rather than mirrored into state: it is\n // set once when the welcome lands and never changes for the session, and\n // in the encrypted connection it is assigned OUTSIDE the serial rehydrate\n // chain, so it is readable before `ready` — which is what lets the picker\n // open and the pre-check run while the document is still rehydrating.\n mediaMaxBlobBytes: connRef.current?.mediaMaxBlobBytes ?? null,\n // Collaborative undo lives on the ENCRYPTED connection: it needs the\n // mirror (a local re-derivation of the canonical log) to compute the\n // inverse. A plaintext room's authority is the server, so undo there\n // needs a wire message and is not wired yet — the optional call reports\n // \"unavailable\" rather than pretending.\n // The ref is TYPED as the plaintext class (the encrypted one is cast in\n // at construction — a pre-existing modelling wrinkle, not this arc's), so\n // the access is structural. The TYPE is the real one now, so a change to\n // UndoOutcome breaks here rather than silently disagreeing.\n undoLast: () => (connRef.current as Partial<{ undoLast: () => UndoOutcome }> | null)?.undoLast?.() ?? \"unavailable\",\n presence,\n refused,\n sessionWarning,\n readOnlyBlocked,\n notOwner,\n // Derived, not stored: one place decides \"writes won't land\", so the gate\n // and the banner can never disagree about whether the user may type.\n //\n // THE SERVER'S ROSTER SIGNAL WINS WHEN PRESENT, and that is the fix for a\n // real user-visible bug: `readOnlyBlocked` is refusal-driven, so it can\n // only ever say \"blocked\" — nothing tells it a lock was LIFTED. A blocked\n // participant therefore sat in viewer mode until reload, and needed the\n // \"Try editing again\" button to escape. The roster status is POSITIVE: it\n // says `allowed` too, and it is re-fanned on every transition, so a lift\n // reaches this client without anyone attempting an edit to discover it.\n //\n // `undefined` means an older server that publishes no status. Fall back to\n // the refusal-driven flag rather than assuming `allowed` — a permissive\n // default would put the user's first keystrokes straight back in the\n // appear-then-vanish loop this exists to remove.\n //\n // A NON-LIVE CONNECTION DOES NOT BLOCK: unreachable is not refused (see\n // refusalBlocked above, and the field's own doc). What DOES fold in is\n // the offline tail CAP — past it an edit cannot be durably kept, which\n // is the same \"cannot land\" fact this predicate exists to gate, and\n // folding it here is what keeps `editable: … && !writesBlocked` the one\n // gate every editor surface already reads.\n //\n // A REFUSAL BLOCKS WRITES FIRST OF ALL: the session is dead, and note the\n // hub does NOT close the socket after a kick, so `connection` can still\n // read \"live\" and the roster can still hold a stale `allowed` — neither\n // may win over a refusal. This is what keeps a document rendered behind a\n // \"session ended\" dialog read-only through the same single predicate.\n writesBlocked: refusalBlocked || offlineCapped,\n // Offline editing state (doc 15 §2) — see the CollabSession doc. Null\n // whenever a refusal already owns the story: the two must never both\n // claim the banner.\n offline: offlineActive ? { editsHeld: offlineHeld, capped: offlineCapped } : null,\n // Left UNSET by a disconnect on purpose. These four values describe what\n // the SERVER decided about this participant, and a client that cannot\n // reach the server has not been told anything new — reporting a stale\n // `allowed` beside `writesBlocked: true` would be a straight\n // contradiction, and inventing a fifth value would put a transport fault\n // into a permissions vocabulary. `connection` is where that story lives.\n writeStatus: myWrite,\n connection,\n reconnect,\n retryWrites: () => setReadOnlyBlocked(false),\n epochChanged,\n roster,\n setProfile: (p) => connRef.current?.setProfile(p),\n activity: connRef.current?.activity ?? [],\n admin: (action) => connRef.current?.admin(action),\n selfHeals,\n droppedPreReady,\n persistErrors,\n storeSlow,\n sendFailures,\n arrival,\n reconcile: (mode) => {\n const conn = connRef.current;\n const tail = offlineTailRef.current ?? [];\n if (!conn || !tail.length) {\n offlineTailRef.current = undefined;\n offlineEpochRef.current = null;\n setOfflineHeld(0);\n setArrival(null);\n return;\n }\n setArrival(null); // the choice is made; the banner comes down now\n if (mode === \"suggest\") {\n // Replay the tail as tracked changes on the crowd's doc, drained\n // one-in-flight, popping + persisting per intent (see runTailReplay\n // — a kill mid-replay resumes from the store, and the remainder is\n // re-OFFERED rather than silently replayed, because the tail keeps\n // its recorded epoch until it fully drains).\n runTailReplay(conn, \"suggest\");\n } else {\n // Draft: the offline copy (confirmed state + this tail) was banked\n // to the draft/superseded slot when the epoch change surfaced;\n // clear the live tail so it is not offered again.\n offlineTailRef.current = undefined;\n offlineEpochRef.current = null;\n setOfflineHeld(0);\n void persisterRef.current?.flush(); // erase the tail from the live slot\n }\n },\n };\n}\n\n/**\n * A complete collaborative editor: joins a document over the collab server and\n * renders its live, reconciled state, forwarding local edits as intents. This\n * is the inbound + outbound integration composed for you — the app supplies\n * only the connection params.\n *\n * Rendering strategy: DocxView renders the replica's LIVE document object\n * directly (`collab.doc`). Broadcasts mutate that same instance in place and\n * bump `version`, which DocxView receives as `renderSignal` and turns into a\n * single in-place repaint — no per-broadcast serialize/parse round-trip. The\n * `source` bytes are computed only on a true-conflict reload (keyed on\n * `docEpoch`) as the placeholder/fallback. The visual rendering runs in the\n * browser (as all of DocxView does); the protocol/convergence/binding it rides\n * on are covered by the headless test suites.\n */\n/** Toolbar groups OFF by default in the collab editor. Uploads (image/icon/\n * screenshot/3D/media/object) are excluded per the demo threat model (no\n * object upload → no stored-payload surface); the rest are commands not yet\n * routed through intents — showing them would make local-only edits that\n * silently diverge from other participants. Flip any of these on explicitly\n * via `toolbarFeatures` once wired. */\nexport const COLLAB_TOOLBAR_DEFAULTS: Partial<Record<ToolbarFeature, boolean>> = {\n // Images ride the doc-16 media relay (bytes out of band, address committed\n // in the intent). The rest of the upload surface stays closed per the demo\n // threat model — no object upload, no stored-payload surface.\n icon: false, screenshot: false, model3D: false, media: false, object: false,\n history: false, // toolbar undo/redo drives the LOCAL history; collaborative undo is server-side\n drawing: false, // ink strokes have no intent yet\n arrange: false, // selected-object arrange ops are not collab-anchored yet\n};\n\nexport function CollabEditor(opts: UseCollabOptions & {\n editable?: boolean;\n /** Render the Word-style ribbon toolbar above the page (default true). */\n toolbar?: boolean;\n /** Ribbon mode: \"simple\" (Home only) or \"advanced\" (default). */\n toolbarMode?: ToolbarMode;\n /** Per-group overrides merged over the collab-safe defaults. */\n toolbarFeatures?: Partial<Record<ToolbarFeature, boolean>>;\n /** Observe the imperative document API (find/replace, inserts, ...). */\n onReady?: (api: DocxViewApi) => void;\n /** Observe the live CollabSession (roster, activity, epochChanged,\n * doc-for-download) — how an app shell renders chips/banners/buttons\n * around the editor without re-implementing its composition. */\n onSession?: (session: CollabSession) => void;\n /** Custom refusal UI (e.g. already-open -> \"use here instead\",\n * no-session -> \"bring it back live\"). Default: a refresh notice.\n *\n * `ctx.docVisible` says which surface the content lands on. False (the\n * pre-live refusals: already-open, room-full, version mismatch, a code\n * gate): the content IS the page, exactly as before — render a full\n * screen. True (post-live refusals: idle-timeout, session-expired): the\n * document is still in hand and stays on screen READ-ONLY behind the\n * content, which is rendered in an overlay covering it — render a\n * dialog/scrim, not a full page. Existing one-argument hosts keep their\n * old markup untouched; the second argument only ADDS the information. */\n refusedContent?: (reason: string, ctx?: { docVisible: boolean }) => ReactNode;\n}): ReactNode {\n const session = useCollab(opts);\n useEffect(() => {\n opts.onSession?.(session);\n // The session object is a fresh literal each render; observing on\n // version/ready/roster/refusal changes is what consumers need.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n // writesBlocked belongs here for the same reason `refused` does: the shell\n // renders the banner and any retry affordance off it, and without it the\n // observer only learns the editor went read-only when some UNRELATED\n // change (the next version bump) happens to re-fire this effect.\n // `connection` belongs here for the same reason `writesBlocked` does: the\n // shell renders the disconnect modal off it, and without it the observer\n // would only learn the socket died when some unrelated change happened to\n // re-fire this effect — which, on a dead connection, may be never.\n }, [session.version, session.ready, session.refused, session.roster, session.epochChanged, session.sessionWarning, session.writesBlocked, session.connection, session.persistErrors]);\n const [api, setApi] = useState<DocxViewApi | null>(null);\n // Caret survival across a true-conflict reload: the docEpoch key change\n // below remounts the whole DocxView, killing the caret's node references.\n // The stable-id encoding survives (the replica reproduces the id table via\n // the sidecar), so capture it from the OUTGOING view the moment the epoch\n // bump reaches this render — the old view is still mounted here, and its\n // own doc/id table was left untouched by the reload (restoreConfirmed\n // builds a fresh document object) — then restore it into the replacement\n // when its onReady fires. Nothing is captured when the user had no caret,\n // so an idle viewer never has focus stolen.\n const pendingCaretRef = useRef<EncodedCaret | null>(null);\n const seenEpochRef = useRef(session.docEpoch);\n if (session.docEpoch !== seenEpochRef.current) {\n seenEpochRef.current = session.docEpoch;\n pendingCaretRef.current = api?.getEncodedCaret() ?? null;\n }\n // Placeholder bytes for the REFUSED branch only — that dead-session view\n // has no live `collab` prop, so it renders from real bytes. The LIVE view\n // renders session.doc directly and its `source` is never parsed, so\n // serializing here on the live path was pure waste — and it sat on the\n // join-mount critical path: a full save() of a ~12 MB document is seconds\n // of main thread between the welcome and the first paint, run again on\n // every docEpoch reload. Computed only when a refusal actually surfaces.\n const bytes = useMemo(\n () => (session.refused && session.doc ? session.doc.save() : null),\n [session.refused, session.doc, session.docEpoch],\n );\n // The live view's `source` prop: a stable inert placeholder. DocxView\n // ignores `source` entirely while `collab.doc` is set (the live-doc path),\n // but the prop is required and its IDENTITY must be stable — a fresh\n // object per render would re-run DocxView's load effect (a whole-document\n // relayout) on every broadcast.\n const liveSource = useMemo(() => new Uint8Array(0), []);\n\n if (session.refused) {\n // A refusal that arrives AFTER a live session (idle-timeout,\n // session-expired) leaves the document in hand — keep it on screen\n // behind the refusal content instead of replacing the page with it.\n // Pre-live refusals (already-open, room-full, version mismatch, code\n // gates) have nothing loaded, and faking an empty page behind a dialog\n // would be a lie: those keep the full-screen refusal exactly as before.\n const docVisible = !!(session.doc && bytes);\n const content = opts.refusedContent\n ? opts.refusedContent(session.refused, { docVisible })\n : createElement(\n \"div\",\n // Legible over a rendered page in the overlay case; inert extra\n // styling on the plain full-screen path costs nothing.\n docVisible\n ? { style: { margin: 16, padding: \"10px 14px\", width: \"fit-content\", background: \"rgba(255,255,255,.95)\", borderRadius: 8, font: \"14px system-ui\", boxShadow: \"0 2px 8px rgba(0,0,0,.25)\" } }\n : undefined,\n `Please refresh — ${session.refused}.`,\n );\n if (!docVisible) {\n return createElement(\"div\", { className: \"dxw-collab-refused\" }, content);\n }\n return createElement(\n \"div\",\n { className: \"dxw-collab-refused-host\", style: { position: \"relative\", height: \"100%\" } },\n createElement(DocxView, {\n source: bytes,\n // NO `collab` prop: a refused session is dead — no socket, nothing to\n // submit to — so this is a plain document view, not a live editor.\n // And the SAME single gate as the live path below: refusal folds into\n // writesBlocked (see CollabSession), so this is always false here —\n // an editable surface over a dead session would apply keystrokes\n // locally and lose them.\n editable: (opts.editable ?? true) && !session.writesBlocked,\n style: { height: \"100%\" },\n }),\n // The overlay covers the document (blocking pointer interaction with a\n // dead page); the host's content styles itself on top of it. The\n // load-bearing `dxw-collab-refused` class keeps its meaning — \"the\n // element holding the refusal content\" — with the overlay placement\n // carried by the additional class. DELIBERATELY NO z-index: it would\n // create a stacking context that traps a host's `position: fixed`\n // dialog/scrim at this layer, silently re-scoping the host's own\n // z-values. DOM order (after the DocxView) already paints it above the\n // document.\n createElement(\n \"div\",\n { className: \"dxw-collab-refused dxw-collab-refused-overlay\", style: { position: \"absolute\", inset: 0 } },\n content,\n ),\n );\n }\n if (!session.ready || !session.doc) {\n // Surface a dead server instead of spinning forever: if the welcome hasn't\n // arrived after a grace period, say so (the socket errored or nothing is\n // listening — the demo's most common local failure is a stopped server).\n return createElement(ConnectingNotice, null);\n }\n\n const view = createElement(DocxView, {\n source: liveSource,\n // height:100% is LOAD-BEARING for virtualization (the refused path below\n // already passes it): DocxView's outer wrapper only gets a height when\n // the caller passes one. Without it the wrapper grows to the document's\n // full height, its overflow:auto container never scrolls, and the\n // virtualizer mounts EVERY page — each keystroke then pays O(total\n // pages) in DOM scans (measured: p50 52ms at 500 pages, 10ms bounded).\n style: { height: \"100%\" },\n onReady: (a: DocxViewApi) => {\n setApi(a);\n opts.onReady?.(a);\n // Re-place the caret captured before a docEpoch remount (see above).\n const caret = pendingCaretRef.current;\n if (caret) {\n pendingCaretRef.current = null;\n a.setCaretFromEncoded(caret);\n }\n },\n // Render the live doc object directly; repaint in place on each version\n // bump. submit + presence + id allocator flow out; DocxView draws carets.\n collab: {\n submit: session.submit,\n submitOp: session.submitOp as (intent: { kind: string } & Record<string, unknown>) => void,\n presence: session.presence,\n allocIds: session.allocIds,\n // Image bytes go out of band over the relay (doc 16); its presence is\n // what makes the toolbar's image button real in a room.\n uploadMedia: session.uploadMedia,\n mediaMaxBlobBytes: session.mediaMaxBlobBytes,\n // Cmd+Z: reverse my last sequenced action over the wire.\n undoLast: () => { session.undoLast(); },\n doc: session.doc,\n // The repaint signal is renderVersion, NOT version: version moves on\n // every onChange (including the editor's own submit + echo, which the\n // editor already painted), and DocxView answers each renderSignal move\n // with a whole-document relayout — behind an inert container past the\n // background-layout page threshold. Riding version made every keystroke\n // in a 500-page room queue that relayout: the \"collab editor is\n // unusable on a big document\" report.\n renderSignal: session.renderVersion,\n // The dirty scope behind renderSignal: a remote text edit repaints as\n // ONE incremental paragraph relayout instead of the whole document.\n takeRenderScope: session.takeRenderScope,\n // Outbound presence: the editor reports caret moves; remote tabs draw\n // this user's cursor (inbound presence above draws theirs here).\n setPresence: session.setPresence,\n // Name flags on remote carets (doc 14 §2): presence and roster share\n // the bound-clientId keyspace, so this join is exact.\n participantNames: Object.fromEntries(session.roster.map((r) => [r.clientId, r.profile.name])),\n },\n // VIEWER MODE when the server will not take this client's writes. Not a\n // banner over a live editor: that shape applied every keystroke locally\n // and lost it, which is why the read-only banner could be on screen while\n // the user's text appeared and then vanished.\n editable: (opts.editable ?? true) && !session.writesBlocked,\n // Re-key only on docEpoch (a true-conflict reload) — NOT on every change.\n // Between reloads the live doc mutates in place and the key stays stable,\n // so DocxView repaints in place instead of re-mounting (no flash/jump).\n key: session.docEpoch,\n });\n\n if (opts.toolbar === false) return view;\n return createElement(\n \"div\",\n { className: \"dxw-collab-shell\", style: { display: \"flex\", flexDirection: \"column\", height: \"100%\" } },\n createElement(DocxToolbar, {\n api,\n mode: opts.toolbarMode ?? \"advanced\",\n features: { ...COLLAB_TOOLBAR_DEFAULTS, ...opts.toolbarFeatures },\n }),\n createElement(\"div\", { style: { flex: 1, minHeight: 0 } }, view),\n );\n}\n\n/** \"Connecting…\" that upgrades to a clear failure message after 5s — a demo\n * pointed at a stopped server previously spun forever with no signal. */\nfunction ConnectingNotice(): ReactNode {\n const [slow, setSlow] = useState(false);\n useEffect(() => {\n const t = setTimeout(() => setSlow(true), 5000);\n return () => clearTimeout(t);\n }, []);\n // The spinner is an EMPTY element carrying a class, not inline styles: a host\n // that styles it gets a spinner, a host that does not sees nothing extra\n // (an empty span renders as nothing). The package should not ship an opinion\n // about the look, and it must not require CSS to be legible either — hence\n // the text stands on its own.\n return createElement(\n \"div\",\n {\n className: \"dxw-collab-connecting\",\n style: { padding: 16, font: \"14px system-ui\" },\n role: \"status\",\n \"aria-live\": \"polite\",\n \"aria-busy\": \"true\",\n },\n createElement(\"span\", { className: \"dxw-collab-spinner\", \"aria-hidden\": \"true\", key: \"s\" }),\n slow\n ? \"Still connecting — is the collab server running? (start it, then reload this page)\"\n : \"Connecting…\",\n );\n}\n\n/** Structural type for the DocxView `collab` prop — mirrors CollabSession so\n * DocxView can accept an injected session via a type-only import (no runtime\n * dependency on the collab engine). */\nexport type { CollabSession as InjectedCollabSession };\n\n// Browser bundle store (IndexedDB) + the seams apps need to wire persistence\n// themselves. Exported from the collab entry only — the local-only `wordinweb`\n// entry has no path to any of this (doc 07 tree-shaking rule).\nexport type { UndoOutcome } from \"@wordinweb/collab/client\";\nexport { IndexedDbBundleStore } from \"./bundle-store.js\";\nexport { InMemoryBundleStore, BundlePersister, parseBundleKey, versionKey, draftKey, supersededKey } from \"@wordinweb/collab/client\";\nexport type { StoredDocSummary, ParsedBundleKey, StoredDocKind } from \"@wordinweb/collab/client\";\nexport { mintDocKey, docKeyFromFragment, deriveEpochKeys, sealCheckpoint, stretchShareCode, bytesToB64, docHash, mediaAddressesOf } from \"@wordinweb/collab/client\";\nexport type { BundleStore, DocBundle } from \"@wordinweb/collab/client\";\n","/**\n * Wire intents for replicated editing.\n *\n * Positions address content by stable id (plan doc 02): `{ blockId, runId,\n * offset }`, where offset is a character offset within the run's text. Ids\n * come from the core StableIds side table; the server validates every\n * client-supplied id before applying (validate-then-install, doc 11) — the\n * types here are the transport shape, not a trust boundary.\n *\n * `base` is the last server sequence number the originating client had\n * applied when it produced the intent; the server transforms the intent\n * against everything sequenced in `(base, seq)` before applying and\n * broadcasting the canonical form (doc 03).\n */\n\nexport type StableId = number;\n\n/** A caret/endpoint position within a run. */\nexport interface Position {\n blockId: StableId;\n runId: StableId;\n offset: number;\n}\n\ninterface IntentBase {\n /** Originating client (authenticated identity; the server never trusts a\n * claimed id — see doc 11). */\n clientId: string;\n /** Per-client monotonic counter; `(clientId, clientSeq)` is the idempotency\n * key that dedups re-sends after reconnect (doc 03). */\n clientSeq: number;\n /** Last applied server seq at production time. */\n base: number;\n}\n\n/**\n * Tracked-change marks (plan doc 14 §3 L2): strike text ranges as w:del\n * revisions and/or mark paragraph glyphs (split \"ins\" marks, merge \"del\"\n * suggestions) — the SUGGESTING counterparts of deleteText/mergeParagraph.\n * Nothing is removed: Word keeps both sides until accept/reject (the\n * already-collab-wired acceptRevision/rejectRevision/acceptAllRevisions).\n * Author/date travel IN the intent (doc 05 rule a: nondeterministic values\n * are generated once by the originator); revision w:ids are scan-derived\n * from identical tree state on every replica. Positional offsets were\n * client-resolved (rule b — no Intl re-derivation at apply).\n */\nexport interface SuggestRevisionIntent extends IntentBase {\n kind: \"suggestRevision\";\n ranges?: { blockId: StableId; runId: StableId; start: number; end: number }[];\n marks?: { blockId: StableId; glyph: \"ins\" | \"del\" }[];\n suggest: { author: string; date: string };\n}\n\n/** Insert `text` at a position. With `suggest`, the insertion is recorded as\n * a tracked change (w:ins) carrying the author + date (revision tracking /\n * suggesting mode), rather than a plain insert. */\nexport interface InsertTextIntent extends IntentBase {\n kind: \"insertText\";\n at: Position;\n text: string;\n /** Tracked-change (suggesting) metadata; omit for a plain insert. */\n suggest?: { author: string; date: string };\n}\n\n/** Delete `[start, end)` characters within a single run. */\nexport interface DeleteTextIntent extends IntentBase {\n kind: \"deleteText\";\n blockId: StableId;\n runId: StableId;\n start: number;\n end: number;\n}\n\n/**\n * Split a paragraph at a position. Text after `at.offset` in run `at.runId`\n * (and following runs) moves to a new paragraph. The originating client\n * allocates the ids for the new paragraph and the new run holding the moved\n * tail; every replica installs these recorded values (doc 03), and they also\n * let the transform remap concurrent positions that fall in the moved tail.\n */\nexport interface SplitParagraphIntent extends IntentBase {\n kind: \"splitParagraph\";\n at: Position;\n newBlockId: StableId;\n newRunId: StableId;\n}\n\n/**\n * Character-format an entire run (bold/italic/underline/…). Whole-run only:\n * it mutates the run's w:rPr in place, so no run splits, no new ids, and the\n * run id is preserved — which makes its transform identity (formatting moves\n * no text). Sub-range formatting splits the run into up to three, needing the\n * run-split id-inheritance + position remapping (plan doc 03 F3); that is a\n * documented extension, not implemented here.\n */\nexport interface FormatRunIntent extends IntentBase {\n kind: \"formatRun\";\n blockId: StableId;\n runId: StableId;\n /** RunFormatPatch (bold/italic/underline/strike/color/…). Structural shape\n * mirrors @wordinweb/core's RunFormatPatch; carried verbatim. */\n patch: Record<string, unknown>;\n}\n\n/**\n * Paragraph-level formatting: alignment and/or paragraph style. Block-level —\n * it mutates the paragraph's w:pPr, creates no tracked nodes, and moves no\n * text, so the block id is preserved and its transform is identity.\n */\nexport interface FormatParagraphIntent extends IntentBase {\n kind: \"formatParagraph\";\n blockId: StableId;\n align?: \"left\" | \"center\" | \"right\" | \"justify\";\n /** Paragraph style id; null clears to Normal. Omit to leave unchanged. */\n styleId?: string | null;\n}\n\n/**\n * Turn a paragraph into a bullet/numbered list item, or clear its list\n * formatting (kind null). Block-level: mutates w:pPr/numbering, preserves the\n * block id, moves no text — transform identity.\n */\nexport interface SetListTypeIntent extends IntentBase {\n kind: \"setListType\";\n blockId: StableId;\n listKind: \"bullet\" | \"number\" | null;\n}\n\n/**\n * Format a character sub-range of a single run (plan doc 03 F3). The run is\n * split into up to three pieces — before [0,start), middle [start,end)\n * (formatted), after [end,len) — replacing the original run. The originating\n * client allocates ids for the pieces that exist and carries them so every\n * replica addresses the pieces identically; the transform remaps any\n * concurrent position in the old run into the correct piece.\n */\nexport interface FormatRangeIntent extends IntentBase {\n kind: \"formatRange\";\n blockId: StableId;\n runId: StableId;\n start: number;\n end: number;\n patch: Record<string, unknown>;\n /** Piece ids, present iff the piece exists: before when start>0, after when\n * end<runLen. middle always. */\n beforeId?: StableId;\n middleId: StableId;\n afterId?: StableId;\n}\n\n/**\n * A table operation that does NOT create new tracked nodes (so no carried ids\n * are needed and the transform is identity): delete row/column/table, cell\n * shading, cell vertical align. The target cell is addressed by the stable id\n * of a paragraph inside it. Row/column INSERTION (which creates cells with new\n * paragraphs and runs needing carried ids) is a documented harder extension.\n */\nexport interface TableOpIntent extends IntentBase {\n /** Stable id of a paragraph inside the target cell. */\n cellParagraphId: StableId;\n kind: \"tableOp\";\n op:\n | \"deleteRow\"\n | \"deleteCol\"\n | \"deleteTable\"\n | \"rowAbove\"\n | \"rowBelow\"\n | \"colLeft\"\n | \"colRight\"\n | { kind: \"cellShading\"; fill: string | null }\n | { kind: \"cellVAlign\"; v: \"top\" | \"center\" | \"bottom\" }\n | { kind: \"textWrapping\"; wrapping: \"none\" | \"around\"; xPx: number; yPx: number };\n /** For INSERT ops (rowAbove/rowBelow/colLeft/colRight): carried ids for the\n * new tracked nodes (p / r) in document order, so replicas address them\n * alike. */\n nodeIds?: StableId[];\n}\n\n/**\n * Merge a paragraph into its predecessor (Backspace at paragraph start). The\n * paragraph's runs MOVE into the previous paragraph (element identity\n * preserved), so run-addressed positions survive unchanged — an identity\n * transform. The merged paragraph's block id is retired.\n */\nexport interface MergeParagraphIntent extends IntentBase {\n kind: \"mergeParagraph\";\n /** The paragraph to merge into the one before it. */\n blockId: StableId;\n}\n\n/**\n * Add a review comment anchored to an entire run. Comment markers\n * (commentRangeStart/End + a commentReference run) are inserted as run\n * siblings and the comment body goes into comments.xml — no commented run's\n * text moves, so the transform is identity. The nondeterministic values\n * (w14:paraId, w:date) are generated once by the originating client and\n * carried here (plan doc 05) so every replica writes identical XML. Sub-range\n * comments (splitting the run) are the documented harder extension.\n */\nexport interface CommentRunIntent extends IntentBase {\n kind: \"commentRun\";\n runId: StableId;\n text: string;\n author: string;\n initials?: string;\n /** Carried provenance for deterministic XML across replicas. */\n date: string;\n paraId: string;\n}\n\n/**\n * Rich paste: splice validated OOXML paragraph blocks after a target\n * paragraph. The pasting client converts clipboard HTML to OOXML locally\n * (browser-only, engine-dependent — doc 02 M4) and carries the serialized\n * blocks; the server VALIDATES them against the positive allowlist before\n * applying (doc 11 gate 2). `nodeIds` are carried ids for the new tracked\n * nodes (p / r) in document order, so every replica addresses them alike.\n * Inserting separate blocks shifts no existing run's offsets — identity\n * transform.\n */\nexport interface PasteBlocksIntent extends IntentBase {\n kind: \"pasteBlocks\";\n /** Paragraph after which to insert the pasted blocks. */\n afterBlockId: StableId;\n /** Serialized OOXML block list (w:p elements), validated at apply. */\n blocksXml: string;\n /** Carried ids for the new p/r nodes, in document order. */\n nodeIds: StableId[];\n}\n\n/**\n * Insert an image at a run (as a sibling drawing run — no text split, identity\n * transform). The plan's media design carries bytes out-of-band via presigned\n * upload with the intent carrying (part, extents, sha) — modeled here with the\n * bytes inline (base64), which is correct for small images and keeps the whole\n * flow testable headlessly; the out-of-band path is a transport optimization\n * over the same convergence. Client-measured dimensions (widthPx/heightPx) are\n * carried so layout reserves space deterministically. `nodeIds` are carried\n * ids for the new drawing run(s).\n */\nexport interface InsertImageIntent extends IntentBase {\n kind: \"insertImage\";\n runId: StableId;\n /**\n * sha256 hex of the BLOB (plan doc 16 §2): plaintext bytes in plaintext\n * mode, ciphertext in E2EE mode. This is the reservation's hash\n * COMMITMENT — every replica registers the part pending and verifies any\n * later-arriving bytes against it (the swap-proofing trust chain, doc 16\n * §1.1). Bytes NEVER ride the intent (doc 06: sequencer bandwidth is\n * proportional to typing, not content).\n */\n blobSha: string;\n /** Blob length in bytes — validate.ts bounds it (doc 13 blocker 3). */\n bytesLen: number;\n /** File extension without dot (png/jpg/gif…) — drives the content type. */\n ext: string;\n /** E2EE only: base64 12-byte GCM IV, recorded so any holder can re-seal\n * byte-identically for re-supply (doc 16 §5.3). */\n iv?: string;\n widthPx: number;\n heightPx: number;\n nodeIds: StableId[];\n}\n\n/**\n * Insert a page/column break at the END of a run (inserts sibling break + tail\n * runs — no text split, identity transform). Mid-run breaks (which split the\n * run) follow the carried-id-split pattern (formatRange) and are the\n * documented extension. `nodeIds` are carried ids for the new runs.\n */\nexport interface InsertBreakIntent extends IntentBase {\n kind: \"insertBreak\";\n runId: StableId;\n breakKind: \"page\" | \"column\";\n nodeIds: StableId[];\n}\n\n/**\n * Insert a math (OMML) formula from a linear expression at the end of a run\n * (sibling insertion — identity transform). `nodeIds` for any new tracked\n * nodes.\n */\nexport interface InsertMathIntent extends IntentBase {\n kind: \"insertMath\";\n runId: StableId;\n mathText: string;\n nodeIds: StableId[];\n}\n\n/** Insert a shape/textbox drawing at the end of a run (sibling — identity). */\nexport interface InsertShapeIntent extends IntentBase {\n kind: \"insertShape\";\n runId: StableId;\n preset: \"line\" | \"verticalLine\" | \"rectangle\" | \"roundedRectangle\" | \"ellipse\" | \"diamond\" | \"textBox\";\n text?: string;\n nodeIds: StableId[];\n}\n\n/**\n * Reply to an existing comment (threading). Addressed by the parent comment's\n * id (a deterministic string). Carries provenance (w14:paraId candidates in\n * consumption order + the w:date) so every replica writes identical XML. No\n * document run's text moves — identity transform.\n */\nexport interface ReplyCommentIntent extends IntentBase {\n kind: \"replyComment\";\n parentId: string;\n text: string;\n author: string;\n initials?: string;\n date: string;\n /** paraId candidates in the order replyToComment consumes them: the reply's\n * paraId, preceded by the parent's if the parent lacks one. */\n paraIds: string[];\n}\n\n/** Adjust a paragraph's indent (Tab/Shift-Tab). Block-level, identity. */\nexport interface AdjustIndentIntent extends IntentBase {\n kind: \"adjustIndent\";\n blockId: StableId;\n direction: 1 | -1;\n}\n\n/** Set paragraph line/before/after spacing. Block-level, identity. */\nexport interface SetSpacingIntent extends IntentBase {\n kind: \"setSpacing\";\n blockId: StableId;\n /** ParagraphSpacingPatch (before/after/line/lineRule) — carried verbatim. */\n patch: Record<string, unknown>;\n}\n\n/** Insert a page-number field at the end of a run (sibling insertion,\n * identity). Page fields are deterministic (no clock). */\nexport interface InsertPageFieldIntent extends IntentBase {\n kind: \"insertPageField\";\n runId: StableId;\n fieldKind: \"page\" | \"pageOfTotal\";\n nodeIds: StableId[];\n}\n\n/** Wrap a run in a hyperlink to `url`. The url is scheme-validated at apply\n * (rejects javascript:/data: etc. — doc 11 gate 1). Run-level, identity. */\nexport interface SetLinkIntent extends IntentBase {\n kind: \"setLink\";\n runId: StableId;\n url: string;\n nodeIds: StableId[];\n}\n\n/** Insert a footnote reference at the end of a run + its footnote text. */\nexport interface InsertFootnoteIntent extends IntentBase {\n kind: \"insertFootnote\";\n runId: StableId;\n text: string;\n nodeIds: StableId[];\n}\n\n/** Set/clear a drop cap on a paragraph. Block-level. */\nexport interface SetDropCapIntent extends IntentBase {\n kind: \"setDropCap\";\n blockId: StableId;\n mode: \"drop\" | \"margin\" | null;\n nodeIds: StableId[];\n}\n\n/** Set/clear a paragraph divider (horizontal rule). Block-level, identity. */\nexport interface SetDividerIntent extends IntentBase {\n kind: \"setDivider\";\n blockId: StableId;\n divider: Record<string, unknown> | null;\n}\n\n/** Insert a bookmark anchor at the end of a run. */\nexport interface InsertBookmarkIntent extends IntentBase {\n kind: \"insertBookmark\";\n runId: StableId;\n name: string;\n}\n\nexport type Intent =\n | InsertTextIntent\n | DeleteTextIntent\n | SplitParagraphIntent\n | FormatRunIntent\n | FormatParagraphIntent\n | SetListTypeIntent\n | FormatRangeIntent\n | TableOpIntent\n | MergeParagraphIntent\n | CommentRunIntent\n | PasteBlocksIntent\n | InsertImageIntent\n | SuggestRevisionIntent\n | InsertBreakIntent\n | InsertMathIntent\n | InsertShapeIntent\n | ReplyCommentIntent\n | AdjustIndentIntent\n | SetSpacingIntent\n | InsertPageFieldIntent\n | SetLinkIntent\n | InsertFootnoteIntent\n | SetDropCapIntent\n | SetDividerIntent\n | InsertBookmarkIntent\n | InsertBlankPageIntent\n | InsertSectionBreakIntent\n | InsertCrossRefIntent\n | InsertCoverPageIntent\n | SetPageLayoutIntent\n | SetListLevelIntent\n | InsertWordArtIntent\n | InsertChartIntent\n | InsertSmartArtIntent\n | SetLineNumberingIntent\n | InsertDateTimeFieldIntent\n | InsertFieldIntent\n | SetDrawingRotationIntent\n | SetDrawingFillIntent\n | SetChartDataIntent\n | SetSmartArtNodeTextIntent\n | SetDrawingWordArtTextIntent\n | SetDrawingLineStyleIntent\n | SetImageAltTextIntent\n | RemoveLinkIntent\n | SetImageWrapIntent\n | SetDrawingOrderIntent\n | SetSmartArtDataIntent\n | SetSmartArtFillIntent\n | SetSmartArtTextFormatIntent\n | SetFloatingPagePositionIntent\n | ResizeDrawingIntent\n | ResizeTableColumnIntent\n | ResizeTableRowIntent\n | MoveTableIntent\n | RemoveDrawingIntent\n | SetMathLinearIntent\n | DeleteMathIntent\n | MoveMathIntent\n | EnsureHeaderFooterIntent\n | DeleteCommentIntent\n | InsertBookmarkRangeIntent\n | ToggleCheckboxIntent\n | AcceptRevisionIntent\n | RejectRevisionIntent\n | AcceptAllRevisionsIntent\n | InsertTableIntent;\n\n/** Insert a blank page at the end of a run. */\nexport interface InsertBlankPageIntent extends IntentBase {\n kind: \"insertBlankPage\";\n runId: StableId;\n nodeIds: StableId[];\n}\n\n/** Insert a section break at the end of a run. */\nexport interface InsertSectionBreakIntent extends IntentBase {\n kind: \"insertSectionBreak\";\n runId: StableId;\n breakType: \"nextPage\" | \"continuous\";\n nodeIds: StableId[];\n}\n\n/** Insert a cross-reference to a bookmark at the end of a run. */\nexport interface InsertCrossRefIntent extends IntentBase {\n kind: \"insertCrossRef\";\n runId: StableId;\n bookmark: string;\n refKind: \"text\" | \"page\";\n nodeIds: StableId[];\n}\n\n/** Insert a cover page (document-level). */\nexport interface InsertCoverPageIntent extends IntentBase {\n kind: \"insertCoverPage\";\n /** CoverPageContent ({title, subtitle?, author?, …}) — carried verbatim. */\n content: Record<string, unknown>;\n nodeIds: StableId[];\n}\n\n/** Update page setup: margins/size/orientation/columns/borders. When target is\n * omitted it applies to every section; document-level, identity transform. */\nexport interface SetPageLayoutIntent extends IntentBase {\n kind: \"setPageLayout\";\n /** PageLayoutPatch ({margins?, size?, orientation?, columns?, …}) — carried\n * verbatim; validated against a shape+range allowlist. */\n patch: Record<string, unknown>;\n}\n\n/** Indent (+1) or outdent (-1) a paragraph's list nesting level. Block-level. */\nexport interface SetListLevelIntent extends IntentBase {\n kind: \"setListLevel\";\n blockId: StableId;\n delta: 1 | -1;\n}\n\n/** Insert decorative WordArt (a text drawing) at the end of a run. */\nexport interface InsertWordArtIntent extends IntentBase {\n kind: \"insertWordArt\";\n runId: StableId;\n text: string;\n preset: \"plain\" | \"archUp\" | \"archDown\" | \"wave\" | \"chevron\";\n nodeIds: StableId[];\n}\n\n/** Insert a data chart (column/bar/line/pie) at the end of a run. The chart\n * data is carried verbatim; the workbook part is generated deterministically. */\nexport interface InsertChartIntent extends IntentBase {\n kind: \"insertChart\";\n runId: StableId;\n chart: {\n type: \"column\" | \"bar\" | \"line\" | \"pie\";\n title?: string;\n categories: string[];\n series: { name: string; values: number[] }[];\n };\n nodeIds: StableId[];\n}\n\n/** Insert a SmartArt diagram (process/cycle/hierarchy/list) at end of a run. */\nexport interface InsertSmartArtIntent extends IntentBase {\n kind: \"insertSmartArt\";\n runId: StableId;\n smartArt: { layout: \"process\" | \"cycle\" | \"hierarchy\" | \"list\"; items: string[] };\n nodeIds: StableId[];\n}\n\n/** Toggle/configure margin line numbering for the section(s). Document-level. */\nexport interface SetLineNumberingIntent extends IntentBase {\n kind: \"setLineNumbering\";\n patch: {\n enabled: boolean;\n countBy?: number;\n restart?: \"continuous\" | \"newPage\" | \"newSection\";\n start?: number;\n };\n}\n\n/** Insert an auto-updating DATE or TIME field at the end of a run. The picture\n * (format string) is carried so every replica renders identical field XML. */\nexport interface InsertDateTimeFieldIntent extends IntentBase {\n kind: \"insertDateTimeField\";\n runId: StableId;\n dtKind: \"date\" | \"time\";\n picture: string;\n nodeIds: StableId[];\n}\n\n/** Insert a Word field (e.g. PAGE, NUMPAGES, REF) at the end of a run. The\n * instruction is restricted to a safe positive allowlist (no INCLUDETEXT/DDE/\n * LINK external-content fields). */\nexport interface InsertFieldIntent extends IntentBase {\n kind: \"insertField\";\n runId: StableId;\n instruction: string;\n cachedResult?: string;\n nodeIds: StableId[];\n}\n\n/** Rotate the drawing carried by a run. Identity transform (run-addressed). */\nexport interface SetDrawingRotationIntent extends IntentBase {\n kind: \"setDrawingRotation\";\n runId: StableId;\n degrees: number;\n}\n\n/** Set/clear the solid fill of the drawing carried by a run. `color` is a\n * 6-hex-digit RGB (no #) or null to clear. */\nexport interface SetDrawingFillIntent extends IntentBase {\n kind: \"setDrawingFill\";\n runId: StableId;\n color: string | null;\n}\n\n/** Replace the data of the chart carried by a run (same shape as insertChart's\n * chart). Edits an existing chart in place. */\nexport interface SetChartDataIntent extends IntentBase {\n kind: \"setChartData\";\n runId: StableId;\n chart: {\n type: \"column\" | \"bar\" | \"line\" | \"pie\";\n title?: string;\n categories: string[];\n series: { name: string; values: number[] }[];\n };\n}\n\n/** Set the text of one node of the SmartArt diagram carried by a run. */\nexport interface SetSmartArtNodeTextIntent extends IntentBase {\n kind: \"setSmartArtNodeText\";\n runId: StableId;\n index: number;\n text: string;\n}\n\n/** Replace the text of the WordArt drawing carried by a run. */\nexport interface SetDrawingWordArtTextIntent extends IntentBase {\n kind: \"setDrawingWordArtText\";\n runId: StableId;\n text: string;\n}\n\n/** Set the outline (line) style of the drawing carried by a run. */\nexport interface SetDrawingLineStyleIntent extends IntentBase {\n kind: \"setDrawingLineStyle\";\n runId: StableId;\n color: string; // 6-hex RGB (no #)\n widthPx: number;\n dash: \"solid\" | \"dashed\" | \"dotted\";\n}\n\n/** Set/clear the alt text (accessibility description) of the image/drawing\n * carried by a run. Empty string clears it. */\nexport interface SetImageAltTextIntent extends IntentBase {\n kind: \"setImageAltText\";\n runId: StableId;\n alt: string;\n}\n\n/** Remove the hyperlink wrapping a run's text (inverse of setLink). */\nexport interface RemoveLinkIntent extends IntentBase {\n kind: \"removeLink\";\n runId: StableId;\n}\n\n/** Set the text-wrap mode of the drawing carried by a run. A floating mode\n * converts an inline drawing to an anchor. */\nexport interface SetImageWrapIntent extends IntentBase {\n kind: \"setImageWrap\";\n runId: StableId;\n mode: \"inline\" | \"square\" | \"topAndBottom\" | \"none\" | \"behind\";\n}\n\n/** Bring the (floating) drawing carried by a run to front/back. */\nexport interface SetDrawingOrderIntent extends IntentBase {\n kind: \"setDrawingOrder\";\n runId: StableId;\n order: \"front\" | \"back\";\n}\n\n/** Replace the whole SmartArt diagram (layout + items) carried by a run. */\nexport interface SetSmartArtDataIntent extends IntentBase {\n kind: \"setSmartArtData\";\n runId: StableId;\n smartArt: { layout: \"process\" | \"cycle\" | \"hierarchy\" | \"list\"; items: string[] };\n}\n\n/** Set/clear the fill of a SmartArt node (or all nodes when nodeIndex omitted).\n * `color` is 6-hex RGB (no #) or null to clear. */\nexport interface SetSmartArtFillIntent extends IntentBase {\n kind: \"setSmartArtFill\";\n runId: StableId;\n color: string | null;\n nodeIndex?: number;\n}\n\n/** Set the text format of a SmartArt node (or all nodes). */\nexport interface SetSmartArtTextFormatIntent extends IntentBase {\n kind: \"setSmartArtTextFormat\";\n runId: StableId;\n format: {\n fontFamily: string;\n fontSizePt: number;\n color: string; // 6-hex\n bold: boolean;\n italic: boolean;\n alignment: \"left\" | \"center\" | \"right\";\n };\n nodeIndex?: number;\n}\n\n/** Position the (floating) drawing carried by a run relative to the page. */\nexport interface SetFloatingPagePositionIntent extends IntentBase {\n kind: \"setFloatingPagePosition\";\n runId: StableId;\n xPx: number;\n yPx: number;\n}\n\n/** Resize the drawing carried by a run (extent in px; the mutation clamps to\n * a 1px-EMU floor and keeps line geometry degenerate on its minor axis). */\nexport interface ResizeDrawingIntent extends IntentBase {\n kind: \"resizeDrawing\";\n runId: StableId;\n widthPx: number;\n heightPx: number;\n}\n\n/** Drag-resize a table column boundary. Addressed like tableOp: a paragraph\n * inside the table. `renderedWidths` (the dragger's measured column widths)\n * rides as DATA so every replica applies the identical mutation. */\nexport interface ResizeTableColumnIntent extends IntentBase {\n kind: \"resizeTableColumn\";\n cellParagraphId: StableId;\n boundary: number;\n deltaPx: number;\n renderedWidths?: number[];\n}\n\n/** Drag-resize a table row's height (trHeight atLeast). */\nexport interface ResizeTableRowIntent extends IntentBase {\n kind: \"resizeTableRow\";\n cellParagraphId: StableId;\n rowIdx: number;\n heightPx: number;\n}\n\n/** Position a table at page coordinates (converts inline to float). */\nexport interface MoveTableIntent extends IntentBase {\n kind: \"moveTable\";\n cellParagraphId: StableId;\n xPx: number;\n yPx: number;\n preservePageStart: boolean;\n pageDelta: number;\n}\n\n/** Delete the drawing carried by a run (removes the carrier run). */\nexport interface RemoveDrawingIntent extends IntentBase {\n kind: \"removeDrawing\";\n runId: StableId;\n}\n\n/** Replace the equation of the math object in a paragraph (linear syntax).\n * Math (m:oMath) lives at paragraph level, so this is block-addressed. */\nexport interface SetMathLinearIntent extends IntentBase {\n kind: \"setMathLinear\";\n blockId: StableId;\n mathText: string;\n}\n\n/** Delete the math object in a paragraph. */\nexport interface DeleteMathIntent extends IntentBase {\n kind: \"deleteMath\";\n blockId: StableId;\n}\n\n/** Drag-move the equation out of `blockId` to the text position `at`. The\n * equation is addressed like the other math intents (block + firstMathIn); the\n * destination is an ordinary wire caret. Dropping mid-text splits the\n * destination run, so the split-off tail run takes a carried id. */\nexport interface MoveMathIntent extends IntentBase {\n kind: \"moveMath\";\n blockId: StableId;\n at: Position;\n nodeIds: StableId[];\n}\n\n/** Create the document's header (or footer) part the way Word does on first\n * entry into the margin band: a new part + relationship + content-type\n * override, referenced from every sectPr (one is materialized when a minimal\n * document has none). Applies as a clean no-op when the part already exists,\n * so it is safe for two participants to open the header at the same moment.\n * The created paragraph and run take carried ids — every later edit in the\n * header addresses them. */\nexport interface EnsureHeaderFooterIntent extends IntentBase {\n kind: \"ensureHeaderFooter\";\n hfKind: \"header\" | \"footer\";\n nodeIds: StableId[];\n}\n\n/** Delete a comment (and its reply thread) by id. */\nexport interface DeleteCommentIntent extends IntentBase {\n kind: \"deleteComment\";\n commentId: string;\n}\n\n/** Wrap a sub-range of a run's text in a named bookmark. */\nexport interface InsertBookmarkRangeIntent extends IntentBase {\n kind: \"insertBookmarkRange\";\n runId: StableId;\n name: string;\n start: number;\n end: number;\n}\n\n/** Toggle the checkbox content control carried by a run. */\nexport interface ToggleCheckboxIntent extends IntentBase {\n kind: \"toggleCheckbox\";\n runId: StableId;\n}\n\n/** Accept one tracked change, addressed by its position in document order\n * (collectRevisions order — deterministic across replicas). */\nexport interface AcceptRevisionIntent extends IntentBase {\n kind: \"acceptRevision\";\n index: number;\n}\n\n/** Reject one tracked change, addressed by document-order index. */\nexport interface RejectRevisionIntent extends IntentBase {\n kind: \"rejectRevision\";\n index: number;\n}\n\n/** Accept every tracked change in the document. */\nexport interface AcceptAllRevisionsIntent extends IntentBase {\n kind: \"acceptAllRevisions\";\n}\n\n/** Insert a rows×cols table after the paragraph containing the anchor run. */\nexport interface InsertTableIntent extends IntentBase {\n kind: \"insertTable\";\n runId: StableId;\n rows: number;\n cols: number;\n nodeIds: StableId[];\n}\n\n/** A sequenced log entry: an applied intent with its assigned seq, or a\n * rejection no-op (doc 03) that still occupies a position in the total order\n * so every replica agrees where a drop took effect. */\nexport type LogEntry =\n | { seq: number; kind: \"applied\"; intent: Intent }\n | { seq: number; kind: \"rejected\"; clientId: string; clientSeq: number; reason: string };\n\nexport function idempotencyKey(i: { clientId: string; clientSeq: number }): string {\n return `${i.clientId}:${i.clientSeq}`;\n}\n\n/** An intent minus its wire bookkeeping — distributive over the union so each\n * variant keeps its own fields (a plain Omit<Intent,…> would collapse to the\n * shared keys only). Used by the editor producer and the undo inverse. */\nexport type IntentBody = Intent extends infer T ? (T extends Intent ? Omit<T, \"clientId\" | \"clientSeq\" | \"base\"> : never) : never;\n","import { Intent, Position } from \"./intents.js\";\n\n/**\n * The canonical transform (plan doc 03). Stable ids make structural\n * concurrency safe — a run is re-found by id regardless of what moved around\n * it — but a character *offset* within a run can be stale when a concurrent\n * edit changed that same run's text ahead of it. This adjusts positions\n * run-locally: OT-lite, not a general OT system, implemented once and used by\n * both the server (canonical) and the client (reconciliation replay).\n *\n * A prior intent's effect on a run is summarized as a RunEdit. Two shapes:\n * - text change: at `at`, `del` chars removed then `ins` inserted (insert,\n * delete).\n * - split: text at/after `at` in `runId` moves to `movedToRunId` starting at\n * offset 0 (splitParagraph). A position after the split point is remapped\n * to the new run rather than shifted.\n */\nexport interface RunEdit {\n runId: number;\n at: number;\n del: number;\n ins: number;\n /** Present for splits: the run the tail (offset >= at) moved into. */\n movedToRunId?: number;\n /** Present for a format-range split: the old run is replaced by up to three\n * pieces and a position remaps by which piece it falls into. */\n formatSplit?: { start: number; end: number; beforeId?: number; middleId: number; afterId?: number };\n}\n\n/** The run edits a prior intent imposes on the document (only same-run edits\n * matter to offset transformation; cross-run structure is handled by ids). */\nexport function runEditsOf(intent: Intent): RunEdit[] {\n switch (intent.kind) {\n case \"insertText\":\n return [{ runId: intent.at.runId, at: intent.at.offset, del: 0, ins: intent.text.length }];\n case \"deleteText\":\n return [{ runId: intent.runId, at: intent.start, del: intent.end - intent.start, ins: 0 }];\n case \"splitParagraph\":\n // The tail (offset >= at.offset) leaves this run for the new run.\n return [{ runId: intent.at.runId, at: intent.at.offset, del: 0, ins: 0, movedToRunId: intent.newRunId }];\n case \"formatRun\":\n // Whole-run formatting moves no text and preserves the run id — no\n // position of any concurrent intent is affected.\n return [];\n case \"formatParagraph\":\n // Block-level; moves no text, preserves ids.\n return [];\n case \"setListType\":\n // Block-level; moves no text, preserves ids.\n return [];\n case \"formatRange\":\n // The old run is replaced by up to three pieces; positions remap.\n return [{\n runId: intent.runId, at: intent.start, del: 0, ins: 0,\n formatSplit: { start: intent.start, end: intent.end, beforeId: intent.beforeId, middleId: intent.middleId, afterId: intent.afterId },\n }];\n case \"tableOp\":\n // Non-node-creating table ops don't shift any surviving run's offsets;\n // concurrent edits to deleted cells fail cleanly at apply (retired ids).\n return [];\n case \"mergeParagraph\":\n // Runs move (identity preserved) into the previous paragraph; run-\n // addressed offsets are unchanged.\n return [];\n case \"commentRun\":\n // Comment markers are run siblings; no run's text offsets shift.\n return [];\n case \"pasteBlocks\":\n // Separate new blocks inserted after a paragraph; no existing offsets shift.\n return [];\n case \"suggestRevision\":\n // Strikes WRAP text (w:del) and marks annotate paragraph glyphs —\n // no text moves, so concurrent intents' positions are unaffected.\n // (The wrap does split runs at strike boundaries; a concurrent\n // same-run edit past the boundary resolves-or-rejects IDENTICALLY on\n // every replica — degraded concurrency fidelity, never divergence.\n // The formatRange-style split remap is the designed follow-on.)\n return [];\n case \"insertImage\":\n // Image is a sibling drawing run; no run's text offsets shift.\n return [];\n case \"insertBreak\":\n // Break at end-of-run inserts sibling runs; no text offsets shift.\n return [];\n case \"insertMath\":\n case \"insertShape\":\n // Sibling insertion at end of run; no text offsets shift.\n return [];\n case \"replyComment\":\n // Comment-part change; no document run offsets shift.\n return [];\n case \"adjustIndent\":\n case \"setSpacing\":\n // Block-level; no text offsets shift.\n return [];\n case \"insertPageField\":\n // Sibling field run at end of run; no text offsets shift.\n return [];\n case \"setLink\":\n case \"insertFootnote\":\n case \"setDropCap\":\n case \"setDivider\":\n case \"insertBookmark\":\n case \"insertBlankPage\":\n case \"insertSectionBreak\":\n case \"insertCrossRef\":\n case \"insertCoverPage\":\n case \"setPageLayout\":\n case \"setListLevel\":\n case \"insertWordArt\":\n case \"insertChart\":\n case \"insertSmartArt\":\n case \"setLineNumbering\":\n case \"insertDateTimeField\":\n case \"insertField\":\n case \"setDrawingRotation\":\n case \"setDrawingFill\":\n case \"setChartData\":\n case \"setSmartArtNodeText\":\n case \"setDrawingWordArtText\":\n case \"setDrawingLineStyle\":\n case \"setImageAltText\":\n case \"removeLink\":\n case \"setImageWrap\":\n case \"setDrawingOrder\":\n case \"setSmartArtData\":\n case \"setSmartArtFill\":\n case \"setSmartArtTextFormat\":\n case \"setFloatingPagePosition\":\n case \"resizeDrawing\":\n case \"resizeTableColumn\":\n case \"resizeTableRow\":\n case \"moveTable\":\n case \"removeDrawing\":\n case \"setMathLinear\":\n case \"deleteMath\":\n case \"ensureHeaderFooter\":\n case \"deleteComment\":\n case \"insertBookmarkRange\":\n case \"toggleCheckbox\":\n // Run/block/document-level; no existing run's text offsets shift.\n return [];\n case \"moveMath\":\n // Same shape as insertMath above: dropping the equation mid-text splits\n // the destination run, and that split is not modeled here — a concurrent\n // edit into the tail half of that run resolves-or-rejects identically on\n // every replica (degraded concurrency fidelity, never divergence). The\n // movedToRunId remap is the designed follow-on for both.\n return [];\n case \"insertTable\":\n case \"acceptRevision\":\n case \"rejectRevision\":\n case \"acceptAllRevisions\":\n // Accept/reject CAN change text (an accepted deletion / rejected\n // insertion removes runs). Under the session's one-in-flight constraint\n // these administrative review ops don't overlap a concurrent text edit,\n // so no cross-intent RunEdit is modeled here (see plan doc 03: multi-\n // pending OT is out of scope).\n return [];\n }\n}\n\n/** Adjust a single position against one prior run edit. `leftGravity` is for\n * a position that should NOT move when an insert lands exactly on it (e.g. the\n * END of a delete range — text inserted at the end stays outside the range).\n * Default (right gravity) shifts a position right when an insert is at it. */\nexport function transformPositionAgainstEdit(pos: Position, edit: RunEdit, leftGravity = false): Position {\n if (pos.runId !== edit.runId) return pos;\n\n if (edit.formatSplit) {\n // The old run split into before[0,start) / middle[start,end) / after[end,).\n // Remap the position into whichever piece it lands in (pieces absent when\n // start==0 or end==runLen — positions can't fall in those ranges).\n const { start, end, beforeId, middleId, afterId } = edit.formatSplit;\n if (pos.offset < start && beforeId !== undefined) {\n return { blockId: pos.blockId, runId: beforeId, offset: pos.offset };\n }\n if (pos.offset <= end) {\n return { blockId: pos.blockId, runId: middleId, offset: pos.offset - start };\n }\n if (afterId !== undefined) {\n return { blockId: pos.blockId, runId: afterId, offset: pos.offset - end };\n }\n return { blockId: pos.blockId, runId: middleId, offset: pos.offset - start };\n }\n\n if (edit.movedToRunId !== undefined) {\n // Split: everything strictly after the split point moved to the new run.\n // The split point itself stays with the original run (caret before the\n // break); content beyond it is re-addressed into the moved run.\n if (pos.offset > edit.at) {\n return { blockId: pos.blockId, runId: edit.movedToRunId, offset: pos.offset - edit.at };\n }\n return pos;\n }\n\n const delEnd = edit.at + edit.del;\n // A pure insert (del==0) exactly at the position: right gravity shifts the\n // position after the inserted text; left gravity keeps it before.\n if (edit.del === 0 && edit.at === pos.offset) {\n return leftGravity ? pos : { ...pos, offset: pos.offset + edit.ins };\n }\n if (delEnd <= pos.offset) {\n // Edit lies fully before the position: shift by the net length change.\n return { ...pos, offset: pos.offset + edit.ins - edit.del };\n }\n if (edit.at >= pos.offset) {\n // Edit lies at or after the position: no shift.\n return pos;\n }\n // The position falls inside the deleted span: collapse to the edit point,\n // now sitting just after any inserted replacement text.\n return { ...pos, offset: edit.at + edit.ins };\n}\n\n/** Adjust a position against an ordered list of prior intents (oldest first).\n * `movedRunId` mapping composes: a position remapped into a new run by a split\n * is then transformed against later edits addressed to that new run. */\nexport function transformPosition(pos: Position, ahead: Intent[], leftGravity = false): Position {\n let out = pos;\n for (const intent of ahead) {\n for (const edit of runEditsOf(intent)) {\n out = transformPositionAgainstEdit(out, edit, leftGravity);\n }\n }\n return out;\n}\n\n/** Transform an intent so its positions are valid against the state produced\n * by `ahead` (the intents sequenced since this intent's base). Returns a new\n * intent with adjusted positions; `base` is advanced past `ahead`. */\nexport function transformIntent(intent: Intent, ahead: Intent[]): Intent {\n const newBase = intent.base + ahead.length;\n switch (intent.kind) {\n case \"insertText\":\n return { ...intent, at: transformPosition(intent.at, ahead), base: newBase };\n case \"splitParagraph\":\n return { ...intent, at: transformPosition(intent.at, ahead), base: newBase };\n case \"formatRun\":\n // Addressed by run id only; nothing to transform (whole-run).\n return { ...intent, base: newBase };\n case \"formatParagraph\":\n // Addressed by block id only; nothing to transform.\n return { ...intent, base: newBase };\n case \"setListType\":\n return { ...intent, base: newBase };\n case \"formatRange\": {\n // Transform the [start,end) endpoints against prior edits in the run.\n const s = transformPosition({ blockId: intent.blockId, runId: intent.runId, offset: intent.start }, ahead);\n const e = transformPosition({ blockId: intent.blockId, runId: intent.runId, offset: intent.end }, ahead);\n // If the endpoints landed in different runs (a prior split), the range\n // no longer describes one run — collapse to a no-op the apply rejects.\n if (e.runId !== s.runId) return { ...intent, runId: s.runId, blockId: s.blockId, start: s.offset, end: s.offset, base: newBase };\n return { ...intent, runId: s.runId, blockId: s.blockId, start: s.offset, end: e.offset, base: newBase };\n }\n case \"tableOp\":\n // Addressed by a stable paragraph id; nothing to transform.\n return { ...intent, base: newBase };\n case \"mergeParagraph\":\n return { ...intent, base: newBase };\n case \"commentRun\":\n return { ...intent, base: newBase };\n case \"pasteBlocks\":\n return { ...intent, base: newBase };\n case \"suggestRevision\": {\n if (!intent.ranges?.length) return { ...intent, base: newBase };\n const ranges = intent.ranges\n .map((r) => {\n const s2 = transformPosition({ blockId: r.blockId, runId: r.runId, offset: r.start }, ahead);\n const e2 = transformPosition({ blockId: r.blockId, runId: r.runId, offset: r.end }, ahead);\n // Endpoints scattered across a prior split: collapse to empty —\n // apply drops empty ranges (clean no-op, identical everywhere).\n if (s2.runId !== e2.runId || e2.offset <= s2.offset) return { ...r, start: 0, end: 0 };\n return { blockId: s2.blockId, runId: s2.runId, start: s2.offset, end: e2.offset };\n })\n .filter((r) => r.end > r.start);\n return { ...intent, ranges, base: newBase };\n }\n case \"insertImage\":\n return { ...intent, base: newBase };\n case \"insertBreak\":\n return { ...intent, base: newBase };\n case \"insertMath\":\n case \"insertShape\":\n return { ...intent, base: newBase };\n case \"moveMath\":\n // The equation is block-addressed, but the DROP is an ordinary caret and\n // shifts with prior edits in the destination run.\n return { ...intent, at: transformPosition(intent.at, ahead), base: newBase };\n case \"replyComment\":\n return { ...intent, base: newBase };\n case \"adjustIndent\":\n case \"setSpacing\":\n case \"insertPageField\":\n case \"setLink\":\n case \"insertFootnote\":\n case \"setDropCap\":\n case \"setDivider\":\n case \"insertBookmark\":\n case \"insertBlankPage\":\n case \"insertSectionBreak\":\n case \"insertCrossRef\":\n case \"insertCoverPage\":\n case \"setPageLayout\":\n case \"setListLevel\":\n case \"insertWordArt\":\n case \"insertChart\":\n case \"insertSmartArt\":\n case \"setLineNumbering\":\n case \"insertDateTimeField\":\n case \"insertField\":\n case \"setDrawingRotation\":\n case \"setDrawingFill\":\n case \"setChartData\":\n case \"setSmartArtNodeText\":\n case \"setDrawingWordArtText\":\n case \"setDrawingLineStyle\":\n case \"setImageAltText\":\n case \"removeLink\":\n case \"setImageWrap\":\n case \"setDrawingOrder\":\n case \"setSmartArtData\":\n case \"setSmartArtFill\":\n case \"setSmartArtTextFormat\":\n case \"setFloatingPagePosition\":\n case \"resizeDrawing\":\n case \"resizeTableColumn\":\n case \"resizeTableRow\":\n case \"moveTable\":\n case \"removeDrawing\":\n case \"setMathLinear\":\n case \"deleteMath\":\n case \"ensureHeaderFooter\":\n case \"deleteComment\":\n case \"insertBookmarkRange\":\n case \"toggleCheckbox\":\n case \"insertTable\":\n case \"acceptRevision\":\n case \"rejectRevision\":\n case \"acceptAllRevisions\":\n return { ...intent, base: newBase };\n case \"deleteText\": {\n const s = transformPosition({ blockId: intent.blockId, runId: intent.runId, offset: intent.start }, ahead);\n // The delete's END uses left gravity: text inserted exactly at the end\n // stays OUTSIDE the deleted range (so an undo of an insert doesn't\n // swallow content a concurrent insert placed right after it).\n const e = transformPosition({ blockId: intent.blockId, runId: intent.runId, offset: intent.end }, ahead, true);\n // A delete whose endpoints were split into different runs no longer\n // describes a single-run range; callers treat a collapsed/cross-run\n // range as a no-op rather than corrupting text.\n return { ...intent, runId: s.runId, blockId: s.blockId, start: s.offset, end: e.runId === s.runId ? e.offset : s.offset, base: newBase };\n }\n }\n}\n","import {\n DocxDocument,\n StableIds,\n deleteSuggestedRange,\n markParagraphGlyph,\n applyInsertText,\n applySplitParagraph,\n applyDeleteRange,\n applyRunFormat,\n setParagraphAlignment,\n setParagraphStyle,\n setListType,\n adjustIndent,\n setParagraphSpacing,\n insertPageField,\n setLink,\n insertFootnote,\n setDropCapAt,\n setParagraphDivider,\n insertBookmarkAt,\n insertBlankPageAt,\n insertSectionBreak,\n insertCrossReference,\n insertCoverPage,\n setPageLayout,\n setListLevel,\n insertWordArtAt,\n insertChartAt,\n insertSmartArtAt,\n setLineNumbering,\n insertDateTimeField,\n insertField,\n setDrawingRotation,\n setDrawingFill,\n setChartData,\n setSmartArtNodeText,\n setDrawingWordArtText,\n setDrawingLineStyle,\n setImageAltText,\n removeLink,\n setImageWrap,\n setDrawingOrder,\n setSmartArtData,\n setSmartArtFill,\n setSmartArtTextFormat,\n setFloatingPagePosition,\n setMathLinear,\n deleteMath,\n moveMath,\n deleteComment,\n insertBookmarkAroundSelection,\n checkboxStateElement,\n toggleCheckbox,\n collectRevisions,\n insertTableAfter,\n acceptRevision,\n rejectRevision,\n acceptAllRevisions,\n isSafeUrl,\n applyTableOp,\n runWireLength,\n resolveRunOffset,\n resizeDrawing,\n resizeTableColumn,\n resizeTableRow,\n moveTableTo,\n removeDrawingRun,\n mergeParagraphBackward,\n addComment,\n replyToComment,\n recordedProvenance,\n validatePastedOoxml,\n parseXml,\n localName,\n insertImageAt,\n insertBreakAt,\n insertMathAt,\n insertShapeAt,\n type EditCaret,\n type Run,\n type Block,\n type XmlElement,\n type SelectionSegment,\n} from \"@wordinweb/core\";\nimport { Intent, Position } from \"./intents.js\";\n\n/**\n * How much of the document an applied intent disturbed — the input to\n * dirty-scoped reconciliation (perf B9/B10). Reconciling an edit used to cost\n * O(document) on every path (full model reparse + full id walk), so per-op\n * cost grew with the document; the scope lets the caller pay for THE EDIT.\n *\n * `doc` is the conservative default and means exactly today's behavior: full\n * refresh() + assignFromRoots(). Only intents whose blast radius is provably\n * one paragraph (or the two sides of a split) report a narrower scope; every\n * structural, document-level, or exotic intent keeps the document scope.\n */\nexport type Scope =\n | { kind: \"doc\" }\n /** Only these paragraphs' contents changed. */\n | { kind: \"block\"; blocks: XmlElement[] }\n /** `before` was split; `after` is the new sibling that follows it. */\n | { kind: \"split\"; before: XmlElement; after: XmlElement };\n\nexport type ApplyScope = Scope & { applied: boolean };\n\nconst DOC_SCOPE: Scope = { kind: \"doc\" };\n\n/**\n * Union of two dirty scopes — the batching rule for a coalesced repaint: a\n * receive batch (or one rAF window) can apply several intents, and the single\n * repaint that follows must relayout everything any of them touched, once.\n * Doc absorbs everything. Two block scopes merge their block lists. A split\n * stays a split only while every other dirty block is one of its two halves\n * (the layout engine's insertion fast path re-lays exactly those); any\n * combination the narrow forms cannot express verifiably widens to doc scope,\n * which is always correct.\n */\nexport function unionScopes(a: Scope | null, b: Scope): Scope {\n if (!a) return b;\n if (a.kind === \"doc\" || b.kind === \"doc\") return DOC_SCOPE;\n if (a.kind === \"split\" && b.kind === \"split\") {\n return a.before === b.before && a.after === b.after ? a : DOC_SCOPE;\n }\n if (a.kind === \"split\" || b.kind === \"split\") {\n const split = a.kind === \"split\" ? a : (b as Extract<Scope, { kind: \"split\" }>);\n const block = a.kind === \"block\" ? a : (b as Extract<Scope, { kind: \"block\" }>);\n return block.blocks.every((el) => el === split.before || el === split.after) ? split : DOC_SCOPE;\n }\n const blocks = [...a.blocks];\n for (const el of b.blocks) if (!blocks.includes(el)) blocks.push(el);\n return { kind: \"block\", blocks };\n}\n\n/**\n * Bridge from wire intents to the core mutation functions. Resolves stable\n * ids to model positions, applies the mutation headlessly (no DOM), and\n * assigns carried ids to newly created nodes so every replica agrees on ids\n * (plan doc 03). Callers reconcile the parsed model afterward — see\n * resyncScope; this function performs the tree mutation only.\n *\n * Returns true if the intent applied, false for a clean no-op / unresolvable\n * position (the caller records a rejection, doc 03).\n */\nexport function applyIntent(doc: DocxDocument, ids: StableIds, intent: Intent): boolean {\n return applyIntentScoped(doc, ids, intent).applied;\n}\n\n/** applyIntent plus the dirty scope of what it touched. */\nexport function applyIntentScoped(doc: DocxDocument, ids: StableIds, intent: Intent): ApplyScope {\n const out = { scope: DOC_SCOPE };\n try {\n const applied = applyIntentInner(doc, ids, intent, out);\n return { ...out.scope, applied };\n } catch {\n // Any throw (e.g. a carried-id collision from a buggy/hostile client)\n // becomes a clean rejection: the session logs a reject entry and every\n // replica's receive loop keeps running instead of dying mid-broadcast.\n // A throw can leave a half-applied tree, so the scope is not trustworthy —\n // report document scope, which is what a recovering caller must assume.\n return { kind: \"doc\", applied: false };\n }\n}\n\n/**\n * Reconcile the parsed model and the id table with a tree the given intent\n * just mutated, doing work proportional to the intent's scope. Falls back to\n * the full refresh whenever a targeted reparse cannot handle the container\n * (paragraph outside the body story, a paragraph carrying section breaks or\n * bookmark ranges, a split whose halves aren't plain adjacent siblings) —\n * correctness beats speed, and the fallback is exactly the old behavior.\n */\nexport function resyncScope(doc: DocxDocument, ids: StableIds, scope: Scope): void {\n if (scope.kind === \"block\") {\n let ok = true;\n for (const block of scope.blocks) ok = doc.reparseBodyParagraph(block) !== null && ok;\n if (ok) {\n // Scoped assignment is id-table-identical to the full walk here: a\n // block-scoped intent creates tracked nodes only inside these\n // paragraphs, and everything outside already carries an id. See\n // StableIds.assignFromSubtrees.\n ids.assignFromSubtrees(scope.blocks);\n return;\n }\n } else if (scope.kind === \"split\") {\n if (doc.reparseDirectBodyParagraphSplit(scope.before, scope.after)) {\n ids.assignFromSubtrees([scope.before, scope.after]);\n return;\n }\n }\n doc.refresh();\n ids.assignFromRoots(doc.editableRoots());\n}\n\nfunction applyIntentInner(\n doc: DocxDocument,\n ids: StableIds,\n intent: Intent,\n out: { scope: Scope },\n): boolean {\n const runOf = makeRunLookup(doc, ids);\n // Headless apply does not yet support suggesting mode (needs provenance\n // threaded through the intent); the session forbids it upstream.\n const ctx = { suggesting: false, revMeta: () => { throw new Error(\"suggesting mode unsupported headlessly\"); } };\n\n switch (intent.kind) {\n case \"insertText\": {\n const caret = resolveCaret(ids, runOf, intent.at);\n if (!caret) return false;\n if (intent.suggest) {\n // Tracked change: record the insertion as a w:ins with carried\n // author/date; the revision w:id is scan-based (deterministic).\n const s = intent.suggest;\n const suggestCtx = { suggesting: true, revMeta: () => ({ author: s.author, date: s.date, nextId: () => doc.nextRevisionId() }) };\n // Verify the scope BEFORE mutating: the insertion may split the\n // addressed run into fresh pieces, after which the original run\n // element is no longer in the tree and containment can't be checked.\n const scope = blockScope(ids, intent.at.blockId, intent.at.runId);\n applyInsertText(doc, caret, intent.text, suggestCtx);\n // The insertion (w:ins wrapper, split run pieces) stays inside the\n // addressed paragraph, so reconcile at the PARAGRAPH's cost: scoped\n // reparse + assignFromSubtrees produces the id table the old full\n // refresh()+assignFromRoots() did (the documented equivalence in\n // StableIds.assignFromSubtrees — new nodes only inside the block).\n // This ran a full-document refresh per remote suggest KEYSTROKE, and\n // the doc-wide scope it reported forced a whole-document relayout per\n // keystroke on every peer — on a 500-page document, seconds of inert\n // editor per keystroke (the livelock's fuel). Unverifiable block ids\n // fall back to doc scope; resyncScope's own fallback is the old full\n // refresh, so anything the scoped reparse cannot handle is unchanged.\n if (doc.stableIds) resyncScope(doc, ids, scope);\n out.scope = scope;\n return true;\n }\n applyInsertText(doc, caret, intent.text, ctx);\n // Plain typing splices characters into one w:t — the whole blast radius\n // is the addressed paragraph (perf B9/B10: this is THE hot path).\n out.scope = blockScope(ids, intent.at.blockId, intent.at.runId);\n return true;\n }\n case \"deleteText\": {\n if (intent.end <= intent.start) return false;\n const caret = resolveCaret(ids, runOf, { blockId: intent.blockId, runId: intent.runId, offset: intent.start });\n if (!caret) return false;\n // Wire offsets are cumulative within the run; the resolved caret is\n // local to its w:t. A range that would cross into the NEXT w:t is not\n // emitted by any sender (deletes are per-grapheme within one w:t) —\n // reject it as a clean no-op rather than corrupting a neighbor.\n const localEnd = caret.offset + (intent.end - intent.start);\n if (localEnd > caret.t.text.length) return false;\n // Engine-independent splice via the shared core mutation (offsets were\n // resolved client-side, so no Intl.Segmenter dependency here).\n applyDeleteRange(caret, caret.offset, localEnd);\n // A delete only shortens one w:t's text: no tracked node is removed, so\n // no id retires and the paragraph is the whole blast radius.\n out.scope = blockScope(ids, intent.blockId, intent.runId);\n return true;\n }\n case \"splitParagraph\": {\n const caret = resolveCaret(ids, runOf, intent.at);\n if (!caret) return false;\n const res = applySplitParagraph(doc, caret, ctx);\n if (!res) return false;\n ids.assign(res.after, intent.newBlockId);\n const newRun = res.after.children.find((c) => localRun(c));\n if (newRun) ids.assign(newRun, intent.newRunId);\n // Both halves of the split carry their ids explicitly (above), so the\n // scoped reparse below has only the two paragraphs' MODEL to rebuild.\n out.scope = { kind: \"split\", before: res.before, after: res.after };\n return true;\n }\n case \"formatRun\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry) return false;\n // Whole-run format: a segment with t=null covers the entire run, so\n // applyRunFormat takes the in-place (no-split) path — run id preserved.\n const seg: SelectionSegment = { run: entry.run, t: null, start: 0, end: 0, props: entry.run.props };\n applyRunFormat(doc, [seg], intent.patch as never);\n return true;\n }\n case \"formatParagraph\": {\n const blockEl = ids.elOf(intent.blockId);\n if (!blockEl) return false;\n // setParagraphAlignment/Style resolve the paragraph by walking UP from a\n // target, so pass a descendant w:t (or the block itself as a fallback).\n const target = firstTextDescendant(blockEl) ?? blockEl;\n let changed = false;\n if (intent.align) changed = setParagraphAlignment(doc, [target], intent.align) || changed;\n if (intent.styleId !== undefined) changed = setParagraphStyle(doc, [target], intent.styleId) || changed;\n return changed;\n }\n case \"setListType\": {\n const blockEl = ids.elOf(intent.blockId);\n if (!blockEl) return false;\n const target = firstTextDescendant(blockEl) ?? blockEl;\n return setListType(doc, [target], intent.listKind);\n }\n case \"formatRange\": {\n if (intent.end <= intent.start) return false;\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry || !entry.firstT) return false;\n const len = runWireLength(runEl);\n if (intent.start < 0 || intent.end > len) return false;\n // Wire offsets are cumulative within the run; the segment applyRunFormat\n // takes is local to one w:t. A range crossing a w:t boundary has no\n // single-segment form — reject it as a clean no-op (senders emit\n // per-segment ranges, each within one w:t).\n const hit = resolveRunOffset(runEl, intent.start);\n if (!hit) return false;\n const localEnd = hit.offset + (intent.end - intent.start);\n if (localEnd > hit.t.text.length) return false;\n // Sub-range format: splits the run into before/middle/after (all new).\n const seg: SelectionSegment = { run: entry.run, t: hit.t, start: hit.offset, end: localEnd, props: entry.run.props };\n const formatted = applyRunFormat(doc, [seg], intent.patch as never);\n if (formatted.length === 0) return false;\n // Locate the pieces via the returned middle w:t (robust to whatever\n // parent applyRunFormat spliced into): middle run = parent of middleT;\n // before/after are its previous/next run siblings.\n const middleT = formatted[0].t;\n const middleRun = doc.findParentOf(middleT);\n if (!middleRun) return true; // formatted but not id-splittable; ids stay as-is\n const container = doc.findParentOf(middleRun);\n if (!container) return true;\n const mIdx = container.children.indexOf(middleRun);\n // reassign (not assign): applyRunFormat's internal refresh() may have\n // auto-assigned parse-order ids to the pieces; override with the carried\n // ones so every replica addresses them identically.\n ids.reassign(middleRun, intent.middleId);\n if (intent.start > 0 && intent.beforeId !== undefined) {\n const before = container.children[mIdx - 1];\n if (before && localRun(before)) ids.reassign(before, intent.beforeId);\n }\n if (intent.end < len && intent.afterId !== undefined) {\n const after = container.children[mIdx + 1];\n if (after && localRun(after)) ids.reassign(after, intent.afterId);\n }\n return true;\n }\n case \"tableOp\": {\n const paraEl = ids.elOf(intent.cellParagraphId);\n if (!paraEl) return false;\n const target = firstTextDescendant(paraEl) ?? paraEl;\n const isInsert = intent.op === \"rowAbove\" || intent.op === \"rowBelow\" || intent.op === \"colLeft\" || intent.op === \"colRight\";\n // For insert ops, snapshot the tracked-node set so we can find the new\n // nodes afterward and give them the carried ids.\n const before = isInsert ? trackedSet(ids, doc) : null;\n const ok = applyTableOp(doc, target, intent.op as never);\n if (!ok) return false;\n if (isInsert && before && intent.nodeIds) {\n // Assign carried ids to the newly created tracked nodes in doc order.\n const fresh: XmlElement[] = [];\n walkTracked(doc, (el) => { if (!before.has(el)) fresh.push(el); });\n for (let k = 0; k < fresh.length && k < intent.nodeIds.length; k++) {\n ids.reassign(fresh[k], intent.nodeIds[k]);\n }\n } else {\n // Delete/shading ops: retire stale ids for removed content.\n ids.prune(doc.editableRoots());\n }\n return true;\n }\n case \"mergeParagraph\": {\n const pEl = ids.elOf(intent.blockId);\n if (!pEl) return false;\n const ok = mergeParagraphBackward(doc, pEl);\n if (ok) ids.prune(doc.editableRoots()); // retire the merged paragraph's id\n return ok;\n }\n case \"commentRun\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry) return false;\n // Whole-run comment (t=null covers the run, so no run split). Carried\n // provenance makes the w14:paraId and w:date identical on every replica.\n const seg: SelectionSegment = { run: entry.run, t: null, start: 0, end: 0, props: entry.run.props };\n const prov = recordedProvenance({ dates: [intent.date], paraIds: [intent.paraId] });\n const ok = addComment(doc, [seg], intent.text, intent.author, intent.initials, prov);\n // addComment created a commentReference run + comment paragraph; keep the\n // id table filled for them (parse-order, consistent across live replicas).\n if (ok && doc.stableIds) doc.stableIds.assignFromRoots(doc.editableRoots());\n return ok;\n }\n case \"pasteBlocks\": {\n const afterEl = ids.elOf(intent.afterBlockId);\n if (!afterEl) return false;\n const parent = doc.findParentOf(afterEl);\n if (!parent) return false;\n // Parse + VALIDATE the client-supplied OOXML before it enters the tree\n // (doc 11 gate 2). Reject on any violation.\n let blocks: XmlElement[];\n try {\n const root = parseXml(`<w:root xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\">${intent.blocksXml}</w:root>`);\n blocks = root.children.filter((c) => localName(c.name) === \"p\" || localName(c.name) === \"tbl\");\n } catch {\n return false;\n }\n if (blocks.length === 0) return false;\n const check = validatePastedOoxml(blocks);\n if (!check.ok) return false;\n // Splice after the target paragraph.\n const at = parent.children.indexOf(afterEl);\n parent.children.splice(at + 1, 0, ...blocks);\n doc.refresh();\n // Assign carried ids to the new tracked nodes in document order,\n // overriding any parse-order ids refresh() gave them.\n const newTracked: XmlElement[] = [];\n const walk = (el: XmlElement): void => {\n const ln = localName(el.name);\n if (ln === \"p\" || ln === \"tbl\" || ln === \"r\") newTracked.push(el);\n el.children.forEach(walk);\n };\n blocks.forEach(walk);\n for (let k = 0; k < newTracked.length && k < intent.nodeIds.length; k++) {\n ids.reassign(newTracked[k], intent.nodeIds[k]);\n }\n return true;\n }\n case \"suggestRevision\": {\n // Resolve ranges to text elements via stable ids (offsets were\n // client-resolved — rule b); drop unresolvable/empty ranges as clean\n // no-ops. Marks resolve block elements directly.\n const meta = {\n author: intent.suggest.author,\n date: intent.suggest.date,\n nextId: () => doc.nextRevisionId(),\n };\n const resolved: { t: XmlElement; start: number; end: number; block: XmlElement | null }[] = [];\n for (const r of intent.ranges ?? []) {\n const caret = resolveCaret(ids, runOf, { blockId: r.blockId, runId: r.runId, offset: r.start });\n if (!caret) continue;\n const localEnd = caret.offset + (r.end - r.start);\n if (localEnd > caret.t.text.length) continue;\n // Verify the range's own blockId names the paragraph really holding\n // the run — the same rule blockScope enforces everywhere else. A\n // verified block lets the reconcile below stay scoped; any failure\n // widens to the full-document path.\n const sc = blockScope(ids, r.blockId, r.runId);\n resolved.push({ t: caret.t, start: caret.offset, end: localEnd, block: sc.kind === \"block\" ? sc.blocks[0] : null });\n }\n const markEls: { el: XmlElement; glyph: \"ins\" | \"del\"; block: XmlElement | null }[] = [];\n for (const m of intent.marks ?? []) {\n const el = ids.elOf(m.blockId);\n if (el) markEls.push({ el, glyph: m.glyph, block: localName(el.name) === \"p\" ? el : null });\n }\n if (resolved.length === 0 && markEls.length === 0) return false;\n if (resolved.length) deleteSuggestedRange(doc, resolved, meta);\n for (const m of markEls) markParagraphGlyph(m.el, m.glyph, meta);\n // Strikes wrap runs in w:del and split partially-struck runs — new\n // nodes, but all INSIDE the addressed paragraphs. When every touched\n // paragraph verified, reconcile at those paragraphs' cost (scoped\n // reparse + assignFromSubtrees — id-table-identical to the full walk,\n // see StableIds.assignFromSubtrees) and report them as the dirty\n // scope. The old unconditional refresh()+assignFromRoots() here ran a\n // full-document rebuild per remote suggest-DELETE keystroke and forced\n // a whole-document relayout on every peer; anything unverifiable keeps\n // exactly that behavior via the doc-scope fallback.\n const parts = [...resolved.map((x) => x.block), ...markEls.map((x) => x.block)];\n if (parts.every((b): b is XmlElement => b !== null)) {\n const blocks: XmlElement[] = [];\n for (const b of parts) if (!blocks.includes(b)) blocks.push(b);\n const scope: Scope = { kind: \"block\", blocks };\n resyncScope(doc, ids, scope);\n out.scope = scope;\n } else {\n doc.refresh();\n ids.assignFromRoots(doc.editableRoots());\n }\n return true;\n }\n case \"insertImage\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry || !entry.firstT) return false;\n // Doc 16 §2: apply performs the REGISTRATION half only — part + rel +\n // content-type + the drawing run with client-measured extents (layout\n // reserves exact space from XML alone, doc 05). Bytes arrive out of\n // band and install via installMedia when fetched; until then the part\n // is pending and renders as a placeholder. The sha/iv metadata is\n // recorded on the doc for verification + E2EE re-supply (doc 16 §5.3).\n const relId = doc.registerPendingImage(intent.blobSha, intent.ext, { iv: intent.iv });\n const before = trackedSet(ids, doc);\n const newRun = insertImageAt(doc, entry.firstT, relId, intent.widthPx, intent.heightPx);\n if (!newRun) return false;\n doc.refresh();\n const fresh: XmlElement[] = [];\n walkTracked(doc, (el) => { if (!before.has(el)) fresh.push(el); });\n for (let k = 0; k < fresh.length && k < intent.nodeIds.length; k++) {\n ids.reassign(fresh[k], intent.nodeIds[k]);\n }\n return true;\n }\n case \"insertBlankPage\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry || !entry.firstT) return false;\n const before = trackedSet(ids, doc);\n const ok = insertBlankPageAt(doc, entry.firstT, entry.firstT.text.length);\n if (ok) assignFreshTracked(ids, doc, before, intent.nodeIds);\n return ok;\n }\n case \"insertSectionBreak\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry || !entry.firstT) return false;\n const before = trackedSet(ids, doc);\n const ok = insertSectionBreak(doc, entry.firstT, intent.breakType);\n if (ok) assignFreshTracked(ids, doc, before, intent.nodeIds);\n return ok;\n }\n case \"insertCrossRef\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry || !entry.firstT) return false;\n const before = trackedSet(ids, doc);\n const ok = insertCrossReference(doc, entry.firstT, entry.firstT.text.length, intent.bookmark, intent.refKind);\n if (ok) assignFreshTracked(ids, doc, before, intent.nodeIds);\n return ok;\n }\n case \"insertCoverPage\": {\n const before = trackedSet(ids, doc);\n const ok = insertCoverPage(doc, intent.content as never);\n if (ok) assignFreshTracked(ids, doc, before, intent.nodeIds);\n return ok;\n }\n case \"setPageLayout\":\n // Document-level page setup (all sections). No new nodes to id.\n return setPageLayout(doc, intent.patch as never);\n case \"setListLevel\": {\n const blockEl = ids.elOf(intent.blockId);\n if (!blockEl) return false;\n const target = firstTextDescendant(blockEl) ?? blockEl;\n return setListLevel(doc, [target], intent.delta);\n }\n case \"insertWordArt\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry || !entry.firstT) return false;\n const before = trackedSet(ids, doc);\n const drawing = insertWordArtAt(doc, entry.firstT, intent.text, intent.preset);\n if (!drawing) return false;\n assignFreshTracked(ids, doc, before, intent.nodeIds);\n return true;\n }\n case \"insertChart\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry || !entry.firstT) return false;\n const before = trackedSet(ids, doc);\n const drawing = insertChartAt(doc, entry.firstT, intent.chart as never);\n if (!drawing) return false;\n assignFreshTracked(ids, doc, before, intent.nodeIds);\n return true;\n }\n case \"insertSmartArt\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry || !entry.firstT) return false;\n const before = trackedSet(ids, doc);\n const drawing = insertSmartArtAt(doc, entry.firstT, intent.smartArt as never);\n if (!drawing) return false;\n assignFreshTracked(ids, doc, before, intent.nodeIds);\n return true;\n }\n case \"setLineNumbering\":\n // Document-level (all sections). No new nodes to id.\n return setLineNumbering(doc, intent.patch as never);\n case \"insertDateTimeField\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry || !entry.firstT) return false;\n const before = trackedSet(ids, doc);\n const ok = insertDateTimeField(doc, entry.firstT, entry.firstT.text.length, intent.dtKind, intent.picture);\n if (ok) assignFreshTracked(ids, doc, before, intent.nodeIds);\n return ok;\n }\n case \"insertField\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry || !entry.firstT) return false;\n const before = trackedSet(ids, doc);\n const ok = insertField(doc, entry.firstT, entry.firstT.text.length, intent.instruction, intent.cachedResult ?? \"\");\n if (ok) assignFreshTracked(ids, doc, before, intent.nodeIds);\n return ok;\n }\n case \"setDrawingRotation\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const drawing = firstDrawingIn(runEl);\n if (!drawing) return false;\n return setDrawingRotation(doc, drawing, intent.degrees);\n }\n case \"setDrawingFill\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const drawing = firstDrawingIn(runEl);\n if (!drawing) return false;\n return setDrawingFill(doc, drawing, intent.color);\n }\n case \"setChartData\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const drawing = firstDrawingIn(runEl);\n if (!drawing) return false;\n return setChartData(doc, drawing, intent.chart as never);\n }\n case \"setSmartArtNodeText\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const drawing = firstDrawingIn(runEl);\n if (!drawing) return false;\n return setSmartArtNodeText(doc, drawing, intent.index, intent.text);\n }\n case \"setDrawingWordArtText\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const drawing = firstDrawingIn(runEl);\n if (!drawing) return false;\n return setDrawingWordArtText(doc, drawing, intent.text);\n }\n case \"setDrawingLineStyle\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const drawing = firstDrawingIn(runEl);\n if (!drawing) return false;\n return setDrawingLineStyle(doc, drawing, intent.color, intent.widthPx, intent.dash);\n }\n case \"setImageAltText\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const drawing = firstDrawingIn(runEl);\n if (!drawing) return false;\n return setImageAltText(doc, drawing, intent.alt);\n }\n case \"removeLink\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry || !entry.firstT) return false;\n return removeLink(doc, entry.firstT);\n }\n case \"setImageWrap\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const drawing = firstDrawingIn(runEl);\n if (!drawing) return false;\n return setImageWrap(doc, drawing, intent.mode);\n }\n case \"setDrawingOrder\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const drawing = firstDrawingIn(runEl);\n if (!drawing) return false;\n return setDrawingOrder(doc, drawing, intent.order);\n }\n case \"setSmartArtData\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const drawing = firstDrawingIn(runEl);\n if (!drawing) return false;\n return setSmartArtData(doc, drawing, intent.smartArt as never);\n }\n case \"setSmartArtFill\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const drawing = firstDrawingIn(runEl);\n if (!drawing) return false;\n return setSmartArtFill(doc, drawing, intent.color, intent.nodeIndex);\n }\n case \"setSmartArtTextFormat\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const drawing = firstDrawingIn(runEl);\n if (!drawing) return false;\n return setSmartArtTextFormat(doc, drawing, intent.format as never, intent.nodeIndex);\n }\n case \"setFloatingPagePosition\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const drawing = firstDrawingIn(runEl);\n if (!drawing) return false;\n return setFloatingPagePosition(doc, drawing, intent.xPx, intent.yPx);\n }\n case \"resizeDrawing\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const drawing = firstDrawingIn(runEl);\n if (!drawing) return false;\n return resizeDrawing(doc, drawing, intent.widthPx, intent.heightPx);\n }\n case \"resizeTableColumn\": {\n const tbl = tableOfParagraph(doc, ids, intent.cellParagraphId);\n if (!tbl) return false;\n return resizeTableColumn(doc, tbl, intent.boundary, intent.deltaPx, intent.renderedWidths);\n }\n case \"resizeTableRow\": {\n const tbl = tableOfParagraph(doc, ids, intent.cellParagraphId);\n if (!tbl) return false;\n return resizeTableRow(doc, tbl, intent.rowIdx, intent.heightPx);\n }\n case \"moveTable\": {\n const tbl = tableOfParagraph(doc, ids, intent.cellParagraphId);\n if (!tbl) return false;\n return moveTableTo(doc, tbl, intent.xPx, intent.yPx, intent.preservePageStart, intent.pageDelta);\n }\n case \"removeDrawing\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const drawing = firstDrawingIn(runEl);\n if (!drawing) return false;\n const ok = removeDrawingRun(doc, drawing);\n if (ok) ids.prune(doc.editableRoots()); // retire the removed run's ids\n return ok;\n }\n case \"setMathLinear\": {\n const blockEl = ids.elOf(intent.blockId);\n if (!blockEl) return false;\n const math = firstMathIn(blockEl);\n if (!math) return false;\n return setMathLinear(doc, math, intent.mathText);\n }\n case \"deleteMath\": {\n const blockEl = ids.elOf(intent.blockId);\n if (!blockEl) return false;\n const math = firstMathIn(blockEl);\n if (!math) return false;\n return deleteMath(doc, math);\n }\n case \"moveMath\": {\n const blockEl = ids.elOf(intent.blockId);\n if (!blockEl) return false;\n const math = firstMathIn(blockEl);\n if (!math) return false;\n const dest = resolveCaret(ids, runOf, intent.at);\n if (!dest) return false;\n const before = trackedSet(ids, doc);\n if (!moveMath(doc, math, dest.t, dest.offset)) return false;\n // A mid-text drop splits the destination run; the tail takes the\n // carried id so every replica addresses it identically. (reassign, not\n // assign: moveMath's internal refresh() may already have auto-numbered\n // it in parse order.)\n const fresh: XmlElement[] = [];\n walkTracked(doc, (el) => { if (!before.has(el)) fresh.push(el); });\n for (let k = 0; k < fresh.length && k < intent.nodeIds.length; k++) ids.reassign(fresh[k], intent.nodeIds[k]);\n return true;\n }\n case \"ensureHeaderFooter\": {\n // Already there: a clean no-op, so two participants opening the header\n // at the same moment can't create two parts.\n if (doc.hasHfPart(intent.hfKind)) return false;\n const before = trackedSet(ids, doc);\n doc.ensureHfPart(intent.hfKind);\n const fresh: XmlElement[] = [];\n walkTracked(doc, (el) => { if (!before.has(el)) fresh.push(el); });\n for (let k = 0; k < fresh.length && k < intent.nodeIds.length; k++) ids.reassign(fresh[k], intent.nodeIds[k]);\n return true;\n }\n case \"deleteComment\":\n return deleteComment(doc, intent.commentId);\n case \"insertBookmarkRange\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry || !entry.firstT) return false;\n if (intent.end > runWireLength(runEl)) return false;\n const hit = resolveRunOffset(runEl, intent.start);\n if (!hit) return false;\n const localEnd = hit.offset + (intent.end - intent.start);\n if (localEnd > hit.t.text.length) return false;\n const seg: SelectionSegment = { run: entry.run, t: hit.t, start: hit.offset, end: localEnd, props: entry.run.props };\n return insertBookmarkAroundSelection(doc, [seg], intent.name);\n }\n case \"toggleCheckbox\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry) return false;\n const cbEl = checkboxStateElement(entry.run, entry.firstT);\n if (!cbEl) return false;\n toggleCheckbox(doc, cbEl);\n return true;\n }\n case \"acceptRevision\": {\n const refs = collectRevisions(doc);\n if (intent.index >= refs.length) return false;\n return acceptRevision(doc, refs[intent.index]);\n }\n case \"rejectRevision\": {\n const refs = collectRevisions(doc);\n if (intent.index >= refs.length) return false;\n return rejectRevision(doc, refs[intent.index]);\n }\n case \"acceptAllRevisions\":\n return acceptAllRevisions(doc) > 0;\n case \"insertTable\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry || !entry.firstT) return false;\n const before = trackedSet(ids, doc);\n const ok = insertTableAfter(doc, entry.firstT, intent.rows, intent.cols);\n if (ok) assignFreshTracked(ids, doc, before, intent.nodeIds);\n return ok;\n }\n case \"setLink\": {\n if (!isSafeUrl(intent.url)) return false; // reject javascript:/data: etc.\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry || !entry.firstT) return false;\n const seg: SelectionSegment = { run: entry.run, t: entry.firstT, start: 0, end: entry.firstT.text.length, props: entry.run.props };\n const before = trackedSet(ids, doc);\n const ok = setLink(doc, [seg], intent.url);\n if (ok) assignFreshTracked(ids, doc, before, intent.nodeIds);\n return ok;\n }\n case \"insertFootnote\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry || !entry.firstT) return false;\n const before = trackedSet(ids, doc);\n const id = insertFootnote(doc, entry.firstT, entry.firstT.text.length, intent.text);\n if (id === null) return false;\n assignFreshTracked(ids, doc, before, intent.nodeIds);\n return true;\n }\n case \"setDropCap\": {\n const blockEl = ids.elOf(intent.blockId);\n if (!blockEl) return false;\n const target = firstTextDescendant(blockEl) ?? blockEl;\n const before = trackedSet(ids, doc);\n const ok = setDropCapAt(doc, target, intent.mode as never);\n if (ok) assignFreshTracked(ids, doc, before, intent.nodeIds);\n return ok;\n }\n case \"setDivider\": {\n const blockEl = ids.elOf(intent.blockId);\n if (!blockEl) return false;\n const target = firstTextDescendant(blockEl) ?? blockEl;\n return setParagraphDivider(doc, [target], intent.divider as never);\n }\n case \"insertBookmark\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry || !entry.firstT) return false;\n return insertBookmarkAt(doc, entry.firstT, entry.firstT.text.length, intent.name);\n }\n case \"adjustIndent\": {\n const blockEl = ids.elOf(intent.blockId);\n if (!blockEl) return false;\n const target = firstTextDescendant(blockEl) ?? blockEl;\n return adjustIndent(doc, [target], intent.direction);\n }\n case \"setSpacing\": {\n const blockEl = ids.elOf(intent.blockId);\n if (!blockEl) return false;\n const target = firstTextDescendant(blockEl) ?? blockEl;\n return setParagraphSpacing(doc, [target], intent.patch as never);\n }\n case \"insertPageField\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry || !entry.firstT) return false;\n const before = trackedSet(ids, doc);\n const ok = insertPageField(doc, entry.firstT, entry.firstT.text.length, intent.fieldKind);\n if (!ok) return false;\n assignFreshTracked(ids, doc, before, intent.nodeIds);\n return true;\n }\n case \"replyComment\": {\n const prov = recordedProvenance({ dates: [intent.date], paraIds: intent.paraIds });\n const ok = replyToComment(doc, intent.parentId, intent.text, intent.author, intent.initials, prov);\n if (ok && doc.stableIds) doc.stableIds.assignFromRoots(doc.editableRoots());\n return ok;\n }\n case \"insertBreak\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry || !entry.firstT) return false;\n // At END of the run: inserts sibling break + tail runs, no text split.\n const before = trackedSet(ids, doc);\n const res = insertBreakAt(doc, entry.firstT, entry.firstT.text.length, intent.breakKind);\n if (!res) return false;\n const fresh: XmlElement[] = [];\n walkTracked(doc, (el) => { if (!before.has(el)) fresh.push(el); });\n for (let k = 0; k < fresh.length && k < intent.nodeIds.length; k++) {\n ids.reassign(fresh[k], intent.nodeIds[k]);\n }\n return true;\n }\n case \"insertMath\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry || !entry.firstT) return false;\n const before = trackedSet(ids, doc);\n const res = insertMathAt(doc, entry.firstT, entry.firstT.text.length, intent.mathText);\n if (!res) return false;\n assignFreshTracked(ids, doc, before, intent.nodeIds);\n return true;\n }\n case \"insertShape\": {\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return false;\n const entry = runOf(runEl);\n if (!entry || !entry.firstT) return false;\n const before = trackedSet(ids, doc);\n const res = insertShapeAt(doc, entry.firstT, intent.preset, intent.text ?? \"\");\n if (!res) return false;\n assignFreshTracked(ids, doc, before, intent.nodeIds);\n return true;\n }\n }\n}\n\n/** Assign carried ids to tracked nodes created since `before`, in doc order. */\nfunction assignFreshTracked(ids: StableIds, doc: DocxDocument, before: Set<XmlElement>, nodeIds: number[]): void {\n const fresh: XmlElement[] = [];\n walkTracked(doc, (el) => { if (!before.has(el)) fresh.push(el); });\n for (let k = 0; k < fresh.length && k < nodeIds.length; k++) ids.reassign(fresh[k], nodeIds[k]);\n}\n\nfunction base64ToBytes(b64: string): Uint8Array {\n const bin = typeof atob === \"function\" ? atob(b64) : Buffer.from(b64, \"base64\").toString(\"binary\");\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n}\n\nfunction localRun(el: XmlElement): boolean {\n const n = el.name;\n return n === \"w:r\" || n.endsWith(\":r\") || n === \"r\";\n}\n\n/** Walk all id-tracked elements (p / tbl / r) reachable from editable roots. */\nfunction walkTracked(doc: DocxDocument, visit: (el: XmlElement) => void): void {\n const walk = (el: XmlElement): void => {\n const ln = localName(el.name);\n if (ln === \"p\" || ln === \"tbl\" || ln === \"r\") visit(el);\n el.children.forEach(walk);\n };\n doc.editableRoots().forEach(walk);\n}\nfunction trackedSet(_ids: StableIds, doc: DocxDocument): Set<XmlElement> {\n const set = new Set<XmlElement>();\n walkTracked(doc, (el) => set.add(el));\n return set;\n}\n\nfunction firstTextDescendant(el: XmlElement): XmlElement | null {\n if (el.name === \"w:t\" || el.name.endsWith(\":t\")) return el;\n for (const c of el.children) {\n const found = firstTextDescendant(c);\n if (found) return found;\n }\n return null;\n}\n\n/** The table containing an id-addressed cell paragraph — the NEAREST tbl\n * ancestor, so a nested table's paragraph addresses the inner table (senders\n * pick a paragraph whose nearest tbl is the intended one). */\nfunction tableOfParagraph(doc: DocxDocument, ids: StableIds, cellParagraphId: number): XmlElement | null {\n const paraEl = ids.elOf(cellParagraphId);\n if (!paraEl) return null;\n for (let cur: XmlElement | null = paraEl; cur; cur = doc.findParentOf(cur) ?? null) {\n if (localName(cur.name) === \"tbl\") return cur;\n }\n return null;\n}\n\n/** The w:drawing element inside a run's subtree (drawing-edit intents address\n * a drawing via the run that carries it — drawings aren't tracked/id'd). */\nfunction firstDrawingIn(el: XmlElement): XmlElement | null {\n if (localName(el.name) === \"drawing\") return el;\n for (const c of el.children) {\n const found = firstDrawingIn(c);\n if (found) return found;\n }\n return null;\n}\n\n/** The m:oMath element inside a run's subtree (math-edit intents address a math\n * object via the run that carries it). */\nfunction firstMathIn(el: XmlElement): XmlElement | null {\n if (localName(el.name) === \"oMath\") return el;\n for (const c of el.children) {\n const found = firstMathIn(c);\n if (found) return found;\n }\n return null;\n}\n\ninterface RunEntry {\n run: Run;\n firstT: XmlElement | undefined;\n}\n\n/** Look up a run's source XML element in the model (needed for the checkbox\n * guard in applyInsertText and to locate the run's first w:t). */\ntype RunLookup = (runEl: XmlElement, blockIdHint?: number) => RunEntry | undefined;\n\nfunction runEntry(run: Run): RunEntry {\n return { run, firstT: run.content.find((c) => c.kind === \"text\")?.srcT };\n}\n\n/**\n * A run lookup that resolves ONE run instead of indexing the whole model.\n *\n * The old form built a Map over every run in the document on every apply — an\n * O(document) walk to answer a single-character insert (perf B9). Nearly every\n * intent addresses a run whose paragraph the intent also names, so the hinted\n * path indexes just that paragraph. Anything else (drawing/table/comment\n * intents that carry only a run id, or a hint that doesn't hold the run) falls\n * back to the full index, built at most once per apply — same answers as\n * before, off the same model, just not paid for by every keystroke.\n */\nfunction makeRunLookup(doc: DocxDocument, ids: StableIds): RunLookup {\n let full: Map<XmlElement, RunEntry> | null = null;\n const scoped = new Map<XmlElement, RunEntry>();\n return (runEl, blockIdHint) => {\n if (!full) {\n const cached = scoped.get(runEl);\n if (cached) return cached;\n if (blockIdHint !== undefined) {\n const blockEl = ids.elOf(blockIdHint);\n const para = blockEl ? doc.paragraphBySource(blockEl) : null;\n if (para) {\n for (const item of para.children) {\n for (const run of item.type === \"run\" ? [item] : item.runs) {\n if (run.src) scoped.set(run.src, runEntry(run));\n }\n }\n const hit = scoped.get(runEl);\n if (hit) return hit;\n }\n }\n full = buildRunMap(doc);\n }\n return full.get(runEl);\n };\n}\n\n/** True when `needle` is `el` or sits somewhere under it. */\nfunction contains(el: XmlElement, needle: XmlElement): boolean {\n if (el === needle) return true;\n for (const c of el.children) if (contains(c, needle)) return true;\n return false;\n}\n\n/**\n * Block scope for a text-level intent: the paragraph it addressed, but only\n * once that paragraph is CONFIRMED to hold the addressed run. The two ids\n * arrive together on the wire from a sender that may be buggy or hostile, and\n * a scope naming the wrong paragraph would leave the real one's model stale —\n * a silent, permanent divergence. Anything unverifiable (the block id resolves\n * to a table, to nothing, or to a paragraph that doesn't hold the run) falls\n * back to document scope, which is always correct.\n */\nfunction blockScope(ids: StableIds, blockId: number, runId: number): Scope {\n const blockEl = ids.elOf(blockId);\n const runEl = ids.elOf(runId);\n if (!blockEl || !runEl || localName(blockEl.name) !== \"p\") return DOC_SCOPE;\n return contains(blockEl, runEl) ? { kind: \"block\", blocks: [blockEl] } : DOC_SCOPE;\n}\n\n/** Index every run in the model by its source element — body story AND the\n * header/footer parts, including the text-box stories hanging off either.\n *\n * The hf half is not an optimization: header paragraphs carry stable ids (they\n * ride in doc.editableRoots(), so the id table and the checkpoint sidecar have\n * always covered them) but they are parsed into doc.headers/doc.footers, NOT\n * into doc.sections. Walking sections alone left every run in a header\n * unresolvable, so resolveCaret returned null and EVERY text/format intent\n * aimed at a header was a clean reject on the server and on every peer — while\n * the originator, which applies locally before emitting, kept the edit. That is\n * the silent fork the header/footer toolbar group was gated off to avoid.\n *\n * The text-box half is the SAME bug one level deeper, and it is how pleading\n * paper draws its margin line numbers: the number column is a w:txbxContent\n * story in the header, not w:lnNumType. Those story blocks hang off the RUN\n * that carries the drawing (run.content anchor -> shape.blocks), which neither\n * the section walk nor the hf walk descends into. The editor lets a caret into\n * that story (parse marks it textboxStory, and its runs are id-tracked), so a\n * user could type in the gutter and watch every keystroke get rejected by the\n * server and every peer — \"I can't change the number column\". */\nfunction buildRunMap(doc: DocxDocument): Map<XmlElement, RunEntry> {\n const map = new Map<XmlElement, RunEntry>();\n const visitRun = (run: Run): void => {\n if (run.src) map.set(run.src, runEntry(run));\n // A text box's story is a little document of its own. Recursing through\n // visitBlocks also covers a text box nested inside another one's story.\n for (const c of run.content) {\n if (c.kind === \"anchor\" && c.shape.type === \"textbox\") visitBlocks(c.shape.blocks);\n }\n };\n const visitBlocks = (blocks: Block[]): void => {\n for (const b of blocks) {\n if (b.type === \"paragraph\") {\n for (const item of b.children) {\n if (item.type === \"run\") visitRun(item);\n else for (const r of item.runs) visitRun(r);\n }\n } else if (b.type === \"table\") {\n for (const row of b.rows) for (const cell of row.cells) visitBlocks(cell.blocks);\n }\n }\n };\n for (const section of doc.sections) visitBlocks(section.blocks);\n for (const hf of doc.headers.values()) visitBlocks(hf.blocks);\n for (const hf of doc.footers.values()) visitBlocks(hf.blocks);\n return map;\n}\n\n/** Resolve a wire position (offset in the run's WIRE space — cumulative with\n * separators counted, checkpoint B1 rev 2) to the concrete (w:t, local\n * offset) it falls in. Strict bounds: a position past the run's wire length\n * is unresolvable (clean rejection), unlike the caret-restore decodeCaret\n * which clamps. */\nfunction resolveCaret(ids: StableIds, runOf: RunLookup, pos: Position): EditCaret | null {\n const runEl = ids.elOf(pos.runId);\n if (!runEl) return null;\n const entry = runOf(runEl, pos.blockId);\n if (!entry || !entry.firstT) return null;\n if (pos.offset < 0 || pos.offset > runWireLength(runEl)) return null;\n const hit = resolveRunOffset(runEl, pos.offset);\n if (!hit) return null;\n return { t: hit.t, run: entry.run, offset: hit.offset };\n}\n","import { DocxDocument, StableIds } from \"@wordinweb/core\";\nimport { Intent, LogEntry, idempotencyKey } from \"./intents.js\";\nimport { transformIntent } from \"./transform.js\";\nimport { applyIntentScoped, resyncScope, unionScopes, type Scope } from \"./apply.js\";\n\n// transformIntent is used in replayPending below.\n\n/**\n * A client-side replica with optimistic apply and rollback-replay (plan doc\n * 03 Stage B). It applies local intents immediately against its live\n * document, and reconciles when the server's canonical broadcasts arrive:\n *\n * - a broadcast that is the canonical form of our own oldest pending intent\n * advances the confirmed baseline and drops that pending entry;\n * - a remote broadcast triggers restore-to-confirmed, apply the canonical\n * broadcasts, then replay our still-pending intents on top (transformed\n * through the same shared function the server used).\n *\n * The confirmed baseline is snapshotted with save()/load() — correct and\n * simple; save() is side-effect-free (checkpoint-purity invariant), so\n * snapshotting never perturbs live state. A production client swaps in the\n * lighter confirmed-buffer scheme from doc 03; the observable behavior is the\n * same and is what these tests pin.\n */\nexport class ClientReplica {\n doc: DocxDocument;\n ids: StableIds;\n /** Server seq the confirmed baseline reflects. */\n confirmedSeq = 0;\n private confirmedBytes: Uint8Array;\n private confirmedSidecar: ReturnType<StableIds[\"exportSidecar\"]>;\n private pending: Intent[] = [];\n /** Confirmed entries NOT yet folded into confirmedBytes: own-echoes\n * deferred mid-burst (the original role) AND — since the lazy-baseline\n * change — every fast-path confirmed entry, so the O(doc) save() stays\n * off the receive hot path entirely. restoreConfirmed replays the tail\n * onto the baseline; exportBundleState folds it for persistence; the\n * fast path folds eagerly once the tail reaches FOLD_TAIL_AT (bounding\n * conflict-replay cost while amortizing saves to ~1/FOLD_TAIL_AT ops). */\n private confirmedTail: LogEntry[] = [];\n /** Fold cadence for the lazy confirmed baseline (see confirmedTail). */\n private static FOLD_TAIL_AT = 100;\n /**\n * Bumped every time THIS CLASS mutates the live doc (optimistic apply,\n * remote apply, replay, restore) — and deliberately NOT on bookkeeping\n * that leaves the doc untouched (trackLocal, own-echo confirmation).\n *\n * This is the renderer's repaint gate. The react layer used to repaint on\n * EVERY onChange, but for the editor-driven typing path (trackLocal + the\n * own-echo fast path) the doc was already mutated AND painted by the\n * editor, so each keystroke queued a redundant whole-document relayout —\n * invisible on 5 pages, catastrophic past the background-layout threshold\n * (500 pages: the layout ran async with the container inert until it\n * finished, blocking further typing for seconds per keystroke).\n */\n docVersion = 0;\n\n /**\n * Union of the dirty scopes behind the docVersion bumps not yet consumed by\n * a repaint (see takeRenderScope). Every path that bumps docVersion also\n * records what it disturbed: applyAndResync its intent's ApplyScope,\n * restoreConfirmed doc scope (the doc OBJECT was replaced). The renderer\n * drains this when it repaints, so a coalesced repaint covering several\n * bumps sees the union — the batching rule lives in unionScopes.\n */\n private renderScope: Scope | null = null;\n\n /** Drain the dirty scope accumulated since the last take (null when no\n * doc-mutating change was recorded). The caller repaints exactly this. */\n takeRenderScope(): Scope | null {\n const s = this.renderScope;\n this.renderScope = null;\n return s;\n }\n\n constructor(bytes: Uint8Array, sidecar?: ReturnType<StableIds[\"exportSidecar\"]>) {\n this.doc = DocxDocument.load(bytes);\n this.ids = this.doc.enableStableIds();\n // A snapshot from a session whose history contains splits carries\n // non-sequential ids that parse-order assignment cannot reproduce\n // (round-2 F1) — when the welcome supplies the session's sidecar, install\n // it so every later intent's carried ids resolve to the same nodes here\n // as on the server. Without one (fresh doc, legacy tests) parse order is\n // exact by construction.\n if (sidecar) this.ids.importSidecar(this.doc.editableRoots(), sidecar);\n this.confirmedBytes = this.doc.save();\n this.confirmedSidecar = this.ids.exportSidecar(this.doc.editableRoots());\n }\n\n /**\n * Apply a locally produced intent optimistically and enqueue it as pending.\n * The intent's `base` must be the current confirmed seq.\n *\n * CONSTRAINT (one-in-flight): a client should hold at most one un-confirmed\n * pending intent at a time — submit the next only after the previous is\n * confirmed. This is the model production OT servers use (e.g. ShareDB).\n * Multiple concurrent same-client pending whose offsets assume each other\n * cannot be correctly rebased against an interleaved remote edit without\n * operation inverses, which the OT-lite transform deliberately does not\n * implement (plan doc 03 marks adjusted-sibling replay a scoped next step).\n * The transform below is correct for the single-in-flight case; the replay\n * still stores transformed forms so a burst that stays same-client-only\n * (no remote interleave) also composes.\n */\n submitLocal(intent: Intent): void {\n // Keep the parsed model consistent with the mutated XML: op-style\n // optimistic submits (toolbar commands routed through the canonical\n // apply) are rendered directly from this replica's live model.\n this.applyAndResync(intent);\n this.pending.push(intent);\n }\n\n /** Track an intent the caller ALREADY applied to this replica's live doc\n * (the editor-driven path: DocxEditor mutates `doc` through its own command\n * and emits the intent afterwards). Applying it again here would double it —\n * the demo typed \"Hello\" and got \"Hello\" + its reversal. Pending tracking\n * and reconciliation behave exactly as for submitLocal. */\n trackLocal(intent: Intent): void {\n this.pending.push(intent);\n }\n\n /** Number of un-confirmed local intents (for the one-in-flight discipline). */\n get pendingCount(): number {\n return this.pending.length;\n }\n\n /** Identity of the OLDEST un-confirmed local intent — the delivery-retry\n * probe (a stuck front pending means the op or its echo was lost). */\n firstPending(): { clientId: string; clientSeq: number } | null {\n const p = this.pending[0];\n return p ? { clientId: p.clientId, clientSeq: p.clientSeq } : null;\n }\n\n /** Copies of every pending intent in submit order (their reconciliation-\n * transformed forms) — the rate-limit re-drive resends the WHOLE queue:\n * chained bursts (each op addressing ids a previous op allocated) strand\n * everything behind one refused op, and dedup makes resends free. */\n pendingCopies(): Intent[] {\n return [...this.pending];\n }\n\n /** The current pending copy of an intent by idempotency key, or null if it\n * was confirmed or dropped. This is the RECONCILIATION-TRANSFORMED form —\n * replayPending rewrites pending against every remote entry ingested since\n * submission — and therefore the only correct body to re-seal on a\n * stale-base retry: resealing the raw original resubmits untransformed\n * coordinates that the server then applies verbatim, while the optimistic\n * doc holds the transformed form — a silently divergent baseline once the\n * own-echo fast path snapshots it (the checkpoint-boundary divergence). */\n pendingIntent(clientId: string, clientSeq: number): Intent | null {\n return this.pending.find((p) => p.clientId === clientId && p.clientSeq === clientSeq) ?? null;\n }\n\n /**\n * The replica's durable state — exactly the fields the doc-12 §4 bundle\n * persists (the persistence layer is a thin observer over this). The bytes\n * and sidecar are the CONFIRMED baseline, never the live optimistic doc:\n * a bundle must be replayable (confirmed + pending re-submit), and\n * snapshotting optimistic state would bake unsequenced edits into what a\n * re-seed later presents as canonical (doc 03 re-capture rule).\n * `pending` is a copy — callers can't mutate reconciliation state.\n */\n exportBundleState(): {\n confirmedSeq: number;\n confirmedBytes: Uint8Array;\n confirmedSidecar: ReturnType<StableIds[\"exportSidecar\"]>;\n pending: Intent[];\n mediaMeta: [string, { sha: string; iv?: string; genesisId?: string }][];\n } {\n // Fold any deferred confirmed entries into the baseline so the bundle's\n // confirmedBytes is COMPLETE — they are already confirmed and are not in\n // `pending`, so without this a bundle exported mid-burst would silently\n // drop them (they'd be recoverable from neither field).\n let confirmedBytes = this.confirmedBytes;\n let confirmedSidecar = this.confirmedSidecar;\n if (this.confirmedTail.length) {\n if (this.pending.length === 0) {\n // Quiescent: the live doc IS baseline+tail exactly — save IT. This\n // also preserves out-of-band state no intent stream can rebuild\n // (installed media pixels live on the doc, not in the log).\n confirmedBytes = this.doc.save();\n confirmedSidecar = this.ids.exportSidecar(this.doc.editableRoots());\n } else {\n const base = DocxDocument.load(this.confirmedBytes);\n const baseIds = base.enableStableIds();\n baseIds.importSidecar(base.editableRoots(), this.confirmedSidecar);\n for (const e of this.confirmedTail) {\n if (e.kind !== \"applied\") continue;\n // Reconcile after each one, exactly as the live paths do: a tail\n // that splits a paragraph and then edits the new one needs the\n // split's nodes in THIS doc's model before the next intent resolves\n // them, or the entry silently fails to apply and the exported\n // baseline is missing a confirmed edit.\n const res = applyIntentScoped(base, baseIds, e.intent);\n if (res.applied) resyncScope(base, baseIds, res);\n }\n // Replay re-REGISTERS media (the intent half) but cannot restore\n // PIXELS — carry every installed part over from the live doc.\n this.carryInstalledMedia(this.doc, base);\n confirmedBytes = base.save();\n confirmedSidecar = baseIds.exportSidecar(base.editableRoots());\n }\n }\n return {\n confirmedSeq: this.confirmedSeq,\n confirmedBytes,\n confirmedSidecar,\n pending: [...this.pending],\n mediaMeta: [...this.doc.mediaMeta] as [string, { sha: string; iv?: string; genesisId?: string }][],\n };\n }\n\n async exportBundleStateAsync(): Promise<ReturnType<ClientReplica[\"exportBundleState\"]>> {\n let confirmedBytes = this.confirmedBytes;\n let confirmedSidecar = this.confirmedSidecar;\n if (this.confirmedTail.length) {\n // Macrotask hop before the heavy work: folding the confirmed tail\n // re-parses the confirmed docx and replays the tail (~50-150ms at 500\n // pages) synchronously. This method starts as a promise continuation\n // (the persister's serial write chain), which can drain at listener\n // boundaries inside an in-flight input dispatch — landing the cost in\n // a keystroke's handler on the persister's 1s cadence. In its own task\n // it runs between dispatches. Taken only on this branch so exports of\n // an unchanged document (blank-doc tests, idle sessions) keep their\n // exact timing.\n await new Promise((resolve) => setTimeout(resolve, 0));\n if (this.pending.length === 0) {\n confirmedBytes = await this.doc.saveAsync();\n confirmedSidecar = this.ids.exportSidecar(this.doc.editableRoots());\n } else {\n const base = DocxDocument.load(this.confirmedBytes);\n const baseIds = base.enableStableIds();\n baseIds.importSidecar(base.editableRoots(), this.confirmedSidecar);\n for (const e of this.confirmedTail) {\n if (e.kind !== \"applied\") continue;\n const res = applyIntentScoped(base, baseIds, e.intent);\n if (res.applied) resyncScope(base, baseIds, res);\n }\n this.carryInstalledMedia(this.doc, base);\n confirmedBytes = await base.saveAsync();\n confirmedSidecar = baseIds.exportSidecar(base.editableRoots());\n }\n }\n return {\n confirmedSeq: this.confirmedSeq,\n confirmedBytes,\n confirmedSidecar,\n pending: [...this.pending],\n mediaMeta: [...this.doc.mediaMeta] as [string, { sha: string; iv?: string; genesisId?: string }][],\n };\n }\n\n /**\n * Receive the server's canonical broadcast entries (in seq order) and\n * reconcile. The invariant is: live doc = confirmed baseline + remaining\n * pending. So every receive restores to the confirmed baseline, applies the\n * canonical batch (advancing the baseline and dropping matched pending),\n * then replays the still-pending intents transformed against the remote\n * intents in the batch (plan doc 03). Idempotent for already-seen seqs.\n */\n /** True after a receive() that reloaded the doc object (a true conflict\n * reconciliation) — the renderer must re-key/rebind. Stays false for the\n * in-place fast paths so the editor updates without a re-mount. */\n reloaded = false;\n\n receive(entries: LogEntry[]): void {\n this.reloaded = false;\n const fresh = entries.filter((e) => e.seq > this.confirmedSeq);\n if (fresh.length === 0) return;\n\n const remoteAhead = fresh\n .filter((e): e is Extract<LogEntry, { kind: \"applied\" }> => e.kind === \"applied\" && !isOurs(e, this.pending))\n .map((e) => e.intent);\n\n // A REJECTION of one of our own pending intents means our optimistic doc\n // contains a mutation the canonical history never will. Dropping the\n // pending entry alone (the old behavior) left that mutation in the doc\n // forever — the client diverged permanently from the server. Roll back:\n // restore the confirmed snapshot, advance through the fresh entries, and\n // replay the surviving pending intents.\n const rejectedOurs = fresh.some((e) => e.kind === \"rejected\" && isOurs(e, this.pending));\n if (rejectedOurs) {\n this.restoreConfirmed();\n this.reloaded = true;\n for (const e of fresh) this.advanceConfirmed(e, e.kind === \"applied\");\n this.snapshotConfirmed();\n this.replayPending(remoteAhead);\n this.resync(); // model consistent with the replayed XML for the renderer\n return;\n }\n\n // FAST PATH — no reload, doc object stays stable (production real-time\n // behavior: apply in place, repaint incrementally, no re-mount/flash):\n // (a) every fresh entry is our OWN echo: the optimistic doc already has\n // them — just advance confirmed and drop the matched pending;\n // (b) nothing is pending: apply the remote entries in place (no risk of\n // double-applying an optimistic edit).\n // Do these fresh own-echoes confirm EVERY pending op? A fast typing burst\n // puts several ops in flight; an echo batch confirms only the oldest, so\n // the optimistic doc still holds the un-confirmed tail. The fast path below\n // snapshots the optimistic doc as the confirmed baseline — correct ONLY\n // when the doc equals the new baseline (nothing pending, or these echoes\n // drain it). If a burst leaves pending un-confirmed, snapshotting here would\n // bake that tail into the baseline and replay it AGAIN on the next remote\n // batch — the duplication/divergence real typing hits (\"SABABAB\" on the\n // server vs \"SABABABAAA\" locally). Route that to the reload path, which\n // rebuilds the baseline from the true confirmed state.\n const freshKeys = new Set(\n fresh.map((e) => (e.kind === \"applied\" ? idempotencyKey(e.intent) : `${e.clientId}:${e.clientSeq}`)),\n );\n const drainsAllPending = this.pending.every((p) => freshKeys.has(idempotencyKey(p)));\n // FAST PATH — no reload, doc object stays stable (and out-of-band state like\n // installed media pixels, which live on the doc and can't be rebuilt from\n // intents, is preserved by snapshotting the live doc):\n // (b) nothing pending: apply the remote entries in place; or\n // (a) every fresh entry is our own echo AND drains all pending: the\n // optimistic doc already equals the new confirmed baseline.\n if (this.pending.length === 0 || (remoteAhead.length === 0 && drainsAllPending)) {\n const applyToDoc = remoteAhead.length > 0;\n for (const e of fresh) {\n // Own echoes are already applied optimistically; only apply entries\n // that aren't our own pending, to avoid double-apply.\n this.advanceConfirmed(e, /*applyToDoc*/ applyToDoc);\n // LAZY BASELINE (perf B9/B10): the entry is confirmed and lives in\n // the optimistic doc, but is NOT re-serialized into confirmedBytes\n // here — snapshotConfirmed() ran doc.save() on EVERY drain, an\n // O(document) serialize per receive batch that made per-edit cost\n // grow with document size (the 6× seeding curve) and starved the\n // UI under floods. The tail records it; restoreConfirmed replays\n // it on the (rare) conflict path, exportBundleState folds it for\n // persistence, and the periodic fold below bounds both.\n if (e.kind === \"applied\") this.confirmedTail.push(e);\n }\n // There is deliberately no full resync() here. It used to close this\n // loop — one O(document) model reparse per incoming batch, the stall a\n // flood of remote edits produced (B10). advanceConfirmed now reconciles\n // each entry against what THAT entry touched, so the full refresh is\n // paid only by entries that actually report document scope.\n //\n // Pending is empty on both fast-path shapes, so live == baseline+tail\n // exactly — folding here (an O(doc) save) is CORRECT whenever we\n // choose to pay it; paying it every FOLD_TAIL_AT entries amortizes\n // the save to ~1% of ops instead of every drain.\n if (this.confirmedTail.length >= ClientReplica.FOLD_TAIL_AT) this.snapshotConfirmed();\n return;\n }\n\n // OWN ECHOES ONLY, but a typing burst left later ops un-confirmed. The\n // optimistic doc already holds every pending op and stays correct as-is —\n // so DON'T reload it (a reload swaps the doc object mid-typing, breaking the\n // editor's live binding and dropping the caret). Advance only the CONFIRMED\n // BASELINE by the echoed ops, in place: apply them to a copy of the baseline\n // and re-snapshot, leaving the un-confirmed tail solely in the optimistic\n // doc. (Snapshotting the optimistic doc here — the old bug — baked that tail\n // into the baseline and replayed it again on the next remote → duplication.)\n if (remoteAhead.length === 0) {\n // DEFER these confirmed own-echoes: this.doc already holds them (applied\n // optimistically) and MUST NOT be reloaded mid-typing (a reload swaps the\n // doc object, breaking the editor binding + caret). Record them in\n // confirmedTail — restoreConfirmed replays them onto the baseline, and the\n // next snapshot (when the burst drains) folds them into confirmedBytes.\n // Snapshotting the optimistic doc here (the old bug) baked the STILL-\n // pending tail into the baseline and replayed it again on the next remote\n // batch → the duplication/divergence real typing hit.\n for (const e of fresh) {\n if (e.kind === \"applied\") this.confirmedTail.push(e);\n const key = e.kind === \"applied\" ? idempotencyKey(e.intent) : `${e.clientId}:${e.clientSeq}`;\n this.pending = this.pending.filter((p) => idempotencyKey(p) !== key);\n this.confirmedSeq = e.seq;\n }\n return; // this.doc untouched — no reload, no re-mount, caret kept\n }\n\n // SLOW PATH — a true conflict (pending + interleaved remote): reload the\n // confirmed baseline, advance through the fresh entries, then replay the\n // still-pending intents transformed against the remote ones.\n this.restoreConfirmed();\n this.reloaded = true;\n for (const e of fresh) this.advanceConfirmed(e, true);\n this.snapshotConfirmed();\n this.replayPending(remoteAhead);\n this.resync(); // model consistent with the replayed XML for the renderer\n }\n\n /**\n * Apply one intent to the live doc and reconcile ONLY what it touched\n * (perf B9/B10). Every applied intent used to cost a full model reparse plus\n * a full id walk somewhere on this path, so a keystroke arriving into a\n * 600-paragraph document cost 600 paragraphs of work — the seeding curve and\n * the stall under an incoming flood. Text-level intents now reparse their\n * own paragraph; anything structural or unverifiable still takes the full\n * refresh (see resyncScope). Returns false when the intent didn't apply.\n */\n private applyAndResync(intent: Intent): boolean {\n const res = applyIntentScoped(this.doc, this.ids, intent);\n if (!res.applied) return false;\n resyncScope(this.doc, this.ids, res);\n this.docVersion++;\n this.renderScope = unionScopes(this.renderScope, res);\n return true;\n }\n\n private advanceConfirmed(e: LogEntry, applyToDoc: boolean): void {\n if (e.kind === \"applied\" && applyToDoc) this.applyAndResync(e.intent);\n // Drop a matching pending intent (ours, now confirmed).\n const key = e.kind === \"applied\" ? idempotencyKey(e.intent) : `${e.clientId}:${e.clientSeq}`;\n this.pending = this.pending.filter((p) => idempotencyKey(p) !== key);\n this.confirmedSeq = e.seq;\n }\n\n private replayPending(remoteAhead: Intent[]): void {\n // Each remaining pending intent is transformed through the shared function\n // against the interleaved remote intents (the same transform the server\n // applied), then re-applied on the restored baseline. The TRANSFORMED form\n // is stored back so successive remote batches compose cumulatively; its\n // idempotency key is unchanged, so the eventual canonical echo still drops\n // it. A pending that no longer applies is dropped (clean failure).\n const stillPending = this.pending;\n this.pending = [];\n for (const p of stillPending) {\n const transformed = transformIntent(p, remoteAhead);\n if (this.applyAndResync(transformed)) this.pending.push(transformed);\n }\n }\n\n /** Copy installed media pixels (out-of-band state — bytes arrive via the\n * relay, not the intent log) from one doc into another whose matching\n * parts are still pending. */\n private carryInstalledMedia(from: DocxDocument, to: DocxDocument): void {\n for (const [part] of from.mediaMeta) {\n if (from.pendingMedia.has(part)) continue; // no pixels held locally\n const bytes = from.pkg.binary(part);\n if (bytes) to.installMedia(part, bytes);\n }\n }\n\n private restoreConfirmed(): void {\n // The doc OBJECT is replaced below — a repaint is needed even when the\n // tail replay applies nothing (applyAndResync bumps per applied entry).\n // Every retained layout/page binds parsed objects of the OLD doc, so no\n // narrow scope can describe this: the repaint must be document-wide.\n this.docVersion++;\n this.renderScope = { kind: \"doc\" };\n // The lazy baseline can be up to FOLD_TAIL_AT entries stale, so pixels\n // installed during the tail window exist only on the LIVE doc — capture\n // them before it is replaced, re-install after the replay.\n const pixelSource = this.doc;\n this.doc = DocxDocument.load(this.confirmedBytes);\n this.ids = this.doc.enableStableIds();\n // Reproduce the exact id table via the sidecar — parse-order alone would\n // renumber split-created carried ids and break address resolution\n // (plan round-2 F1).\n this.ids.importSidecar(this.doc.editableRoots(), this.confirmedSidecar);\n // Fold in own-echoes confirmed since the last snapshot (deferred to avoid\n // reloading during a typing burst). These are already confirmed, so they\n // belong to the baseline before pending is replayed on top. A replayed\n // split mutates the raw XML without reparsing doc.sections, so resync —\n // exactly like advanceConfirmed/replayPending — or the NEXT apply in this\n // same receive() builds its run map from the stale model and silently\n // drops a remote edit addressing the split's new run (permanent, silent\n // divergence under fast-typing+Enter bursts).\n for (const e of this.confirmedTail) {\n if (e.kind === \"applied\") this.applyAndResync(e.intent);\n }\n // Re-install pixels held by the replaced live doc (see pixelSource note).\n this.carryInstalledMedia(pixelSource, this.doc);\n }\n\n private snapshotConfirmed(): void {\n this.confirmedBytes = this.doc.save();\n this.confirmedSidecar = this.ids.exportSidecar(this.doc.editableRoots());\n // this.doc == the full confirmed baseline here (callers snapshot only when\n // nothing pending remains, or after a restore+advance), so the deferred\n // tail is now baked into confirmedBytes — clear it.\n this.confirmedTail = [];\n }\n\n private resync(): void {\n this.doc.refresh();\n this.ids.assignFromRoots(this.doc.editableRoots());\n }\n}\n\nfunction isOurs(e: LogEntry, pending: Intent[]): boolean {\n const key = e.kind === \"applied\" ? idempotencyKey(e.intent) : `${e.clientId}:${e.clientSeq}`;\n return pending.some((p) => idempotencyKey(p) === key);\n}\n\n/** Re-export the transform for clients that need to pre-transform a pending\n * intent's addresses for display before the canonical form returns. */\nexport { transformIntent };\n","import type { Intent } from \"./intents.js\";\nimport type { IdSidecar } from \"./session.js\";\nimport type { LineageHead } from \"./protocol.js\";\nimport type { CollabConnection } from \"./connection.js\";\n\n/**\n * The client-side document bundle (plan doc 12 §4) — under zero custody this\n * is THE durable copy of a document anywhere: the server deletes everything\n * at session end, so each participating browser persists its own bundle and\n * any holder can re-seed the same link later.\n *\n * Contents are the replica's CONFIRMED state plus the pending queue:\n * `confirmedBytes` is a complete docx at `confirmedSeq`; `confirmedSidecar`\n * rides with it always (round-4 F10 — bytes alone cannot reproduce the id\n * table across split-created ids); `pending` is idempotency-keyed and safe\n * to replay after a crash (the server dedups by (clientId, clientSeq)).\n */\nexport interface DocBundle {\n docId: string;\n /** Epoch of the session this state came from (doc 12 §5): on rejoin,\n * same ⇒ seamless resume; different ⇒ someone re-seeded while away. */\n genesisId: string;\n confirmedSeq: number;\n confirmedBytes: Uint8Array;\n confirmedSidecar: IdSidecar;\n /** Local intents not yet confirmed when the bundle was written — replayed\n * verbatim on resume; idempotent by (clientId, clientSeq). */\n pending: Intent[];\n /**\n * The clientSeq watermark at write time. Resume MUST restore this before\n * submitting anything new: a fresh connection restarting from 1 would\n * reuse already-sequenced (clientId, clientSeq) keys and the server would\n * dedup its NEW edits as re-sends — self-inflicted silent edit loss (the\n * same failure shape doc 12 §7's single-tab rule guards against).\n */\n clientSeq: number;\n savedAt: number;\n /**\n * The lineage chain (doc 15 §1): every epoch this copy has passed\n * through, newest last — `[..., {this bundle's genesisId, seq, hash}]`.\n * Rides with re-seeds so returning holders can prove ancestry and\n * fast-forward instead of forking. Capped; ancestry checks are O(chain).\n */\n lineage: LineageHead[];\n /**\n * Local intents recorded while the session was UNREACHABLE (doc 15 §2 /\n * doc 12 §5 \"offline mode is first-class\"): the user kept editing after\n * the socket died. On rejoin these are the offline tail the arrival\n * ladder reconciles — replayed as ordinary intents (same epoch), as\n * suggestions (diverged, small), or parked in a draft (diverged, large).\n * Distinct from `pending` (in-flight when a LIVE session was\n * interrupted): tail entries carry NO wire bookkeeping — clientId /\n * clientSeq / base are assigned at replay time by the ordinary submit\n * path, so the stored objects are the payload half of an Intent only.\n */\n offlineTail?: Intent[];\n /**\n * The genesisId of the epoch the offline tail was recorded AGAINST. This\n * is what lets a resume decide the arrival rung without guessing: same\n * epoch ⇒ the tail is morally a large pending queue and replays silently;\n * different (or absent, for a tail written by an older build) ⇒ true\n * divergence, offer suggest/draft — never silently bulldoze.\n */\n offlineTailEpoch?: string;\n /** Per-part media metadata (doc 16 §5.3): the sha (and E2EE iv/epoch)\n * of every media part this copy knows. The docx bytes carry the PIXELS;\n * this carries the ADDRESSES — without it a resumed holder couldn't\n * answer re-supply requests or verify fetches (metadata is in-memory on\n * the doc and would otherwise die with the session). */\n mediaMeta?: [string, { sha: string; iv?: string; genesisId?: string }][];\n}\n\n/**\n * THE KEY CONVENTION, in one place. Everything a browser stores rides the\n * bundle store under a key derived from the base docId:\n *\n * - `<docId>` — the live bundle\n * - `<docId>#version-<ts>[-<label>]` — a frozen restore point\n * - `<docId>#draft-<genesis>` — an offline copy parked on divergence\n * - `<docId>#superseded-<genesis>` — a banked copy after a fast-forward\n * - `local:…` — a document that never went live\n *\n * The builders and the parser below are the only code that knows these\n * shapes; UIs render from `parseBundleKey` instead of re-deriving them.\n */\nexport type StoredDocKind = \"live\" | \"version\" | \"draft\" | \"superseded\" | \"local\" | \"unknown\";\n\nexport interface ParsedBundleKey {\n /** The base document id (the part before any `#` suffix). */\n docId: string;\n kind: StoredDocKind;\n /** A version's human label, when the key carries one. */\n label?: string;\n /** A version's freeze time (ms), parsed from the key. */\n versionSavedAt?: number;\n}\n\nexport function versionKey(docId: string, savedAt: number, label?: string): string {\n return `${docId}#version-${savedAt}${label ? `-${label}` : \"\"}`;\n}\nexport function draftKey(docId: string, genesisId: string): string {\n return `${docId}#draft-${genesisId}`;\n}\nexport function supersededKey(docId: string, genesisId: string): string {\n return `${docId}#superseded-${genesisId}`;\n}\n\n/** Parse any store key. An unrecognised `#` suffix degrades to `unknown`\n * rather than throwing — a future key shape must not break today's listing. */\nexport function parseBundleKey(key: string): ParsedBundleKey {\n if (key.startsWith(\"local:\")) return { docId: key, kind: \"local\" };\n const hash = key.indexOf(\"#\");\n if (hash === -1) return { docId: key, kind: \"live\" };\n const docId = key.slice(0, hash);\n const suffix = key.slice(hash + 1);\n const version = /^version-(\\d+)(?:-(.*))?$/.exec(suffix);\n if (version) return { docId, kind: \"version\", versionSavedAt: Number(version[1]), label: version[2] };\n if (suffix.startsWith(\"draft-\")) return { docId, kind: \"draft\" };\n if (suffix.startsWith(\"superseded-\")) return { docId, kind: \"superseded\" };\n return { docId, kind: \"unknown\" };\n}\n\n/**\n * One stored entry, as METADATA ONLY — everything a list needs to render\n * (what it is, when, how big) without the document bytes. A 500-page doc is\n * megabytes; listing ten of them must not deserialise all ten, so `list()`\n * returns these and `get(key)` fetches bytes only when one is opened.\n */\nexport interface StoredDocSummary extends ParsedBundleKey {\n /** The full store key — what `get()`/`delete()` take. */\n key: string;\n /** When this entry was written (bundle.savedAt, ms). */\n savedAt: number;\n /** Size of the stored document bytes. */\n byteLength: number;\n}\n\n/**\n * Storage seam for bundles. The browser implementation is IndexedDB (the\n * only storage that fits multi-MB binary docs); tests and Node use the\n * in-memory implementation — the doc-12 test plan's \"in-memory IndexedDB\n * stub\", which keeps every bundle test deterministic and dependency-free.\n */\nexport interface BundleStore {\n get(docId: string): Promise<DocBundle | null>;\n put(bundle: DocBundle): Promise<void>;\n delete(docId: string): Promise<void>;\n /** Enumerate everything stored, metadata only (see StoredDocSummary). */\n list(): Promise<StoredDocSummary[]>;\n}\n\nexport class InMemoryBundleStore implements BundleStore {\n private bundles = new Map<string, DocBundle>();\n /** Write count — lets tests assert the throttle's coalescing precisely. */\n writes = 0;\n\n async get(docId: string): Promise<DocBundle | null> {\n return this.bundles.get(docId) ?? null;\n }\n async put(bundle: DocBundle): Promise<void> {\n this.writes++;\n this.bundles.set(bundle.docId, bundle);\n }\n async delete(docId: string): Promise<void> {\n this.bundles.delete(docId);\n }\n async list(): Promise<StoredDocSummary[]> {\n return [...this.bundles.values()].map((b) => ({\n ...parseBundleKey(b.docId),\n key: b.docId,\n savedAt: b.savedAt,\n byteLength: b.confirmedBytes.byteLength,\n }));\n }\n}\n\n/**\n * Persists a connection's state to a BundleStore on a THROTTLE — not a\n * debounce (round-4 F8): a debounce resets per event, so sustained typing\n * defers the write for the whole burst and an OS kill loses the burst; a\n * throttle bounds data loss to the throttle window (default 1s), which is\n * the actual durability guarantee doc 12 §4 states (\"RPO ≈ your last bundle\n * write ≈ the throttle window\").\n *\n * Wiring: call `notify()` from the connection's onChange (every local edit,\n * echo, and remote broadcast); call `flush()` from pagehide/visibilitychange\n * — best-effort (IndexedDB has no synchronous API; the throttle is the real\n * mechanism, the flush just narrows the tail).\n *\n * The scheduler (setTimeout/clearTimeout) and clock are injectable so tests\n * drive time deterministically.\n */\nexport class BundlePersister {\n private lastWrite = -Infinity;\n private trailing: unknown = null;\n private stopped = false;\n /** Writes are async (digest + store I/O) and must not overlap on one\n * store; a serial chain guarantees ordering and lets flush() await\n * every in-flight write (round-4 F8: the flush is only meaningful if it\n * durably lands the latest state before pagehide). */\n private chain: Promise<void> = Promise.resolve();\n\n constructor(\n private conn: CollabConnection,\n private store: BundleStore,\n private docId: string,\n private opts: {\n throttleMs?: number;\n now?: () => number;\n setTimer?: (fn: () => void, ms: number) => unknown;\n clearTimer?: (t: unknown) => void;\n /**\n * A persistence write FAILED. Wire this — in a zero-custody design the\n * browser's stored bundle IS the durable copy, so a swallowed quota or\n * blocked-storage error means the user's only durable copy silently\n * stops updating while the editor looks perfectly healthy. That is the\n * most dangerous silence in this codebase, which is why it is now a\n * callback instead of an empty catch.\n */\n onError?: (err: unknown) => void;\n /**\n * The current offline tail (doc 15 §2), read at write time. The\n * connection's exportBundle cannot know it (the tail is recorded by\n * the layer ABOVE the connection, across connection rebuilds), so the\n * owner of the tail injects a getter. Returning an empty tail (or\n * omitting the getter) ERASES any stored tail — which is exactly\n * right once a replay has drained it.\n */\n offlineTail?: () => { tail: Intent[]; epoch?: string } | null;\n } = {},\n ) {}\n\n private get throttleMs(): number {\n return this.opts.throttleMs ?? 1000;\n }\n private now(): number {\n return (this.opts.now ?? Date.now)();\n }\n\n /** State changed — write now if the window allows, else arm ONE trailing\n * write for the window's end (never more than one timer in flight; N\n * notifies inside a window coalesce into a single trailing write). */\n notify(): void {\n if (this.stopped) return;\n const elapsed = this.now() - this.lastWrite;\n if (elapsed >= this.throttleMs) {\n this.lastWrite = this.now(); // claim the slot SYNCHRONOUSLY so a\n this.enqueueWrite(); // second notify in the same tick arms\n return; // the trailing timer, not a 2nd write\n }\n if (this.trailing === null) {\n const set = this.opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));\n this.trailing = set(() => {\n this.trailing = null;\n this.lastWrite = this.now();\n this.enqueueWrite();\n }, this.throttleMs - elapsed);\n }\n }\n\n /** Immediate best-effort write (pagehide/visibilitychange/session-end).\n * Awaits ALL in-flight writes plus this one so the latest state is\n * durably landed before the tab goes away. */\n async flush(): Promise<void> {\n if (this.stopped) return;\n if (this.trailing !== null) {\n (this.opts.clearTimer ?? clearTimeout)(this.trailing as never);\n this.trailing = null;\n }\n this.lastWrite = this.now();\n this.enqueueWrite();\n await this.chain;\n }\n\n /** Append a write to the serial chain (never overlapping). */\n private enqueueWrite(): void {\n this.chain = this.chain\n .then(() => this.write())\n .catch((err: unknown) => {\n // The chain must not wedge — a failed write cannot block every later\n // one — but it must not be SILENT either (see onError above). The\n // next throttled write retries naturally; what the caller needs is to\n // know that \"saved\" is currently a lie.\n this.opts.onError?.(err);\n });\n }\n\n /** Detach (unmount): cancel the trailing timer; no further writes. */\n stop(): void {\n this.stopped = true;\n if (this.trailing !== null) {\n (this.opts.clearTimer ?? clearTimeout)(this.trailing as never);\n this.trailing = null;\n }\n }\n\n private async write(): Promise<void> {\n const bundle = await this.conn.exportBundleAsync(this.docId);\n if (!bundle) return; // not welcomed yet — nothing durable to say\n bundle.savedAt = this.lastWrite;\n // Maintain the lineage chain (doc 15 §1): the PREVIOUS bundle's chain\n // carries forward; the head for the current epoch is refreshed in\n // place (same genesisId) or appended (first write in a new epoch).\n const prior = await this.store.get(this.docId);\n const chain = [...(prior?.lineage ?? [])];\n const digest = await crypto.subtle.digest(\"SHA-256\", bundle.confirmedBytes as BufferSource);\n let hash = \"\";\n for (const b of new Uint8Array(digest)) hash += b.toString(16).padStart(2, \"0\");\n const head = { genesisId: bundle.genesisId, seq: bundle.confirmedSeq, docHash: hash };\n if (chain.length && chain[chain.length - 1].genesisId === bundle.genesisId) chain[chain.length - 1] = head;\n else chain.push(head);\n bundle.lineage = chain.slice(-50); // bounded; ancient epochs age out\n // Offline tail (doc 15 §2): rides every write so a refresh mid-offline\n // (or mid-replay) keeps the un-replayed remainder. An empty tail clears\n // the stored one — a drained replay must not be re-offered.\n const off = this.opts.offlineTail?.();\n if (off && off.tail.length) {\n bundle.offlineTail = off.tail;\n bundle.offlineTailEpoch = off.epoch;\n }\n await this.store.put(bundle);\n }\n}\n","import type { Intent } from \"./intents.js\";\nimport type { CollabConnection } from \"./connection.js\";\n\n/**\n * Offline-edit reconciliation (plan doc 15 §2 / the arrival ladder). Zero\n * custody means a copy edited while the session was gone must reconcile on\n * rejoin. This module does NOT do a document merge — the architecture's core\n * primitive is already \"replay my edits on a changed base,\" so an offline\n * tail replays through the same submit path a reconnecting client's pending\n * queue uses. What lives here is the two pieces that make the OWNER-adopted\n * default — rebase-as-SUGGESTIONS — work: the intent→suggestion converter,\n * and a drained replay driver (one-in-flight discipline, the supported\n * mode; a bounded offline tail replayed with drains needs no sibling math).\n */\n\n/**\n * How many intents the offline tail may hold before RECORDING STOPS AND THE\n * EDITOR IS GATED (doc 15 §4.3: the tail must be bounded). The number:\n *\n * - Anything past the suggest threshold (50) arrives as a draft anyway, so\n * the cap governs only how much offline work the draft can retain — not\n * the review burden, which the ladder already bounds.\n * - Each entry is a small JSON object (~100 bytes), and the tail rides\n * EVERY throttled bundle write, so the cap bounds write amplification to\n * ~200KB per write in the worst case.\n * - 2000 keystroke-grade intents is well past the feature's design target\n * (doc 15 §5: \"the laptop-closed-on-the-train case — small tails, high\n * frequency\"); at that scale the honest answer is a downloaded copy.\n *\n * AT THE CAP THE GATE CLOSES LOUDLY — the editor goes read-only and the UI\n * says so — rather than recording silently stopping, which would be the\n * exact accept-then-lose failure the write gate exists to prevent.\n */\nexport const OFFLINE_TAIL_CAP = 2000;\n\n/**\n * Convert an offline intent tail to its SUGGESTION form (doc 15: the\n * default reconciliation). Text-producing intents become tracked changes —\n * visible, attributed, non-destructive (Word keeps both sides), reviewable\n * with the already-wired accept/reject flow:\n *\n * - `insertText` → `insertText` with `suggest{author,date}` (a w:ins).\n * - `deleteText` → `suggestRevision` striking that range (a w:del wrap;\n * nothing is removed until the crowd accepts).\n *\n * Structural intents (splits, merges, tables, images, format) have no\n * clean non-destructive suggestion form and are DROPPED from the suggestion\n * replay — surfaced to the caller as `dropped` so the UI can say \"N\n * structural changes couldn't be suggested; keep them in a draft instead\"\n * (no silent loss — doc 16 §honest-failure discipline applied to rebase).\n * The alternative path (destructive rebase) keeps them but is gated on the\n * doc-03 sibling math for tails > 1.\n */\nexport function toSuggestions(\n tail: Intent[],\n author: string,\n date: string,\n): { suggestions: Omit<Intent, \"clientId\" | \"clientSeq\" | \"base\">[]; dropped: Intent[] } {\n const suggestions: Omit<Intent, \"clientId\" | \"clientSeq\" | \"base\">[] = [];\n const dropped: Intent[] = [];\n for (const intent of tail) {\n if (intent.kind === \"insertText\") {\n suggestions.push({ kind: \"insertText\", at: intent.at, text: intent.text, suggest: { author, date } } as never);\n } else if (intent.kind === \"deleteText\") {\n suggestions.push({\n kind: \"suggestRevision\",\n ranges: [{ blockId: intent.blockId, runId: intent.runId, start: intent.start, end: intent.end }],\n suggest: { author, date },\n } as never);\n } else {\n dropped.push(intent);\n }\n }\n return { suggestions, dropped };\n}\n\n/**\n * Replay a list of local intents through a connection with the one-in-flight\n * discipline: submit one, wait for it to confirm (drain), submit the next.\n * This is the supported concurrency mode (doc 03) — a bounded offline tail\n * replayed with drains never needs multi-pending sibling rebasing, which is\n * why rebase-as-suggestions ships now while destructive multi-pending rebase\n * stays gated. Each replayed intent transforms against everything the crowd\n * did while you were gone (the connection's normal submit path), so it lands\n * correctly on the current document, not the one you left.\n *\n * `waitDrained` is injected (the connection exposes `pendingCount` via its\n * replica; the app polls it, or in tests a synchronous loopback drains\n * instantly). Returns when the whole tail is submitted-and-drained.\n */\nexport async function replayDrained(\n conn: CollabConnection,\n intents: Omit<Intent, \"clientId\" | \"clientSeq\" | \"base\">[],\n opts: { submit: (i: Omit<Intent, \"clientId\" | \"clientSeq\" | \"base\">) => void; waitDrained: () => Promise<void> },\n): Promise<void> {\n for (const intent of intents) {\n opts.submit(intent);\n await opts.waitDrained();\n }\n void conn;\n}\n\n/**\n * The arrival-ladder decision (doc 15): given the offline tail size and the\n * divergence verdict, pick the reconciliation mode. Pure — the UI renders\n * the choice; this encodes the policy so it is testable and consistent.\n */\nexport function arrivalMode(\n tailLength: number,\n diverged: boolean,\n opts: { suggestThreshold?: number } = {},\n): \"fast-forward\" | \"suggest\" | \"draft\" {\n if (!diverged) return \"fast-forward\";\n const threshold = opts.suggestThreshold ?? 50;\n // Small divergence → suggestions (the default); large → draft only (a\n // giant suggestion pile is review spam — the burden stays on the incomer).\n return tailLength <= threshold ? \"suggest\" : \"draft\";\n}\n","import type { Intent, LogEntry } from \"./intents.js\";\nimport type { IdSidecar } from \"./session.js\";\nimport type { IntentEnvelope, SealedPresence } from \"./e2ee.js\";\n\n/** A sequenced envelope in an encrypted room's log (doc 13 §2): the\n * server-assigned seq plus the opaque envelope. The server can read only\n * the bookkeeping; clients derive the canonical form themselves. */\nexport type EnvelopeEntry = IntentEnvelope & { seq: number };\n\n/**\n * One head in a document's LINEAGE chain (doc 15 §1): the identity of the\n * confirmed state a session epoch ended at. Ancestry is decided by hash\n * membership — never by dates (clocks are self-asserted). A rejoiner whose\n * own head appears in the seed's lineage is a strict ancestor and can\n * FAST-FORWARD silently; anything else is true divergence (draft/fork).\n */\nexport interface LineageHead {\n genesisId: string;\n seq: number;\n /** sha256 hex of the confirmed docx bytes at (genesisId, seq). */\n docHash: string;\n}\n\n/** A sealed checkpoint blob as the wire carries it (doc 13 §3). */\nexport interface SealedCheckpoint {\n seq: number;\n iv: string;\n ciphertext: string;\n}\n\n/**\n * The engine version fence for E2EE rooms (doc 13 §2): canonical forms are\n * CLIENT-derived in encrypted mode, so a room with mixed transform/apply\n * semantics can diverge with no arbiter. Bump whenever those semantics\n * change; the sequencer refuses a hello whose engineVersion mismatches the\n * room's (the stale client gets the download/draft path). Plaintext rooms\n * ignore it (the server's own apply is the arbiter there, and\n * PROTOCOL_VERSION already gates wire compatibility).\n *\n * THE FENCE ONLY WORKS IF THIS ACTUALLY GETS BUMPED. A live fork was traced\n * to two same-\"e1\" builds with different apply semantics (a stale tab across\n * the offset-basis migration): 63 ops sequenced, zero refusals, silent\n * canonical divergence — the exact failure this constant exists to prevent.\n * Bump ledger (append a line for every semantics change):\n * e1 initial E2EE ship\n * e2 separator-inclusive wire offset basis (B1 rev 2); new intents\n * resizeDrawing/resizeTableColumn/resizeTableRow/moveTable/\n * removeDrawing; extender/selection-delete emission changes;\n * canonical insertBreak\n * e3 header/footer editing: the apply's run index now covers the\n * header/footer parts (hf paragraphs were addressable by id but\n * unresolvable, so every hf text intent silently rejected); new\n * intents ensureHeaderFooter and moveMath\n * e4 sealed presence in encrypted rooms (#20). A DELIBERATE OVER-\n * APPLICATION of the rule above: presence is NOT canonical, so a mixed\n * room would degrade to missing remote carets rather than forking the\n * document, and the strict rule would not demand a bump. Bumped anyway\n * to keep dev rooms homogeneous while this lands, after a live fork was\n * traced to two same-version builds with different semantics. Do NOT\n * read this line as evidence that presence is canonical — it is not.\n * e5 text-box story editing: the apply's run index now descends into\n * w:txbxContent (the e3 gap one level deeper — story runs were\n * addressable by id but unresolvable, so every text intent aimed at a\n * text box silently rejected). Squarely canonical: an e4 peer rejects\n * the very intent an e5 peer applies, so a mixed room forks the\n * document on the first keystroke in a pleading number column.\n * e6 padded sealed bodies (traffic-analysis hardening, e2ee.ts): intent,\n * presence and checkpoint plaintexts now carry a 4-byte length prefix\n * and zero fill to fixed buckets, so ciphertext size no longer tracks\n * edit size. Squarely canonical for intents: an e5 openIntent\n * JSON.parses the RAW plaintext, so a padded body throws there — the\n * edit no-ops on every e5 mirror while applying on every e6 mirror, a\n * document fork on the FIRST padded envelope. (Presence and\n * checkpoints ride the same bump; sealed media is deliberately NOT\n * padded — its sha addresses require byte-identical re-encryption.)\n * e7 table text wrapping: tableOp now carries None/Around. An e6 peer\n * interprets the new object as vertical alignment and diverges, so the\n * room must contain only clients that know the new canonical mutation.\n */\nexport const ENGINE_VERSION = \"e7\";\n\n/**\n * Wire protocol between a collab client and the server host. Transport-\n * agnostic: the hub speaks these messages; a WebSocket (or in-memory test)\n * adapter serializes them. Kept deliberately small (plan doc 06).\n */\n\n/** One highlighted stretch of a remote participant's selection: a half-open\n * `[start, end)` inside ONE run, in the same WIRE offset basis carets and\n * suggestRevision use (cumulative within the run, inline separators counting\n * one unit each — see core `EncodedCaret`). The sender emits one entry per\n * selection segment, and a segment never straddles a w:t, so a range stays\n * inside a single run by construction. */\nexport interface PresenceRange {\n blockId: number;\n runId: number;\n start: number;\n end: number;\n}\n\n/** Cap on `PresencePosition.ranges`. Presence crosses a trust boundary (any\n * participant can hand-craft it, and in E2EE mode the server never validates\n * it), so receivers clamp instead of trusting the sender's array length. A\n * selection with more than this many segments renders its first 64. */\nexport const PRESENCE_MAX_RANGES = 64;\n\n/** An ephemeral cursor/selection position on the presence channel: stable-id\n * addresses (plan doc 03). Never logged, never persisted. */\nexport interface PresencePosition {\n anchor: { blockId: number; runId: number; offset: number };\n focus?: { blockId: number; runId: number; offset: number };\n /** Selection highlight (Google-Docs style): the stretches the participant\n * has selected, drawn in their presence color under the remote caret.\n * OPTIONAL and additive — a payload from an older client carries only the\n * anchor and still renders a caret. Absent/empty ⇒ no selection. */\n ranges?: PresenceRange[];\n}\n\n/** Ignore-what-you-can't-trust filter for an inbound presence payload: drops\n * non-finite/negative/inverted ranges and clamps the list to\n * PRESENCE_MAX_RANGES, leaving the caret (the pre-ranges shape) untouched.\n * Applied client-side on receive because the hub RELAYS presence verbatim —\n * it has never validated caret coordinates either, and in encrypted rooms it\n * couldn't be the arbiter anyway. Returns the payload unchanged when there is\n * nothing to strip, so the common path allocates nothing. */\nexport function sanitizePresencePosition(pos: PresencePosition | null): PresencePosition | null {\n if (!pos || !pos.ranges) return pos;\n const int = (n: unknown): boolean => typeof n === \"number\" && Number.isFinite(n) && n >= 0;\n const clean: PresenceRange[] = [];\n for (const r of Array.isArray(pos.ranges) ? pos.ranges : []) {\n if (clean.length >= PRESENCE_MAX_RANGES) break;\n if (!r || typeof r !== \"object\") continue;\n if (!int(r.blockId) || !int(r.runId) || !int(r.start) || !int(r.end)) continue;\n if (r.end <= r.start) continue;\n clean.push({ blockId: r.blockId, runId: r.runId, start: r.start, end: r.end });\n }\n if (clean.length === pos.ranges.length) return pos;\n return clean.length > 0 ? { ...pos, ranges: clean } : { ...pos, ranges: undefined };\n}\n\n/**\n * A participant's self-asserted display profile (plan doc 14 §2). No\n * accounts: anyone with the link can claim any name — stated in the UI\n * (\"names are chosen by participants\") — but the F4 clientId binding\n * guarantees CONTINUITY: one name maps to one actor per session and nobody\n * can impersonate an existing participant mid-session. The server sanitizes\n * (length, control chars, color shape) and clients still render text-node-\n * only with palette-validated colors (doc 11 XSS vector 7 — defense in\n * depth, since a hostile server or E2EE mode moves sanitization client-side).\n */\nexport interface ParticipantProfile {\n /** 1–40 chars after server sanitization. */\n name: string;\n /** Hex color `#rrggbb`; invalid values are replaced server-side by a\n * palette color derived from the clientId hash. */\n color: string;\n}\n\n/** One roster row (doc 14 §2): keyed by the BOUND clientId — the same\n * identity intents carry, so attribution, presence, and roster share one\n * keyspace. Disconnected participants stay listed (greyed) for the session's\n * lifetime; a reconnect under the same clientId resumes the entry. */\n/**\n * Whether a participant's edits will be SEQUENCED, and if not, why.\n *\n * A POSITIVE signal, which is the entire point. Until this existed a client\n * could only discover it was blocked by making an edit and being refused —\n * which cost the user their first keystrokes every time (optimistic local\n * mutation, refusal, heal: a burst of text that appears and vanishes), and\n * left them stuck in viewer mode after a lock was LIFTED, because nothing\n * announced the good news either.\n *\n * The three blocked states arrive as one `read-only` refusal on the wire, so\n * they are separated here — the difference is what a UI can honestly say:\n * - `owner-lock` the owner paused editing for the whole room; it may lift\n * - `demoted` the owner made THIS participant a viewer\n * - `viewer-role` the access token grants read access only; nothing the\n * owner does in-session changes it\n */\nexport type WriteStatus = \"allowed\" | \"owner-lock\" | \"demoted\" | \"viewer-role\";\n\n/** One roster row (doc 14 §2): keyed by the BOUND clientId — the same\n * identity intents carry, so attribution, presence, and roster share one\n * keyspace. Disconnected participants stay listed (greyed) for the session's\n * lifetime; a reconnect under the same clientId resumes the entry. */\nexport interface RosterEntry {\n clientId: string;\n profile: ParticipantProfile;\n connected: boolean;\n /**\n * Whether this participant may write, re-fanned on every transition.\n *\n * OPTIONAL because it is additive: an older server omits it. A client that\n * sees `undefined` must NOT assume \"allowed\" — it should fall back to the\n * refusal-driven behaviour it had before, exactly as the published media\n * limit's `null` means \"skip the pre-check\". Inventing a permissive default\n * would put the user back in the first-edit-lost loop this removes.\n *\n * PER-CLIENT, not a room flag: an owner keeps writing through their own\n * room-wide lock, so a single broadcast value would be wrong for them.\n */\n write?: WriteStatus;\n}\n\n/** Client → server. */\nexport type ClientMessage =\n | {\n t: \"hello\";\n protocolVersion: number;\n docId: string;\n /**\n * The identity every subsequent submit/presence message is bound to.\n * The hub registers it for this socket at hello and REFUSES any submit\n * whose intent.clientId differs (plan doc 11 decision 8 / round-4 F4):\n * `(clientId, clientSeq)` is the idempotency key, so an unbound claimed\n * id would let one client poison another's dedup space (silent edit\n * loss) and forge attribution. Registration-on-first-hello is\n * sufficient — the id needs continuity, not global meaning.\n */\n clientId: string;\n /**\n * Single-live-connection rule (plan doc 12 §7): a second hello for the\n * same (docId, clientId) is refused `already-open` — same-profile tabs\n * share localStorage identity and would collide clientSeq counters.\n * With `takeover: true` the NEW connection wins instead: the hub kicks\n * the old socket (it may be a zombie tab) and admits this one.\n */\n takeover?: boolean;\n token?: string;\n sinceSeq: number;\n /** The epoch id from the client's stored bundle, when resuming (plan\n * doc 12 §5). Informational to the server today — epoch comparison is\n * client-side (welcome.genesisId vs the bundle's) — but carried so the\n * server can log resume-vs-fresh joins and future-proof case handling. */\n genesisId?: string;\n /** Display profile (doc 14 §2) — optional; omitted = anonymous default\n * derived server-side. In E2EE mode this becomes an opaque encrypted\n * blob (doc 13/14) — the shape change rides that protocol bump. */\n profile?: ParticipantProfile;\n /** Engine (transform/apply semantics) version — REQUIRED for encrypted\n * rooms, where the fence prevents mixed-semantics divergence. */\n engineVersion?: string;\n /**\n * Share-code proof (doc 13 §7): the PBKDF2-stretched code, base64.\n * The server compares against the verifier registered at seed time and\n * enforces an attempt budget — 10^6 combinations is plenty against 5\n * online tries, and in E2EE mode the code ALSO mixes into key\n * derivation, so even a server bypass yields undecryptable content.\n */\n codeProof?: string;\n /**\n * Owner-capability proof (doc 14 §2.5): the token minted at seed and\n * returned ONLY to the seeder — never part of the shared link. A\n * matching token flags this connection as the epoch's owner\n * (read-only bypass + admin channel). Roles are capabilities, not\n * accounts: lose the bundle, lose the crown; re-seed to reclaim.\n */\n ownerToken?: string;\n }\n /** Update this connection's profile mid-session (rename / recolor). */\n | { t: \"profile\"; profile: ParticipantProfile }\n /**\n * Owner admin channel (doc 14 §2.5) — only honored from a connection\n * whose hello proved the owner token. All enforceable by a BLIND server\n * (identity/role are plaintext bookkeeping). Honest limits: kick/demote\n * key by clientId (a determined user can mint a fresh id — a nudge, not\n * a wall, without accounts), and in E2EE mode an owner controls WRITES,\n * never reads (re-key is the only read fence).\n */\n | { t: \"admin\"; action:\n | { op: \"kick\"; clientId: string }\n | { op: \"readOnly\"; on: boolean }\n | { op: \"setRole\"; clientId: string; role: \"editor\" | \"viewer\" } }\n | { t: \"submit\"; intent: Intent }\n /** Encrypted-mode submit (doc 13 §2): opaque body, plaintext bookkeeping.\n * The hub refuses this on plaintext rooms and refuses plaintext `submit`\n * on encrypted rooms — no mixed-mode documents, enforced both ways. */\n | { t: \"submit-enc\"; envelope: IntentEnvelope }\n /** Upload a sealed checkpoint (doc 13 §3) — accepted ONLY from the\n * connection the server currently designates as checkpointer. */\n | { t: \"checkpoint\"; checkpoint: SealedCheckpoint }\n /** Media re-supply (doc 16 §3): \"I need blob <sha>\" (relay miss). */\n | { t: \"media-need\"; sha: string }\n /** \"I hold these blobs\" — reply to media-request, or unsolicited right\n * after a welcome whose mediaNeeded intersects local holdings (§5.4). */\n | { t: \"media-have\"; shas: string[] }\n /** Encrypted hash gossip (doc 13 §2): an OPAQUE sealed {seq, hash} blob\n * the server relays without reading — divergence detection must not leak\n * even document hashes to a blind server (a hash is a stable content\n * fingerprint: confirmable by anyone holding a guess). */\n | { t: \"gossip\"; iv: string; ciphertext: string }\n /** Presence. In an ENCRYPTED room this is a `SealedPresence` blob and the\n * hub relays it without looking (#20): the server learns who is pointing,\n * never where. Plaintext rooms keep the structured position, which the hub\n * still clamps at the relay. */\n | { t: \"presence\"; position: PresencePosition | SealedPresence | null }\n /**\n * APPLICATION-LEVEL LIVENESS PROBE (the reconnect arc). The server answers\n * every one with {@link ServerMessage} `pong` carrying the same nonce.\n *\n * WHY THIS EXISTS AT ALL, given TCP and the WebSocket close handshake: a\n * phone that sleeps mid-session leaves a HALF-OPEN connection. The peer is\n * gone, but nothing tells this end — `onclose` never fires, `onerror` never\n * fires, `readyState` still reads OPEN, and every `send()` succeeds into a\n * socket whose bytes will never arrive. The only way to learn the truth is\n * to ask for a reply and notice it did not come.\n *\n * `nonce` is an opaque round-trip token, echoed back unmodified. It exists\n * so a pong can be matched to the ping it answers — without it a stale pong\n * from before a reconnect could silently satisfy the CURRENT probe and mask\n * a genuinely dead socket. It carries no meaning and is never persisted.\n *\n * DELIBERATELY NOT ACTIVITY. The hub answers this WITHOUT calling\n * `noteActivity`, so a heartbeat cannot hold an abandoned room open — the\n * exact hazard the idle-timeout notes call out (\"an activity-based clock is\n * what a keepalive script defeats\"). A ping proves the socket is alive; it\n * proves nothing about a human being present, and those are different\n * questions with different consequences.\n */\n | { t: \"ping\"; nonce: number };\n\n/** Server → client. */\nexport type ServerMessage =\n | {\n t: \"welcome\";\n docId: string;\n seq: number;\n snapshot: string;\n /**\n * The stable-id sidecar for the snapshot (plan doc 12 §2, round-4 F10).\n * A snapshot NEVER travels without it: the joiner cannot re-derive the\n * id table from parse order once history contains split-created carried\n * ids (round-2 F1) — it would silently mis-address every later intent.\n */\n sidecar: IdSidecar;\n tail: LogEntry[];\n /**\n * The session EPOCH id (plan doc 12): minted fresh every time a doc is\n * seeded/re-seeded. A resuming client compares it against its bundle's\n * stored genesisId — same ⇒ seamless rejoin (case 1); different ⇒\n * someone re-seeded while it was away (case 2: take server state, keep\n * the local copy per doc 15 lineage — NEVER silently merge epochs).\n */\n genesisId: string;\n /** Session encryption mode (plan doc 13 §6). Clients derive the truth\n * from their link (#k present ⇒ encrypted) and HARD-REFUSE a welcome\n * that contradicts it — the wire value must never downgrade a client. */\n mode: \"plaintext\" | \"encrypted\";\n /** The seed's lineage chain (doc 15): lets a rejoining holder decide\n * fast-forward vs fork CLIENT-side. Absent for provider-created and\n * legacy rooms (⇒ every epoch mismatch is a fork, the safe default). */\n lineage?: LineageHead[];\n /** Shas with waiters / outstanding unavailability (doc 16 §3): a\n * joining holder intersects with its local media and volunteers —\n * the mechanism behind \"reappears when a holder rejoins\". */\n mediaNeeded?: string[];\n /** Per-part media ADDRESSES (doc 16 §6 late-join): the declared sha\n * (+ E2EE iv) for every registered media part, so a joiner can fetch\n * pixels its snapshot references but does not contain — without this\n * the address lives only in the already-folded insertImage intent and\n * late-join media is unfetchable. Plaintext rooms only; encrypted\n * rooms carry the same map INSIDE the sealed checkpoint body (the\n * server must not learn part structure beyond what PUT addresses\n * already reveal). Parse-derived holes with unknown sha are omitted. */\n media?: { part: string; sha: string; iv?: string }[];\n /**\n * The relay's configured per-blob size limit, in bytes, so a client can\n * CHECK A FILE BEFORE UPLOADING IT rather than discovering the limit\n * from a 413 afterwards.\n *\n * The number flows FORWARD — server to client, once per session — which\n * is why it lives here rather than being threaded back through the\n * upload's return type. A refusal has to travel up through every layer\n * between the relay and the file picker, and each of those layers has\n * its own idea of what failure looks like; a value published at join\n * time has no such path to get lost on.\n *\n * NOT A SECRET, and nothing is gated on it: the same number already\n * appears in a 413 body, and anyone can discover it by uploading\n * something large. It is per-SERVER-CONFIG, not per-room, so it carries\n * nothing about the room's contents or participants.\n *\n * Optional and additive: an older server omits it, and a client that\n * gets nothing should skip the pre-check rather than invent a limit —\n * the server still enforces the real one.\n */\n mediaMaxBlobBytes?: number;\n }\n | { t: \"broadcast\"; entries: LogEntry[] }\n /**\n * Encrypted-mode welcome (doc 13 §3): the seed checkpoint (sealed by the\n * epoch's seeder) + the epoch's envelope log after it. BASE-COMPLETE by\n * construction (round-4 blocker 1): the demo server retains the WHOLE\n * epoch log in RAM (doc 12 — rooms are session-scoped), so every entry a\n * joiner needs to re-derive canonical forms is present; client-produced\n * mid-session checkpoints (doc 13 item 6) are a RAM optimization on top,\n * not a correctness requirement, and land with the retention rule.\n */\n | {\n t: \"welcome-enc\";\n docId: string;\n genesisId: string;\n checkpoint: SealedCheckpoint;\n tail: EnvelopeEntry[];\n mode: \"encrypted\";\n mediaNeeded?: string[];\n /** Relay per-blob size limit — see the `welcome` field of the same\n * name. Published identically in encrypted rooms: it is server config,\n * not room content, so a blind sequencer can state it in the clear. */\n mediaMaxBlobBytes?: number;\n }\n /** Encrypted-mode broadcast: sequenced opaque envelopes. */\n | { t: \"broadcast-enc\"; entries: EnvelopeEntry[] }\n /** Relayed gossip blob; `from` is the sender's BOUND clientId. */\n | { t: \"gossip\"; from: string; iv: string; ciphertext: string }\n /** Media re-supply control (doc 16 §4). request = broadcast \"who has\n * it\"; upload = to ONE chosen holder; ready/unavailable = to waiters. */\n | { t: \"media-request\"; sha: string }\n | { t: \"media-upload\"; sha: string }\n | { t: \"media-ready\"; sha: string }\n | { t: \"media-unavailable\"; sha: string }\n /** Checkpointer designation (doc 13 §3): the SERVER assigns the role —\n * the v1 lowest-clientId election was riggable (round-4 blocker 2); an\n * assigned role can only be held by an authenticated connection the\n * server picked. Rotated on disconnect. */\n | { t: \"checkpointer\"; active: boolean }\n /** `participant` is the sender's bound clientId (round-4 F14) — the same\n * identifier intents carry — so presence joins the roster/attribution\n * keyspace and survives the sender reconnecting on a new socket. */\n | { t: \"presence\"; participant: string; position: PresencePosition | SealedPresence | null }\n /** Full roster snapshot, fanned out on every join/leave/profile change\n * (rooms are small; a snapshot beats delta bookkeeping). Ephemeral like\n * presence: never logged, never persisted, dies with the room. */\n | { t: \"roster\"; roster: RosterEntry[] }\n /**\n * The session is going to END, and this is the grace period before it does\n * (server lifecycle arc). Sent once per armed deadline to every connection\n * in the room so the UI can run a countdown — a session that vanishes\n * without warning is indistinguishable from a crash, and the whole point of\n * these timeouts is that they are POLICY, visibly applied.\n *\n * - `idle` nobody has done anything for the idle window. CANCELLABLE:\n * any qualifying activity (a submit, an admin action, a media\n * transfer, someone joining — never presence) resets the clock\n * and produces `session-warning-cleared`.\n * - `lifetime` the room has reached its absolute age cap. NOT cancellable\n * by anything; the deadline was fixed when the room was\n * created. Ending it does not end the document — any holder\n * re-seeds from their bundle into a NEW epoch.\n *\n * `inMs` is the ACTUAL remaining time when the frame was built, not the\n * configured lead time: warnings are emitted by a periodic sweep, so the\n * real remainder is up to one sweep interval less than the configured\n * value, and a client counting down from the constant would lie.\n */\n | { t: \"session-warning\"; reason: \"idle\" | \"lifetime\"; inMs: number }\n /**\n * A previously warned deadline is no longer approaching — the countdown\n * should disappear. Only ever `idle`: the lifetime cap cannot be cancelled,\n * and narrowing the type here means a consumer cannot write a branch the\n * server is incapable of producing.\n */\n | { t: \"session-warning-cleared\"; reason: \"idle\" }\n /**\n * The answer to a client `ping`, echoing its nonce. Proof that the socket is\n * end-to-end alive RIGHT NOW — not merely that the local OS still believes\n * in it, which is all `readyState === OPEN` ever establishes.\n *\n * Emitted unconditionally and statelessly: no room lookup, no auth check, no\n * activity bookkeeping. A liveness answer that could itself be refused would\n * be indistinguishable from the death it is supposed to detect.\n */\n | { t: \"pong\"; nonce: number }\n | { t: \"refused\"; reason: string };\n\n/** Bump when the intent apply/transform semantics change in a way that makes\n * mixed-version clients diverge (plan doc 03 version lockstep). A client whose\n * version differs is refused at hello with \"please refresh\".\n * v2: hello carries clientId (bound identity) + takeover; welcome carries the\n * id sidecar.\n *\n * v3: ping/pong liveness (the reconnect arc).\n *\n * THIS ONE LOOKS ADDITIVE AND IS NOT, and the reason is worth stating because\n * the instinct to skip the bump is strong and wrong. Neither dispatch switch\n * has a `default:` clause — the hub's (hub.ts) and both clients'\n * (connection.ts / enc-connection.ts) — so an unknown `t` is silently dropped\n * at every hop. That cuts in opposite directions per side:\n *\n * - An OLD CLIENT receiving `pong` ignores it. Harmless.\n * - An OLD SERVER receiving `ping` ignores it AND SENDS NOTHING BACK.\n *\n * The second case is fatal to the feature. The heartbeat's entire value is\n * that a missing pong is CONCLUSIVE evidence the socket is dead — that is the\n * only signal that catches the half-open sleeping phone, where onclose and\n * onerror never fire. Against a v2 server every pong is missing, so a new\n * client would diagnose a perfectly healthy session as lost and gate the\n * editor read-only: a silent-data-loss bug traded for a can't-type-at-all bug.\n * Weakening the client to treat silence as inconclusive would restore the very\n * ambiguity the probe exists to remove.\n *\n * Because hello ALREADY hard-refuses a protocolVersion mismatch, bumping makes\n * \"I am connected\" imply \"my peer answers pings\" by construction, and a missing\n * pong stays unambiguous. The cost is that v2 clients are refused with \"please\n * refresh\" — the mechanism working as designed, and the same price every prior\n * bump paid.\n *\n * ENGINE_VERSION is deliberately NOT bumped. That fence guards CANONICAL\n * DIVERGENCE in E2EE rooms, where clients derive document state themselves and\n * no arbiter exists. Liveness frames are never sealed, never sequenced, never\n * logged, and never reach apply or transform; two clients disagreeing about\n * them cannot produce two different documents. Bumping it would force every\n * live room to re-seed for a change that cannot affect a byte of content. */\nexport const PROTOCOL_VERSION = 3;\n","import type { DocxDocument } from \"@wordinweb/core\";\n\n/**\n * Media transfer client (plan doc 16 §5) — the half that was specced and\n * left unbuilt while the server relay shipped.\n *\n * Bytes NEVER ride the WebSocket sequencer: the intent carries only an\n * address (`blobSha`) and the blob travels over HTTP. Everything here is\n * organised around one rule from doc 16 §1.1 — nobody is ever TOLD a hash.\n * The placer commits `blobSha` inside the sequenced, authenticated intent;\n * from then on every participant re-derives sha256 itself and compares\n * against that commitment. A deliverer's claim about what it is delivering\n * is never consulted, so a hostile relay or peer can at worst withhold\n * bytes, never substitute them.\n *\n * ENVELOPE (as SHIPPED, which differs from doc 16 §1's prose): the blob is\n * the BARE AES-GCM ciphertext and the 12-byte IV rides in the intent's `iv`\n * field — the shape doc 16 §2 specifies and `sealMediaBlob` implements.\n * §1/§5.1's \"prepend IV to the blob\" is stale text. The sha is over the\n * ciphertext either way, which is what lets the blind relay verify it.\n */\n\n/** One registered media part's address, as it travels to a late joiner:\n * plaintext rooms in `welcome.media`, encrypted rooms inside the SEALED\n * checkpoint body (the server must not learn part structure). */\nexport interface MediaAddress {\n part: string;\n sha: string;\n iv?: string;\n}\n\n/** The late-join address map for a document: every registered part whose sha\n * is actually KNOWN. Parse-derived holes (sha === \"\") are omitted — an empty\n * address is not an address, and passing one on would only teach the joiner\n * the same nothing. Mirrors the hub's filter for plaintext welcomes. */\nexport function mediaAddressesOf(doc: DocxDocument): MediaAddress[] {\n const out: MediaAddress[] = [];\n for (const [part, meta] of doc.mediaMeta) {\n if (!meta.sha) continue;\n out.push({ part, sha: meta.sha, ...(meta.iv ? { iv: meta.iv } : {}) });\n }\n return out;\n}\n\n/**\n * Install late-join media addresses onto a freshly rehydrated document\n * (doc 16 §6).\n *\n * A joiner's snapshot contains the REGISTRATIONS — relationships pointing at\n * absent parts — but not the declared shas, because those live only in\n * intents already folded into the snapshot. `load()` therefore derives holes\n * with an empty sha, which render a skeleton but can never be fetched. This\n * fills those addresses in, turning \"I can see an image belongs here\" into\n * \"I can go and get it\".\n *\n * Only ever fills a part that is genuinely pending: an address must never\n * overwrite the metadata of a part whose bytes this replica already holds.\n */\nexport function applyMediaAddresses(\n doc: DocxDocument,\n addresses: MediaAddress[] | undefined,\n): void {\n if (!addresses?.length) return;\n for (const { part, sha, iv } of addresses) {\n if (!sha || doc.mediaStatus(part) === \"ready\") continue;\n const meta = { sha, ...(iv ? { iv } : {}) };\n doc.pendingMedia.set(part, meta);\n doc.mediaMeta.set(part, meta);\n }\n}\n\n/** Lowercase hex sha256 — the address of a blob. */\nexport async function sha256Hex(bytes: Uint8Array): Promise<string> {\n const digest = await crypto.subtle.digest(\"SHA-256\", bytes as BufferSource);\n let hex = \"\";\n for (const b of new Uint8Array(digest)) hex += b.toString(16).padStart(2, \"0\");\n return hex;\n}\n\n/** Injectable fetch so tests drive the relay without a socket or a server. */\nexport type FetchLike = (url: string, init?: { method?: string; body?: Uint8Array }) => Promise<{\n ok: boolean;\n status: number;\n arrayBuffer(): Promise<ArrayBuffer>;\n}>;\n\nexport interface MediaTransportOptions {\n /** Origin serving the doc-16 §3 routes, e.g. \"http://localhost:1234\". */\n httpBase: string;\n /** Defaults to global fetch. */\n fetchImpl?: FetchLike;\n}\n\nfunction mediaUrl(httpBase: string, docId: string, sha: string): string {\n return `${httpBase.replace(/\\/$/, \"\")}/docs/${encodeURIComponent(docId)}/media/${sha}`;\n}\n\n/** PUT a blob at its own hash. 200 already-present / 201 stored are both\n * success; anything else (413 too large, 507 room quota, 400 sha mismatch)\n * is a refusal the PLACER must see before it emits any intent. */\nexport async function putBlob(\n opts: MediaTransportOptions,\n docId: string,\n sha: string,\n blob: Uint8Array,\n): Promise<{ ok: boolean; status: number }> {\n const f = opts.fetchImpl ?? (globalThis.fetch as unknown as FetchLike);\n try {\n const res = await f(mediaUrl(opts.httpBase, docId, sha), { method: \"PUT\", body: blob });\n return { ok: res.status === 200 || res.status === 201, status: res.status };\n } catch {\n return { ok: false, status: 0 }; // offline / CORS / DNS — same as a refusal\n }\n}\n\n/** GET a blob. null means \"the relay does not have it\" (404) — the caller\n * then asks the room via `media-need`. */\nexport async function getBlob(\n opts: MediaTransportOptions,\n docId: string,\n sha: string,\n): Promise<Uint8Array | null> {\n const f = opts.fetchImpl ?? (globalThis.fetch as unknown as FetchLike);\n try {\n const res = await f(mediaUrl(opts.httpBase, docId, sha));\n if (!res.ok) return null;\n return new Uint8Array(await res.arrayBuffer());\n } catch {\n return null;\n }\n}\n\n/** How a pending part is faring, for the placeholder UI (doc 16 §5.2 step 4). */\nexport type MediaState = \"fetching\" | \"waiting\" | \"unavailable\";\n\n/** Crypto seam: plaintext mode supplies neither, E2EE mode supplies both. */\nexport interface MediaCrypto {\n /** Seal plaintext for upload; reusing `iv` reproduces a blob byte-identically. */\n seal(plaintext: Uint8Array, iv?: string): Promise<{ blob: Uint8Array; iv: string }>;\n /** Open a downloaded blob; MUST throw on a GCM tag failure. */\n open(blob: Uint8Array, iv: string): Promise<Uint8Array>;\n}\n\nexport interface MediaClientCallbacks {\n /** Bytes landed (or a state changed) — repaint. */\n onChange?: () => void;\n /** Placeholder state per part, for the \"unavailable\" affordance. */\n onState?: (part: string, state: MediaState) => void;\n /** Ask the room for a sha (the connection sends the WS frame). */\n need: (sha: string) => void;\n /** Volunteer holdings (the connection sends the WS frame). */\n have: (shas: string[]) => void;\n}\n\n/**\n * Per-replica media duties: eager fetch for holes, re-supply for holdings.\n *\n * Deliberately owns no transport and no keys of its own — the connection\n * hands it a doc getter, a crypto seam (absent in plaintext mode), and the\n * two WS senders. That keeps ONE implementation of the state machine behind\n * both the plaintext and encrypted connections, which had drifted to \"fully\n * built\" and \"entirely absent\" respectively.\n */\nexport class MediaClient {\n private inFlight = new Set<string>();\n private states = new Map<string, MediaState>();\n /** Shas we asked the room for, awaiting a media-ready. */\n private waitingFor = new Set<string>();\n\n constructor(\n private opts: MediaTransportOptions,\n private getDoc: () => DocxDocument | null,\n private cb: MediaClientCallbacks,\n private crypto?: MediaCrypto,\n ) {}\n\n /** Current placeholder state of a pending part (undefined once ready). */\n stateOf(part: string): MediaState | undefined {\n return this.states.get(part);\n }\n\n /**\n * PLACER (doc 16 §5.1). Seal (E2EE) → address → upload. Returns the intent\n * fields on success, or null when the relay refused — in which case the\n * caller MUST NOT emit an insertImage, or the room gets a reservation\n * pointing at a blob that does not exist.\n */\n async upload(docId: string, plaintext: Uint8Array): Promise<{ blobSha: string; bytesLen: number; iv?: string } | null> {\n const sealed = this.crypto ? await this.crypto.seal(plaintext) : null;\n const blob = sealed ? sealed.blob : plaintext;\n const blobSha = await sha256Hex(blob);\n const res = await putBlob(this.opts, docId, blobSha, blob);\n if (!res.ok) return null;\n // \"Bytes install into the placer's own doc immediately\" (doc 16 §5.1).\n // Keeping the plaintext here is what makes that true no matter WHEN the\n // reservation applies — in an encrypted room the intent is sealed and\n // applied on an async queue, so the part often doesn't exist yet at the\n // moment upload() returns, and the placer would otherwise download its\n // own image back from the relay just to look at it.\n this.ownBlobs.set(blobSha, plaintext);\n return { blobSha, bytesLen: blob.length, iv: sealed?.iv };\n }\n\n /** Plaintext of blobs THIS client uploaded, so its own images never take a\n * network round trip. Bounded: an entry is dropped once installed. */\n private ownBlobs = new Map<string, Uint8Array>();\n\n /**\n * RECEIVER (doc 16 §5.2). Eager-fetch every pending part the doc knows\n * about. Idempotent and safe to call after every applied broadcast: parts\n * already ready or already in flight are skipped.\n */\n async fetchPending(docId: string): Promise<void> {\n const doc = this.getDoc();\n if (!doc) return;\n for (const [part, meta] of [...doc.pendingMedia]) {\n // An empty sha means the hole was DERIVED from the package (a rel to a\n // missing part) rather than from an intent, so no address was ever\n // committed for it and there is nothing to ask the relay for. It still\n // renders a skeleton; see the late-join note in the arc report.\n if (!meta.sha || this.inFlight.has(part)) continue;\n this.inFlight.add(part);\n void this.fetchOne(docId, part, meta).finally(() => this.inFlight.delete(part));\n }\n }\n\n private setState(part: string, state: MediaState): void {\n this.states.set(part, state);\n // Mirror onto the doc so the renderer's skeleton can say which of\n // \"still coming\" and \"nobody online has it\" it is showing, without the\n // renderer knowing anything about transports.\n this.getDoc()?.mediaTransferState.set(part, state);\n this.cb.onState?.(part, state);\n }\n\n private async fetchOne(docId: string, part: string, meta: { sha: string; iv?: string }): Promise<void> {\n const doc = this.getDoc();\n if (!doc || doc.mediaStatus(part) === \"ready\") return;\n // Our own upload: install straight from memory (no relay, no decrypt —\n // we are holding the plaintext that produced this address).\n const own = this.ownBlobs.get(meta.sha);\n if (own) {\n doc.installMedia(part, own);\n this.states.delete(part);\n doc.mediaTransferState.delete(part);\n this.ownBlobs.delete(meta.sha);\n this.cb.onChange?.();\n return;\n }\n this.setState(part, \"fetching\");\n const blob = await getBlob(this.opts, docId, meta.sha);\n if (!blob) {\n // Relay miss: register as a waiter and let the re-supply flow run.\n this.setState(part, \"waiting\");\n this.waitingFor.add(meta.sha);\n this.cb.need(meta.sha);\n return;\n }\n await this.installVerified(part, meta, blob);\n }\n\n /**\n * Verify then install. The sha check is against the value COMMITTED IN THE\n * INTENT, never against anything the deliverer said — defense in depth\n * behind the relay's own check (doc 16 §5.2 step 3), and the only thing\n * standing between a compromised relay and arbitrary bytes in the document.\n */\n private async installVerified(part: string, meta: { sha: string; iv?: string }, blob: Uint8Array): Promise<boolean> {\n if ((await sha256Hex(blob)) !== meta.sha) {\n this.setState(part, \"waiting\"); // wrong bytes: keep the skeleton, keep waiting\n return false;\n }\n let bytes = blob;\n if (this.crypto) {\n if (!meta.iv) return false; // encrypted room, no IV recorded: cannot open\n try {\n bytes = await this.crypto.open(blob, meta.iv);\n } catch {\n this.setState(part, \"waiting\"); // GCM tag failure — reject, keep skeleton\n return false;\n }\n }\n const doc = this.getDoc();\n if (!doc) return false;\n doc.installMedia(part, bytes);\n this.states.delete(part);\n doc.mediaTransferState.delete(part);\n this.waitingFor.delete(meta.sha);\n this.cb.onChange?.();\n return true;\n }\n\n /** Server says the blob is fetchable now — retry the parts waiting on it. */\n async onReady(docId: string, sha: string): Promise<void> {\n this.waitingFor.delete(sha);\n const doc = this.getDoc();\n if (!doc) return;\n for (const [part, meta] of [...doc.pendingMedia]) {\n if (meta.sha !== sha || this.inFlight.has(part)) continue;\n this.inFlight.add(part);\n void this.fetchOne(docId, part, meta).finally(() => this.inFlight.delete(part));\n }\n }\n\n /** No holder is online. The registration stays; a later media-ready (or a\n * holder rejoining, §5.4) recovers it with no action from the user. */\n onUnavailable(sha: string): void {\n const doc = this.getDoc();\n if (!doc) return;\n for (const [part, meta] of doc.pendingMedia) {\n if (meta.sha === sha) this.setState(part, \"unavailable\");\n }\n }\n\n /** Shas this replica can serve: parts whose bytes are PRESENT. */\n heldShas(): string[] {\n const doc = this.getDoc();\n if (!doc) return [];\n const out: string[] = [];\n for (const [part, meta] of doc.mediaMeta) {\n if (doc.mediaStatus(part) === \"ready\") out.push(meta.sha);\n }\n return out;\n }\n\n /** §5.4: intersect the room's outstanding needs with local holdings and\n * volunteer — this is what makes evicted media reappear when a holder\n * comes back, with no polling anywhere. */\n volunteer(mediaNeeded: string[] | undefined): void {\n if (!mediaNeeded?.length) return;\n const held = new Set(this.heldShas());\n const intersection = mediaNeeded.filter((sha) => held.has(sha));\n if (intersection.length) this.cb.have(intersection);\n }\n\n /** Someone in the room needs a sha: answer if we hold it (§5.3). */\n answerRequest(sha: string): void {\n if (this.heldShas().includes(sha)) this.cb.have([sha]);\n }\n\n /**\n * HOLDER DUTY (doc 16 §5.3): chosen to re-supply — upload our copy.\n *\n * In an encrypted room this RE-SEALS with the IV recorded on the part, not\n * a fresh one: same key + same IV + same plaintext reproduces the exact\n * ciphertext, which is the only thing that still hashes to the address the\n * intent committed to. A fresh IV would produce a perfectly valid blob at\n * the WRONG address, and the upload would be rejected — do not \"fix\" it\n * that way (doc 16 §5.3 / doc 13 §4 both carry this warning).\n *\n * The local sha assertion before PUT is deliberate: failing it means THIS\n * replica's pixels are corrupt, and uploading them would just burn the\n * re-supply rotation on bytes the relay will reject anyway.\n */\n async resupply(docId: string, sha: string): Promise<boolean> {\n const doc = this.getDoc();\n if (!doc) return false;\n for (const [part, meta] of doc.mediaMeta) {\n if (meta.sha !== sha || doc.mediaStatus(part) !== \"ready\") continue;\n const pixels = doc.media(part);\n if (!pixels) continue;\n let blob = pixels;\n if (this.crypto) {\n if (!meta.iv) return false;\n blob = (await this.crypto.seal(pixels, meta.iv)).blob;\n }\n if ((await sha256Hex(blob)) !== sha) return false; // local corruption — never upload\n const res = await putBlob(this.opts, docId, sha, blob);\n return res.ok;\n }\n return false;\n }\n}\n","import type { Intent } from \"./intents.js\";\n\n/**\n * E2EE primitives for the blind-sequencer mode (plan doc 13). Pure WebCrypto\n * (available in browsers and Node ≥16 globals) — no dependencies, usable\n * from both the client and tests.\n *\n * Key model (doc 13 §1): a per-DOCUMENT master key `K_doc` minted at\n * go-live, carried only in the share link's URL fragment. Per-epoch subkeys\n * are DERIVED — `K_epoch = HKDF(K_doc, genesisId [+ share code])` — then\n * domain-separated into K_content (intents/checkpoints) and K_media\n * (blobs). Epoch derivation kills cross-epoch replay (round-4 F5a) and\n * resets the GCM IV budget per epoch (F21); nobody ever redistributes keys,\n * because anyone with the link + the (public) epoch id derives the same\n * values.\n *\n * AAD discipline (round-4 F5): every ciphertext is bound to its exact\n * position in the protocol — intents to (docId, genesisId, clientId,\n * clientSeq, base); checkpoints to (docId, genesisId, seq). `base` is\n * authenticated because it is a TRANSFORM INPUT: an unauthenticated base\n * would let a keyless server shift where an edit applies.\n */\n\n/** The plaintext bookkeeping + opaque body the blind sequencer handles. */\nexport interface IntentEnvelope {\n clientId: string;\n clientSeq: number;\n base: number;\n /** base64 12-byte GCM IV (fresh per envelope). */\n iv: string;\n /** base64 AES-256-GCM ciphertext (+tag) of the JSON-serialized intent. */\n ciphertext: string;\n}\n\nexport interface EpochKeys {\n kContent: CryptoKey;\n kMedia: CryptoKey;\n /**\n * Presence (carets/selections). A THIRD domain-separated key rather than\n * kContent, because presence is high-frequency, low-value material with a\n * completely different exposure profile: it is sealed and opened on every\n * caret move, so it burns IVs fastest and is the likeliest place for a\n * future mistake. Key separation costs one HKDF call at join and means a\n * presence-side compromise cannot touch document content.\n */\n kPresence: CryptoKey;\n}\n\nconst te = new TextEncoder();\nconst td = new TextDecoder();\n\n/**\n * Sealed-body PADDING (traffic-analysis hardening). AES-GCM is length-\n * preserving and the server orders envelopes it cannot open, so without\n * padding the ciphertext length tracks the edit exactly: a 1-char keystroke,\n * a suggesting keystroke, a paste and a table op each had a distinct sealed\n * size, giving an observer a paste-length oracle (±3 chars) plus a\n * \"suggesting mode\" signature — against a document it already holds.\n *\n * Wrapper format (below the intent system on purpose — a `pad` field inside\n * the JSON would ride along in pending copies, undo inverses and mirror\n * logs, and its tolerance through validate/transform/apply is unverified):\n *\n * [4-byte big-endian body length][JSON bytes][zero fill to bucket]\n *\n * Every seal/open goes through pad/unpad here, so all call sites (submit,\n * undo, resume replay, stale-base re-seal, stuck redrive) are covered by\n * this one choke point. AAD and fresh-IV-per-envelope are untouched.\n *\n * Bucket ladders (plaintext bytes, prefix included):\n * - intents: {384, 1024, 4096}, then the next 4 KiB multiple. 384 is sized\n * so every single-edit operation — keystroke, backspace, Enter, format,\n * suggesting keystroke, table op, insertImage, sentence-sized paste —\n * lands in ONE size class (measured max ≈ 320 bytes of JSON).\n * - presence: {128, 1024}, then the next 1 KiB multiple (a 64-range\n * selection clamp can exceed 1024). Hides selection size; the\n * caret-versus-\"has a selection\" bit intentionally remains visible.\n * - checkpoints: 64 KiB multiples. One envelope per ~CHECKPOINT_EVERY\n * edits, so the overhead is noise; hides the fine-grained document-size\n * trajectory.\n *\n * The hard caps mirror the hub's refusal thresholds, which count BASE64\n * chars of ciphertext (hub.ts): 256 KiB chars/envelope ⇒ 196 608 raw\n * ciphertext bytes ⇒ minus the 16-byte GCM tag ⇒ 196 592 plaintext bytes;\n * 16 MiB chars/checkpoint ⇒ 12 582 896. Padding NEVER pushes a body past\n * its cap: the bucket target clamps to the cap, and a body already over the\n * cap is left unpadded (the server refuses it exactly as it did before).\n *\n * ENGINE-VERSION COUPLING: an e5 client JSON.parses the RAW plaintext, so a\n * length-prefixed body throws there — the padded edit would no-op on e5\n * mirrors while applying on e6, a canonical fork on the first padded\n * envelope. ENGINE_VERSION e6 fences the mixed room out; do not ship one\n * side without the other.\n *\n * Media blobs stay UNPADDED (sealMediaBlob): re-supply requires\n * byte-identical deterministic re-encryption, so padding new uploads would\n * fork sha addresses against pre-e6 registrations.\n */\nconst INTENT_PAD_RUNGS = [384, 1024, 4096] as const;\nconst INTENT_PAD_STEP = 4096;\n/** 256 KiB base64 chars (hub envelope cap) → 196 608 ct bytes − 16 tag. */\nconst INTENT_PLAIN_CAP = (256 * 1024 * 3) / 4 - 16;\nconst PRESENCE_PAD_RUNGS = [128, 1024] as const;\nconst PRESENCE_PAD_STEP = 1024;\n/** Presence has no hub-side cap of its own; reuse the envelope cap as the\n * guard so padding can never be the thing that makes a frame oversized. */\nconst PRESENCE_PLAIN_CAP = INTENT_PLAIN_CAP;\nconst CHECKPOINT_PAD_STEP = 64 * 1024;\n/** 16 MiB base64 chars (hub checkpoint cap) → 12 582 912 ct bytes − 16. */\nconst CHECKPOINT_PLAIN_CAP = (16 * 1024 * 1024 * 3) / 4 - 16;\n\nfunction padBody(body: Uint8Array, rungs: readonly number[], step: number, cap: number): Uint8Array {\n const need = 4 + body.length;\n let target = rungs.find((r) => need <= r) ?? Math.ceil(need / step) * step;\n if (target > cap) target = cap; // hard guard: padding never pushes past the cap\n if (target < need) target = need; // over-cap body: leave unpadded; refused server-side as before\n const out = new Uint8Array(target); // zero-filled\n new DataView(out.buffer).setUint32(0, body.length);\n out.set(body, 4);\n return out;\n}\n\nfunction unpadBody(pt: ArrayBuffer): Uint8Array {\n const bytes = new Uint8Array(pt);\n if (bytes.length < 4) throw new Error(\"padded body: too short\");\n const len = new DataView(pt).getUint32(0);\n if (len > bytes.length - 4) throw new Error(\"padded body: bad length prefix\");\n return bytes.subarray(4, 4 + len);\n}\n\nexport function bytesToB64(bytes: Uint8Array): string {\n let bin = \"\";\n for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);\n return typeof btoa === \"function\" ? btoa(bin) : Buffer.from(bytes).toString(\"base64\");\n}\nexport function b64ToBytes(b64: string): Uint8Array {\n const bin = typeof atob === \"function\" ? atob(b64) : Buffer.from(b64, \"base64\").toString(\"binary\");\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n}\n\n/** Mint a fresh 256-bit document master key (go-live, doc 13 §1). Returned\n * base64url — the exact string that rides the URL fragment (`#k=`). */\nexport function mintDocKey(): string {\n const bytes = new Uint8Array(32);\n crypto.getRandomValues(bytes);\n return bytesToB64(bytes).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** Parse `#k=<key>` out of a URL fragment; null if absent. The PRESENCE of\n * this is what fixes the doc's mode client-side (doc 13 §6: `#k` present ⇒\n * encrypted, always — a welcome claiming otherwise is hard-refused). */\nexport function docKeyFromFragment(fragment: string): string | null {\n const m = /(?:^|[#&])k=([A-Za-z0-9_-]+)/.exec(fragment);\n return m ? m[1] : null;\n}\n\n/**\n * Derive this epoch's working keys. Share code (doc 13 §7) mixes in when\n * set: a leaked link without the code cannot decrypt anything. The code is\n * stretched by the CALLER via stretchShareCode (PBKDF2) — this function\n * just mixes; keeping stretching separate lets the UI do it once.\n */\nexport async function deriveEpochKeys(\n docKeyB64url: string,\n genesisId: string,\n stretchedCode?: Uint8Array,\n): Promise<EpochKeys> {\n const raw = b64ToBytes(docKeyB64url.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n const master = await crypto.subtle.importKey(\"raw\", raw as BufferSource, \"HKDF\", false, [\"deriveKey\"]);\n const salt = te.encode(`wordinweb-epoch:${genesisId}`);\n const mix = stretchedCode ?? new Uint8Array(0);\n const derive = (info: string) =>\n crypto.subtle.deriveKey(\n { name: \"HKDF\", hash: \"SHA-256\", salt: salt as BufferSource, info: concat(te.encode(info), mix) as BufferSource },\n master,\n { name: \"AES-GCM\", length: 256 },\n false,\n [\"encrypt\", \"decrypt\"],\n );\n return {\n kContent: await derive(\"content\"),\n kMedia: await derive(\"media\"),\n kPresence: await derive(\"presence\"),\n };\n}\n\n/** PBKDF2-SHA256 stretch of the share code (doc 13 §7): 600k iterations,\n * WebCrypto-native (no WASM dep), salt bound to the docId so a precomputed\n * table for one doc is useless for another. Deliberately NOT epoch-salted:\n * the hello must carry the proof BEFORE the welcome reveals the epoch id\n * (chicken-and-egg otherwise); epoch-binding of the KEYS happens in\n * deriveEpochKeys' HKDF salt instead, which is where it matters. */\nexport async function stretchShareCode(code: string, docId: string): Promise<Uint8Array> {\n const key = await crypto.subtle.importKey(\"raw\", te.encode(code) as BufferSource, \"PBKDF2\", false, [\"deriveBits\"]);\n const bits = await crypto.subtle.deriveBits(\n { name: \"PBKDF2\", hash: \"SHA-256\", salt: te.encode(`wordinweb-code:${docId}`) as BufferSource, iterations: 600_000 },\n key,\n 256,\n );\n return new Uint8Array(bits);\n}\n\n/** Seal an intent into an envelope under K_content (doc 13 §2). The AAD\n * binds every plaintext bookkeeping field the transform depends on. */\nexport async function sealIntent(\n kContent: CryptoKey,\n docId: string,\n genesisId: string,\n intent: Intent,\n): Promise<IntentEnvelope> {\n const iv = new Uint8Array(12);\n crypto.getRandomValues(iv);\n const aad = intentAad(docId, genesisId, intent.clientId, intent.clientSeq, intent.base);\n const body = padBody(te.encode(JSON.stringify(intent)), INTENT_PAD_RUNGS, INTENT_PAD_STEP, INTENT_PLAIN_CAP);\n const ct = await crypto.subtle.encrypt(\n { name: \"AES-GCM\", iv: iv as BufferSource, additionalData: aad as BufferSource },\n kContent,\n body as BufferSource,\n );\n return {\n clientId: intent.clientId,\n clientSeq: intent.clientSeq,\n base: intent.base,\n iv: bytesToB64(iv),\n ciphertext: bytesToB64(new Uint8Array(ct)),\n };\n}\n\n/** Open an envelope; throws on ANY tamper — wrong epoch, altered base,\n * spliced clientSeq, flipped ciphertext bit. Callers treat a throw as a\n * deterministic no-op for that seq (doc 13 §2: garbage from a malicious\n * participant no-ops identically on every honest client). */\nexport async function openIntent(\n kContent: CryptoKey,\n docId: string,\n genesisId: string,\n env: IntentEnvelope,\n): Promise<Intent> {\n const aad = intentAad(docId, genesisId, env.clientId, env.clientSeq, env.base);\n const pt = await crypto.subtle.decrypt(\n { name: \"AES-GCM\", iv: b64ToBytes(env.iv) as BufferSource, additionalData: aad as BufferSource },\n kContent,\n b64ToBytes(env.ciphertext) as BufferSource,\n );\n const intent = JSON.parse(td.decode(unpadBody(pt))) as Intent;\n // Belt-and-braces: the plaintext bookkeeping must MATCH the sealed body\n // (AAD already guarantees it — this catches a buggy honest client early).\n if (intent.clientId !== env.clientId || intent.clientSeq !== env.clientSeq || intent.base !== env.base) {\n throw new Error(\"envelope bookkeeping mismatch\");\n }\n return intent;\n}\n\n/** Seal a checkpoint bundle (doc 13 §3): AAD binds (docId, genesisId, seq)\n * so a stored checkpoint cannot be replayed at another position (F5c). The\n * body carries the docx + sidecar + the canonical docHash for joiner\n * cross-checking (blocker-2 verification). */\n/**\n * The sealed checkpoint's plaintext body (doc 13 §3). `mediaMeta` is the\n * doc-16 §6 late-join map: the declared sha (+ IV) of every registered media\n * part, so a joiner whose snapshot contains the REGISTRATIONS can also fetch\n * the bytes — the addresses live only in already-folded intents otherwise,\n * which is what made late-join media unfetchable.\n *\n * It rides INSIDE the sealed body on purpose: in an encrypted room the server\n * must not learn the document's part structure. It learns which shas exist\n * only when someone PUTs or GETs one, and nothing more.\n *\n * OPTIONAL and tolerantly read — a checkpoint sealed by an older build simply\n * has no map, and such a joiner degrades to reserving the box without being\n * able to fill it (the prior behavior). Additive, so no engine bump.\n */\nexport interface CheckpointBody {\n docx: string;\n sidecar: unknown;\n docHash: string;\n mediaMeta?: { part: string; sha: string; iv?: string }[];\n}\n\nexport async function sealCheckpoint(\n kContent: CryptoKey,\n docId: string,\n genesisId: string,\n seq: number,\n body: CheckpointBody,\n): Promise<{ iv: string; ciphertext: string }> {\n const iv = new Uint8Array(12);\n crypto.getRandomValues(iv);\n const aad = te.encode(`cp:${docId}:${genesisId}:${seq}`);\n const padded = padBody(te.encode(JSON.stringify(body)), [], CHECKPOINT_PAD_STEP, CHECKPOINT_PLAIN_CAP);\n const ct = await crypto.subtle.encrypt(\n { name: \"AES-GCM\", iv: iv as BufferSource, additionalData: aad as BufferSource },\n kContent,\n padded as BufferSource,\n );\n return { iv: bytesToB64(iv), ciphertext: bytesToB64(new Uint8Array(ct)) };\n}\n\nexport async function openCheckpoint(\n kContent: CryptoKey,\n docId: string,\n genesisId: string,\n seq: number,\n sealed: { iv: string; ciphertext: string },\n): Promise<CheckpointBody> {\n const aad = te.encode(`cp:${docId}:${genesisId}:${seq}`);\n const pt = await crypto.subtle.decrypt(\n { name: \"AES-GCM\", iv: b64ToBytes(sealed.iv) as BufferSource, additionalData: aad as BufferSource },\n kContent,\n b64ToBytes(sealed.ciphertext) as BufferSource,\n );\n return JSON.parse(td.decode(unpadBody(pt)));\n}\n\nfunction intentAad(docId: string, genesisId: string, clientId: string, clientSeq: number, base: number): Uint8Array {\n return te.encode(`in:${docId}:${genesisId}:${clientId}:${clientSeq}:${base}`);\n}\n\n/**\n * Presence AAD. The `pr:` prefix is DOMAIN SEPARATION on top of key\n * separation: even if some future change accidentally sealed presence under\n * kContent, an intent's AAD (`in:…`) and a presence AAD could never be\n * confused for one another, so a sealed intent could not be replayed as a\n * caret or vice versa. Two independent barriers, one line.\n *\n * Binds the sender because the RECEIVER builds this AAD from the server's\n * `participant` stamp — the hub's bound clientId. A server that re-attributes\n * a blob to a different participant therefore produces a decryption failure,\n * not a mislabelled caret.\n *\n * WHAT IT DELIBERATELY DOES NOT PREVENT: a same-doc, same-epoch, same-sender\n * REPLAY. A server that re-sends an older blob paints a stale caret for that\n * participant until their next update. Preventing it needs a counter or clock\n * inside the sealed body and per-sender state on every receiver; presence is\n * ephemeral, last-write-wins UI where the cost of being briefly stale is a\n * cursor in the wrong place. Accepted deliberately, not overlooked.\n */\nfunction presenceAad(docId: string, genesisId: string, clientId: string): Uint8Array {\n return te.encode(`pr:${docId}:${genesisId}:${clientId}`);\n}\n\n/** A presence payload as it crosses a BLIND relay: an opaque blob. The\n * server sees two base64 strings and the sender's id — never a coordinate. */\nexport interface SealedPresence {\n iv: string;\n ciphertext: string;\n}\n\n/** True for a payload that is sealed rather than a plaintext position. Used\n * to stay tolerant of a peer on the other side of the version fence. */\nexport function isSealedPresence(pos: unknown): pos is SealedPresence {\n return (\n !!pos &&\n typeof pos === \"object\" &&\n typeof (pos as SealedPresence).iv === \"string\" &&\n typeof (pos as SealedPresence).ciphertext === \"string\"\n );\n}\n\n/** Seal a presence position under K_presence (doc 13 / #20). */\nexport async function sealPresence(\n kPresence: CryptoKey,\n docId: string,\n genesisId: string,\n clientId: string,\n position: unknown,\n): Promise<SealedPresence> {\n const iv = new Uint8Array(12);\n crypto.getRandomValues(iv);\n const ct = await crypto.subtle.encrypt(\n {\n name: \"AES-GCM\",\n iv: iv as BufferSource,\n additionalData: presenceAad(docId, genesisId, clientId) as BufferSource,\n },\n kPresence,\n padBody(te.encode(JSON.stringify(position)), PRESENCE_PAD_RUNGS, PRESENCE_PAD_STEP, PRESENCE_PLAIN_CAP) as BufferSource,\n );\n return { iv: bytesToB64(iv), ciphertext: bytesToB64(new Uint8Array(ct)) };\n}\n\n/**\n * Open a sealed presence payload. Throws on any tamper, wrong epoch, wrong\n * document, or re-attribution to a different sender — callers treat a throw\n * as \"drop this participant's caret\", never as a session-level failure: a\n * hostile participant sealing garbage must cost its own cursor and nobody\n * else's.\n */\nexport async function openPresence(\n kPresence: CryptoKey,\n docId: string,\n genesisId: string,\n clientId: string,\n sealed: SealedPresence,\n): Promise<unknown> {\n const pt = await crypto.subtle.decrypt(\n {\n name: \"AES-GCM\",\n iv: b64ToBytes(sealed.iv) as BufferSource,\n additionalData: presenceAad(docId, genesisId, clientId) as BufferSource,\n },\n kPresence,\n b64ToBytes(sealed.ciphertext) as BufferSource,\n );\n return JSON.parse(td.decode(unpadBody(pt)));\n}\n\nfunction concat(a: Uint8Array, b: Uint8Array): Uint8Array {\n const out = new Uint8Array(a.length + b.length);\n out.set(a, 0);\n out.set(b, a.length);\n return out;\n}\n\n/**\n * Seal a media blob under K_media (doc 13 §4 / doc 16 §6). The IV is\n * RETURNED (it rides in the insertImage intent) so any holder can later\n * re-seal the same plaintext byte-identically for re-supply — deterministic\n * re-encryption of the SAME message, not nonce reuse; a fresh IV would\n * change the ciphertext and break the sha address (do NOT \"fix\" this).\n * The sha the intent commits to is over the CIPHERTEXT — verifiable by the\n * blind relay without opening anything.\n */\nexport async function sealMediaBlob(\n kMedia: CryptoKey,\n plaintext: Uint8Array,\n ivB64?: string,\n): Promise<{ blob: Uint8Array; iv: string }> {\n let iv: Uint8Array;\n if (ivB64) iv = b64ToBytes(ivB64);\n else {\n iv = new Uint8Array(12);\n crypto.getRandomValues(iv);\n }\n const ct = new Uint8Array(\n await crypto.subtle.encrypt({ name: \"AES-GCM\", iv: iv as BufferSource }, kMedia, plaintext as BufferSource),\n );\n return { blob: ct, iv: bytesToB64(iv) };\n}\n\n/** Open a media blob (GCM tag failure throws — reject, keep the skeleton). */\nexport async function openMediaBlob(kMedia: CryptoKey, blob: Uint8Array, ivB64: string): Promise<Uint8Array> {\n return new Uint8Array(\n await crypto.subtle.decrypt({ name: \"AES-GCM\", iv: b64ToBytes(ivB64) as BufferSource }, kMedia, blob as BufferSource),\n );\n}\n","import { ClientReplica } from \"./replica.js\";\nimport type { Scope } from \"./apply.js\";\nimport { DocxDocument } from \"@wordinweb/core\";\nimport { Intent, type LogEntry } from \"./intents.js\";\nimport {\n ClientMessage,\n ServerMessage,\n PROTOCOL_VERSION,\n PresencePosition,\n ParticipantProfile,\n RosterEntry,\n sanitizePresencePosition,\n} from \"./protocol.js\";\nimport type { DocBundle } from \"./bundle.js\";\nimport { MediaClient, applyMediaAddresses, type MediaTransportOptions, type MediaState } from \"./media.js\";\nimport { isSealedPresence } from \"./e2ee.js\";\n\n/**\n * A transport the connection drives: send a client message, and register a\n * handler for inbound server messages. A WebSocket adapter (or a fake, in\n * tests) implements it. The connection contains no socket code — the client\n * counterpart to the transport-free server hub (plan doc 07).\n */\nexport interface ClientTransport {\n send(msg: ClientMessage): void;\n onMessage(cb: (msg: ServerMessage) => void): void;\n}\n\nexport interface ConnectionCallbacks {\n /** The document changed (welcome loaded, a broadcast applied). */\n onChange?: () => void;\n /** A remote participant's cursor/selection moved (or cleared: null). */\n onPresence?: (participant: string, position: PresencePosition | null) => void;\n /** The server refused the connection (e.g. version mismatch). */\n onRefused?: (reason: string) => void;\n /**\n * Resume landed in a DIFFERENT epoch than the bundle's (doc 12 §5 case 2):\n * someone re-seeded while this client was away. The connection has taken\n * the server's state and did NOT replay the bundle's pending intents\n * (they belong to the old epoch — replaying them would be a silent\n * cross-epoch merge, which the fork rule forbids). The consumer decides\n * what to do with the old bundle: doc-15 lineage fast-forward/draft, or\n * pre-lineage, \"your offline copy is saved as a draft\".\n */\n onEpochChange?: (storedGenesisId: string, currentGenesisId: string) => void;\n /** The session roster changed (join/leave/rename) — full snapshot. */\n onRoster?: (roster: RosterEntry[]) => void;\n /**\n * The connection detected that the local OPTIMISTIC replica drifted from\n * the canonical document at quiescence and rebuilt it in place (encrypted\n * mode: the mirror is local ground truth, so the check costs no network).\n * The doc object was swapped and docEpoch bumped — the renderer remounts\n * exactly like any true-conflict reload. Informational: the heal already\n * happened; consumers surface/telemeter it (the B6a typist-drift class).\n */\n onSelfHeal?: (info: { seq: number; liveHash: string; canonicalHash: string }) => void;\n /**\n * A submit was DROPPED instead of sent, and the caller needs to know.\n *\n * Today the only reason is \"not-ready\": a submit arriving before the\n * welcome has nothing to apply against, no confirmed seq to base itself on,\n * and (encrypted) no key to seal with, so it cannot be honoured or queued.\n * It used to return silently — no error, no callback, no counter, no\n * replay — which made lost edits indistinguishable from edits that were\n * never made. B13 measured 403 submits, 172 envelopes on the wire and 172\n * in the document, with a completely clean server log; a channel that\n * consumes intents without a trace is what that requires.\n *\n * NOT `onRefused`: that one means the SERVER refused the connection and the\n * demo renders a \"please refresh\" screen on it. This is non-fatal and\n * per-intent — the connection stays perfectly usable.\n */\n onSubmitDropped?: (reason: \"not-ready\") => void;\n /**\n * Something threw on an async path that has nowhere to return an error —\n * a seal, a transport send, a persistence write. These used to die in a\n * serial chain's `.catch(() => {})`, which is how B13's lost edits and a\n * silently-failing durable copy both stayed invisible.\n *\n * DELIBERATELY SEPARATE from onRefused (\"the server refused the\n * connection\", which tears the session down) and onSubmitDropped (\"an edit\n * was lost before it could be sent\"). Each of the three now means exactly\n * one thing; conflating failure channels is precisely how the bugs this\n * arc is cleaning up managed to hide.\n *\n * `where` is a fixed code-site label, never interpolated with session data.\n */\n onError?: (info: { where: string; error: unknown }) => void;\n /**\n * Resume landed in a different epoch that is a STRICT DESCENDANT of this\n * client's stored head (doc 15 §1 fast-forward): the seed's lineage\n * contains our (genesisId, hash) at ≥ our seq, so adopting the server's\n * state loses nothing — no draft, no banner noise. The consumer should\n * still bank the superseded bundle recoverable-not-gone (version ring /\n * a superseded slot) per doc 15's fabricated-lineage mitigation.\n */\n onFastForward?: (fromGenesisId: string, toGenesisId: string) => void;\n /**\n * The SERVER is going to end this session, and this is the grace period\n * before it does (server lifecycle arc). Render a countdown from `inMs`.\n *\n * - `idle` nobody has done anything for the idle window. Cancellable:\n * any qualifying activity — an accepted edit, an admin\n * action, a media transfer, someone joining, but NEVER\n * presence — resets the clock and fires\n * {@link onSessionWarningCleared}. Typing genuinely saves the\n * session, so the countdown is worth showing prominently.\n * - `lifetime` the room hit its absolute age cap. NOT cancellable by\n * anything: the deadline was fixed when the room was created,\n * which is the point (an activity-based clock is exactly what\n * a keepalive script defeats). The document is not lost — the\n * local bundle re-seeds it into a new epoch.\n *\n * `inMs` is the measured remainder at send time, not a configured constant,\n * so counting down from it lands on the real ending.\n *\n * NOT `onRefused`: nothing has been refused yet and the session is fully\n * usable during the grace. The kick that eventually follows arrives as\n * `onRefused(\"idle-timeout\")` / `onRefused(\"session-expired\")`.\n */\n onSessionWarning?: (info: { reason: \"idle\" | \"lifetime\"; inMs: number }) => void;\n /**\n * A previously announced deadline is no longer approaching — take the\n * countdown down. Only ever `idle`, because the lifetime cap cannot be\n * cancelled; the narrow type means a consumer cannot write a branch the\n * server is incapable of producing.\n */\n onSessionWarningCleared?: (info: { reason: \"idle\" }) => void;\n /**\n * ONE OF THIS CLIENT'S OWN INTENTS WAS REJECTED by canonical validation —\n * it will never apply, anywhere, and the edit is gone.\n *\n * This channel exists because of a bug that cost a user their photos. The\n * validator carried a stale image-size bound, so every large image was\n * rejected as \"bad size\" — and the failure was completely invisible. The\n * reason is worth stating exactly, because it inverts the usual assumption:\n * a rejection is sequenced, so EVERY REPLICA AGREES the intent is dead.\n * Nothing diverges, nothing throws, no upload fails. Our entire invariant\n * arsenal is built to detect disagreement, and this bug's signature was\n * agreement. The image simply never appeared.\n *\n * A wrong bound can happen again; next time somebody should SEE it. Fired\n * only for intents THIS client authored — another participant's rejection is\n * their business and not actionable here.\n *\n * `reason` is the validator's fixed vocabulary (`insertImage: bad size`,\n * `invalid base`, …) — never session content, so it is safe to surface.\n */\n onIntentRejected?: (info: { reason: string; clientSeq: number }) => void;\n /** Media re-supply control (doc 16 §5): the room asks who holds `sha`;\n * a holder answers via `mediaHave` and uploads over HTTP when chosen. */\n onMediaRequest?: (sha: string) => void;\n /** A needed blob became fetchable / definitively unavailable. */\n onMediaReady?: (sha: string) => void;\n onMediaUnavailable?: (sha: string) => void;\n /** A pending part's placeholder state changed (fetching/waiting/\n * unavailable) — the renderer shows the \"unavailable\" affordance off this. */\n onMediaState?: (part: string, state: MediaState) => void;\n}\n\nfunction base64ToBytes(b64: string): Uint8Array {\n const bin = typeof atob === \"function\" ? atob(b64) : Buffer.from(b64, \"base64\").toString(\"binary\");\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n}\n\n/**\n * Client-side session: joins a document, applies canonical broadcasts to a\n * ClientReplica (optimistic apply + reconciliation), and relays presence.\n * The editor layer submits local intents and reads `replica.doc`.\n */\nexport class CollabConnection {\n private replica: ClientReplica | null = null;\n private clientSeq = 0;\n /** Bumps only when reconciliation RELOADED the document (a true conflict) —\n * the renderer re-mounts on this, but updates in place otherwise (no flash\n * for the common non-conflicting edits). */\n docEpoch = 0;\n /** Per-client id allocator for carried node ids (split/format-range/insert):\n * a client-specific base block keeps concurrently-allocated ids disjoint\n * across clients so they never collide. */\n private idCounter = 0;\n private idBase = -1;\n\n /** The epoch id of the session this connection joined (from the welcome).\n * The resume layer compares it against the bundle's stored genesisId:\n * same ⇒ seamless rejoin; different ⇒ someone re-seeded while away\n * (doc 12 §5 case 2 — take server state, keep the local copy; the doc-15\n * lineage check decides fast-forward vs draft). Null until welcomed. */\n genesisId: string | null = null;\n /** Session encryption mode from the welcome (doc 13 §6). The E2EE layer\n * hard-refuses a value contradicting the link's `#k` fragment. */\n mode: \"plaintext\" | \"encrypted\" | null = null;\n /**\n * The relay's per-blob upload limit in bytes, from the welcome — so a file\n * can be checked BEFORE it is uploaded rather than after a 413.\n *\n * `null` means the server did not publish one (an older host). Callers must\n * treat that as \"skip the pre-check\", NOT as \"no limit\" and not as a\n * default they invent: the server still enforces its real limit either way,\n * and a client guessing a larger number would promise the user an upload\n * that cannot succeed.\n */\n mediaMaxBlobBytes: number | null = null;\n\n /** Out-of-band media transfer (doc 16 §5). Null until `httpBase` is\n * supplied — without a relay origin a client simply has no media duties\n * (every existing caller keeps working unchanged). */\n media: MediaClient | null = null;\n /** The room this connection joined; media URLs are per-doc. */\n private docId = \"\";\n\n constructor(\n private transport: ClientTransport,\n private clientId: string,\n private cb: ConnectionCallbacks = {},\n /** Origin serving the doc-16 §3 media routes; enables media transfer. */\n mediaOpts?: MediaTransportOptions,\n ) {\n this.transport.onMessage((msg) => this.onServer(msg));\n if (mediaOpts) {\n this.media = new MediaClient(mediaOpts, () => this.replica?.doc ?? null, {\n // Media installs mutate the doc outside the replica's counter — bump\n // the render signal so the skeleton repaints (see docVersion).\n onChange: () => { this.docVersionBase++; this.cb.onChange?.(); },\n onState: (part, state) => this.cb.onMediaState?.(part, state),\n need: (sha) => this.mediaNeed(sha),\n have: (shas) => this.mediaHave(shas),\n });\n }\n }\n\n /** Upload bytes and get the intent's media fields, or null if the relay\n * refused — the caller must not emit an insertImage in that case. */\n async uploadMedia(plaintext: Uint8Array): Promise<{ blobSha: string; bytesLen: number; iv?: string } | null> {\n return this.media ? this.media.upload(this.docId, plaintext) : null;\n }\n\n /** Replace the callbacks (used by bindEditor to attach after construction). */\n setCallbacks(cb: ConnectionCallbacks): void {\n this.cb = cb;\n }\n\n /** Allocate `n` fresh carried node ids in this client's disjoint block. */\n allocIds(n: number): number[] {\n if (this.idBase < 0) {\n // A large per-client base derived from the client id, so two clients'\n // allocations never overlap (blocks of 1e7 ids each).\n let h = 0;\n for (let i = 0; i < this.clientId.length; i++) h = (h * 31 + this.clientId.charCodeAt(i)) >>> 0;\n this.idBase = 1_000_000_000 + (h % 100_000) * 10_000_000;\n }\n const out: number[] = [];\n for (let i = 0; i < n; i++) out.push(this.idBase + this.idCounter++);\n return out;\n }\n\n /**\n * Join a document. The server replies with a welcome (snapshot + sidecar +\n * tail). The hello carries this connection's clientId — the hub binds it to\n * the socket and refuses submits under any other id (doc 11 decision 8).\n * `takeover: true` claims the identity from an existing live connection\n * (the doc-12 §7 \"use here instead\" path for a second same-profile tab);\n * without it, a duplicate join is refused `already-open`.\n */\n join(docId: string, token?: string, opts?: { takeover?: boolean; profile?: ParticipantProfile; codeProof?: string; ownerToken?: string }): void {\n this.docId = docId;\n this.transport.send({\n t: \"hello\",\n protocolVersion: PROTOCOL_VERSION,\n docId,\n clientId: this.clientId,\n takeover: opts?.takeover,\n token,\n sinceSeq: 0,\n profile: opts?.profile,\n codeProof: opts?.codeProof,\n ownerToken: opts?.ownerToken,\n });\n }\n\n /** Owner admin op (doc 14 §2.5): honored only if this connection proved\n * the owner token at hello — otherwise the server refuses `not-owner`. */\n admin(action:\n | { op: \"kick\"; clientId: string }\n | { op: \"readOnly\"; on: boolean }\n | { op: \"setRole\"; clientId: string; role: \"editor\" | \"viewer\" }): void {\n this.transport.send({ t: \"admin\", action });\n }\n\n /** The latest roster snapshot (doc 14 §2); empty until the first fan-out. */\n roster: RosterEntry[] = [];\n\n /**\n * Attribution layer 1 (doc 14 §3): the canonical log IS the attribution\n * record — every applied entry carries its author's bound clientId. This\n * is the bounded client-side derivation an activity panel renders\n * (clientId → roster name/color). Newest last, capped, derived state:\n * never persisted, dies with the connection (zero custody).\n */\n activity: { seq: number; clientId: string; kind: string }[] = [];\n private static ACTIVITY_CAP = 100;\n\n private recordActivity(entries: { seq: number; kind: string; intent?: Intent }[]): void {\n for (const e of entries) {\n if (e.kind === \"applied\" && e.intent) {\n this.activity.push({ seq: e.seq, clientId: e.intent.clientId, kind: e.intent.kind });\n }\n }\n if (this.activity.length > CollabConnection.ACTIVITY_CAP) {\n this.activity.splice(0, this.activity.length - CollabConnection.ACTIVITY_CAP);\n }\n }\n\n /** Ask the room for a blob this replica needs (doc 16 §5.2). */\n mediaNeed(sha: string): void {\n this.transport.send({ t: \"media-need\", sha });\n }\n /** Volunteer holdings (reply to a request, or after a welcome whose\n * mediaNeeded intersects local media — §5.4). */\n mediaHave(shas: string[]): void {\n if (shas.length) this.transport.send({ t: \"media-have\", shas });\n }\n /** Shas this replica can re-supply: metadata of parts whose bytes are\n * PRESENT (pending parts are exactly what we can't serve). */\n heldMediaShas(): string[] {\n const doc = this.replica?.doc;\n if (!doc) return [];\n const out: string[] = [];\n for (const [part, meta] of doc.mediaMeta) {\n if (doc.mediaStatus(part) === \"ready\") out.push(meta.sha);\n }\n return out;\n }\n\n /** Rename/recolor this participant mid-session. The server sanitizes and\n * fans out the updated roster; the local copy updates on that echo (no\n * optimistic roster — a 1-RTT lag on your own rename is imperceptible and\n * keeps one code path). */\n setProfile(profile: ParticipantProfile): void {\n this.transport.send({ t: \"profile\", profile });\n }\n\n /** Bundle being resumed from (set by resume(), consumed at welcome). */\n private resuming: DocBundle | null = null;\n\n /**\n * Rejoin a document from a persisted bundle (doc 12 §5). Restores the\n * clientSeq watermark FIRST — a fresh counter would reuse already-\n * sequenced (clientId, clientSeq) keys and the server would dedup this\n * client's NEW edits as re-sends (silent edit loss). The welcome decides\n * the case: same epoch ⇒ replay pending (below); different ⇒\n * onEpochChange, pending withheld.\n */\n resume(bundle: DocBundle, token?: string, opts?: { profile?: ParticipantProfile; codeProof?: string; ownerToken?: string }): void {\n this.clientSeq = Math.max(this.clientSeq, bundle.clientSeq);\n this.resuming = bundle;\n this.docId = bundle.docId;\n this.transport.send({\n profile: opts?.profile,\n codeProof: opts?.codeProof,\n t: \"hello\",\n protocolVersion: PROTOCOL_VERSION,\n docId: bundle.docId,\n clientId: this.clientId,\n // Resume IS the zombie-takeover case (doc 12 §7): after a crash or\n // refresh, the previous socket for this identity may still be inside\n // the 60s grace window server-side. This connection is that identity's\n // continuation — claiming it is correct, and the incumbent (if any)\n // is a dead tab by definition of \"we are resuming from the bundle\".\n takeover: true,\n token,\n sinceSeq: bundle.confirmedSeq,\n genesisId: bundle.genesisId,\n ownerToken: opts?.ownerToken,\n });\n }\n\n /**\n * The current durable state as a doc-12 bundle, or null before welcome.\n * `savedAt` is stamped by the persister at write time (clock injection —\n * this module never reads Date.now itself).\n */\n exportBundle(docId: string): DocBundle | null {\n if (!this.replica || this.genesisId === null) return null;\n return {\n docId,\n genesisId: this.genesisId,\n ...this.replica.exportBundleState(),\n clientSeq: this.clientSeq,\n savedAt: 0,\n lineage: [], // the persister maintains the chain across writes\n };\n }\n\n async exportBundleAsync(docId: string): Promise<DocBundle | null> {\n if (!this.replica || this.genesisId === null) return null;\n return {\n docId,\n genesisId: this.genesisId,\n ...await this.replica.exportBundleStateAsync(),\n clientSeq: this.clientSeq,\n savedAt: 0,\n lineage: [],\n };\n }\n\n /** The live document (null until welcome). The editor renders this. */\n get doc() {\n return this.replica?.doc ?? null;\n }\n\n /** Accumulates doc changes across replica rebuilds (welcome), so the getter\n * below stays monotonic per connection. */\n private docVersionBase = 0;\n /**\n * Counts changes TO THE RENDERED DOCUMENT — the repaint signal, as opposed\n * to onChange (which also fires for pure bookkeeping such as a tracked own\n * echo). The editor-driven typing path mutates and paints the doc itself,\n * so its submit + echo leave this untouched; remote applies, optimistic\n * canonical applies (toolbar ops), reloads, and media installs advance it.\n * The react layer repaints on THIS, not on onChange — repainting on\n * onChange queued a whole-document relayout per keystroke, catastrophic\n * past the background-layout page threshold.\n */\n get docVersion(): number {\n return this.docVersionBase + (this.replica?.docVersion ?? 0);\n }\n\n /** docVersionBase at the last takeRenderScope — a base bump between takes\n * (welcome doc replacement, media install) has no narrow scope. */\n private takenDocVersionBase = 0;\n\n /**\n * Drain the dirty scope behind the docVersion movement since the last take\n * — what the repaint answering `docVersion` must relayout. Replica applies\n * carry their per-intent scope; connection-level bumps (a replaced doc\n * object, media pixels landing) report document scope. Null means NOTHING\n * was recorded since the last take — the caller already painted everything\n * and may skip the repaint.\n */\n takeRenderScope(): Scope | null {\n const replicaScope = this.replica?.takeRenderScope() ?? null;\n if (this.docVersionBase !== this.takenDocVersionBase) {\n this.takenDocVersionBase = this.docVersionBase;\n return { kind: \"doc\" };\n }\n return replicaScope;\n }\n\n get ready(): boolean {\n return this.replica !== null;\n }\n\n /** Un-confirmed local intents in flight — the drained-replay discipline\n * (doc 15 §2 / rebase.ts) polls this to submit one intent at a time. */\n get pendingCount(): number {\n return this.replica?.pendingCount ?? 0;\n }\n\n /**\n * Submit a local edit. The caller supplies the intent minus its wire\n * bookkeeping (clientId/clientSeq/base) — the connection fills those from\n * the current confirmed seq and applies it optimistically before sending.\n */\n submit(intent: Omit<Intent, \"clientId\" | \"clientSeq\" | \"base\">): void {\n this.submitFull(intent, /*preApplied*/ false);\n }\n\n /** Submit an intent whose mutation the caller ALREADY performed on this\n * connection's live doc (the editor-driven path: DocxEditor applies the\n * command to `conn.doc` and then emits the intent). Skips the optimistic\n * re-apply — applying twice doubled every keystroke — but tracks pending and\n * sends identically, so echoes and reconciliation work unchanged. */\n submitPreApplied(intent: Omit<Intent, \"clientId\" | \"clientSeq\" | \"base\">): void {\n this.submitFull(intent, /*preApplied*/ true);\n }\n\n /** Submits dropped because the connection was not ready (see\n * onSubmitDropped). Exposed like `selfHeals` so a harness or a UI can read\n * it: when a run loses intents, this number says how many died HERE rather\n * than anywhere else on the path. */\n droppedPreReady = 0;\n\n private submitFull(intent: Omit<Intent, \"clientId\" | \"clientSeq\" | \"base\">, preApplied: boolean): void {\n if (!this.replica) {\n // LOUD, NOT LOSSY. Nothing can be done with this intent — there is no\n // replica to apply it to and no confirmed seq to base it on — but\n // vanishing silently is what made this class invisible. Buffering was\n // considered and rejected: a pre-welcome intent's `base` is meaningless\n // until the welcome sets confirmedSeq, and its carried ids come from an\n // allocation the connection cannot make yet, so a \"queue\" would really\n // be a rebase. The product answer is upstream — the editor stays\n // read-only until ready — and this is the backstop that makes a\n // violation of that impossible to miss.\n this.droppedPreReady++;\n this.cb.onSubmitDropped?.(\"not-ready\");\n return;\n }\n const full = {\n ...intent,\n clientId: this.clientId,\n clientSeq: ++this.clientSeq,\n base: this.replica.confirmedSeq,\n } as Intent;\n if (preApplied) this.replica.trackLocal(full);\n else this.replica.submitLocal(full);\n this.transport.send({ t: \"submit\", intent: full });\n this.cb.onChange?.();\n }\n\n /** Broadcast this client's cursor/selection (ephemeral). Selection ranges\n * are clamped on the way OUT too, so an over-long selection costs the room\n * a bounded payload rather than every receiver a discard. */\n setPresence(position: PresencePosition | null): void {\n this.transport.send({ t: \"presence\", position: sanitizePresencePosition(position) });\n }\n\n /**\n * Surface any of OUR OWN intents that canonical validation rejected.\n *\n * A rejection is sequenced and agreed by every replica, so it produces no\n * divergence to notice — which is exactly how a stale image-size bound\n * silently ate a user's photos. See ConnectionCallbacks.onIntentRejected.\n */\n private reportRejections(entries: LogEntry[]): void {\n if (!this.cb.onIntentRejected) return;\n for (const e of entries) {\n if (e.kind === \"rejected\" && e.clientId === this.clientId) {\n this.cb.onIntentRejected({ reason: e.reason, clientSeq: e.clientSeq });\n }\n }\n }\n\n private onServer(msg: ServerMessage): void {\n switch (msg.t) {\n case \"welcome\": {\n // The snapshot represents seq `msg.seq`; the replica's confirmed\n // baseline is that seq, and any tail entries are strictly after it.\n // The sidecar rides with the snapshot (round-4 F10) so the replica's\n // id table matches the server's even across split-created ids.\n this.docVersionBase += (this.replica?.docVersion ?? 0) + 1; // new doc object ⇒ repaint\n this.replica = new ClientReplica(base64ToBytes(msg.snapshot), msg.sidecar);\n this.replica.confirmedSeq = msg.seq;\n this.genesisId = msg.genesisId;\n this.mode = msg.mode;\n this.mediaMaxBlobBytes = msg.mediaMaxBlobBytes ?? null;\n if (msg.tail.length) this.replica.receive(msg.tail);\n // Doc 16 §5.3: restore media ADDRESSES from the resumed bundle —\n // the welcome snapshot carries pixels but not shas (metadata is\n // in-memory on the doc); without this a resumed holder could not\n // volunteer or verify. Imported before the §5.4 intersection below.\n if (this.resuming?.mediaMeta) {\n // The welcome snapshot carries the REGISTRATION; the relay may\n // long since have evicted the bytes (that's the re-supply\n // premise) — but this holder's bundle has them. Restore pixels\n // from the bundle's own zip so this client renders immediately\n // AND can serve re-supply for everyone else.\n const held = DocxDocument.load(this.resuming.confirmedBytes);\n for (const [part, meta] of this.resuming.mediaMeta) {\n this.replica.doc.mediaMeta.set(part, meta);\n const bytes = held.media(part);\n if (bytes && this.replica.doc.mediaStatus(part) === \"pending\") {\n this.replica.doc.installMedia(part, bytes);\n }\n }\n }\n // Doc 16 §6 late-join: the snapshot carries the registrations, this\n // carries their ADDRESSES — without it a joiner reserves each image's\n // box and can never fill it. Applied before the eager fetch below.\n if (this.replica) applyMediaAddresses(this.replica.doc, msg.media);\n // §5.4: a joining holder resurrects \"unavailable\" media — intersect\n // the room's needs with local holdings and volunteer. Routed through\n // the MediaClient so the plaintext and encrypted connections share\n // ONE implementation of every media duty (they had drifted to fully\n // built and entirely absent).\n if (this.media) this.media.volunteer(msg.mediaNeeded);\n else if (msg.mediaNeeded?.length) {\n const held = new Set(this.heldMediaShas());\n this.mediaHave(msg.mediaNeeded.filter((sha) => held.has(sha)));\n }\n this.recordActivity(msg.tail as never);\n\n // Resume epilogue (doc 12 §5). Case 1 — same epoch: replay the\n // bundle's pending intents by RE-SENDING them, untracked. This is\n // deliberately fire-and-observe: an intent the server already\n // sequenced pre-crash dedups to its old entry (seq ≤ welcome.seq,\n // filtered as stale — it's already inside the snapshot); one it\n // never saw sequences fresh and arrives as an ordinary remote\n // broadcast this replica applies in place. Either way the edit ends\n // up in the doc exactly once, with zero client-side bookkeeping —\n // the (clientId, clientSeq) dedup is the whole mechanism.\n // Case 2 — different epoch: someone re-seeded while we were away;\n // pending belongs to the OLD epoch and is withheld (replaying it\n // would silently merge across epochs — the fork rule forbids it);\n // the consumer is told and decides (draft / doc-15 lineage).\n const resumed = this.resuming;\n this.resuming = null;\n if (resumed) {\n if (resumed.genesisId === msg.genesisId) {\n for (const intent of resumed.pending) this.transport.send({ t: \"submit\", intent });\n } else if (isAncestorOf(resumed, msg.lineage)) {\n // Doc 15 fast-forward: our head is in the seed's chain at >= our\n // seq with a MATCHING hash — the new epoch strictly extends what\n // we have; adopt silently. (Hash equality is the ancestry proof;\n // a fabricated chain without our real hash fails this check and\n // falls through to the fork path below.)\n this.cb.onFastForward?.(resumed.genesisId, msg.genesisId);\n } else {\n this.cb.onEpochChange?.(resumed.genesisId, msg.genesisId);\n }\n }\n this.cb.onChange?.();\n // Doc 16 §5.2: a welcome snapshot can arrive with pending parts\n // (the relay may have evicted bytes the registration still names).\n void this.media?.fetchPending(this.docId);\n return;\n }\n case \"broadcast\": {\n this.replica?.receive(msg.entries);\n this.reportRejections(msg.entries);\n this.recordActivity(msg.entries as never);\n if (this.replica?.reloaded) this.docEpoch++;\n this.cb.onChange?.();\n // EAGER FETCH (doc 16 §5.2 step 2): an applied insertImage leaves a\n // hole; pulling it immediately is what keeps bundles complete and\n // makes re-supply the rare path rather than the normal one.\n void this.media?.fetchPending(this.docId);\n return;\n }\n case \"presence\": {\n // A SEALED payload in a plaintext room (#20). Unreachable in a\n // consistent room — sealing is what the encrypted connection does —\n // so this is a peer on the wrong side of the mode boundary. Ignore it\n // rather than hand a blob to the renderer: there is no key here to\n // open it with, and rendering `{iv, ciphertext}` as coordinates would\n // be a crash or a cursor at NaN.\n if (isSealedPresence(msg.position)) return;\n // Another participant's coordinates, relayed unvalidated by the hub:\n // strip malformed selection ranges before anything renders them.\n this.cb.onPresence?.(msg.participant, sanitizePresencePosition(msg.position));\n return;\n }\n case \"roster\": {\n this.roster = msg.roster;\n this.cb.onRoster?.(msg.roster);\n return;\n }\n case \"session-warning\": {\n this.cb.onSessionWarning?.({ reason: msg.reason, inMs: msg.inMs });\n return;\n }\n case \"session-warning-cleared\": {\n this.cb.onSessionWarningCleared?.({ reason: msg.reason });\n return;\n }\n case \"media-request\": {\n // \"Who has this?\" — volunteer if we do (doc 16 §5.3).\n this.media?.answerRequest(msg.sha);\n this.cb.onMediaRequest?.(msg.sha);\n return;\n }\n case \"media-ready\": {\n void this.media?.onReady(this.docId, msg.sha);\n this.cb.onMediaReady?.(msg.sha);\n return;\n }\n case \"media-unavailable\": {\n this.media?.onUnavailable(msg.sha);\n this.cb.onMediaUnavailable?.(msg.sha);\n return;\n }\n case \"media-upload\": {\n // Chosen as the re-supplier: PUT our copy over HTTP (bytes never\n // ride the WS). Plaintext mode uploads the stored pixels as-is.\n void this.media?.resupply(this.docId, msg.sha);\n this.cb.onMediaRequest?.(msg.sha);\n return;\n }\n case \"refused\": {\n if (msg.reason === \"rate-limit\") {\n // Throttled, not failed: re-drive the WHOLE pending queue after\n // backoff — chained bursts strand everything behind one refused\n // op, and dedup by (clientId, clientSeq) makes full-queue\n // resends harmless. One timer at a time; backoff doubles per\n // refusal, resets on drain (checked at fire time).\n if (this.rlRedriveTimer !== null) return;\n this.rlRedriveBackoffMs = Math.min(this.rlRedriveBackoffMs === 0 ? 300 : this.rlRedriveBackoffMs * 2, 5000);\n this.rlRedriveTimer = setTimeout(() => {\n this.rlRedriveTimer = null;\n if (!this.replica) return;\n const queue = this.replica.pendingCopies();\n if (queue.length === 0) {\n this.rlRedriveBackoffMs = 0;\n return;\n }\n for (const body of queue) {\n this.transport.send({ t: \"submit\", intent: { ...body, base: this.replica.confirmedSeq } as Intent });\n }\n }, this.rlRedriveBackoffMs);\n return;\n }\n this.cb.onRefused?.(msg.reason);\n return;\n }\n }\n }\n\n private rlRedriveTimer: ReturnType<typeof setTimeout> | null = null;\n private rlRedriveBackoffMs = 0;\n}\n\n/** Doc 15 §1 ancestry check: the rejoiner's own recorded head must appear\n * in the seed's lineage — same epoch, at least our seq, and the HASH of the\n * state we hold. O(chain), no dates involved (clocks are untrusted). */\nfunction isAncestorOf(\n bundle: DocBundle,\n seedLineage: { genesisId: string; seq: number; docHash: string }[] | undefined,\n): boolean {\n if (!seedLineage) return false;\n const ownHead = bundle.lineage?.[bundle.lineage.length - 1];\n if (!ownHead) return false;\n return seedLineage.some(\n (h) => h.genesisId === ownHead.genesisId && h.seq >= ownHead.seq && h.docHash === ownHead.docHash,\n );\n}\n","import { DocxDocument, StableIds, resolveRunOffset } from \"@wordinweb/core\";\nimport { Intent, IntentBody } from \"./intents.js\";\n\n/**\n * Collaborative undo via rebased inverses (plan doc 03 Phase 8). The inverse\n * of an applied intent is computed at apply time (when the pre-state is known)\n * and stored. Undo submits that inverse with `base` set to the ORIGINAL\n * intent's sequence number, so the existing canonical transform rebases it\n * against every intent sequenced since — which is exactly what makes undo\n * correct under concurrency (the flaw round-2 F5 identified in the\n * inverse-at-fixed-state designs is resolved by rebasing through the log,\n * ProseMirror-style).\n *\n * Returns null for intents whose inverse isn't defined yet (formatting/table/\n * comment undo needs prior-state capture — a documented extension); those are\n * simply not undoable for now.\n */\nexport function invertIntent(doc: DocxDocument, ids: StableIds, intent: Intent): IntentBody | null {\n switch (intent.kind) {\n case \"insertText\": {\n // Inverse: delete the text that was inserted.\n return {\n kind: \"deleteText\",\n blockId: intent.at.blockId,\n runId: intent.at.runId,\n start: intent.at.offset,\n end: intent.at.offset + intent.text.length,\n };\n }\n case \"deleteText\": {\n // Inverse: re-insert the removed text — capture it from the live run\n // BEFORE the delete applies (the caller invokes invert pre-apply).\n // Wire offsets are cumulative within the run (checkpoint B1): resolve\n // to the concrete w:t and slice locally.\n const runEl = ids.elOf(intent.runId);\n if (!runEl) return null;\n const hit = resolveRunOffset(runEl, intent.start);\n if (!hit) return null;\n const localEnd = hit.offset + (intent.end - intent.start);\n if (localEnd > hit.t.text.length) return null; // cross-w:t range: apply rejects it too\n const removed = hit.t.text.slice(hit.offset, localEnd);\n if (removed.length === 0) return null;\n return {\n kind: \"insertText\",\n at: { blockId: intent.blockId, runId: intent.runId, offset: intent.start },\n text: removed,\n };\n }\n case \"splitParagraph\": {\n // Inverse: merge the newly created paragraph back into its predecessor.\n return { kind: \"mergeParagraph\", blockId: intent.newBlockId };\n }\n case \"insertImage\": {\n // Inverse: remove the drawing's carrier run. The apply gives the newly\n // created carrier the FIRST carried id (the same shape insertShape\n // produces), so the inverse addresses it without a new wire kind.\n //\n // The media PART stays registered and its bytes stay in the package —\n // undo is a document edit, not a relay operation. Re-doing the same\n // insert would re-upload to the same content address and hit the\n // relay's already-present path, so the orphaned registration costs a\n // dead relationship, never a wrong image.\n const carrier = intent.nodeIds?.[0];\n if (carrier === undefined) return null;\n return { kind: \"removeDrawing\", runId: carrier };\n }\n // Formatting / table / comment / merge inverses need prior-state capture\n // (was-bold?, the merged paragraph's structure, …) — a documented\n // extension; not undoable yet.\n default:\n return null;\n }\n}\n","import { Intent } from \"./intents.js\";\n\n/**\n * Structural validation of an inbound intent BEFORE it is transformed/applied\n * (plan doc 06 \"validate before sequencing\"; doc 11 F9 per-intent caps). This\n * bounds per-intent work and rejects malformed/oversized intents — a cheap,\n * deterministic guard that runs on the sequencer's hot path. It is NOT the\n * document-level check (id resolution happens at apply); it only rejects\n * intents that are ill-formed on their face.\n */\n\nexport interface IntentLimits {\n /** Max characters in a single insert (a paste is many intents, not one). */\n maxInsertLength: number;\n /** Max characters a single delete may remove. */\n maxDeleteLength: number;\n /** Max length of a comment body. */\n maxCommentLength: number;\n /** Max serialized length of pasted OOXML (before the structural validator). */\n maxPasteBytes: number;\n}\n\n/**\n * ABSOLUTE ceiling on an `insertImage`'s declared byte length.\n *\n * DELIBERATELY NOT A FIELD OF {@link IntentLimits}, unlike every other bound\n * here, and that asymmetry is the point. `validateIntent` runs on BOTH sides:\n * the server's DocumentSession is the plaintext authority, and an encrypted\n * client instantiates the same DocumentSession as its local mirror. The single\n * production call site passes no limits argument, so both sides evaluate\n * identical bounds — that structural equality, not discipline, is why\n * canonical forms agree.\n *\n * A per-deployment bound would put a CONFIG VALUE on both sides of that\n * equality. A client that joined an older server, or across a config change,\n * would disagree with the sequencer about whether an intent is `applied` or\n * `rejected`, and in an encrypted room — where every client derives canon\n * locally — that is a silent fork. A module constant has no parameter anyone\n * can get wrong.\n *\n * So this is a PROTOCOL-LEVEL SANITY BOUND, not deployment policy. The policy\n * is the relay's `WW_MEDIA_MAX_BLOB_BYTES`, which the server clamps to this\n * ceiling so it is always the binding limit.\n *\n * HISTORY, because the failure hid for an instructive reason: this was\n * `10 * 1024 * 1024`, correct while the media cap was a fixed 10 MB. When the\n * cap became configurable the two were never reconciled, and every image\n * between them died in an invisible window — the intent was REJECTED\n * identically on every replica, so nothing diverged, no upload failed, and no\n * error surfaced. The image just never appeared. Production's 5 MB cap sat\n * below the old ceiling, so the window was empty there; it opened only in dev,\n * where the cap was raised to 50 MB, and it swallowed the owner's 16-26 MB\n * photos.\n */\nexport const MAX_IMAGE_BYTES = 256 * 1024 * 1024;\n\nexport const DEFAULT_INTENT_LIMITS: IntentLimits = {\n maxInsertLength: 100_000,\n maxDeleteLength: 1_000_000,\n maxCommentLength: 20_000,\n maxPasteBytes: 2_000_000,\n};\n\ninterface ChartShape {\n type: unknown; title?: unknown; categories: unknown; series: unknown;\n}\n/** Shared chart-payload validation (insertChart + setChartData carry the same\n * shape): a positive type allowlist plus size/element bounds. */\nfunction chartError(c: ChartShape, who: string): string | null {\n if (typeof c !== \"object\" || c === null) return `${who}: bad chart`;\n if (![\"column\", \"bar\", \"line\", \"pie\"].includes(c.type as string)) return `${who}: bad type`;\n if (!Array.isArray(c.categories) || c.categories.length === 0 || c.categories.length > 100) return `${who}: bad categories`;\n if (c.categories.some((x) => typeof x !== \"string\" || x.length > 200)) return `${who}: bad category`;\n if (c.title !== undefined && (typeof c.title !== \"string\" || c.title.length > 200)) return `${who}: bad title`;\n if (!Array.isArray(c.series) || c.series.length === 0 || c.series.length > 24) return `${who}: bad series`;\n for (const s of c.series as { name: unknown; values: unknown }[]) {\n if (typeof s !== \"object\" || s === null || typeof s.name !== \"string\" || s.name.length > 200) return `${who}: bad series name`;\n if (!Array.isArray(s.values) || s.values.length > 100 || s.values.some((v) => typeof v !== \"number\" || !Number.isFinite(v))) return `${who}: bad series values`;\n }\n return null;\n}\n\n/** Shared SmartArt-payload validation (insertSmartArt + setSmartArtData). */\nfunction smartArtError(a: { layout: unknown; items: unknown }, who: string): string | null {\n if (typeof a !== \"object\" || a === null) return `${who}: bad smartArt`;\n if (![\"process\", \"cycle\", \"hierarchy\", \"list\"].includes(a.layout as string)) return `${who}: bad layout`;\n if (!Array.isArray(a.items) || a.items.length === 0 || a.items.length > 50) return `${who}: bad items`;\n if (a.items.some((x) => typeof x !== \"string\" || x.length > 500)) return `${who}: bad item`;\n return null;\n}\n\n/** Returns a rejection reason, or null if the intent is well-formed. */\nexport function validateIntent(intent: Intent, limits: IntentLimits = DEFAULT_INTENT_LIMITS): string | null {\n const nonNegInt = (n: number) => Number.isInteger(n) && n >= 0;\n switch (intent.kind) {\n case \"insertText\":\n if (typeof intent.text !== \"string\") return \"insertText: text not a string\";\n if (intent.text.length === 0) return \"insertText: empty\";\n if (intent.text.length > limits.maxInsertLength) return \"insertText: too long\";\n if (!nonNegInt(intent.at.offset)) return \"insertText: bad offset\";\n return null;\n case \"suggestRevision\": {\n const nRanges = intent.ranges?.length ?? 0;\n const nMarks = intent.marks?.length ?? 0;\n if (nRanges + nMarks === 0) return \"suggestRevision: empty\";\n if (nRanges > 100 || nMarks > 20) return \"suggestRevision: too many\";\n for (const r of intent.ranges ?? []) {\n if (!nonNegInt(r.start) || !nonNegInt(r.end) || r.end <= r.start) return \"suggestRevision: bad range\";\n if (r.end - r.start > limits.maxDeleteLength) return \"suggestRevision: too large\";\n }\n for (const m of intent.marks ?? []) {\n if (m.glyph !== \"ins\" && m.glyph !== \"del\") return \"suggestRevision: bad glyph\";\n }\n if (typeof intent.suggest?.author !== \"string\" || intent.suggest.author.length > 100) return \"suggestRevision: bad author\";\n if (typeof intent.suggest?.date !== \"string\" || intent.suggest.date.length > 40) return \"suggestRevision: bad date\";\n return null;\n }\n case \"deleteText\":\n if (!nonNegInt(intent.start) || !nonNegInt(intent.end)) return \"deleteText: bad range\";\n if (intent.end <= intent.start) return \"deleteText: empty range\";\n if (intent.end - intent.start > limits.maxDeleteLength) return \"deleteText: too large\";\n return null;\n case \"splitParagraph\":\n if (!nonNegInt(intent.at.offset)) return \"splitParagraph: bad offset\";\n if (!nonNegInt(intent.newBlockId) || !nonNegInt(intent.newRunId)) return \"splitParagraph: bad ids\";\n return null;\n case \"formatRange\":\n if (!nonNegInt(intent.start) || !nonNegInt(intent.end) || intent.end <= intent.start) return \"formatRange: bad range\";\n if (!nonNegInt(intent.middleId)) return \"formatRange: bad middleId\";\n return null;\n case \"commentRun\":\n if (typeof intent.text !== \"string\" || intent.text.length === 0) return \"commentRun: empty\";\n if (intent.text.length > limits.maxCommentLength) return \"commentRun: too long\";\n if (typeof intent.paraId !== \"string\" || typeof intent.date !== \"string\") return \"commentRun: bad provenance\";\n return null;\n case \"pasteBlocks\":\n if (typeof intent.blocksXml !== \"string\" || intent.blocksXml.length === 0) return \"pasteBlocks: empty\";\n if (intent.blocksXml.length > limits.maxPasteBytes) return \"pasteBlocks: too large\";\n if (!Array.isArray(intent.nodeIds)) return \"pasteBlocks: bad nodeIds\";\n // Deep structural validation (element allowlist) happens at apply via\n // validatePastedOoxml; this only bounds the raw size on the hot path.\n return null;\n case \"insertImage\":\n if (!/^[0-9a-f]{64}$/.test(intent.blobSha)) return \"insertImage: bad sha\";\n if (!nonNegInt(intent.bytesLen) || intent.bytesLen === 0 || intent.bytesLen > MAX_IMAGE_BYTES)\n return \"insertImage: bad size\";\n if (![\"png\", \"jpg\", \"jpeg\", \"gif\", \"bmp\", \"webp\"].includes(intent.ext)) return \"insertImage: bad ext\";\n if (intent.iv !== undefined && !/^[A-Za-z0-9+/]{16}$/.test(intent.iv)) return \"insertImage: bad iv\";\n if (!Number.isFinite(intent.widthPx) || !Number.isFinite(intent.heightPx)) return \"insertImage: bad size\";\n return null;\n case \"insertBreak\":\n if (intent.breakKind !== \"page\" && intent.breakKind !== \"column\") return \"insertBreak: bad kind\";\n return null;\n case \"insertMath\":\n if (typeof intent.mathText !== \"string\" || intent.mathText.length === 0) return \"insertMath: empty\";\n if (intent.mathText.length > 10_000) return \"insertMath: too long\";\n return null;\n case \"insertShape\": {\n const presets = [\"line\", \"verticalLine\", \"rectangle\", \"roundedRectangle\", \"ellipse\", \"diamond\", \"textBox\"];\n if (!presets.includes(intent.preset)) return \"insertShape: bad preset\";\n return null;\n }\n case \"replyComment\":\n if (typeof intent.text !== \"string\" || intent.text.length === 0) return \"replyComment: empty\";\n if (intent.text.length > 20_000) return \"replyComment: too long\";\n if (typeof intent.parentId !== \"string\" || !Array.isArray(intent.paraIds) || typeof intent.date !== \"string\") return \"replyComment: bad fields\";\n return null;\n case \"adjustIndent\":\n if (intent.direction !== 1 && intent.direction !== -1) return \"adjustIndent: bad direction\";\n return null;\n case \"setSpacing\":\n if (typeof intent.patch !== \"object\" || intent.patch === null) return \"setSpacing: bad patch\";\n return null;\n case \"insertPageField\":\n if (intent.fieldKind !== \"page\" && intent.fieldKind !== \"pageOfTotal\") return \"insertPageField: bad kind\";\n return null;\n case \"setLink\":\n // URL scheme validated at apply via isSafeUrl; bound the length here.\n if (typeof intent.url !== \"string\" || intent.url.length === 0 || intent.url.length > 4000) return \"setLink: bad url\";\n return null;\n case \"insertFootnote\":\n if (typeof intent.text !== \"string\" || intent.text.trim().length === 0) return \"insertFootnote: empty\";\n if (intent.text.length > 20_000) return \"insertFootnote: too long\";\n return null;\n case \"setDropCap\":\n if (intent.mode !== null && intent.mode !== \"drop\" && intent.mode !== \"margin\") return \"setDropCap: bad mode\";\n return null;\n case \"setDivider\":\n return null;\n case \"insertBookmark\":\n if (typeof intent.name !== \"string\" || intent.name.length === 0 || intent.name.length > 400) return \"insertBookmark: bad name\";\n return null;\n case \"insertBlankPage\":\n return null;\n case \"insertSectionBreak\":\n if (intent.breakType !== \"nextPage\" && intent.breakType !== \"continuous\") return \"insertSectionBreak: bad type\";\n return null;\n case \"insertCrossRef\":\n if (typeof intent.bookmark !== \"string\" || intent.bookmark.length === 0) return \"insertCrossRef: bad bookmark\";\n if (intent.refKind !== \"text\" && intent.refKind !== \"page\") return \"insertCrossRef: bad kind\";\n return null;\n case \"insertCoverPage\":\n if (typeof intent.content !== \"object\" || intent.content === null) return \"insertCoverPage: bad content\";\n return null;\n case \"setPageLayout\": {\n // Positive allowlist + numeric bounds: the patch is free-form, so reject\n // any unknown key or out-of-range scalar rather than pass it to core.\n const p = intent.patch;\n if (typeof p !== \"object\" || p === null) return \"setPageLayout: bad patch\";\n const rec = p as Record<string, unknown>;\n const allowed = [\"margins\", \"mirrorMargins\", \"size\", \"orientation\", \"columns\", \"columnSeparator\", \"pageBorders\"];\n for (const k of Object.keys(rec)) if (!allowed.includes(k)) return `setPageLayout: unknown key ${k}`;\n const inRange = (v: unknown, lo: number, hi: number) => typeof v === \"number\" && Number.isFinite(v) && v >= lo && v <= hi;\n if (rec.orientation !== undefined && rec.orientation !== \"portrait\" && rec.orientation !== \"landscape\") return \"setPageLayout: bad orientation\";\n if (rec.columns !== undefined && !inRange(rec.columns, 1, 12)) return \"setPageLayout: bad columns\";\n if (rec.margins !== undefined) {\n if (typeof rec.margins !== \"object\" || rec.margins === null) return \"setPageLayout: bad margins\";\n for (const [mk, mv] of Object.entries(rec.margins as Record<string, unknown>)) {\n if (![\"top\", \"right\", \"bottom\", \"left\"].includes(mk)) return `setPageLayout: bad margin ${mk}`;\n if (!inRange(mv, 0, 22)) return \"setPageLayout: margin out of range\";\n }\n }\n if (rec.size !== undefined) {\n const s = rec.size as Record<string, unknown>;\n if (typeof s !== \"object\" || s === null || !inRange(s.width, 1, 22) || !inRange(s.height, 1, 22)) return \"setPageLayout: bad size\";\n }\n return null;\n }\n case \"setListLevel\":\n if (intent.delta !== 1 && intent.delta !== -1) return \"setListLevel: bad delta\";\n return null;\n case \"insertWordArt\": {\n if (typeof intent.text !== \"string\" || intent.text.length === 0) return \"insertWordArt: empty text\";\n if (intent.text.length > 500) return \"insertWordArt: text too long\";\n if (![\"plain\", \"archUp\", \"archDown\", \"wave\", \"chevron\"].includes(intent.preset)) return \"insertWordArt: bad preset\";\n return null;\n }\n case \"insertChart\":\n return chartError(intent.chart, \"insertChart\");\n case \"setChartData\":\n return chartError(intent.chart, \"setChartData\");\n case \"setSmartArtNodeText\":\n if (typeof intent.index !== \"number\" || !Number.isInteger(intent.index) || intent.index < 0 || intent.index > 1000) return \"setSmartArtNodeText: bad index\";\n if (typeof intent.text !== \"string\" || intent.text.length > 500) return \"setSmartArtNodeText: bad text\";\n return null;\n case \"setDrawingWordArtText\":\n if (typeof intent.text !== \"string\" || intent.text.length === 0 || intent.text.length > 500) return \"setDrawingWordArtText: bad text\";\n return null;\n case \"setDrawingLineStyle\":\n if (!/^[0-9a-fA-F]{6}$/.test(intent.color)) return \"setDrawingLineStyle: bad color\";\n if (typeof intent.widthPx !== \"number\" || !Number.isFinite(intent.widthPx) || intent.widthPx <= 0 || intent.widthPx > 100) return \"setDrawingLineStyle: bad width\";\n if (![\"solid\", \"dashed\", \"dotted\"].includes(intent.dash)) return \"setDrawingLineStyle: bad dash\";\n return null;\n case \"setImageAltText\":\n if (typeof intent.alt !== \"string\" || intent.alt.length > 1000) return \"setImageAltText: bad alt\";\n return null;\n case \"removeLink\":\n return null;\n case \"insertSmartArt\":\n return smartArtError(intent.smartArt, \"insertSmartArt\");\n case \"setSmartArtData\":\n return smartArtError(intent.smartArt, \"setSmartArtData\");\n case \"setImageWrap\":\n if (![\"inline\", \"square\", \"topAndBottom\", \"none\", \"behind\"].includes(intent.mode)) return \"setImageWrap: bad mode\";\n return null;\n case \"setDrawingOrder\":\n if (intent.order !== \"front\" && intent.order !== \"back\") return \"setDrawingOrder: bad order\";\n return null;\n case \"setSmartArtFill\":\n if (intent.color !== null && !/^[0-9a-fA-F]{6}$/.test(intent.color)) return \"setSmartArtFill: bad color\";\n if (intent.nodeIndex !== undefined && (typeof intent.nodeIndex !== \"number\" || !Number.isInteger(intent.nodeIndex) || intent.nodeIndex < 0 || intent.nodeIndex > 1000)) return \"setSmartArtFill: bad nodeIndex\";\n return null;\n case \"setSmartArtTextFormat\": {\n const f = intent.format;\n if (typeof f !== \"object\" || f === null) return \"setSmartArtTextFormat: bad format\";\n if (typeof f.fontFamily !== \"string\" || f.fontFamily.length === 0 || f.fontFamily.length > 100) return \"setSmartArtTextFormat: bad fontFamily\";\n if (typeof f.fontSizePt !== \"number\" || !Number.isFinite(f.fontSizePt) || f.fontSizePt < 1 || f.fontSizePt > 1638) return \"setSmartArtTextFormat: bad fontSizePt\";\n if (!/^[0-9a-fA-F]{6}$/.test(f.color)) return \"setSmartArtTextFormat: bad color\";\n if (typeof f.bold !== \"boolean\" || typeof f.italic !== \"boolean\") return \"setSmartArtTextFormat: bad bold/italic\";\n if (![\"left\", \"center\", \"right\"].includes(f.alignment)) return \"setSmartArtTextFormat: bad alignment\";\n if (intent.nodeIndex !== undefined && (typeof intent.nodeIndex !== \"number\" || !Number.isInteger(intent.nodeIndex) || intent.nodeIndex < 0 || intent.nodeIndex > 1000)) return \"setSmartArtTextFormat: bad nodeIndex\";\n return null;\n }\n case \"setFloatingPagePosition\": {\n const ok = (v: unknown) => typeof v === \"number\" && Number.isFinite(v) && v >= -5000 && v <= 5000;\n if (!ok(intent.xPx) || !ok(intent.yPx)) return \"setFloatingPagePosition: bad position\";\n return null;\n }\n case \"resizeDrawing\": {\n const ok = (v: unknown) => typeof v === \"number\" && Number.isFinite(v) && v >= 1 && v <= 5000;\n if (!ok(intent.widthPx) || !ok(intent.heightPx)) return \"resizeDrawing: bad extent\";\n return null;\n }\n case \"resizeTableColumn\": {\n const num = (v: unknown, lo: number, hi: number) => typeof v === \"number\" && Number.isFinite(v) && v >= lo && v <= hi;\n if (!Number.isInteger(intent.boundary) || intent.boundary < 1 || intent.boundary > 200) return \"resizeTableColumn: bad boundary\";\n if (!num(intent.deltaPx, -5000, 5000)) return \"resizeTableColumn: bad delta\";\n if (intent.renderedWidths !== undefined) {\n if (!Array.isArray(intent.renderedWidths) || intent.renderedWidths.length > 200 ||\n !intent.renderedWidths.every((w) => num(w, 0, 20000))) return \"resizeTableColumn: bad widths\";\n }\n return null;\n }\n case \"resizeTableRow\": {\n if (!Number.isInteger(intent.rowIdx) || intent.rowIdx < 0 || intent.rowIdx > 5000) return \"resizeTableRow: bad row\";\n if (typeof intent.heightPx !== \"number\" || !Number.isFinite(intent.heightPx) || intent.heightPx < 1 || intent.heightPx > 20000) return \"resizeTableRow: bad height\";\n return null;\n }\n case \"moveTable\": {\n const num = (v: unknown) => typeof v === \"number\" && Number.isFinite(v) && v >= -5000 && v <= 20000;\n if (!num(intent.xPx) || !num(intent.yPx)) return \"moveTable: bad position\";\n if (typeof intent.preservePageStart !== \"boolean\") return \"moveTable: bad flag\";\n if (!Number.isInteger(intent.pageDelta) || Math.abs(intent.pageDelta) > 500) return \"moveTable: bad pageDelta\";\n return null;\n }\n case \"removeDrawing\":\n return null; // run-addressed only; resolution is the whole check\n case \"setMathLinear\":\n if (typeof intent.mathText !== \"string\" || intent.mathText.length === 0 || intent.mathText.length > 1000) return \"setMathLinear: bad mathText\";\n return null;\n case \"deleteMath\":\n return null;\n case \"moveMath\":\n if (!nonNegInt(intent.at?.offset)) return \"moveMath: bad offset\";\n if (!Array.isArray(intent.nodeIds) || intent.nodeIds.length > 64) return \"moveMath: bad nodeIds\";\n return null;\n case \"ensureHeaderFooter\":\n if (intent.hfKind !== \"header\" && intent.hfKind !== \"footer\") return \"ensureHeaderFooter: bad kind\";\n if (!Array.isArray(intent.nodeIds) || intent.nodeIds.length > 64) return \"ensureHeaderFooter: bad nodeIds\";\n return null;\n case \"deleteComment\":\n if (typeof intent.commentId !== \"string\" || intent.commentId.length === 0 || intent.commentId.length > 64) return \"deleteComment: bad id\";\n return null;\n case \"insertBookmarkRange\":\n // Bookmark names: letters/digits/underscore, must start with letter/_.\n if (typeof intent.name !== \"string\" || !/^[A-Za-z_][A-Za-z0-9_]{0,39}$/.test(intent.name)) return \"insertBookmarkRange: bad name\";\n if (!nonNegInt(intent.start) || !nonNegInt(intent.end) || intent.end <= intent.start) return \"insertBookmarkRange: bad range\";\n return null;\n case \"toggleCheckbox\":\n return null;\n case \"acceptRevision\":\n case \"rejectRevision\":\n if (!nonNegInt(intent.index) || intent.index > 100000) return `${intent.kind}: bad index`;\n return null;\n case \"acceptAllRevisions\":\n return null;\n case \"insertTable\": {\n const okDim = (v: unknown) => typeof v === \"number\" && Number.isInteger(v) && v >= 1 && v <= 50;\n if (!okDim(intent.rows) || !okDim(intent.cols)) return \"insertTable: bad dimensions\";\n return null;\n }\n case \"setLineNumbering\": {\n const p = intent.patch;\n if (typeof p !== \"object\" || p === null || typeof p.enabled !== \"boolean\") return \"setLineNumbering: bad patch\";\n if (p.countBy !== undefined && (typeof p.countBy !== \"number\" || !Number.isInteger(p.countBy) || p.countBy < 1 || p.countBy > 100)) return \"setLineNumbering: bad countBy\";\n if (p.restart !== undefined && ![\"continuous\", \"newPage\", \"newSection\"].includes(p.restart)) return \"setLineNumbering: bad restart\";\n if (p.start !== undefined && (typeof p.start !== \"number\" || !Number.isInteger(p.start) || p.start < 0 || p.start > 100000)) return \"setLineNumbering: bad start\";\n return null;\n }\n case \"insertDateTimeField\": {\n if (intent.dtKind !== \"date\" && intent.dtKind !== \"time\") return \"insertDateTimeField: bad kind\";\n if (typeof intent.picture !== \"string\" || intent.picture.length === 0 || intent.picture.length > 100) return \"insertDateTimeField: bad picture\";\n // A date/time picture is a format mask; reject field-code control chars.\n if (/[\\\\{}\"]/.test(intent.picture)) return \"insertDateTimeField: illegal picture char\";\n return null;\n }\n case \"insertField\": {\n if (typeof intent.instruction !== \"string\" || intent.instruction.length === 0 || intent.instruction.length > 200) return \"insertField: bad instruction\";\n if (intent.cachedResult !== undefined && (typeof intent.cachedResult !== \"string\" || intent.cachedResult.length > 1000)) return \"insertField: bad cachedResult\";\n // Positive allowlist on the field TYPE (first token). Blocks external-\n // content / code fields (INCLUDETEXT, INCLUDEPICTURE, DDE, DDEAUTO, LINK,\n // HYPERLINK, IMPORT, …) that could exfiltrate or execute (plan doc 11).\n const SAFE_FIELDS = new Set([\n \"PAGE\", \"NUMPAGES\", \"SECTIONPAGES\", \"SECTION\", \"DATE\", \"TIME\",\n \"CREATEDATE\", \"SAVEDATE\", \"PRINTDATE\", \"AUTHOR\", \"TITLE\", \"SUBJECT\",\n \"KEYWORDS\", \"COMMENTS\", \"FILENAME\", \"NUMWORDS\", \"NUMCHARS\", \"PAGEREF\",\n \"REF\", \"SEQ\", \"STYLEREF\", \"TOC\", \"INDEX\", \"LISTNUM\", \"QUOTE\",\n ]);\n const type = intent.instruction.trim().split(/\\s+/)[0]?.toUpperCase();\n if (!type || !SAFE_FIELDS.has(type)) return \"insertField: field type not allowed\";\n // The whole instruction must be printable ASCII (no control/format chars).\n if (!/^[\\x20-\\x7e]+$/.test(intent.instruction)) return \"insertField: illegal instruction char\";\n return null;\n }\n case \"setDrawingRotation\":\n if (typeof intent.degrees !== \"number\" || !Number.isFinite(intent.degrees)) return \"setDrawingRotation: bad degrees\";\n return null;\n case \"setDrawingFill\":\n if (intent.color !== null && !/^[0-9a-fA-F]{6}$/.test(intent.color)) return \"setDrawingFill: bad color\";\n return null;\n case \"formatRun\":\n case \"formatParagraph\":\n case \"setListType\":\n case \"mergeParagraph\":\n // Id-addressed, no free-form payload to bound here.\n return null;\n case \"tableOp\": {\n const op = intent.op;\n if (typeof op === \"string\") {\n return [\"deleteRow\", \"deleteCol\", \"deleteTable\", \"rowAbove\", \"rowBelow\", \"colLeft\", \"colRight\"].includes(op)\n ? null\n : \"tableOp: bad operation\";\n }\n if (op.kind === \"cellShading\") {\n return op.fill === null || /^[0-9a-fA-F]{6}$/.test(op.fill) ? null : \"tableOp: bad shading\";\n }\n if (op.kind === \"cellVAlign\") {\n return [\"top\", \"center\", \"bottom\"].includes(op.v) ? null : \"tableOp: bad vertical alignment\";\n }\n if (op.kind !== \"textWrapping\") return \"tableOp: bad operation\";\n if (\n (op.wrapping !== \"none\" && op.wrapping !== \"around\") ||\n !Number.isFinite(op.xPx) ||\n !Number.isFinite(op.yPx)\n ) return \"tableOp: bad text wrapping\";\n return null;\n }\n }\n}\n","import { DocxDocument, StableIds } from \"@wordinweb/core\";\nimport { Intent, IntentBody, LogEntry, idempotencyKey } from \"./intents.js\";\n\n/** The ID sidecar carried in a checkpoint bundle (plan doc 03). */\nexport type IdSidecar = ReturnType<StableIds[\"exportSidecar\"]>;\nimport { transformIntent } from \"./transform.js\";\nimport { applyIntentScoped, resyncScope } from \"./apply.js\";\nimport { invertIntent } from \"./invert.js\";\nimport { validateIntent } from \"./validate.js\";\n\n/**\n * The authoritative document session (plan doc 06), transport-free. It owns\n * the live document, the stable-id table, and the intent log; it sequences\n * each submitted intent through the canonical pipeline and emits broadcasts.\n * No sockets, no storage, no timers — a pure state machine so the whole\n * protocol is unit-testable with simulated clients (doc 07/09 H4).\n *\n * Pipeline per submit (doc 06): transform against (base, seq] → apply on the\n * live tree → append a sequenced log entry (applied or rejected) → return the\n * broadcast (the canonical, transformed form) for the transport to fan out.\n */\nexport interface Broadcast {\n entries: LogEntry[];\n}\n\n/** Undo-issued intents use clientSeq at or above this so they aren't\n * re-pushed onto the undo stack (avoids undo-of-undo growth). */\nconst UNDO_CLIENT_SEQ_BASE = 1_000_000_000;\n\n/**\n * What the top of a client's undo stack offers. The three cases are\n * deliberately distinct because the UI says something different for each:\n * nothing to undo (button disabled), the last action can't be reversed yet\n * (button disabled WITH a reason), or here is the inverse to submit.\n */\n/**\n * What a collaborative undo attempt did, as the UI needs to distinguish it:\n *\n * undone the inverse was submitted and painted optimistically\n * cannot-undo the last action has no inverse yet — a HARD STOP; undo does\n * not reach past it (the cure is more inverses, not skipping)\n * changed-since the target is already gone in canonical state, so the undo\n * could never land. The entry IS consumed: what the user\n * asked for has effectively happened, so their next press\n * moving to the previous action cannot surprise them\n * nothing-to-undo the stack is empty\n * unavailable this connection has no collaborative undo (plaintext rooms,\n * whose authority is the server)\n */\nexport type UndoOutcome = \"undone\" | \"cannot-undo\" | \"changed-since\" | \"nothing-to-undo\" | \"unavailable\";\n\nexport type UndoCandidate =\n | { kind: \"undoable\"; seq: number; inverse: IntentBody }\n | { kind: \"cannot-undo\" }\n | { kind: \"nothing-to-undo\" };\n\nexport class DocumentSession {\n readonly doc: DocxDocument;\n readonly ids: StableIds;\n private log: LogEntry[] = [];\n private seen = new Set<string>();\n /** Base sequence number when the session was seeded from a mid-history\n * checkpoint (E2EE mirrors, doc 13 §3): entries 1..floor are baked into\n * the seed bytes; numbering continues from here. */\n private seqFloor = 0;\n /**\n * Undo stack per client: EVERY applied intent that client issued, newest\n * last, with its pre-computed inverse — or `null` when the kind has no\n * inverse yet.\n *\n * The null MARKERS are load-bearing, not bookkeeping. Stacking only the\n * invertible intents makes undo mean \"your last action THAT HAPPENS TO BE\n * INVERTIBLE\", which silently reaches past the others: type \"hello\", bold\n * it, press undo, and the bold stays while the typing disappears (measured,\n * not theorised — the bold applies and the depth never moves). Recording\n * every action lets undo mean \"your last action\" and answer honestly when\n * that action cannot be reversed yet.\n */\n private undoStacks = new Map<string, { seq: number; inverse: IntentBody | null }[]>();\n private undoSeq = new Map<string, number>();\n\n constructor(doc: DocxDocument) {\n this.doc = doc;\n this.ids = doc.enableStableIds();\n }\n\n /** Newest assigned sequence number (0 before any entry). Derived from the\n * last log entry so it stays correct after a snapshot prunes the log\n * prefix (rehydration). */\n get seq(): number {\n return this.log.length === 0 ? this.seqFloor : this.log[this.log.length - 1].seq;\n }\n\n /** Declare that this session's document already reflects seqs 1..n (it\n * was seeded from a checkpoint at n) — numbering continues from there. */\n setSeqFloor(n: number): void {\n this.seqFloor = n;\n }\n\n entriesSince(base: number): LogEntry[] {\n // Entries strictly after `base` by sequence number (log prefix may be\n // pruned past a snapshot, so index math would be wrong).\n return this.log.filter((e) => e.seq > base);\n }\n\n /**\n * A checkpoint bundle (plan doc 03): the snapshot docx bytes at the current\n * seq plus the ID sidecar, so a client bootstrapping from it reproduces the\n * exact id table (parse order alone cannot, because split-created nodes\n * carry non-sequential ids).\n */\n checkpoint(): { seq: number; docx: Uint8Array; sidecar: IdSidecar } {\n return {\n seq: this.seq,\n docx: this.doc.save(),\n sidecar: this.ids.exportSidecar(this.doc.editableRoots()),\n };\n }\n\n /** Install an ID sidecar (from a checkpoint bundle) onto the current\n * document — used when rehydrating from a snapshot so subsequent tail\n * entries resolve their carried ids correctly. */\n installSidecar(sidecar: IdSidecar): void {\n this.ids.importSidecar(this.doc.editableRoots(), sidecar);\n }\n\n /**\n * Rehydrate from a persisted log tail whose entries are already sequenced\n * and canonical (their positions were transformed when first submitted).\n * Applies them in order WITHOUT re-transforming, and restores the log,\n * dedup set, and seq. The document passed to the constructor must be the\n * snapshot the tail continues from.\n *\n * NOTE (plan round-2 F1): this reconstructs stable ids by the snapshot's\n * parse order plus the carried ids in split entries. That is exact for\n * histories without pre-snapshot splits; a snapshot taken after splits\n * needs the ID sidecar (documented next step) to reproduce the id table.\n */\n loadCanonical(tail: LogEntry[]): void {\n for (const e of tail) {\n if (e.kind === \"applied\") {\n const res = applyIntentScoped(this.doc, this.ids, e.intent);\n if (res.applied && res.kind === \"split\") resyncScope(this.doc, this.ids, res);\n this.seen.add(idempotencyKey(e.intent));\n } else {\n this.seen.add(`${e.clientId}:${e.clientSeq}`);\n }\n this.log.push(e);\n }\n }\n\n /**\n * Submit an intent from a client. Deduplicates re-sends by idempotency key,\n * transforms against everything sequenced since the intent's base, applies\n * (or rejects), logs, and returns the sequenced entry(ies) to broadcast.\n */\n submit(intent: Intent): LogEntry {\n const key = idempotencyKey(intent);\n if (this.seen.has(key)) {\n // Already sequenced: return its existing entry (idempotent re-send).\n const prior = this.log.find(\n (e) =>\n (e.kind === \"applied\" && idempotencyKey(e.intent) === key) ||\n (e.kind === \"rejected\" && `${e.clientId}:${e.clientSeq}` === key),\n );\n if (prior) return prior;\n }\n this.seen.add(key);\n\n // Cheap structural validation before any work (doc 06 / doc 11 F9): reject\n // malformed/oversized intents on the hot path, before transform/apply.\n const invalid = validateIntent(intent);\n if (invalid) return this.reject(intent, invalid);\n\n if (intent.base < 0 || intent.base > this.seq) {\n return this.reject(intent, \"invalid base\");\n }\n // Transform against CONCURRENT intents only: entries after the client's\n // base authored by OTHER clients. The client's own later-sequenced intents\n // are not concurrent — it authored this intent on a doc that already\n // contained them (client-FIFO order), so transforming against them would\n // double-count every one of its own edits. This is what broke burst\n // typing over a real socket: keystroke N carried base = last echo, the\n // server shifted it by the client's own N-1 in-flight inserts, and\n // offsets drifted until every subsequent apply failed.\n const ahead = this.log\n .filter(\n (e): e is Extract<LogEntry, { kind: \"applied\" }> =>\n e.kind === \"applied\" && e.seq > intent.base && e.intent.clientId !== intent.clientId,\n )\n .map((e) => e.intent);\n const canonical = transformIntent(intent, ahead);\n\n // Compute the inverse BEFORE applying, while the pre-state is intact\n // (e.g. a delete's removed text is still present). Null when not undoable.\n const inverse = invertIntent(this.doc, this.ids, canonical);\n\n let result: ReturnType<typeof applyIntentScoped>;\n try {\n result = applyIntentScoped(this.doc, this.ids, canonical);\n } catch {\n result = { kind: \"doc\", applied: false };\n }\n if (!result.applied) return this.reject(intent, \"apply failed\");\n\n // Structural intents changed the tree shape; rebuild the model for what\n // they touched and re-key survivors. A split is the only intent whose new\n // nodes the session must see in the MODEL before the next apply resolves\n // them (everything else either refreshes inside the mutation or addresses\n // nodes the model already holds) — so it is the only scope acted on here,\n // and it now reparses two paragraphs instead of the document (perf B9).\n if (result.kind === \"split\") resyncScope(this.doc, this.ids, result);\n\n const entry: LogEntry = { seq: this.seq + 1, kind: \"applied\", intent: canonical };\n this.log.push(entry);\n // Record the action on the originating client's undo stack — WITH a null\n // inverse when the kind isn't invertible yet, so undo can stop there\n // instead of reaching past it (see undoStacks). Intents that are\n // themselves undos are excluded via the reserved clientSeq range.\n if (!this.isUndoIntent(intent)) {\n const stack = this.undoStacks.get(intent.clientId) ?? [];\n stack.push({ seq: entry.seq, inverse });\n this.undoStacks.set(intent.clientId, stack);\n }\n return entry;\n }\n\n private isUndoIntent(intent: Intent): boolean {\n // Undo-issued intents use clientSeq in a reserved high range so they don't\n // themselves get pushed onto the undo stack (no undo-of-undo here).\n return intent.clientSeq >= UNDO_CLIENT_SEQ_BASE;\n }\n\n /**\n * Take this client's most recent action off its undo stack WITHOUT applying\n * anything — the caller decides how to submit the inverse.\n *\n * This exists because `undo()` below both pops AND submits, and an\n * ENCRYPTED client must never do the second half here: its mirror is a\n * local re-derivation of the canonical log and may only advance by\n * ingesting sequenced envelopes. Applying an inverse directly to the mirror\n * would desynchronise it from every other client's mirror — the one thing\n * the mirror architecture exists to prevent. So the encrypted path takes\n * the inverse, seals it, and submits it as an ordinary intent; it arrives\n * back through ingest like anything else.\n *\n * `cannot-undo` does NOT pop: undo stops at an action it cannot reverse\n * rather than skipping to an older one. That is a deliberate dead end — the\n * cure is implementing more inverses, not quietly undoing something the\n * user did not ask about.\n */\n takeUndo(clientId: string): UndoCandidate {\n const stack = this.undoStacks.get(clientId);\n if (!stack || stack.length === 0) return { kind: \"nothing-to-undo\" };\n const top = stack[stack.length - 1];\n if (!top.inverse) return { kind: \"cannot-undo\" };\n stack.pop();\n return { kind: \"undoable\", seq: top.seq, inverse: top.inverse };\n }\n\n /** The clientSeq an undo intent must carry so it is not itself stacked.\n * Exposed because the encrypted client builds its own undo intents. */\n nextUndoClientSeq(clientId: string): number {\n const n = (this.undoSeq.get(clientId) ?? 0) + 1;\n this.undoSeq.set(clientId, n);\n return UNDO_CLIENT_SEQ_BASE + n;\n }\n\n /**\n * Selective per-user undo (plan doc 03 Phase 8): revert the given client's\n * most recent action by submitting its inverse with `base` set to the\n * ORIGINAL intent's seq — the canonical transform then rebases the inverse\n * against everything sequenced since, which is what makes undo correct under\n * concurrency. Returns the resulting log entry, or null when there is\n * nothing to undo OR the last action has no inverse yet (use `takeUndo` when\n * the difference matters to the caller). The inverse is applied through the\n * normal pipeline, so it converges and broadcasts like any intent.\n *\n * PLAINTEXT path only — see takeUndo for why an encrypted client cannot use\n * this method.\n */\n undo(clientId: string): LogEntry | null {\n const candidate = this.takeUndo(clientId);\n if (candidate.kind !== \"undoable\") return null;\n const undoIntent = {\n ...candidate.inverse,\n clientId,\n clientSeq: this.nextUndoClientSeq(clientId),\n base: candidate.seq, // rebase the inverse through everything applied since.\n } as Intent;\n return this.submit(undoIntent);\n }\n\n /** How many of this client's actions are on its undo stack — including ones\n * whose inverse doesn't exist yet, because they are still actions the user\n * took. Ask `takeUndo` (or peek via `undoState`) for reversibility. */\n undoDepth(clientId: string): number {\n return this.undoStacks.get(clientId)?.length ?? 0;\n }\n\n /**\n * Do the nodes an inverse addresses still exist in CANONICAL state?\n *\n * A cheap NECESSARY condition, deliberately not a full validation: every\n * inverse this session produces is id-addressed, so a retired id means the\n * target is definitively gone (someone else's delete already sequenced) and\n * the undo cannot possibly land. An encrypted client checks this against\n * its mirror before painting optimistically — declining up front is both\n * honest and flash-free, where submitting would paint the undo and then\n * visibly revert it on the rejection.\n *\n * It does NOT prove the inverse WILL apply: a target that still exists may\n * have changed in ways only the real apply can reject (a text range that no\n * longer spans what it did). Those fall through to the ordinary optimistic\n * rollback every other op already has. Sound about \"no\", silent about \"yes\"\n * — which is the right asymmetry for a pre-flight check, since a false\n * \"can't\" would block a legitimate undo while a false \"can\" only costs the\n * flash we already tolerate.\n */\n targetsResolve(body: IntentBody): boolean {\n const ids: unknown[] = [];\n const b = body as unknown as Record<string, unknown>;\n for (const key of [\"blockId\", \"runId\", \"cellParagraphId\"]) {\n if (b[key] !== undefined) ids.push(b[key]);\n }\n const at = b.at as { blockId?: unknown; runId?: unknown } | undefined;\n if (at) {\n if (at.blockId !== undefined) ids.push(at.blockId);\n if (at.runId !== undefined) ids.push(at.runId);\n }\n // An inverse addressing nothing (none exist today) is not provably dead.\n return ids.every((id) => typeof id === \"number\" && this.ids.elOf(id) !== undefined);\n }\n\n /** What the next undo WOULD do, without consuming anything — for enabling\n * a toolbar button and choosing its tooltip. */\n undoState(clientId: string): UndoCandidate[\"kind\"] {\n const stack = this.undoStacks.get(clientId);\n if (!stack || stack.length === 0) return \"nothing-to-undo\";\n return stack[stack.length - 1].inverse ? \"undoable\" : \"cannot-undo\";\n }\n\n /**\n * E2EE mode (doc 13 §2): an envelope that fails to open (garbage from a\n * malicious participant, or any tamper) still consumed a sequence number\n * — every honest client must agree that seq is a no-op, deterministically,\n * because the applied/rejected verdict feeds the transform's `ahead` set.\n * GCM authentication IS deterministic (same bytes fail for everyone), so\n * ingesting the failure as a sequenced rejection keeps all mirrors\n * byte-identical. Client-side use only; the blind server never calls this.\n */\n ingestOpaqueFailure(clientId: string, clientSeq: number): LogEntry {\n this.seen.add(`${clientId}:${clientSeq}`);\n return this.reject({ clientId, clientSeq } as Intent, \"undecryptable\");\n }\n\n private reject(intent: Intent, reason: string): LogEntry {\n const entry: LogEntry = {\n seq: this.seq + 1,\n kind: \"rejected\",\n clientId: intent.clientId,\n clientSeq: intent.clientSeq,\n reason,\n };\n this.log.push(entry);\n return entry;\n }\n}\n","import { serializeXml, type DocxDocument } from \"@wordinweb/core\";\n\n/**\n * The canonical document hash (plan doc 05): sha256 over the\n * deterministically-serialized editable roots in fixed part order.\n * `serializeXml` is a pure function of the tree (attribute order preserved\n * from parse, fixed escaping, no environment reads), so replicas that hold\n * the same document produce the same hash on any engine — which is what\n * makes the hash a convergence VERIFIER rather than a hope.\n *\n * Doc-05 scope note: the full recipe also folds in relationship roots,\n * content-types, and declared media shas (package state is\n * divergence-capable). Those parts are immutable in the demo's enabled\n * intent surface (media is gated off; no intent edits rels/content-types\n * outside media), so the editable roots are the complete divergence surface\n * today — the fold-in lands with the media gate (doc 16).\n */\nexport async function docHash(doc: DocxDocument): Promise<string> {\n const parts = doc.editableRoots().map((r) => serializeXml(r));\n const digest = await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(parts.join(\"\u0000\")) as BufferSource);\n let hex = \"\";\n for (const b of new Uint8Array(digest)) hex += b.toString(16).padStart(2, \"0\");\n return hex;\n}\n","import { DocxDocument } from \"@wordinweb/core\";\nimport { DocumentSession, type UndoOutcome } from \"./session.js\";\nimport { ClientReplica } from \"./replica.js\";\nimport { Intent } from \"./intents.js\";\nimport {\n ClientMessage,\n ServerMessage,\n EnvelopeEntry,\n PROTOCOL_VERSION,\n ENGINE_VERSION,\n PresencePosition,\n ParticipantProfile,\n sanitizePresencePosition,\n} from \"./protocol.js\";\nimport type { ClientTransport, ConnectionCallbacks } from \"./connection.js\";\nimport type { Scope } from \"./apply.js\";\nimport type { RosterEntry } from \"./protocol.js\";\nimport type { DocBundle } from \"./bundle.js\";\nimport { deriveEpochKeys, openCheckpoint, openIntent, sealIntent, sealCheckpoint, sealMediaBlob, openMediaBlob, sealPresence, openPresence, isSealedPresence, b64ToBytes, bytesToB64, type EpochKeys } from \"./e2ee.js\";\nimport { docHash } from \"./hash.js\";\nimport { transformIntent } from \"./transform.js\";\nimport { MediaClient, applyMediaAddresses, mediaAddressesOf, type MediaTransportOptions } from \"./media.js\";\n\n/**\n * The E2EE client connection (plan doc 13 §2) — the counterpart of a BLIND\n * sequencer. The server orders opaque envelopes; this class re-derives the\n * canonical history the plaintext server would have produced, using a local\n * **mirror**: a real `DocumentSession` — the exact authority code the\n * plaintext server runs — fed the DECRYPTED originals in server order.\n *\n * Why this shape: the mirror's `submit()` performs the same\n * transform-against-concurrent → validate → apply → log pipeline the server\n * does, and since every client feeds it the identical ordered inputs, every\n * client derives the identical canonical entries (\"the transform moves, the\n * math doesn't change\" — made literal: it is the same function, the same\n * class). Those canonical entries then drive an unchanged `ClientReplica`,\n * so optimistic apply, rollback-replay, echoes, and rejection handling are\n * ALL the plaintext code paths — the encryption layer adds sealing at the\n * edges and nothing else. Convergence in encrypted mode therefore inherits\n * every plaintext convergence test by construction.\n *\n * Undecryptable envelopes (malicious participant, tampered blob) consume\n * their seq as a deterministic rejection on every honest client\n * (`ingestOpaqueFailure`) — GCM authentication is deterministic, so all\n * mirrors agree byte-for-byte on the no-op.\n *\n * Mode is derived from the LINK, never the wire (doc 13 §6): this class\n * exists because the caller holds `#k` — a plaintext `welcome` arriving\n * anyway is a downgrade attempt and is hard-refused.\n */\nexport class EncryptedCollabConnection {\n private mirror: DocumentSession | null = null;\n private replica: ClientReplica | null = null;\n private keys: EpochKeys | null = null;\n private clientSeq = 0;\n private docId = \"\";\n genesisId: string | null = null;\n /** Relay per-blob upload limit from the welcome, or `null` when the server\n * published none (an older host) — in which case callers SKIP the\n * pre-check rather than inventing a limit. See the plaintext connection's\n * field of the same name. */\n mediaMaxBlobBytes: number | null = null;\n docEpoch = 0;\n /** Envelope processing is async (WebCrypto) but MUST be strictly ordered\n * (seq order is the convergence contract) — a serial promise chain. */\n private queue: Promise<void> = Promise.resolve();\n private idCounter = 0;\n private idBase = -1;\n /** Own doc hashes at gossip points (seq → hash), a small ring — peers'\n * gossip for a seq we also hashed is comparable; anything else is skipped\n * (clients hash at the SAME seq cadence, so overlap is the common case). */\n private ownHashes = new Map<number, string>();\n /** Every K ingested seqs, hash + gossip (doc 13 §2). */\n private static GOSSIP_EVERY = 20;\n /** Server-designated checkpointer flag (doc 13 §3) + cadence. */\n private isCheckpointer = false;\n private static CHECKPOINT_EVERY = 50;\n /** The last intent this connection sealed+sent (one-in-flight makes this\n * the only candidate a `stale-base` refusal can refer to). */\n private lastSent: Intent | null = null;\n /** SELF-HEAL (the B6a typist-drift catch): at quiescence the optimistic\n * replica must equal the mirror's canonical doc — the mirror is LOCAL\n * ground truth (the same authority code the plaintext server runs, fed\n * the identical ordered inputs), so drift in the optimistic\n * rollback-replay machinery is detectable and repairable without a\n * network round-trip. Debounced past the drain; staleness-guarded. */\n private selfCheckTimer: ReturnType<typeof setTimeout> | null = null;\n private lastSelfCheckSeq = -1;\n /** Number of times the replica was rebuilt from the mirror (telemetry). */\n selfHeals = 0;\n /** STUCK-PENDING RECOVERY (swarm finding): under load a submit can be\n * lost between pending-tracking and delivery (or its echo lost), leaving\n * the client permanently un-quiescent — which also blocks the drift\n * self-check. The front pending is watched; if it makes no progress, the\n * RECONCILIATION-TRANSFORMED copy is re-sealed at the current base and\n * resent (server dedup by (clientId, clientSeq) makes a resend of an\n * already-sequenced op a no-op). After MAX retries the pending is dropped\n * and the replica heals from the mirror so the room converges. */\n private stuckTimer: ReturnType<typeof setTimeout> | null = null;\n private stuckKey: string | null = null;\n private stuckRetries = 0;\n private static STUCK_MAX_RETRIES = 3;\n /** Rate-limit recovery (see the \"refused\" handler): one scheduled\n * full-queue re-drive at a time, exponential backoff 300ms→5s, reset\n * when the queue drains. */\n private redriveTimer: ReturnType<typeof setTimeout> | null = null;\n private redriveBackoffMs = 0;\n\n /** Out-of-band media transfer (doc 16 §5), sealed under K_media. */\n media: MediaClient | null = null;\n\n constructor(\n private transport: ClientTransport,\n private clientId: string,\n /** The document master key — the `#k=` fragment value (doc 13 §1). */\n private docKey: string,\n private cb: ConnectionCallbacks = {},\n /** Stretched share code (doc 13 §7) when the doc has one. */\n private stretchedCode?: Uint8Array,\n /** Test seam: self-check debounce delay (default 400ms). */\n private selfCheckDelayMs = 400,\n /** Origin serving the doc-16 §3 media routes; enables media transfer. */\n mediaOpts?: MediaTransportOptions,\n ) {\n this.transport.onMessage((msg) => this.onServer(msg));\n if (mediaOpts) {\n // E2EE media (doc 16 §6): blobs are sealed under K_media, a key\n // DISTINCT from K_content, and the relay only ever sees ciphertext —\n // the sha it verifies addresses that ciphertext, so a blind server can\n // still swap-proof uploads. The IV is returned by the seal and rides\n // in the intent, because re-supply must reproduce the SAME ciphertext.\n this.media = new MediaClient(\n mediaOpts,\n () => this.replica?.doc ?? null,\n {\n // Media installs mutate the doc (pixels land) outside the replica's\n // counter — bump the render signal so the skeleton repaints.\n onChange: () => { this.docVersionBase++; this.cb.onChange?.(); },\n onState: (part, state) => this.cb.onMediaState?.(part, state),\n need: (sha) => this.transport.send({ t: \"media-need\", sha }),\n have: (shas) => { if (shas.length) this.transport.send({ t: \"media-have\", shas }); },\n },\n {\n seal: async (plaintext, iv) => {\n if (!this.keys) throw new Error(\"media seal before keys\");\n return sealMediaBlob(this.keys.kMedia, plaintext, iv);\n },\n open: async (blob, iv) => {\n if (!this.keys) throw new Error(\"media open before keys\");\n return openMediaBlob(this.keys.kMedia, blob, iv);\n },\n },\n );\n }\n }\n\n /** Upload bytes and get the intent's media fields (blobSha over the\n * CIPHERTEXT, plus the IV the re-supply path must reuse), or null when the\n * relay refused — the caller must not emit an insertImage in that case. */\n async uploadMedia(plaintext: Uint8Array): Promise<{ blobSha: string; bytesLen: number; iv?: string } | null> {\n return this.media ? this.media.upload(this.docId, plaintext) : null;\n }\n\n join(docId: string, token?: string, opts?: { takeover?: boolean; profile?: ParticipantProfile; codeProof?: string; ownerToken?: string }): void {\n this.docId = docId;\n this.transport.send({\n t: \"hello\",\n protocolVersion: PROTOCOL_VERSION,\n docId,\n clientId: this.clientId,\n takeover: opts?.takeover,\n token,\n sinceSeq: 0,\n profile: opts?.profile,\n codeProof: opts?.codeProof,\n ownerToken: opts?.ownerToken, // owner-capability proof (doc 14 §2.5)\n engineVersion: ENGINE_VERSION, // the fence for client-derived canon (doc 13 §2)\n });\n }\n\n get doc() {\n return this.replica?.doc ?? null;\n }\n get ready(): boolean {\n return this.replica !== null;\n }\n\n /** Accumulates doc changes across replica rebuilds (self-heal/welcome), so\n * the getter below stays monotonic per connection. */\n private docVersionBase = 0;\n /**\n * Counts changes TO THE RENDERED DOCUMENT — the repaint signal, as opposed\n * to onChange (which also fires for pure bookkeeping: a tracked own echo, a\n * roster-adjacent state change). The editor-driven typing path mutates and\n * paints the doc itself, so its submit + echo leave this untouched; remote\n * applies, optimistic canonical applies (toolbar ops), reloads, heals, and\n * media installs advance it. The react layer repaints on THIS, not on\n * onChange — repainting on onChange queued a whole-document relayout per\n * keystroke, catastrophic past the background-layout page threshold.\n */\n get docVersion(): number {\n return this.docVersionBase + (this.replica?.docVersion ?? 0);\n }\n\n /** docVersionBase at the last takeRenderScope — a base bump between takes\n * (welcome/heal doc replacement, media install) has no narrow scope. */\n private takenDocVersionBase = 0;\n\n /**\n * Drain the dirty scope behind the docVersion movement since the last take\n * — parity with the plaintext connection (see connection.ts). Replica\n * applies carry their per-intent scope; base bumps report document scope;\n * null means nothing was recorded (the caller may skip the repaint).\n */\n takeRenderScope(): Scope | null {\n const replicaScope = this.replica?.takeRenderScope() ?? null;\n if (this.docVersionBase !== this.takenDocVersionBase) {\n this.takenDocVersionBase = this.docVersionBase;\n return { kind: \"doc\" };\n }\n return replicaScope;\n }\n\n /** Un-confirmed local intents in flight — drained-replay parity with the\n * plaintext connection (doc 15 §2 / rebase.ts). */\n get pendingCount(): number {\n return this.replica?.pendingCount ?? 0;\n }\n\n /** Roster + activity parity with the plaintext connection (doc 14). */\n roster: RosterEntry[] = [];\n activity: { seq: number; clientId: string; kind: string }[] = [];\n\n setProfile(profile: ParticipantProfile): void {\n this.transport.send({ t: \"profile\", profile } as ClientMessage);\n }\n\n /** Bundle being resumed from (consumed after the welcome replay). */\n private resuming: DocBundle | null = null;\n\n /**\n * Rejoin from a persisted bundle (doc 12 §5, encrypted flavor). Identical\n * contract to the plaintext connection: clientSeq watermark restored\n * FIRST; pending replayed fire-and-observe on same-epoch welcomes (the\n * sequencer's plaintext-bookkeeping dedup gives exactly-once, and the\n * whole-epoch welcome-enc replay already reconstructed everything that\n * was sequenced pre-crash); different epoch ⇒ onEpochChange, pending\n * withheld (fork rule). The bundle's confirmed bytes are NOT loaded here\n * — encrypted joiners always rebuild from the sealed checkpoint + tail,\n * which is both simpler and verifiable (hash gossip).\n */\n resume(bundle: DocBundle, token?: string, opts?: { profile?: ParticipantProfile; codeProof?: string; ownerToken?: string }): void {\n this.clientSeq = Math.max(this.clientSeq, bundle.clientSeq);\n this.resuming = bundle;\n this.join(bundle.docId, token, { ...opts, takeover: true });\n }\n\n /** Owner admin op (doc 14 §2.5): honored only if this connection proved\n * the owner token at hello — otherwise the server refuses `not-owner`.\n * The admin channel is PLAINTEXT bookkeeping by design (roles are\n * integrity, not confidentiality — doc 14 §2.5 honest limits), so the\n * encrypted connection sends the same frame the plaintext one does.\n * HISTORY: this method was MISSING here while present on the plaintext\n * connection — the react layer's `as unknown as CollabConnection` cast\n * hid it from the typechecker, so the demo's owner controls were silent\n * TypeErrors on encrypted docs (the default), long misattributed to a\n * Vite prebundle race (\"stale session w/o admin\"). */\n admin(action:\n | { op: \"kick\"; clientId: string }\n | { op: \"readOnly\"; on: boolean }\n | { op: \"setRole\"; clientId: string; role: \"editor\" | \"viewer\" }): void {\n this.transport.send({ t: \"admin\", action });\n }\n\n /** Durable state as a doc-12 bundle (kDoc rides in it for revival). */\n exportBundle(docId: string): DocBundle | null {\n if (!this.replica || this.genesisId === null) return null;\n return {\n docId,\n genesisId: this.genesisId,\n ...this.replica.exportBundleState(),\n clientSeq: this.clientSeq,\n savedAt: 0,\n lineage: [], // the persister maintains the chain across writes\n };\n }\n\n async exportBundleAsync(docId: string): Promise<DocBundle | null> {\n if (!this.replica || this.genesisId === null) return null;\n return {\n docId,\n genesisId: this.genesisId,\n ...await this.replica.exportBundleStateAsync(),\n clientSeq: this.clientSeq,\n savedAt: 0,\n lineage: [],\n };\n }\n\n /** Same disjoint carried-id allocation as the plaintext connection. */\n allocIds(n: number): number[] {\n if (this.idBase < 0) {\n let h = 0;\n for (let i = 0; i < this.clientId.length; i++) h = (h * 31 + this.clientId.charCodeAt(i)) >>> 0;\n this.idBase = 1_000_000_000 + (h % 100_000) * 10_000_000;\n }\n const out: number[] = [];\n for (let i = 0; i < n; i++) out.push(this.idBase + this.idCounter++);\n return out;\n }\n\n /** Submit a local edit (optimistic canonical apply, like the plaintext\n * `submit`); sealing happens on the ordered queue. */\n submit(intent: Omit<Intent, \"clientId\" | \"clientSeq\" | \"base\">): void {\n this.submitFull(intent, false);\n }\n /** Editor-driven path: the mutation is already in the live doc. */\n submitPreApplied(intent: Omit<Intent, \"clientId\" | \"clientSeq\" | \"base\">): void {\n this.submitFull(intent, true);\n }\n\n /**\n * COLLABORATIVE UNDO (plan doc 03 Phase 8), encrypted flavour.\n *\n * The mirror is a real DocumentSession fed the canonical log, so it already\n * knows the inverse of this client's last action — no new wire message and\n * no server involvement are needed. The inverse is submitted as an ORDINARY\n * sealed intent with `base` set to the ORIGINAL action's seq, which is what\n * makes undo correct under concurrency: the canonical transform rebases it\n * through everything sequenced since, and if the target has been deleted or\n * changed the apply rejects it identically on every replica.\n *\n * Two things this deliberately does NOT do. It does not call the mirror's\n * `undo()`, because that would apply to the mirror out of band and the\n * mirror may only advance by ingesting sequenced envelopes. And it does not\n * paint an undo it can already prove is dead: `targetsResolve` checks the\n * canonical id table first, so the common conflict (someone else removed\n * the thing) is declined up front rather than painted and yanked back.\n */\n undoLast(): UndoOutcome {\n if (!this.mirror || !this.replica || !this.keys || !this.genesisId) return \"unavailable\";\n const candidate = this.mirror.takeUndo(this.clientId);\n if (candidate.kind !== \"undoable\") return candidate.kind;\n //\n // TWO WAYS AN UNDO CAN DECLINE, AND THEY GET OPPOSITE TREATMENT. Do not\n // unify them — they look alike (both \"undo didn't happen\") and are not.\n //\n // MARKER (`cannot-undo`, decided in takeUndo): the last action has no\n // inverse yet — say, a bold. Its effect is STILL IN the document, so\n // reaching past it to undo something older would surprise the user:\n // their bold stays, their earlier typing vanishes. The entry is NOT\n // popped; undo stops here. A temporary stop that disappears the day\n // someone implements that inverse.\n //\n // OVERTAKEN (`changed-since`, decided just below): the target is\n // already gone in canonical state — a peer deleted the image first. The\n // effect this undo would produce is ALREADY TRUE, so consuming the entry\n // leaves the document exactly where the undo aimed, and the next press\n // moving to the previous action cannot surprise anyone. The entry IS\n // popped (takeUndo already did). Refusing would strand undo forever at\n // an entry that can never become reversible — strictly worse than the\n // marker's temporary stop.\n //\n if (!this.mirror.targetsResolve(candidate.inverse)) return \"changed-since\";\n // REBASE HERE, not on the wire. The inverse describes the document as it\n // was at `candidate.seq`; everything sequenced since must be transformed\n // through before it can be applied to the document as it is NOW.\n //\n // Submitting it raw at the old base and letting the canonical transform\n // rebase it — the plaintext session's own trick — does NOT work for an\n // optimistic client: the local apply would paint the stale form (measured:\n // undoing \"HELLO\" after a peer typed \"ZZ\" in front of it deleted \"abHEL\"\n // and left \"ZZLO\"), and the pending copy would then disagree with the\n // canonical form the server derives, which is the stale-base divergence\n // class this codebase has been bitten by before. Rebasing against the\n // MIRROR — the same canonical log the server would transform against —\n // yields an intent that is correct at the current state, so it submits as\n // an ordinary intent at the ordinary base and every existing mechanism\n // (optimistic apply, echo, rollback-replay) applies unchanged.\n const ahead = this.mirror\n .entriesSince(candidate.seq)\n .filter((e): e is Extract<typeof e, { kind: \"applied\" }> =>\n e.kind === \"applied\" && e.intent.clientId !== this.clientId)\n .map((e) => e.intent);\n const rebased = transformIntent(\n {\n ...candidate.inverse,\n clientId: this.clientId,\n // Reserved range: keeps the undo itself off every replica's undo stack.\n clientSeq: this.mirror.nextUndoClientSeq(this.clientId),\n base: candidate.seq,\n } as Intent,\n ahead,\n );\n const full = { ...rebased, base: this.replica.confirmedSeq } as Intent;\n // Optimistic canonical apply, exactly like a toolbar op: the inverse\n // paints immediately and rolls back through the normal machinery if the\n // race it could not foresee (target dies mid-flight) rejects it.\n this.replica.submitLocal(full);\n this.enqueue(async () => {\n try {\n this.lastSent = full;\n const env = await sealIntent(this.keys!.kContent, this.docId, this.genesisId!, full);\n this.transport.send({ t: \"submit-enc\", envelope: env } as ClientMessage);\n } catch (err) {\n // ITS OWN CATCH, so the loss accounting can tell submits from ingest.\n // Both ride the same serial chain, and the chain's catch cannot\n // distinguish them — but a seal or send that throws HERE is a LOST\n // EDIT (the replica already applied it optimistically and counted it\n // pending), while an ingest throw is not. Reporting both under one\n // label would make the next B13-style arithmetic ambiguous again,\n // which is the exact failure that investigation turned on.\n this.sendFailures++;\n this.cb.onError?.({ where: \"enc.submit\", error: err });\n }\n });\n this.scheduleSelfCheck();\n this.cb.onChange?.();\n return \"undone\";\n }\n\n /** What the next undo WOULD do, for enabling a button and its tooltip. */\n undoState(): UndoOutcome {\n if (!this.mirror) return \"unavailable\";\n const state = this.mirror.undoState(this.clientId);\n return state === \"undoable\" ? \"undone\" : state;\n }\n\n /**\n * Throws on the ingest/submit chain — in practice a seal or transport send\n * that failed AFTER the edit was accepted locally and counted as pending.\n *\n * Separate from droppedPreReady on purpose, and the arithmetic is why: that\n * one counts edits refused BEFORE they were ever applied, this one counts\n * edits lost AFTER. Summing them against a swarm run's \"lost\" figure is how\n * B13's 231 would have been attributed in minutes instead of a night, and\n * merging the two would have made that sum ambiguous again.\n */\n sendFailures = 0;\n /** Submits dropped because the connection was not ready — see\n * ConnectionCallbacks.onSubmitDropped. The window is WIDER here than in the\n * plaintext connection: this one must also derive epoch keys and open the\n * seeded checkpoint before it can honour anything, which on a large\n * document is real time during which the editor's submit path is reachable. */\n droppedPreReady = 0;\n\n private submitFull(intent: Omit<Intent, \"clientId\" | \"clientSeq\" | \"base\">, preApplied: boolean): void {\n if (!this.replica || !this.keys || !this.genesisId) {\n // LOUD, NOT LOSSY — same contract as the plaintext connection, and the\n // same reasoning against buffering, with one more term: there is no\n // kContent yet, so the intent could not even be sealed.\n this.droppedPreReady++;\n this.cb.onSubmitDropped?.(\"not-ready\");\n return;\n }\n const full = {\n ...intent,\n clientId: this.clientId,\n clientSeq: ++this.clientSeq,\n base: this.replica.confirmedSeq,\n } as Intent;\n if (preApplied) this.replica.trackLocal(full);\n else this.replica.submitLocal(full);\n // Seal + send ride the serial queue so a fast second keystroke can't\n // overtake the first one's async encryption (order = correctness).\n this.enqueue(async () => {\n try {\n this.lastSent = full;\n const env = await sealIntent(this.keys!.kContent, this.docId, this.genesisId!, full);\n this.transport.send({ t: \"submit-enc\", envelope: env } as ClientMessage);\n } catch (err) {\n // ITS OWN CATCH, so the loss accounting can tell submits from ingest.\n // Both ride the same serial chain, and the chain's catch cannot\n // distinguish them — but a seal or send that throws HERE is a LOST\n // EDIT (the replica already applied it optimistically and counted it\n // pending), while an ingest throw is not. Reporting both under one\n // label would make the next B13-style arithmetic ambiguous again,\n // which is the exact failure that investigation turned on.\n this.sendFailures++;\n this.cb.onError?.({ where: \"enc.submit\", error: err });\n }\n });\n // Arm the stuck-pending watchdog NOW: if the seal/send above throws (the\n // queue's catch swallows task failures by design) or the frame is lost,\n // no broadcast will ever arrive to re-arm the checks — this is the only\n // guaranteed trigger for a lone client's lost op.\n this.scheduleSelfCheck();\n this.cb.onChange?.();\n }\n\n /**\n * OUTBOUND presence: a COALESCING single-flight sealer, not a queue.\n *\n * Sealing is async, so the send can no longer be a straight-line call, and\n * three shapes were considered. Fire-and-forget lets two rapid caret moves\n * finish encrypting out of order and paint a stale cursor. Reusing\n * `this.queue` would put a high-frequency, low-value stream behind — and\n * ahead of — broadcast ingest, which is the ordering the document's\n * correctness depends on; perf B10 and B13 were both about work piling up\n * on that chain, and this is that lesson applied before the fact rather\n * than after. Chaining every move onto a private promise fixes ordering but\n * still queues N seals for N twitches of the mouse.\n *\n * So: at most ONE seal in flight, and at most ONE position waiting. A move\n * that arrives while a seal is running REPLACES the waiting one — which is\n * not a compromise but the correct semantics, because presence is\n * last-write-wins and a superseded caret has no reader. Bounded by\n * construction: two slots, whatever the input rate.\n */\n /** Serial chain for INBOUND presence opens (see the receive branch). */\n private presenceInQueue: Promise<void> = Promise.resolve();\n private presenceLatest: { position: PresencePosition | null } | null = null;\n private presenceFlushing = false;\n\n setPresence(position: PresencePosition | null): void {\n // SEALED (doc 13 / #20). Presence used to ride the wire in the clear even\n // in encrypted rooms: the blind sequencer could not read what anyone\n // wrote, but it could watch where everyone pointed, and selection ranges\n // added \"how much is selected\" on top. Now the position is sealed under\n // K_presence with the sender bound into the AAD, so the relay sees two\n // base64 strings. The structural clamp still runs — on the SENDER here,\n // and again on every receiver after opening, because a hostile\n // participant can seal whatever it likes.\n const clean = sanitizePresencePosition(position);\n const keys = this.keys;\n const genesisId = this.genesisId;\n if (!keys || !genesisId) {\n // Pre-welcome: there is no key yet. Presence is ephemeral and the next\n // caret move re-sends, so dropping is correct and costs nothing — the\n // only thing that would be wrong is leaking it in the clear instead.\n return;\n }\n this.presenceLatest = { position: clean };\n if (this.presenceFlushing) return; // the in-flight loop will pick this up\n this.presenceFlushing = true;\n void (async () => {\n try {\n while (this.presenceLatest) {\n const next = this.presenceLatest;\n this.presenceLatest = null;\n const sealed = await sealPresence(keys.kPresence, this.docId, genesisId, this.clientId, next.position);\n this.transport.send({ t: \"presence\", position: sealed });\n }\n } catch {\n // A failed seal costs this one caret; the next move re-sends.\n } finally {\n this.presenceFlushing = false;\n }\n })();\n }\n\n private enqueue(task: () => Promise<void>): void {\n this.queue = this.queue.then(task).catch((err: unknown) => {\n // A failed task must not wedge the chain — but it must not vanish\n // either. Ingest failures ARE handled inside their own tasks (an\n // unopenable envelope becomes a deterministic no-op, not a throw), so\n // anything arriving here is unexpected. It also carries SUBMITS: the\n // seal-and-send half of submitFull rides this same chain, so a throw\n // here is a lost edit with no other trace — the exact shape B13 spent a\n // night chasing.\n this.sendFailures++;\n this.cb.onError?.({ where: \"enc.queue\", error: err });\n });\n }\n\n private onServer(msg: ServerMessage): void {\n switch (msg.t) {\n case \"welcome-enc\": {\n // Read OUTSIDE the serial chain: it is a plain number with no\n // dependency on keys or the mirror, and a consumer asking \"how big may\n // this file be\" should not wait on a rehydrate to find out.\n this.mediaMaxBlobBytes = msg.mediaMaxBlobBytes ?? null;\n this.enqueue(async () => {\n // Phase timings for the join-mount critical path (the \"joining a\n // session on a big document takes seconds\" report) — printed only\n // when a host armed __dxwPerf (benchmarks, e2e, the perf HUD).\n const perfOn = !!(globalThis as { __dxwPerf?: unknown }).__dxwPerf;\n const t: Record<string, number> = {};\n let t0 = performance.now();\n this.genesisId = msg.genesisId;\n this.keys = await deriveEpochKeys(this.docKey, msg.genesisId, this.stretchedCode);\n t.deriveMs = performance.now() - t0; t0 = performance.now();\n const cp = await openCheckpoint(this.keys.kContent, msg.docId, msg.genesisId, msg.checkpoint.seq, msg.checkpoint);\n const bytes = b64ToBytes(cp.docx);\n t.openMs = performance.now() - t0; t0 = performance.now();\n // The mirror is the local blind-mode \"server\": seeded exactly as a\n // rehydrating plaintext server would be — bytes, then sidecar,\n // then the tail replayed through the canonical pipeline.\n this.mirror = new DocumentSession(DocxDocument.load(bytes));\n this.mirror.setSeqFloor(msg.checkpoint.seq); // numbering continues from the checkpoint\n if (cp.sidecar) this.mirror.installSidecar(cp.sidecar as never);\n t.mirrorParseMs = performance.now() - t0; t0 = performance.now();\n this.docVersionBase += (this.replica?.docVersion ?? 0) + 1; // new doc object ⇒ repaint\n this.replica = new ClientReplica(bytes, (cp.sidecar ?? undefined) as never);\n this.replica.confirmedSeq = msg.checkpoint.seq;\n t.replicaParseMs = performance.now() - t0; t0 = performance.now();\n // Doc 16 §6 late-join: the addresses ride inside the SEALED body,\n // so they are available only here, after the checkpoint opened.\n // Tolerantly read — a checkpoint from an older build has none and\n // this replica simply keeps the reserve-but-cannot-fill behavior.\n applyMediaAddresses(this.replica.doc, cp.mediaMeta);\n applyMediaAddresses(this.mirror.doc, cp.mediaMeta);\n for (const env of msg.tail) await this.ingest(env);\n t.tailMs = performance.now() - t0;\n if (perfOn) {\n // eslint-disable-next-line no-console\n console.log(\n `STRESS-METRIC join-welcome docxBytes=${bytes.byteLength} tail=${msg.tail.length} ` +\n Object.entries(t).map(([k, v]) => `${k}=${v.toFixed(2)}`).join(\" \"),\n );\n }\n // Resume epilogue — same semantics as the plaintext connection.\n const resumed = this.resuming;\n this.resuming = null;\n if (resumed) {\n if (resumed.genesisId === msg.genesisId) {\n for (const intent of resumed.pending) {\n const env2 = await sealIntent(this.keys!.kContent, this.docId, msg.genesisId, intent as Intent);\n this.transport.send({ t: \"submit-enc\", envelope: env2 } as ClientMessage);\n }\n } else {\n this.cb.onEpochChange?.(resumed.genesisId, msg.genesisId);\n }\n }\n this.cb.onChange?.();\n // Doc 16 §5.3/§5.4: restore media addresses AND pixels from the\n // resumed bundle before volunteering — an E2EE checkpoint usually\n // carries the bytes, but a bundle-resuming holder is the one who\n // can rescue media the relay has already evicted.\n if (resumed?.mediaMeta) {\n const held = DocxDocument.load(resumed.confirmedBytes);\n for (const [part, meta] of resumed.mediaMeta) {\n this.replica.doc.mediaMeta.set(part, meta);\n const pixels = held.media(part);\n if (pixels && this.replica.doc.mediaStatus(part) === \"pending\") {\n this.replica.doc.installMedia(part, pixels);\n }\n }\n }\n this.media?.volunteer(msg.mediaNeeded);\n void this.media?.fetchPending(this.docId);\n });\n return;\n }\n case \"broadcast-enc\": {\n this.enqueue(async () => {\n if (!this.replica) return; // pre-welcome broadcasts are impossible; guard anyway\n for (const env of msg.entries) await this.ingest(env);\n if (this.replica.reloaded) this.docEpoch++;\n this.cb.onChange?.();\n this.scheduleSelfCheck();\n // Eager fetch (doc 16 §5.2 step 2) — an applied insertImage leaves\n // a hole this pulls immediately.\n void this.media?.fetchPending(this.docId);\n });\n return;\n }\n case \"media-request\": {\n this.media?.answerRequest(msg.sha);\n this.cb.onMediaRequest?.(msg.sha);\n return;\n }\n case \"media-upload\": {\n // Chosen re-supplier. In an encrypted room this re-seals with the\n // RECORDED IV so the ciphertext — and therefore the address — is\n // reproduced exactly; see MediaClient.resupply.\n void this.media?.resupply(this.docId, msg.sha);\n this.cb.onMediaRequest?.(msg.sha);\n return;\n }\n case \"media-ready\": {\n void this.media?.onReady(this.docId, msg.sha);\n this.cb.onMediaReady?.(msg.sha);\n return;\n }\n case \"media-unavailable\": {\n this.media?.onUnavailable(msg.sha);\n this.cb.onMediaUnavailable?.(msg.sha);\n return;\n }\n case \"welcome\": {\n // We hold the key, so this doc IS encrypted — a plaintext welcome is\n // a downgrade attempt (malicious server or hostile plaintext\n // re-seed) and is never obeyed (doc 13 §6, round-4 F11).\n this.cb.onRefused?.(\"mode-downgrade\");\n return;\n }\n case \"presence\": {\n const raw = msg.position;\n if (raw === null) {\n this.cb.onPresence?.(msg.participant, null);\n return;\n }\n if (!isSealedPresence(raw)) {\n // A PLAINTEXT position in an encrypted room. The e4 fence should\n // make this unreachable (a peer old enough to send one cannot join),\n // so treat it as noise rather than render it: accepting it would\n // quietly re-open the leak this change closes — the peer that sent\n // it also sent it to the server in the clear.\n return;\n }\n const keys = this.keys;\n const genesisId = this.genesisId;\n if (!keys || !genesisId) return; // pre-welcome presence: nothing to open it with\n // INBOUND opens are serialized on their own chain — separate from the\n // outbound coalescer above, and emphatically not `this.queue`. Serial\n // rather than coalescing because these are OTHER participants: two\n // carets from the same peer must not resolve out of order, and two\n // peers' carets must not be collapsed into one. The chain does not\n // accumulate — each link settles as its decrypt finishes, so its depth\n // is whatever the network delivered, not a backlog this code created.\n this.presenceInQueue = this.presenceInQueue\n .then(async () => {\n // The AAD binds the sender the SERVER stamped, so a relay that\n // re-attributes a blob to another participant fails to decrypt\n // rather than mislabelling a cursor.\n const opened = await openPresence(keys.kPresence, this.docId, genesisId, msg.participant, raw);\n // Post-open clamp: decryption proves WHO sealed it, never that the\n // contents are sane. A hostile participant can seal an unbounded\n // range list under a perfectly valid key.\n this.cb.onPresence?.(msg.participant, sanitizePresencePosition(opened as PresencePosition | null));\n })\n .catch(() => {\n // Undecryptable, tampered, replayed from another room, or garbage:\n // drop THIS participant's caret and nothing else. Never throws\n // into the ingest path — one bad peer must not cost the document.\n });\n return;\n }\n case \"roster\": {\n this.roster = msg.roster;\n this.cb.onRoster?.(msg.roster);\n return;\n }\n // Session lifecycle warnings are PLAINTEXT bookkeeping and identical in\n // both modes: the deadline is the server's own policy, not document\n // content, so a blind sequencer announces it in the clear. Dispatched\n // directly rather than through the serial queue — nothing here touches\n // keys, the mirror, or the replica.\n case \"session-warning\": {\n this.cb.onSessionWarning?.({ reason: msg.reason, inMs: msg.inMs });\n return;\n }\n case \"session-warning-cleared\": {\n this.cb.onSessionWarningCleared?.({ reason: msg.reason });\n return;\n }\n case \"gossip\": {\n this.enqueue(() => this.onGossip(msg.iv, msg.ciphertext));\n return;\n }\n case \"checkpointer\": {\n this.isCheckpointer = msg.active;\n return;\n }\n case \"refused\": {\n if (msg.reason === \"stale-base\") {\n // The server adopted a checkpoint past our envelope's base\n // (quiescent-checkpoint guard). By the time this refusal arrives\n // the broadcasts up to that checkpoint have been processed, so\n // re-seal the intent (same clientId/clientSeq — dedup-safe) with a\n // fresh base and resubmit. One-in-flight discipline makes lastSent\n // the only possible referent — but the BODY must be the replica's\n // PENDING copy, not lastSent verbatim: replayPending has already\n // transformed pending against every remote entry ingested since\n // (the same transform the server would have applied had the base\n // been fresh). Resealing the raw original at the bumped base makes\n // the server apply UNTRANSFORMED coordinates verbatim while the\n // optimistic doc holds the transformed form; the own-echo fast\n // path then snapshots the divergent optimistic doc as confirmed —\n // silent permanent divergence at the checkpoint boundary. If the\n // pending copy was dropped during replay, do NOT resubmit: the\n // edit no longer exists locally, and optimistic == canonical only\n // if it stays out of the log on both sides.\n const retry = this.lastSent;\n if (retry && this.replica && this.keys && this.genesisId) {\n this.enqueue(async () => {\n const body = this.replica!.pendingIntent(retry.clientId, retry.clientSeq);\n if (!body) return;\n const rebased = { ...body, base: this.replica!.confirmedSeq } as Intent;\n const env = await sealIntent(this.keys!.kContent, this.docId, this.genesisId!, rebased);\n this.transport.send({ t: \"submit-enc\", envelope: env } as ClientMessage);\n });\n }\n return;\n }\n if (msg.reason === \"rate-limit\") {\n // Throttled, not failed: reset the watchdog retry budget\n // (throttling must never escalate to drop+heal) and RE-DRIVE THE\n // WHOLE PENDING QUEUE after backoff — a refused op in a CHAINED\n // burst (each op addressing ids a previous op allocated) strands\n // everything behind it, and front-only recovery redelivers one op\n // per watchdog window, far too slow for a bulk client. Dedup by\n // (clientId, clientSeq) makes full-queue resends free.\n this.stuckRetries = 0;\n this.scheduleRateLimitRedrive();\n return;\n }\n this.cb.onRefused?.(msg.reason);\n return;\n }\n }\n }\n\n /** Re-drive the whole pending queue after rate-limit backoff. One timer\n * at a time; backoff doubles per refusal and resets when the queue\n * drains (see scheduleSelfCheck). */\n private scheduleRateLimitRedrive(): void {\n if (this.redriveTimer !== null) return;\n this.redriveBackoffMs = Math.min(this.redriveBackoffMs === 0 ? 300 : this.redriveBackoffMs * 2, 5000);\n this.redriveTimer = setTimeout(() => {\n this.redriveTimer = null;\n this.enqueue(async () => {\n if (!this.replica || !this.keys || !this.genesisId) return;\n for (const body of this.replica.pendingCopies()) {\n const rebased = { ...body, base: this.replica.confirmedSeq } as Intent;\n const env = await sealIntent(this.keys.kContent, this.docId, this.genesisId, rebased);\n this.transport.send({ t: \"submit-enc\", envelope: env } as ClientMessage);\n }\n });\n }, this.redriveBackoffMs);\n }\n\n /** Debounce a quiescent self-check: only when nothing is pending (a burst\n * still in flight legitimately diverges live from canonical), re-armed by\n * every broadcast so the check runs once the wire goes quiet. While\n * pending exists, arm the stuck-pending watchdog instead. */\n private scheduleSelfCheck(): void {\n if (!this.replica) return;\n if (this.replica.pendingCount !== 0) {\n this.scheduleStuckCheck();\n return;\n }\n this.redriveBackoffMs = 0; // queue drained — rate-limit backoff resets\n if (this.redriveTimer !== null) {\n clearTimeout(this.redriveTimer);\n this.redriveTimer = null;\n }\n if (this.stuckTimer !== null) {\n clearTimeout(this.stuckTimer);\n this.stuckTimer = null;\n }\n this.stuckKey = null;\n this.stuckRetries = 0;\n if (this.selfCheckTimer !== null) clearTimeout(this.selfCheckTimer);\n this.selfCheckTimer = setTimeout(() => {\n this.selfCheckTimer = null;\n this.enqueue(() => this.selfCheck());\n }, this.selfCheckDelayMs);\n }\n\n /** Watch the FRONT pending op; progress (a different front, or empty)\n * resets the retry budget. The window is 5× the self-check debounce. */\n private scheduleStuckCheck(): void {\n const front = this.replica?.firstPending();\n if (!front) return;\n const key = `${front.clientId}:${front.clientSeq}`;\n if (this.stuckKey !== key) {\n this.stuckKey = key;\n this.stuckRetries = 0;\n }\n if (this.stuckTimer !== null) clearTimeout(this.stuckTimer);\n this.stuckTimer = setTimeout(() => {\n this.stuckTimer = null;\n this.enqueue(() => this.stuckCheck(front.clientId, front.clientSeq));\n }, this.selfCheckDelayMs * 5);\n }\n\n /** Runs inside the serial queue: if the same front pending is still\n * stuck, resend its transformed copy at the current base; past the retry\n * budget, drop it and heal from the mirror (convergence over delivery). */\n private async stuckCheck(clientId: string, clientSeq: number): Promise<void> {\n if (!this.replica || !this.mirror || !this.keys || !this.genesisId) return;\n const front = this.replica.firstPending();\n if (!front || front.clientId !== clientId || front.clientSeq !== clientSeq) {\n // Progressed — if fully drained, the normal quiescent path re-arms.\n this.scheduleSelfCheck();\n return;\n }\n if (this.stuckRetries >= EncryptedCollabConnection.STUCK_MAX_RETRIES) {\n const liveHash = await docHash(this.replica.doc);\n const canonicalHash = await docHash(this.mirror.doc);\n const cp = this.mirror.checkpoint();\n this.docVersionBase += this.replica.docVersion + 1; // doc object replaced ⇒ repaint\n this.replica = new ClientReplica(cp.docx, cp.sidecar as never);\n this.replica.confirmedSeq = cp.seq;\n this.docEpoch++;\n this.selfHeals++;\n this.stuckKey = null;\n this.stuckRetries = 0;\n this.cb.onSelfHeal?.({ seq: cp.seq, liveHash, canonicalHash });\n this.cb.onChange?.();\n return;\n }\n this.stuckRetries++;\n const body = this.replica.pendingIntent(clientId, clientSeq);\n if (body) {\n const rebased = { ...body, base: this.replica.confirmedSeq } as Intent;\n const env = await sealIntent(this.keys.kContent, this.docId, this.genesisId, rebased);\n this.transport.send({ t: \"submit-enc\", envelope: env } as ClientMessage);\n }\n this.scheduleStuckCheck();\n }\n\n /** Runs INSIDE the serial queue (no ingest can interleave): compare the\n * optimistic replica against the mirror at quiescence; on drift, rebuild\n * the replica from the mirror — the same shape as a reload heal, but\n * automatic, local, and scoped to the one client that drifted. */\n private async selfCheck(): Promise<void> {\n if (!this.replica || !this.mirror) return;\n if (this.replica.pendingCount !== 0) return; // burst resumed — next drain re-arms\n const seq = this.mirror.seq;\n if (seq === this.lastSelfCheckSeq) return; // this state already verified\n // Macrotask hop (see the checkpoint duty in ingest): this task starts as\n // a continuation of whatever the serial chain last awaited, which can\n // drain inside an in-flight input dispatch — putting the first hash's\n // full-doc serialization inside a keystroke's handler. The pending/seq\n // recheck below covers state moving across the hop.\n await new Promise((resolve) => setTimeout(resolve, 0));\n if (this.replica.pendingCount !== 0 || this.mirror.seq !== seq) return; // raced a local submit\n const liveHash = await docHash(this.replica.doc);\n // Macrotask hop (see the checkpoint duty in ingest): the second hash's\n // full-doc serialization otherwise runs as a continuation of the first\n // hash's digest, which can drain inside an in-flight input dispatch.\n // The raced-a-local-submit recheck below already covers state moving\n // across this await.\n await new Promise((resolve) => setTimeout(resolve, 0));\n const canonicalHash = await docHash(this.mirror.doc);\n if (this.replica.pendingCount !== 0 || this.mirror.seq !== seq) return; // raced a local submit\n this.lastSelfCheckSeq = seq;\n if (liveHash === canonicalHash) return;\n // DRIFT (the B6a class): the optimistic machinery lost/garbled an edit\n // while the canonical derivation is intact. Rebuild the replica from the\n // mirror — byte-exact canonical state + the exact id table (checkpoint()\n // is a pure snapshot; save() is side-effect-free by invariant).\n const cp = this.mirror.checkpoint();\n this.docVersionBase += this.replica.docVersion + 1; // doc object replaced ⇒ repaint\n this.replica = new ClientReplica(cp.docx, cp.sidecar as never);\n this.replica.confirmedSeq = cp.seq;\n this.docEpoch++;\n this.selfHeals++;\n this.cb.onSelfHeal?.({ seq, liveHash, canonicalHash });\n this.cb.onChange?.();\n }\n\n /** Feed one sequenced envelope through mirror + replica (in seq order). */\n private async ingest(env: EnvelopeEntry): Promise<void> {\n let entry;\n try {\n const intent = await openIntent(this.keys!.kContent, this.docId, this.genesisId!, env);\n entry = this.mirror!.submit(intent);\n } catch {\n // Deterministic no-op: unopenable for us ⇒ unopenable for every\n // honest client (GCM auth is a pure function of bytes + key).\n entry = this.mirror!.ingestOpaqueFailure(env.clientId, env.clientSeq);\n }\n // The mirror re-derives seqs in arrival order; they must match the\n // sequencer's. A mismatch means entries were dropped/reordered in\n // transit — surface as a refusal (the resync story, doc 13 §2).\n if (entry.seq !== env.seq) {\n this.cb.onRefused?.(\"sequence-desync\");\n return;\n }\n this.replica!.receive([entry]);\n // A rejection is where an edit DIES, and in encrypted rooms this mirror is\n // the only place it happens — the server is blind and cannot validate, so\n // there is no server-side signal at all. Surfacing it here is the whole\n // reason the channel exists: the stale image-size bound rejected every\n // large photo identically on every replica, which means nothing diverged\n // and nothing complained. See ConnectionCallbacks.onIntentRejected.\n if (entry.kind === \"rejected\" && entry.clientId === this.clientId) {\n this.cb.onIntentRejected?.({ reason: entry.reason, clientSeq: entry.clientSeq });\n }\n if (entry.kind === \"applied\") {\n this.activity.push({ seq: entry.seq, clientId: entry.intent.clientId, kind: entry.intent.kind });\n if (this.activity.length > 100) this.activity.splice(0, this.activity.length - 100);\n }\n\n // Checkpoint duty (doc 13 §3): the designated client uploads its\n // mirror's canonical state every K seqs. The mirror is confirmed-only\n // by construction (no optimistic edits ever touch it), satisfying the\n // round-4 F17 rule structurally; the server adopts it only if it is\n // quiescent (exactly at the log head) — a miss just retries next round.\n if (this.isCheckpointer && env.seq % EncryptedCollabConnection.CHECKPOINT_EVERY === 0) {\n // Hop to a fresh MACROTASK before the heavy work. checkpoint() and\n // docHash() serialize the full canonical doc (~60-100ms at 500 pages),\n // and as bare promise continuations they drain at listener boundaries\n // INSIDE an in-flight input dispatch — the cost landed inside a\n // keystroke's handler and delayed its paint (measured: 40-85ms spikes\n // on the keystrokes at these seq points). In its own task the work\n // runs BETWEEN dispatches instead. Still on the serial chain: no later\n // ingest interleaves, so the mirror is still exactly at env.seq.\n await new Promise((resolve) => setTimeout(resolve, 0));\n const cp = this.mirror!.checkpoint();\n const sealed = await sealCheckpoint(this.keys!.kContent, this.docId, this.genesisId!, cp.seq, {\n docx: bytesToB64(cp.docx),\n sidecar: cp.sidecar,\n docHash: await docHash(this.mirror!.doc),\n // Doc 16 §6 late-join, sealed half: the MIRROR never installs pixels,\n // but it holds every REGISTRATION — which is all a joiner needs to go\n // and fetch them. Inside the sealed body, so a blind server still\n // learns nothing about the document's part structure.\n mediaMeta: mediaAddressesOf(this.mirror!.doc),\n });\n this.transport.send({ t: \"checkpoint\", checkpoint: { seq: cp.seq, ...sealed } } as ClientMessage);\n }\n\n // Divergence DETECTION (doc 13 §2 / round-4 blocker 3): every K seqs,\n // hash the mirror's canonical doc, remember it, and gossip it sealed.\n // Every honest client hashes at the same seq points, so peers' blobs\n // are directly comparable; a mismatch means an engine bug or a\n // tampered history — surfaced as `divergence` (the resync path:\n // rejoin from the welcome, which is always available).\n if (env.seq % EncryptedCollabConnection.GOSSIP_EVERY === 0) {\n // Same macrotask hop as the checkpoint duty above: docHash serializes\n // the whole canonical doc, and every honest client must hash the state\n // at exactly this seq — the serial chain guarantees that across the hop.\n await new Promise((resolve) => setTimeout(resolve, 0));\n const hash = await docHash(this.mirror!.doc);\n this.ownHashes.set(env.seq, hash);\n if (this.ownHashes.size > 5) {\n this.ownHashes.delete(Math.min(...this.ownHashes.keys()));\n }\n const iv = new Uint8Array(12);\n crypto.getRandomValues(iv);\n const body = new TextEncoder().encode(JSON.stringify({ seq: env.seq, hash }));\n const aad = new TextEncoder().encode(`gs:${this.docId}:${this.genesisId}`);\n const ct = await crypto.subtle.encrypt(\n { name: \"AES-GCM\", iv: iv as BufferSource, additionalData: aad as BufferSource },\n (this.keys as unknown as { kContent: CryptoKey }).kContent,\n body as BufferSource,\n );\n this.transport.send({ t: \"gossip\", iv: bytesToB64(iv), ciphertext: bytesToB64(new Uint8Array(ct)) } as ClientMessage);\n }\n }\n\n /** Verify a peer's gossiped hash against our own at the same seq. */\n private async onGossip(iv: string, ciphertext: string): Promise<void> {\n if (!this.keys || !this.genesisId) return;\n let claim: { seq: number; hash: string };\n try {\n const aad = new TextEncoder().encode(`gs:${this.docId}:${this.genesisId}`);\n const pt = await crypto.subtle.decrypt(\n { name: \"AES-GCM\", iv: b64ToBytes(iv) as BufferSource, additionalData: aad as BufferSource },\n (this.keys as unknown as { kContent: CryptoKey }).kContent,\n b64ToBytes(ciphertext) as BufferSource,\n );\n claim = JSON.parse(new TextDecoder().decode(pt));\n } catch {\n return; // garbage gossip: ignorable (unlike intents, it holds no seq)\n }\n const own = this.ownHashes.get(claim.seq);\n if (own !== undefined && own !== claim.hash) {\n // Two honest clients disagreeing on canonical state at the same seq:\n // the round-4 blocker-3 detection case. Telemetry-worthy always;\n // recovery is a rejoin (fresh welcome-enc replay).\n this.cb.onRefused?.(\"divergence\");\n }\n }\n}\n","import { ClientTransport } from \"./connection.js\";\nimport { ClientMessage, ServerMessage } from \"./protocol.js\";\n\n/** Minimal structural shape of a browser WebSocket (or a test double). */\nexport interface SocketLike {\n send(data: string): void;\n addEventListener(type: \"message\", cb: (ev: { data: unknown }) => void): void;\n addEventListener(type: \"open\", cb: () => void): void;\n}\n\n/**\n * Adapt a browser WebSocket to a ClientTransport: JSON-frame outbound client\n * messages, parse inbound server messages. Buffers sends until the socket is\n * open. The only browser-touching client code — everything else in\n * @wordinweb/collab is transport-free.\n */\nexport function createWebSocketTransport(socket: SocketLike): ClientTransport {\n let open = false;\n const backlog: string[] = [];\n socket.addEventListener(\"open\", () => {\n open = true;\n for (const frame of backlog.splice(0)) socket.send(frame);\n });\n let handler: ((msg: ServerMessage) => void) | null = null;\n socket.addEventListener(\"message\", (ev) => {\n let msg: ServerMessage;\n try {\n msg = JSON.parse(typeof ev.data === \"string\" ? ev.data : String(ev.data)) as ServerMessage;\n } catch {\n return;\n }\n handler?.(msg);\n });\n return {\n send(msg: ClientMessage): void {\n const frame = JSON.stringify(msg);\n if (open) socket.send(frame);\n else backlog.push(frame);\n },\n onMessage(cb): void {\n handler = cb;\n },\n };\n}\n","import type { ClientMessage, ServerMessage } from \"./protocol.js\";\nimport type { ClientTransport } from \"./connection.js\";\n\n/**\n * Whether this client's edits can currently reach the sequencer.\n *\n * - `live` the round trip completed recently; submits will land.\n * - `reconnecting` the socket is gone and a retry is scheduled. TRANSIENT and\n * usually invisible — a laptop lid closed for ten seconds\n * passes through here and back to `live` without the user\n * noticing.\n * - `lost` the retries are exhausted. Needs a human: reconnect or\n * reload.\n *\n * A NON-LIVE STATE IS NOT A WRITE GATE (doc 12 §5: offline editing is\n * first-class). What consumers MUST guarantee on anything but `live` is that\n * nothing typed can be LOST: edits apply locally and are recorded to the\n * durable offline tail (doc 15 §2) for replay on reconnect — the react\n * layer's capture path does exactly this. The write gate (`writesBlocked`)\n * stays reserved for server REFUSALS, where accepting a keystroke really is\n * accepting-then-losing it. An editable surface whose disconnected edits are\n * neither kept nor gated remains the silent-loss bug this type was created\n * to surface — keep them either captured or blocked, never dropped.\n */\nexport type ConnectionState = \"live\" | \"reconnecting\" | \"lost\";\n\nexport interface LivenessOptions {\n /** Gap between probes once the previous one was answered. */\n intervalMs?: number;\n /** How long a probe may go unanswered before the socket is declared dead. */\n timeoutMs?: number;\n setTimer?: (fn: () => void, ms: number) => unknown;\n clearTimer?: (t: unknown) => void;\n}\n\n/** Probe cadence. 15s/10s means a dead socket is noticed within ~25s at worst\n * and the wake-up probe (below) makes the common phone case immediate, while\n * costing two tiny frames a minute on an idle tab. */\nconst DEFAULT_INTERVAL_MS = 15_000;\nconst DEFAULT_TIMEOUT_MS = 10_000;\n\n/**\n * Application-level heartbeat over an established transport.\n *\n * WHY THE TRANSPORT CANNOT ANSWER THIS ITSELF. `onclose` and `onerror` catch a\n * socket that dies politely — a server restart, a closed lid that the OS\n * notices, a dropped Wi-Fi association. They do NOT catch the case this whole\n * arc exists for: a phone browser suspended mid-session leaves a HALF-OPEN\n * connection where the peer is gone but nothing local knows it. No event\n * fires, `readyState` still reads OPEN, and `send()` keeps succeeding into a\n * void. Asking for a reply and noticing its absence is the only way to learn\n * the truth, so the heartbeat is not a belt-and-braces addition to the close\n * handlers — it is the only detector that covers the motivating scenario.\n *\n * Timers are injected so tests drive the clock deterministically, matching\n * BundlePersister rather than inventing a second convention.\n */\nexport class LivenessMonitor {\n private probeTimer: unknown = null;\n private deadlineTimer: unknown = null;\n private nonce = 0;\n /** The nonce of the probe currently awaiting an answer; null when idle. */\n private awaiting: number | null = null;\n private stopped = false;\n\n constructor(\n private readonly send: (msg: ClientMessage) => void,\n /** Called ONCE per monitor when a probe goes unanswered. */\n private readonly onDead: () => void,\n private readonly opts: LivenessOptions = {},\n ) {}\n\n private get intervalMs(): number {\n return this.opts.intervalMs ?? DEFAULT_INTERVAL_MS;\n }\n private get timeoutMs(): number {\n return this.opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n }\n private setTimer(fn: () => void, ms: number): unknown {\n return (this.opts.setTimer ?? ((f, m) => setTimeout(f, m)))(fn, ms);\n }\n private clearTimer(t: unknown): void {\n if (t !== null) (this.opts.clearTimer ?? clearTimeout)(t as never);\n }\n\n /** Begin probing. Call when the socket opens. */\n start(): void {\n if (this.stopped) return;\n this.arm();\n }\n\n /** Stop every timer and answer nothing further. Idempotent — the reconnect\n * path calls it from both the close handler and the effect teardown. */\n stop(): void {\n this.stopped = true;\n this.clearTimer(this.probeTimer);\n this.clearTimer(this.deadlineTimer);\n this.probeTimer = null;\n this.deadlineTimer = null;\n this.awaiting = null;\n }\n\n /** Schedule the next probe one interval out. */\n private arm(): void {\n if (this.stopped) return;\n this.clearTimer(this.probeTimer);\n this.probeTimer = this.setTimer(() => {\n this.probeTimer = null;\n this.probe();\n }, this.intervalMs);\n }\n\n /**\n * Probe NOW rather than waiting for the interval — wired to\n * `visibilitychange`, because a tab waking from sleep is the exact moment\n * the connection is most likely already dead and the worst moment to make\n * the user wait a full interval to find out. Without this the motivating\n * scenario (unlock phone, start typing) spends up to 25s in the silent-loss\n * state the arc exists to close.\n *\n * A probe already in flight is left alone: re-arming its deadline on every\n * focus event would let a user who keeps switching tabs postpone the verdict\n * indefinitely.\n */\n probe(): void {\n if (this.stopped || this.awaiting !== null) return;\n const nonce = ++this.nonce;\n this.awaiting = nonce;\n this.deadlineTimer = this.setTimer(() => {\n this.deadlineTimer = null;\n // Still waiting on the same probe ⇒ the round trip never completed.\n if (this.stopped || this.awaiting !== nonce) return;\n this.stop(); // one verdict per monitor; the reconnect owns what follows\n this.onDead();\n }, this.timeoutMs);\n // Sent AFTER the deadline is armed: a transport that throws synchronously\n // must still be caught by the timeout rather than leaving a monitor that\n // silently never probes again.\n try {\n this.send({ t: \"ping\", nonce });\n } catch {\n // Ignored on purpose — the armed deadline above is the report. Throwing\n // here would propagate into a timer callback with no handler.\n }\n }\n\n /**\n * Feed an inbound frame to the monitor.\n *\n * ONLY A MATCHING PONG COUNTS AS PROOF OF LIFE — deliberately, and this is\n * the subtle decision in the file. The obvious optimisation is to treat any\n * inbound traffic as evidence the connection works, which is what most\n * heartbeats do. It is wrong here: it assumes both directions fail together.\n * A connection whose OUTBOUND half is broken while broadcasts still arrive\n * would look permanently healthy under that rule, and that is precisely the\n * user-visible bug being fixed — edits going nowhere while the screen keeps\n * updating with everyone else's. A pong proves the full round trip an edit\n * actually needs, so nothing weaker is accepted.\n */\n noteInbound(msg: ServerMessage): void {\n if (this.stopped) return;\n if (msg.t !== \"pong\") return;\n if (this.awaiting === null || msg.nonce !== this.awaiting) return; // stale\n this.awaiting = null;\n this.clearTimer(this.deadlineTimer);\n this.deadlineTimer = null;\n this.arm();\n }\n}\n\n/**\n * Wrap a transport with a heartbeat, returning both halves.\n *\n * A WRAPPER RATHER THAN A HOOK INSIDE THE CONNECTION, because\n * `ClientTransport.onMessage` holds exactly ONE handler — registering a second\n * would overwrite the connection's own and silently stop the document\n * updating. Teeing here keeps that single-handler contract intact and leaves\n * both CollabConnection and EncryptedCollabConnection completely unaware that\n * a heartbeat exists.\n *\n * Pongs are consumed rather than forwarded: neither connection class has a\n * `default:` in its dispatch, so forwarding would be harmless — but passing a\n * message no consumer handles invites exactly the \"why is this ignored?\"\n * confusion the rest of this protocol avoids.\n */\nexport function monitorTransport(\n inner: ClientTransport,\n onDead: () => void,\n opts: LivenessOptions = {},\n): { transport: ClientTransport; monitor: LivenessMonitor } {\n const monitor = new LivenessMonitor((msg) => inner.send(msg), onDead, opts);\n const transport: ClientTransport = {\n send: (msg) => inner.send(msg),\n onMessage: (cb) => {\n inner.onMessage((msg) => {\n monitor.noteInbound(msg);\n if (msg.t === \"pong\") return;\n cb(msg);\n });\n },\n };\n return { transport, monitor };\n}\n","import { parseBundleKey, type BundleStore, type DocBundle, type StoredDocSummary } from \"@wordinweb/collab/client\";\n\n/** The tiny record `list()` reads instead of the bundle (see below). */\ninterface MetaRecord {\n docId: string;\n savedAt: number;\n byteLength: number;\n}\n\n/**\n * Browser BundleStore over IndexedDB — the only browser storage that fits\n * multi-MB binary docs (doc 12 §4). Bundles are stored structured-clone-ably\n * (Uint8Array clones natively) in `bundles`, keyed by docId. A second store,\n * `meta`, holds a per-key {savedAt, byteLength} record written in the SAME\n * transaction as each bundle: `list()` reads only key names and these tiny\n * records, so enumerating ten multi-MB documents never deserialises their\n * bytes. Bundles written by the v1 schema have no meta record; `list()`\n * reads those once and backfills, so the cost is paid one time per legacy\n * record, not per listing.\n *\n * Every method opens lazily so constructing the store never touches the API\n * (SSR-safe); callers on platforms without IndexedDB should inject the\n * in-memory store instead.\n *\n * Durability honesty (doc 12 §4, round-4 F8): IndexedDB is evictable —\n * `navigator.storage.persist()` is requested once per store on first write,\n * but browsers may deny it; Safari time-boxes script-writable storage and\n * private windows drop it on close. The download-.docx escape hatch in the\n * UI, not this class, is the user's guarantee. Failures here REJECT — a\n * swallowed quota error would let the user believe work is saved when it is\n * not, so surfacing them is the caller's contract, not an option.\n */\n/**\n * Legacy records measured per `list()` call. Small on purpose: each costs a\n * full bundle read, and the cost is paid on a page load.\n */\nconst LEGACY_BACKFILL_PER_LIST = 4;\n\nexport class IndexedDbBundleStore implements BundleStore {\n private db: Promise<IDBDatabase> | null = null;\n private persistRequested = false;\n\n constructor(private dbName = \"wordinweb-bundles\") {}\n\n private open(): Promise<IDBDatabase> {\n if (!this.db) {\n this.db = new Promise((resolve, reject) => {\n const req = indexedDB.open(this.dbName, 2);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains(\"bundles\")) db.createObjectStore(\"bundles\", { keyPath: \"docId\" });\n if (!db.objectStoreNames.contains(\"meta\")) db.createObjectStore(\"meta\", { keyPath: \"docId\" });\n };\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n }\n return this.db;\n }\n\n /** One request in one transaction, as a promise. */\n private tx<T>(stores: string | string[], mode: IDBTransactionMode, run: (t: IDBTransaction) => IDBRequest<T>): Promise<T> {\n return this.open().then(\n (db) =>\n new Promise<T>((resolve, reject) => {\n const t = db.transaction(stores, mode);\n const req = run(t);\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n // Quota failures can abort the transaction without a request error.\n t.onabort = () => reject(t.error ?? new Error(\"IndexedDB transaction aborted\"));\n }),\n );\n }\n\n async get(docId: string): Promise<DocBundle | null> {\n return (await this.tx(\"bundles\", \"readonly\", (t) => t.objectStore(\"bundles\").get(docId))) ?? null;\n }\n\n async put(bundle: DocBundle): Promise<void> {\n if (!this.persistRequested) {\n this.persistRequested = true;\n // Best-effort, once: ask the browser not to evict us (doc 12 §4).\n void globalThis.navigator?.storage?.persist?.();\n }\n const db = await this.open();\n // Bundle + meta land atomically: a listing must never describe a bundle\n // that failed to write, or miss one that succeeded.\n await new Promise<void>((resolve, reject) => {\n const t = db.transaction([\"bundles\", \"meta\"], \"readwrite\");\n t.objectStore(\"bundles\").put(bundle);\n const meta: MetaRecord = { docId: bundle.docId, savedAt: bundle.savedAt, byteLength: bundle.confirmedBytes.byteLength };\n t.objectStore(\"meta\").put(meta);\n t.oncomplete = () => resolve();\n t.onerror = () => reject(t.error);\n t.onabort = () => reject(t.error ?? new Error(\"IndexedDB transaction aborted\"));\n });\n }\n\n async delete(docId: string): Promise<void> {\n const db = await this.open();\n await new Promise<void>((resolve, reject) => {\n const t = db.transaction([\"bundles\", \"meta\"], \"readwrite\");\n t.objectStore(\"bundles\").delete(docId);\n t.objectStore(\"meta\").delete(docId);\n t.oncomplete = () => resolve();\n t.onerror = () => reject(t.error);\n t.onabort = () => reject(t.error ?? new Error(\"IndexedDB transaction aborted\"));\n });\n }\n\n async list(): Promise<StoredDocSummary[]> {\n const keys = (await this.tx(\"bundles\", \"readonly\", (t) => t.objectStore(\"bundles\").getAllKeys())) as string[];\n const metas = (await this.tx(\"meta\", \"readonly\", (t) => t.objectStore(\"meta\").getAll())) as MetaRecord[];\n const byKey = new Map(metas.map((m) => [m.docId, m]));\n const out: StoredDocSummary[] = [];\n // BACKFILL IS BOUNDED, and that bound is the whole point.\n //\n // Records written before the meta store existed carry no metadata, and the\n // only way to recover a bundle's size is to read the bundle — megabytes,\n // deserialised, to learn two numbers. Doing that for every legacy key made\n // opening the page deserialise the entire store: on a browser holding a\n // pile of leaked versions it took over thirty seconds and a matching spike\n // in memory, before anything rendered. Precisely the browsers that most\n // need the reclaim this listing feeds.\n //\n // So a few per call. The listing stays fast, sizes converge over a handful\n // of loads, and until a record is measured it reports `byteLength: 0` —\n // read by callers as \"unknown\", never as \"empty\" (see StoredDocSummary).\n let budget = LEGACY_BACKFILL_PER_LIST;\n for (const key of keys) {\n let meta = byKey.get(key);\n if (!meta && budget > 0) {\n budget--;\n const bundle = await this.get(key);\n if (!bundle) continue; // deleted between the two reads\n meta = { docId: key, savedAt: bundle.savedAt, byteLength: bundle.confirmedBytes.byteLength };\n await this.tx(\"meta\", \"readwrite\", (t) => t.objectStore(\"meta\").put(meta));\n }\n out.push({\n ...parseBundleKey(key),\n key,\n // Unmeasured legacy record: 0 means UNKNOWN. A byte-budget caller must\n // not treat it as free, and must not delete it for being large either.\n savedAt: meta?.savedAt ?? 0,\n byteLength: meta?.byteLength ?? 0,\n });\n }\n return out;\n }\n}\n"],"mappings":"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","names":["crypto"]}
|