{"version":3,"sources":["../src/region.ts"],"sourcesContent":["// Region-awareness (UI_AS_APPS_SPEC §4.1). The host can mount the SAME app in more\n// than one chrome region — e.g. the agents activity puts one app in BOTH the panel\n// slot (`panel.agent`, the conversation list) and the stage slot\n// (`stage.conversation`, the selected conversation). `getRegion()` lets that one app\n// tell the slots apart and render the right view.\n//\n// This is descriptive only: the region id is a non-secret string the host already\n// knows. It grants nothing and gates nothing — it just names where the app is\n// mounted. The host reports it on the §4 discovery global beside `appMountPath`.\n\nimport { useEffect, useState } from 'react';\nimport { getHostRuntime } from './hostRuntime';\nimport { createPushChannel } from './pushChannel';\nimport { REGION_VISIBILITY, REQUEST_REGION_VISIBILITY } from './generated/protocol';\n\n/**\n * The chrome region this app instance is mounted in (e.g. `\"panel.agent\"`,\n * `\"stage.conversation\"`), or `null` when unknown — a standalone app, local\n * `vite dev`, or an older host that doesn't report it.\n */\nexport const getRegion = (): string | null => getHostRuntime()?.region ?? null;\n\n/**\n * React hook form of {@link getRegion}. The region is fixed for an app instance's\n * lifetime, but the discovery global can arrive just after first paint, so this\n * re-reads once the host runtime's `ready` promise resolves.\n */\nexport const useRegion = (): string | null => {\n  const [region, setRegion] = useState<string | null>(getRegion);\n  useEffect(() => {\n    if (region !== null) return;\n    let live = true;\n    void getHostRuntime()?.ready?.then(() => {\n      if (live) setRegion(getRegion());\n    });\n    return () => {\n      live = false;\n    };\n  }, [region]);\n  return region;\n};\n\n// --- region visibility (R3-562) ---------------------------------------------\n//\n// The host can keep a region MOUNTED and merely hide it, so switching the workbench\n// to another activity does not reboot this app's iframe and destroy whatever it was\n// doing (`AGENT_RUN_DURABILITY_SPEC` §7 R-ARD-20). Hiding stops the frame PAINTING,\n// not executing — which is the problem this read exists to solve.\n//\n// An app that is doing something the user is meant to WATCH must stop while hidden.\n// The case that forced it is an agent loop: `LLM_AND_AGENTS_SPEC` §3.3's loop\n// observability contract requires a streaming transcript, an ordered tool-call log\n// and a reachable stop button, and none of those survives a `display:none` + `inert`\n// subtree. So the loop pauses at its next turn boundary and continues on reveal —\n// nothing is torn down, nothing is lost, and nothing runs where nobody can stop it\n// (R-ARD-20a).\n//\n// Like `getRegion()` above, this is DESCRIPTIVE ONLY: it grants nothing, gates\n// nothing, and names no resource. It is one boolean about the host's own chrome —\n// which is why the host answers it for every frame regardless of capabilities.\n//\n// An app that ignores it behaves exactly as it did before, and so does an app running\n// anywhere the host never pushes it (a standalone tab, `vite dev`, an older host):\n// `initial` is `false` — VISIBLE — because the alternative is an app that pauses\n// forever wherever nobody is telling it anything.\n\nconst visibility = createPushChannel<boolean>({\n  pushType: REGION_VISIBILITY,\n  requestType: REQUEST_REGION_VISIBILITY,\n  initial: false,\n  // Tolerant parse: `undefined` means \"ignore this message\", so a malformed push from\n  // some future/older host leaves the last good value standing rather than flipping\n  // the app to a value nobody sent.\n  parse: (msg) => (typeof msg.hidden === 'boolean' ? msg.hidden : undefined),\n});\n\n/**\n * Whether the host has hidden this app's region — it is still mounted and running,\n * but off screen and `inert`, so the user can neither see it nor interact with it.\n *\n * `false` when the host says nothing (a standalone app, `vite dev`, an older host).\n */\nexport const isRegionHidden = (): boolean => visibility.get();\n\n/**\n * Subscribe to this region's visibility. The listener is invoked immediately with the\n * current value, then on every change. Returns an unsubscribe fn.\n *\n * Use it to STOP doing what the user is supposed to be watching, at your own safe\n * boundary — never mid-operation. An agent loop pauses between turns, so every\n * `tool_use` still has its `tool_result`; a poller stops polling; an animation stops\n * animating. Do not use it to hide UI: the host has already done that.\n */\nexport const onRegionVisibilityChange = (listener: (hidden: boolean) => void): (() => void) =>\n  visibility.onChange(listener);\n\n/** React hook form of {@link isRegionHidden}, re-rendering on change. */\nexport const useRegionHidden = (): boolean => visibility.use();\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,mBAAoC;AACpC,yBAA+B;AAC/B,yBAAkC;AAClC,sBAA6D;AAOtD,MAAM,YAAY,UAAqB,mCAAe,GAAG,UAAU;AAOnE,MAAM,YAAY,MAAqB;AAC5C,QAAM,CAAC,QAAQ,SAAS,QAAI,uBAAwB,SAAS;AAC7D,8BAAU,MAAM;AACd,QAAI,WAAW,KAAM;AACrB,QAAI,OAAO;AACX,aAAK,mCAAe,GAAG,OAAO,KAAK,MAAM;AACvC,UAAI,KAAM,WAAU,UAAU,CAAC;AAAA,IACjC,CAAC;AACD,WAAO,MAAM;AACX,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AACX,SAAO;AACT;AA0BA,MAAM,iBAAa,sCAA2B;AAAA,EAC5C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA;AAAA;AAAA;AAAA,EAIT,OAAO,CAAC,QAAS,OAAO,IAAI,WAAW,YAAY,IAAI,SAAS;AAClE,CAAC;AAQM,MAAM,iBAAiB,MAAe,WAAW,IAAI;AAWrD,MAAM,2BAA2B,CAAC,aACvC,WAAW,SAAS,QAAQ;AAGvB,MAAM,kBAAkB,MAAe,WAAW,IAAI;","names":[]}