@immediately-run/sdk 0.61.0 → 0.64.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.
Files changed (73) hide show
  1. package/dist/agentChatClient.d.cts +1 -0
  2. package/dist/agentChatClient.d.ts +1 -0
  3. package/dist/agentLoop.cjs +7 -1
  4. package/dist/agentLoop.cjs.map +1 -1
  5. package/dist/agentLoop.d.cts +21 -0
  6. package/dist/agentLoop.d.ts +21 -0
  7. package/dist/agentLoop.js +7 -1
  8. package/dist/agentLoop.js.map +1 -1
  9. package/dist/agentPause.cjs +68 -0
  10. package/dist/agentPause.cjs.map +1 -0
  11. package/dist/agentPause.d.cts +37 -0
  12. package/dist/agentPause.d.ts +37 -0
  13. package/dist/agentPause.js +45 -0
  14. package/dist/agentPause.js.map +1 -0
  15. package/dist/anchorClick.cjs +40 -0
  16. package/dist/anchorClick.cjs.map +1 -0
  17. package/dist/anchorClick.d.cts +23 -0
  18. package/dist/anchorClick.d.ts +23 -0
  19. package/dist/anchorClick.js +16 -0
  20. package/dist/anchorClick.js.map +1 -0
  21. package/dist/components/Link.cjs +4 -11
  22. package/dist/components/Link.cjs.map +1 -1
  23. package/dist/components/Link.d.cts +1 -1
  24. package/dist/components/Link.d.ts +1 -1
  25. package/dist/components/Link.js +3 -10
  26. package/dist/components/Link.js.map +1 -1
  27. package/dist/index.cjs +4 -0
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +4 -2
  30. package/dist/index.d.ts +4 -2
  31. package/dist/index.js +2 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/mounts.cjs +16 -0
  34. package/dist/mounts.cjs.map +1 -1
  35. package/dist/mounts.d.cts +18 -1
  36. package/dist/mounts.d.ts +18 -1
  37. package/dist/mounts.js +16 -0
  38. package/dist/mounts.js.map +1 -1
  39. package/dist/openRepository.cjs +44 -0
  40. package/dist/openRepository.cjs.map +1 -0
  41. package/dist/openRepository.d.cts +36 -0
  42. package/dist/openRepository.d.ts +36 -0
  43. package/dist/openRepository.js +21 -0
  44. package/dist/openRepository.js.map +1 -0
  45. package/dist/platformLink.cjs +18 -3
  46. package/dist/platformLink.cjs.map +1 -1
  47. package/dist/platformLink.d.cts +33 -10
  48. package/dist/platformLink.d.ts +33 -10
  49. package/dist/platformLink.js +18 -3
  50. package/dist/platformLink.js.map +1 -1
  51. package/dist/protocolSchemes.cjs +2 -0
  52. package/dist/protocolSchemes.cjs.map +1 -1
  53. package/dist/protocolSchemes.d.cts +2 -0
  54. package/dist/protocolSchemes.d.ts +2 -0
  55. package/dist/protocolSchemes.js +4 -0
  56. package/dist/protocolSchemes.js.map +1 -1
  57. package/dist/region.cjs +22 -2
  58. package/dist/region.cjs.map +1 -1
  59. package/dist/region.d.cts +20 -1
  60. package/dist/region.d.ts +20 -1
  61. package/dist/region.js +18 -1
  62. package/dist/region.js.map +1 -1
  63. package/dist/urlUtils.cjs.map +1 -1
  64. package/dist/urlUtils.d.cts +6 -2
  65. package/dist/urlUtils.d.ts +6 -2
  66. package/dist/urlUtils.js.map +1 -1
  67. package/dist/version.cjs +1 -1
  68. package/dist/version.cjs.map +1 -1
  69. package/dist/version.d.cts +1 -1
  70. package/dist/version.d.ts +1 -1
  71. package/dist/version.js +1 -1
  72. package/dist/version.js.map +1 -1
  73. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/platformLink.tsx"],"sourcesContent":["import type { AnchorHTMLAttributes, ReactNode } from 'react';\nimport { use } from 'react';\n\nimport { TinkerableContext } from './TinkerableContext';\nimport { platformHref } from './urlUtils';\n\n/**\n * Build a PLATFORM-space href (`/present/…`, `/edit/github/…`, `/home`) in the host's URL\n * space, reading `outerHref` from {@link TinkerableContext} the way `useTinkerableLink` does.\n * The returned closure is fresh each render (its output is pure, so identity churn is\n * harmless); an empty context (no host, `vite dev`) yields the path unchanged.\n *\n * Render the result through {@link PlatformLink}, which also carries `target=\"_top\"` —\n * a root-relative href inside the sandboxed frame would resolve against the SANDBOX origin\n * and land nowhere, and an anchor without `target=\"_top\"` navigates the frame instead of\n * the page.\n */\nexport const usePlatformHref = (): ((path: string) => string) => {\n const { outerHref } = use(TinkerableContext);\n return (path: string) => platformHref(outerHref, path);\n};\n\nexport interface PlatformLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {\n /** A root-relative platform path, e.g. `/present/github/acme/todo`. */\n path: string;\n children?: ReactNode;\n}\n\n/**\n * The ONE way to render an anchor to a PLATFORM route: it builds the href with\n * {@link platformHref} (resolving against the host's outer origin) and always carries\n * `target=\"_top\"`, because an anchor inside the sandboxed frame otherwise navigates the\n * frame instead of the host document. External URLs (`https://…`) are not platform routes\n * and should stay plain `<a target=\"_blank\">` anchors.\n */\nexport function PlatformLink({ path, children, ...rest }: PlatformLinkProps) {\n const platform = usePlatformHref();\n return (\n <a {...rest} href={platform(path)} target=\"_top\">\n {children}\n </a>\n );\n}\n"],"mappings":";AAsCI;AArCJ,SAAS,WAAW;AAEpB,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;AAatB,MAAM,kBAAkB,MAAkC;AAC/D,QAAM,EAAE,UAAU,IAAI,IAAI,iBAAiB;AAC3C,SAAO,CAAC,SAAiB,aAAa,WAAW,IAAI;AACvD;AAeO,SAAS,aAAa,EAAE,MAAM,UAAU,GAAG,KAAK,GAAsB;AAC3E,QAAM,WAAW,gBAAgB;AACjC,SACE,oBAAC,OAAG,GAAG,MAAM,MAAM,SAAS,IAAI,GAAG,QAAO,QACvC,UACH;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../src/platformLink.tsx"],"sourcesContent":["import type { AnchorHTMLAttributes, ReactNode } from 'react';\nimport { use } from 'react';\n\nimport { isBrowserGestureClick, useComposedAnchorClick } from './anchorClick';\nimport { navigate } from './routing';\nimport { TinkerableContext } from './TinkerableContext';\nimport { platformHref } from './urlUtils';\n\n/**\n * Build a PLATFORM-space href (`/present/…`, `/edit/github/…`, `/home`) in the host's URL\n * space, reading `outerHref` from {@link TinkerableContext} the way `useTinkerableLink` does.\n * The returned closure is fresh each render (its output is pure, so identity churn is\n * harmless); an empty context (no host, `vite dev`) yields the path unchanged.\n *\n * Prefer {@link PlatformLink} over calling this directly. An href alone does not reach a\n * platform route from inside the app frame (see that component), so a consumer that renders\n * its own anchor from this string must ask the host itself — otherwise it ships a link that\n * copies and opens-in-new-tab correctly and does nothing at all on a plain click. It is kept\n * exported because the wire and the module surface are additive-only\n * (`SDK_PACKAGING_SPEC` §9): an app pinned to an older SDK may already import it.\n */\nexport const usePlatformHref = (): ((path: string) => string) => {\n const { outerHref } = use(TinkerableContext);\n return (path: string) => platformHref(outerHref, path);\n};\n\n/**\n * Targets that reuse an existing browsing context. All three are unreachable from inside the\n * sandboxed app frame by the anchor alone — `_top`/`_parent` are refused outright, `_self`\n * merely moves the frame — so all three are asked of the host instead. Anything else opens a\n * new context, which the sandbox allows.\n */\nconst SAME_CONTEXT_TARGETS = new Set(['_top', '_self', '_parent']);\n\nexport interface PlatformLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {\n /** A root-relative platform path, e.g. `/present/github/acme/todo`. */\n path: string;\n children?: ReactNode;\n}\n\n/**\n * The ONE way to render an anchor to a PLATFORM route.\n *\n * It builds the href with {@link platformHref} (resolving against the host's outer origin)\n * and, on a plain left-click, asks the HOST to perform the navigation.\n *\n * **Why the anchor alone does not work (R3-568).** Until R3-568 this component relied solely\n * on `target=\"_top\"`. The app frame's sandbox omits `allow-top-navigation-by-user-activation`\n * deliberately — an app that can move the top-level window on its own schedule is a phishing\n * primitive — so the browser refuses outright and logs *\"Unsafe attempt to initiate\n * navigation…\"*. Every platform link in every app was inert on the host: measured on\n * production, no Open, no Fork, no Run, and no way to sign in.\n *\n * The host is therefore the only thing that can perform this navigation, and it is asked the\n * same way in-app routing asks — {@link navigate}. The host decides: it accepts a target\n * outside the app's own path prefix only when the target is same-origin, is a recognised\n * platform route, and the HOST's own `navigator.userActivation` says a person just acted.\n * Nothing the app asserts substitutes for that gesture, and a refusal is the host's to report.\n *\n * **The `href` and `target` stay.** They are what make copy-link, middle-click and\n * open-in-new-tab produce something that resolves for another reader — gestures the sandbox\n * does allow (`allow-popups`), which the handler below deliberately declines to intercept.\n * The href is also the correct behaviour with no host at all (`vite dev`), where there is\n * nobody to ask.\n *\n * External URLs (`https://…`) are not platform routes and should stay plain\n * `<a target=\"_blank\">` anchors.\n */\nexport function PlatformLink({ path, children, onClick, target = '_top', ...rest }: PlatformLinkProps) {\n const { outerHref } = use(TinkerableContext);\n const href = platformHref(outerHref, path);\n\n const clickHandler = useComposedAnchorClick(\n onClick,\n (event) => {\n // Open-in-new-tab gestures are the browser's — the sandbox allows those.\n if (isBrowserGestureClick(event)) return;\n // Intercept every target that stays in an EXISTING browsing context, not just the\n // default. `_top` and `_parent` both address the host document from inside the app\n // frame and are refused by the same missing sandbox flag; `_self` would navigate the\n // app frame itself to a host URL, framing the host inside its own sandbox — the\n // regression `components/Link.tsx` documents. Only a NEW context (`_blank`, a named\n // window) is genuinely the browser's, because that is what `allow-popups` permits.\n if (!SAME_CONTEXT_TARGETS.has(target)) return;\n // No host (`vite dev`): there is nobody to ask, and the anchor's own href is right.\n if (!outerHref) return;\n event.preventDefault();\n navigate(href);\n },\n [href, outerHref, target],\n );\n\n return (\n <a {...rest} href={href} target={target} onClick={clickHandler}>\n {children}\n </a>\n );\n}\n"],"mappings":";AA6FI;AA5FJ,SAAS,WAAW;AAEpB,SAAS,uBAAuB,8BAA8B;AAC9D,SAAS,gBAAgB;AACzB,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;AAetB,MAAM,kBAAkB,MAAkC;AAC/D,QAAM,EAAE,UAAU,IAAI,IAAI,iBAAiB;AAC3C,SAAO,CAAC,SAAiB,aAAa,WAAW,IAAI;AACvD;AAQA,MAAM,uBAAuB,oBAAI,IAAI,CAAC,QAAQ,SAAS,SAAS,CAAC;AAoC1D,SAAS,aAAa,EAAE,MAAM,UAAU,SAAS,SAAS,QAAQ,GAAG,KAAK,GAAsB;AACrG,QAAM,EAAE,UAAU,IAAI,IAAI,iBAAiB;AAC3C,QAAM,OAAO,aAAa,WAAW,IAAI;AAEzC,QAAM,eAAe;AAAA,IACnB;AAAA,IACA,CAAC,UAAU;AAET,UAAI,sBAAsB,KAAK,EAAG;AAOlC,UAAI,CAAC,qBAAqB,IAAI,MAAM,EAAG;AAEvC,UAAI,CAAC,UAAW;AAChB,YAAM,eAAe;AACrB,eAAS,IAAI;AAAA,IACf;AAAA,IACA,CAAC,MAAM,WAAW,MAAM;AAAA,EAC1B;AAEA,SACE,oBAAC,OAAG,GAAG,MAAM,MAAY,QAAgB,SAAS,cAC/C,UACH;AAEJ;","names":[]}
@@ -34,6 +34,8 @@ const SCHEMES = {
34
34
  [import_protocol.PROTOCOL_IPC]: schemeOf(import_protocol.PROTOCOL_IPC),
35
35
  [import_protocol.PROTOCOL_LAUNCH]: schemeOf(import_protocol.PROTOCOL_LAUNCH),
36
36
  [import_protocol.PROTOCOL_LLM]: schemeOf(import_protocol.PROTOCOL_LLM),
37
+ [import_protocol.PROTOCOL_LOCALSTORE]: schemeOf(import_protocol.PROTOCOL_LOCALSTORE),
38
+ [import_protocol.PROTOCOL_OPENREPO]: schemeOf(import_protocol.PROTOCOL_OPENREPO),
37
39
  [import_protocol.PROTOCOL_RECENTS]: schemeOf(import_protocol.PROTOCOL_RECENTS),
38
40
  [import_protocol.PROTOCOL_SECRETS]: schemeOf(import_protocol.PROTOCOL_SECRETS),
39
41
  [import_protocol.PROTOCOL_SETTINGS]: schemeOf(import_protocol.PROTOCOL_SETTINGS),
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/protocolSchemes.ts"],"sourcesContent":["// The `protocol-<scheme>` SCHEMES, derived from the wire names.\n//\n// `protocolRequest(scheme, method, params)` takes the scheme — `'theme'` — while the\n// wire name the frame dispatches on is `'protocol-theme'`; the frame adds the prefix.\n// So the typed service wrappers cannot pass the published `PROTOCOL_*` constant\n// directly, and spelling the scheme inline would put a second, unguarded copy of the\n// name back in the tree — exactly what R3-274c removes.\n//\n// Instead the schemes are *derived* from the wire names, keyed BY the wire name:\n//\n// protocolRequest(SCHEMES[PROTOCOL_THEME], 'set', [{ theme }])\n//\n// Keying by the constant is what makes the derivation unfalsifiable — there is no\n// second place to name the family, so there is no pair to get wrong. `schemeOf`\n// returns a template-literal conditional, so each value has a literal type (`'theme'`),\n// which buys two more things:\n//\n// - a wire name that stops matching `protocol-*` stops compiling here, rather than\n// silently producing an empty scheme at runtime;\n// - `check-protocol-snapshot.mjs` resolves `SCHEMES[PROTOCOL_THEME]` through the type\n// checker exactly like a plain literal, so the call sites stay visible to the gate.\n//\n// One export, deliberately: `./*` is a public subpath, so every name added here is\n// public API forever (ways_of_working §6, additive-only).\n//\n// This module is NOT generated — the derivation is the content — so it lives outside\n// `src/generated/`.\nimport {\n PROTOCOL_ANALYTICS,\n PROTOCOL_CONTRIBUTE,\n PROTOCOL_DND,\n PROTOCOL_EDITOR,\n PROTOCOL_FEED,\n PROTOCOL_FETCH,\n PROTOCOL_IPC,\n PROTOCOL_LAUNCH,\n PROTOCOL_LLM,\n PROTOCOL_RECENTS,\n PROTOCOL_SECRETS,\n PROTOCOL_SETTINGS,\n PROTOCOL_SPACES,\n PROTOCOL_TASK,\n PROTOCOL_THEME,\n PROTOCOL_VCS,\n} from './generated/protocol';\n\nconst PREFIX = 'protocol-';\n\n/** The scheme half of a `protocol-<scheme>` wire name. */\ntype SchemeOf<N extends string> = N extends `${typeof PREFIX}${infer S}` ? S : never;\n\n/**\n * `'protocol-theme'` → `'theme'`, as a literal type.\n *\n * The cast is the only place the derivation is asserted rather than computed; the\n * `N extends \\`protocol-${string}\\`` bound is what makes it sound — a wire name that is\n * not scheme-shaped is a compile error at the call, not a `never` at runtime.\n */\nconst schemeOf = <N extends `${typeof PREFIX}${string}`>(name: N): SchemeOf<N> =>\n name.slice(PREFIX.length) as SchemeOf<N>;\n\n/** Every `protocol-*` scheme the SDK speaks, keyed by its wire name. */\nexport const SCHEMES = {\n [PROTOCOL_ANALYTICS]: schemeOf(PROTOCOL_ANALYTICS),\n [PROTOCOL_CONTRIBUTE]: schemeOf(PROTOCOL_CONTRIBUTE),\n [PROTOCOL_DND]: schemeOf(PROTOCOL_DND),\n [PROTOCOL_EDITOR]: schemeOf(PROTOCOL_EDITOR),\n [PROTOCOL_FEED]: schemeOf(PROTOCOL_FEED),\n [PROTOCOL_FETCH]: schemeOf(PROTOCOL_FETCH),\n [PROTOCOL_IPC]: schemeOf(PROTOCOL_IPC),\n [PROTOCOL_LAUNCH]: schemeOf(PROTOCOL_LAUNCH),\n [PROTOCOL_LLM]: schemeOf(PROTOCOL_LLM),\n [PROTOCOL_RECENTS]: schemeOf(PROTOCOL_RECENTS),\n [PROTOCOL_SECRETS]: schemeOf(PROTOCOL_SECRETS),\n [PROTOCOL_SETTINGS]: schemeOf(PROTOCOL_SETTINGS),\n [PROTOCOL_SPACES]: schemeOf(PROTOCOL_SPACES),\n [PROTOCOL_TASK]: schemeOf(PROTOCOL_TASK),\n [PROTOCOL_THEME]: schemeOf(PROTOCOL_THEME),\n [PROTOCOL_VCS]: schemeOf(PROTOCOL_VCS),\n} as const;\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA2BA,sBAiBO;AAEP,MAAM,SAAS;AAYf,MAAM,WAAW,CAAwC,SACvD,KAAK,MAAM,OAAO,MAAM;AAGnB,MAAM,UAAU;AAAA,EACrB,CAAC,kCAAkB,GAAG,SAAS,kCAAkB;AAAA,EACjD,CAAC,mCAAmB,GAAG,SAAS,mCAAmB;AAAA,EACnD,CAAC,4BAAY,GAAG,SAAS,4BAAY;AAAA,EACrC,CAAC,+BAAe,GAAG,SAAS,+BAAe;AAAA,EAC3C,CAAC,6BAAa,GAAG,SAAS,6BAAa;AAAA,EACvC,CAAC,8BAAc,GAAG,SAAS,8BAAc;AAAA,EACzC,CAAC,4BAAY,GAAG,SAAS,4BAAY;AAAA,EACrC,CAAC,+BAAe,GAAG,SAAS,+BAAe;AAAA,EAC3C,CAAC,4BAAY,GAAG,SAAS,4BAAY;AAAA,EACrC,CAAC,gCAAgB,GAAG,SAAS,gCAAgB;AAAA,EAC7C,CAAC,gCAAgB,GAAG,SAAS,gCAAgB;AAAA,EAC7C,CAAC,iCAAiB,GAAG,SAAS,iCAAiB;AAAA,EAC/C,CAAC,+BAAe,GAAG,SAAS,+BAAe;AAAA,EAC3C,CAAC,6BAAa,GAAG,SAAS,6BAAa;AAAA,EACvC,CAAC,8BAAc,GAAG,SAAS,8BAAc;AAAA,EACzC,CAAC,4BAAY,GAAG,SAAS,4BAAY;AACvC;","names":[]}
1
+ {"version":3,"sources":["../src/protocolSchemes.ts"],"sourcesContent":["// The `protocol-<scheme>` SCHEMES, derived from the wire names.\n//\n// `protocolRequest(scheme, method, params)` takes the scheme — `'theme'` — while the\n// wire name the frame dispatches on is `'protocol-theme'`; the frame adds the prefix.\n// So the typed service wrappers cannot pass the published `PROTOCOL_*` constant\n// directly, and spelling the scheme inline would put a second, unguarded copy of the\n// name back in the tree — exactly what R3-274c removes.\n//\n// Instead the schemes are *derived* from the wire names, keyed BY the wire name:\n//\n// protocolRequest(SCHEMES[PROTOCOL_THEME], 'set', [{ theme }])\n//\n// Keying by the constant is what makes the derivation unfalsifiable — there is no\n// second place to name the family, so there is no pair to get wrong. `schemeOf`\n// returns a template-literal conditional, so each value has a literal type (`'theme'`),\n// which buys two more things:\n//\n// - a wire name that stops matching `protocol-*` stops compiling here, rather than\n// silently producing an empty scheme at runtime;\n// - `check-protocol-snapshot.mjs` resolves `SCHEMES[PROTOCOL_THEME]` through the type\n// checker exactly like a plain literal, so the call sites stay visible to the gate.\n//\n// One export, deliberately: `./*` is a public subpath, so every name added here is\n// public API forever (ways_of_working §6, additive-only).\n//\n// This module is NOT generated — the derivation is the content — so it lives outside\n// `src/generated/`.\nimport {\n PROTOCOL_ANALYTICS,\n PROTOCOL_CONTRIBUTE,\n PROTOCOL_DND,\n PROTOCOL_EDITOR,\n PROTOCOL_FEED,\n PROTOCOL_FETCH,\n PROTOCOL_IPC,\n PROTOCOL_LAUNCH,\n PROTOCOL_LLM,\n PROTOCOL_LOCALSTORE,\n PROTOCOL_OPENREPO,\n PROTOCOL_RECENTS,\n PROTOCOL_SECRETS,\n PROTOCOL_SETTINGS,\n PROTOCOL_SPACES,\n PROTOCOL_TASK,\n PROTOCOL_THEME,\n PROTOCOL_VCS,\n} from './generated/protocol';\n\nconst PREFIX = 'protocol-';\n\n/** The scheme half of a `protocol-<scheme>` wire name. */\ntype SchemeOf<N extends string> = N extends `${typeof PREFIX}${infer S}` ? S : never;\n\n/**\n * `'protocol-theme'` → `'theme'`, as a literal type.\n *\n * The cast is the only place the derivation is asserted rather than computed; the\n * `N extends \\`protocol-${string}\\`` bound is what makes it sound — a wire name that is\n * not scheme-shaped is a compile error at the call, not a `never` at runtime.\n */\nconst schemeOf = <N extends `${typeof PREFIX}${string}`>(name: N): SchemeOf<N> =>\n name.slice(PREFIX.length) as SchemeOf<N>;\n\n/** Every `protocol-*` scheme the SDK speaks, keyed by its wire name. */\nexport const SCHEMES = {\n [PROTOCOL_ANALYTICS]: schemeOf(PROTOCOL_ANALYTICS),\n [PROTOCOL_CONTRIBUTE]: schemeOf(PROTOCOL_CONTRIBUTE),\n [PROTOCOL_DND]: schemeOf(PROTOCOL_DND),\n [PROTOCOL_EDITOR]: schemeOf(PROTOCOL_EDITOR),\n [PROTOCOL_FEED]: schemeOf(PROTOCOL_FEED),\n [PROTOCOL_FETCH]: schemeOf(PROTOCOL_FETCH),\n [PROTOCOL_IPC]: schemeOf(PROTOCOL_IPC),\n [PROTOCOL_LAUNCH]: schemeOf(PROTOCOL_LAUNCH),\n [PROTOCOL_LLM]: schemeOf(PROTOCOL_LLM),\n [PROTOCOL_LOCALSTORE]: schemeOf(PROTOCOL_LOCALSTORE),\n [PROTOCOL_OPENREPO]: schemeOf(PROTOCOL_OPENREPO),\n [PROTOCOL_RECENTS]: schemeOf(PROTOCOL_RECENTS),\n [PROTOCOL_SECRETS]: schemeOf(PROTOCOL_SECRETS),\n [PROTOCOL_SETTINGS]: schemeOf(PROTOCOL_SETTINGS),\n [PROTOCOL_SPACES]: schemeOf(PROTOCOL_SPACES),\n [PROTOCOL_TASK]: schemeOf(PROTOCOL_TASK),\n [PROTOCOL_THEME]: schemeOf(PROTOCOL_THEME),\n [PROTOCOL_VCS]: schemeOf(PROTOCOL_VCS),\n} as const;\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA2BA,sBAmBO;AAEP,MAAM,SAAS;AAYf,MAAM,WAAW,CAAwC,SACvD,KAAK,MAAM,OAAO,MAAM;AAGnB,MAAM,UAAU;AAAA,EACrB,CAAC,kCAAkB,GAAG,SAAS,kCAAkB;AAAA,EACjD,CAAC,mCAAmB,GAAG,SAAS,mCAAmB;AAAA,EACnD,CAAC,4BAAY,GAAG,SAAS,4BAAY;AAAA,EACrC,CAAC,+BAAe,GAAG,SAAS,+BAAe;AAAA,EAC3C,CAAC,6BAAa,GAAG,SAAS,6BAAa;AAAA,EACvC,CAAC,8BAAc,GAAG,SAAS,8BAAc;AAAA,EACzC,CAAC,4BAAY,GAAG,SAAS,4BAAY;AAAA,EACrC,CAAC,+BAAe,GAAG,SAAS,+BAAe;AAAA,EAC3C,CAAC,4BAAY,GAAG,SAAS,4BAAY;AAAA,EACrC,CAAC,mCAAmB,GAAG,SAAS,mCAAmB;AAAA,EACnD,CAAC,iCAAiB,GAAG,SAAS,iCAAiB;AAAA,EAC/C,CAAC,gCAAgB,GAAG,SAAS,gCAAgB;AAAA,EAC7C,CAAC,gCAAgB,GAAG,SAAS,gCAAgB;AAAA,EAC7C,CAAC,iCAAiB,GAAG,SAAS,iCAAiB;AAAA,EAC/C,CAAC,+BAAe,GAAG,SAAS,+BAAe;AAAA,EAC3C,CAAC,6BAAa,GAAG,SAAS,6BAAa;AAAA,EACvC,CAAC,8BAAc,GAAG,SAAS,8BAAc;AAAA,EACzC,CAAC,4BAAY,GAAG,SAAS,4BAAY;AACvC;","names":[]}
@@ -9,6 +9,8 @@ declare const SCHEMES: {
9
9
  readonly "protocol-ipc": "ipc";
10
10
  readonly "protocol-launch": "launch";
11
11
  readonly "protocol-llm": "llm";
12
+ readonly "protocol-localstore": "localstore";
13
+ readonly "protocol-openrepo": "openrepo";
12
14
  readonly "protocol-recents": "recents";
13
15
  readonly "protocol-secrets": "secrets";
14
16
  readonly "protocol-settings": "settings";
@@ -9,6 +9,8 @@ declare const SCHEMES: {
9
9
  readonly "protocol-ipc": "ipc";
10
10
  readonly "protocol-launch": "launch";
11
11
  readonly "protocol-llm": "llm";
12
+ readonly "protocol-localstore": "localstore";
13
+ readonly "protocol-openrepo": "openrepo";
12
14
  readonly "protocol-recents": "recents";
13
15
  readonly "protocol-secrets": "secrets";
14
16
  readonly "protocol-settings": "settings";
@@ -9,6 +9,8 @@ import {
9
9
  PROTOCOL_IPC,
10
10
  PROTOCOL_LAUNCH,
11
11
  PROTOCOL_LLM,
12
+ PROTOCOL_LOCALSTORE,
13
+ PROTOCOL_OPENREPO,
12
14
  PROTOCOL_RECENTS,
13
15
  PROTOCOL_SECRETS,
14
16
  PROTOCOL_SETTINGS,
@@ -29,6 +31,8 @@ const SCHEMES = {
29
31
  [PROTOCOL_IPC]: schemeOf(PROTOCOL_IPC),
30
32
  [PROTOCOL_LAUNCH]: schemeOf(PROTOCOL_LAUNCH),
31
33
  [PROTOCOL_LLM]: schemeOf(PROTOCOL_LLM),
34
+ [PROTOCOL_LOCALSTORE]: schemeOf(PROTOCOL_LOCALSTORE),
35
+ [PROTOCOL_OPENREPO]: schemeOf(PROTOCOL_OPENREPO),
32
36
  [PROTOCOL_RECENTS]: schemeOf(PROTOCOL_RECENTS),
33
37
  [PROTOCOL_SECRETS]: schemeOf(PROTOCOL_SECRETS),
34
38
  [PROTOCOL_SETTINGS]: schemeOf(PROTOCOL_SETTINGS),
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/protocolSchemes.ts"],"sourcesContent":["// The `protocol-<scheme>` SCHEMES, derived from the wire names.\n//\n// `protocolRequest(scheme, method, params)` takes the scheme — `'theme'` — while the\n// wire name the frame dispatches on is `'protocol-theme'`; the frame adds the prefix.\n// So the typed service wrappers cannot pass the published `PROTOCOL_*` constant\n// directly, and spelling the scheme inline would put a second, unguarded copy of the\n// name back in the tree — exactly what R3-274c removes.\n//\n// Instead the schemes are *derived* from the wire names, keyed BY the wire name:\n//\n// protocolRequest(SCHEMES[PROTOCOL_THEME], 'set', [{ theme }])\n//\n// Keying by the constant is what makes the derivation unfalsifiable — there is no\n// second place to name the family, so there is no pair to get wrong. `schemeOf`\n// returns a template-literal conditional, so each value has a literal type (`'theme'`),\n// which buys two more things:\n//\n// - a wire name that stops matching `protocol-*` stops compiling here, rather than\n// silently producing an empty scheme at runtime;\n// - `check-protocol-snapshot.mjs` resolves `SCHEMES[PROTOCOL_THEME]` through the type\n// checker exactly like a plain literal, so the call sites stay visible to the gate.\n//\n// One export, deliberately: `./*` is a public subpath, so every name added here is\n// public API forever (ways_of_working §6, additive-only).\n//\n// This module is NOT generated — the derivation is the content — so it lives outside\n// `src/generated/`.\nimport {\n PROTOCOL_ANALYTICS,\n PROTOCOL_CONTRIBUTE,\n PROTOCOL_DND,\n PROTOCOL_EDITOR,\n PROTOCOL_FEED,\n PROTOCOL_FETCH,\n PROTOCOL_IPC,\n PROTOCOL_LAUNCH,\n PROTOCOL_LLM,\n PROTOCOL_RECENTS,\n PROTOCOL_SECRETS,\n PROTOCOL_SETTINGS,\n PROTOCOL_SPACES,\n PROTOCOL_TASK,\n PROTOCOL_THEME,\n PROTOCOL_VCS,\n} from './generated/protocol';\n\nconst PREFIX = 'protocol-';\n\n/** The scheme half of a `protocol-<scheme>` wire name. */\ntype SchemeOf<N extends string> = N extends `${typeof PREFIX}${infer S}` ? S : never;\n\n/**\n * `'protocol-theme'` → `'theme'`, as a literal type.\n *\n * The cast is the only place the derivation is asserted rather than computed; the\n * `N extends \\`protocol-${string}\\`` bound is what makes it sound — a wire name that is\n * not scheme-shaped is a compile error at the call, not a `never` at runtime.\n */\nconst schemeOf = <N extends `${typeof PREFIX}${string}`>(name: N): SchemeOf<N> =>\n name.slice(PREFIX.length) as SchemeOf<N>;\n\n/** Every `protocol-*` scheme the SDK speaks, keyed by its wire name. */\nexport const SCHEMES = {\n [PROTOCOL_ANALYTICS]: schemeOf(PROTOCOL_ANALYTICS),\n [PROTOCOL_CONTRIBUTE]: schemeOf(PROTOCOL_CONTRIBUTE),\n [PROTOCOL_DND]: schemeOf(PROTOCOL_DND),\n [PROTOCOL_EDITOR]: schemeOf(PROTOCOL_EDITOR),\n [PROTOCOL_FEED]: schemeOf(PROTOCOL_FEED),\n [PROTOCOL_FETCH]: schemeOf(PROTOCOL_FETCH),\n [PROTOCOL_IPC]: schemeOf(PROTOCOL_IPC),\n [PROTOCOL_LAUNCH]: schemeOf(PROTOCOL_LAUNCH),\n [PROTOCOL_LLM]: schemeOf(PROTOCOL_LLM),\n [PROTOCOL_RECENTS]: schemeOf(PROTOCOL_RECENTS),\n [PROTOCOL_SECRETS]: schemeOf(PROTOCOL_SECRETS),\n [PROTOCOL_SETTINGS]: schemeOf(PROTOCOL_SETTINGS),\n [PROTOCOL_SPACES]: schemeOf(PROTOCOL_SPACES),\n [PROTOCOL_TASK]: schemeOf(PROTOCOL_TASK),\n [PROTOCOL_THEME]: schemeOf(PROTOCOL_THEME),\n [PROTOCOL_VCS]: schemeOf(PROTOCOL_VCS),\n} as const;\n"],"mappings":";AA2BA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,SAAS;AAYf,MAAM,WAAW,CAAwC,SACvD,KAAK,MAAM,OAAO,MAAM;AAGnB,MAAM,UAAU;AAAA,EACrB,CAAC,kBAAkB,GAAG,SAAS,kBAAkB;AAAA,EACjD,CAAC,mBAAmB,GAAG,SAAS,mBAAmB;AAAA,EACnD,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,aAAa,GAAG,SAAS,aAAa;AAAA,EACvC,CAAC,cAAc,GAAG,SAAS,cAAc;AAAA,EACzC,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,gBAAgB,GAAG,SAAS,gBAAgB;AAAA,EAC7C,CAAC,gBAAgB,GAAG,SAAS,gBAAgB;AAAA,EAC7C,CAAC,iBAAiB,GAAG,SAAS,iBAAiB;AAAA,EAC/C,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,aAAa,GAAG,SAAS,aAAa;AAAA,EACvC,CAAC,cAAc,GAAG,SAAS,cAAc;AAAA,EACzC,CAAC,YAAY,GAAG,SAAS,YAAY;AACvC;","names":[]}
1
+ {"version":3,"sources":["../src/protocolSchemes.ts"],"sourcesContent":["// The `protocol-<scheme>` SCHEMES, derived from the wire names.\n//\n// `protocolRequest(scheme, method, params)` takes the scheme — `'theme'` — while the\n// wire name the frame dispatches on is `'protocol-theme'`; the frame adds the prefix.\n// So the typed service wrappers cannot pass the published `PROTOCOL_*` constant\n// directly, and spelling the scheme inline would put a second, unguarded copy of the\n// name back in the tree — exactly what R3-274c removes.\n//\n// Instead the schemes are *derived* from the wire names, keyed BY the wire name:\n//\n// protocolRequest(SCHEMES[PROTOCOL_THEME], 'set', [{ theme }])\n//\n// Keying by the constant is what makes the derivation unfalsifiable — there is no\n// second place to name the family, so there is no pair to get wrong. `schemeOf`\n// returns a template-literal conditional, so each value has a literal type (`'theme'`),\n// which buys two more things:\n//\n// - a wire name that stops matching `protocol-*` stops compiling here, rather than\n// silently producing an empty scheme at runtime;\n// - `check-protocol-snapshot.mjs` resolves `SCHEMES[PROTOCOL_THEME]` through the type\n// checker exactly like a plain literal, so the call sites stay visible to the gate.\n//\n// One export, deliberately: `./*` is a public subpath, so every name added here is\n// public API forever (ways_of_working §6, additive-only).\n//\n// This module is NOT generated — the derivation is the content — so it lives outside\n// `src/generated/`.\nimport {\n PROTOCOL_ANALYTICS,\n PROTOCOL_CONTRIBUTE,\n PROTOCOL_DND,\n PROTOCOL_EDITOR,\n PROTOCOL_FEED,\n PROTOCOL_FETCH,\n PROTOCOL_IPC,\n PROTOCOL_LAUNCH,\n PROTOCOL_LLM,\n PROTOCOL_LOCALSTORE,\n PROTOCOL_OPENREPO,\n PROTOCOL_RECENTS,\n PROTOCOL_SECRETS,\n PROTOCOL_SETTINGS,\n PROTOCOL_SPACES,\n PROTOCOL_TASK,\n PROTOCOL_THEME,\n PROTOCOL_VCS,\n} from './generated/protocol';\n\nconst PREFIX = 'protocol-';\n\n/** The scheme half of a `protocol-<scheme>` wire name. */\ntype SchemeOf<N extends string> = N extends `${typeof PREFIX}${infer S}` ? S : never;\n\n/**\n * `'protocol-theme'` → `'theme'`, as a literal type.\n *\n * The cast is the only place the derivation is asserted rather than computed; the\n * `N extends \\`protocol-${string}\\`` bound is what makes it sound — a wire name that is\n * not scheme-shaped is a compile error at the call, not a `never` at runtime.\n */\nconst schemeOf = <N extends `${typeof PREFIX}${string}`>(name: N): SchemeOf<N> =>\n name.slice(PREFIX.length) as SchemeOf<N>;\n\n/** Every `protocol-*` scheme the SDK speaks, keyed by its wire name. */\nexport const SCHEMES = {\n [PROTOCOL_ANALYTICS]: schemeOf(PROTOCOL_ANALYTICS),\n [PROTOCOL_CONTRIBUTE]: schemeOf(PROTOCOL_CONTRIBUTE),\n [PROTOCOL_DND]: schemeOf(PROTOCOL_DND),\n [PROTOCOL_EDITOR]: schemeOf(PROTOCOL_EDITOR),\n [PROTOCOL_FEED]: schemeOf(PROTOCOL_FEED),\n [PROTOCOL_FETCH]: schemeOf(PROTOCOL_FETCH),\n [PROTOCOL_IPC]: schemeOf(PROTOCOL_IPC),\n [PROTOCOL_LAUNCH]: schemeOf(PROTOCOL_LAUNCH),\n [PROTOCOL_LLM]: schemeOf(PROTOCOL_LLM),\n [PROTOCOL_LOCALSTORE]: schemeOf(PROTOCOL_LOCALSTORE),\n [PROTOCOL_OPENREPO]: schemeOf(PROTOCOL_OPENREPO),\n [PROTOCOL_RECENTS]: schemeOf(PROTOCOL_RECENTS),\n [PROTOCOL_SECRETS]: schemeOf(PROTOCOL_SECRETS),\n [PROTOCOL_SETTINGS]: schemeOf(PROTOCOL_SETTINGS),\n [PROTOCOL_SPACES]: schemeOf(PROTOCOL_SPACES),\n [PROTOCOL_TASK]: schemeOf(PROTOCOL_TASK),\n [PROTOCOL_THEME]: schemeOf(PROTOCOL_THEME),\n [PROTOCOL_VCS]: schemeOf(PROTOCOL_VCS),\n} as const;\n"],"mappings":";AA2BA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,SAAS;AAYf,MAAM,WAAW,CAAwC,SACvD,KAAK,MAAM,OAAO,MAAM;AAGnB,MAAM,UAAU;AAAA,EACrB,CAAC,kBAAkB,GAAG,SAAS,kBAAkB;AAAA,EACjD,CAAC,mBAAmB,GAAG,SAAS,mBAAmB;AAAA,EACnD,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,aAAa,GAAG,SAAS,aAAa;AAAA,EACvC,CAAC,cAAc,GAAG,SAAS,cAAc;AAAA,EACzC,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,mBAAmB,GAAG,SAAS,mBAAmB;AAAA,EACnD,CAAC,iBAAiB,GAAG,SAAS,iBAAiB;AAAA,EAC/C,CAAC,gBAAgB,GAAG,SAAS,gBAAgB;AAAA,EAC7C,CAAC,gBAAgB,GAAG,SAAS,gBAAgB;AAAA,EAC7C,CAAC,iBAAiB,GAAG,SAAS,iBAAiB;AAAA,EAC/C,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,aAAa,GAAG,SAAS,aAAa;AAAA,EACvC,CAAC,cAAc,GAAG,SAAS,cAAc;AAAA,EACzC,CAAC,YAAY,GAAG,SAAS,YAAY;AACvC;","names":[]}
package/dist/region.cjs CHANGED
@@ -19,11 +19,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var region_exports = {};
20
20
  __export(region_exports, {
21
21
  getRegion: () => getRegion,
22
- useRegion: () => useRegion
22
+ isRegionHidden: () => isRegionHidden,
23
+ onRegionVisibilityChange: () => onRegionVisibilityChange,
24
+ useRegion: () => useRegion,
25
+ useRegionHidden: () => useRegionHidden
23
26
  });
24
27
  module.exports = __toCommonJS(region_exports);
25
28
  var import_react = require("react");
26
29
  var import_hostRuntime = require("./hostRuntime");
30
+ var import_pushChannel = require("./pushChannel");
31
+ var import_protocol = require("./generated/protocol");
27
32
  const getRegion = () => (0, import_hostRuntime.getHostRuntime)()?.region ?? null;
28
33
  const useRegion = () => {
29
34
  const [region, setRegion] = (0, import_react.useState)(getRegion);
@@ -39,9 +44,24 @@ const useRegion = () => {
39
44
  }, [region]);
40
45
  return region;
41
46
  };
47
+ const visibility = (0, import_pushChannel.createPushChannel)({
48
+ pushType: import_protocol.REGION_VISIBILITY,
49
+ requestType: import_protocol.REQUEST_REGION_VISIBILITY,
50
+ initial: false,
51
+ // Tolerant parse: `undefined` means "ignore this message", so a malformed push from
52
+ // some future/older host leaves the last good value standing rather than flipping
53
+ // the app to a value nobody sent.
54
+ parse: (msg) => typeof msg.hidden === "boolean" ? msg.hidden : void 0
55
+ });
56
+ const isRegionHidden = () => visibility.get();
57
+ const onRegionVisibilityChange = (listener) => visibility.onChange(listener);
58
+ const useRegionHidden = () => visibility.use();
42
59
  // Annotate the CommonJS export names for ESM import in node:
43
60
  0 && (module.exports = {
44
61
  getRegion,
45
- useRegion
62
+ isRegionHidden,
63
+ onRegionVisibilityChange,
64
+ useRegion,
65
+ useRegionHidden
46
66
  });
47
67
  //# sourceMappingURL=region.cjs.map
@@ -1 +1 @@
1
- {"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';\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"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,mBAAoC;AACpC,yBAA+B;AAOxB,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;","names":[]}
1
+ {"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":[]}
package/dist/region.d.cts CHANGED
@@ -10,5 +10,24 @@ declare const getRegion: () => string | null;
10
10
  * re-reads once the host runtime's `ready` promise resolves.
11
11
  */
12
12
  declare const useRegion: () => string | null;
13
+ /**
14
+ * Whether the host has hidden this app's region — it is still mounted and running,
15
+ * but off screen and `inert`, so the user can neither see it nor interact with it.
16
+ *
17
+ * `false` when the host says nothing (a standalone app, `vite dev`, an older host).
18
+ */
19
+ declare const isRegionHidden: () => boolean;
20
+ /**
21
+ * Subscribe to this region's visibility. The listener is invoked immediately with the
22
+ * current value, then on every change. Returns an unsubscribe fn.
23
+ *
24
+ * Use it to STOP doing what the user is supposed to be watching, at your own safe
25
+ * boundary — never mid-operation. An agent loop pauses between turns, so every
26
+ * `tool_use` still has its `tool_result`; a poller stops polling; an animation stops
27
+ * animating. Do not use it to hide UI: the host has already done that.
28
+ */
29
+ declare const onRegionVisibilityChange: (listener: (hidden: boolean) => void) => (() => void);
30
+ /** React hook form of {@link isRegionHidden}, re-rendering on change. */
31
+ declare const useRegionHidden: () => boolean;
13
32
 
14
- export { getRegion, useRegion };
33
+ export { getRegion, isRegionHidden, onRegionVisibilityChange, useRegion, useRegionHidden };
package/dist/region.d.ts CHANGED
@@ -10,5 +10,24 @@ declare const getRegion: () => string | null;
10
10
  * re-reads once the host runtime's `ready` promise resolves.
11
11
  */
12
12
  declare const useRegion: () => string | null;
13
+ /**
14
+ * Whether the host has hidden this app's region — it is still mounted and running,
15
+ * but off screen and `inert`, so the user can neither see it nor interact with it.
16
+ *
17
+ * `false` when the host says nothing (a standalone app, `vite dev`, an older host).
18
+ */
19
+ declare const isRegionHidden: () => boolean;
20
+ /**
21
+ * Subscribe to this region's visibility. The listener is invoked immediately with the
22
+ * current value, then on every change. Returns an unsubscribe fn.
23
+ *
24
+ * Use it to STOP doing what the user is supposed to be watching, at your own safe
25
+ * boundary — never mid-operation. An agent loop pauses between turns, so every
26
+ * `tool_use` still has its `tool_result`; a poller stops polling; an animation stops
27
+ * animating. Do not use it to hide UI: the host has already done that.
28
+ */
29
+ declare const onRegionVisibilityChange: (listener: (hidden: boolean) => void) => (() => void);
30
+ /** React hook form of {@link isRegionHidden}, re-rendering on change. */
31
+ declare const useRegionHidden: () => boolean;
13
32
 
14
- export { getRegion, useRegion };
33
+ export { getRegion, isRegionHidden, onRegionVisibilityChange, useRegion, useRegionHidden };
package/dist/region.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import "./chunk-VHAA22YE.js";
2
2
  import { useEffect, useState } from "react";
3
3
  import { getHostRuntime } from "./hostRuntime";
4
+ import { createPushChannel } from "./pushChannel";
5
+ import { REGION_VISIBILITY, REQUEST_REGION_VISIBILITY } from "./generated/protocol";
4
6
  const getRegion = () => getHostRuntime()?.region ?? null;
5
7
  const useRegion = () => {
6
8
  const [region, setRegion] = useState(getRegion);
@@ -16,8 +18,23 @@ const useRegion = () => {
16
18
  }, [region]);
17
19
  return region;
18
20
  };
21
+ const visibility = createPushChannel({
22
+ pushType: REGION_VISIBILITY,
23
+ requestType: REQUEST_REGION_VISIBILITY,
24
+ initial: false,
25
+ // Tolerant parse: `undefined` means "ignore this message", so a malformed push from
26
+ // some future/older host leaves the last good value standing rather than flipping
27
+ // the app to a value nobody sent.
28
+ parse: (msg) => typeof msg.hidden === "boolean" ? msg.hidden : void 0
29
+ });
30
+ const isRegionHidden = () => visibility.get();
31
+ const onRegionVisibilityChange = (listener) => visibility.onChange(listener);
32
+ const useRegionHidden = () => visibility.use();
19
33
  export {
20
34
  getRegion,
21
- useRegion
35
+ isRegionHidden,
36
+ onRegionVisibilityChange,
37
+ useRegion,
38
+ useRegionHidden
22
39
  };
23
40
  //# sourceMappingURL=region.js.map
@@ -1 +1 @@
1
- {"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';\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"],"mappings":";AAUA,SAAS,WAAW,gBAAgB;AACpC,SAAS,sBAAsB;AAOxB,MAAM,YAAY,MAAqB,eAAe,GAAG,UAAU;AAOnE,MAAM,YAAY,MAAqB;AAC5C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAwB,SAAS;AAC7D,YAAU,MAAM;AACd,QAAI,WAAW,KAAM;AACrB,QAAI,OAAO;AACX,SAAK,eAAe,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;","names":[]}
1
+ {"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":";AAUA,SAAS,WAAW,gBAAgB;AACpC,SAAS,sBAAsB;AAC/B,SAAS,yBAAyB;AAClC,SAAS,mBAAmB,iCAAiC;AAOtD,MAAM,YAAY,MAAqB,eAAe,GAAG,UAAU;AAOnE,MAAM,YAAY,MAAqB;AAC5C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAwB,SAAS;AAC7D,YAAU,MAAM;AACd,QAAI,WAAW,KAAM;AACrB,QAAI,OAAO;AACX,SAAK,eAAe,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,aAAa,kBAA2B;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":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/urlUtils.ts"],"sourcesContent":["import { APP_ROOT, underAppRoot } from '@immediately-run/platform-constants';\n\nimport { joinPaths } from './pathUtils';\nimport { NavigationState, PathState } from './TinkerableContext';\n\nexport const FILES_PREFIX = '/files';\n\n/**\n * Mount point of the Git repository inside the sandbox filesystem, and the join\n * into it — both now defined ONCE in `@immediately-run/platform-constants` (R3-275)\n * and re-exported here so every import site is unchanged.\n *\n * They were declared here AND in `sandbox/src/fsLayout.ts`, with two different\n * implementations of the join (this one over `joinPaths`, the sandbox's hand-rolled).\n * The two agreed; nothing made them agree — and the metadata key space is derived\n * from this constant on both sides, so a drift would have split that key space\n * silently. The package's tests replicate BOTH implementations, quirks included.\n *\n * The sandbox fs is rooted at `/` (so apps can reach dynamic mounts like\n * `/firestore`), with the repo mounted at `APP_ROOT`. URL subpaths are\n * repo-relative, so the file router resolves them under it.\n */\nexport { APP_ROOT, underAppRoot };\n\n/**\n * The origin every outer URL is rebuilt on — `protocol//host`, **port included**.\n *\n * `url.hostname` drops the port; `url.host` keeps it (and still omits it for the default\n * 80/443, so a production URL is byte-identical either way). Using `hostname` here was\n * invisible on `https://immediately.run` and broke every routed link under local dev on any\n * port but 80: `http://localhost:3100/edit/…` was rebuilt as `http://localhost/edit/…`.\n *\n * Worse than a wrong link, it silently changed link BEHAVIOUR. `repositoryPrefixURL` is\n * built from this, and `isInternalHref` decides by prefix-matching against it — so an\n * absolute in-app URL failed the match, was classified EXTERNAL, and rendered as a plain\n * `<a>`. Clicking it performed a real navigation out of the sandboxed frame instead of\n * routing: the app appeared to \"reload\" on an ordinary internal link.\n */\nexport const getOuterHostname = (outerHref: string) => {\n const url = new URL(outerHref);\n return `${url.protocol}//${url.host}`;\n};\n\nexport const getSearchParams = (search?: string): Record<string, string> =>\n Object.fromEntries([...new URLSearchParams(search ?? window.location.search).entries()]);\n\n/**\n * Split a link target into its path part and its `#fragment` (the `#` dropped),\n * e.g. `\"FOO.mdx#sec-8-9\"` → `[\"FOO.mdx\", \"sec-8-9\"]`, `\"#sec-3\"` → `[\"\", \"sec-3\"]`,\n * `\"FOO.mdx\"` → `[\"FOO.mdx\", \"\"]`. Only the **first** `#` splits — a fragment never\n * contains another `#`. The seam between wiki-links (§13) and heading ids (§15):\n * existence is resolved on the path part, the fragment is threaded to navigation.\n * MARKDOWN_SYNTAX_SPEC §13.5.\n */\nexport const splitHash = (target: string): [string, string] => {\n const i = target.indexOf('#');\n return i === -1 ? [target, ''] : [target.slice(0, i), target.slice(i + 1)];\n};\n\nexport const parseTarget = (target: string, navigation: NavigationState): NavigationState => {\n const newNavigation = { ...navigation };\n let [prehash, hash] = target.split('#');\n if (prehash) {\n let [path, search] = prehash.split('?');\n if (path) {\n newNavigation.sandboxPath = path;\n }\n newNavigation.search = search ? search : '';\n }\n newNavigation.hash = hash ? hash : '';\n return newNavigation;\n};\n\nexport const maybeParseUrl = (str: string): URL | null => {\n try {\n return new URL(str);\n } catch (_) {\n return null;\n }\n};\n\nexport const isAbsolutePath = (sandboxPath: string) => sandboxPath.startsWith('/');\n\n/**\n * The origin+repo prefix every same-app URL starts with — used by {@link isInternalHref} to\n * decide whether an absolute href is \"this app\" or somewhere else.\n *\n * `hash` and `search` are cleared, not just `sandboxPath`. They belong to the CURRENT page,\n * and leaving them in put them on the end of a *prefix*: from a page carrying `#sec-8-9`,\n * the prefix became `…/main/#sec-8-9`, no same-app URL could start with it, every absolute\n * in-app link was classified EXTERNAL, and clicking one navigated the sandboxed frame away\n * instead of routing. A page with a fragment quietly broke its own links.\n */\nexport const repositoryPrefixURL = (outerHref: string, navigationState: NavigationState) =>\n constructUrl(outerHref, {\n ...navigationState,\n sandboxPath: '',\n hash: '',\n search: '',\n });\n\nexport const constructOuterUrl = (\n previousOuterHref: string,\n sandboxTarget: string,\n navigationState: NavigationState,\n addFilesPrefix = true,\n): string => {\n if (isAbsolutePath(sandboxTarget)) {\n // Split a trailing `#fragment` off before the target is folded into the\n // sandboxPath, and carry it in `hash` instead — a fragment addresses a section,\n // not a file, so it must not leak into the path (MARKDOWN_SYNTAX_SPEC §13.5). An\n // absolute target with no fragment clears any stale hash from the prior state.\n const [pathPart, hash] = splitHash(sandboxTarget);\n return constructUrl(previousOuterHref, {\n ...navigationState,\n sandboxPath: addFilesPrefix ? joinPaths(FILES_PREFIX, pathPart) : pathPart,\n hash,\n });\n }\n return new URL(sandboxTarget, constructUrl(previousOuterHref, navigationState)).toString();\n};\n\nexport const isInternalHref = (outerHref: string, target: string, navigationState: NavigationState) => {\n const parsedUrl = maybeParseUrl(target);\n if (parsedUrl) {\n return target.startsWith(repositoryPrefixURL(outerHref, navigationState));\n }\n // if target is not a valid URL, then assume it's relative.\n return true;\n};\n\n/**\n * The href to render for a PLATFORM-space path — `/present/github/…`, `/edit/github/…`,\n * `/home`, `/settings/language-model`. These are the HOST's URLs, not this app's: inside the\n * sandboxed frame a root-relative `href` resolves against the SANDBOX origin, which serves no\n * such page, so the path is resolved against the OUTER origin (`outerHref`) instead. An\n * absolute `path` (`https://…`) is returned as-is, which `new URL` already does. With no host\n * (`vite dev`, an empty `outerHref`) or an unresolvable pair, `path` is returned unchanged so\n * the local dev server keeps working. A non-string `path` throws: a caller passing one has a\n * bug worth surfacing, not silently rendering.\n *\n * Render the result through `PlatformLink` (`./platformLink`), which also carries\n * `target=\"_top\"` — an anchor inside the sandboxed frame otherwise navigates the frame.\n */\nexport const platformHref = (outerHref: string, path: string): string => {\n if (typeof path !== 'string') {\n throw new TypeError(`platformHref: path must be a string, got ${typeof path}`);\n }\n if (!outerHref) return path;\n try {\n return new URL(path, outerHref).toString();\n } catch {\n return path;\n }\n};\n\nexport type PathSegment = {\n name: string;\n pattern: string;\n transform?: (pathSegment: string) => string;\n // When true, the leading slash that delimits this segment is optional, so the\n // whole `/segment` group can be absent. Used for the trailing sandboxPath:\n // an outer href of `/mode/provider/namespace/repository/ref` (no trailing\n // slash, no sub-path) must still parse, otherwise the regex matches nothing\n // and every segment comes back empty.\n optionalLeadingSlash?: boolean;\n // Inverse of `transform`, applied when BUILDING a url (`constructUrl`). Without it a\n // decoded ref would be written back raw and split into two segments again.\n encode?: (value: string) => string;\n};\n\n// One URL path segment is \"anything but a slash\". The previous patterns instead\n// ENUMERATED the allowed characters (`[a-zA-Z0-9-_]+`), which silently rejected three\n// real shapes — and rejection here is not an error, it is a whole-match failure that\n// makes `parsePath` return every field EMPTY, so the app's navigation state just goes\n// blank with nothing logged:\n//\n// /…/repo/v1.2.3/… a semver TAG (dot)\n// /…/foo.js/main/… a dotted REPOSITORY name (dot — very common)\n// /…/repo/claude%2Fx/… a `/`-containing REF, percent-encoded as one segment\n//\n// The third is the one that motivated this (a ref may contain `/`; the host now encodes\n// it — see site-main `encodeRef`), but the first two were already broken and are the\n// same mistake. `[^/]+` is both the fix and the accurate description of a segment.\n/** Percent-decode one path segment, tolerating a malformed escape: this is untrusted\n * URL input, and `decodeURIComponent('%zz')` throws. A bad ref must not blank out the\n * whole navigation state. */\nconst decodeSegment = (value: string): string => {\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n};\n\nconst PATH_SEGMENTS: PathSegment[] = [\n { name: 'mode', pattern: '\\\\w+' },\n { name: 'provider', pattern: '[^/]+' },\n { name: 'namespace', pattern: '[^/]+' },\n { name: 'repository', pattern: '[^/]+' },\n // The ref is percent-encoded by the host so a `/` inside it stays ONE segment; decode\n // on the way in and re-encode on the way out (`encode`), so app code always sees the\n // real ref (`claude/x`) and never the wire form.\n { name: 'ref', pattern: '[^/]+', transform: decodeSegment, encode: encodeURIComponent },\n { name: 'sandboxPath', pattern: '.*', transform: (s) => `/${s}`, optionalLeadingSlash: true },\n];\n\nconst OUTER_HREF_REGEXP = new RegExp(\n '^' +\n PATH_SEGMENTS.map(({ name, pattern, optionalLeadingSlash }) =>\n optionalLeadingSlash ? `(?:\\/(?<${name}>${pattern}))?` : `\\/(?<${name}>${pattern})`,\n ).join('') +\n '$',\n);\n\nexport const parsePath = (pathname: string): PathState => {\n const matchResults = pathname.match(OUTER_HREF_REGEXP)?.groups ?? {};\n return PATH_SEGMENTS.reduce((acc: Partial<PathState>, { name, transform }: PathSegment) => {\n let value: string | undefined = undefined;\n if (name in matchResults) {\n value = matchResults[name];\n }\n if (!value) {\n // fall back to default value if var not present in\n value = '';\n }\n if (typeof value === 'string') {\n acc[name] = transform ? transform(value) : value;\n }\n return acc;\n }, {}) as PathState;\n};\n\nexport const parseHref = (href: string): NavigationState => {\n const parsedUrl = new URL(href);\n const pathnameState = parsePath(parsedUrl.pathname);\n return {\n ...pathnameState,\n search: parsedUrl.search.substring(1),\n hash: parsedUrl.hash.substring(1),\n } as NavigationState;\n};\n\nconst stripSlashPrefix = (s: string): string => (s.startsWith('/') ? s.substring(1) : s);\n\nexport const constructUrl = (outerHref: string, navigationState: NavigationState): string => {\n const path = PATH_SEGMENTS.map(({ name, encode }) => {\n const value = stripSlashPrefix(navigationState[name] ?? '');\n // Encode AFTER stripping the delimiter slash: `encode` is per-segment, and the\n // sandboxPath (which has no `encode`) keeps its own internal slashes.\n return encode ? encode(value) : value;\n }).join('/');\n const host = getOuterHostname(outerHref);\n let url = `${host}/${path}`;\n if (navigationState.search) {\n url += '?' + navigationState.search;\n }\n if (navigationState.hash) {\n url += '#' + navigationState.hash;\n }\n return url;\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/urlUtils.ts"],"sourcesContent":["import { APP_ROOT, underAppRoot } from '@immediately-run/platform-constants';\n\nimport { joinPaths } from './pathUtils';\nimport { NavigationState, PathState } from './TinkerableContext';\n\nexport const FILES_PREFIX = '/files';\n\n/**\n * Mount point of the Git repository inside the sandbox filesystem, and the join\n * into it — both now defined ONCE in `@immediately-run/platform-constants` (R3-275)\n * and re-exported here so every import site is unchanged.\n *\n * They were declared here AND in `sandbox/src/fsLayout.ts`, with two different\n * implementations of the join (this one over `joinPaths`, the sandbox's hand-rolled).\n * The two agreed; nothing made them agree — and the metadata key space is derived\n * from this constant on both sides, so a drift would have split that key space\n * silently. The package's tests replicate BOTH implementations, quirks included.\n *\n * The sandbox fs is rooted at `/` (so apps can reach dynamic mounts like\n * `/firestore`), with the repo mounted at `APP_ROOT`. URL subpaths are\n * repo-relative, so the file router resolves them under it.\n */\nexport { APP_ROOT, underAppRoot };\n\n/**\n * The origin every outer URL is rebuilt on — `protocol//host`, **port included**.\n *\n * `url.hostname` drops the port; `url.host` keeps it (and still omits it for the default\n * 80/443, so a production URL is byte-identical either way). Using `hostname` here was\n * invisible on `https://immediately.run` and broke every routed link under local dev on any\n * port but 80: `http://localhost:3100/edit/…` was rebuilt as `http://localhost/edit/…`.\n *\n * Worse than a wrong link, it silently changed link BEHAVIOUR. `repositoryPrefixURL` is\n * built from this, and `isInternalHref` decides by prefix-matching against it — so an\n * absolute in-app URL failed the match, was classified EXTERNAL, and rendered as a plain\n * `<a>`. Clicking it performed a real navigation out of the sandboxed frame instead of\n * routing: the app appeared to \"reload\" on an ordinary internal link.\n */\nexport const getOuterHostname = (outerHref: string) => {\n const url = new URL(outerHref);\n return `${url.protocol}//${url.host}`;\n};\n\nexport const getSearchParams = (search?: string): Record<string, string> =>\n Object.fromEntries([...new URLSearchParams(search ?? window.location.search).entries()]);\n\n/**\n * Split a link target into its path part and its `#fragment` (the `#` dropped),\n * e.g. `\"FOO.mdx#sec-8-9\"` → `[\"FOO.mdx\", \"sec-8-9\"]`, `\"#sec-3\"` → `[\"\", \"sec-3\"]`,\n * `\"FOO.mdx\"` → `[\"FOO.mdx\", \"\"]`. Only the **first** `#` splits — a fragment never\n * contains another `#`. The seam between wiki-links (§13) and heading ids (§15):\n * existence is resolved on the path part, the fragment is threaded to navigation.\n * MARKDOWN_SYNTAX_SPEC §13.5.\n */\nexport const splitHash = (target: string): [string, string] => {\n const i = target.indexOf('#');\n return i === -1 ? [target, ''] : [target.slice(0, i), target.slice(i + 1)];\n};\n\nexport const parseTarget = (target: string, navigation: NavigationState): NavigationState => {\n const newNavigation = { ...navigation };\n let [prehash, hash] = target.split('#');\n if (prehash) {\n let [path, search] = prehash.split('?');\n if (path) {\n newNavigation.sandboxPath = path;\n }\n newNavigation.search = search ? search : '';\n }\n newNavigation.hash = hash ? hash : '';\n return newNavigation;\n};\n\nexport const maybeParseUrl = (str: string): URL | null => {\n try {\n return new URL(str);\n } catch (_) {\n return null;\n }\n};\n\nexport const isAbsolutePath = (sandboxPath: string) => sandboxPath.startsWith('/');\n\n/**\n * The origin+repo prefix every same-app URL starts with — used by {@link isInternalHref} to\n * decide whether an absolute href is \"this app\" or somewhere else.\n *\n * `hash` and `search` are cleared, not just `sandboxPath`. They belong to the CURRENT page,\n * and leaving them in put them on the end of a *prefix*: from a page carrying `#sec-8-9`,\n * the prefix became `…/main/#sec-8-9`, no same-app URL could start with it, every absolute\n * in-app link was classified EXTERNAL, and clicking one navigated the sandboxed frame away\n * instead of routing. A page with a fragment quietly broke its own links.\n */\nexport const repositoryPrefixURL = (outerHref: string, navigationState: NavigationState) =>\n constructUrl(outerHref, {\n ...navigationState,\n sandboxPath: '',\n hash: '',\n search: '',\n });\n\nexport const constructOuterUrl = (\n previousOuterHref: string,\n sandboxTarget: string,\n navigationState: NavigationState,\n addFilesPrefix = true,\n): string => {\n if (isAbsolutePath(sandboxTarget)) {\n // Split a trailing `#fragment` off before the target is folded into the\n // sandboxPath, and carry it in `hash` instead — a fragment addresses a section,\n // not a file, so it must not leak into the path (MARKDOWN_SYNTAX_SPEC §13.5). An\n // absolute target with no fragment clears any stale hash from the prior state.\n const [pathPart, hash] = splitHash(sandboxTarget);\n return constructUrl(previousOuterHref, {\n ...navigationState,\n sandboxPath: addFilesPrefix ? joinPaths(FILES_PREFIX, pathPart) : pathPart,\n hash,\n });\n }\n return new URL(sandboxTarget, constructUrl(previousOuterHref, navigationState)).toString();\n};\n\nexport const isInternalHref = (outerHref: string, target: string, navigationState: NavigationState) => {\n const parsedUrl = maybeParseUrl(target);\n if (parsedUrl) {\n return target.startsWith(repositoryPrefixURL(outerHref, navigationState));\n }\n // if target is not a valid URL, then assume it's relative.\n return true;\n};\n\n/**\n * The href to render for a PLATFORM-space path — `/present/github/…`, `/edit/github/…`,\n * `/home`, `/settings/language-model`. These are the HOST's URLs, not this app's: inside the\n * sandboxed frame a root-relative `href` resolves against the SANDBOX origin, which serves no\n * such page, so the path is resolved against the OUTER origin (`outerHref`) instead. An\n * absolute `path` (`https://…`) is returned as-is, which `new URL` already does. With no host\n * (`vite dev`, an empty `outerHref`) or an unresolvable pair, `path` is returned unchanged so\n * the local dev server keeps working. A non-string `path` throws: a caller passing one has a\n * bug worth surfacing, not silently rendering.\n *\n * Render the result through `PlatformLink` (`./platformLink`), which does the part an href\n * cannot: it asks the HOST to navigate. An anchor alone does NOT reach a platform route from\n * inside the app frame — the sandbox withholds top-navigation deliberately, so `target=\"_top\"`\n * is refused by the browser (R3-568). A consumer that renders its own anchor from this href\n * gets copy-link and open-in-new-tab, but its plain clicks will do nothing unless it asks the\n * host too.\n */\nexport const platformHref = (outerHref: string, path: string): string => {\n if (typeof path !== 'string') {\n throw new TypeError(`platformHref: path must be a string, got ${typeof path}`);\n }\n if (!outerHref) return path;\n try {\n return new URL(path, outerHref).toString();\n } catch {\n return path;\n }\n};\n\nexport type PathSegment = {\n name: string;\n pattern: string;\n transform?: (pathSegment: string) => string;\n // When true, the leading slash that delimits this segment is optional, so the\n // whole `/segment` group can be absent. Used for the trailing sandboxPath:\n // an outer href of `/mode/provider/namespace/repository/ref` (no trailing\n // slash, no sub-path) must still parse, otherwise the regex matches nothing\n // and every segment comes back empty.\n optionalLeadingSlash?: boolean;\n // Inverse of `transform`, applied when BUILDING a url (`constructUrl`). Without it a\n // decoded ref would be written back raw and split into two segments again.\n encode?: (value: string) => string;\n};\n\n// One URL path segment is \"anything but a slash\". The previous patterns instead\n// ENUMERATED the allowed characters (`[a-zA-Z0-9-_]+`), which silently rejected three\n// real shapes — and rejection here is not an error, it is a whole-match failure that\n// makes `parsePath` return every field EMPTY, so the app's navigation state just goes\n// blank with nothing logged:\n//\n// /…/repo/v1.2.3/… a semver TAG (dot)\n// /…/foo.js/main/… a dotted REPOSITORY name (dot — very common)\n// /…/repo/claude%2Fx/… a `/`-containing REF, percent-encoded as one segment\n//\n// The third is the one that motivated this (a ref may contain `/`; the host now encodes\n// it — see site-main `encodeRef`), but the first two were already broken and are the\n// same mistake. `[^/]+` is both the fix and the accurate description of a segment.\n/** Percent-decode one path segment, tolerating a malformed escape: this is untrusted\n * URL input, and `decodeURIComponent('%zz')` throws. A bad ref must not blank out the\n * whole navigation state. */\nconst decodeSegment = (value: string): string => {\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n};\n\nconst PATH_SEGMENTS: PathSegment[] = [\n { name: 'mode', pattern: '\\\\w+' },\n { name: 'provider', pattern: '[^/]+' },\n { name: 'namespace', pattern: '[^/]+' },\n { name: 'repository', pattern: '[^/]+' },\n // The ref is percent-encoded by the host so a `/` inside it stays ONE segment; decode\n // on the way in and re-encode on the way out (`encode`), so app code always sees the\n // real ref (`claude/x`) and never the wire form.\n { name: 'ref', pattern: '[^/]+', transform: decodeSegment, encode: encodeURIComponent },\n { name: 'sandboxPath', pattern: '.*', transform: (s) => `/${s}`, optionalLeadingSlash: true },\n];\n\nconst OUTER_HREF_REGEXP = new RegExp(\n '^' +\n PATH_SEGMENTS.map(({ name, pattern, optionalLeadingSlash }) =>\n optionalLeadingSlash ? `(?:\\/(?<${name}>${pattern}))?` : `\\/(?<${name}>${pattern})`,\n ).join('') +\n '$',\n);\n\nexport const parsePath = (pathname: string): PathState => {\n const matchResults = pathname.match(OUTER_HREF_REGEXP)?.groups ?? {};\n return PATH_SEGMENTS.reduce((acc: Partial<PathState>, { name, transform }: PathSegment) => {\n let value: string | undefined = undefined;\n if (name in matchResults) {\n value = matchResults[name];\n }\n if (!value) {\n // fall back to default value if var not present in\n value = '';\n }\n if (typeof value === 'string') {\n acc[name] = transform ? transform(value) : value;\n }\n return acc;\n }, {}) as PathState;\n};\n\nexport const parseHref = (href: string): NavigationState => {\n const parsedUrl = new URL(href);\n const pathnameState = parsePath(parsedUrl.pathname);\n return {\n ...pathnameState,\n search: parsedUrl.search.substring(1),\n hash: parsedUrl.hash.substring(1),\n } as NavigationState;\n};\n\nconst stripSlashPrefix = (s: string): string => (s.startsWith('/') ? s.substring(1) : s);\n\nexport const constructUrl = (outerHref: string, navigationState: NavigationState): string => {\n const path = PATH_SEGMENTS.map(({ name, encode }) => {\n const value = stripSlashPrefix(navigationState[name] ?? '');\n // Encode AFTER stripping the delimiter slash: `encode` is per-segment, and the\n // sandboxPath (which has no `encode`) keeps its own internal slashes.\n return encode ? encode(value) : value;\n }).join('/');\n const host = getOuterHostname(outerHref);\n let url = `${host}/${path}`;\n if (navigationState.search) {\n url += '?' + navigationState.search;\n }\n if (navigationState.hash) {\n url += '#' + navigationState.hash;\n }\n return url;\n};\n"],"mappings":"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","names":[]}
@@ -57,8 +57,12 @@ declare const isInternalHref: (outerHref: string, target: string, navigationStat
57
57
  * the local dev server keeps working. A non-string `path` throws: a caller passing one has a
58
58
  * bug worth surfacing, not silently rendering.
59
59
  *
60
- * Render the result through `PlatformLink` (`./platformLink`), which also carries
61
- * `target="_top"` — an anchor inside the sandboxed frame otherwise navigates the frame.
60
+ * Render the result through `PlatformLink` (`./platformLink`), which does the part an href
61
+ * cannot: it asks the HOST to navigate. An anchor alone does NOT reach a platform route from
62
+ * inside the app frame — the sandbox withholds top-navigation deliberately, so `target="_top"`
63
+ * is refused by the browser (R3-568). A consumer that renders its own anchor from this href
64
+ * gets copy-link and open-in-new-tab, but its plain clicks will do nothing unless it asks the
65
+ * host too.
62
66
  */
63
67
  declare const platformHref: (outerHref: string, path: string) => string;
64
68
  type PathSegment = {
@@ -57,8 +57,12 @@ declare const isInternalHref: (outerHref: string, target: string, navigationStat
57
57
  * the local dev server keeps working. A non-string `path` throws: a caller passing one has a
58
58
  * bug worth surfacing, not silently rendering.
59
59
  *
60
- * Render the result through `PlatformLink` (`./platformLink`), which also carries
61
- * `target="_top"` — an anchor inside the sandboxed frame otherwise navigates the frame.
60
+ * Render the result through `PlatformLink` (`./platformLink`), which does the part an href
61
+ * cannot: it asks the HOST to navigate. An anchor alone does NOT reach a platform route from
62
+ * inside the app frame — the sandbox withholds top-navigation deliberately, so `target="_top"`
63
+ * is refused by the browser (R3-568). A consumer that renders its own anchor from this href
64
+ * gets copy-link and open-in-new-tab, but its plain clicks will do nothing unless it asks the
65
+ * host too.
62
66
  */
63
67
  declare const platformHref: (outerHref: string, path: string) => string;
64
68
  type PathSegment = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/urlUtils.ts"],"sourcesContent":["import { APP_ROOT, underAppRoot } from '@immediately-run/platform-constants';\n\nimport { joinPaths } from './pathUtils';\nimport { NavigationState, PathState } from './TinkerableContext';\n\nexport const FILES_PREFIX = '/files';\n\n/**\n * Mount point of the Git repository inside the sandbox filesystem, and the join\n * into it — both now defined ONCE in `@immediately-run/platform-constants` (R3-275)\n * and re-exported here so every import site is unchanged.\n *\n * They were declared here AND in `sandbox/src/fsLayout.ts`, with two different\n * implementations of the join (this one over `joinPaths`, the sandbox's hand-rolled).\n * The two agreed; nothing made them agree — and the metadata key space is derived\n * from this constant on both sides, so a drift would have split that key space\n * silently. The package's tests replicate BOTH implementations, quirks included.\n *\n * The sandbox fs is rooted at `/` (so apps can reach dynamic mounts like\n * `/firestore`), with the repo mounted at `APP_ROOT`. URL subpaths are\n * repo-relative, so the file router resolves them under it.\n */\nexport { APP_ROOT, underAppRoot };\n\n/**\n * The origin every outer URL is rebuilt on — `protocol//host`, **port included**.\n *\n * `url.hostname` drops the port; `url.host` keeps it (and still omits it for the default\n * 80/443, so a production URL is byte-identical either way). Using `hostname` here was\n * invisible on `https://immediately.run` and broke every routed link under local dev on any\n * port but 80: `http://localhost:3100/edit/…` was rebuilt as `http://localhost/edit/…`.\n *\n * Worse than a wrong link, it silently changed link BEHAVIOUR. `repositoryPrefixURL` is\n * built from this, and `isInternalHref` decides by prefix-matching against it — so an\n * absolute in-app URL failed the match, was classified EXTERNAL, and rendered as a plain\n * `<a>`. Clicking it performed a real navigation out of the sandboxed frame instead of\n * routing: the app appeared to \"reload\" on an ordinary internal link.\n */\nexport const getOuterHostname = (outerHref: string) => {\n const url = new URL(outerHref);\n return `${url.protocol}//${url.host}`;\n};\n\nexport const getSearchParams = (search?: string): Record<string, string> =>\n Object.fromEntries([...new URLSearchParams(search ?? window.location.search).entries()]);\n\n/**\n * Split a link target into its path part and its `#fragment` (the `#` dropped),\n * e.g. `\"FOO.mdx#sec-8-9\"` → `[\"FOO.mdx\", \"sec-8-9\"]`, `\"#sec-3\"` → `[\"\", \"sec-3\"]`,\n * `\"FOO.mdx\"` → `[\"FOO.mdx\", \"\"]`. Only the **first** `#` splits — a fragment never\n * contains another `#`. The seam between wiki-links (§13) and heading ids (§15):\n * existence is resolved on the path part, the fragment is threaded to navigation.\n * MARKDOWN_SYNTAX_SPEC §13.5.\n */\nexport const splitHash = (target: string): [string, string] => {\n const i = target.indexOf('#');\n return i === -1 ? [target, ''] : [target.slice(0, i), target.slice(i + 1)];\n};\n\nexport const parseTarget = (target: string, navigation: NavigationState): NavigationState => {\n const newNavigation = { ...navigation };\n let [prehash, hash] = target.split('#');\n if (prehash) {\n let [path, search] = prehash.split('?');\n if (path) {\n newNavigation.sandboxPath = path;\n }\n newNavigation.search = search ? search : '';\n }\n newNavigation.hash = hash ? hash : '';\n return newNavigation;\n};\n\nexport const maybeParseUrl = (str: string): URL | null => {\n try {\n return new URL(str);\n } catch (_) {\n return null;\n }\n};\n\nexport const isAbsolutePath = (sandboxPath: string) => sandboxPath.startsWith('/');\n\n/**\n * The origin+repo prefix every same-app URL starts with — used by {@link isInternalHref} to\n * decide whether an absolute href is \"this app\" or somewhere else.\n *\n * `hash` and `search` are cleared, not just `sandboxPath`. They belong to the CURRENT page,\n * and leaving them in put them on the end of a *prefix*: from a page carrying `#sec-8-9`,\n * the prefix became `…/main/#sec-8-9`, no same-app URL could start with it, every absolute\n * in-app link was classified EXTERNAL, and clicking one navigated the sandboxed frame away\n * instead of routing. A page with a fragment quietly broke its own links.\n */\nexport const repositoryPrefixURL = (outerHref: string, navigationState: NavigationState) =>\n constructUrl(outerHref, {\n ...navigationState,\n sandboxPath: '',\n hash: '',\n search: '',\n });\n\nexport const constructOuterUrl = (\n previousOuterHref: string,\n sandboxTarget: string,\n navigationState: NavigationState,\n addFilesPrefix = true,\n): string => {\n if (isAbsolutePath(sandboxTarget)) {\n // Split a trailing `#fragment` off before the target is folded into the\n // sandboxPath, and carry it in `hash` instead — a fragment addresses a section,\n // not a file, so it must not leak into the path (MARKDOWN_SYNTAX_SPEC §13.5). An\n // absolute target with no fragment clears any stale hash from the prior state.\n const [pathPart, hash] = splitHash(sandboxTarget);\n return constructUrl(previousOuterHref, {\n ...navigationState,\n sandboxPath: addFilesPrefix ? joinPaths(FILES_PREFIX, pathPart) : pathPart,\n hash,\n });\n }\n return new URL(sandboxTarget, constructUrl(previousOuterHref, navigationState)).toString();\n};\n\nexport const isInternalHref = (outerHref: string, target: string, navigationState: NavigationState) => {\n const parsedUrl = maybeParseUrl(target);\n if (parsedUrl) {\n return target.startsWith(repositoryPrefixURL(outerHref, navigationState));\n }\n // if target is not a valid URL, then assume it's relative.\n return true;\n};\n\n/**\n * The href to render for a PLATFORM-space path — `/present/github/…`, `/edit/github/…`,\n * `/home`, `/settings/language-model`. These are the HOST's URLs, not this app's: inside the\n * sandboxed frame a root-relative `href` resolves against the SANDBOX origin, which serves no\n * such page, so the path is resolved against the OUTER origin (`outerHref`) instead. An\n * absolute `path` (`https://…`) is returned as-is, which `new URL` already does. With no host\n * (`vite dev`, an empty `outerHref`) or an unresolvable pair, `path` is returned unchanged so\n * the local dev server keeps working. A non-string `path` throws: a caller passing one has a\n * bug worth surfacing, not silently rendering.\n *\n * Render the result through `PlatformLink` (`./platformLink`), which also carries\n * `target=\"_top\"` — an anchor inside the sandboxed frame otherwise navigates the frame.\n */\nexport const platformHref = (outerHref: string, path: string): string => {\n if (typeof path !== 'string') {\n throw new TypeError(`platformHref: path must be a string, got ${typeof path}`);\n }\n if (!outerHref) return path;\n try {\n return new URL(path, outerHref).toString();\n } catch {\n return path;\n }\n};\n\nexport type PathSegment = {\n name: string;\n pattern: string;\n transform?: (pathSegment: string) => string;\n // When true, the leading slash that delimits this segment is optional, so the\n // whole `/segment` group can be absent. Used for the trailing sandboxPath:\n // an outer href of `/mode/provider/namespace/repository/ref` (no trailing\n // slash, no sub-path) must still parse, otherwise the regex matches nothing\n // and every segment comes back empty.\n optionalLeadingSlash?: boolean;\n // Inverse of `transform`, applied when BUILDING a url (`constructUrl`). Without it a\n // decoded ref would be written back raw and split into two segments again.\n encode?: (value: string) => string;\n};\n\n// One URL path segment is \"anything but a slash\". The previous patterns instead\n// ENUMERATED the allowed characters (`[a-zA-Z0-9-_]+`), which silently rejected three\n// real shapes — and rejection here is not an error, it is a whole-match failure that\n// makes `parsePath` return every field EMPTY, so the app's navigation state just goes\n// blank with nothing logged:\n//\n// /…/repo/v1.2.3/… a semver TAG (dot)\n// /…/foo.js/main/… a dotted REPOSITORY name (dot — very common)\n// /…/repo/claude%2Fx/… a `/`-containing REF, percent-encoded as one segment\n//\n// The third is the one that motivated this (a ref may contain `/`; the host now encodes\n// it — see site-main `encodeRef`), but the first two were already broken and are the\n// same mistake. `[^/]+` is both the fix and the accurate description of a segment.\n/** Percent-decode one path segment, tolerating a malformed escape: this is untrusted\n * URL input, and `decodeURIComponent('%zz')` throws. A bad ref must not blank out the\n * whole navigation state. */\nconst decodeSegment = (value: string): string => {\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n};\n\nconst PATH_SEGMENTS: PathSegment[] = [\n { name: 'mode', pattern: '\\\\w+' },\n { name: 'provider', pattern: '[^/]+' },\n { name: 'namespace', pattern: '[^/]+' },\n { name: 'repository', pattern: '[^/]+' },\n // The ref is percent-encoded by the host so a `/` inside it stays ONE segment; decode\n // on the way in and re-encode on the way out (`encode`), so app code always sees the\n // real ref (`claude/x`) and never the wire form.\n { name: 'ref', pattern: '[^/]+', transform: decodeSegment, encode: encodeURIComponent },\n { name: 'sandboxPath', pattern: '.*', transform: (s) => `/${s}`, optionalLeadingSlash: true },\n];\n\nconst OUTER_HREF_REGEXP = new RegExp(\n '^' +\n PATH_SEGMENTS.map(({ name, pattern, optionalLeadingSlash }) =>\n optionalLeadingSlash ? `(?:\\/(?<${name}>${pattern}))?` : `\\/(?<${name}>${pattern})`,\n ).join('') +\n '$',\n);\n\nexport const parsePath = (pathname: string): PathState => {\n const matchResults = pathname.match(OUTER_HREF_REGEXP)?.groups ?? {};\n return PATH_SEGMENTS.reduce((acc: Partial<PathState>, { name, transform }: PathSegment) => {\n let value: string | undefined = undefined;\n if (name in matchResults) {\n value = matchResults[name];\n }\n if (!value) {\n // fall back to default value if var not present in\n value = '';\n }\n if (typeof value === 'string') {\n acc[name] = transform ? transform(value) : value;\n }\n return acc;\n }, {}) as PathState;\n};\n\nexport const parseHref = (href: string): NavigationState => {\n const parsedUrl = new URL(href);\n const pathnameState = parsePath(parsedUrl.pathname);\n return {\n ...pathnameState,\n search: parsedUrl.search.substring(1),\n hash: parsedUrl.hash.substring(1),\n } as NavigationState;\n};\n\nconst stripSlashPrefix = (s: string): string => (s.startsWith('/') ? s.substring(1) : s);\n\nexport const constructUrl = (outerHref: string, navigationState: NavigationState): string => {\n const path = PATH_SEGMENTS.map(({ name, encode }) => {\n const value = stripSlashPrefix(navigationState[name] ?? '');\n // Encode AFTER stripping the delimiter slash: `encode` is per-segment, and the\n // sandboxPath (which has no `encode`) keeps its own internal slashes.\n return encode ? encode(value) : value;\n }).join('/');\n const host = getOuterHostname(outerHref);\n let url = `${host}/${path}`;\n if (navigationState.search) {\n url += '?' + navigationState.search;\n }\n if (navigationState.hash) {\n url += '#' + navigationState.hash;\n }\n return url;\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/urlUtils.ts"],"sourcesContent":["import { APP_ROOT, underAppRoot } from '@immediately-run/platform-constants';\n\nimport { joinPaths } from './pathUtils';\nimport { NavigationState, PathState } from './TinkerableContext';\n\nexport const FILES_PREFIX = '/files';\n\n/**\n * Mount point of the Git repository inside the sandbox filesystem, and the join\n * into it — both now defined ONCE in `@immediately-run/platform-constants` (R3-275)\n * and re-exported here so every import site is unchanged.\n *\n * They were declared here AND in `sandbox/src/fsLayout.ts`, with two different\n * implementations of the join (this one over `joinPaths`, the sandbox's hand-rolled).\n * The two agreed; nothing made them agree — and the metadata key space is derived\n * from this constant on both sides, so a drift would have split that key space\n * silently. The package's tests replicate BOTH implementations, quirks included.\n *\n * The sandbox fs is rooted at `/` (so apps can reach dynamic mounts like\n * `/firestore`), with the repo mounted at `APP_ROOT`. URL subpaths are\n * repo-relative, so the file router resolves them under it.\n */\nexport { APP_ROOT, underAppRoot };\n\n/**\n * The origin every outer URL is rebuilt on — `protocol//host`, **port included**.\n *\n * `url.hostname` drops the port; `url.host` keeps it (and still omits it for the default\n * 80/443, so a production URL is byte-identical either way). Using `hostname` here was\n * invisible on `https://immediately.run` and broke every routed link under local dev on any\n * port but 80: `http://localhost:3100/edit/…` was rebuilt as `http://localhost/edit/…`.\n *\n * Worse than a wrong link, it silently changed link BEHAVIOUR. `repositoryPrefixURL` is\n * built from this, and `isInternalHref` decides by prefix-matching against it — so an\n * absolute in-app URL failed the match, was classified EXTERNAL, and rendered as a plain\n * `<a>`. Clicking it performed a real navigation out of the sandboxed frame instead of\n * routing: the app appeared to \"reload\" on an ordinary internal link.\n */\nexport const getOuterHostname = (outerHref: string) => {\n const url = new URL(outerHref);\n return `${url.protocol}//${url.host}`;\n};\n\nexport const getSearchParams = (search?: string): Record<string, string> =>\n Object.fromEntries([...new URLSearchParams(search ?? window.location.search).entries()]);\n\n/**\n * Split a link target into its path part and its `#fragment` (the `#` dropped),\n * e.g. `\"FOO.mdx#sec-8-9\"` → `[\"FOO.mdx\", \"sec-8-9\"]`, `\"#sec-3\"` → `[\"\", \"sec-3\"]`,\n * `\"FOO.mdx\"` → `[\"FOO.mdx\", \"\"]`. Only the **first** `#` splits — a fragment never\n * contains another `#`. The seam between wiki-links (§13) and heading ids (§15):\n * existence is resolved on the path part, the fragment is threaded to navigation.\n * MARKDOWN_SYNTAX_SPEC §13.5.\n */\nexport const splitHash = (target: string): [string, string] => {\n const i = target.indexOf('#');\n return i === -1 ? [target, ''] : [target.slice(0, i), target.slice(i + 1)];\n};\n\nexport const parseTarget = (target: string, navigation: NavigationState): NavigationState => {\n const newNavigation = { ...navigation };\n let [prehash, hash] = target.split('#');\n if (prehash) {\n let [path, search] = prehash.split('?');\n if (path) {\n newNavigation.sandboxPath = path;\n }\n newNavigation.search = search ? search : '';\n }\n newNavigation.hash = hash ? hash : '';\n return newNavigation;\n};\n\nexport const maybeParseUrl = (str: string): URL | null => {\n try {\n return new URL(str);\n } catch (_) {\n return null;\n }\n};\n\nexport const isAbsolutePath = (sandboxPath: string) => sandboxPath.startsWith('/');\n\n/**\n * The origin+repo prefix every same-app URL starts with — used by {@link isInternalHref} to\n * decide whether an absolute href is \"this app\" or somewhere else.\n *\n * `hash` and `search` are cleared, not just `sandboxPath`. They belong to the CURRENT page,\n * and leaving them in put them on the end of a *prefix*: from a page carrying `#sec-8-9`,\n * the prefix became `…/main/#sec-8-9`, no same-app URL could start with it, every absolute\n * in-app link was classified EXTERNAL, and clicking one navigated the sandboxed frame away\n * instead of routing. A page with a fragment quietly broke its own links.\n */\nexport const repositoryPrefixURL = (outerHref: string, navigationState: NavigationState) =>\n constructUrl(outerHref, {\n ...navigationState,\n sandboxPath: '',\n hash: '',\n search: '',\n });\n\nexport const constructOuterUrl = (\n previousOuterHref: string,\n sandboxTarget: string,\n navigationState: NavigationState,\n addFilesPrefix = true,\n): string => {\n if (isAbsolutePath(sandboxTarget)) {\n // Split a trailing `#fragment` off before the target is folded into the\n // sandboxPath, and carry it in `hash` instead — a fragment addresses a section,\n // not a file, so it must not leak into the path (MARKDOWN_SYNTAX_SPEC §13.5). An\n // absolute target with no fragment clears any stale hash from the prior state.\n const [pathPart, hash] = splitHash(sandboxTarget);\n return constructUrl(previousOuterHref, {\n ...navigationState,\n sandboxPath: addFilesPrefix ? joinPaths(FILES_PREFIX, pathPart) : pathPart,\n hash,\n });\n }\n return new URL(sandboxTarget, constructUrl(previousOuterHref, navigationState)).toString();\n};\n\nexport const isInternalHref = (outerHref: string, target: string, navigationState: NavigationState) => {\n const parsedUrl = maybeParseUrl(target);\n if (parsedUrl) {\n return target.startsWith(repositoryPrefixURL(outerHref, navigationState));\n }\n // if target is not a valid URL, then assume it's relative.\n return true;\n};\n\n/**\n * The href to render for a PLATFORM-space path — `/present/github/…`, `/edit/github/…`,\n * `/home`, `/settings/language-model`. These are the HOST's URLs, not this app's: inside the\n * sandboxed frame a root-relative `href` resolves against the SANDBOX origin, which serves no\n * such page, so the path is resolved against the OUTER origin (`outerHref`) instead. An\n * absolute `path` (`https://…`) is returned as-is, which `new URL` already does. With no host\n * (`vite dev`, an empty `outerHref`) or an unresolvable pair, `path` is returned unchanged so\n * the local dev server keeps working. A non-string `path` throws: a caller passing one has a\n * bug worth surfacing, not silently rendering.\n *\n * Render the result through `PlatformLink` (`./platformLink`), which does the part an href\n * cannot: it asks the HOST to navigate. An anchor alone does NOT reach a platform route from\n * inside the app frame — the sandbox withholds top-navigation deliberately, so `target=\"_top\"`\n * is refused by the browser (R3-568). A consumer that renders its own anchor from this href\n * gets copy-link and open-in-new-tab, but its plain clicks will do nothing unless it asks the\n * host too.\n */\nexport const platformHref = (outerHref: string, path: string): string => {\n if (typeof path !== 'string') {\n throw new TypeError(`platformHref: path must be a string, got ${typeof path}`);\n }\n if (!outerHref) return path;\n try {\n return new URL(path, outerHref).toString();\n } catch {\n return path;\n }\n};\n\nexport type PathSegment = {\n name: string;\n pattern: string;\n transform?: (pathSegment: string) => string;\n // When true, the leading slash that delimits this segment is optional, so the\n // whole `/segment` group can be absent. Used for the trailing sandboxPath:\n // an outer href of `/mode/provider/namespace/repository/ref` (no trailing\n // slash, no sub-path) must still parse, otherwise the regex matches nothing\n // and every segment comes back empty.\n optionalLeadingSlash?: boolean;\n // Inverse of `transform`, applied when BUILDING a url (`constructUrl`). Without it a\n // decoded ref would be written back raw and split into two segments again.\n encode?: (value: string) => string;\n};\n\n// One URL path segment is \"anything but a slash\". The previous patterns instead\n// ENUMERATED the allowed characters (`[a-zA-Z0-9-_]+`), which silently rejected three\n// real shapes — and rejection here is not an error, it is a whole-match failure that\n// makes `parsePath` return every field EMPTY, so the app's navigation state just goes\n// blank with nothing logged:\n//\n// /…/repo/v1.2.3/… a semver TAG (dot)\n// /…/foo.js/main/… a dotted REPOSITORY name (dot — very common)\n// /…/repo/claude%2Fx/… a `/`-containing REF, percent-encoded as one segment\n//\n// The third is the one that motivated this (a ref may contain `/`; the host now encodes\n// it — see site-main `encodeRef`), but the first two were already broken and are the\n// same mistake. `[^/]+` is both the fix and the accurate description of a segment.\n/** Percent-decode one path segment, tolerating a malformed escape: this is untrusted\n * URL input, and `decodeURIComponent('%zz')` throws. A bad ref must not blank out the\n * whole navigation state. */\nconst decodeSegment = (value: string): string => {\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n};\n\nconst PATH_SEGMENTS: PathSegment[] = [\n { name: 'mode', pattern: '\\\\w+' },\n { name: 'provider', pattern: '[^/]+' },\n { name: 'namespace', pattern: '[^/]+' },\n { name: 'repository', pattern: '[^/]+' },\n // The ref is percent-encoded by the host so a `/` inside it stays ONE segment; decode\n // on the way in and re-encode on the way out (`encode`), so app code always sees the\n // real ref (`claude/x`) and never the wire form.\n { name: 'ref', pattern: '[^/]+', transform: decodeSegment, encode: encodeURIComponent },\n { name: 'sandboxPath', pattern: '.*', transform: (s) => `/${s}`, optionalLeadingSlash: true },\n];\n\nconst OUTER_HREF_REGEXP = new RegExp(\n '^' +\n PATH_SEGMENTS.map(({ name, pattern, optionalLeadingSlash }) =>\n optionalLeadingSlash ? `(?:\\/(?<${name}>${pattern}))?` : `\\/(?<${name}>${pattern})`,\n ).join('') +\n '$',\n);\n\nexport const parsePath = (pathname: string): PathState => {\n const matchResults = pathname.match(OUTER_HREF_REGEXP)?.groups ?? {};\n return PATH_SEGMENTS.reduce((acc: Partial<PathState>, { name, transform }: PathSegment) => {\n let value: string | undefined = undefined;\n if (name in matchResults) {\n value = matchResults[name];\n }\n if (!value) {\n // fall back to default value if var not present in\n value = '';\n }\n if (typeof value === 'string') {\n acc[name] = transform ? transform(value) : value;\n }\n return acc;\n }, {}) as PathState;\n};\n\nexport const parseHref = (href: string): NavigationState => {\n const parsedUrl = new URL(href);\n const pathnameState = parsePath(parsedUrl.pathname);\n return {\n ...pathnameState,\n search: parsedUrl.search.substring(1),\n hash: parsedUrl.hash.substring(1),\n } as NavigationState;\n};\n\nconst stripSlashPrefix = (s: string): string => (s.startsWith('/') ? s.substring(1) : s);\n\nexport const constructUrl = (outerHref: string, navigationState: NavigationState): string => {\n const path = PATH_SEGMENTS.map(({ name, encode }) => {\n const value = stripSlashPrefix(navigationState[name] ?? '');\n // Encode AFTER stripping the delimiter slash: `encode` is per-segment, and the\n // sandboxPath (which has no `encode`) keeps its own internal slashes.\n return encode ? encode(value) : value;\n }).join('/');\n const host = getOuterHostname(outerHref);\n let url = `${host}/${path}`;\n if (navigationState.search) {\n url += '?' + navigationState.search;\n }\n if (navigationState.hash) {\n url += '#' + navigationState.hash;\n }\n return url;\n};\n"],"mappings":"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","names":[]}
package/dist/version.cjs CHANGED
@@ -21,7 +21,7 @@ __export(version_exports, {
21
21
  SDK_VERSION: () => SDK_VERSION
22
22
  });
23
23
  module.exports = __toCommonJS(version_exports);
24
- const SDK_VERSION = "0.61.0";
24
+ const SDK_VERSION = "0.64.1";
25
25
  // Annotate the CommonJS export names for ESM import in node:
26
26
  0 && (module.exports = {
27
27
  SDK_VERSION
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.61.0';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIO,MAAM,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.64.1';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIO,MAAM,cAAc;","names":[]}
@@ -1,4 +1,4 @@
1
1
  /** This SDK's package version, baked from package.json at build (SP2-6). */
2
- declare const SDK_VERSION = "0.61.0";
2
+ declare const SDK_VERSION = "0.64.1";
3
3
 
4
4
  export { SDK_VERSION };
package/dist/version.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  /** This SDK's package version, baked from package.json at build (SP2-6). */
2
- declare const SDK_VERSION = "0.61.0";
2
+ declare const SDK_VERSION = "0.64.1";
3
3
 
4
4
  export { SDK_VERSION };
package/dist/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import "./chunk-VHAA22YE.js";
2
- const SDK_VERSION = "0.61.0";
2
+ const SDK_VERSION = "0.64.1";
3
3
  export {
4
4
  SDK_VERSION
5
5
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.61.0';\n"],"mappings":";AAIO,MAAM,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.64.1';\n"],"mappings":";AAIO,MAAM,cAAc;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@immediately-run/sdk",
3
- "version": "0.61.0",
3
+ "version": "0.64.1",
4
4
  "description": "Runtime SDK for code executing inside an immediately.run sandbox.",
5
5
  "license": "MIT",
6
6
  "repository": "github:immediately-run/immediately-run-sdk",
@@ -67,12 +67,12 @@
67
67
  "@immediately-run/mdx-plugins": "0.5.0",
68
68
  "@immediately-run/platform-constants": "0.2.0",
69
69
  "@immediately-run/safe-content": "0.1.0",
70
- "@immediately-run/sandbox-protocol": "0.9.0",
70
+ "@immediately-run/sandbox-protocol": "0.10.1",
71
71
  "react-error-boundary": "^6.0.0"
72
72
  },
73
73
  "devDependencies": {
74
74
  "@immediately-run/prettier-config": "0.1.0",
75
- "@immediately-run/verify-checks": "0.1.1",
75
+ "@immediately-run/verify-checks": "0.2.0",
76
76
  "@testing-library/dom": "^10.4.1",
77
77
  "@testing-library/react": "^16.3.2",
78
78
  "@types/jest": "^29.5.0",