{"version":3,"sources":["../src/collab/index.ts","../src/collab/session.ts","../src/collab/election.ts","../src/collab/provider.ts","../src/core/ambient.ts","../src/core/topics.ts"],"sourcesContent":["/**\n * `@ai-matrx/realtime/collab` — Yjs over Supabase Broadcast on the ONE RealtimeManager, with the live\n * saver election. Framework-free (React lives in `/collab/react`). Moved from matrx-frontend\n * `lib/collab` and `features/workbooks/collab/autosave-writer.ts`.\n */\nexport { openCollabSession } from \"./session\";\nexport type { CollabSession, OpenCollabSessionOptions } from \"./session\";\n\nexport { DEFAULT_ANTI_ENTROPY_MS, SupabaseYjsProvider } from \"./provider\";\nexport type { SupabaseYjsProviderOptions } from \"./provider\";\n\nexport { electCollabHost, mayAutosave, remoteOriginOf, REMOTE_PARAM_TAG } from \"./election\";\n\nexport type {\n  AwarenessMessage,\n  AwarenessState,\n  CollabPeer,\n  HostElectionResult,\n  StateRequestMessage,\n  StateResponseMessage,\n  WorkbookCollabContext,\n  YjsUpdateMessage,\n} from \"./types\";\n","/**\n * openCollabSession — one co-editing room: a Y.Doc, its Awareness, the\n * Supabase-Broadcast provider on the app's ONE RealtimeManager, and the saver\n * election (election.ts, frozen as the editors ran it). \"Exactly one peer per\n * session saves\" is `isSaver()`.\n */\nimport * as Y from \"yjs\";\nimport { Awareness, removeAwarenessStates } from \"y-protocols/awareness\";\n\nimport type { RealtimeManager } from \"../core/manager\";\nimport { electCollabHost } from \"./election\";\nimport { SupabaseYjsProvider } from \"./provider\";\nimport type { CollabPeer } from \"./types\";\n\nexport interface CollabSession {\n  doc: Y.Doc;\n  awareness: Awareness;\n  /** Exactly one peer per session saves. */\n  isSaver(): boolean;\n  onSaverChange(cb: (isSaver: boolean) => void): () => void;\n  destroy(): void;\n}\n\nexport interface OpenCollabSessionOptions {\n  /** `<noun>:<id>`, e.g. `workbook:<uuid>`. The wire topic is `yjs:<noun>:<id>`. */\n  channel: `${string}:${string}`;\n  /** Default: the app's ONE manager (`currentRealtimeManager`); a second only in tests. */\n  manager?: RealtimeManager;\n  me: CollabPeer;\n  /** Workbooks true (one tab of a person writes); documents false (each originating tab writes its own edit). */\n  tabTieBreak: boolean;\n  /** Tests / harnesses only; see SupabaseYjsProvider. */\n  antiEntropyIntervalMs?: number;\n  readAntiEntropyIntervalMs?: () => Promise<number>;\n}\n\nexport function openCollabSession(o: OpenCollabSessionOptions): CollabSession {\n  const cut = o.channel.indexOf(\":\");\n  const noun = o.channel.slice(0, cut);\n  const id = o.channel.slice(cut + 1);\n\n  const doc = new Y.Doc();\n  const awareness = new Awareness(doc);\n  awareness.setLocalState({ uid: o.me.uid, clientId: o.me.clientId, name: o.me.name, color: o.me.color });\n\n  const listeners = new Set<(isSaver: boolean) => void>();\n  let destroyed = false;\n  let last: boolean | null = null;\n\n  const compute = (): boolean =>\n    electCollabHost(\n      [...awareness.getStates().values()] as Array<Record<string, unknown>>,\n      o.me.uid,\n      o.me.clientId,\n      provider.isTabLeader(),\n      o.tabTieBreak,\n    ).isHost;\n\n  const settle = (): void => {\n    if (destroyed) return;\n    const now = compute();\n    if (now === last) return;\n    last = now;\n    for (const cb of [...listeners]) cb(now);\n  };\n\n  const provider = new SupabaseYjsProvider({\n    workbookId: id,\n    channelPrefix: noun,\n    clientId: o.me.clientId,\n    doc,\n    awareness,\n    ...(o.manager ? { manager: o.manager } : {}),\n    ...(o.antiEntropyIntervalMs !== undefined ? { antiEntropyIntervalMs: o.antiEntropyIntervalMs } : {}),\n    ...(o.readAntiEntropyIntervalMs ? { readAntiEntropyIntervalMs: o.readAntiEntropyIntervalMs } : {}),\n    onLeaderChange: settle,\n  });\n  awareness.on(\"change\", settle);\n\n  // Awareness is only sent when it changes, so a joiner and the peers already there would not learn of\n  // each other until a cursor moved. Announce on connect, and answer every newcomer once with our state\n  // (the newcomer then sees us as \"updated\", not \"added\", so this cannot ping-pong).\n  const announce = (): void => {\n    const mine = awareness.getLocalState();\n    if (!destroyed && mine) awareness.setLocalState({ ...mine });\n  };\n  awareness.on(\"update\", (c: { added: number[] }) => {\n    if (c.added.some((id) => id !== doc.clientID)) announce();\n  });\n  void provider.connect().then(() => {\n    announce();\n    settle();\n  }, (err: unknown) => {\n    console.warn(`[collab] ${o.channel} did not connect`, err);\n  });\n  last = compute();\n\n  return {\n    doc,\n    awareness,\n    isSaver: () => (destroyed ? false : compute()),\n    onSaverChange(cb) {\n      listeners.add(cb);\n      return () => void listeners.delete(cb);\n    },\n    destroy() {\n      if (destroyed) return;\n      removeAwarenessStates(awareness, [doc.clientID], \"leave\");\n      provider.disconnect();\n      destroyed = true;\n      listeners.clear();\n      awareness.destroy();\n      doc.destroy();\n    },\n  };\n}\n","/**\n * Who writes the canonical snapshot in a collab workbook — pure, so it loads in the editor without yjs\n * (the session file pulls yjs in; the editor must not).\n */\n\n/** The sentinel the session stamps on a mutation that ARRIVED from a peer (value: the peer's uid). */\nexport const REMOTE_PARAM_TAG = \"__matrxRemote\";\n\n/**\n * THE host election (the host writes the canonical snapshot for OTHER people's edits). The lowest uid\n * wins. Two tabs of ONE person share a uid; with `tabTieBreak` the tab's own client id breaks the tie so\n * exactly one tab is host (the document editor keeps the uid-only rule: there every originating tab\n * writes its own edit).\n */\nexport function electCollabHost(\n  states: Array<Record<string, unknown>>,\n  selfUid: string,\n  selfClientId: string,\n  tabLeader: boolean,\n  tabTieBreak = false,\n): { isHost: boolean; hostUid: string | null } {\n  const keys = states\n    .filter((s): s is Record<string, unknown> & { uid: string } => typeof s.uid === \"string\")\n    .map((s) => ({ uid: s.uid, clientId: typeof s.clientId === \"string\" ? s.clientId : \"\" }))\n    .sort((a, b) => {\n      if (a.uid !== b.uid) return a.uid < b.uid ? -1 : 1;\n      return a.clientId < b.clientId ? -1 : a.clientId > b.clientId ? 1 : 0;\n    });\n  const host = keys[0] ?? null;\n  const sameUid = !!host && host.uid === selfUid;\n  return {\n    isHost: sameUid && (!tabTieBreak || host.clientId === selfClientId) && tabLeader,\n    hostUid: host?.uid ?? null,\n  };\n}\n\n/**\n * Who may AUTOSAVE after one mutation, so that one edit is written exactly once (review 2026-10-15:\n * with two tabs of one person open, the tab that made the edit AND the tab that received it both wrote\n * a snapshot and patched the workbook).\n *  - An edit made HERE (`remoteFrom` null): written here when this person's tabs are the host\n *    (`hostUid` is me, or nobody is known yet = solo). If another person is host, the host writes it.\n *  - An edit that ARRIVED from a peer: only the elected host writes it, and never when it came from\n *    this same person (the tab that made it writes it).\n */\nexport function mayAutosave(i: {\n  collab: boolean;\n  isHost: boolean;\n  hostUid: string | null;\n  selfUid: string;\n  remoteFrom: string | true | null;\n}): boolean {\n  if (!i.collab) return true;\n  if (i.remoteFrom === null) return i.hostUid === null || i.hostUid === i.selfUid;\n  return i.isHost && i.remoteFrom !== i.selfUid;\n}\n\n/** Where a mutation that Univer just ran came from, read off the tag the session stamps on remote ones. */\nexport function remoteOriginOf(params: unknown): string | true | null {\n  if (!params || typeof params !== \"object\" || Array.isArray(params)) return null;\n  const tag = (params as Record<string, unknown>)[REMOTE_PARAM_TAG];\n  if (typeof tag === \"string\") return tag;\n  return tag === true ? true : null;\n}\n","/**\n * SupabaseYjsProvider — Yjs sync transport over Supabase Broadcast.\n *\n * Self-contained: no Univer dependencies. Carries Yjs document updates and\n * y-protocols awareness (presence) between peers in a single workbook channel.\n *\n * Wire protocol (see `./types.ts`):\n *   event: \"y-update\"        — base64 Yjs updateV2, optionally chunked\n *   event: \"y-awareness\"     — base64 awareness update\n *   event: \"y-request-state\" — new joiner asks existing peers for full state\n *   event: \"y-state\"         — full state response targeted at one clientId\n *   event: \"y-sv\"            — anti-entropy probe: state vector + delete-set digest\n *\n * Design notes:\n *  - Outbound y-doc updates use a \"remote\" origin convention so the doc.update\n *    listener can avoid echoing applied remote updates back onto the wire.\n *  - Initial state sync is best-effort: 1500ms \"alone timer\" decides solo mode.\n *  - Awareness outbound is throttled at ~50ms to coalesce cursor spam.\n *\n * REALTIME: `@ai-matrx/realtime` owns the channel. THE TOPIC IS THE ROOM here\n * (pure broadcast), so the package puts the declared topic on the wire verbatim\n * and ref-counts one underlying channel per room — which is precisely the\n * problem the `client` option existed to work around (\"supabase-js returns the\n * existing channel object for a duplicate topic and a second subscribe on it\n * fails\"), so that option is gone and the verification harness passes two\n * MANAGERS instead of two clients.\n *\n * What else the adoption bought this provider: the decoupled ordered handler\n * queue (a burst of chunked frames no longer runs back-to-back on the socket\n * callback path — the frozen-tab class, and this provider ships 200KB base64\n * frames), jittered reconnect with the stability reset, tab-sleep and network\n * awareness, dedup, and diagnostics.\n *\n * AND THE CATCH-UP. Realtime has no replay, and a CRDT is not exempt: every\n * peer update that landed while the socket was down is gone, and Yjs cannot\n * know it is missing them — the doc simply stays quietly divergent. The correct\n * re-read for a CRDT is to ask the peers for state again, which is exactly what\n * a new joiner already does, so `onBackfill` re-sends `y-request-state` — and\n * EVERY state answer addressed to us is merged, not only the first (Yjs applies\n * are idempotent; \"first answer wins\" made the catch-up a no-op). The gap runs\n * both ways, so the catch-up also re-sends this member's whole state: its own\n * offline edits reach the peers.\n *\n * ANTI-ENTROPY. Broadcast is at-most-once, and a frame can be lost while the\n * socket stays up (rate limit, a hiccup that never becomes a disconnect). Yjs\n * parks every later update that builds on the lost one as pending, forever,\n * and a lost delete leaves no trace at all. So while connected each member\n * probes the room with `y-sv` — its state vector plus a digest of its delete\n * set — (a) shortly after it holds pending structs, (b) once after a burst of\n * local edits goes quiet, and (c) on a jittered knob-backed interval, but only\n * if the doc changed since the last tick or something is still pending. A peer\n * that holds structs or deletes the prober lacks answers with\n * `encodeStateAsUpdateV2(doc, theirVector)` as a targeted `y-state`; a peer that\n * lacks something answers once with its own probe (marked `reply`, which is never\n * answered with another probe). Equal vectors and digests produce no frame, so an\n * idle room is silent. Our own probes are rate-limited (MIN_PROBE_GAP_MS).\n *\n * TEARDOWN. `disconnect()` sends the pending awareness (the \"leave\") before it\n * closes, so a caller tears down synchronously — a deferred disconnect left an\n * unmounted screen's provider applying the room's frames to its doc. A destroyed\n * doc disconnects its provider, and nothing is ever applied to one.\n *\n * Echo suppression is the package default now (it was `broadcast: {self:false}`,\n * the same intent). The vestigial `presence: {key}` in the old channel config is\n * gone: this provider binds no presence handlers and never called `track()`, so\n * it published nothing — awareness rides the `y-awareness` broadcast instead.\n *\n */\nimport * as Y from \"yjs\";\nimport {\n  Awareness,\n  applyAwarenessUpdate,\n  encodeAwarenessUpdate,\n} from \"y-protocols/awareness\";\nimport { currentRealtimeManager } from \"../core/ambient\";\nimport { defineChannelNamespace } from \"../core/topics\";\nimport type { ChannelHandle, RealtimeManager } from \"../core/manager\";\n\nexport type SupabaseYjsProviderOptions = {\n  /**\n   * Opaque resource identifier (e.g. workbookId, documentId). Combined with\n   * `channelPrefix` to form the Broadcast channel name, so distinct resource\n   * types live on distinct channels even when their UUIDs are unrelated.\n   */\n  workbookId: string;\n  /**\n   * Channel namespace — defaults to `\"workbook\"` for back-compat with the\n   * spreadsheet surface that built this provider. The docs surface passes\n   * `\"document\"` so the channel becomes `yjs:document:<id>` instead of\n   * `yjs:workbook:<id>`. Pick distinct values per surface to keep CRDT rooms\n   * from cross-talking.\n   */\n  channelPrefix?: string;\n  /** Stable per-tab session id. Use crypto.randomUUID() at the call site. */\n  clientId: string;\n  doc: Y.Doc;\n  awareness: Awareness;\n  /** Cap base64-encoded payload size. Default 200_000 (under Broadcast's 256KB limit, accounting for envelope overhead). */\n  chunkSize?: number;\n  /**\n   * Override the realtime manager. Defaults to the app's ONE manager (the\n   * provider publishes it). Only the verification harness passes its own —\n   * two providers in ONE process must not share a manager, or the package's\n   * room registry would correctly hand them the same channel and they would\n   * never see each other's frames.\n   */\n  manager?: RealtimeManager;\n  /** Fires when this holder gains or loses tab leadership of its room. */\n  onLeaderChange?: () => void;\n  /**\n   * Take frames the package believes came from this same client session.\n   * The package stamps every frame with ONE per-process session id and drops\n   * frames carrying its own, so two managers in one process (the verification\n   * harness) silence each other. Safe here: doc updates are idempotent CRDT\n   * updates and the control frames filter their own `clientId`. Off in the app.\n   */\n  acceptOwnSessionFrames?: boolean;\n  /**\n   * Base period (ms) of the anti-entropy tick. Defaults to the\n   * `readAntiEntropyIntervalMs`; tests pass a number.\n   */\n  antiEntropyIntervalMs?: number;\n  /**\n   * Reader for the anti-entropy period when `antiEntropyIntervalMs` is not\n   * given. The app supplies its feature-knob reader\n   * (`collab.anti_entropy_interval_ms`); the package owns no knob store. With\n   * neither, the tick runs at DEFAULT_ANTI_ENTROPY_MS.\n   */\n  readAntiEntropyIntervalMs?: () => Promise<number>;\n};\n\n/** One place names this channel. A second, different declaration throws. */\nconst yjsChannel = defineChannelNamespace({\n  namespace: \"yjs\",\n  parts: [\"prefix\", \"resourceId\"],\n  description: \"Yjs CRDT doc + awareness frames for one collaborative resource\",\n});\n\nconst REMOTE_DOC_ORIGIN = \"remote\";\nconst REMOTE_AWARENESS_ORIGIN = \"remote-awareness\";\nconst READY_TIMEOUT_MS = 1500;\n/** Hard cap on channel subscription — a blocked WebSocket degrades to solo\n *  mode instead of hanging the session start. */\nconst SUBSCRIBE_TIMEOUT_MS = 8000;\nconst AWARENESS_THROTTLE_MS = 50;\nconst CHUNK_BATCH_TTL_MS = 5000;\nconst DEFAULT_CHUNK_SIZE = 200_000;\n\n/** Anti-entropy period (ms) when the host supplies neither a number nor a reader. */\nexport const DEFAULT_ANTI_ENTROPY_MS = 30_000;\n/** Protocol timings, not policy: how long out-of-order frames get to fill a\n *  gap before we probe, how long local typing must pause before the post-burst\n *  probe, and the floor between two of our own probes. */\nconst PENDING_PROBE_DELAY_MS = 400;\nconst QUIET_PROBE_DELAY_MS = 1500;\nconst MIN_PROBE_GAP_MS = 1000;\n\ntype ChunkFrame = {\n  batchId: string;\n  seq: number;\n  total: number;\n  u: string;\n};\n\ntype StateFrame = ChunkFrame & {\n  forClientId: string;\n};\n\ntype AwarenessFrame = {\n  u: string;\n};\n\ntype RequestStateFrame = {\n  clientId: string;\n};\n\ntype StateVectorFrame = {\n  clientId: string;\n  /** base64 V1 state vector. */\n  sv: string;\n  /** Digest of the delete set — deletions never move the state vector. */\n  ds: string;\n  /** An answer to someone else's probe; never answered with another probe. */\n  reply?: boolean;\n};\n\ntype PendingBatch = {\n  parts: Map<number, string>;\n  total: number;\n  timer: ReturnType<typeof setTimeout>;\n};\n\n// Browser-only base64 helpers — keeps the wire payload JSON-safe.\nfunction toBase64(bytes: Uint8Array): string {\n  let s = \"\";\n  // Chunk to avoid blowing the call stack on large updates.\n  const chunk = 0x8000;\n  for (let i = 0; i < bytes.length; i += chunk) {\n    s += String.fromCharCode(...bytes.subarray(i, i + chunk));\n  }\n  return btoa(s);\n}\n\nfunction fromBase64(b64: string): Uint8Array {\n  return Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));\n}\n\n/** FNV-1a over bytes — a cheap equality digest, not a security hash. */\nfunction fnv1a(bytes: Uint8Array): string {\n  let h = 0x811c9dc5;\n  for (let i = 0; i < bytes.length; i++) {\n    h ^= bytes[i] ?? 0;\n    h = Math.imul(h, 0x01000193);\n  }\n  return `${bytes.length.toString(36)}.${(h >>> 0).toString(36)}`;\n}\n\nfunction hasPending(doc: Y.Doc): boolean {\n  return doc.store.pendingStructs !== null || doc.store.pendingDs !== null;\n}\n\nexport class SupabaseYjsProvider {\n  private readonly workbookId: string;\n  private readonly clientId: string;\n  private readonly acceptOwnSessionFrames: boolean;\n  private readonly doc: Y.Doc;\n  private readonly awareness: Awareness;\n  private readonly chunkSize: number;\n  private readonly channelName: string;\n  private readonly manager: RealtimeManager | null;\n  private readonly onLeaderChange: (() => void) | undefined;\n\n  private channel: ChannelHandle | null = null;\n  private _disposed = false;\n  private connected = false;\n\n  private hasInitialState = false;\n  private readyPromise: Promise<void>;\n  private resolveReady: (() => void) | null = null;\n  private aloneTimer: ReturnType<typeof setTimeout> | null = null;\n\n  private readonly pendingDocBatches = new Map<string, PendingBatch>();\n  private readonly pendingStateBatches = new Map<string, PendingBatch>();\n\n  private awarenessFlushTimer: ReturnType<typeof setTimeout> | null = null;\n  private awarenessFlushQueue = new Set<number>();\n\n  // Anti-entropy state.\n  private readonly antiEntropyIntervalOption: number | undefined;\n  private readonly readAntiEntropyIntervalMs: (() => Promise<number>) | undefined;\n  private antiEntropyIntervalMs: number | null = null;\n  private tickTimer: ReturnType<typeof setTimeout> | null = null;\n  private quietTimer: ReturnType<typeof setTimeout> | null = null;\n  private pendingTimer: ReturnType<typeof setTimeout> | null = null;\n  private pendingBackoffMs = MIN_PROBE_GAP_MS;\n  private lastProbeAt = 0;\n  private changedSinceTick = false;\n\n  // Stable bound listeners so we can detach on disconnect.\n  private readonly onDocDestroy = (): void => this.disconnect();\n\n  private readonly onDocUpdate = (update: Uint8Array, origin: unknown): void => {\n    if (this._disposed || !this.channel) return;\n    this.changedSinceTick = true;\n    if (origin === REMOTE_DOC_ORIGIN) return; // don't echo\n    this.broadcastChunked(\"y-update\", update);\n    this.scheduleQuietProbe();\n  };\n\n  private readonly onAwarenessUpdate = (\n    changes: { added: number[]; updated: number[]; removed: number[] },\n    origin: unknown,\n  ): void => {\n    if (this._disposed) return;\n    if (origin === REMOTE_AWARENESS_ORIGIN) return;\n    for (const id of changes.added) this.awarenessFlushQueue.add(id);\n    for (const id of changes.updated) this.awarenessFlushQueue.add(id);\n    for (const id of changes.removed) this.awarenessFlushQueue.add(id);\n    this.scheduleAwarenessFlush();\n  };\n\n  constructor(options: SupabaseYjsProviderOptions) {\n    this.workbookId = options.workbookId;\n    this.clientId = options.clientId;\n    this.doc = options.doc;\n    this.awareness = options.awareness;\n    this.chunkSize = options.chunkSize ?? DEFAULT_CHUNK_SIZE;\n    this.manager = options.manager ?? currentRealtimeManager();\n    this.onLeaderChange = options.onLeaderChange;\n    this.acceptOwnSessionFrames = options.acceptOwnSessionFrames === true;\n    this.antiEntropyIntervalOption = options.antiEntropyIntervalMs;\n    this.readAntiEntropyIntervalMs = options.readAntiEntropyIntervalMs;\n    this.channelName = yjsChannel.topic({\n      prefix: options.channelPrefix ?? \"workbook\",\n      resourceId: this.workbookId,\n    });\n    this.readyPromise = new Promise<void>((resolve) => {\n      this.resolveReady = resolve;\n    });\n  }\n\n  async connect(): Promise<void> {\n    if (this._disposed || this.connected) return;\n    this.connected = true;\n\n    if (!this.manager) {\n      // Not silent: without a manager there are no peers, and a collaborative\n      // surface that silently went solo is the worst possible outcome here.\n      console.warn(\n        `[collab] no realtime manager for ${this.channelName} — continuing solo ` +\n          \"(no live peers). Mount <RealtimeProvider> (providers/RealtimeHost) \" +\n          \"above this surface, or pass `manager` explicitly.\",\n      );\n      this.hasInitialState = true;\n      this.resolveReady?.();\n      return;\n    }\n\n    // Resolve on success OR terminal failure OR hard timeout — a blocked\n    // WebSocket must never hang the caller. On failure we degrade to solo\n    // mode: the editor keeps working, just without live peers, and ready()\n    // resolves so nothing upstream awaits forever.\n    let settle: ((ok: boolean) => void) | null = null;\n    const subscribedPromise = new Promise<boolean>((resolve) => {\n      settle = resolve;\n    });\n    const timer = setTimeout(() => settle?.(false), SUBSCRIBE_TIMEOUT_MS);\n\n    this.channel = this.manager.open({\n      topic: this.channelName,\n      broadcast: [\n        {\n          event: \"y-update\",\n          onMessage: ({ data }) => this.handleDocFrame(data as ChunkFrame),\n        },\n        {\n          event: \"y-awareness\",\n          onMessage: ({ data }) => this.handleAwarenessFrame(data as AwarenessFrame),\n        },\n        {\n          event: \"y-request-state\",\n          onMessage: ({ data }) => this.handleStateRequest(data as RequestStateFrame),\n        },\n        {\n          event: \"y-state\",\n          onMessage: ({ data }) => this.handleStateResponse(data as StateFrame),\n        },\n        {\n          event: \"y-sv\",\n          onMessage: ({ data }) => this.handleStateVector(data as StateVectorFrame),\n        },\n      ],\n      // Chunked frames must be reassembled IN ORDER; the package's queue\n      // preserves per-channel order, so `seq` reassembly stays correct.\n      eventKey: (_source, payload) => {\n        const frame = payload as Partial<ChunkFrame> | undefined;\n        return frame?.batchId === undefined\n          ? undefined\n          : `${frame.batchId}:${String(frame.seq)}`;\n      },\n      onLeaderChange: () => this.onLeaderChange?.(),\n      ...(this.acceptOwnSessionFrames ? { echoSuppression: false } : {}),\n      onStatusChange: (status) => {\n        if (typeof process !== \"undefined\" && process.env?.COLLAB_DEBUG) {\n          console.debug(`[collab:debug] ${this.channelName} status=${status}`);\n        }\n        if (status === \"connected\") {\n          clearTimeout(timer);\n          settle?.(true);\n        }\n      },\n      // THE CATCH-UP. A CRDT is not exempt from \"realtime has no replay\": every\n      // peer update that landed while the socket was down is gone, and Yjs\n      // cannot know it is missing them — the doc just stays quietly divergent.\n      // Asking the peers for state again is the correct re-read, and it is the\n      // same thing a new joiner does.\n      // The gap runs both ways: also push our whole state so the peers merge the\n      // edits we made while disconnected (idempotent when there were none).\n      onBackfill: () => {\n        if (this.inert() || !this.channel) return;\n        const resync: RequestStateFrame = { clientId: this.clientId };\n        this.channel.send(\"y-request-state\", resync);\n        this.broadcastChunked(\"y-update\", Y.encodeStateAsUpdateV2(this.doc));\n      },\n    });\n\n    this.doc.on(\"updateV2\", this.onDocUpdate);\n    this.doc.on(\"destroy\", this.onDocDestroy);\n    this.awareness.on(\"update\", this.onAwarenessUpdate);\n\n    const subscribed = await subscribedPromise;\n    clearTimeout(timer);\n\n    if (this._disposed) return;\n\n    if (!subscribed) {\n      console.warn(\n        `[collab] could not subscribe to ${this.channelName} — continuing solo (no live peers)`,\n      );\n      this.hasInitialState = true;\n      this.resolveReady?.();\n      return;\n    }\n\n    // Ask existing peers for the current doc; resolve solo if nobody answers.\n    const req: RequestStateFrame = { clientId: this.clientId };\n    this.channel.send(\"y-request-state\", req);\n    this.startAntiEntropy();\n\n    this.aloneTimer = setTimeout(() => {\n      this.aloneTimer = null;\n      if (!this.hasInitialState) {\n        this.hasInitialState = true;\n        this.resolveReady?.();\n      }\n    }, READY_TIMEOUT_MS);\n  }\n\n  disconnect(): void {\n    if (this._disposed) return;\n\n    // Deliver what the throttle still holds — typically the caller's \"leave\"\n    // (`removeAwarenessStates` just before this). Dropping it forced callers to\n    // defer disconnect(), and a deferred provider kept applying the room's frames\n    // to a doc whose editor was already gone.\n    if (this.awarenessFlushTimer) {\n      clearTimeout(this.awarenessFlushTimer);\n      this.awarenessFlushTimer = null;\n    }\n    if (!this.doc.isDestroyed) this.flushAwareness();\n    this.awarenessFlushQueue.clear();\n\n    this._disposed = true;\n    this.connected = false;\n\n    this.doc.off(\"updateV2\", this.onDocUpdate);\n    this.doc.off(\"destroy\", this.onDocDestroy);\n    this.awareness.off(\"update\", this.onAwarenessUpdate);\n\n    if (this.aloneTimer) {\n      clearTimeout(this.aloneTimer);\n      this.aloneTimer = null;\n    }\n    for (const t of [this.tickTimer, this.quietTimer, this.pendingTimer]) {\n      if (t) clearTimeout(t);\n    }\n    this.tickTimer = this.quietTimer = this.pendingTimer = null;\n\n    for (const batch of this.pendingDocBatches.values()) clearTimeout(batch.timer);\n    for (const batch of this.pendingStateBatches.values()) clearTimeout(batch.timer);\n    this.pendingDocBatches.clear();\n    this.pendingStateBatches.clear();\n\n    if (this.channel) {\n      this.channel.close();\n      this.channel = null;\n    }\n\n    // Unblock anyone awaiting ready() after disconnect.\n    if (!this.hasInitialState) {\n      this.hasInitialState = true;\n      this.resolveReady?.();\n    }\n  }\n\n  ready(): Promise<void> {\n    return this.readyPromise;\n  }\n\n  /**\n   * One holder per room per tab is the leader (package-owned). Same-tab holders\n   * of one document also hear each other's updates through the package, so they\n   * converge; only the leader may be host/autosaver. No channel (solo) = leader.\n   */\n  isTabLeader(): boolean {\n    return this.channel ? this.channel.isLocalLeader() : true;\n  }\n\n  // ─── Internals ───────────────────────────────────────────────────────────\n\n  private broadcastChunked(event: \"y-update\" | \"y-state\", update: Uint8Array, extra?: Record<string, string>): void {\n    if (!this.channel) return;\n    const b64 = toBase64(update);\n    const batchId = this.makeBatchId();\n\n    if (b64.length <= this.chunkSize) {\n      const payload: ChunkFrame = { batchId, seq: 0, total: 1, u: b64 };\n      this.channel.send(event, extra ? { ...payload, ...extra } : payload);\n      return;\n    }\n\n    const total = Math.ceil(b64.length / this.chunkSize);\n    for (let seq = 0; seq < total; seq++) {\n      const slice = b64.slice(seq * this.chunkSize, (seq + 1) * this.chunkSize);\n      const payload: ChunkFrame = { batchId, seq, total, u: slice };\n      this.channel.send(event, extra ? { ...payload, ...extra } : payload);\n    }\n  }\n\n  /** Torn down, or the doc under us is gone: nothing may be applied or answered. */\n  private inert(): boolean {\n    return this._disposed || this.doc.isDestroyed;\n  }\n\n  private handleDocFrame(frame: ChunkFrame): void {\n    if (this.inert()) return;\n    const assembled = this.assemble(this.pendingDocBatches, frame);\n    if (!assembled) return;\n    const update = fromBase64(assembled);\n    // V2 decoder — outbound updates come from doc.on('updateV2'). Mixing V1\n    // apply with V2 frames silently corrupts the doc.\n    Y.applyUpdateV2(this.doc, update, REMOTE_DOC_ORIGIN);\n    this.checkPending();\n  }\n\n  private handleAwarenessFrame(frame: AwarenessFrame): void {\n    if (this.inert()) return;\n    const update = fromBase64(frame.u);\n    applyAwarenessUpdate(this.awareness, update, REMOTE_AWARENESS_ORIGIN);\n  }\n\n  private handleStateRequest(req: RequestStateFrame): void {\n    if (this.inert() || !this.channel) return;\n    if (req.clientId === this.clientId) return; // ignore our own (shouldn't fire with self:false)\n    const sv = Y.encodeStateAsUpdateV2(this.doc);\n    this.broadcastChunked(\"y-state\", sv, { forClientId: req.clientId });\n  }\n\n  private handleStateResponse(frame: StateFrame): void {\n    if (this.inert()) return;\n    if (frame.forClientId !== this.clientId) return;\n    // EVERY answer is merged — the first resolves ready(), later ones (other\n    // peers, the reconnect catch-up's re-request) carry what we missed. Yjs\n    // applies are idempotent, so a redundant answer changes nothing.\n    const assembled = this.assemble(this.pendingStateBatches, frame);\n    if (!assembled) return;\n    const update = fromBase64(assembled);\n    // V2 decoder — state snapshots are encoded with encodeStateAsUpdateV2.\n    Y.applyUpdateV2(this.doc, update, REMOTE_DOC_ORIGIN);\n    this.checkPending();\n    if (this.hasInitialState) return;\n    this.hasInitialState = true;\n    if (this.aloneTimer) {\n      clearTimeout(this.aloneTimer);\n      this.aloneTimer = null;\n    }\n    this.resolveReady?.();\n  }\n\n  // ─── Anti-entropy ────────────────────────────────────────────────────────\n\n  private startAntiEntropy(): void {\n    if (this.antiEntropyIntervalOption !== undefined) {\n      this.antiEntropyIntervalMs = this.antiEntropyIntervalOption;\n      this.scheduleTick();\n      return;\n    }\n    if (!this.readAntiEntropyIntervalMs) {\n      this.antiEntropyIntervalMs = DEFAULT_ANTI_ENTROPY_MS;\n      this.scheduleTick();\n      return;\n    }\n    this.readAntiEntropyIntervalMs()\n      .then((ms) => {\n        if (this.inert()) return;\n        this.antiEntropyIntervalMs = ms;\n        this.scheduleTick();\n      })\n      .catch((err: unknown) => {\n        // Not silent: without the tick, a lost tail frame is only recovered by\n        // the next edit's probe or a reconnect.\n        console.warn(\n          `[collab] anti-entropy tick disabled for ${this.channelName}: could not read the interval`,\n          err,\n        );\n      });\n  }\n\n  /** Jittered (±25%) so a room's members do not probe in lockstep. */\n  private scheduleTick(): void {\n    const base = this.antiEntropyIntervalMs;\n    if (this.inert() || !this.channel || base === null || base <= 0) return;\n    const delay = Math.round(base * (0.75 + Math.random() * 0.5));\n    this.tickTimer = setTimeout(() => {\n      this.tickTimer = null;\n      if (this.inert()) return;\n      if (this.changedSinceTick || hasPending(this.doc)) {\n        this.changedSinceTick = false;\n        this.probe(false);\n      }\n      this.scheduleTick();\n    }, delay);\n  }\n\n  /** One probe after a burst of local edits goes quiet (debounced). */\n  private scheduleQuietProbe(): void {\n    if (this.quietTimer) clearTimeout(this.quietTimer);\n    this.quietTimer = setTimeout(() => {\n      this.quietTimer = null;\n      this.probe(false);\n    }, QUIET_PROBE_DELAY_MS);\n  }\n\n  /** After a remote apply: a gap that survives the reorder window is probed. */\n  private checkPending(): void {\n    if (!hasPending(this.doc)) {\n      this.pendingBackoffMs = MIN_PROBE_GAP_MS;\n      return;\n    }\n    this.schedulePendingProbe(PENDING_PROBE_DELAY_MS);\n  }\n\n  private schedulePendingProbe(delay: number): void {\n    if (this.pendingTimer || this.inert()) return;\n    this.pendingTimer = setTimeout(() => {\n      this.pendingTimer = null;\n      if (this.inert() || !hasPending(this.doc)) {\n        this.pendingBackoffMs = MIN_PROBE_GAP_MS;\n        return;\n      }\n      this.probe(false);\n      // Still stuck after the answer window → probe again, backing off to the tick period.\n      const cap = this.antiEntropyIntervalMs ?? 30_000;\n      this.pendingBackoffMs = Math.min(this.pendingBackoffMs * 2, cap);\n      this.schedulePendingProbe(this.pendingBackoffMs);\n    }, delay);\n  }\n\n  private deleteSetDigest(): string {\n    // An update relative to our own vector carries no structs — only the delete set.\n    return fnv1a(Y.encodeStateAsUpdate(this.doc, Y.encodeStateVector(this.doc)));\n  }\n\n  /** Send our state vector + delete-set digest. Rate-limited unless it is a reply. */\n  private probe(reply: boolean): void {\n    if (this.inert() || !this.channel) return;\n    const now = Date.now();\n    const wait = this.lastProbeAt + MIN_PROBE_GAP_MS - now;\n    // Replies bypass the floor: they only ever answer a peer's (rate-limited) probe.\n    if (!reply && wait > 0) {\n      this.schedulePendingProbeAfterGap(wait);\n      return;\n    }\n    this.lastProbeAt = now;\n    const frame: StateVectorFrame = {\n      clientId: this.clientId,\n      sv: toBase64(Y.encodeStateVector(this.doc)),\n      ds: this.deleteSetDigest(),\n      ...(reply ? { reply: true } : {}),\n    };\n    this.channel.send(\"y-sv\", frame);\n  }\n\n  /** A rate-limited non-reply probe is deferred, never dropped. */\n  private schedulePendingProbeAfterGap(wait: number): void {\n    if (this.quietTimer) return; // a probe is already coming\n    this.quietTimer = setTimeout(() => {\n      this.quietTimer = null;\n      this.probe(false);\n    }, wait);\n  }\n\n  private handleStateVector(frame: StateVectorFrame): void {\n    if (this.inert() || !this.channel) return;\n    if (frame.clientId === this.clientId) return;\n    const theirSvBytes = fromBase64(frame.sv);\n    const theirs = Y.decodeStateVector(theirSvBytes);\n    const ours = Y.decodeStateVector(Y.encodeStateVector(this.doc));\n    let weHaveMore = false;\n    for (const [client, clock] of ours) {\n      if (clock > (theirs.get(client) ?? 0)) {\n        weHaveMore = true;\n        break;\n      }\n    }\n    let theyHaveMore = false;\n    for (const [client, clock] of theirs) {\n      if (clock > (ours.get(client) ?? 0)) {\n        theyHaveMore = true;\n        break;\n      }\n    }\n    const dsDiffers = frame.ds !== this.deleteSetDigest();\n    if (weHaveMore || dsDiffers) {\n      this.broadcastChunked(\"y-state\", Y.encodeStateAsUpdateV2(this.doc, theirSvBytes), {\n        forClientId: frame.clientId,\n      });\n    }\n    if (!frame.reply && (theyHaveMore || dsDiffers || hasPending(this.doc))) {\n      this.probe(true);\n    }\n  }\n\n  /** Reassemble chunked frames by batchId; returns the joined base64 string when complete. */\n  private assemble(store: Map<string, PendingBatch>, frame: ChunkFrame): string | null {\n    if (frame.total <= 1) return frame.u;\n\n    let batch = store.get(frame.batchId);\n    if (!batch) {\n      const timer = setTimeout(() => store.delete(frame.batchId), CHUNK_BATCH_TTL_MS);\n      batch = { parts: new Map(), total: frame.total, timer };\n      store.set(frame.batchId, batch);\n    }\n    batch.parts.set(frame.seq, frame.u);\n    if (batch.parts.size < batch.total) return null;\n\n    clearTimeout(batch.timer);\n    store.delete(frame.batchId);\n    let combined = \"\";\n    for (let i = 0; i < batch.total; i++) {\n      const part = batch.parts.get(i);\n      if (part == null) return null; // shouldn't happen given the size check above\n      combined += part;\n    }\n    return combined;\n  }\n\n  private scheduleAwarenessFlush(): void {\n    if (this.awarenessFlushTimer || this._disposed) return;\n    this.awarenessFlushTimer = setTimeout(() => {\n      this.awarenessFlushTimer = null;\n      this.flushAwareness();\n    }, AWARENESS_THROTTLE_MS);\n  }\n\n  private flushAwareness(): void {\n    if (this.inert() || !this.channel || this.awarenessFlushQueue.size === 0) return;\n    const clients = Array.from(this.awarenessFlushQueue);\n    this.awarenessFlushQueue.clear();\n    const update = encodeAwarenessUpdate(this.awareness, clients);\n    const payload: AwarenessFrame = { u: toBase64(update) };\n    this.channel.send(\"y-awareness\", payload);\n  }\n\n  private makeBatchId(): string {\n    if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n      return crypto.randomUUID();\n    }\n    return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n  }\n}\n","/**\n * THE AMBIENT MANAGER — the door for code that is not a React component.\n *\n * `<RealtimeProvider>` builds exactly ONE manager per app, and `useRealtimeManager()`\n * hands it to components. But a large share of realtime ownership in a real app\n * is deliberately NOT in a component:\n *\n *  - Redux middleware, which is where this doctrine says a feature's\n *    subscription SHOULD live (a component effect re-subscribes on identity\n *    churn and multiplies channels across mounts).\n *  - Plain service modules and thunks that own a module-level subscription.\n *\n * None of those can call a hook. Before this module they had exactly two\n * options, and both are defects:\n *\n *  1. `createRealtimeManager(...)` of their own — a SECOND manager, and\n *     therefore a second write ledger, so the app's own optimistic writes look\n *     REMOTE to that manager and every one of them costs a refetch. This is the\n *     failure the \"one provider per app\" rule exists to prevent, and a\n *     middleware author reaching for the constructor cannot see it happening.\n *  2. Hand-roll `client.channel(...)` — which is the thing this package exists\n *     to delete.\n *\n * So the provider PUBLISHES its manager here, and non-React owners read it.\n * There is no second manager and no second ledger: this is a pointer to the one\n * the provider already built.\n *\n * WHY IT IS SUBSCRIBABLE. The manager's lifetime is `client` + `actorId`: it is\n * `null` before auth resolves and it is REBUILT when the signed-in user changes\n * (and disposed, taking every channel with it). A middleware that read\n * `currentRealtimeManager()` once at module load would hold null forever on a\n * cold start, and one that ignored the change would hold a disposed manager\n * after a user switch — silently, with a screen that looks healthy. So the\n * contract is `onRealtimeManagerChange(listener)`: open on arrival, drop your\n * handle on departure. `subscribeToRealtimeManager` below is the whole recipe\n * in one call, so no consumer has to remember the shape.\n *\n * No module-level mutable state (the package ban): the registry lives in a\n * `globalThis` slot, shared across the ESM and CJS module graphs.\n */\n\nimport { globalSlot } from \"@ai-matrx/kit/global-slot\";\nimport type { ChannelHandle, ChannelSpec, RealtimeManager } from \"./manager\";\n\ninterface AmbientRegistry {\n  manager: RealtimeManager | null;\n  listeners: Set<(manager: RealtimeManager | null) => void>;\n}\n\nfunction registry(): AmbientRegistry {\n  return globalSlot<AmbientRegistry>(\"ai-matrx.realtime.ambient-manager\", () => ({\n    manager: null,\n    listeners: new Set(),\n  }));\n}\n\n/**\n * Publish the app's manager. Called by `<RealtimeProvider>` ONLY — a consumer\n * that calls this is claiming to be the app's provider, which is the thing the\n * one-provider rule forbids.\n */\nexport function setAmbientRealtimeManager(manager: RealtimeManager | null): void {\n  const state = registry();\n  if (state.manager === manager) return;\n  state.manager = manager;\n  for (const listener of Array.from(state.listeners)) listener(manager);\n}\n\n/**\n * The app's manager right now, or `null` when no provider is mounted or its\n * Supabase client has not resolved yet.\n *\n * Reading this ONCE is almost always wrong — see the header. Prefer\n * {@link subscribeToRealtimeManager}.\n */\nexport function currentRealtimeManager(): RealtimeManager | null {\n  return registry().manager;\n}\n\n/**\n * Observe the app's manager. The listener fires immediately with the current\n * value (which may be `null`), and again on every change. Returns an\n * unsubscribe.\n */\nexport function onRealtimeManagerChange(\n  listener: (manager: RealtimeManager | null) => void,\n): () => void {\n  const state = registry();\n  state.listeners.add(listener);\n  listener(state.manager);\n  return () => {\n    state.listeners.delete(listener);\n  };\n}\n\n/**\n * THE RECIPE, IN ONE CALL — for a Redux middleware or module-level owner that\n * wants one channel open for as long as the app has a manager.\n *\n * Opens `spec` when a manager exists, closes the handle when it goes away, and\n * re-opens on the new manager when the provider rebuilds one. Returns a stop\n * function that closes the channel and unsubscribes.\n *\n * `spec` is a FACTORY so the caller can capture whatever it needs at open time\n * (the current user id, a fresh `dispatch`) rather than at module load.\n */\nexport function subscribeToRealtimeManager(\n  spec: (manager: RealtimeManager) => ChannelSpec,\n): () => void {\n  let handle: ChannelHandle | null = null;\n  const stop = onRealtimeManagerChange((manager) => {\n    if (handle) {\n      handle.close();\n      handle = null;\n    }\n    if (manager) handle = manager.open(spec(manager));\n  });\n  return () => {\n    stop();\n    if (handle) {\n      handle.close();\n      handle = null;\n    }\n  };\n}\n","/**\n * THE CHANNEL-NAMING REGISTRY — one place where channels are named, so two\n * features can never collide (R1).\n *\n * Two failures live here, and they look nothing alike:\n *\n * 1. **Two features choosing the same topic string.** Supabase's\n *    `RealtimeClient.channel(topic)` DEDUPES BY TOPIC: a second call with a\n *    topic that is still registered returns the SAME channel object. Two\n *    features that both picked `\"messages\"` therefore silently share one\n *    channel, and the second one's `.on(...)` calls land after the first\n *    one's `subscribe()` — which throws, or (worse) attaches nothing. The cure\n *    is a declared namespace per feature, refused at registration time if it\n *    is already taken by a different declaration.\n *\n * 2. **One feature colliding with ITSELF across a remount.** React 19\n *    double-invokes effects and Fast Refresh re-runs them, while\n *    `removeChannel` is async and NOT awaited by React. The remount's\n *    `.channel(topic)` therefore hands back the previous mount's still-joined\n *    channel, and `.on(\"postgres_changes\", ...)` on it throws \"cannot add\n *    postgres_changes callbacks after subscribe()\". The cure is\n *    `uniqueChannelTopic()`.\n *\n * 🚨 **`uniqueChannelTopic()` IS FOR POSTGRES CHANGES ONLY.** For that transport\n * the topic really is nothing but a client-side connection key — the server\n * pushes on the declared filter — so making every INSTANCE unique costs nothing.\n * For **broadcast and presence the topic IS the room**: it is the routing key\n * the server fans messages out on, and a unique instance topic silently puts\n * every participant in a room of one. That was the 0.1.x defect. Those channels\n * go on the wire with their DECLARED topic and are shared+ref-counted inside the\n * client by `rooms.ts`, which is where failure 2 is handled for them.\n *\n * Both are applied automatically by the manager — a consumer names a channel\n * through a namespace and never thinks about any of it again.\n */\n\nimport { globalSlot } from \"@ai-matrx/kit/global-slot\";\n\n/** Every Matrx channel topic starts here, so a topic is recognizable in logs. */\nexport const TOPIC_PREFIX = \"mx\";\n\nexport interface ChannelNamespaceSpec {\n  /** Feature-level name, unique platform-wide. Lowercase, `[a-z0-9-]`. */\n  namespace: string;\n  /**\n   * Ordered part names this namespace requires, e.g. `[\"userId\"]` or\n   * `[\"conversationId\"]`. A namespace with zero parts is a singleton channel.\n   */\n  parts: readonly string[];\n  /** What this channel carries. Shown by the diagnostics surface. */\n  description: string;\n  /**\n   * A FOREIGN topic root, used verbatim in place of `mx:<namespace>`.\n   *\n   * Only for a channel whose topic string is a contract owned by a separately\n   * released peer — the deployed Chrome extension's `matrx-extension-bridge`\n   * is the case this exists for. Renaming such a topic to `mx:…` does not\n   * migrate anything; it puts this client in a room of its own while the peer\n   * keeps listening where it always did, silently.\n   *\n   * The namespace is still declared and still collision-checked, so nothing\n   * else can claim it and the diagnostics surface still lists it. Pair it with\n   * `wire: {mode:\"raw\"}` on the channel spec — a foreign topic almost always\n   * comes with a foreign payload shape.\n   */\n  foreignTopic?: string;\n}\n\nexport interface ChannelNamespace {\n  readonly spec: ChannelNamespaceSpec;\n  /** Build the stable topic for one instance of this channel. */\n  topic(parts?: Readonly<Record<string, string>>): string;\n}\n\ninterface Registry {\n  namespaces: Map<string, ChannelNamespaceSpec>;\n  sequence: number;\n}\n\nfunction registry(): Registry {\n  return globalSlot<Registry>(\"ai-matrx.realtime.topic-registry\", () => ({\n    namespaces: new Map(),\n    sequence: 0,\n  }));\n}\n\nconst NAMESPACE_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\nexport class ChannelNamespaceCollisionError extends Error {\n  readonly namespace: string;\n  readonly existing: ChannelNamespaceSpec;\n  readonly attempted: ChannelNamespaceSpec;\n\n  constructor(\n    namespace: string,\n    existing: ChannelNamespaceSpec,\n    attempted: ChannelNamespaceSpec,\n  ) {\n    super(\n      `Realtime channel namespace \"${namespace}\" is already declared as ` +\n        `{parts: [${existing.parts.join(\", \")}]} \"${existing.description}\" and cannot be ` +\n        `re-declared as {parts: [${attempted.parts.join(\", \")}]} \"${attempted.description}\". ` +\n        `Two features sharing a topic share ONE Supabase channel and silently lose ` +\n        `each other's bindings. Pick a distinct namespace, or import the existing one.`,\n    );\n    this.namespace = namespace;\n    this.existing = existing;\n    this.attempted = attempted;\n    this.name = \"ChannelNamespaceCollisionError\";\n  }\n}\n\nfunction sameSpec(a: ChannelNamespaceSpec, b: ChannelNamespaceSpec): boolean {\n  return (\n    a.description === b.description &&\n    a.foreignTopic === b.foreignTopic &&\n    a.parts.length === b.parts.length &&\n    a.parts.every((part, index) => part === b.parts[index])\n  );\n}\n\n/** Same topic shape (parts + foreign topic): the same channel, whatever its prose says. */\nfunction sameShape(a: ChannelNamespaceSpec, b: ChannelNamespaceSpec): boolean {\n  return (\n    a.foreignTopic === b.foreignTopic &&\n    a.parts.length === b.parts.length &&\n    a.parts.every((part, index) => part === b.parts[index])\n  );\n}\n\nfunction isProduction(): boolean {\n  return (\n    typeof process !== \"undefined\" &&\n    typeof process.env === \"object\" &&\n    process.env?.NODE_ENV === \"production\"\n  );\n}\n\n/**\n * Declare a channel namespace. Idempotent for an identical declaration (so\n * Fast Refresh and dual module graphs are fine); throws on a genuine collision.\n *\n * Outside production, the SAME channel (same parts and foreign topic) re-declared\n * with a new description is a newer copy of its own module — Fast Refresh, or a\n * package reinstalled under a long-running dev server whose globalThis registry\n * still holds the old declaration. It replaces the old declaration and warns;\n * it never throws (live 2026-10-07: one in-place description change 500'd every\n * page on the shared dev server). A different shape under the same name is two\n * channels and is always refused; in production a description mismatch is a\n * second declarer and is refused too.\n */\nexport function defineChannelNamespace(\n  spec: ChannelNamespaceSpec,\n): ChannelNamespace {\n  if (!NAMESPACE_PATTERN.test(spec.namespace)) {\n    throw new Error(\n      `Invalid realtime channel namespace \"${spec.namespace}\": use lowercase ` +\n        `words joined by hyphens (e.g. \"note-editors\"). The topic string ends up ` +\n        `in logs and the diagnostics surface, so it must stay readable.`,\n    );\n  }\n  if (spec.description.trim().length === 0) {\n    throw new Error(\n      `Realtime channel namespace \"${spec.namespace}\" needs a description — the ` +\n        `diagnostics surface lists live channels by it, and an unnamed channel is ` +\n        `an unfixable bug report.`,\n    );\n  }\n\n  const store = registry().namespaces;\n  const existing = store.get(spec.namespace);\n  if (existing && !sameSpec(existing, spec)) {\n    if (!sameShape(existing, spec) || isProduction()) {\n      throw new ChannelNamespaceCollisionError(spec.namespace, existing, spec);\n    }\n    console.warn(\n      `[realtime] channel namespace \"${spec.namespace}\" re-declared with a new description ` +\n        `(\"${existing.description}\" -> \"${spec.description}\"); keeping the new one. ` +\n        `Expected after Fast Refresh or a package reinstall in development. If two features ` +\n        `declare this name, give one of them a distinct namespace.`,\n    );\n    store.set(spec.namespace, spec);\n  }\n  if (!existing) store.set(spec.namespace, spec);\n\n  return {\n    spec,\n    topic(parts) {\n      const values: string[] = [];\n      for (const name of spec.parts) {\n        const value = parts?.[name];\n        if (value === undefined || value === \"\") {\n          throw new Error(\n            `Realtime channel \"${spec.namespace}\" requires part \"${name}\". ` +\n              `A missing part would produce a topic shared by every caller that ` +\n              `also omitted it — everyone would receive everyone's events.`,\n          );\n        }\n        values.push(encodeTopicPart(value));\n      }\n      const root =\n        spec.foreignTopic !== undefined ? [spec.foreignTopic] : [TOPIC_PREFIX, spec.namespace];\n      return [...root, ...values].join(\":\");\n    },\n  };\n}\n\n/** Every namespace declared in this realm — the diagnostics surface reads it. */\nexport function listChannelNamespaces(): readonly ChannelNamespaceSpec[] {\n  return [...registry().namespaces.values()].sort((a, b) =>\n    a.namespace.localeCompare(b.namespace),\n  );\n}\n\n/**\n * Make a topic unique to THIS channel instance. **Postgres Changes only** — see\n * the file header. Calling it for a broadcast or presence channel splits the\n * room.\n *\n * Ported from `@ai-matrx/data`'s `uniqueChannelTopic` (40 call sites in\n * matrx-frontend). Channel NAMING belongs to this package, so this is its home;\n * `@ai-matrx/data` keeps its copy only until its consumers move here (see the\n * README § Migrating off hand-rolled channels).\n *\n * The counter lives in a globalThis slot, not a module `let`: with dual\n * ESM/CJS output an unsplit module can be instantiated twice, and two counters\n * both starting at 1 hand out the same \"unique\" topic twice.\n */\nexport function uniqueChannelTopic(base: string): string {\n  const store = registry();\n  store.sequence += 1;\n  return `${base}#${store.sequence}`;\n}\n\nfunction encodeTopicPart(value: string): string {\n  // `:` is the topic separator and `#` is the instance separator; anything\n  // carrying them would make a topic ambiguous to read and to parse.\n  return value.replace(/[:#\\s]/g, \"_\");\n}\n\n/** Test-only: forget every declaration so a suite can prove collision behavior. */\nexport function resetChannelRegistryForTests(): void {\n  const store = registry();\n  store.namespaces.clear();\n  store.sequence = 0;\n}\n"],"mappings":"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","names":["Y","import_awareness","import_global_slot","registry","id"]}