@ai-matrx/summon 0.1.3 → 0.1.5

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 (49) hide show
  1. package/CHANGELOG.md +4 -1
  2. package/README.md +22 -2
  3. package/dist/dock.cjs +552 -0
  4. package/dist/dock.cjs.map +1 -0
  5. package/dist/dock.d.cts +224 -0
  6. package/dist/dock.d.ts +224 -0
  7. package/dist/dock.js +537 -0
  8. package/dist/dock.js.map +1 -0
  9. package/dist/index.cjs +4 -1
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +6 -0
  12. package/dist/index.d.ts +6 -0
  13. package/dist/index.js +4 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/persistence-data.cjs +9 -0
  16. package/dist/persistence-data.cjs.map +1 -1
  17. package/dist/persistence-data.d.cts +2 -1
  18. package/dist/persistence-data.d.ts +2 -1
  19. package/dist/persistence-data.js +9 -0
  20. package/dist/persistence-data.js.map +1 -1
  21. package/dist/persistence.cjs +6 -4
  22. package/dist/persistence.cjs.map +1 -1
  23. package/dist/persistence.js +6 -4
  24. package/dist/persistence.js.map +1 -1
  25. package/dist/react.cjs +19 -0
  26. package/dist/react.cjs.map +1 -1
  27. package/dist/react.d.cts +8 -1
  28. package/dist/react.d.ts +8 -1
  29. package/dist/react.js +19 -0
  30. package/dist/react.js.map +1 -1
  31. package/dist/url.cjs +32 -50
  32. package/dist/url.cjs.map +1 -1
  33. package/dist/url.d.cts +2 -8
  34. package/dist/url.d.ts +2 -8
  35. package/dist/url.js +8 -24
  36. package/dist/url.js.map +1 -1
  37. package/dist/window-host.cjs +43 -1
  38. package/dist/window-host.cjs.map +1 -1
  39. package/dist/window-host.d.cts +45 -1
  40. package/dist/window-host.d.ts +45 -1
  41. package/dist/window-host.js +44 -2
  42. package/dist/window-host.js.map +1 -1
  43. package/dist/window.cjs +127 -149
  44. package/dist/window.cjs.map +1 -1
  45. package/dist/window.d.cts +2 -46
  46. package/dist/window.d.ts +2 -46
  47. package/dist/window.js +14 -34
  48. package/dist/window.js.map +1 -1
  49. package/package.json +14 -4
package/dist/url.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/url.ts","../src/url/useOverlayLink.ts","../src/url/grammar.ts","../src/url/OverlayLinkManager.tsx","../src/url/door.ts"],"sourcesContent":["// @ai-matrx/summon/url — address-bar links: the `?panels=` grammar, the link manager, the\n// `useOverlayLink` publisher and the one door (`openFromLink`) every link source goes through.\nexport { useOverlayLink } from \"./url/useOverlayLink\";\nexport {\n parseLinkToken,\n parseLinkParam,\n formatLinkToken,\n encodeLinkId,\n encodeLinkArg,\n decodeLinkPart,\n readPanelsParam,\n withPanelsParam,\n type LinkToken,\n type LinkEntry,\n} from \"./url/grammar\";\nexport {\n OverlayLinkManager,\n serializeLinkEntries,\n mergeManagedLinkParam,\n withUnresolvedTokens,\n unopenedWindowNotice,\n type OverlayLinkManagerProps,\n} from \"./url/OverlayLinkManager\";\nexport {\n openFromLink,\n openPanelRequest,\n hydrateLinkToken,\n canonicalLinkKey,\n canonicalizeToken,\n openRegisteredOverlay,\n type OpenFromLinkResult,\n type OpenFromLinkOptions,\n type OpenPanelRequest,\n type OpenPanelResult,\n type TokenOutcome,\n type LinkDispatch,\n} from \"./url/door\";\n","import { useEffect, useMemo } from \"react\";\nimport { registerSyncEntry, unregisterSyncEntry } from \"@ai-matrx/summon\";\nimport { useSummonDispatch } from \"@ai-matrx/summon/react\";\n\n/**\n * Publish an open instance's address into `?panels=` while it is mounted. `linkKey` undefined →\n * nothing is published (a window with no link); so is an undefined or empty `id`. The token is `linkKey:id[:args]`; args are\n * compared by value, so a new object with equal contents never re-registers.\n */\nexport function useOverlayLink(linkKey: string | undefined, id: string | undefined, args?: Readonly<Record<string, string>>): void {\n const dispatch = useSummonDispatch();\n const argsKey = args\n ? JSON.stringify(\n Object.keys(args)\n .sort()\n .map((k) => [k, args[k]]),\n )\n : \"\";\n // Rebuilt only when the VALUES change (argsKey), never on a fresh object with equal contents.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const stableArgs = useMemo(() => (args ? { ...args } : undefined), [argsKey]);\n\n useEffect(() => {\n if (!linkKey || !id) return undefined;\n dispatch(registerSyncEntry(stableArgs ? { typeKey: linkKey, instanceId: id, args: stableArgs } : { typeKey: linkKey, instanceId: id }));\n return () => {\n dispatch(unregisterSyncEntry({ typeKey: linkKey, instanceId: id }));\n };\n }, [dispatch, linkKey, id, stableArgs]);\n}\n","/**\n * The `?panels=` link grammar.\n *\n * panels = token (\",\" token)*\n * token = key [\":\" id [\":\" args]]\n * args = k \"-\" v (\"_\" k \"-\" v)*\n *\n * GRAMMAR v2 (this file): on WRITE, ids are percent-encoded for `% , :` and arg keys and values\n * for `% , : _ -`, so any value — a uuid, a date, a path, a list — survives the round trip.\n * Keys and ids keep `-` and `_` unencoded: they are not separators inside an id, and leaving them means a\n * uuid id writes byte-identically to v1. On READ, v1 tokens (unencoded) parse unchanged: an arg\n * pair splits on its FIRST hyphen, and decoding only touches well-formed `%XX` escapes.\n *\n * Framework-free; this is the one grammar every door (address bar, notifications, extension)\n * shares.\n */\n\nexport interface LinkToken {\n /** The token exactly as it appeared (the verbatim form written back while unresolved). */\n readonly raw: string;\n readonly key: string;\n /** `undefined` for a bare-key token (`keyword_research`). */\n readonly id: string | undefined;\n /** `undefined` when the token carries no args segment. */\n readonly args: Readonly<Record<string, string>> | undefined;\n}\n\nexport interface LinkEntry {\n readonly key: string;\n readonly id: string;\n readonly args?: Readonly<Record<string, string>> | undefined;\n}\n\nconst hex = (ch: string): string => `%${ch.charCodeAt(0).toString(16).toUpperCase().padStart(2, \"0\")}`;\n\n/** Encode a token id (`% , :`). */\nexport function encodeLinkId(id: string): string {\n return id.replace(/[%,:]/g, hex);\n}\n\n/** Encode an arg key or value (`% , : _ -`). */\nexport function encodeLinkArg(value: string): string {\n return value.replace(/[%,:_-]/g, hex);\n}\n\n/** Decode well-formed `%XX` escapes; anything else (a v1 literal `%`) is kept as written. */\nexport function decodeLinkPart(value: string): string {\n if (!value.includes(\"%\")) return value;\n return value.replace(/(?:%[0-9A-Fa-f]{2})+/g, (run) => {\n try {\n return decodeURIComponent(run);\n } catch {\n return run;\n }\n });\n}\n\n/** Parse one token (v1 or v2). Returns `null` for an empty token. */\nexport function parseLinkToken(raw: string): LinkToken | null {\n if (!raw) return null;\n const first = raw.indexOf(\":\");\n const key = first === -1 ? raw : raw.slice(0, first);\n if (!key) return null;\n if (first === -1) return { raw, key, id: undefined, args: undefined };\n const rest = raw.slice(first + 1);\n const second = rest.indexOf(\":\");\n const idPart = second === -1 ? rest : rest.slice(0, second);\n const argsPart = second === -1 ? undefined : rest.slice(second + 1);\n let args: Record<string, string> | undefined;\n if (argsPart) {\n args = {};\n for (const pair of argsPart.split(\"_\")) {\n // Split on the FIRST hyphen only: a v1 value may hold hyphens (uuid, date).\n const at = pair.indexOf(\"-\");\n if (at <= 0 || at === pair.length - 1) continue;\n args[decodeLinkPart(pair.slice(0, at))] = decodeLinkPart(pair.slice(at + 1));\n }\n }\n return { raw, key: decodeLinkPart(key), id: decodeLinkPart(idPart), args };\n}\n\n/** Parse a whole `panels` value. Empty segments are skipped. */\nexport function parseLinkParam(param: string | null | undefined): LinkToken[] {\n if (!param) return [];\n const out: LinkToken[] = [];\n for (const raw of param.split(\",\")) {\n const token = parseLinkToken(raw);\n if (token) out.push(token);\n }\n return out;\n}\n\n/** Write one entry as a v2 token. Args are written in key order so equal entries write equal tokens. */\nexport function formatLinkToken(entry: LinkEntry): string {\n let token = `${encodeLinkId(entry.key)}:${encodeLinkId(entry.id)}`;\n const args = entry.args;\n if (args) {\n const keys = Object.keys(args).sort();\n if (keys.length > 0) {\n token += `:${keys.map((k) => `${encodeLinkArg(k)}-${encodeLinkArg(args[k] ?? \"\")}`).join(\"_\")}`;\n }\n }\n return token;\n}\n\n/** The `panels` value from a search string or a full URL (`null` when absent). */\nexport function readPanelsParam(searchOrUrl: string): string | null {\n const q = searchOrUrl.indexOf(\"?\");\n const hash = searchOrUrl.indexOf(\"#\");\n let search: string;\n if (q === -1) {\n // A bare query string without \"?\" (`panels=…`) or a URL with no query.\n search = searchOrUrl.includes(\"=\") && !searchOrUrl.includes(\"/\") ? searchOrUrl : \"\";\n } else {\n search = searchOrUrl.slice(q + 1, hash > q ? hash : undefined);\n }\n return new URLSearchParams(search).get(\"panels\");\n}\n\n/** `search` with `panels` set to `param` (removed when empty). Returns \"\" or \"?…\". */\nexport function withPanelsParam(search: string, param: string): string {\n const params = new URLSearchParams(search.startsWith(\"?\") ? search.slice(1) : search);\n if (param) params.set(\"panels\", param);\n else params.delete(\"panels\");\n const qs = params.toString();\n return qs ? `?${qs}` : \"\";\n}\n","/**\n * OverlayLinkManager — the address bar's `?panels=` channel (successor of matrx-frontend's\n * UrlPanelManager). Mount ONCE inside <SummonProvider>. Driven entirely by `ports.router`.\n *\n * 1. On mount: read `?panels=`, hydrate each token through the one door (`hydrateLinkToken`).\n * 2. After that: write back the union of registered link entries (`useOverlayLink`) plus every\n * pasted token that has not been settled yet, via `router.replaceSearch` — never navigation.\n *\n * THE ADDRESS IS NEVER DESTROYED BY A CLOCK (matrx-frontend V-28 NEW-5). A token whose window has\n * not registered yet is kept verbatim in the address however long its lazy chunk takes. The\n * deadline (12 s prod / 45 s dev) decides one thing only: when to stop waiting silently and tell\n * the person, through `ports.notify` and `ports.errors`.\n *\n * A token is SETTLED when ANY registered entry carries its canonical key — the window a hydrator\n * opens may publish a different id (a vault link names an item, the vault window publishes its\n * singleton id) and may even be a different overlay than the one owning the key (key `agent`\n * opens display-mode shells). An alias resolves to its definition's own `link.key`.\n */\nimport { useEffect, useRef, useState, useSyncExternalStore } from \"react\";\nimport {\n LAZY_WINDOW_MOUNT_DEADLINE_MS,\n getOverlayMetaByLinkKey,\n selectIsUrlHydrated,\n selectUrlSyncEntries,\n setHydrated,\n type UrlSyncEntry,\n} from \"@ai-matrx/summon\";\nimport { useSummonDispatch, useSummonPorts, useSummonSelector } from \"@ai-matrx/summon/react\";\nimport { canonicalLinkKey, canonicalizeToken, hydrateLinkToken } from \"./door\";\nimport { formatLinkToken, parseLinkParam, readPanelsParam, withPanelsParam } from \"./grammar\";\n\nexport interface OverlayLinkManagerProps {\n /**\n * Restrict hydration and write-back to these link keys (a route-scoped manager). Tokens of\n * other keys are preserved verbatim and never hydrated. Omit for the app-wide manager.\n */\n readonly managedKeys?: readonly string[];\n /** Override the notice deadline (ms). Default: 12 s in production, 45 s in development. */\n readonly noticeDeadlineMs?: number;\n}\n\n/** Serialize registered entries as v2 tokens (only managed keys when given). */\nexport function serializeLinkEntries(entries: Readonly<Record<string, UrlSyncEntry>>, managedKeys?: readonly string[]): string {\n const managed = managedKeys ? new Set(managedKeys) : null;\n return Object.values(entries)\n .filter((entry) => !managed || managed.has(entry.typeKey))\n .map((entry) => formatLinkToken({ key: entry.typeKey, id: entry.instanceId, args: entry.args }))\n .join(\",\");\n}\n\n/** Keep the current param's unmanaged tokens; replace the managed ones with `nextManaged`. */\nexport function mergeManagedLinkParam(currentParam: string, nextManaged: string, managedKeys?: readonly string[]): string {\n if (!managedKeys) return nextManaged;\n const managed = new Set(managedKeys);\n const unmanaged = currentParam\n .split(\",\")\n .filter(Boolean)\n .filter((token) => !managed.has(token.split(\":\")[0] ?? \"\"));\n return [...unmanaged, ...nextManaged.split(\",\").filter(Boolean)].join(\",\");\n}\n\n/**\n * Re-attach pasted tokens no window has settled. A token is skipped when its CANONICAL key is\n * already represented by a live entry, so a late registration replaces the placeholder.\n */\nexport function withUnresolvedTokens(managedParam: string, unresolvedTokens: readonly string[]): string {\n if (unresolvedTokens.length === 0) return managedParam;\n const managed = managedParam.split(\",\").filter(Boolean);\n const represented = new Set(managed.map((token) => canonicalLinkKey(token.split(\":\")[0] ?? \"\")));\n const kept = unresolvedTokens.filter((token) => Boolean(token) && !represented.has(canonicalLinkKey(token.split(\":\")[0] ?? \"\")));\n return [...managed, ...kept].join(\",\");\n}\n\n/** The honest sentence for a link naming a window that did not open. */\nexport function unopenedWindowNotice(keys: readonly string[]): string {\n return (\n `This link names a window this build could not open: ${keys.join(\", \")}. ` +\n `The link is unchanged in your address bar — reload to try again, or report it if it keeps failing.`\n );\n}\n\ninterface PendingToken {\n /** What is written back while unsettled (canonical key, pasted id and args). */\n readonly token: string;\n /** The key the person pasted (named in the notice). */\n readonly tokenKey: string;\n readonly canonicalKey: string;\n}\n\nconst noopSubscribe = () => () => {};\n\nexport function OverlayLinkManager({ managedKeys, noticeDeadlineMs }: OverlayLinkManagerProps): null {\n const ports = useSummonPorts();\n const dispatch = useSummonDispatch();\n const entries = useSummonSelector(selectUrlSyncEntries);\n const isHydrated = useSummonSelector(selectIsUrlHydrated);\n const router = ports.router;\n // Re-run the write-back after ANY address change the router reports.\n const search = useSyncExternalStore(router.subscribe ?? noopSubscribe, router.search, () => \"\");\n\n const initialLoadDone = useRef(false);\n // Keyed by canonical key + id so two tokens that canonicalise together never overwrite each\n // other (V-30 NEW-5); the settle sweep still runs on canonical key.\n const unresolved = useRef(new Map<string, PendingToken>());\n const announced = useRef(new Set<string>());\n const [noticeDue, setNoticeDue] = useState(false);\n const [asyncTick, setAsyncTick] = useState(0);\n const deadline = noticeDeadlineMs ?? LAZY_WINDOW_MOUNT_DEADLINE_MS;\n\n const announce = (keys: readonly string[], reason: \"no-hydrator\" | \"never-registered\" | \"hydrator-threw\"): void => {\n const fresh = keys.filter((key) => !announced.current.has(key));\n if (fresh.length === 0) return;\n for (const key of fresh) announced.current.add(key);\n const joined = fresh.join(\", \");\n const why =\n reason === \"no-hydrator\"\n ? `no registered definition owns ${fresh.length > 1 ? \"these keys\" : \"this key\"}`\n : reason === \"hydrator-threw\"\n ? \"its hydrator threw\"\n : `hydrated, but no window registered its link within ${deadline}ms`;\n const message =\n `[overlays/url] ?panels= token(s) [${joined}]: ${why}. The token is KEPT in the address. ` +\n `Give the overlay definition a matching \\`link\\` (key or alias), or stop publishing the link.`;\n ports.errors.capture(new Error(message), {\n source: \"url-panel-unopened\",\n relation: `?panels=${joined}`,\n code: reason,\n typeKeys: fresh,\n deadlineMs: deadline,\n userMessage: unopenedWindowNotice(fresh),\n });\n ports.notify.error(unopenedWindowNotice(fresh), { duration: 12_000 });\n };\n const announceRef = useRef(announce);\n announceRef.current = announce;\n\n // 1. HYDRATION (address → store), once.\n useEffect(() => {\n if (initialLoadDone.current) return;\n initialLoadDone.current = true;\n const managed = managedKeys ? new Set(managedKeys) : null;\n const tokens = parseLinkParam(readPanelsParam(router.search()));\n const noHydrator: string[] = [];\n for (const token of tokens) {\n if (managed && !managed.has(token.key)) continue; // preserved verbatim by the write-back\n const canonicalKey = canonicalLinkKey(token.key);\n const pendingKey = `${canonicalKey}\\u0000${token.id ?? \"\"}`;\n const pending: PendingToken = { token: canonicalizeToken(token.raw), tokenKey: token.key, canonicalKey };\n if (!getOverlayMetaByLinkKey(token.key)?.link) {\n unresolved.current.set(pendingKey, { ...pending, token: token.raw });\n noHydrator.push(token.key);\n continue;\n }\n unresolved.current.set(pendingKey, pending);\n const outcome = hydrateLinkToken(token, dispatch, (error) => {\n ports.errors.capture(error, { source: \"url-panel-hydrator\", token: token.raw });\n });\n const apply = (result: string): void => {\n // A refused link is dropped from the address; it is not \"still to come\".\n if (result === \"refused\" && unresolved.current.get(pendingKey) === pending) {\n unresolved.current.delete(pendingKey);\n setAsyncTick((n) => n + 1);\n }\n };\n if (typeof outcome === \"string\") apply(outcome);\n else void outcome.then(apply);\n }\n if (noHydrator.length > 0) announceRef.current(noHydrator, \"no-hydrator\");\n dispatch(setHydrated());\n }, [dispatch, managedKeys, router, ports]);\n\n // The notice deadline: never gates a write; only turns a silent wait into a visible notice.\n useEffect(() => {\n if (!isHydrated || unresolved.current.size === 0) return undefined;\n const timer = setTimeout(() => setNoticeDue(true), deadline);\n return () => clearTimeout(timer);\n }, [isHydrated, deadline]);\n\n // 2. WRITE-BACK (store → address).\n useEffect(() => {\n if (!isHydrated) return;\n const observed = new Set(Object.values(entries).map((entry) => canonicalLinkKey(entry.typeKey)));\n for (const [key, pending] of [...unresolved.current.entries()]) {\n if (observed.has(pending.canonicalKey)) unresolved.current.delete(key);\n }\n const stillPending = [...unresolved.current.values()];\n if (noticeDue && stillPending.length > 0) {\n announceRef.current(\n stillPending.map((pending) => pending.tokenKey),\n \"never-registered\",\n );\n }\n // The LIVE address, never a snapshot: the page may have written `?view=` a moment ago.\n const liveSearch = router.search();\n const currentParam = readPanelsParam(liveSearch) ?? \"\";\n const nextManaged = withUnresolvedTokens(\n serializeLinkEntries(entries, managedKeys),\n stillPending.map((pending) => pending.token),\n );\n const nextParam = mergeManagedLinkParam(currentParam, nextManaged, managedKeys);\n if (currentParam !== nextParam) router.replaceSearch(withPanelsParam(liveSearch, nextParam));\n }, [entries, isHydrated, managedKeys, router, search, noticeDue, asyncTick]);\n\n return null;\n}\n","/**\n * The ONE door every link source goes through: the address-bar manager on load, a notification\n * that opens its link in place, the extension bridge. A token is resolved through the registry —\n * `getOverlayMetaByLinkKey(key).link.hydrate({ id, args, open, dispatch })` — never a side table.\n */\nimport type { Dispatch, UnknownAction } from \"@reduxjs/toolkit\";\nimport {\n DEFAULT_INSTANCE_ID,\n getOverlayMeta,\n getOverlayMetaByLinkKey,\n isRegisteredOverlay,\n overlaysRawActions,\n type AnyOverlayMeta,\n type OverlayHostDispatch,\n} from \"@ai-matrx/summon\";\nimport { parseLinkParam, readPanelsParam, type LinkToken } from \"./grammar\";\n\n/** Any Redux dispatch that takes plain actions (a host's thunk-aware dispatch qualifies). */\nexport type LinkDispatch = Dispatch<UnknownAction>;\n\n/**\n * Open an overlay by its registry id from inside the package. Uses the raw action so internal\n * code passes plain strings (a host's augmented `OverlayId` union never needs a cast here); the\n * definition's `defaults` are merged underneath exactly as the typed creator does.\n */\nexport function openRegisteredOverlay(\n dispatch: LinkDispatch,\n overlayId: string,\n instanceId: string = DEFAULT_INSTANCE_ID,\n data?: unknown,\n): void {\n const defaults = getOverlayMeta(overlayId)?.defaults;\n dispatch(\n overlaysRawActions.openOverlay({\n overlayId,\n instanceId,\n ...(data === undefined ? {} : { data }),\n ...(isRecord(defaults) ? { defaults } : {}),\n }),\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** The key a token is judged by: the owning definition's own `link.key` (aliases resolve to it). */\nexport function canonicalLinkKey(key: string): string {\n return getOverlayMetaByLinkKey(key)?.link?.key ?? key;\n}\n\n/** The token with its key replaced by the canonical key; id and args carry across untouched. */\nexport function canonicalizeToken(raw: string): string {\n const at = raw.indexOf(\":\");\n const key = at === -1 ? raw : raw.slice(0, at);\n const canonical = canonicalLinkKey(key);\n if (canonical === key) return raw;\n return at === -1 ? canonical : `${canonical}${raw.slice(at)}`;\n}\n\nexport type TokenOutcome = \"opened\" | \"refused\" | \"unknown\" | \"failed\";\n\n/**\n * Run one token's hydrator. Synchronous hydrators dispatch before this returns; an async one\n * yields a promise. A throw (sync or async) is `\"failed\"` and is reported to `onError`.\n */\nexport function hydrateLinkToken(\n token: LinkToken,\n dispatch: LinkDispatch,\n onError?: (error: unknown, token: LinkToken) => void,\n): TokenOutcome | Promise<TokenOutcome> {\n const meta: AnyOverlayMeta | undefined = getOverlayMetaByLinkKey(token.key);\n const link = meta?.link;\n if (!meta || !link) return \"unknown\";\n const ownerId = meta.id;\n const settle = (result: void | \"refused\"): TokenOutcome => (result === \"refused\" ? \"refused\" : \"opened\");\n const fail = (error: unknown): TokenOutcome => {\n onError?.(error, token);\n return \"failed\";\n };\n try {\n const result = link.hydrate({\n id: token.id ?? \"\",\n args: token.args ?? {},\n open: (options) => openRegisteredOverlay(dispatch, ownerId, options?.instanceId ?? DEFAULT_INSTANCE_ID, options?.data),\n // The host's store dispatch IS the host dispatch at runtime; its thunk-aware type is the\n // host's augmentation (`OverlayHostTypes`), which the package cannot see.\n dispatch: dispatch as unknown as OverlayHostDispatch,\n });\n if (result && typeof (result as PromiseLike<unknown>).then === \"function\") {\n return Promise.resolve(result).then(settle, fail);\n }\n return settle(result as void | \"refused\");\n } catch (error) {\n return fail(error);\n }\n}\n\nexport interface OpenFromLinkResult {\n /** Tokens (verbatim) whose hydrator ran and did not refuse. */\n readonly opened: string[];\n /** Tokens whose hydrator declined the link on purpose. */\n readonly refused: string[];\n /** Tokens whose key no registered definition owns. */\n readonly unknown: string[];\n /** Tokens whose hydrator threw. */\n readonly failed: string[];\n}\n\nexport interface OpenFromLinkOptions {\n readonly dispatch: LinkDispatch;\n /**\n * All or nothing: when any token's key is unknown, open NOTHING and report the unknown tokens\n * (a notification then opens the link in a new tab instead of half of it in place).\n */\n readonly requireAll?: boolean;\n readonly onError?: (error: unknown, token: LinkToken) => void;\n}\n\n/**\n * Open every `?panels=` token of a search string or URL through its definition's hydrator.\n * Synchronous hydrators have dispatched by the time this returns; the promise settles once\n * async hydrators finish.\n */\nexport async function openFromLink(searchOrUrl: string, options: OpenFromLinkOptions): Promise<OpenFromLinkResult> {\n const tokens = parseLinkParam(readPanelsParam(searchOrUrl));\n const result: OpenFromLinkResult = { opened: [], refused: [], unknown: [], failed: [] };\n const unknown = tokens.filter((token) => !getOverlayMetaByLinkKey(token.key)?.link);\n if (options.requireAll && unknown.length > 0) {\n result.unknown.push(...unknown.map((token) => token.raw));\n return result;\n }\n const outcomes = tokens.map((token) => hydrateLinkToken(token, options.dispatch, options.onError));\n const settled = await Promise.all(outcomes.map((outcome) => Promise.resolve(outcome)));\n settled.forEach((outcome, index) => {\n const raw = tokens[index]?.raw ?? \"\";\n result[outcome === \"opened\" ? \"opened\" : outcome].push(raw);\n });\n return result;\n}\n\n// ── The extension door ──────────────────────────────────────────────────────────────────────\n\nexport interface OpenPanelRequest {\n readonly panelId: string;\n readonly instanceId?: string;\n readonly data?: Readonly<Record<string, unknown>>;\n}\n\nexport type OpenPanelResult =\n | { readonly ok: true; readonly opened: true; readonly panelId: string; readonly instanceId: string | null }\n | { readonly ok: false; readonly error: \"invalid_payload\" | \"unknown_panel\"; readonly details?: unknown };\n\nfunction readOpenPanelRequest(raw: unknown): OpenPanelRequest | string {\n if (!isRecord(raw)) return \"payload must be an object\";\n const { panelId, instanceId, data } = raw;\n if (typeof panelId !== \"string\" || panelId.length === 0) return \"panelId must be a non-empty string\";\n if (instanceId !== undefined && instanceId !== null && (typeof instanceId !== \"string\" || instanceId.length === 0)) {\n return \"instanceId must be a non-empty string when given\";\n }\n if (data !== undefined && data !== null && !isRecord(data)) return \"data must be an object when given\";\n return {\n panelId,\n ...(typeof instanceId === \"string\" ? { instanceId } : {}),\n ...(isRecord(data) ? { data } : {}),\n };\n}\n\n/**\n * Open a registered overlay by id for an outside caller (the browser extension's `openPanel`\n * envelope). Validates the payload and the id; failures come back as values, never throws, so\n * the bridge can reply verbatim. `instanceId: null` in the reply = the caller used the singleton.\n */\nexport function openPanelRequest(rawPayload: unknown, options: { readonly dispatch: LinkDispatch }): OpenPanelResult {\n const request = readOpenPanelRequest(rawPayload);\n if (typeof request === \"string\") return { ok: false, error: \"invalid_payload\", details: request };\n if (!isRegisteredOverlay(request.panelId)) {\n return {\n ok: false,\n error: \"unknown_panel\",\n details: { panelId: request.panelId, hint: \"panelId must be the id of a registered overlay definition.\" },\n };\n }\n openRegisteredOverlay(options.dispatch, request.panelId, request.instanceId ?? DEFAULT_INSTANCE_ID, request.data);\n return { ok: true, opened: true, panelId: request.panelId, instanceId: request.instanceId ?? null };\n}\n"],"mappings":"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","names":["import_react","import_react","import_summon","import_summon"]}
1
+ {"version":3,"sources":["../src/url.ts","../src/url/grammar.ts","../src/url/OverlayLinkManager.tsx","../src/url/door.ts"],"sourcesContent":["// @ai-matrx/summon/url — address-bar links: the `?panels=` grammar, the link manager, the\n// `useOverlayLink` publisher and the one door (`openFromLink`) every link source goes through.\nexport { useOverlayLink } from \"@ai-matrx/summon/react\";\nexport {\n parseLinkToken,\n parseLinkParam,\n formatLinkToken,\n encodeLinkId,\n encodeLinkArg,\n decodeLinkPart,\n readPanelsParam,\n withPanelsParam,\n type LinkToken,\n type LinkEntry,\n} from \"./url/grammar\";\nexport {\n OverlayLinkManager,\n serializeLinkEntries,\n mergeManagedLinkParam,\n withUnresolvedTokens,\n unopenedWindowNotice,\n type OverlayLinkManagerProps,\n} from \"./url/OverlayLinkManager\";\nexport {\n openFromLink,\n openPanelRequest,\n hydrateLinkToken,\n canonicalLinkKey,\n canonicalizeToken,\n openRegisteredOverlay,\n type OpenFromLinkResult,\n type OpenFromLinkOptions,\n type OpenPanelRequest,\n type OpenPanelResult,\n type TokenOutcome,\n type LinkDispatch,\n} from \"./url/door\";\n","/**\n * The `?panels=` link grammar.\n *\n * panels = token (\",\" token)*\n * token = key [\":\" id [\":\" args]]\n * args = k \"-\" v (\"_\" k \"-\" v)*\n *\n * GRAMMAR v2 (this file): on WRITE, ids are percent-encoded for `% , :` and arg keys and values\n * for `% , : _ -`, so any value — a uuid, a date, a path, a list — survives the round trip.\n * Keys and ids keep `-` and `_` unencoded: they are not separators inside an id, and leaving them means a\n * uuid id writes byte-identically to v1. On READ, v1 tokens (unencoded) parse unchanged: an arg\n * pair splits on its FIRST hyphen, and decoding only touches well-formed `%XX` escapes.\n *\n * Framework-free; this is the one grammar every door (address bar, notifications, extension)\n * shares.\n */\n\nexport interface LinkToken {\n /** The token exactly as it appeared (the verbatim form written back while unresolved). */\n readonly raw: string;\n readonly key: string;\n /** `undefined` for a bare-key token (`keyword_research`). */\n readonly id: string | undefined;\n /** `undefined` when the token carries no args segment. */\n readonly args: Readonly<Record<string, string>> | undefined;\n}\n\nexport interface LinkEntry {\n readonly key: string;\n readonly id: string;\n readonly args?: Readonly<Record<string, string>> | undefined;\n}\n\nconst hex = (ch: string): string => `%${ch.charCodeAt(0).toString(16).toUpperCase().padStart(2, \"0\")}`;\n\n/** Encode a token id (`% , :`). */\nexport function encodeLinkId(id: string): string {\n return id.replace(/[%,:]/g, hex);\n}\n\n/** Encode an arg key or value (`% , : _ -`). */\nexport function encodeLinkArg(value: string): string {\n return value.replace(/[%,:_-]/g, hex);\n}\n\n/** Decode well-formed `%XX` escapes; anything else (a v1 literal `%`) is kept as written. */\nexport function decodeLinkPart(value: string): string {\n if (!value.includes(\"%\")) return value;\n return value.replace(/(?:%[0-9A-Fa-f]{2})+/g, (run) => {\n try {\n return decodeURIComponent(run);\n } catch {\n return run;\n }\n });\n}\n\n/** Parse one token (v1 or v2). Returns `null` for an empty token. */\nexport function parseLinkToken(raw: string): LinkToken | null {\n if (!raw) return null;\n const first = raw.indexOf(\":\");\n const key = first === -1 ? raw : raw.slice(0, first);\n if (!key) return null;\n if (first === -1) return { raw, key, id: undefined, args: undefined };\n const rest = raw.slice(first + 1);\n const second = rest.indexOf(\":\");\n const idPart = second === -1 ? rest : rest.slice(0, second);\n const argsPart = second === -1 ? undefined : rest.slice(second + 1);\n let args: Record<string, string> | undefined;\n if (argsPart) {\n args = {};\n for (const pair of argsPart.split(\"_\")) {\n // Split on the FIRST hyphen only: a v1 value may hold hyphens (uuid, date).\n const at = pair.indexOf(\"-\");\n if (at <= 0 || at === pair.length - 1) continue;\n args[decodeLinkPart(pair.slice(0, at))] = decodeLinkPart(pair.slice(at + 1));\n }\n }\n return { raw, key: decodeLinkPart(key), id: decodeLinkPart(idPart), args };\n}\n\n/** Parse a whole `panels` value. Empty segments are skipped. */\nexport function parseLinkParam(param: string | null | undefined): LinkToken[] {\n if (!param) return [];\n const out: LinkToken[] = [];\n for (const raw of param.split(\",\")) {\n const token = parseLinkToken(raw);\n if (token) out.push(token);\n }\n return out;\n}\n\n/** Write one entry as a v2 token. Args are written in key order so equal entries write equal tokens. */\nexport function formatLinkToken(entry: LinkEntry): string {\n let token = `${encodeLinkId(entry.key)}:${encodeLinkId(entry.id)}`;\n const args = entry.args;\n if (args) {\n const keys = Object.keys(args).sort();\n if (keys.length > 0) {\n token += `:${keys.map((k) => `${encodeLinkArg(k)}-${encodeLinkArg(args[k] ?? \"\")}`).join(\"_\")}`;\n }\n }\n return token;\n}\n\n/** The `panels` value from a search string or a full URL (`null` when absent). */\nexport function readPanelsParam(searchOrUrl: string): string | null {\n const q = searchOrUrl.indexOf(\"?\");\n const hash = searchOrUrl.indexOf(\"#\");\n let search: string;\n if (q === -1) {\n // A bare query string without \"?\" (`panels=…`) or a URL with no query.\n search = searchOrUrl.includes(\"=\") && !searchOrUrl.includes(\"/\") ? searchOrUrl : \"\";\n } else {\n search = searchOrUrl.slice(q + 1, hash > q ? hash : undefined);\n }\n return new URLSearchParams(search).get(\"panels\");\n}\n\n/** `search` with `panels` set to `param` (removed when empty). Returns \"\" or \"?…\". */\nexport function withPanelsParam(search: string, param: string): string {\n const params = new URLSearchParams(search.startsWith(\"?\") ? search.slice(1) : search);\n if (param) params.set(\"panels\", param);\n else params.delete(\"panels\");\n const qs = params.toString();\n return qs ? `?${qs}` : \"\";\n}\n","/**\n * OverlayLinkManager — the address bar's `?panels=` channel (successor of matrx-frontend's\n * UrlPanelManager). Mount ONCE inside <SummonProvider>. Driven entirely by `ports.router`.\n *\n * 1. On mount: read `?panels=`, hydrate each token through the one door (`hydrateLinkToken`).\n * 2. After that: write back the union of registered link entries (`useOverlayLink`) plus every\n * pasted token that has not been settled yet, via `router.replaceSearch` — never navigation.\n *\n * THE ADDRESS IS NEVER DESTROYED BY A CLOCK (matrx-frontend V-28 NEW-5). A token whose window has\n * not registered yet is kept verbatim in the address however long its lazy chunk takes. The\n * deadline (12 s prod / 45 s dev) decides one thing only: when to stop waiting silently and tell\n * the person, through `ports.notify` and `ports.errors`.\n *\n * A token is SETTLED when ANY registered entry carries its canonical key — the window a hydrator\n * opens may publish a different id (a vault link names an item, the vault window publishes its\n * singleton id) and may even be a different overlay than the one owning the key (key `agent`\n * opens display-mode shells). An alias resolves to its definition's own `link.key`.\n */\nimport { useEffect, useRef, useState, useSyncExternalStore } from \"react\";\nimport {\n LAZY_WINDOW_MOUNT_DEADLINE_MS,\n getOverlayMetaByLinkKey,\n selectIsUrlHydrated,\n selectUrlSyncEntries,\n setHydrated,\n type UrlSyncEntry,\n} from \"@ai-matrx/summon\";\nimport { useSummonDispatch, useSummonPorts, useSummonSelector } from \"@ai-matrx/summon/react\";\nimport { canonicalLinkKey, canonicalizeToken, hydrateLinkToken } from \"./door\";\nimport { formatLinkToken, parseLinkParam, readPanelsParam, withPanelsParam } from \"./grammar\";\n\nexport interface OverlayLinkManagerProps {\n /**\n * Restrict hydration and write-back to these link keys (a route-scoped manager). Tokens of\n * other keys are preserved verbatim and never hydrated. Omit for the app-wide manager.\n */\n readonly managedKeys?: readonly string[];\n /** Override the notice deadline (ms). Default: 12 s in production, 45 s in development. */\n readonly noticeDeadlineMs?: number;\n}\n\n/** Serialize registered entries as v2 tokens (only managed keys when given). */\nexport function serializeLinkEntries(entries: Readonly<Record<string, UrlSyncEntry>>, managedKeys?: readonly string[]): string {\n const managed = managedKeys ? new Set(managedKeys) : null;\n return Object.values(entries)\n .filter((entry) => !managed || managed.has(entry.typeKey))\n .map((entry) => formatLinkToken({ key: entry.typeKey, id: entry.instanceId, args: entry.args }))\n .join(\",\");\n}\n\n/** Keep the current param's unmanaged tokens; replace the managed ones with `nextManaged`. */\nexport function mergeManagedLinkParam(currentParam: string, nextManaged: string, managedKeys?: readonly string[]): string {\n if (!managedKeys) return nextManaged;\n const managed = new Set(managedKeys);\n const unmanaged = currentParam\n .split(\",\")\n .filter(Boolean)\n .filter((token) => !managed.has(token.split(\":\")[0] ?? \"\"));\n return [...unmanaged, ...nextManaged.split(\",\").filter(Boolean)].join(\",\");\n}\n\n/**\n * Re-attach pasted tokens no window has settled. A token is skipped when its CANONICAL key is\n * already represented by a live entry, so a late registration replaces the placeholder.\n */\nexport function withUnresolvedTokens(managedParam: string, unresolvedTokens: readonly string[]): string {\n if (unresolvedTokens.length === 0) return managedParam;\n const managed = managedParam.split(\",\").filter(Boolean);\n const represented = new Set(managed.map((token) => canonicalLinkKey(token.split(\":\")[0] ?? \"\")));\n const kept = unresolvedTokens.filter((token) => Boolean(token) && !represented.has(canonicalLinkKey(token.split(\":\")[0] ?? \"\")));\n return [...managed, ...kept].join(\",\");\n}\n\n/** The honest sentence for a link naming a window that did not open. */\nexport function unopenedWindowNotice(keys: readonly string[]): string {\n return (\n `This link names a window this build could not open: ${keys.join(\", \")}. ` +\n `The link is unchanged in your address bar — reload to try again, or report it if it keeps failing.`\n );\n}\n\ninterface PendingToken {\n /** What is written back while unsettled (canonical key, pasted id and args). */\n readonly token: string;\n /** The key the person pasted (named in the notice). */\n readonly tokenKey: string;\n readonly canonicalKey: string;\n}\n\nconst noopSubscribe = () => () => {};\n\nexport function OverlayLinkManager({ managedKeys, noticeDeadlineMs }: OverlayLinkManagerProps): null {\n const ports = useSummonPorts();\n const dispatch = useSummonDispatch();\n const entries = useSummonSelector(selectUrlSyncEntries);\n const isHydrated = useSummonSelector(selectIsUrlHydrated);\n const router = ports.router;\n // Re-run the write-back after ANY address change the router reports.\n const search = useSyncExternalStore(router.subscribe ?? noopSubscribe, router.search, () => \"\");\n\n const initialLoadDone = useRef(false);\n // Keyed by canonical key + id so two tokens that canonicalise together never overwrite each\n // other (V-30 NEW-5); the settle sweep still runs on canonical key.\n const unresolved = useRef(new Map<string, PendingToken>());\n const announced = useRef(new Set<string>());\n const [noticeDue, setNoticeDue] = useState(false);\n const [asyncTick, setAsyncTick] = useState(0);\n const deadline = noticeDeadlineMs ?? LAZY_WINDOW_MOUNT_DEADLINE_MS;\n\n const announce = (keys: readonly string[], reason: \"no-hydrator\" | \"never-registered\" | \"hydrator-threw\"): void => {\n const fresh = keys.filter((key) => !announced.current.has(key));\n if (fresh.length === 0) return;\n for (const key of fresh) announced.current.add(key);\n const joined = fresh.join(\", \");\n const why =\n reason === \"no-hydrator\"\n ? `no registered definition owns ${fresh.length > 1 ? \"these keys\" : \"this key\"}`\n : reason === \"hydrator-threw\"\n ? \"its hydrator threw\"\n : `hydrated, but no window registered its link within ${deadline}ms`;\n const message =\n `[overlays/url] ?panels= token(s) [${joined}]: ${why}. The token is KEPT in the address. ` +\n `Give the overlay definition a matching \\`link\\` (key or alias), or stop publishing the link.`;\n ports.errors.capture(new Error(message), {\n source: \"url-panel-unopened\",\n relation: `?panels=${joined}`,\n code: reason,\n typeKeys: fresh,\n deadlineMs: deadline,\n userMessage: unopenedWindowNotice(fresh),\n });\n ports.notify.error(unopenedWindowNotice(fresh), { duration: 12_000 });\n };\n const announceRef = useRef(announce);\n announceRef.current = announce;\n\n // 1. HYDRATION (address → store), once.\n useEffect(() => {\n if (initialLoadDone.current) return;\n initialLoadDone.current = true;\n const managed = managedKeys ? new Set(managedKeys) : null;\n const tokens = parseLinkParam(readPanelsParam(router.search()));\n const noHydrator: string[] = [];\n for (const token of tokens) {\n if (managed && !managed.has(token.key)) continue; // preserved verbatim by the write-back\n const canonicalKey = canonicalLinkKey(token.key);\n const pendingKey = `${canonicalKey}\\u0000${token.id ?? \"\"}`;\n const pending: PendingToken = { token: canonicalizeToken(token.raw), tokenKey: token.key, canonicalKey };\n if (!getOverlayMetaByLinkKey(token.key)?.link) {\n unresolved.current.set(pendingKey, { ...pending, token: token.raw });\n noHydrator.push(token.key);\n continue;\n }\n unresolved.current.set(pendingKey, pending);\n const outcome = hydrateLinkToken(token, dispatch, (error) => {\n ports.errors.capture(error, { source: \"url-panel-hydrator\", token: token.raw });\n });\n const apply = (result: string): void => {\n // A refused link is dropped from the address; it is not \"still to come\".\n if (result === \"refused\" && unresolved.current.get(pendingKey) === pending) {\n unresolved.current.delete(pendingKey);\n setAsyncTick((n) => n + 1);\n }\n };\n if (typeof outcome === \"string\") apply(outcome);\n else void outcome.then(apply);\n }\n if (noHydrator.length > 0) announceRef.current(noHydrator, \"no-hydrator\");\n dispatch(setHydrated());\n }, [dispatch, managedKeys, router, ports]);\n\n // The notice deadline: never gates a write; only turns a silent wait into a visible notice.\n useEffect(() => {\n if (!isHydrated || unresolved.current.size === 0) return undefined;\n const timer = setTimeout(() => setNoticeDue(true), deadline);\n return () => clearTimeout(timer);\n }, [isHydrated, deadline]);\n\n // 2. WRITE-BACK (store → address).\n useEffect(() => {\n if (!isHydrated) return;\n const observed = new Set(Object.values(entries).map((entry) => canonicalLinkKey(entry.typeKey)));\n for (const [key, pending] of [...unresolved.current.entries()]) {\n if (observed.has(pending.canonicalKey)) unresolved.current.delete(key);\n }\n const stillPending = [...unresolved.current.values()];\n if (noticeDue && stillPending.length > 0) {\n announceRef.current(\n stillPending.map((pending) => pending.tokenKey),\n \"never-registered\",\n );\n }\n // The LIVE address, never a snapshot: the page may have written `?view=` a moment ago.\n const liveSearch = router.search();\n const currentParam = readPanelsParam(liveSearch) ?? \"\";\n const nextManaged = withUnresolvedTokens(\n serializeLinkEntries(entries, managedKeys),\n stillPending.map((pending) => pending.token),\n );\n const nextParam = mergeManagedLinkParam(currentParam, nextManaged, managedKeys);\n if (currentParam !== nextParam) router.replaceSearch(withPanelsParam(liveSearch, nextParam));\n }, [entries, isHydrated, managedKeys, router, search, noticeDue, asyncTick]);\n\n return null;\n}\n","/**\n * The ONE door every link source goes through: the address-bar manager on load, a notification\n * that opens its link in place, the extension bridge. A token is resolved through the registry —\n * `getOverlayMetaByLinkKey(key).link.hydrate({ id, args, open, dispatch })` — never a side table.\n */\nimport type { Dispatch, UnknownAction } from \"@reduxjs/toolkit\";\nimport {\n DEFAULT_INSTANCE_ID,\n getOverlayMeta,\n getOverlayMetaByLinkKey,\n isRegisteredOverlay,\n overlaysRawActions,\n type AnyOverlayMeta,\n type OverlayHostDispatch,\n} from \"@ai-matrx/summon\";\nimport { parseLinkParam, readPanelsParam, type LinkToken } from \"./grammar\";\n\n/** Any Redux dispatch that takes plain actions (a host's thunk-aware dispatch qualifies). */\nexport type LinkDispatch = Dispatch<UnknownAction>;\n\n/**\n * Open an overlay by its registry id from inside the package. Uses the raw action so internal\n * code passes plain strings (a host's augmented `OverlayId` union never needs a cast here); the\n * definition's `defaults` are merged underneath exactly as the typed creator does.\n */\nexport function openRegisteredOverlay(\n dispatch: LinkDispatch,\n overlayId: string,\n instanceId: string = DEFAULT_INSTANCE_ID,\n data?: unknown,\n): void {\n const defaults = getOverlayMeta(overlayId)?.defaults;\n dispatch(\n overlaysRawActions.openOverlay({\n overlayId,\n instanceId,\n ...(data === undefined ? {} : { data }),\n ...(isRecord(defaults) ? { defaults } : {}),\n }),\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** The key a token is judged by: the owning definition's own `link.key` (aliases resolve to it). */\nexport function canonicalLinkKey(key: string): string {\n return getOverlayMetaByLinkKey(key)?.link?.key ?? key;\n}\n\n/** The token with its key replaced by the canonical key; id and args carry across untouched. */\nexport function canonicalizeToken(raw: string): string {\n const at = raw.indexOf(\":\");\n const key = at === -1 ? raw : raw.slice(0, at);\n const canonical = canonicalLinkKey(key);\n if (canonical === key) return raw;\n return at === -1 ? canonical : `${canonical}${raw.slice(at)}`;\n}\n\nexport type TokenOutcome = \"opened\" | \"refused\" | \"unknown\" | \"failed\";\n\n/**\n * Run one token's hydrator. Synchronous hydrators dispatch before this returns; an async one\n * yields a promise. A throw (sync or async) is `\"failed\"` and is reported to `onError`.\n */\nexport function hydrateLinkToken(\n token: LinkToken,\n dispatch: LinkDispatch,\n onError?: (error: unknown, token: LinkToken) => void,\n): TokenOutcome | Promise<TokenOutcome> {\n const meta: AnyOverlayMeta | undefined = getOverlayMetaByLinkKey(token.key);\n const link = meta?.link;\n if (!meta || !link) return \"unknown\";\n const ownerId = meta.id;\n const settle = (result: void | \"refused\"): TokenOutcome => (result === \"refused\" ? \"refused\" : \"opened\");\n const fail = (error: unknown): TokenOutcome => {\n onError?.(error, token);\n return \"failed\";\n };\n try {\n const result = link.hydrate({\n id: token.id ?? \"\",\n args: token.args ?? {},\n open: (options) => openRegisteredOverlay(dispatch, ownerId, options?.instanceId ?? DEFAULT_INSTANCE_ID, options?.data),\n // The host's store dispatch IS the host dispatch at runtime; its thunk-aware type is the\n // host's augmentation (`OverlayHostTypes`), which the package cannot see.\n dispatch: dispatch as unknown as OverlayHostDispatch,\n });\n if (result && typeof (result as PromiseLike<unknown>).then === \"function\") {\n return Promise.resolve(result).then(settle, fail);\n }\n return settle(result as void | \"refused\");\n } catch (error) {\n return fail(error);\n }\n}\n\nexport interface OpenFromLinkResult {\n /** Tokens (verbatim) whose hydrator ran and did not refuse. */\n readonly opened: string[];\n /** Tokens whose hydrator declined the link on purpose. */\n readonly refused: string[];\n /** Tokens whose key no registered definition owns. */\n readonly unknown: string[];\n /** Tokens whose hydrator threw. */\n readonly failed: string[];\n}\n\nexport interface OpenFromLinkOptions {\n readonly dispatch: LinkDispatch;\n /**\n * All or nothing: when any token's key is unknown, open NOTHING and report the unknown tokens\n * (a notification then opens the link in a new tab instead of half of it in place).\n */\n readonly requireAll?: boolean;\n readonly onError?: (error: unknown, token: LinkToken) => void;\n}\n\n/**\n * Open every `?panels=` token of a search string or URL through its definition's hydrator.\n * Synchronous hydrators have dispatched by the time this returns; the promise settles once\n * async hydrators finish.\n */\nexport async function openFromLink(searchOrUrl: string, options: OpenFromLinkOptions): Promise<OpenFromLinkResult> {\n const tokens = parseLinkParam(readPanelsParam(searchOrUrl));\n const result: OpenFromLinkResult = { opened: [], refused: [], unknown: [], failed: [] };\n const unknown = tokens.filter((token) => !getOverlayMetaByLinkKey(token.key)?.link);\n if (options.requireAll && unknown.length > 0) {\n result.unknown.push(...unknown.map((token) => token.raw));\n return result;\n }\n const outcomes = tokens.map((token) => hydrateLinkToken(token, options.dispatch, options.onError));\n const settled = await Promise.all(outcomes.map((outcome) => Promise.resolve(outcome)));\n settled.forEach((outcome, index) => {\n const raw = tokens[index]?.raw ?? \"\";\n result[outcome === \"opened\" ? \"opened\" : outcome].push(raw);\n });\n return result;\n}\n\n// ── The extension door ──────────────────────────────────────────────────────────────────────\n\nexport interface OpenPanelRequest {\n readonly panelId: string;\n readonly instanceId?: string;\n readonly data?: Readonly<Record<string, unknown>>;\n}\n\nexport type OpenPanelResult =\n | { readonly ok: true; readonly opened: true; readonly panelId: string; readonly instanceId: string | null }\n | { readonly ok: false; readonly error: \"invalid_payload\" | \"unknown_panel\"; readonly details?: unknown };\n\nfunction readOpenPanelRequest(raw: unknown): OpenPanelRequest | string {\n if (!isRecord(raw)) return \"payload must be an object\";\n const { panelId, instanceId, data } = raw;\n if (typeof panelId !== \"string\" || panelId.length === 0) return \"panelId must be a non-empty string\";\n if (instanceId !== undefined && instanceId !== null && (typeof instanceId !== \"string\" || instanceId.length === 0)) {\n return \"instanceId must be a non-empty string when given\";\n }\n if (data !== undefined && data !== null && !isRecord(data)) return \"data must be an object when given\";\n return {\n panelId,\n ...(typeof instanceId === \"string\" ? { instanceId } : {}),\n ...(isRecord(data) ? { data } : {}),\n };\n}\n\n/**\n * Open a registered overlay by id for an outside caller (the browser extension's `openPanel`\n * envelope). Validates the payload and the id; failures come back as values, never throws, so\n * the bridge can reply verbatim. `instanceId: null` in the reply = the caller used the singleton.\n */\nexport function openPanelRequest(rawPayload: unknown, options: { readonly dispatch: LinkDispatch }): OpenPanelResult {\n const request = readOpenPanelRequest(rawPayload);\n if (typeof request === \"string\") return { ok: false, error: \"invalid_payload\", details: request };\n if (!isRegisteredOverlay(request.panelId)) {\n return {\n ok: false,\n error: \"unknown_panel\",\n details: { panelId: request.panelId, hint: \"panelId must be the id of a registered overlay definition.\" },\n };\n }\n openRegisteredOverlay(options.dispatch, request.panelId, request.instanceId ?? DEFAULT_INSTANCE_ID, request.data);\n return { ok: true, opened: true, panelId: request.panelId, instanceId: request.instanceId ?? null };\n}\n"],"mappings":"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","names":["import_react","import_summon","import_react"]}
package/dist/url.d.cts CHANGED
@@ -1,13 +1,7 @@
1
+ export { useOverlayLink } from '@ai-matrx/summon/react';
1
2
  import { UrlSyncEntry } from '@ai-matrx/summon';
2
3
  import { Dispatch, UnknownAction } from '@reduxjs/toolkit';
3
4
 
4
- /**
5
- * Publish an open instance's address into `?panels=` while it is mounted. `linkKey` undefined →
6
- * nothing is published (a window with no link); so is an undefined or empty `id`. The token is `linkKey:id[:args]`; args are
7
- * compared by value, so a new object with equal contents never re-registers.
8
- */
9
- declare function useOverlayLink(linkKey: string | undefined, id: string | undefined, args?: Readonly<Record<string, string>>): void;
10
-
11
5
  /**
12
6
  * The `?panels=` link grammar.
13
7
  *
@@ -150,4 +144,4 @@ declare function openPanelRequest(rawPayload: unknown, options: {
150
144
  readonly dispatch: LinkDispatch;
151
145
  }): OpenPanelResult;
152
146
 
153
- export { type LinkDispatch, type LinkEntry, type LinkToken, type OpenFromLinkOptions, type OpenFromLinkResult, type OpenPanelRequest, type OpenPanelResult, OverlayLinkManager, type OverlayLinkManagerProps, type TokenOutcome, canonicalLinkKey, canonicalizeToken, decodeLinkPart, encodeLinkArg, encodeLinkId, formatLinkToken, hydrateLinkToken, mergeManagedLinkParam, openFromLink, openPanelRequest, openRegisteredOverlay, parseLinkParam, parseLinkToken, readPanelsParam, serializeLinkEntries, unopenedWindowNotice, useOverlayLink, withPanelsParam, withUnresolvedTokens };
147
+ export { type LinkDispatch, type LinkEntry, type LinkToken, type OpenFromLinkOptions, type OpenFromLinkResult, type OpenPanelRequest, type OpenPanelResult, OverlayLinkManager, type OverlayLinkManagerProps, type TokenOutcome, canonicalLinkKey, canonicalizeToken, decodeLinkPart, encodeLinkArg, encodeLinkId, formatLinkToken, hydrateLinkToken, mergeManagedLinkParam, openFromLink, openPanelRequest, openRegisteredOverlay, parseLinkParam, parseLinkToken, readPanelsParam, serializeLinkEntries, unopenedWindowNotice, withPanelsParam, withUnresolvedTokens };
package/dist/url.d.ts CHANGED
@@ -1,13 +1,7 @@
1
+ export { useOverlayLink } from '@ai-matrx/summon/react';
1
2
  import { UrlSyncEntry } from '@ai-matrx/summon';
2
3
  import { Dispatch, UnknownAction } from '@reduxjs/toolkit';
3
4
 
4
- /**
5
- * Publish an open instance's address into `?panels=` while it is mounted. `linkKey` undefined →
6
- * nothing is published (a window with no link); so is an undefined or empty `id`. The token is `linkKey:id[:args]`; args are
7
- * compared by value, so a new object with equal contents never re-registers.
8
- */
9
- declare function useOverlayLink(linkKey: string | undefined, id: string | undefined, args?: Readonly<Record<string, string>>): void;
10
-
11
5
  /**
12
6
  * The `?panels=` link grammar.
13
7
  *
@@ -150,4 +144,4 @@ declare function openPanelRequest(rawPayload: unknown, options: {
150
144
  readonly dispatch: LinkDispatch;
151
145
  }): OpenPanelResult;
152
146
 
153
- export { type LinkDispatch, type LinkEntry, type LinkToken, type OpenFromLinkOptions, type OpenFromLinkResult, type OpenPanelRequest, type OpenPanelResult, OverlayLinkManager, type OverlayLinkManagerProps, type TokenOutcome, canonicalLinkKey, canonicalizeToken, decodeLinkPart, encodeLinkArg, encodeLinkId, formatLinkToken, hydrateLinkToken, mergeManagedLinkParam, openFromLink, openPanelRequest, openRegisteredOverlay, parseLinkParam, parseLinkToken, readPanelsParam, serializeLinkEntries, unopenedWindowNotice, useOverlayLink, withPanelsParam, withUnresolvedTokens };
147
+ export { type LinkDispatch, type LinkEntry, type LinkToken, type OpenFromLinkOptions, type OpenFromLinkResult, type OpenPanelRequest, type OpenPanelResult, OverlayLinkManager, type OverlayLinkManagerProps, type TokenOutcome, canonicalLinkKey, canonicalizeToken, decodeLinkPart, encodeLinkArg, encodeLinkId, formatLinkToken, hydrateLinkToken, mergeManagedLinkParam, openFromLink, openPanelRequest, openRegisteredOverlay, parseLinkParam, parseLinkToken, readPanelsParam, serializeLinkEntries, unopenedWindowNotice, withPanelsParam, withUnresolvedTokens };
package/dist/url.js CHANGED
@@ -1,23 +1,7 @@
1
1
  "use client";
2
2
 
3
- // src/url/useOverlayLink.ts
4
- import { useEffect, useMemo } from "react";
5
- import { registerSyncEntry, unregisterSyncEntry } from "@ai-matrx/summon";
6
- import { useSummonDispatch } from "@ai-matrx/summon/react";
7
- function useOverlayLink(linkKey, id, args) {
8
- const dispatch = useSummonDispatch();
9
- const argsKey = args ? JSON.stringify(
10
- Object.keys(args).sort().map((k) => [k, args[k]])
11
- ) : "";
12
- const stableArgs = useMemo(() => args ? { ...args } : void 0, [argsKey]);
13
- useEffect(() => {
14
- if (!linkKey || !id) return void 0;
15
- dispatch(registerSyncEntry(stableArgs ? { typeKey: linkKey, instanceId: id, args: stableArgs } : { typeKey: linkKey, instanceId: id }));
16
- return () => {
17
- dispatch(unregisterSyncEntry({ typeKey: linkKey, instanceId: id }));
18
- };
19
- }, [dispatch, linkKey, id, stableArgs]);
20
- }
3
+ // src/url.ts
4
+ import { useOverlayLink } from "@ai-matrx/summon/react";
21
5
 
22
6
  // src/url/grammar.ts
23
7
  var hex = (ch) => `%${ch.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`;
@@ -98,7 +82,7 @@ function withPanelsParam(search, param) {
98
82
  }
99
83
 
100
84
  // src/url/OverlayLinkManager.tsx
101
- import { useEffect as useEffect2, useRef, useState, useSyncExternalStore } from "react";
85
+ import { useEffect, useRef, useState, useSyncExternalStore } from "react";
102
86
  import {
103
87
  LAZY_WINDOW_MOUNT_DEADLINE_MS,
104
88
  getOverlayMetaByLinkKey as getOverlayMetaByLinkKey2,
@@ -106,7 +90,7 @@ import {
106
90
  selectUrlSyncEntries,
107
91
  setHydrated
108
92
  } from "@ai-matrx/summon";
109
- import { useSummonDispatch as useSummonDispatch2, useSummonPorts, useSummonSelector } from "@ai-matrx/summon/react";
93
+ import { useSummonDispatch, useSummonPorts, useSummonSelector } from "@ai-matrx/summon/react";
110
94
 
111
95
  // src/url/door.ts
112
96
  import {
@@ -236,7 +220,7 @@ var noopSubscribe = () => () => {
236
220
  };
237
221
  function OverlayLinkManager({ managedKeys, noticeDeadlineMs }) {
238
222
  const ports = useSummonPorts();
239
- const dispatch = useSummonDispatch2();
223
+ const dispatch = useSummonDispatch();
240
224
  const entries = useSummonSelector(selectUrlSyncEntries);
241
225
  const isHydrated = useSummonSelector(selectIsUrlHydrated);
242
226
  const router = ports.router;
@@ -266,7 +250,7 @@ function OverlayLinkManager({ managedKeys, noticeDeadlineMs }) {
266
250
  };
267
251
  const announceRef = useRef(announce);
268
252
  announceRef.current = announce;
269
- useEffect2(() => {
253
+ useEffect(() => {
270
254
  if (initialLoadDone.current) return;
271
255
  initialLoadDone.current = true;
272
256
  const managed = managedKeys ? new Set(managedKeys) : null;
@@ -298,12 +282,12 @@ function OverlayLinkManager({ managedKeys, noticeDeadlineMs }) {
298
282
  if (noHydrator.length > 0) announceRef.current(noHydrator, "no-hydrator");
299
283
  dispatch(setHydrated());
300
284
  }, [dispatch, managedKeys, router, ports]);
301
- useEffect2(() => {
285
+ useEffect(() => {
302
286
  if (!isHydrated || unresolved.current.size === 0) return void 0;
303
287
  const timer = setTimeout(() => setNoticeDue(true), deadline);
304
288
  return () => clearTimeout(timer);
305
289
  }, [isHydrated, deadline]);
306
- useEffect2(() => {
290
+ useEffect(() => {
307
291
  if (!isHydrated) return;
308
292
  const observed = new Set(Object.values(entries).map((entry) => canonicalLinkKey(entry.typeKey)));
309
293
  for (const [key, pending] of [...unresolved.current.entries()]) {
package/dist/url.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/url/useOverlayLink.ts","../src/url/grammar.ts","../src/url/OverlayLinkManager.tsx","../src/url/door.ts"],"sourcesContent":["import { useEffect, useMemo } from \"react\";\nimport { registerSyncEntry, unregisterSyncEntry } from \"@ai-matrx/summon\";\nimport { useSummonDispatch } from \"@ai-matrx/summon/react\";\n\n/**\n * Publish an open instance's address into `?panels=` while it is mounted. `linkKey` undefined →\n * nothing is published (a window with no link); so is an undefined or empty `id`. The token is `linkKey:id[:args]`; args are\n * compared by value, so a new object with equal contents never re-registers.\n */\nexport function useOverlayLink(linkKey: string | undefined, id: string | undefined, args?: Readonly<Record<string, string>>): void {\n const dispatch = useSummonDispatch();\n const argsKey = args\n ? JSON.stringify(\n Object.keys(args)\n .sort()\n .map((k) => [k, args[k]]),\n )\n : \"\";\n // Rebuilt only when the VALUES change (argsKey), never on a fresh object with equal contents.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const stableArgs = useMemo(() => (args ? { ...args } : undefined), [argsKey]);\n\n useEffect(() => {\n if (!linkKey || !id) return undefined;\n dispatch(registerSyncEntry(stableArgs ? { typeKey: linkKey, instanceId: id, args: stableArgs } : { typeKey: linkKey, instanceId: id }));\n return () => {\n dispatch(unregisterSyncEntry({ typeKey: linkKey, instanceId: id }));\n };\n }, [dispatch, linkKey, id, stableArgs]);\n}\n","/**\n * The `?panels=` link grammar.\n *\n * panels = token (\",\" token)*\n * token = key [\":\" id [\":\" args]]\n * args = k \"-\" v (\"_\" k \"-\" v)*\n *\n * GRAMMAR v2 (this file): on WRITE, ids are percent-encoded for `% , :` and arg keys and values\n * for `% , : _ -`, so any value — a uuid, a date, a path, a list — survives the round trip.\n * Keys and ids keep `-` and `_` unencoded: they are not separators inside an id, and leaving them means a\n * uuid id writes byte-identically to v1. On READ, v1 tokens (unencoded) parse unchanged: an arg\n * pair splits on its FIRST hyphen, and decoding only touches well-formed `%XX` escapes.\n *\n * Framework-free; this is the one grammar every door (address bar, notifications, extension)\n * shares.\n */\n\nexport interface LinkToken {\n /** The token exactly as it appeared (the verbatim form written back while unresolved). */\n readonly raw: string;\n readonly key: string;\n /** `undefined` for a bare-key token (`keyword_research`). */\n readonly id: string | undefined;\n /** `undefined` when the token carries no args segment. */\n readonly args: Readonly<Record<string, string>> | undefined;\n}\n\nexport interface LinkEntry {\n readonly key: string;\n readonly id: string;\n readonly args?: Readonly<Record<string, string>> | undefined;\n}\n\nconst hex = (ch: string): string => `%${ch.charCodeAt(0).toString(16).toUpperCase().padStart(2, \"0\")}`;\n\n/** Encode a token id (`% , :`). */\nexport function encodeLinkId(id: string): string {\n return id.replace(/[%,:]/g, hex);\n}\n\n/** Encode an arg key or value (`% , : _ -`). */\nexport function encodeLinkArg(value: string): string {\n return value.replace(/[%,:_-]/g, hex);\n}\n\n/** Decode well-formed `%XX` escapes; anything else (a v1 literal `%`) is kept as written. */\nexport function decodeLinkPart(value: string): string {\n if (!value.includes(\"%\")) return value;\n return value.replace(/(?:%[0-9A-Fa-f]{2})+/g, (run) => {\n try {\n return decodeURIComponent(run);\n } catch {\n return run;\n }\n });\n}\n\n/** Parse one token (v1 or v2). Returns `null` for an empty token. */\nexport function parseLinkToken(raw: string): LinkToken | null {\n if (!raw) return null;\n const first = raw.indexOf(\":\");\n const key = first === -1 ? raw : raw.slice(0, first);\n if (!key) return null;\n if (first === -1) return { raw, key, id: undefined, args: undefined };\n const rest = raw.slice(first + 1);\n const second = rest.indexOf(\":\");\n const idPart = second === -1 ? rest : rest.slice(0, second);\n const argsPart = second === -1 ? undefined : rest.slice(second + 1);\n let args: Record<string, string> | undefined;\n if (argsPart) {\n args = {};\n for (const pair of argsPart.split(\"_\")) {\n // Split on the FIRST hyphen only: a v1 value may hold hyphens (uuid, date).\n const at = pair.indexOf(\"-\");\n if (at <= 0 || at === pair.length - 1) continue;\n args[decodeLinkPart(pair.slice(0, at))] = decodeLinkPart(pair.slice(at + 1));\n }\n }\n return { raw, key: decodeLinkPart(key), id: decodeLinkPart(idPart), args };\n}\n\n/** Parse a whole `panels` value. Empty segments are skipped. */\nexport function parseLinkParam(param: string | null | undefined): LinkToken[] {\n if (!param) return [];\n const out: LinkToken[] = [];\n for (const raw of param.split(\",\")) {\n const token = parseLinkToken(raw);\n if (token) out.push(token);\n }\n return out;\n}\n\n/** Write one entry as a v2 token. Args are written in key order so equal entries write equal tokens. */\nexport function formatLinkToken(entry: LinkEntry): string {\n let token = `${encodeLinkId(entry.key)}:${encodeLinkId(entry.id)}`;\n const args = entry.args;\n if (args) {\n const keys = Object.keys(args).sort();\n if (keys.length > 0) {\n token += `:${keys.map((k) => `${encodeLinkArg(k)}-${encodeLinkArg(args[k] ?? \"\")}`).join(\"_\")}`;\n }\n }\n return token;\n}\n\n/** The `panels` value from a search string or a full URL (`null` when absent). */\nexport function readPanelsParam(searchOrUrl: string): string | null {\n const q = searchOrUrl.indexOf(\"?\");\n const hash = searchOrUrl.indexOf(\"#\");\n let search: string;\n if (q === -1) {\n // A bare query string without \"?\" (`panels=…`) or a URL with no query.\n search = searchOrUrl.includes(\"=\") && !searchOrUrl.includes(\"/\") ? searchOrUrl : \"\";\n } else {\n search = searchOrUrl.slice(q + 1, hash > q ? hash : undefined);\n }\n return new URLSearchParams(search).get(\"panels\");\n}\n\n/** `search` with `panels` set to `param` (removed when empty). Returns \"\" or \"?…\". */\nexport function withPanelsParam(search: string, param: string): string {\n const params = new URLSearchParams(search.startsWith(\"?\") ? search.slice(1) : search);\n if (param) params.set(\"panels\", param);\n else params.delete(\"panels\");\n const qs = params.toString();\n return qs ? `?${qs}` : \"\";\n}\n","/**\n * OverlayLinkManager — the address bar's `?panels=` channel (successor of matrx-frontend's\n * UrlPanelManager). Mount ONCE inside <SummonProvider>. Driven entirely by `ports.router`.\n *\n * 1. On mount: read `?panels=`, hydrate each token through the one door (`hydrateLinkToken`).\n * 2. After that: write back the union of registered link entries (`useOverlayLink`) plus every\n * pasted token that has not been settled yet, via `router.replaceSearch` — never navigation.\n *\n * THE ADDRESS IS NEVER DESTROYED BY A CLOCK (matrx-frontend V-28 NEW-5). A token whose window has\n * not registered yet is kept verbatim in the address however long its lazy chunk takes. The\n * deadline (12 s prod / 45 s dev) decides one thing only: when to stop waiting silently and tell\n * the person, through `ports.notify` and `ports.errors`.\n *\n * A token is SETTLED when ANY registered entry carries its canonical key — the window a hydrator\n * opens may publish a different id (a vault link names an item, the vault window publishes its\n * singleton id) and may even be a different overlay than the one owning the key (key `agent`\n * opens display-mode shells). An alias resolves to its definition's own `link.key`.\n */\nimport { useEffect, useRef, useState, useSyncExternalStore } from \"react\";\nimport {\n LAZY_WINDOW_MOUNT_DEADLINE_MS,\n getOverlayMetaByLinkKey,\n selectIsUrlHydrated,\n selectUrlSyncEntries,\n setHydrated,\n type UrlSyncEntry,\n} from \"@ai-matrx/summon\";\nimport { useSummonDispatch, useSummonPorts, useSummonSelector } from \"@ai-matrx/summon/react\";\nimport { canonicalLinkKey, canonicalizeToken, hydrateLinkToken } from \"./door\";\nimport { formatLinkToken, parseLinkParam, readPanelsParam, withPanelsParam } from \"./grammar\";\n\nexport interface OverlayLinkManagerProps {\n /**\n * Restrict hydration and write-back to these link keys (a route-scoped manager). Tokens of\n * other keys are preserved verbatim and never hydrated. Omit for the app-wide manager.\n */\n readonly managedKeys?: readonly string[];\n /** Override the notice deadline (ms). Default: 12 s in production, 45 s in development. */\n readonly noticeDeadlineMs?: number;\n}\n\n/** Serialize registered entries as v2 tokens (only managed keys when given). */\nexport function serializeLinkEntries(entries: Readonly<Record<string, UrlSyncEntry>>, managedKeys?: readonly string[]): string {\n const managed = managedKeys ? new Set(managedKeys) : null;\n return Object.values(entries)\n .filter((entry) => !managed || managed.has(entry.typeKey))\n .map((entry) => formatLinkToken({ key: entry.typeKey, id: entry.instanceId, args: entry.args }))\n .join(\",\");\n}\n\n/** Keep the current param's unmanaged tokens; replace the managed ones with `nextManaged`. */\nexport function mergeManagedLinkParam(currentParam: string, nextManaged: string, managedKeys?: readonly string[]): string {\n if (!managedKeys) return nextManaged;\n const managed = new Set(managedKeys);\n const unmanaged = currentParam\n .split(\",\")\n .filter(Boolean)\n .filter((token) => !managed.has(token.split(\":\")[0] ?? \"\"));\n return [...unmanaged, ...nextManaged.split(\",\").filter(Boolean)].join(\",\");\n}\n\n/**\n * Re-attach pasted tokens no window has settled. A token is skipped when its CANONICAL key is\n * already represented by a live entry, so a late registration replaces the placeholder.\n */\nexport function withUnresolvedTokens(managedParam: string, unresolvedTokens: readonly string[]): string {\n if (unresolvedTokens.length === 0) return managedParam;\n const managed = managedParam.split(\",\").filter(Boolean);\n const represented = new Set(managed.map((token) => canonicalLinkKey(token.split(\":\")[0] ?? \"\")));\n const kept = unresolvedTokens.filter((token) => Boolean(token) && !represented.has(canonicalLinkKey(token.split(\":\")[0] ?? \"\")));\n return [...managed, ...kept].join(\",\");\n}\n\n/** The honest sentence for a link naming a window that did not open. */\nexport function unopenedWindowNotice(keys: readonly string[]): string {\n return (\n `This link names a window this build could not open: ${keys.join(\", \")}. ` +\n `The link is unchanged in your address bar — reload to try again, or report it if it keeps failing.`\n );\n}\n\ninterface PendingToken {\n /** What is written back while unsettled (canonical key, pasted id and args). */\n readonly token: string;\n /** The key the person pasted (named in the notice). */\n readonly tokenKey: string;\n readonly canonicalKey: string;\n}\n\nconst noopSubscribe = () => () => {};\n\nexport function OverlayLinkManager({ managedKeys, noticeDeadlineMs }: OverlayLinkManagerProps): null {\n const ports = useSummonPorts();\n const dispatch = useSummonDispatch();\n const entries = useSummonSelector(selectUrlSyncEntries);\n const isHydrated = useSummonSelector(selectIsUrlHydrated);\n const router = ports.router;\n // Re-run the write-back after ANY address change the router reports.\n const search = useSyncExternalStore(router.subscribe ?? noopSubscribe, router.search, () => \"\");\n\n const initialLoadDone = useRef(false);\n // Keyed by canonical key + id so two tokens that canonicalise together never overwrite each\n // other (V-30 NEW-5); the settle sweep still runs on canonical key.\n const unresolved = useRef(new Map<string, PendingToken>());\n const announced = useRef(new Set<string>());\n const [noticeDue, setNoticeDue] = useState(false);\n const [asyncTick, setAsyncTick] = useState(0);\n const deadline = noticeDeadlineMs ?? LAZY_WINDOW_MOUNT_DEADLINE_MS;\n\n const announce = (keys: readonly string[], reason: \"no-hydrator\" | \"never-registered\" | \"hydrator-threw\"): void => {\n const fresh = keys.filter((key) => !announced.current.has(key));\n if (fresh.length === 0) return;\n for (const key of fresh) announced.current.add(key);\n const joined = fresh.join(\", \");\n const why =\n reason === \"no-hydrator\"\n ? `no registered definition owns ${fresh.length > 1 ? \"these keys\" : \"this key\"}`\n : reason === \"hydrator-threw\"\n ? \"its hydrator threw\"\n : `hydrated, but no window registered its link within ${deadline}ms`;\n const message =\n `[overlays/url] ?panels= token(s) [${joined}]: ${why}. The token is KEPT in the address. ` +\n `Give the overlay definition a matching \\`link\\` (key or alias), or stop publishing the link.`;\n ports.errors.capture(new Error(message), {\n source: \"url-panel-unopened\",\n relation: `?panels=${joined}`,\n code: reason,\n typeKeys: fresh,\n deadlineMs: deadline,\n userMessage: unopenedWindowNotice(fresh),\n });\n ports.notify.error(unopenedWindowNotice(fresh), { duration: 12_000 });\n };\n const announceRef = useRef(announce);\n announceRef.current = announce;\n\n // 1. HYDRATION (address → store), once.\n useEffect(() => {\n if (initialLoadDone.current) return;\n initialLoadDone.current = true;\n const managed = managedKeys ? new Set(managedKeys) : null;\n const tokens = parseLinkParam(readPanelsParam(router.search()));\n const noHydrator: string[] = [];\n for (const token of tokens) {\n if (managed && !managed.has(token.key)) continue; // preserved verbatim by the write-back\n const canonicalKey = canonicalLinkKey(token.key);\n const pendingKey = `${canonicalKey}\\u0000${token.id ?? \"\"}`;\n const pending: PendingToken = { token: canonicalizeToken(token.raw), tokenKey: token.key, canonicalKey };\n if (!getOverlayMetaByLinkKey(token.key)?.link) {\n unresolved.current.set(pendingKey, { ...pending, token: token.raw });\n noHydrator.push(token.key);\n continue;\n }\n unresolved.current.set(pendingKey, pending);\n const outcome = hydrateLinkToken(token, dispatch, (error) => {\n ports.errors.capture(error, { source: \"url-panel-hydrator\", token: token.raw });\n });\n const apply = (result: string): void => {\n // A refused link is dropped from the address; it is not \"still to come\".\n if (result === \"refused\" && unresolved.current.get(pendingKey) === pending) {\n unresolved.current.delete(pendingKey);\n setAsyncTick((n) => n + 1);\n }\n };\n if (typeof outcome === \"string\") apply(outcome);\n else void outcome.then(apply);\n }\n if (noHydrator.length > 0) announceRef.current(noHydrator, \"no-hydrator\");\n dispatch(setHydrated());\n }, [dispatch, managedKeys, router, ports]);\n\n // The notice deadline: never gates a write; only turns a silent wait into a visible notice.\n useEffect(() => {\n if (!isHydrated || unresolved.current.size === 0) return undefined;\n const timer = setTimeout(() => setNoticeDue(true), deadline);\n return () => clearTimeout(timer);\n }, [isHydrated, deadline]);\n\n // 2. WRITE-BACK (store → address).\n useEffect(() => {\n if (!isHydrated) return;\n const observed = new Set(Object.values(entries).map((entry) => canonicalLinkKey(entry.typeKey)));\n for (const [key, pending] of [...unresolved.current.entries()]) {\n if (observed.has(pending.canonicalKey)) unresolved.current.delete(key);\n }\n const stillPending = [...unresolved.current.values()];\n if (noticeDue && stillPending.length > 0) {\n announceRef.current(\n stillPending.map((pending) => pending.tokenKey),\n \"never-registered\",\n );\n }\n // The LIVE address, never a snapshot: the page may have written `?view=` a moment ago.\n const liveSearch = router.search();\n const currentParam = readPanelsParam(liveSearch) ?? \"\";\n const nextManaged = withUnresolvedTokens(\n serializeLinkEntries(entries, managedKeys),\n stillPending.map((pending) => pending.token),\n );\n const nextParam = mergeManagedLinkParam(currentParam, nextManaged, managedKeys);\n if (currentParam !== nextParam) router.replaceSearch(withPanelsParam(liveSearch, nextParam));\n }, [entries, isHydrated, managedKeys, router, search, noticeDue, asyncTick]);\n\n return null;\n}\n","/**\n * The ONE door every link source goes through: the address-bar manager on load, a notification\n * that opens its link in place, the extension bridge. A token is resolved through the registry —\n * `getOverlayMetaByLinkKey(key).link.hydrate({ id, args, open, dispatch })` — never a side table.\n */\nimport type { Dispatch, UnknownAction } from \"@reduxjs/toolkit\";\nimport {\n DEFAULT_INSTANCE_ID,\n getOverlayMeta,\n getOverlayMetaByLinkKey,\n isRegisteredOverlay,\n overlaysRawActions,\n type AnyOverlayMeta,\n type OverlayHostDispatch,\n} from \"@ai-matrx/summon\";\nimport { parseLinkParam, readPanelsParam, type LinkToken } from \"./grammar\";\n\n/** Any Redux dispatch that takes plain actions (a host's thunk-aware dispatch qualifies). */\nexport type LinkDispatch = Dispatch<UnknownAction>;\n\n/**\n * Open an overlay by its registry id from inside the package. Uses the raw action so internal\n * code passes plain strings (a host's augmented `OverlayId` union never needs a cast here); the\n * definition's `defaults` are merged underneath exactly as the typed creator does.\n */\nexport function openRegisteredOverlay(\n dispatch: LinkDispatch,\n overlayId: string,\n instanceId: string = DEFAULT_INSTANCE_ID,\n data?: unknown,\n): void {\n const defaults = getOverlayMeta(overlayId)?.defaults;\n dispatch(\n overlaysRawActions.openOverlay({\n overlayId,\n instanceId,\n ...(data === undefined ? {} : { data }),\n ...(isRecord(defaults) ? { defaults } : {}),\n }),\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** The key a token is judged by: the owning definition's own `link.key` (aliases resolve to it). */\nexport function canonicalLinkKey(key: string): string {\n return getOverlayMetaByLinkKey(key)?.link?.key ?? key;\n}\n\n/** The token with its key replaced by the canonical key; id and args carry across untouched. */\nexport function canonicalizeToken(raw: string): string {\n const at = raw.indexOf(\":\");\n const key = at === -1 ? raw : raw.slice(0, at);\n const canonical = canonicalLinkKey(key);\n if (canonical === key) return raw;\n return at === -1 ? canonical : `${canonical}${raw.slice(at)}`;\n}\n\nexport type TokenOutcome = \"opened\" | \"refused\" | \"unknown\" | \"failed\";\n\n/**\n * Run one token's hydrator. Synchronous hydrators dispatch before this returns; an async one\n * yields a promise. A throw (sync or async) is `\"failed\"` and is reported to `onError`.\n */\nexport function hydrateLinkToken(\n token: LinkToken,\n dispatch: LinkDispatch,\n onError?: (error: unknown, token: LinkToken) => void,\n): TokenOutcome | Promise<TokenOutcome> {\n const meta: AnyOverlayMeta | undefined = getOverlayMetaByLinkKey(token.key);\n const link = meta?.link;\n if (!meta || !link) return \"unknown\";\n const ownerId = meta.id;\n const settle = (result: void | \"refused\"): TokenOutcome => (result === \"refused\" ? \"refused\" : \"opened\");\n const fail = (error: unknown): TokenOutcome => {\n onError?.(error, token);\n return \"failed\";\n };\n try {\n const result = link.hydrate({\n id: token.id ?? \"\",\n args: token.args ?? {},\n open: (options) => openRegisteredOverlay(dispatch, ownerId, options?.instanceId ?? DEFAULT_INSTANCE_ID, options?.data),\n // The host's store dispatch IS the host dispatch at runtime; its thunk-aware type is the\n // host's augmentation (`OverlayHostTypes`), which the package cannot see.\n dispatch: dispatch as unknown as OverlayHostDispatch,\n });\n if (result && typeof (result as PromiseLike<unknown>).then === \"function\") {\n return Promise.resolve(result).then(settle, fail);\n }\n return settle(result as void | \"refused\");\n } catch (error) {\n return fail(error);\n }\n}\n\nexport interface OpenFromLinkResult {\n /** Tokens (verbatim) whose hydrator ran and did not refuse. */\n readonly opened: string[];\n /** Tokens whose hydrator declined the link on purpose. */\n readonly refused: string[];\n /** Tokens whose key no registered definition owns. */\n readonly unknown: string[];\n /** Tokens whose hydrator threw. */\n readonly failed: string[];\n}\n\nexport interface OpenFromLinkOptions {\n readonly dispatch: LinkDispatch;\n /**\n * All or nothing: when any token's key is unknown, open NOTHING and report the unknown tokens\n * (a notification then opens the link in a new tab instead of half of it in place).\n */\n readonly requireAll?: boolean;\n readonly onError?: (error: unknown, token: LinkToken) => void;\n}\n\n/**\n * Open every `?panels=` token of a search string or URL through its definition's hydrator.\n * Synchronous hydrators have dispatched by the time this returns; the promise settles once\n * async hydrators finish.\n */\nexport async function openFromLink(searchOrUrl: string, options: OpenFromLinkOptions): Promise<OpenFromLinkResult> {\n const tokens = parseLinkParam(readPanelsParam(searchOrUrl));\n const result: OpenFromLinkResult = { opened: [], refused: [], unknown: [], failed: [] };\n const unknown = tokens.filter((token) => !getOverlayMetaByLinkKey(token.key)?.link);\n if (options.requireAll && unknown.length > 0) {\n result.unknown.push(...unknown.map((token) => token.raw));\n return result;\n }\n const outcomes = tokens.map((token) => hydrateLinkToken(token, options.dispatch, options.onError));\n const settled = await Promise.all(outcomes.map((outcome) => Promise.resolve(outcome)));\n settled.forEach((outcome, index) => {\n const raw = tokens[index]?.raw ?? \"\";\n result[outcome === \"opened\" ? \"opened\" : outcome].push(raw);\n });\n return result;\n}\n\n// ── The extension door ──────────────────────────────────────────────────────────────────────\n\nexport interface OpenPanelRequest {\n readonly panelId: string;\n readonly instanceId?: string;\n readonly data?: Readonly<Record<string, unknown>>;\n}\n\nexport type OpenPanelResult =\n | { readonly ok: true; readonly opened: true; readonly panelId: string; readonly instanceId: string | null }\n | { readonly ok: false; readonly error: \"invalid_payload\" | \"unknown_panel\"; readonly details?: unknown };\n\nfunction readOpenPanelRequest(raw: unknown): OpenPanelRequest | string {\n if (!isRecord(raw)) return \"payload must be an object\";\n const { panelId, instanceId, data } = raw;\n if (typeof panelId !== \"string\" || panelId.length === 0) return \"panelId must be a non-empty string\";\n if (instanceId !== undefined && instanceId !== null && (typeof instanceId !== \"string\" || instanceId.length === 0)) {\n return \"instanceId must be a non-empty string when given\";\n }\n if (data !== undefined && data !== null && !isRecord(data)) return \"data must be an object when given\";\n return {\n panelId,\n ...(typeof instanceId === \"string\" ? { instanceId } : {}),\n ...(isRecord(data) ? { data } : {}),\n };\n}\n\n/**\n * Open a registered overlay by id for an outside caller (the browser extension's `openPanel`\n * envelope). Validates the payload and the id; failures come back as values, never throws, so\n * the bridge can reply verbatim. `instanceId: null` in the reply = the caller used the singleton.\n */\nexport function openPanelRequest(rawPayload: unknown, options: { readonly dispatch: LinkDispatch }): OpenPanelResult {\n const request = readOpenPanelRequest(rawPayload);\n if (typeof request === \"string\") return { ok: false, error: \"invalid_payload\", details: request };\n if (!isRegisteredOverlay(request.panelId)) {\n return {\n ok: false,\n error: \"unknown_panel\",\n details: { panelId: request.panelId, hint: \"panelId must be the id of a registered overlay definition.\" },\n };\n }\n openRegisteredOverlay(options.dispatch, request.panelId, request.instanceId ?? DEFAULT_INSTANCE_ID, request.data);\n return { ok: true, opened: true, panelId: request.panelId, instanceId: request.instanceId ?? null };\n}\n"],"mappings":"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","names":["useEffect","getOverlayMetaByLinkKey","useSummonDispatch","useSummonDispatch","useEffect","getOverlayMetaByLinkKey"]}
1
+ {"version":3,"sources":["../src/url.ts","../src/url/grammar.ts","../src/url/OverlayLinkManager.tsx","../src/url/door.ts"],"sourcesContent":["// @ai-matrx/summon/url — address-bar links: the `?panels=` grammar, the link manager, the\n// `useOverlayLink` publisher and the one door (`openFromLink`) every link source goes through.\nexport { useOverlayLink } from \"@ai-matrx/summon/react\";\nexport {\n parseLinkToken,\n parseLinkParam,\n formatLinkToken,\n encodeLinkId,\n encodeLinkArg,\n decodeLinkPart,\n readPanelsParam,\n withPanelsParam,\n type LinkToken,\n type LinkEntry,\n} from \"./url/grammar\";\nexport {\n OverlayLinkManager,\n serializeLinkEntries,\n mergeManagedLinkParam,\n withUnresolvedTokens,\n unopenedWindowNotice,\n type OverlayLinkManagerProps,\n} from \"./url/OverlayLinkManager\";\nexport {\n openFromLink,\n openPanelRequest,\n hydrateLinkToken,\n canonicalLinkKey,\n canonicalizeToken,\n openRegisteredOverlay,\n type OpenFromLinkResult,\n type OpenFromLinkOptions,\n type OpenPanelRequest,\n type OpenPanelResult,\n type TokenOutcome,\n type LinkDispatch,\n} from \"./url/door\";\n","/**\n * The `?panels=` link grammar.\n *\n * panels = token (\",\" token)*\n * token = key [\":\" id [\":\" args]]\n * args = k \"-\" v (\"_\" k \"-\" v)*\n *\n * GRAMMAR v2 (this file): on WRITE, ids are percent-encoded for `% , :` and arg keys and values\n * for `% , : _ -`, so any value — a uuid, a date, a path, a list — survives the round trip.\n * Keys and ids keep `-` and `_` unencoded: they are not separators inside an id, and leaving them means a\n * uuid id writes byte-identically to v1. On READ, v1 tokens (unencoded) parse unchanged: an arg\n * pair splits on its FIRST hyphen, and decoding only touches well-formed `%XX` escapes.\n *\n * Framework-free; this is the one grammar every door (address bar, notifications, extension)\n * shares.\n */\n\nexport interface LinkToken {\n /** The token exactly as it appeared (the verbatim form written back while unresolved). */\n readonly raw: string;\n readonly key: string;\n /** `undefined` for a bare-key token (`keyword_research`). */\n readonly id: string | undefined;\n /** `undefined` when the token carries no args segment. */\n readonly args: Readonly<Record<string, string>> | undefined;\n}\n\nexport interface LinkEntry {\n readonly key: string;\n readonly id: string;\n readonly args?: Readonly<Record<string, string>> | undefined;\n}\n\nconst hex = (ch: string): string => `%${ch.charCodeAt(0).toString(16).toUpperCase().padStart(2, \"0\")}`;\n\n/** Encode a token id (`% , :`). */\nexport function encodeLinkId(id: string): string {\n return id.replace(/[%,:]/g, hex);\n}\n\n/** Encode an arg key or value (`% , : _ -`). */\nexport function encodeLinkArg(value: string): string {\n return value.replace(/[%,:_-]/g, hex);\n}\n\n/** Decode well-formed `%XX` escapes; anything else (a v1 literal `%`) is kept as written. */\nexport function decodeLinkPart(value: string): string {\n if (!value.includes(\"%\")) return value;\n return value.replace(/(?:%[0-9A-Fa-f]{2})+/g, (run) => {\n try {\n return decodeURIComponent(run);\n } catch {\n return run;\n }\n });\n}\n\n/** Parse one token (v1 or v2). Returns `null` for an empty token. */\nexport function parseLinkToken(raw: string): LinkToken | null {\n if (!raw) return null;\n const first = raw.indexOf(\":\");\n const key = first === -1 ? raw : raw.slice(0, first);\n if (!key) return null;\n if (first === -1) return { raw, key, id: undefined, args: undefined };\n const rest = raw.slice(first + 1);\n const second = rest.indexOf(\":\");\n const idPart = second === -1 ? rest : rest.slice(0, second);\n const argsPart = second === -1 ? undefined : rest.slice(second + 1);\n let args: Record<string, string> | undefined;\n if (argsPart) {\n args = {};\n for (const pair of argsPart.split(\"_\")) {\n // Split on the FIRST hyphen only: a v1 value may hold hyphens (uuid, date).\n const at = pair.indexOf(\"-\");\n if (at <= 0 || at === pair.length - 1) continue;\n args[decodeLinkPart(pair.slice(0, at))] = decodeLinkPart(pair.slice(at + 1));\n }\n }\n return { raw, key: decodeLinkPart(key), id: decodeLinkPart(idPart), args };\n}\n\n/** Parse a whole `panels` value. Empty segments are skipped. */\nexport function parseLinkParam(param: string | null | undefined): LinkToken[] {\n if (!param) return [];\n const out: LinkToken[] = [];\n for (const raw of param.split(\",\")) {\n const token = parseLinkToken(raw);\n if (token) out.push(token);\n }\n return out;\n}\n\n/** Write one entry as a v2 token. Args are written in key order so equal entries write equal tokens. */\nexport function formatLinkToken(entry: LinkEntry): string {\n let token = `${encodeLinkId(entry.key)}:${encodeLinkId(entry.id)}`;\n const args = entry.args;\n if (args) {\n const keys = Object.keys(args).sort();\n if (keys.length > 0) {\n token += `:${keys.map((k) => `${encodeLinkArg(k)}-${encodeLinkArg(args[k] ?? \"\")}`).join(\"_\")}`;\n }\n }\n return token;\n}\n\n/** The `panels` value from a search string or a full URL (`null` when absent). */\nexport function readPanelsParam(searchOrUrl: string): string | null {\n const q = searchOrUrl.indexOf(\"?\");\n const hash = searchOrUrl.indexOf(\"#\");\n let search: string;\n if (q === -1) {\n // A bare query string without \"?\" (`panels=…`) or a URL with no query.\n search = searchOrUrl.includes(\"=\") && !searchOrUrl.includes(\"/\") ? searchOrUrl : \"\";\n } else {\n search = searchOrUrl.slice(q + 1, hash > q ? hash : undefined);\n }\n return new URLSearchParams(search).get(\"panels\");\n}\n\n/** `search` with `panels` set to `param` (removed when empty). Returns \"\" or \"?…\". */\nexport function withPanelsParam(search: string, param: string): string {\n const params = new URLSearchParams(search.startsWith(\"?\") ? search.slice(1) : search);\n if (param) params.set(\"panels\", param);\n else params.delete(\"panels\");\n const qs = params.toString();\n return qs ? `?${qs}` : \"\";\n}\n","/**\n * OverlayLinkManager — the address bar's `?panels=` channel (successor of matrx-frontend's\n * UrlPanelManager). Mount ONCE inside <SummonProvider>. Driven entirely by `ports.router`.\n *\n * 1. On mount: read `?panels=`, hydrate each token through the one door (`hydrateLinkToken`).\n * 2. After that: write back the union of registered link entries (`useOverlayLink`) plus every\n * pasted token that has not been settled yet, via `router.replaceSearch` — never navigation.\n *\n * THE ADDRESS IS NEVER DESTROYED BY A CLOCK (matrx-frontend V-28 NEW-5). A token whose window has\n * not registered yet is kept verbatim in the address however long its lazy chunk takes. The\n * deadline (12 s prod / 45 s dev) decides one thing only: when to stop waiting silently and tell\n * the person, through `ports.notify` and `ports.errors`.\n *\n * A token is SETTLED when ANY registered entry carries its canonical key — the window a hydrator\n * opens may publish a different id (a vault link names an item, the vault window publishes its\n * singleton id) and may even be a different overlay than the one owning the key (key `agent`\n * opens display-mode shells). An alias resolves to its definition's own `link.key`.\n */\nimport { useEffect, useRef, useState, useSyncExternalStore } from \"react\";\nimport {\n LAZY_WINDOW_MOUNT_DEADLINE_MS,\n getOverlayMetaByLinkKey,\n selectIsUrlHydrated,\n selectUrlSyncEntries,\n setHydrated,\n type UrlSyncEntry,\n} from \"@ai-matrx/summon\";\nimport { useSummonDispatch, useSummonPorts, useSummonSelector } from \"@ai-matrx/summon/react\";\nimport { canonicalLinkKey, canonicalizeToken, hydrateLinkToken } from \"./door\";\nimport { formatLinkToken, parseLinkParam, readPanelsParam, withPanelsParam } from \"./grammar\";\n\nexport interface OverlayLinkManagerProps {\n /**\n * Restrict hydration and write-back to these link keys (a route-scoped manager). Tokens of\n * other keys are preserved verbatim and never hydrated. Omit for the app-wide manager.\n */\n readonly managedKeys?: readonly string[];\n /** Override the notice deadline (ms). Default: 12 s in production, 45 s in development. */\n readonly noticeDeadlineMs?: number;\n}\n\n/** Serialize registered entries as v2 tokens (only managed keys when given). */\nexport function serializeLinkEntries(entries: Readonly<Record<string, UrlSyncEntry>>, managedKeys?: readonly string[]): string {\n const managed = managedKeys ? new Set(managedKeys) : null;\n return Object.values(entries)\n .filter((entry) => !managed || managed.has(entry.typeKey))\n .map((entry) => formatLinkToken({ key: entry.typeKey, id: entry.instanceId, args: entry.args }))\n .join(\",\");\n}\n\n/** Keep the current param's unmanaged tokens; replace the managed ones with `nextManaged`. */\nexport function mergeManagedLinkParam(currentParam: string, nextManaged: string, managedKeys?: readonly string[]): string {\n if (!managedKeys) return nextManaged;\n const managed = new Set(managedKeys);\n const unmanaged = currentParam\n .split(\",\")\n .filter(Boolean)\n .filter((token) => !managed.has(token.split(\":\")[0] ?? \"\"));\n return [...unmanaged, ...nextManaged.split(\",\").filter(Boolean)].join(\",\");\n}\n\n/**\n * Re-attach pasted tokens no window has settled. A token is skipped when its CANONICAL key is\n * already represented by a live entry, so a late registration replaces the placeholder.\n */\nexport function withUnresolvedTokens(managedParam: string, unresolvedTokens: readonly string[]): string {\n if (unresolvedTokens.length === 0) return managedParam;\n const managed = managedParam.split(\",\").filter(Boolean);\n const represented = new Set(managed.map((token) => canonicalLinkKey(token.split(\":\")[0] ?? \"\")));\n const kept = unresolvedTokens.filter((token) => Boolean(token) && !represented.has(canonicalLinkKey(token.split(\":\")[0] ?? \"\")));\n return [...managed, ...kept].join(\",\");\n}\n\n/** The honest sentence for a link naming a window that did not open. */\nexport function unopenedWindowNotice(keys: readonly string[]): string {\n return (\n `This link names a window this build could not open: ${keys.join(\", \")}. ` +\n `The link is unchanged in your address bar — reload to try again, or report it if it keeps failing.`\n );\n}\n\ninterface PendingToken {\n /** What is written back while unsettled (canonical key, pasted id and args). */\n readonly token: string;\n /** The key the person pasted (named in the notice). */\n readonly tokenKey: string;\n readonly canonicalKey: string;\n}\n\nconst noopSubscribe = () => () => {};\n\nexport function OverlayLinkManager({ managedKeys, noticeDeadlineMs }: OverlayLinkManagerProps): null {\n const ports = useSummonPorts();\n const dispatch = useSummonDispatch();\n const entries = useSummonSelector(selectUrlSyncEntries);\n const isHydrated = useSummonSelector(selectIsUrlHydrated);\n const router = ports.router;\n // Re-run the write-back after ANY address change the router reports.\n const search = useSyncExternalStore(router.subscribe ?? noopSubscribe, router.search, () => \"\");\n\n const initialLoadDone = useRef(false);\n // Keyed by canonical key + id so two tokens that canonicalise together never overwrite each\n // other (V-30 NEW-5); the settle sweep still runs on canonical key.\n const unresolved = useRef(new Map<string, PendingToken>());\n const announced = useRef(new Set<string>());\n const [noticeDue, setNoticeDue] = useState(false);\n const [asyncTick, setAsyncTick] = useState(0);\n const deadline = noticeDeadlineMs ?? LAZY_WINDOW_MOUNT_DEADLINE_MS;\n\n const announce = (keys: readonly string[], reason: \"no-hydrator\" | \"never-registered\" | \"hydrator-threw\"): void => {\n const fresh = keys.filter((key) => !announced.current.has(key));\n if (fresh.length === 0) return;\n for (const key of fresh) announced.current.add(key);\n const joined = fresh.join(\", \");\n const why =\n reason === \"no-hydrator\"\n ? `no registered definition owns ${fresh.length > 1 ? \"these keys\" : \"this key\"}`\n : reason === \"hydrator-threw\"\n ? \"its hydrator threw\"\n : `hydrated, but no window registered its link within ${deadline}ms`;\n const message =\n `[overlays/url] ?panels= token(s) [${joined}]: ${why}. The token is KEPT in the address. ` +\n `Give the overlay definition a matching \\`link\\` (key or alias), or stop publishing the link.`;\n ports.errors.capture(new Error(message), {\n source: \"url-panel-unopened\",\n relation: `?panels=${joined}`,\n code: reason,\n typeKeys: fresh,\n deadlineMs: deadline,\n userMessage: unopenedWindowNotice(fresh),\n });\n ports.notify.error(unopenedWindowNotice(fresh), { duration: 12_000 });\n };\n const announceRef = useRef(announce);\n announceRef.current = announce;\n\n // 1. HYDRATION (address → store), once.\n useEffect(() => {\n if (initialLoadDone.current) return;\n initialLoadDone.current = true;\n const managed = managedKeys ? new Set(managedKeys) : null;\n const tokens = parseLinkParam(readPanelsParam(router.search()));\n const noHydrator: string[] = [];\n for (const token of tokens) {\n if (managed && !managed.has(token.key)) continue; // preserved verbatim by the write-back\n const canonicalKey = canonicalLinkKey(token.key);\n const pendingKey = `${canonicalKey}\\u0000${token.id ?? \"\"}`;\n const pending: PendingToken = { token: canonicalizeToken(token.raw), tokenKey: token.key, canonicalKey };\n if (!getOverlayMetaByLinkKey(token.key)?.link) {\n unresolved.current.set(pendingKey, { ...pending, token: token.raw });\n noHydrator.push(token.key);\n continue;\n }\n unresolved.current.set(pendingKey, pending);\n const outcome = hydrateLinkToken(token, dispatch, (error) => {\n ports.errors.capture(error, { source: \"url-panel-hydrator\", token: token.raw });\n });\n const apply = (result: string): void => {\n // A refused link is dropped from the address; it is not \"still to come\".\n if (result === \"refused\" && unresolved.current.get(pendingKey) === pending) {\n unresolved.current.delete(pendingKey);\n setAsyncTick((n) => n + 1);\n }\n };\n if (typeof outcome === \"string\") apply(outcome);\n else void outcome.then(apply);\n }\n if (noHydrator.length > 0) announceRef.current(noHydrator, \"no-hydrator\");\n dispatch(setHydrated());\n }, [dispatch, managedKeys, router, ports]);\n\n // The notice deadline: never gates a write; only turns a silent wait into a visible notice.\n useEffect(() => {\n if (!isHydrated || unresolved.current.size === 0) return undefined;\n const timer = setTimeout(() => setNoticeDue(true), deadline);\n return () => clearTimeout(timer);\n }, [isHydrated, deadline]);\n\n // 2. WRITE-BACK (store → address).\n useEffect(() => {\n if (!isHydrated) return;\n const observed = new Set(Object.values(entries).map((entry) => canonicalLinkKey(entry.typeKey)));\n for (const [key, pending] of [...unresolved.current.entries()]) {\n if (observed.has(pending.canonicalKey)) unresolved.current.delete(key);\n }\n const stillPending = [...unresolved.current.values()];\n if (noticeDue && stillPending.length > 0) {\n announceRef.current(\n stillPending.map((pending) => pending.tokenKey),\n \"never-registered\",\n );\n }\n // The LIVE address, never a snapshot: the page may have written `?view=` a moment ago.\n const liveSearch = router.search();\n const currentParam = readPanelsParam(liveSearch) ?? \"\";\n const nextManaged = withUnresolvedTokens(\n serializeLinkEntries(entries, managedKeys),\n stillPending.map((pending) => pending.token),\n );\n const nextParam = mergeManagedLinkParam(currentParam, nextManaged, managedKeys);\n if (currentParam !== nextParam) router.replaceSearch(withPanelsParam(liveSearch, nextParam));\n }, [entries, isHydrated, managedKeys, router, search, noticeDue, asyncTick]);\n\n return null;\n}\n","/**\n * The ONE door every link source goes through: the address-bar manager on load, a notification\n * that opens its link in place, the extension bridge. A token is resolved through the registry —\n * `getOverlayMetaByLinkKey(key).link.hydrate({ id, args, open, dispatch })` — never a side table.\n */\nimport type { Dispatch, UnknownAction } from \"@reduxjs/toolkit\";\nimport {\n DEFAULT_INSTANCE_ID,\n getOverlayMeta,\n getOverlayMetaByLinkKey,\n isRegisteredOverlay,\n overlaysRawActions,\n type AnyOverlayMeta,\n type OverlayHostDispatch,\n} from \"@ai-matrx/summon\";\nimport { parseLinkParam, readPanelsParam, type LinkToken } from \"./grammar\";\n\n/** Any Redux dispatch that takes plain actions (a host's thunk-aware dispatch qualifies). */\nexport type LinkDispatch = Dispatch<UnknownAction>;\n\n/**\n * Open an overlay by its registry id from inside the package. Uses the raw action so internal\n * code passes plain strings (a host's augmented `OverlayId` union never needs a cast here); the\n * definition's `defaults` are merged underneath exactly as the typed creator does.\n */\nexport function openRegisteredOverlay(\n dispatch: LinkDispatch,\n overlayId: string,\n instanceId: string = DEFAULT_INSTANCE_ID,\n data?: unknown,\n): void {\n const defaults = getOverlayMeta(overlayId)?.defaults;\n dispatch(\n overlaysRawActions.openOverlay({\n overlayId,\n instanceId,\n ...(data === undefined ? {} : { data }),\n ...(isRecord(defaults) ? { defaults } : {}),\n }),\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** The key a token is judged by: the owning definition's own `link.key` (aliases resolve to it). */\nexport function canonicalLinkKey(key: string): string {\n return getOverlayMetaByLinkKey(key)?.link?.key ?? key;\n}\n\n/** The token with its key replaced by the canonical key; id and args carry across untouched. */\nexport function canonicalizeToken(raw: string): string {\n const at = raw.indexOf(\":\");\n const key = at === -1 ? raw : raw.slice(0, at);\n const canonical = canonicalLinkKey(key);\n if (canonical === key) return raw;\n return at === -1 ? canonical : `${canonical}${raw.slice(at)}`;\n}\n\nexport type TokenOutcome = \"opened\" | \"refused\" | \"unknown\" | \"failed\";\n\n/**\n * Run one token's hydrator. Synchronous hydrators dispatch before this returns; an async one\n * yields a promise. A throw (sync or async) is `\"failed\"` and is reported to `onError`.\n */\nexport function hydrateLinkToken(\n token: LinkToken,\n dispatch: LinkDispatch,\n onError?: (error: unknown, token: LinkToken) => void,\n): TokenOutcome | Promise<TokenOutcome> {\n const meta: AnyOverlayMeta | undefined = getOverlayMetaByLinkKey(token.key);\n const link = meta?.link;\n if (!meta || !link) return \"unknown\";\n const ownerId = meta.id;\n const settle = (result: void | \"refused\"): TokenOutcome => (result === \"refused\" ? \"refused\" : \"opened\");\n const fail = (error: unknown): TokenOutcome => {\n onError?.(error, token);\n return \"failed\";\n };\n try {\n const result = link.hydrate({\n id: token.id ?? \"\",\n args: token.args ?? {},\n open: (options) => openRegisteredOverlay(dispatch, ownerId, options?.instanceId ?? DEFAULT_INSTANCE_ID, options?.data),\n // The host's store dispatch IS the host dispatch at runtime; its thunk-aware type is the\n // host's augmentation (`OverlayHostTypes`), which the package cannot see.\n dispatch: dispatch as unknown as OverlayHostDispatch,\n });\n if (result && typeof (result as PromiseLike<unknown>).then === \"function\") {\n return Promise.resolve(result).then(settle, fail);\n }\n return settle(result as void | \"refused\");\n } catch (error) {\n return fail(error);\n }\n}\n\nexport interface OpenFromLinkResult {\n /** Tokens (verbatim) whose hydrator ran and did not refuse. */\n readonly opened: string[];\n /** Tokens whose hydrator declined the link on purpose. */\n readonly refused: string[];\n /** Tokens whose key no registered definition owns. */\n readonly unknown: string[];\n /** Tokens whose hydrator threw. */\n readonly failed: string[];\n}\n\nexport interface OpenFromLinkOptions {\n readonly dispatch: LinkDispatch;\n /**\n * All or nothing: when any token's key is unknown, open NOTHING and report the unknown tokens\n * (a notification then opens the link in a new tab instead of half of it in place).\n */\n readonly requireAll?: boolean;\n readonly onError?: (error: unknown, token: LinkToken) => void;\n}\n\n/**\n * Open every `?panels=` token of a search string or URL through its definition's hydrator.\n * Synchronous hydrators have dispatched by the time this returns; the promise settles once\n * async hydrators finish.\n */\nexport async function openFromLink(searchOrUrl: string, options: OpenFromLinkOptions): Promise<OpenFromLinkResult> {\n const tokens = parseLinkParam(readPanelsParam(searchOrUrl));\n const result: OpenFromLinkResult = { opened: [], refused: [], unknown: [], failed: [] };\n const unknown = tokens.filter((token) => !getOverlayMetaByLinkKey(token.key)?.link);\n if (options.requireAll && unknown.length > 0) {\n result.unknown.push(...unknown.map((token) => token.raw));\n return result;\n }\n const outcomes = tokens.map((token) => hydrateLinkToken(token, options.dispatch, options.onError));\n const settled = await Promise.all(outcomes.map((outcome) => Promise.resolve(outcome)));\n settled.forEach((outcome, index) => {\n const raw = tokens[index]?.raw ?? \"\";\n result[outcome === \"opened\" ? \"opened\" : outcome].push(raw);\n });\n return result;\n}\n\n// ── The extension door ──────────────────────────────────────────────────────────────────────\n\nexport interface OpenPanelRequest {\n readonly panelId: string;\n readonly instanceId?: string;\n readonly data?: Readonly<Record<string, unknown>>;\n}\n\nexport type OpenPanelResult =\n | { readonly ok: true; readonly opened: true; readonly panelId: string; readonly instanceId: string | null }\n | { readonly ok: false; readonly error: \"invalid_payload\" | \"unknown_panel\"; readonly details?: unknown };\n\nfunction readOpenPanelRequest(raw: unknown): OpenPanelRequest | string {\n if (!isRecord(raw)) return \"payload must be an object\";\n const { panelId, instanceId, data } = raw;\n if (typeof panelId !== \"string\" || panelId.length === 0) return \"panelId must be a non-empty string\";\n if (instanceId !== undefined && instanceId !== null && (typeof instanceId !== \"string\" || instanceId.length === 0)) {\n return \"instanceId must be a non-empty string when given\";\n }\n if (data !== undefined && data !== null && !isRecord(data)) return \"data must be an object when given\";\n return {\n panelId,\n ...(typeof instanceId === \"string\" ? { instanceId } : {}),\n ...(isRecord(data) ? { data } : {}),\n };\n}\n\n/**\n * Open a registered overlay by id for an outside caller (the browser extension's `openPanel`\n * envelope). Validates the payload and the id; failures come back as values, never throws, so\n * the bridge can reply verbatim. `instanceId: null` in the reply = the caller used the singleton.\n */\nexport function openPanelRequest(rawPayload: unknown, options: { readonly dispatch: LinkDispatch }): OpenPanelResult {\n const request = readOpenPanelRequest(rawPayload);\n if (typeof request === \"string\") return { ok: false, error: \"invalid_payload\", details: request };\n if (!isRegisteredOverlay(request.panelId)) {\n return {\n ok: false,\n error: \"unknown_panel\",\n details: { panelId: request.panelId, hint: \"panelId must be the id of a registered overlay definition.\" },\n };\n }\n openRegisteredOverlay(options.dispatch, request.panelId, request.instanceId ?? DEFAULT_INSTANCE_ID, request.data);\n return { ok: true, opened: true, panelId: request.panelId, instanceId: request.instanceId ?? null };\n}\n"],"mappings":"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","names":["getOverlayMetaByLinkKey","getOverlayMetaByLinkKey"]}
@@ -24,12 +24,16 @@ __export(window_host_exports, {
24
24
  DEFAULT_ESCAPE_YIELD_SELECTOR: () => DEFAULT_ESCAPE_YIELD_SELECTOR,
25
25
  DEFAULT_MOBILE_TOP_OFFSET: () => DEFAULT_MOBILE_TOP_OFFSET,
26
26
  DEFAULT_WINDOW_CONFIG: () => DEFAULT_WINDOW_CONFIG,
27
+ PopoutContext: () => PopoutContext,
27
28
  WindowSelectionSurface: () => WindowSelectionSurface,
28
29
  WindowTraySync: () => WindowTraySync,
29
30
  WindowsProvider: () => WindowsProvider,
30
31
  getTrayPreviewEntry: () => getTrayPreviewEntry,
31
32
  registerTrayPreview: () => registerTrayPreview,
32
33
  resetTrayPreviews: () => resetTrayPreviews,
34
+ useOverlaySurfaceRenderAck: () => useOverlaySurfaceRenderAck,
35
+ usePopout: () => usePopout,
36
+ usePopoutContainer: () => usePopoutContainer,
33
37
  useWindowConfig: () => useWindowConfig
34
38
  });
35
39
  module.exports = __toCommonJS(window_host_exports);
@@ -74,9 +78,18 @@ var import_react3 = require("@ai-matrx/summon/react");
74
78
  var import_summon = require("@ai-matrx/summon");
75
79
  var import_summon2 = require("@ai-matrx/summon");
76
80
  var DEBOUNCE_MS = 500;
81
+ function selectAnyWindowGeometry(state) {
82
+ const wm = state.windowManager;
83
+ if (!wm) return false;
84
+ for (const _ in wm.windows) return true;
85
+ for (const _ in wm.pendingRestores) return true;
86
+ return false;
87
+ }
77
88
  function WindowTraySync() {
78
89
  const dispatch = (0, import_react3.useSummonDispatch)();
90
+ const active = (0, import_react3.useSummonSelector)(selectAnyWindowGeometry);
79
91
  (0, import_react2.useEffect)(() => {
92
+ if (!active) return;
80
93
  let timer = null;
81
94
  const syncWindowGeometry = () => {
82
95
  const { vw, vh, degenerate } = (0, import_summon2.safeViewportDims)();
@@ -99,10 +112,39 @@ function WindowTraySync() {
99
112
  window.removeEventListener("resize", handleResize);
100
113
  if (timer !== null) clearTimeout(timer);
101
114
  };
102
- }, [dispatch]);
115
+ }, [dispatch, active]);
103
116
  return null;
104
117
  }
105
118
 
119
+ // src/window/popout/PopoutContext.tsx
120
+ var import_react4 = require("react");
121
+ var DEFAULT_VALUE = {
122
+ popoutDocument: null,
123
+ popoutWindow: null,
124
+ popoutContainer: null,
125
+ mode: null
126
+ };
127
+ var PopoutContext = (0, import_react4.createContext)(DEFAULT_VALUE);
128
+ function usePopout() {
129
+ return (0, import_react4.useContext)(PopoutContext);
130
+ }
131
+
132
+ // src/window/popout/usePopoutContainer.ts
133
+ function usePopoutContainer() {
134
+ return usePopout().popoutContainer ?? void 0;
135
+ }
136
+
137
+ // src/window/useOverlaySurfaceRenderAck.ts
138
+ var import_react5 = require("react");
139
+ var import_summon3 = require("@ai-matrx/summon");
140
+ function useOverlaySurfaceRenderAck(overlayId, active) {
141
+ (0, import_react5.useEffect)(() => {
142
+ if (!active) return void 0;
143
+ (0, import_summon3.ackOverlaySurfaceRender)(overlayId);
144
+ return () => (0, import_summon3.clearOverlaySurfaceRender)(overlayId);
145
+ }, [active, overlayId]);
146
+ }
147
+
106
148
  // src/window/tray/trayPreviewRegistry.ts
107
149
  var import_global_slot = require("@ai-matrx/kit/global-slot");
108
150
  var EMPTY = {};