{"version":3,"sources":["../src/editorContext.ts"],"sourcesContent":["import { createPushChannel } from './pushChannel';\nimport { EDITOR_CONTEXT, REQUEST_EDITOR_CONTEXT } from './generated/protocol';\n\n/**\n * The editor \"dirty set\" mirrored from the immediately.run host into the sandbox\n * (UI_AS_APPS_SPEC §5.3): which files the user has changed but not yet saved.\n *\n * This is the ELEVATED `editor:read` capability — only a system app whose binding\n * grants it (e.g. the contribute dialog) receives it. The active file and ref are\n * already available to every app via routing (`useNavigationState`), so this\n * channel carries only the genuine delta: the unsaved paths. An app without\n * `editor:read` simply sees an empty dirty set.\n */\nexport interface EditorContext {\n  /** Repo-relative paths the user has modified but not yet saved. */\n  dirtyPaths: string[];\n  /** Repo-relative paths currently open as tabs in the host editor (§4.2). */\n  openFiles: string[];\n  /**\n   * The one file currently focused in the host editor (repo-relative, leading\n   * slash — e.g. `/src/index.ts`), or `null` when no file is open. Distinct from\n   * {@link dirtyPaths} (the unsaved SET) and {@link openFiles} (the open-tab set):\n   * this is the single ACTIVE file. A baseline app reads its own active file from\n   * the route, but a self-routed system panel (`drivesHostRoute=false`, e.g. the\n   * file explorer) does not see the host editor's route, so it receives the active\n   * file here instead (UI_AS_APPS_SPEC §5.3 self-routed-panel refinement).\n   */\n  activeFile: string | null;\n  /**\n   * The viewed-document HINT (R3-268): the working-tree file the STAGE app is\n   * currently rendering (leading slash), or `null`. Distinct from\n   * {@link activeFile} (the editor's focus): this is what is ON STAGE. It is an\n   * app-supplied claim the host validated for existence only — consume it as a\n   * highlight and nothing more (never scroll, move focus, or switch files on\n   * it; that contract is what keeps the signal activation-free).\n   */\n  viewedFile: string | null;\n}\n\n// Read over the transport (SDK_PACKAGING_SPEC §4): the host pushes `editor-context`\n// and answers `request-editor-context` — but only for a frame holding `editor:read`\n// (gated by the channel router). An app without it gets no reply, so the empty\n// default below stands. Wire format: site-main channelBridge.ts.\nconst isStringArray = (v: unknown): v is string[] => Array.isArray(v) && v.every((p) => typeof p === 'string');\n\nconst channel = createPushChannel<EditorContext>({\n  pushType: EDITOR_CONTEXT,\n  requestType: REQUEST_EDITOR_CONTEXT,\n  initial: { dirtyPaths: [], openFiles: [], activeFile: null, viewedFile: null },\n  parse: (msg) =>\n    isStringArray(msg.dirtyPaths)\n      ? {\n          dirtyPaths: msg.dirtyPaths,\n          // `openFiles` is newer than `dirtyPaths`; tolerate an older host that\n          // omits it (defensive SDK — defaults to empty rather than rejecting).\n          openFiles: isStringArray(msg.openFiles) ? msg.openFiles : [],\n          // `activeFile` is newer still; an older host that omits it (or sends a\n          // non-string) reads as `null` — no file highlighted, never a throw.\n          activeFile: typeof msg.activeFile === 'string' ? msg.activeFile : null,\n          // `viewedFile` is the newest (R3-268); same tolerance — an older host\n          // that omits it reads as `null` (no stage highlight), never a throw.\n          viewedFile: typeof msg.viewedFile === 'string' ? msg.viewedFile : null,\n        }\n      : undefined,\n});\n\n/**\n * Returns the current editor context (dirty set). Poll this for a one-off read;\n * use {@link onEditorContextChange} or {@link useEditorContext} to react.\n */\nexport const getEditorContext = (): EditorContext => channel.get();\n\n/**\n * Subscribe to editor-context changes. The listener is invoked immediately with\n * the current context, then again on every change. Returns an unsubscribe fn.\n */\nexport const onEditorContextChange = (listener: (context: EditorContext) => void): (() => void) =>\n  channel.onChange(listener);\n\n/**\n * React hook returning the current editor context (dirty set), re-rendering when\n * it changes. Handy for a contribute dialog: `const { dirtyPaths } =\n * useEditorContext()` to show \"you'll save N files\" before calling `contribute()`.\n */\nexport const useEditorContext = (): EditorContext => channel.use();\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAAkC;AAClC,sBAAuD;AA0CvD,MAAM,gBAAgB,CAAC,MAA8B,MAAM,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,OAAO,MAAM,QAAQ;AAE7G,MAAM,cAAU,sCAAiC;AAAA,EAC/C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS,EAAE,YAAY,CAAC,GAAG,WAAW,CAAC,GAAG,YAAY,MAAM,YAAY,KAAK;AAAA,EAC7E,OAAO,CAAC,QACN,cAAc,IAAI,UAAU,IACxB;AAAA,IACE,YAAY,IAAI;AAAA;AAAA;AAAA,IAGhB,WAAW,cAAc,IAAI,SAAS,IAAI,IAAI,YAAY,CAAC;AAAA;AAAA;AAAA,IAG3D,YAAY,OAAO,IAAI,eAAe,WAAW,IAAI,aAAa;AAAA;AAAA;AAAA,IAGlE,YAAY,OAAO,IAAI,eAAe,WAAW,IAAI,aAAa;AAAA,EACpE,IACA;AACR,CAAC;AAMM,MAAM,mBAAmB,MAAqB,QAAQ,IAAI;AAM1D,MAAM,wBAAwB,CAAC,aACpC,QAAQ,SAAS,QAAQ;AAOpB,MAAM,mBAAmB,MAAqB,QAAQ,IAAI;","names":[]}