{"version":3,"sources":["../src/onFsChange.ts"],"sourcesContent":["// Working-tree change stream (EDITOR_AS_APP_SPEC §4.2). The host pushes the\n// repo-relative paths that just changed in the working tree — from ANY writer: an\n// agent's port write, a host `editor:write` action (create/delete/rename), or the\n// preview's own copy-on-write. A working-tree observer (the editor app, the file\n// explorer) reacts by re-reading the affected files instead of polling.\n//\n// Elevated `editor:read` — a previewed app holds no `editor:read`, so it never sees\n// the stream (the host channel ACL withholds it). Push-only: there is no past-event\n// state worth polling, so the empty initial stands until the first write.\n//\n// Origin-exclusion is the CONSUMER's responsibility: the editor must ignore the\n// echo of its OWN write (a debounced write lags the buffer, so re-reading it as\n// \"external\" would surface a false conflict). Compare the changed file's bytes to\n// what you last wrote; if they match, it is your echo, not an external change.\nimport { createPushChannel } from './pushChannel';\nimport { FS_CHANGE } from './generated/protocol';\n\n/** One server-side change in a mount batch: the path (mount-relative, leading\n *  slash) and its kind — the Node `watch` event mapping is `add`/`remove` →\n *  `rename`, `change` → `change`. (R3-409.) */\nexport interface MountChange {\n  path: string;\n  kind: 'add' | 'change' | 'remove';\n}\n\n/** One working-tree change batch the host pushes: the changed paths plus an epoch. */\nexport interface FsChange {\n  /** Repo-relative paths (leading slash, e.g. `/src/App.tsx`) that just changed. */\n  paths: string[];\n  /**\n   * Monotonic batch id — bumps on every change even if the path set repeats, so a\n   * subscriber re-fires for a second edit to the same file (the value is never\n   * deduplicated away). `0` is the pre-first-event initial.\n   */\n  epoch: number;\n  /**\n   * R3-409 — a SPACE mount's server-side change batch (another tab's/member's\n   * writes), anchored at the mount path the frame holds. The `fs.watch` events\n   * themselves are delivered by the sandbox's fs shim (no SDK surface needed);\n   * this field is DECLARED so the wire shape has one written-down type on this\n   * side too (the R3-274e lesson), and so an SDK consumer that wants the raw\n   * per-mount batches can read them. Absent on the working-tree leg.\n   */\n  mount?: {\n    /** The sandbox mount root the `changes[].path`s are relative to (e.g. `/mnt/{hash}`). */\n    path: string;\n    changes: MountChange[];\n  };\n}\n\nconst isStringArray = (v: unknown): v is string[] => Array.isArray(v) && v.every((p) => typeof p === 'string');\n\nconst isMountBatch = (v: unknown): v is FsChange['mount'] => {\n  if (typeof v !== 'object' || v === null) return false;\n  const m = v as { path?: unknown; changes?: unknown };\n  if (typeof m.path !== 'string' || !Array.isArray(m.changes)) return false;\n  return m.changes.every(\n    (c) =>\n      typeof c === 'object' &&\n      c !== null &&\n      typeof (c as { path?: unknown }).path === 'string' &&\n      ['add', 'change', 'remove'].includes((c as { kind?: unknown }).kind as string),\n  );\n};\n\nconst channel = createPushChannel<FsChange>({\n  pushType: FS_CHANGE,\n  initial: { paths: [], epoch: 0 },\n  parse: (msg) =>\n    isStringArray(msg.paths) && typeof msg.epoch === 'number'\n      ? {\n          paths: msg.paths,\n          epoch: msg.epoch,\n          ...(isMountBatch(msg.mount) ? { mount: msg.mount } : {}),\n        }\n      : undefined,\n});\n\n/** The most recent working-tree change batch (the empty initial until the first). */\nexport const getFsChange = (): FsChange => channel.get();\n\n/**\n * Subscribe to working-tree changes. The listener fires immediately with the\n * current batch, then on every host push. Returns an unsubscribe fn. The common\n * use: re-read an open file when its path appears in `change.paths`.\n */\nexport const onFsChange = (listener: (change: FsChange) => void): (() => void) => channel.onChange(listener);\n\n/** React hook: the current working-tree change batch, re-rendering on every push. */\nexport const useFsChange = (): FsChange => channel.use();\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,yBAAkC;AAClC,sBAA0B;AAmC1B,MAAM,gBAAgB,CAAC,MAA8B,MAAM,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,OAAO,MAAM,QAAQ;AAE7G,MAAM,eAAe,CAAC,MAAuC;AAC3D,MAAI,OAAO,MAAM,YAAY,MAAM,KAAM,QAAO;AAChD,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,SAAS,YAAY,CAAC,MAAM,QAAQ,EAAE,OAAO,EAAG,QAAO;AACpE,SAAO,EAAE,QAAQ;AAAA,IACf,CAAC,MACC,OAAO,MAAM,YACb,MAAM,QACN,OAAQ,EAAyB,SAAS,YAC1C,CAAC,OAAO,UAAU,QAAQ,EAAE,SAAU,EAAyB,IAAc;AAAA,EACjF;AACF;AAEA,MAAM,cAAU,sCAA4B;AAAA,EAC1C,UAAU;AAAA,EACV,SAAS,EAAE,OAAO,CAAC,GAAG,OAAO,EAAE;AAAA,EAC/B,OAAO,CAAC,QACN,cAAc,IAAI,KAAK,KAAK,OAAO,IAAI,UAAU,WAC7C;AAAA,IACE,OAAO,IAAI;AAAA,IACX,OAAO,IAAI;AAAA,IACX,GAAI,aAAa,IAAI,KAAK,IAAI,EAAE,OAAO,IAAI,MAAM,IAAI,CAAC;AAAA,EACxD,IACA;AACR,CAAC;AAGM,MAAM,cAAc,MAAgB,QAAQ,IAAI;AAOhD,MAAM,aAAa,CAAC,aAAuD,QAAQ,SAAS,QAAQ;AAGpG,MAAM,cAAc,MAAgB,QAAQ,IAAI;","names":[]}