@immediately-run/sdk 0.64.1 → 0.66.0

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 (61) hide show
  1. package/dist/boot.cjs +6 -1
  2. package/dist/boot.cjs.map +1 -1
  3. package/dist/boot.js +6 -1
  4. package/dist/boot.js.map +1 -1
  5. package/dist/components/ScrollRestoration.cjs +117 -0
  6. package/dist/components/ScrollRestoration.cjs.map +1 -0
  7. package/dist/components/ScrollRestoration.d.cts +13 -0
  8. package/dist/components/ScrollRestoration.d.ts +13 -0
  9. package/dist/components/ScrollRestoration.js +94 -0
  10. package/dist/components/ScrollRestoration.js.map +1 -0
  11. package/dist/entryState.cjs +99 -0
  12. package/dist/entryState.cjs.map +1 -0
  13. package/dist/entryState.d.cts +42 -0
  14. package/dist/entryState.d.ts +42 -0
  15. package/dist/entryState.js +69 -0
  16. package/dist/entryState.js.map +1 -0
  17. package/dist/index.cjs +12 -1
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +5 -0
  20. package/dist/index.d.ts +5 -0
  21. package/dist/index.js +6 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/openExternal.cjs +41 -0
  24. package/dist/openExternal.cjs.map +1 -0
  25. package/dist/openExternal.d.cts +29 -0
  26. package/dist/openExternal.d.ts +29 -0
  27. package/dist/openExternal.js +18 -0
  28. package/dist/openExternal.js.map +1 -0
  29. package/dist/platformLink.cjs.map +1 -1
  30. package/dist/platformLink.d.cts +6 -2
  31. package/dist/platformLink.d.ts +6 -2
  32. package/dist/platformLink.js.map +1 -1
  33. package/dist/protocolSchemes.cjs +1 -0
  34. package/dist/protocolSchemes.cjs.map +1 -1
  35. package/dist/protocolSchemes.d.cts +1 -0
  36. package/dist/protocolSchemes.d.ts +1 -0
  37. package/dist/protocolSchemes.js +2 -0
  38. package/dist/protocolSchemes.js.map +1 -1
  39. package/dist/routing.cjs +3 -0
  40. package/dist/routing.cjs.map +1 -1
  41. package/dist/routing.js +3 -0
  42. package/dist/routing.js.map +1 -1
  43. package/dist/scrollRestore.cjs +43 -0
  44. package/dist/scrollRestore.cjs.map +1 -0
  45. package/dist/scrollRestore.d.cts +39 -0
  46. package/dist/scrollRestore.d.ts +39 -0
  47. package/dist/scrollRestore.js +18 -0
  48. package/dist/scrollRestore.js.map +1 -0
  49. package/dist/useEntryState.cjs +41 -0
  50. package/dist/useEntryState.cjs.map +1 -0
  51. package/dist/useEntryState.d.cts +28 -0
  52. package/dist/useEntryState.d.ts +28 -0
  53. package/dist/useEntryState.js +17 -0
  54. package/dist/useEntryState.js.map +1 -0
  55. package/dist/version.cjs +1 -1
  56. package/dist/version.cjs.map +1 -1
  57. package/dist/version.d.cts +1 -1
  58. package/dist/version.d.ts +1 -1
  59. package/dist/version.js +1 -1
  60. package/dist/version.js.map +1 -1
  61. package/package.json +3 -3
@@ -10,6 +10,7 @@ import {
10
10
  PROTOCOL_LAUNCH,
11
11
  PROTOCOL_LLM,
12
12
  PROTOCOL_LOCALSTORE,
13
+ PROTOCOL_OPENLINK,
13
14
  PROTOCOL_OPENREPO,
14
15
  PROTOCOL_RECENTS,
15
16
  PROTOCOL_SECRETS,
@@ -32,6 +33,7 @@ const SCHEMES = {
32
33
  [PROTOCOL_LAUNCH]: schemeOf(PROTOCOL_LAUNCH),
33
34
  [PROTOCOL_LLM]: schemeOf(PROTOCOL_LLM),
34
35
  [PROTOCOL_LOCALSTORE]: schemeOf(PROTOCOL_LOCALSTORE),
36
+ [PROTOCOL_OPENLINK]: schemeOf(PROTOCOL_OPENLINK),
35
37
  [PROTOCOL_OPENREPO]: schemeOf(PROTOCOL_OPENREPO),
36
38
  [PROTOCOL_RECENTS]: schemeOf(PROTOCOL_RECENTS),
37
39
  [PROTOCOL_SECRETS]: schemeOf(PROTOCOL_SECRETS),
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/protocolSchemes.ts"],"sourcesContent":["// The `protocol-<scheme>` SCHEMES, derived from the wire names.\n//\n// `protocolRequest(scheme, method, params)` takes the scheme — `'theme'` — while the\n// wire name the frame dispatches on is `'protocol-theme'`; the frame adds the prefix.\n// So the typed service wrappers cannot pass the published `PROTOCOL_*` constant\n// directly, and spelling the scheme inline would put a second, unguarded copy of the\n// name back in the tree — exactly what R3-274c removes.\n//\n// Instead the schemes are *derived* from the wire names, keyed BY the wire name:\n//\n// protocolRequest(SCHEMES[PROTOCOL_THEME], 'set', [{ theme }])\n//\n// Keying by the constant is what makes the derivation unfalsifiable — there is no\n// second place to name the family, so there is no pair to get wrong. `schemeOf`\n// returns a template-literal conditional, so each value has a literal type (`'theme'`),\n// which buys two more things:\n//\n// - a wire name that stops matching `protocol-*` stops compiling here, rather than\n// silently producing an empty scheme at runtime;\n// - `check-protocol-snapshot.mjs` resolves `SCHEMES[PROTOCOL_THEME]` through the type\n// checker exactly like a plain literal, so the call sites stay visible to the gate.\n//\n// One export, deliberately: `./*` is a public subpath, so every name added here is\n// public API forever (ways_of_working §6, additive-only).\n//\n// This module is NOT generated — the derivation is the content — so it lives outside\n// `src/generated/`.\nimport {\n PROTOCOL_ANALYTICS,\n PROTOCOL_CONTRIBUTE,\n PROTOCOL_DND,\n PROTOCOL_EDITOR,\n PROTOCOL_FEED,\n PROTOCOL_FETCH,\n PROTOCOL_IPC,\n PROTOCOL_LAUNCH,\n PROTOCOL_LLM,\n PROTOCOL_LOCALSTORE,\n PROTOCOL_OPENREPO,\n PROTOCOL_RECENTS,\n PROTOCOL_SECRETS,\n PROTOCOL_SETTINGS,\n PROTOCOL_SPACES,\n PROTOCOL_TASK,\n PROTOCOL_THEME,\n PROTOCOL_VCS,\n} from './generated/protocol';\n\nconst PREFIX = 'protocol-';\n\n/** The scheme half of a `protocol-<scheme>` wire name. */\ntype SchemeOf<N extends string> = N extends `${typeof PREFIX}${infer S}` ? S : never;\n\n/**\n * `'protocol-theme'` → `'theme'`, as a literal type.\n *\n * The cast is the only place the derivation is asserted rather than computed; the\n * `N extends \\`protocol-${string}\\`` bound is what makes it sound — a wire name that is\n * not scheme-shaped is a compile error at the call, not a `never` at runtime.\n */\nconst schemeOf = <N extends `${typeof PREFIX}${string}`>(name: N): SchemeOf<N> =>\n name.slice(PREFIX.length) as SchemeOf<N>;\n\n/** Every `protocol-*` scheme the SDK speaks, keyed by its wire name. */\nexport const SCHEMES = {\n [PROTOCOL_ANALYTICS]: schemeOf(PROTOCOL_ANALYTICS),\n [PROTOCOL_CONTRIBUTE]: schemeOf(PROTOCOL_CONTRIBUTE),\n [PROTOCOL_DND]: schemeOf(PROTOCOL_DND),\n [PROTOCOL_EDITOR]: schemeOf(PROTOCOL_EDITOR),\n [PROTOCOL_FEED]: schemeOf(PROTOCOL_FEED),\n [PROTOCOL_FETCH]: schemeOf(PROTOCOL_FETCH),\n [PROTOCOL_IPC]: schemeOf(PROTOCOL_IPC),\n [PROTOCOL_LAUNCH]: schemeOf(PROTOCOL_LAUNCH),\n [PROTOCOL_LLM]: schemeOf(PROTOCOL_LLM),\n [PROTOCOL_LOCALSTORE]: schemeOf(PROTOCOL_LOCALSTORE),\n [PROTOCOL_OPENREPO]: schemeOf(PROTOCOL_OPENREPO),\n [PROTOCOL_RECENTS]: schemeOf(PROTOCOL_RECENTS),\n [PROTOCOL_SECRETS]: schemeOf(PROTOCOL_SECRETS),\n [PROTOCOL_SETTINGS]: schemeOf(PROTOCOL_SETTINGS),\n [PROTOCOL_SPACES]: schemeOf(PROTOCOL_SPACES),\n [PROTOCOL_TASK]: schemeOf(PROTOCOL_TASK),\n [PROTOCOL_THEME]: schemeOf(PROTOCOL_THEME),\n [PROTOCOL_VCS]: schemeOf(PROTOCOL_VCS),\n} as const;\n"],"mappings":";AA2BA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,SAAS;AAYf,MAAM,WAAW,CAAwC,SACvD,KAAK,MAAM,OAAO,MAAM;AAGnB,MAAM,UAAU;AAAA,EACrB,CAAC,kBAAkB,GAAG,SAAS,kBAAkB;AAAA,EACjD,CAAC,mBAAmB,GAAG,SAAS,mBAAmB;AAAA,EACnD,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,aAAa,GAAG,SAAS,aAAa;AAAA,EACvC,CAAC,cAAc,GAAG,SAAS,cAAc;AAAA,EACzC,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,mBAAmB,GAAG,SAAS,mBAAmB;AAAA,EACnD,CAAC,iBAAiB,GAAG,SAAS,iBAAiB;AAAA,EAC/C,CAAC,gBAAgB,GAAG,SAAS,gBAAgB;AAAA,EAC7C,CAAC,gBAAgB,GAAG,SAAS,gBAAgB;AAAA,EAC7C,CAAC,iBAAiB,GAAG,SAAS,iBAAiB;AAAA,EAC/C,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,aAAa,GAAG,SAAS,aAAa;AAAA,EACvC,CAAC,cAAc,GAAG,SAAS,cAAc;AAAA,EACzC,CAAC,YAAY,GAAG,SAAS,YAAY;AACvC;","names":[]}
1
+ {"version":3,"sources":["../src/protocolSchemes.ts"],"sourcesContent":["// The `protocol-<scheme>` SCHEMES, derived from the wire names.\n//\n// `protocolRequest(scheme, method, params)` takes the scheme — `'theme'` — while the\n// wire name the frame dispatches on is `'protocol-theme'`; the frame adds the prefix.\n// So the typed service wrappers cannot pass the published `PROTOCOL_*` constant\n// directly, and spelling the scheme inline would put a second, unguarded copy of the\n// name back in the tree — exactly what R3-274c removes.\n//\n// Instead the schemes are *derived* from the wire names, keyed BY the wire name:\n//\n// protocolRequest(SCHEMES[PROTOCOL_THEME], 'set', [{ theme }])\n//\n// Keying by the constant is what makes the derivation unfalsifiable — there is no\n// second place to name the family, so there is no pair to get wrong. `schemeOf`\n// returns a template-literal conditional, so each value has a literal type (`'theme'`),\n// which buys two more things:\n//\n// - a wire name that stops matching `protocol-*` stops compiling here, rather than\n// silently producing an empty scheme at runtime;\n// - `check-protocol-snapshot.mjs` resolves `SCHEMES[PROTOCOL_THEME]` through the type\n// checker exactly like a plain literal, so the call sites stay visible to the gate.\n//\n// One export, deliberately: `./*` is a public subpath, so every name added here is\n// public API forever (ways_of_working §6, additive-only).\n//\n// This module is NOT generated — the derivation is the content — so it lives outside\n// `src/generated/`.\nimport {\n PROTOCOL_ANALYTICS,\n PROTOCOL_CONTRIBUTE,\n PROTOCOL_DND,\n PROTOCOL_EDITOR,\n PROTOCOL_FEED,\n PROTOCOL_FETCH,\n PROTOCOL_IPC,\n PROTOCOL_LAUNCH,\n PROTOCOL_LLM,\n PROTOCOL_LOCALSTORE,\n PROTOCOL_OPENLINK,\n PROTOCOL_OPENREPO,\n PROTOCOL_RECENTS,\n PROTOCOL_SECRETS,\n PROTOCOL_SETTINGS,\n PROTOCOL_SPACES,\n PROTOCOL_TASK,\n PROTOCOL_THEME,\n PROTOCOL_VCS,\n} from './generated/protocol';\n\nconst PREFIX = 'protocol-';\n\n/** The scheme half of a `protocol-<scheme>` wire name. */\ntype SchemeOf<N extends string> = N extends `${typeof PREFIX}${infer S}` ? S : never;\n\n/**\n * `'protocol-theme'` → `'theme'`, as a literal type.\n *\n * The cast is the only place the derivation is asserted rather than computed; the\n * `N extends \\`protocol-${string}\\`` bound is what makes it sound — a wire name that is\n * not scheme-shaped is a compile error at the call, not a `never` at runtime.\n */\nconst schemeOf = <N extends `${typeof PREFIX}${string}`>(name: N): SchemeOf<N> =>\n name.slice(PREFIX.length) as SchemeOf<N>;\n\n/** Every `protocol-*` scheme the SDK speaks, keyed by its wire name. */\nexport const SCHEMES = {\n [PROTOCOL_ANALYTICS]: schemeOf(PROTOCOL_ANALYTICS),\n [PROTOCOL_CONTRIBUTE]: schemeOf(PROTOCOL_CONTRIBUTE),\n [PROTOCOL_DND]: schemeOf(PROTOCOL_DND),\n [PROTOCOL_EDITOR]: schemeOf(PROTOCOL_EDITOR),\n [PROTOCOL_FEED]: schemeOf(PROTOCOL_FEED),\n [PROTOCOL_FETCH]: schemeOf(PROTOCOL_FETCH),\n [PROTOCOL_IPC]: schemeOf(PROTOCOL_IPC),\n [PROTOCOL_LAUNCH]: schemeOf(PROTOCOL_LAUNCH),\n [PROTOCOL_LLM]: schemeOf(PROTOCOL_LLM),\n [PROTOCOL_LOCALSTORE]: schemeOf(PROTOCOL_LOCALSTORE),\n [PROTOCOL_OPENLINK]: schemeOf(PROTOCOL_OPENLINK),\n [PROTOCOL_OPENREPO]: schemeOf(PROTOCOL_OPENREPO),\n [PROTOCOL_RECENTS]: schemeOf(PROTOCOL_RECENTS),\n [PROTOCOL_SECRETS]: schemeOf(PROTOCOL_SECRETS),\n [PROTOCOL_SETTINGS]: schemeOf(PROTOCOL_SETTINGS),\n [PROTOCOL_SPACES]: schemeOf(PROTOCOL_SPACES),\n [PROTOCOL_TASK]: schemeOf(PROTOCOL_TASK),\n [PROTOCOL_THEME]: schemeOf(PROTOCOL_THEME),\n [PROTOCOL_VCS]: schemeOf(PROTOCOL_VCS),\n} as const;\n"],"mappings":";AA2BA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,SAAS;AAYf,MAAM,WAAW,CAAwC,SACvD,KAAK,MAAM,OAAO,MAAM;AAGnB,MAAM,UAAU;AAAA,EACrB,CAAC,kBAAkB,GAAG,SAAS,kBAAkB;AAAA,EACjD,CAAC,mBAAmB,GAAG,SAAS,mBAAmB;AAAA,EACnD,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,aAAa,GAAG,SAAS,aAAa;AAAA,EACvC,CAAC,cAAc,GAAG,SAAS,cAAc;AAAA,EACzC,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,mBAAmB,GAAG,SAAS,mBAAmB;AAAA,EACnD,CAAC,iBAAiB,GAAG,SAAS,iBAAiB;AAAA,EAC/C,CAAC,iBAAiB,GAAG,SAAS,iBAAiB;AAAA,EAC/C,CAAC,gBAAgB,GAAG,SAAS,gBAAgB;AAAA,EAC7C,CAAC,gBAAgB,GAAG,SAAS,gBAAgB;AAAA,EAC7C,CAAC,iBAAiB,GAAG,SAAS,iBAAiB;AAAA,EAC/C,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,aAAa,GAAG,SAAS,aAAa;AAAA,EACvC,CAAC,cAAc,GAAG,SAAS,cAAc;AAAA,EACzC,CAAC,YAAY,GAAG,SAAS,YAAY;AACvC;","names":[]}
package/dist/routing.cjs CHANGED
@@ -35,6 +35,7 @@ var import_TinkerableContext = require("./TinkerableContext");
35
35
  var import_routeMatch = require("./routeMatch");
36
36
  var import_urlUtils = require("./urlUtils");
37
37
  var import_pathUtils = require("./pathUtils");
38
+ var import_entryState = require("./entryState");
38
39
  var import_protocol = require("./generated/protocol");
39
40
  const useTinkerableLink = (newSandboxLocation) => {
40
41
  const { outerHref, navigationState: navigation } = (0, import_react.use)(import_TinkerableContext.TinkerableContext);
@@ -104,10 +105,12 @@ const navigate = (target, opts) => {
104
105
  } catch {
105
106
  }
106
107
  }
108
+ const entryState = (0, import_entryState.takeQueuedEntryState)();
107
109
  (0, import_sandboxUtils.sendMessage)(import_protocol.URLCHANGE, {
108
110
  url: target,
109
111
  back: false,
110
112
  forward: false,
113
+ ...entryState ? { entryState } : {},
111
114
  ...declared
112
115
  });
113
116
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/routing.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { use, useContext } from 'react';\n\nimport { sendMessage } from './sandboxUtils';\nimport { NavigationState, TinkerableContext } from './TinkerableContext';\nimport { RouteParams, RoutingRule, RoutingSpec } from './RoutingSpec';\nimport { matchRoute } from './routeMatch';\nimport { constructUrl, isAbsolutePath, parseTarget } from './urlUtils';\nimport { joinPaths } from './pathUtils';\nimport { URLCHANGE } from './generated/protocol';\n\n/** The result of matching a path: the winning {@link RoutingRule} plus its captured params. */\nexport type AppliedRoutingRule = {\n routingRule: RoutingRule;\n pathParameters?: Record<string, string>;\n};\n\n/** Build the full outer href for an in-app target (absolute `sandboxPath` or a\n * path relative to the current route), e.g. for an `href` attribute. */\nexport const useTinkerableLink = (newSandboxLocation: string) => {\n const { outerHref, navigationState: navigation } = use(TinkerableContext);\n let newNavigationState = parseTarget(newSandboxLocation, navigation);\n if (!isAbsolutePath(newSandboxLocation)) {\n newNavigationState.sandboxPath = joinPaths(navigation.sandboxPath, newSandboxLocation);\n } else {\n newNavigationState.sandboxPath = newSandboxLocation;\n }\n return constructUrl(outerHref, newNavigationState);\n};\n\n/** Find the first rule in `routingSpec` whose pattern matches the current\n * `sandboxPath`, returning it with the captured params (or `undefined`). */\nexport const applyRoutingRule = (\n routingSpec: RoutingSpec,\n navigationState: NavigationState,\n): AppliedRoutingRule | undefined => {\n const { sandboxPath } = navigationState;\n for (const routingRule of routingSpec.routes) {\n const pathParameters = matchRoute(routingRule.pattern, sandboxPath);\n if (pathParameters) {\n return { routingRule, pathParameters };\n }\n }\n return undefined;\n};\n\n/** Render a matched rule, passing params to a `component` and falling back to `element`/`reactNode`. */\nexport const renderRoute = (routingRule: RoutingRule, params: RouteParams): ReactNode => {\n if (routingRule.component) {\n const Component = routingRule.component;\n return <Component params={params} />;\n }\n return routingRule.element ?? routingRule.reactNode ?? null;\n};\n\n/** Render the route matched for the current location (set up by `boot`'s route table). */\nexport const Router = () => {\n const context = useContext(TinkerableContext);\n const {\n navigationState: { routingRule, pathParameters },\n } = context;\n if (!routingRule) {\n // TODO: better error\n throw new Error(`No route registered for path ${context.navigationState.sandboxPath}!`);\n }\n\n return renderRoute(routingRule, pathParameters ?? {});\n};\n\n/** Read the current route's matched params (`:name` segments and the `*` wildcard). */\nexport const useRouteParams = <T extends RouteParams = RouteParams>(): T =>\n (use(TinkerableContext).navigationState.pathParameters ?? {}) as T;\n\n/**\n * Read the current route: the matched rule's `name`, its `params`, the app-owned\n * `sandboxPath`, and the read-only platform prefix fields (`mode`, `provider`,\n * `namespace`, `repository`, `ref`) — e.g. to tell `/edit` from `/present`.\n */\nexport const useRoute = () => {\n const { navigationState } = use(TinkerableContext);\n const { routingRule, pathParameters, sandboxPath, mode, provider, namespace, repository, ref } = navigationState;\n return {\n name: routingRule?.name,\n params: (pathParameters ?? {}) as RouteParams,\n sandboxPath,\n mode,\n provider,\n namespace,\n repository,\n ref,\n };\n};\n\n/**\n * Navigate within the app. Messages the host to update the URL; the host then\n * pushes the new href back, which drives the actual route change.\n *\n * `opts.viewedDocument` (R3-268) optionally declares which WORKING-TREE file\n * this destination renders — a tri-state rider on the navigation event:\n * - omit the option entirely → the host derives the hint from the URL's\n * `files/` suffix convention (the zero-SDK default);\n * - `null` → this view shows no file (clears the highlight — tag pages,\n * search, home views);\n * - a repo-relative path (the CORPUS path under dispatch — only the viewer\n * can map its own key space) → the file explorer highlights it.\n * The hint is highlight-only by contract (it never scrolls, never moves focus\n * or panes, never switches the editor) and is validated host-side for\n * existence — a wrong path degrades to \"no highlight\", never an error. The\n * host remembers declarations per URL, so back/forward reproduces them\n * without re-announcement.\n */\n// R3-268: an app-registered rule mapping a navigation TARGET to its viewed\n// document, consulted by `navigate()` whenever the caller did not declare one\n// explicitly. Registered ONCE (e.g. at boot) so an app whose links all flow\n// through `<Link>`/`navigate` gets correct declarations everywhere without\n// threading an option through every call site. Return `undefined` for \"no\n// declaration\" (the host falls back to the URL convention), `null` for \"this\n// view shows no file\", or a working-tree repo-relative path.\nlet viewedDocumentResolver: ((targetHref: string) => string | null | undefined) | null = null;\n\n/** Register the app's route→viewed-document rule (R3-268); pass `null` to clear. */\nexport const setViewedDocumentResolver = (\n resolver: ((targetHref: string) => string | null | undefined) | null,\n): void => {\n viewedDocumentResolver = resolver;\n};\n\nexport const navigate = (target: string, opts?: { viewedDocument?: string | null }) => {\n console.log(`[Sandbox] Navigating to ${target}`);\n // Explicit option first; else the registered resolver; else nothing on the\n // wire (the host derives from the URL convention). A resolver throw is\n // swallowed to \"no declaration\" — a mapping bug must never break navigation.\n let declared: { viewedDocument: string | null } | Record<string, never> = {};\n if (opts && 'viewedDocument' in opts) {\n declared = { viewedDocument: opts.viewedDocument ?? null };\n } else if (viewedDocumentResolver) {\n try {\n const v = viewedDocumentResolver(target);\n if (v !== undefined) declared = { viewedDocument: v };\n } catch {\n /* no declaration */\n }\n }\n sendMessage(URLCHANGE, {\n url: target,\n back: false,\n forward: false,\n ...declared,\n });\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkDW;AAjDX,mBAAgC;AAEhC,0BAA4B;AAC5B,+BAAmD;AAEnD,wBAA2B;AAC3B,sBAA0D;AAC1D,uBAA0B;AAC1B,sBAA0B;AAUnB,MAAM,oBAAoB,CAAC,uBAA+B;AAC/D,QAAM,EAAE,WAAW,iBAAiB,WAAW,QAAI,kBAAI,0CAAiB;AACxE,MAAI,yBAAqB,6BAAY,oBAAoB,UAAU;AACnE,MAAI,KAAC,gCAAe,kBAAkB,GAAG;AACvC,uBAAmB,kBAAc,4BAAU,WAAW,aAAa,kBAAkB;AAAA,EACvF,OAAO;AACL,uBAAmB,cAAc;AAAA,EACnC;AACA,aAAO,8BAAa,WAAW,kBAAkB;AACnD;AAIO,MAAM,mBAAmB,CAC9B,aACA,oBACmC;AACnC,QAAM,EAAE,YAAY,IAAI;AACxB,aAAW,eAAe,YAAY,QAAQ;AAC5C,UAAM,qBAAiB,8BAAW,YAAY,SAAS,WAAW;AAClE,QAAI,gBAAgB;AAClB,aAAO,EAAE,aAAa,eAAe;AAAA,IACvC;AAAA,EACF;AACA,SAAO;AACT;AAGO,MAAM,cAAc,CAAC,aAA0B,WAAmC;AACvF,MAAI,YAAY,WAAW;AACzB,UAAM,YAAY,YAAY;AAC9B,WAAO,4CAAC,aAAU,QAAgB;AAAA,EACpC;AACA,SAAO,YAAY,WAAW,YAAY,aAAa;AACzD;AAGO,MAAM,SAAS,MAAM;AAC1B,QAAM,cAAU,yBAAW,0CAAiB;AAC5C,QAAM;AAAA,IACJ,iBAAiB,EAAE,aAAa,eAAe;AAAA,EACjD,IAAI;AACJ,MAAI,CAAC,aAAa;AAEhB,UAAM,IAAI,MAAM,gCAAgC,QAAQ,gBAAgB,WAAW,GAAG;AAAA,EACxF;AAEA,SAAO,YAAY,aAAa,kBAAkB,CAAC,CAAC;AACtD;AAGO,MAAM,iBAAiB,UAC3B,kBAAI,0CAAiB,EAAE,gBAAgB,kBAAkB,CAAC;AAOtD,MAAM,WAAW,MAAM;AAC5B,QAAM,EAAE,gBAAgB,QAAI,kBAAI,0CAAiB;AACjD,QAAM,EAAE,aAAa,gBAAgB,aAAa,MAAM,UAAU,WAAW,YAAY,IAAI,IAAI;AACjG,SAAO;AAAA,IACL,MAAM,aAAa;AAAA,IACnB,QAAS,kBAAkB,CAAC;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AA2BA,IAAI,yBAAqF;AAGlF,MAAM,4BAA4B,CACvC,aACS;AACT,2BAAyB;AAC3B;AAEO,MAAM,WAAW,CAAC,QAAgB,SAA8C;AACrF,UAAQ,IAAI,2BAA2B,MAAM,EAAE;AAI/C,MAAI,WAAsE,CAAC;AAC3E,MAAI,QAAQ,oBAAoB,MAAM;AACpC,eAAW,EAAE,gBAAgB,KAAK,kBAAkB,KAAK;AAAA,EAC3D,WAAW,wBAAwB;AACjC,QAAI;AACF,YAAM,IAAI,uBAAuB,MAAM;AACvC,UAAI,MAAM,OAAW,YAAW,EAAE,gBAAgB,EAAE;AAAA,IACtD,QAAQ;AAAA,IAER;AAAA,EACF;AACA,uCAAY,2BAAW;AAAA,IACrB,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,GAAG;AAAA,EACL,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../src/routing.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { use, useContext } from 'react';\n\nimport { sendMessage } from './sandboxUtils';\nimport { NavigationState, TinkerableContext } from './TinkerableContext';\nimport { RouteParams, RoutingRule, RoutingSpec } from './RoutingSpec';\nimport { matchRoute } from './routeMatch';\nimport { constructUrl, isAbsolutePath, parseTarget } from './urlUtils';\nimport { joinPaths } from './pathUtils';\nimport { takeQueuedEntryState } from './entryState';\nimport { URLCHANGE } from './generated/protocol';\n\n/** The result of matching a path: the winning {@link RoutingRule} plus its captured params. */\nexport type AppliedRoutingRule = {\n routingRule: RoutingRule;\n pathParameters?: Record<string, string>;\n};\n\n/** Build the full outer href for an in-app target (absolute `sandboxPath` or a\n * path relative to the current route), e.g. for an `href` attribute. */\nexport const useTinkerableLink = (newSandboxLocation: string) => {\n const { outerHref, navigationState: navigation } = use(TinkerableContext);\n let newNavigationState = parseTarget(newSandboxLocation, navigation);\n if (!isAbsolutePath(newSandboxLocation)) {\n newNavigationState.sandboxPath = joinPaths(navigation.sandboxPath, newSandboxLocation);\n } else {\n newNavigationState.sandboxPath = newSandboxLocation;\n }\n return constructUrl(outerHref, newNavigationState);\n};\n\n/** Find the first rule in `routingSpec` whose pattern matches the current\n * `sandboxPath`, returning it with the captured params (or `undefined`). */\nexport const applyRoutingRule = (\n routingSpec: RoutingSpec,\n navigationState: NavigationState,\n): AppliedRoutingRule | undefined => {\n const { sandboxPath } = navigationState;\n for (const routingRule of routingSpec.routes) {\n const pathParameters = matchRoute(routingRule.pattern, sandboxPath);\n if (pathParameters) {\n return { routingRule, pathParameters };\n }\n }\n return undefined;\n};\n\n/** Render a matched rule, passing params to a `component` and falling back to `element`/`reactNode`. */\nexport const renderRoute = (routingRule: RoutingRule, params: RouteParams): ReactNode => {\n if (routingRule.component) {\n const Component = routingRule.component;\n return <Component params={params} />;\n }\n return routingRule.element ?? routingRule.reactNode ?? null;\n};\n\n/** Render the route matched for the current location (set up by `boot`'s route table). */\nexport const Router = () => {\n const context = useContext(TinkerableContext);\n const {\n navigationState: { routingRule, pathParameters },\n } = context;\n if (!routingRule) {\n // TODO: better error\n throw new Error(`No route registered for path ${context.navigationState.sandboxPath}!`);\n }\n\n return renderRoute(routingRule, pathParameters ?? {});\n};\n\n/** Read the current route's matched params (`:name` segments and the `*` wildcard). */\nexport const useRouteParams = <T extends RouteParams = RouteParams>(): T =>\n (use(TinkerableContext).navigationState.pathParameters ?? {}) as T;\n\n/**\n * Read the current route: the matched rule's `name`, its `params`, the app-owned\n * `sandboxPath`, and the read-only platform prefix fields (`mode`, `provider`,\n * `namespace`, `repository`, `ref`) — e.g. to tell `/edit` from `/present`.\n */\nexport const useRoute = () => {\n const { navigationState } = use(TinkerableContext);\n const { routingRule, pathParameters, sandboxPath, mode, provider, namespace, repository, ref } = navigationState;\n return {\n name: routingRule?.name,\n params: (pathParameters ?? {}) as RouteParams,\n sandboxPath,\n mode,\n provider,\n namespace,\n repository,\n ref,\n };\n};\n\n/**\n * Navigate within the app. Messages the host to update the URL; the host then\n * pushes the new href back, which drives the actual route change.\n *\n * `opts.viewedDocument` (R3-268) optionally declares which WORKING-TREE file\n * this destination renders — a tri-state rider on the navigation event:\n * - omit the option entirely → the host derives the hint from the URL's\n * `files/` suffix convention (the zero-SDK default);\n * - `null` → this view shows no file (clears the highlight — tag pages,\n * search, home views);\n * - a repo-relative path (the CORPUS path under dispatch — only the viewer\n * can map its own key space) → the file explorer highlights it.\n * The hint is highlight-only by contract (it never scrolls, never moves focus\n * or panes, never switches the editor) and is validated host-side for\n * existence — a wrong path degrades to \"no highlight\", never an error. The\n * host remembers declarations per URL, so back/forward reproduces them\n * without re-announcement.\n */\n// R3-268: an app-registered rule mapping a navigation TARGET to its viewed\n// document, consulted by `navigate()` whenever the caller did not declare one\n// explicitly. Registered ONCE (e.g. at boot) so an app whose links all flow\n// through `<Link>`/`navigate` gets correct declarations everywhere without\n// threading an option through every call site. Return `undefined` for \"no\n// declaration\" (the host falls back to the URL convention), `null` for \"this\n// view shows no file\", or a working-tree repo-relative path.\nlet viewedDocumentResolver: ((targetHref: string) => string | null | undefined) | null = null;\n\n/** Register the app's route→viewed-document rule (R3-268); pass `null` to clear. */\nexport const setViewedDocumentResolver = (\n resolver: ((targetHref: string) => string | null | undefined) | null,\n): void => {\n viewedDocumentResolver = resolver;\n};\n\nexport const navigate = (target: string, opts?: { viewedDocument?: string | null }) => {\n console.log(`[Sandbox] Navigating to ${target}`);\n // Explicit option first; else the registered resolver; else nothing on the\n // wire (the host derives from the URL convention). A resolver throw is\n // swallowed to \"no declaration\" — a mapping bug must never break navigation.\n let declared: { viewedDocument: string | null } | Record<string, never> = {};\n if (opts && 'viewedDocument' in opts) {\n declared = { viewedDocument: opts.viewedDocument ?? null };\n } else if (viewedDocumentResolver) {\n try {\n const v = viewedDocumentResolver(target);\n if (v !== undefined) declared = { viewedDocument: v };\n } catch {\n /* no declaration */\n }\n }\n // The scratch for the entry we are LEAVING, gathered synchronously here because\n // this call is the one moment the app knows a navigation is happening (R3-627).\n // The host stamps it on the current entry before pushing the target, and hands it\n // back if the reader ever returns; it never parses it.\n const entryState = takeQueuedEntryState();\n sendMessage(URLCHANGE, {\n url: target,\n back: false,\n forward: false,\n ...(entryState ? { entryState } : {}),\n ...declared,\n });\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmDW;AAlDX,mBAAgC;AAEhC,0BAA4B;AAC5B,+BAAmD;AAEnD,wBAA2B;AAC3B,sBAA0D;AAC1D,uBAA0B;AAC1B,wBAAqC;AACrC,sBAA0B;AAUnB,MAAM,oBAAoB,CAAC,uBAA+B;AAC/D,QAAM,EAAE,WAAW,iBAAiB,WAAW,QAAI,kBAAI,0CAAiB;AACxE,MAAI,yBAAqB,6BAAY,oBAAoB,UAAU;AACnE,MAAI,KAAC,gCAAe,kBAAkB,GAAG;AACvC,uBAAmB,kBAAc,4BAAU,WAAW,aAAa,kBAAkB;AAAA,EACvF,OAAO;AACL,uBAAmB,cAAc;AAAA,EACnC;AACA,aAAO,8BAAa,WAAW,kBAAkB;AACnD;AAIO,MAAM,mBAAmB,CAC9B,aACA,oBACmC;AACnC,QAAM,EAAE,YAAY,IAAI;AACxB,aAAW,eAAe,YAAY,QAAQ;AAC5C,UAAM,qBAAiB,8BAAW,YAAY,SAAS,WAAW;AAClE,QAAI,gBAAgB;AAClB,aAAO,EAAE,aAAa,eAAe;AAAA,IACvC;AAAA,EACF;AACA,SAAO;AACT;AAGO,MAAM,cAAc,CAAC,aAA0B,WAAmC;AACvF,MAAI,YAAY,WAAW;AACzB,UAAM,YAAY,YAAY;AAC9B,WAAO,4CAAC,aAAU,QAAgB;AAAA,EACpC;AACA,SAAO,YAAY,WAAW,YAAY,aAAa;AACzD;AAGO,MAAM,SAAS,MAAM;AAC1B,QAAM,cAAU,yBAAW,0CAAiB;AAC5C,QAAM;AAAA,IACJ,iBAAiB,EAAE,aAAa,eAAe;AAAA,EACjD,IAAI;AACJ,MAAI,CAAC,aAAa;AAEhB,UAAM,IAAI,MAAM,gCAAgC,QAAQ,gBAAgB,WAAW,GAAG;AAAA,EACxF;AAEA,SAAO,YAAY,aAAa,kBAAkB,CAAC,CAAC;AACtD;AAGO,MAAM,iBAAiB,UAC3B,kBAAI,0CAAiB,EAAE,gBAAgB,kBAAkB,CAAC;AAOtD,MAAM,WAAW,MAAM;AAC5B,QAAM,EAAE,gBAAgB,QAAI,kBAAI,0CAAiB;AACjD,QAAM,EAAE,aAAa,gBAAgB,aAAa,MAAM,UAAU,WAAW,YAAY,IAAI,IAAI;AACjG,SAAO;AAAA,IACL,MAAM,aAAa;AAAA,IACnB,QAAS,kBAAkB,CAAC;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AA2BA,IAAI,yBAAqF;AAGlF,MAAM,4BAA4B,CACvC,aACS;AACT,2BAAyB;AAC3B;AAEO,MAAM,WAAW,CAAC,QAAgB,SAA8C;AACrF,UAAQ,IAAI,2BAA2B,MAAM,EAAE;AAI/C,MAAI,WAAsE,CAAC;AAC3E,MAAI,QAAQ,oBAAoB,MAAM;AACpC,eAAW,EAAE,gBAAgB,KAAK,kBAAkB,KAAK;AAAA,EAC3D,WAAW,wBAAwB;AACjC,QAAI;AACF,YAAM,IAAI,uBAAuB,MAAM;AACvC,UAAI,MAAM,OAAW,YAAW,EAAE,gBAAgB,EAAE;AAAA,IACtD,QAAQ;AAAA,IAER;AAAA,EACF;AAKA,QAAM,iBAAa,wCAAqB;AACxC,uCAAY,2BAAW;AAAA,IACrB,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,IACnC,GAAG;AAAA,EACL,CAAC;AACH;","names":[]}
package/dist/routing.js CHANGED
@@ -6,6 +6,7 @@ import { TinkerableContext } from "./TinkerableContext";
6
6
  import { matchRoute } from "./routeMatch";
7
7
  import { constructUrl, isAbsolutePath, parseTarget } from "./urlUtils";
8
8
  import { joinPaths } from "./pathUtils";
9
+ import { takeQueuedEntryState } from "./entryState";
9
10
  import { URLCHANGE } from "./generated/protocol";
10
11
  const useTinkerableLink = (newSandboxLocation) => {
11
12
  const { outerHref, navigationState: navigation } = use(TinkerableContext);
@@ -75,10 +76,12 @@ const navigate = (target, opts) => {
75
76
  } catch {
76
77
  }
77
78
  }
79
+ const entryState = takeQueuedEntryState();
78
80
  sendMessage(URLCHANGE, {
79
81
  url: target,
80
82
  back: false,
81
83
  forward: false,
84
+ ...entryState ? { entryState } : {},
82
85
  ...declared
83
86
  });
84
87
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/routing.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { use, useContext } from 'react';\n\nimport { sendMessage } from './sandboxUtils';\nimport { NavigationState, TinkerableContext } from './TinkerableContext';\nimport { RouteParams, RoutingRule, RoutingSpec } from './RoutingSpec';\nimport { matchRoute } from './routeMatch';\nimport { constructUrl, isAbsolutePath, parseTarget } from './urlUtils';\nimport { joinPaths } from './pathUtils';\nimport { URLCHANGE } from './generated/protocol';\n\n/** The result of matching a path: the winning {@link RoutingRule} plus its captured params. */\nexport type AppliedRoutingRule = {\n routingRule: RoutingRule;\n pathParameters?: Record<string, string>;\n};\n\n/** Build the full outer href for an in-app target (absolute `sandboxPath` or a\n * path relative to the current route), e.g. for an `href` attribute. */\nexport const useTinkerableLink = (newSandboxLocation: string) => {\n const { outerHref, navigationState: navigation } = use(TinkerableContext);\n let newNavigationState = parseTarget(newSandboxLocation, navigation);\n if (!isAbsolutePath(newSandboxLocation)) {\n newNavigationState.sandboxPath = joinPaths(navigation.sandboxPath, newSandboxLocation);\n } else {\n newNavigationState.sandboxPath = newSandboxLocation;\n }\n return constructUrl(outerHref, newNavigationState);\n};\n\n/** Find the first rule in `routingSpec` whose pattern matches the current\n * `sandboxPath`, returning it with the captured params (or `undefined`). */\nexport const applyRoutingRule = (\n routingSpec: RoutingSpec,\n navigationState: NavigationState,\n): AppliedRoutingRule | undefined => {\n const { sandboxPath } = navigationState;\n for (const routingRule of routingSpec.routes) {\n const pathParameters = matchRoute(routingRule.pattern, sandboxPath);\n if (pathParameters) {\n return { routingRule, pathParameters };\n }\n }\n return undefined;\n};\n\n/** Render a matched rule, passing params to a `component` and falling back to `element`/`reactNode`. */\nexport const renderRoute = (routingRule: RoutingRule, params: RouteParams): ReactNode => {\n if (routingRule.component) {\n const Component = routingRule.component;\n return <Component params={params} />;\n }\n return routingRule.element ?? routingRule.reactNode ?? null;\n};\n\n/** Render the route matched for the current location (set up by `boot`'s route table). */\nexport const Router = () => {\n const context = useContext(TinkerableContext);\n const {\n navigationState: { routingRule, pathParameters },\n } = context;\n if (!routingRule) {\n // TODO: better error\n throw new Error(`No route registered for path ${context.navigationState.sandboxPath}!`);\n }\n\n return renderRoute(routingRule, pathParameters ?? {});\n};\n\n/** Read the current route's matched params (`:name` segments and the `*` wildcard). */\nexport const useRouteParams = <T extends RouteParams = RouteParams>(): T =>\n (use(TinkerableContext).navigationState.pathParameters ?? {}) as T;\n\n/**\n * Read the current route: the matched rule's `name`, its `params`, the app-owned\n * `sandboxPath`, and the read-only platform prefix fields (`mode`, `provider`,\n * `namespace`, `repository`, `ref`) — e.g. to tell `/edit` from `/present`.\n */\nexport const useRoute = () => {\n const { navigationState } = use(TinkerableContext);\n const { routingRule, pathParameters, sandboxPath, mode, provider, namespace, repository, ref } = navigationState;\n return {\n name: routingRule?.name,\n params: (pathParameters ?? {}) as RouteParams,\n sandboxPath,\n mode,\n provider,\n namespace,\n repository,\n ref,\n };\n};\n\n/**\n * Navigate within the app. Messages the host to update the URL; the host then\n * pushes the new href back, which drives the actual route change.\n *\n * `opts.viewedDocument` (R3-268) optionally declares which WORKING-TREE file\n * this destination renders — a tri-state rider on the navigation event:\n * - omit the option entirely → the host derives the hint from the URL's\n * `files/` suffix convention (the zero-SDK default);\n * - `null` → this view shows no file (clears the highlight — tag pages,\n * search, home views);\n * - a repo-relative path (the CORPUS path under dispatch — only the viewer\n * can map its own key space) → the file explorer highlights it.\n * The hint is highlight-only by contract (it never scrolls, never moves focus\n * or panes, never switches the editor) and is validated host-side for\n * existence — a wrong path degrades to \"no highlight\", never an error. The\n * host remembers declarations per URL, so back/forward reproduces them\n * without re-announcement.\n */\n// R3-268: an app-registered rule mapping a navigation TARGET to its viewed\n// document, consulted by `navigate()` whenever the caller did not declare one\n// explicitly. Registered ONCE (e.g. at boot) so an app whose links all flow\n// through `<Link>`/`navigate` gets correct declarations everywhere without\n// threading an option through every call site. Return `undefined` for \"no\n// declaration\" (the host falls back to the URL convention), `null` for \"this\n// view shows no file\", or a working-tree repo-relative path.\nlet viewedDocumentResolver: ((targetHref: string) => string | null | undefined) | null = null;\n\n/** Register the app's route→viewed-document rule (R3-268); pass `null` to clear. */\nexport const setViewedDocumentResolver = (\n resolver: ((targetHref: string) => string | null | undefined) | null,\n): void => {\n viewedDocumentResolver = resolver;\n};\n\nexport const navigate = (target: string, opts?: { viewedDocument?: string | null }) => {\n console.log(`[Sandbox] Navigating to ${target}`);\n // Explicit option first; else the registered resolver; else nothing on the\n // wire (the host derives from the URL convention). A resolver throw is\n // swallowed to \"no declaration\" — a mapping bug must never break navigation.\n let declared: { viewedDocument: string | null } | Record<string, never> = {};\n if (opts && 'viewedDocument' in opts) {\n declared = { viewedDocument: opts.viewedDocument ?? null };\n } else if (viewedDocumentResolver) {\n try {\n const v = viewedDocumentResolver(target);\n if (v !== undefined) declared = { viewedDocument: v };\n } catch {\n /* no declaration */\n }\n }\n sendMessage(URLCHANGE, {\n url: target,\n back: false,\n forward: false,\n ...declared,\n });\n};\n"],"mappings":";AAkDW;AAjDX,SAAS,KAAK,kBAAkB;AAEhC,SAAS,mBAAmB;AAC5B,SAA0B,yBAAyB;AAEnD,SAAS,kBAAkB;AAC3B,SAAS,cAAc,gBAAgB,mBAAmB;AAC1D,SAAS,iBAAiB;AAC1B,SAAS,iBAAiB;AAUnB,MAAM,oBAAoB,CAAC,uBAA+B;AAC/D,QAAM,EAAE,WAAW,iBAAiB,WAAW,IAAI,IAAI,iBAAiB;AACxE,MAAI,qBAAqB,YAAY,oBAAoB,UAAU;AACnE,MAAI,CAAC,eAAe,kBAAkB,GAAG;AACvC,uBAAmB,cAAc,UAAU,WAAW,aAAa,kBAAkB;AAAA,EACvF,OAAO;AACL,uBAAmB,cAAc;AAAA,EACnC;AACA,SAAO,aAAa,WAAW,kBAAkB;AACnD;AAIO,MAAM,mBAAmB,CAC9B,aACA,oBACmC;AACnC,QAAM,EAAE,YAAY,IAAI;AACxB,aAAW,eAAe,YAAY,QAAQ;AAC5C,UAAM,iBAAiB,WAAW,YAAY,SAAS,WAAW;AAClE,QAAI,gBAAgB;AAClB,aAAO,EAAE,aAAa,eAAe;AAAA,IACvC;AAAA,EACF;AACA,SAAO;AACT;AAGO,MAAM,cAAc,CAAC,aAA0B,WAAmC;AACvF,MAAI,YAAY,WAAW;AACzB,UAAM,YAAY,YAAY;AAC9B,WAAO,oBAAC,aAAU,QAAgB;AAAA,EACpC;AACA,SAAO,YAAY,WAAW,YAAY,aAAa;AACzD;AAGO,MAAM,SAAS,MAAM;AAC1B,QAAM,UAAU,WAAW,iBAAiB;AAC5C,QAAM;AAAA,IACJ,iBAAiB,EAAE,aAAa,eAAe;AAAA,EACjD,IAAI;AACJ,MAAI,CAAC,aAAa;AAEhB,UAAM,IAAI,MAAM,gCAAgC,QAAQ,gBAAgB,WAAW,GAAG;AAAA,EACxF;AAEA,SAAO,YAAY,aAAa,kBAAkB,CAAC,CAAC;AACtD;AAGO,MAAM,iBAAiB,MAC3B,IAAI,iBAAiB,EAAE,gBAAgB,kBAAkB,CAAC;AAOtD,MAAM,WAAW,MAAM;AAC5B,QAAM,EAAE,gBAAgB,IAAI,IAAI,iBAAiB;AACjD,QAAM,EAAE,aAAa,gBAAgB,aAAa,MAAM,UAAU,WAAW,YAAY,IAAI,IAAI;AACjG,SAAO;AAAA,IACL,MAAM,aAAa;AAAA,IACnB,QAAS,kBAAkB,CAAC;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AA2BA,IAAI,yBAAqF;AAGlF,MAAM,4BAA4B,CACvC,aACS;AACT,2BAAyB;AAC3B;AAEO,MAAM,WAAW,CAAC,QAAgB,SAA8C;AACrF,UAAQ,IAAI,2BAA2B,MAAM,EAAE;AAI/C,MAAI,WAAsE,CAAC;AAC3E,MAAI,QAAQ,oBAAoB,MAAM;AACpC,eAAW,EAAE,gBAAgB,KAAK,kBAAkB,KAAK;AAAA,EAC3D,WAAW,wBAAwB;AACjC,QAAI;AACF,YAAM,IAAI,uBAAuB,MAAM;AACvC,UAAI,MAAM,OAAW,YAAW,EAAE,gBAAgB,EAAE;AAAA,IACtD,QAAQ;AAAA,IAER;AAAA,EACF;AACA,cAAY,WAAW;AAAA,IACrB,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,GAAG;AAAA,EACL,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../src/routing.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { use, useContext } from 'react';\n\nimport { sendMessage } from './sandboxUtils';\nimport { NavigationState, TinkerableContext } from './TinkerableContext';\nimport { RouteParams, RoutingRule, RoutingSpec } from './RoutingSpec';\nimport { matchRoute } from './routeMatch';\nimport { constructUrl, isAbsolutePath, parseTarget } from './urlUtils';\nimport { joinPaths } from './pathUtils';\nimport { takeQueuedEntryState } from './entryState';\nimport { URLCHANGE } from './generated/protocol';\n\n/** The result of matching a path: the winning {@link RoutingRule} plus its captured params. */\nexport type AppliedRoutingRule = {\n routingRule: RoutingRule;\n pathParameters?: Record<string, string>;\n};\n\n/** Build the full outer href for an in-app target (absolute `sandboxPath` or a\n * path relative to the current route), e.g. for an `href` attribute. */\nexport const useTinkerableLink = (newSandboxLocation: string) => {\n const { outerHref, navigationState: navigation } = use(TinkerableContext);\n let newNavigationState = parseTarget(newSandboxLocation, navigation);\n if (!isAbsolutePath(newSandboxLocation)) {\n newNavigationState.sandboxPath = joinPaths(navigation.sandboxPath, newSandboxLocation);\n } else {\n newNavigationState.sandboxPath = newSandboxLocation;\n }\n return constructUrl(outerHref, newNavigationState);\n};\n\n/** Find the first rule in `routingSpec` whose pattern matches the current\n * `sandboxPath`, returning it with the captured params (or `undefined`). */\nexport const applyRoutingRule = (\n routingSpec: RoutingSpec,\n navigationState: NavigationState,\n): AppliedRoutingRule | undefined => {\n const { sandboxPath } = navigationState;\n for (const routingRule of routingSpec.routes) {\n const pathParameters = matchRoute(routingRule.pattern, sandboxPath);\n if (pathParameters) {\n return { routingRule, pathParameters };\n }\n }\n return undefined;\n};\n\n/** Render a matched rule, passing params to a `component` and falling back to `element`/`reactNode`. */\nexport const renderRoute = (routingRule: RoutingRule, params: RouteParams): ReactNode => {\n if (routingRule.component) {\n const Component = routingRule.component;\n return <Component params={params} />;\n }\n return routingRule.element ?? routingRule.reactNode ?? null;\n};\n\n/** Render the route matched for the current location (set up by `boot`'s route table). */\nexport const Router = () => {\n const context = useContext(TinkerableContext);\n const {\n navigationState: { routingRule, pathParameters },\n } = context;\n if (!routingRule) {\n // TODO: better error\n throw new Error(`No route registered for path ${context.navigationState.sandboxPath}!`);\n }\n\n return renderRoute(routingRule, pathParameters ?? {});\n};\n\n/** Read the current route's matched params (`:name` segments and the `*` wildcard). */\nexport const useRouteParams = <T extends RouteParams = RouteParams>(): T =>\n (use(TinkerableContext).navigationState.pathParameters ?? {}) as T;\n\n/**\n * Read the current route: the matched rule's `name`, its `params`, the app-owned\n * `sandboxPath`, and the read-only platform prefix fields (`mode`, `provider`,\n * `namespace`, `repository`, `ref`) — e.g. to tell `/edit` from `/present`.\n */\nexport const useRoute = () => {\n const { navigationState } = use(TinkerableContext);\n const { routingRule, pathParameters, sandboxPath, mode, provider, namespace, repository, ref } = navigationState;\n return {\n name: routingRule?.name,\n params: (pathParameters ?? {}) as RouteParams,\n sandboxPath,\n mode,\n provider,\n namespace,\n repository,\n ref,\n };\n};\n\n/**\n * Navigate within the app. Messages the host to update the URL; the host then\n * pushes the new href back, which drives the actual route change.\n *\n * `opts.viewedDocument` (R3-268) optionally declares which WORKING-TREE file\n * this destination renders — a tri-state rider on the navigation event:\n * - omit the option entirely → the host derives the hint from the URL's\n * `files/` suffix convention (the zero-SDK default);\n * - `null` → this view shows no file (clears the highlight — tag pages,\n * search, home views);\n * - a repo-relative path (the CORPUS path under dispatch — only the viewer\n * can map its own key space) → the file explorer highlights it.\n * The hint is highlight-only by contract (it never scrolls, never moves focus\n * or panes, never switches the editor) and is validated host-side for\n * existence — a wrong path degrades to \"no highlight\", never an error. The\n * host remembers declarations per URL, so back/forward reproduces them\n * without re-announcement.\n */\n// R3-268: an app-registered rule mapping a navigation TARGET to its viewed\n// document, consulted by `navigate()` whenever the caller did not declare one\n// explicitly. Registered ONCE (e.g. at boot) so an app whose links all flow\n// through `<Link>`/`navigate` gets correct declarations everywhere without\n// threading an option through every call site. Return `undefined` for \"no\n// declaration\" (the host falls back to the URL convention), `null` for \"this\n// view shows no file\", or a working-tree repo-relative path.\nlet viewedDocumentResolver: ((targetHref: string) => string | null | undefined) | null = null;\n\n/** Register the app's route→viewed-document rule (R3-268); pass `null` to clear. */\nexport const setViewedDocumentResolver = (\n resolver: ((targetHref: string) => string | null | undefined) | null,\n): void => {\n viewedDocumentResolver = resolver;\n};\n\nexport const navigate = (target: string, opts?: { viewedDocument?: string | null }) => {\n console.log(`[Sandbox] Navigating to ${target}`);\n // Explicit option first; else the registered resolver; else nothing on the\n // wire (the host derives from the URL convention). A resolver throw is\n // swallowed to \"no declaration\" — a mapping bug must never break navigation.\n let declared: { viewedDocument: string | null } | Record<string, never> = {};\n if (opts && 'viewedDocument' in opts) {\n declared = { viewedDocument: opts.viewedDocument ?? null };\n } else if (viewedDocumentResolver) {\n try {\n const v = viewedDocumentResolver(target);\n if (v !== undefined) declared = { viewedDocument: v };\n } catch {\n /* no declaration */\n }\n }\n // The scratch for the entry we are LEAVING, gathered synchronously here because\n // this call is the one moment the app knows a navigation is happening (R3-627).\n // The host stamps it on the current entry before pushing the target, and hands it\n // back if the reader ever returns; it never parses it.\n const entryState = takeQueuedEntryState();\n sendMessage(URLCHANGE, {\n url: target,\n back: false,\n forward: false,\n ...(entryState ? { entryState } : {}),\n ...declared,\n });\n};\n"],"mappings":";AAmDW;AAlDX,SAAS,KAAK,kBAAkB;AAEhC,SAAS,mBAAmB;AAC5B,SAA0B,yBAAyB;AAEnD,SAAS,kBAAkB;AAC3B,SAAS,cAAc,gBAAgB,mBAAmB;AAC1D,SAAS,iBAAiB;AAC1B,SAAS,4BAA4B;AACrC,SAAS,iBAAiB;AAUnB,MAAM,oBAAoB,CAAC,uBAA+B;AAC/D,QAAM,EAAE,WAAW,iBAAiB,WAAW,IAAI,IAAI,iBAAiB;AACxE,MAAI,qBAAqB,YAAY,oBAAoB,UAAU;AACnE,MAAI,CAAC,eAAe,kBAAkB,GAAG;AACvC,uBAAmB,cAAc,UAAU,WAAW,aAAa,kBAAkB;AAAA,EACvF,OAAO;AACL,uBAAmB,cAAc;AAAA,EACnC;AACA,SAAO,aAAa,WAAW,kBAAkB;AACnD;AAIO,MAAM,mBAAmB,CAC9B,aACA,oBACmC;AACnC,QAAM,EAAE,YAAY,IAAI;AACxB,aAAW,eAAe,YAAY,QAAQ;AAC5C,UAAM,iBAAiB,WAAW,YAAY,SAAS,WAAW;AAClE,QAAI,gBAAgB;AAClB,aAAO,EAAE,aAAa,eAAe;AAAA,IACvC;AAAA,EACF;AACA,SAAO;AACT;AAGO,MAAM,cAAc,CAAC,aAA0B,WAAmC;AACvF,MAAI,YAAY,WAAW;AACzB,UAAM,YAAY,YAAY;AAC9B,WAAO,oBAAC,aAAU,QAAgB;AAAA,EACpC;AACA,SAAO,YAAY,WAAW,YAAY,aAAa;AACzD;AAGO,MAAM,SAAS,MAAM;AAC1B,QAAM,UAAU,WAAW,iBAAiB;AAC5C,QAAM;AAAA,IACJ,iBAAiB,EAAE,aAAa,eAAe;AAAA,EACjD,IAAI;AACJ,MAAI,CAAC,aAAa;AAEhB,UAAM,IAAI,MAAM,gCAAgC,QAAQ,gBAAgB,WAAW,GAAG;AAAA,EACxF;AAEA,SAAO,YAAY,aAAa,kBAAkB,CAAC,CAAC;AACtD;AAGO,MAAM,iBAAiB,MAC3B,IAAI,iBAAiB,EAAE,gBAAgB,kBAAkB,CAAC;AAOtD,MAAM,WAAW,MAAM;AAC5B,QAAM,EAAE,gBAAgB,IAAI,IAAI,iBAAiB;AACjD,QAAM,EAAE,aAAa,gBAAgB,aAAa,MAAM,UAAU,WAAW,YAAY,IAAI,IAAI;AACjG,SAAO;AAAA,IACL,MAAM,aAAa;AAAA,IACnB,QAAS,kBAAkB,CAAC;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AA2BA,IAAI,yBAAqF;AAGlF,MAAM,4BAA4B,CACvC,aACS;AACT,2BAAyB;AAC3B;AAEO,MAAM,WAAW,CAAC,QAAgB,SAA8C;AACrF,UAAQ,IAAI,2BAA2B,MAAM,EAAE;AAI/C,MAAI,WAAsE,CAAC;AAC3E,MAAI,QAAQ,oBAAoB,MAAM;AACpC,eAAW,EAAE,gBAAgB,KAAK,kBAAkB,KAAK;AAAA,EAC3D,WAAW,wBAAwB;AACjC,QAAI;AACF,YAAM,IAAI,uBAAuB,MAAM;AACvC,UAAI,MAAM,OAAW,YAAW,EAAE,gBAAgB,EAAE;AAAA,IACtD,QAAQ;AAAA,IAER;AAAA,EACF;AAKA,QAAM,aAAa,qBAAqB;AACxC,cAAY,WAAW;AAAA,IACrB,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,IACnC,GAAG;AAAA,EACL,CAAC;AACH;","names":[]}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var scrollRestore_exports = {};
20
+ __export(scrollRestore_exports, {
21
+ RESTORE_DEADLINE_MS: () => RESTORE_DEADLINE_MS,
22
+ RESTORE_EPSILON_PX: () => RESTORE_EPSILON_PX,
23
+ nextRestoreAction: () => nextRestoreAction
24
+ });
25
+ module.exports = __toCommonJS(scrollRestore_exports);
26
+ const RESTORE_DEADLINE_MS = 900;
27
+ const RESTORE_EPSILON_PX = 2;
28
+ const nextRestoreAction = (sample) => {
29
+ const { target, scrollHeight, clientHeight, current, elapsedMs, userScrolled } = sample;
30
+ if (!Number.isFinite(target) || target <= 0) return "abandon";
31
+ if (userScrolled) return "abandon";
32
+ if (Math.abs(current - target) <= RESTORE_EPSILON_PX) return "apply";
33
+ if (elapsedMs >= RESTORE_DEADLINE_MS) return "abandon";
34
+ const reachable = Math.max(0, scrollHeight - clientHeight);
35
+ return reachable >= target ? "apply" : "wait";
36
+ };
37
+ // Annotate the CommonJS export names for ESM import in node:
38
+ 0 && (module.exports = {
39
+ RESTORE_DEADLINE_MS,
40
+ RESTORE_EPSILON_PX,
41
+ nextRestoreAction
42
+ });
43
+ //# sourceMappingURL=scrollRestore.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/scrollRestore.ts"],"sourcesContent":["// Deciding WHEN a remembered scroll offset may be applied after a back/forward\n// traversal (R3-627). Pure: no DOM, no React, no timers — the caller samples the\n// geometry and this says what to do with the sample.\n//\n// The failure this exists to prevent: an app's document grows as it renders, so an\n// offset applied on arrival is clamped to whatever height exists at that instant and\n// the reader lands near the top — the very defect the feature is meant to remove. So\n// the restore waits until the content is tall enough to hold the offset, and gives up\n// rather than fighting either the clock or the reader.\n\n/** A geometry sample of the scroller, plus how the attempt is going. */\nexport interface RestoreSample {\n /** The remembered offset we are trying to reach. */\n target: number;\n /** The scroller's full scrollable height right now. */\n scrollHeight: number;\n /** The scroller's visible height right now. */\n clientHeight: number;\n /** Where the scroller is right now. */\n current: number;\n /** Milliseconds since the restore began. */\n elapsedMs: number;\n /** Whether the reader has scrolled since the restore began. */\n userScrolled: boolean;\n}\n\n/** What the caller should do with this sample. */\nexport type RestoreAction = 'apply' | 'wait' | 'abandon';\n\n/** How long to keep waiting for the content to grow before giving up. Matches the\n * give-up window `ScrollAfterNavigation` already uses for fragments, so the two\n * navigation-scroll behaviours settle on the same timescale. */\nexport const RESTORE_DEADLINE_MS = 900;\n\n/** How close counts as arrived. Sub-pixel differences and fractional device pixels\n * must not keep a restore looping. */\nexport const RESTORE_EPSILON_PX = 2;\n\n/**\n * The one decision, given a sample.\n *\n * - `abandon` — the reader has taken over, or the deadline passed. Never fight a\n * user, and never scroll a page they have already started reading.\n * - `apply` — the content can hold the offset (or the offset is already reached,\n * within {@link RESTORE_EPSILON_PX}); scroll and finish.\n * - `wait` — the content is still too short; sample again.\n *\n * A non-finite or negative target is treated as nothing to restore (`abandon`), so a\n * corrupt scratch value can never move the page.\n */\nexport const nextRestoreAction = (sample: RestoreSample): RestoreAction => {\n const { target, scrollHeight, clientHeight, current, elapsedMs, userScrolled } = sample;\n if (!Number.isFinite(target) || target <= 0) return 'abandon';\n if (userScrolled) return 'abandon';\n if (Math.abs(current - target) <= RESTORE_EPSILON_PX) return 'apply';\n if (elapsedMs >= RESTORE_DEADLINE_MS) return 'abandon';\n // The furthest this scroller can currently reach. Applying before the content is\n // this tall is what clamps the reader to the top.\n const reachable = Math.max(0, scrollHeight - clientHeight);\n return reachable >= target ? 'apply' : 'wait';\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgCO,MAAM,sBAAsB;AAI5B,MAAM,qBAAqB;AAc3B,MAAM,oBAAoB,CAAC,WAAyC;AACzE,QAAM,EAAE,QAAQ,cAAc,cAAc,SAAS,WAAW,aAAa,IAAI;AACjF,MAAI,CAAC,OAAO,SAAS,MAAM,KAAK,UAAU,EAAG,QAAO;AACpD,MAAI,aAAc,QAAO;AACzB,MAAI,KAAK,IAAI,UAAU,MAAM,KAAK,mBAAoB,QAAO;AAC7D,MAAI,aAAa,oBAAqB,QAAO;AAG7C,QAAM,YAAY,KAAK,IAAI,GAAG,eAAe,YAAY;AACzD,SAAO,aAAa,SAAS,UAAU;AACzC;","names":[]}
@@ -0,0 +1,39 @@
1
+ /** A geometry sample of the scroller, plus how the attempt is going. */
2
+ interface RestoreSample {
3
+ /** The remembered offset we are trying to reach. */
4
+ target: number;
5
+ /** The scroller's full scrollable height right now. */
6
+ scrollHeight: number;
7
+ /** The scroller's visible height right now. */
8
+ clientHeight: number;
9
+ /** Where the scroller is right now. */
10
+ current: number;
11
+ /** Milliseconds since the restore began. */
12
+ elapsedMs: number;
13
+ /** Whether the reader has scrolled since the restore began. */
14
+ userScrolled: boolean;
15
+ }
16
+ /** What the caller should do with this sample. */
17
+ type RestoreAction = 'apply' | 'wait' | 'abandon';
18
+ /** How long to keep waiting for the content to grow before giving up. Matches the
19
+ * give-up window `ScrollAfterNavigation` already uses for fragments, so the two
20
+ * navigation-scroll behaviours settle on the same timescale. */
21
+ declare const RESTORE_DEADLINE_MS = 900;
22
+ /** How close counts as arrived. Sub-pixel differences and fractional device pixels
23
+ * must not keep a restore looping. */
24
+ declare const RESTORE_EPSILON_PX = 2;
25
+ /**
26
+ * The one decision, given a sample.
27
+ *
28
+ * - `abandon` — the reader has taken over, or the deadline passed. Never fight a
29
+ * user, and never scroll a page they have already started reading.
30
+ * - `apply` — the content can hold the offset (or the offset is already reached,
31
+ * within {@link RESTORE_EPSILON_PX}); scroll and finish.
32
+ * - `wait` — the content is still too short; sample again.
33
+ *
34
+ * A non-finite or negative target is treated as nothing to restore (`abandon`), so a
35
+ * corrupt scratch value can never move the page.
36
+ */
37
+ declare const nextRestoreAction: (sample: RestoreSample) => RestoreAction;
38
+
39
+ export { RESTORE_DEADLINE_MS, RESTORE_EPSILON_PX, type RestoreAction, type RestoreSample, nextRestoreAction };
@@ -0,0 +1,39 @@
1
+ /** A geometry sample of the scroller, plus how the attempt is going. */
2
+ interface RestoreSample {
3
+ /** The remembered offset we are trying to reach. */
4
+ target: number;
5
+ /** The scroller's full scrollable height right now. */
6
+ scrollHeight: number;
7
+ /** The scroller's visible height right now. */
8
+ clientHeight: number;
9
+ /** Where the scroller is right now. */
10
+ current: number;
11
+ /** Milliseconds since the restore began. */
12
+ elapsedMs: number;
13
+ /** Whether the reader has scrolled since the restore began. */
14
+ userScrolled: boolean;
15
+ }
16
+ /** What the caller should do with this sample. */
17
+ type RestoreAction = 'apply' | 'wait' | 'abandon';
18
+ /** How long to keep waiting for the content to grow before giving up. Matches the
19
+ * give-up window `ScrollAfterNavigation` already uses for fragments, so the two
20
+ * navigation-scroll behaviours settle on the same timescale. */
21
+ declare const RESTORE_DEADLINE_MS = 900;
22
+ /** How close counts as arrived. Sub-pixel differences and fractional device pixels
23
+ * must not keep a restore looping. */
24
+ declare const RESTORE_EPSILON_PX = 2;
25
+ /**
26
+ * The one decision, given a sample.
27
+ *
28
+ * - `abandon` — the reader has taken over, or the deadline passed. Never fight a
29
+ * user, and never scroll a page they have already started reading.
30
+ * - `apply` — the content can hold the offset (or the offset is already reached,
31
+ * within {@link RESTORE_EPSILON_PX}); scroll and finish.
32
+ * - `wait` — the content is still too short; sample again.
33
+ *
34
+ * A non-finite or negative target is treated as nothing to restore (`abandon`), so a
35
+ * corrupt scratch value can never move the page.
36
+ */
37
+ declare const nextRestoreAction: (sample: RestoreSample) => RestoreAction;
38
+
39
+ export { RESTORE_DEADLINE_MS, RESTORE_EPSILON_PX, type RestoreAction, type RestoreSample, nextRestoreAction };
@@ -0,0 +1,18 @@
1
+ import "./chunk-VHAA22YE.js";
2
+ const RESTORE_DEADLINE_MS = 900;
3
+ const RESTORE_EPSILON_PX = 2;
4
+ const nextRestoreAction = (sample) => {
5
+ const { target, scrollHeight, clientHeight, current, elapsedMs, userScrolled } = sample;
6
+ if (!Number.isFinite(target) || target <= 0) return "abandon";
7
+ if (userScrolled) return "abandon";
8
+ if (Math.abs(current - target) <= RESTORE_EPSILON_PX) return "apply";
9
+ if (elapsedMs >= RESTORE_DEADLINE_MS) return "abandon";
10
+ const reachable = Math.max(0, scrollHeight - clientHeight);
11
+ return reachable >= target ? "apply" : "wait";
12
+ };
13
+ export {
14
+ RESTORE_DEADLINE_MS,
15
+ RESTORE_EPSILON_PX,
16
+ nextRestoreAction
17
+ };
18
+ //# sourceMappingURL=scrollRestore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/scrollRestore.ts"],"sourcesContent":["// Deciding WHEN a remembered scroll offset may be applied after a back/forward\n// traversal (R3-627). Pure: no DOM, no React, no timers — the caller samples the\n// geometry and this says what to do with the sample.\n//\n// The failure this exists to prevent: an app's document grows as it renders, so an\n// offset applied on arrival is clamped to whatever height exists at that instant and\n// the reader lands near the top — the very defect the feature is meant to remove. So\n// the restore waits until the content is tall enough to hold the offset, and gives up\n// rather than fighting either the clock or the reader.\n\n/** A geometry sample of the scroller, plus how the attempt is going. */\nexport interface RestoreSample {\n /** The remembered offset we are trying to reach. */\n target: number;\n /** The scroller's full scrollable height right now. */\n scrollHeight: number;\n /** The scroller's visible height right now. */\n clientHeight: number;\n /** Where the scroller is right now. */\n current: number;\n /** Milliseconds since the restore began. */\n elapsedMs: number;\n /** Whether the reader has scrolled since the restore began. */\n userScrolled: boolean;\n}\n\n/** What the caller should do with this sample. */\nexport type RestoreAction = 'apply' | 'wait' | 'abandon';\n\n/** How long to keep waiting for the content to grow before giving up. Matches the\n * give-up window `ScrollAfterNavigation` already uses for fragments, so the two\n * navigation-scroll behaviours settle on the same timescale. */\nexport const RESTORE_DEADLINE_MS = 900;\n\n/** How close counts as arrived. Sub-pixel differences and fractional device pixels\n * must not keep a restore looping. */\nexport const RESTORE_EPSILON_PX = 2;\n\n/**\n * The one decision, given a sample.\n *\n * - `abandon` — the reader has taken over, or the deadline passed. Never fight a\n * user, and never scroll a page they have already started reading.\n * - `apply` — the content can hold the offset (or the offset is already reached,\n * within {@link RESTORE_EPSILON_PX}); scroll and finish.\n * - `wait` — the content is still too short; sample again.\n *\n * A non-finite or negative target is treated as nothing to restore (`abandon`), so a\n * corrupt scratch value can never move the page.\n */\nexport const nextRestoreAction = (sample: RestoreSample): RestoreAction => {\n const { target, scrollHeight, clientHeight, current, elapsedMs, userScrolled } = sample;\n if (!Number.isFinite(target) || target <= 0) return 'abandon';\n if (userScrolled) return 'abandon';\n if (Math.abs(current - target) <= RESTORE_EPSILON_PX) return 'apply';\n if (elapsedMs >= RESTORE_DEADLINE_MS) return 'abandon';\n // The furthest this scroller can currently reach. Applying before the content is\n // this tall is what clamps the reader to the top.\n const reachable = Math.max(0, scrollHeight - clientHeight);\n return reachable >= target ? 'apply' : 'wait';\n};\n"],"mappings":";AAgCO,MAAM,sBAAsB;AAI5B,MAAM,qBAAqB;AAc3B,MAAM,oBAAoB,CAAC,WAAyC;AACzE,QAAM,EAAE,QAAQ,cAAc,cAAc,SAAS,WAAW,aAAa,IAAI;AACjF,MAAI,CAAC,OAAO,SAAS,MAAM,KAAK,UAAU,EAAG,QAAO;AACpD,MAAI,aAAc,QAAO;AACzB,MAAI,KAAK,IAAI,UAAU,MAAM,KAAK,mBAAoB,QAAO;AAC7D,MAAI,aAAa,oBAAqB,QAAO;AAG7C,QAAM,YAAY,KAAK,IAAI,GAAG,eAAe,YAAY;AACzD,SAAO,aAAa,SAAS,UAAU;AACzC;","names":[]}
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var useEntryState_exports = {};
20
+ __export(useEntryState_exports, {
21
+ useEntryState: () => useEntryState,
22
+ useNavigationDirection: () => useNavigationDirection
23
+ });
24
+ module.exports = __toCommonJS(useEntryState_exports);
25
+ var import_react = require("react");
26
+ var import_entryState = require("./entryState");
27
+ const useEntryState = (key) => {
28
+ const arrived = (0, import_react.useSyncExternalStore)(import_entryState.subscribeNavigation, import_entryState.getArrivedNavigation, import_entryState.getArrivedNavigation);
29
+ const save = (0, import_react.useCallback)((value) => (0, import_entryState.saveEntryState)(key, value), [key]);
30
+ return { value: arrived.state?.[key], save };
31
+ };
32
+ const useNavigationDirection = () => {
33
+ const arrived = (0, import_react.useSyncExternalStore)(import_entryState.subscribeNavigation, import_entryState.getArrivedNavigation, import_entryState.getArrivedNavigation);
34
+ return arrived.direction;
35
+ };
36
+ // Annotate the CommonJS export names for ESM import in node:
37
+ 0 && (module.exports = {
38
+ useEntryState,
39
+ useNavigationDirection
40
+ });
41
+ //# sourceMappingURL=useEntryState.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useEntryState.ts"],"sourcesContent":["// React surface over the per-history-entry scratch (R3-627). The store itself is in\n// `entryState.ts` and is framework-free; this file is the thin binding so a component\n// re-renders when the host tells the frame which entry it just arrived at.\n\nimport { useCallback, useSyncExternalStore } from 'react';\n\nimport { getArrivedNavigation, saveEntryState, subscribeNavigation, type NavigationDirection } from './entryState';\n\n/**\n * Read the value this app left on the current history entry, and queue the value to\n * leave on the entry the next navigation departs from.\n *\n * `value` is `undefined` on an ordinary forward navigation — there is no bookmark for\n * a page being visited for the first time — and on a traversal to an entry that was\n * stamped by nothing (a navigation the app did not initiate).\n *\n * `save` queues; it does not send. The queued value travels with the next\n * `navigate()`, which is the only moment the app knows an entry is being left.\n */\nexport const useEntryState = <T>(key: string): { value: T | undefined; save: (value: T) => void } => {\n const arrived = useSyncExternalStore(subscribeNavigation, getArrivedNavigation, getArrivedNavigation);\n const save = useCallback((value: T) => saveEntryState(key, value), [key]);\n return { value: arrived.state?.[key] as T | undefined, save };\n};\n\n/**\n * How the browser reached the page being rendered: `push` for an ordinary\n * navigation, `back`/`forward` for a history traversal.\n *\n * An app that resets its own view on arrival — scrolling a container to the top is\n * the usual one — should stand down on a traversal, so a restored position is not\n * immediately thrown away.\n */\nexport const useNavigationDirection = (): NavigationDirection => {\n const arrived = useSyncExternalStore(subscribeNavigation, getArrivedNavigation, getArrivedNavigation);\n return arrived.direction;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,mBAAkD;AAElD,wBAAoG;AAa7F,MAAM,gBAAgB,CAAI,QAAoE;AACnG,QAAM,cAAU,mCAAqB,uCAAqB,wCAAsB,sCAAoB;AACpG,QAAM,WAAO,0BAAY,CAAC,cAAa,kCAAe,KAAK,KAAK,GAAG,CAAC,GAAG,CAAC;AACxE,SAAO,EAAE,OAAO,QAAQ,QAAQ,GAAG,GAAoB,KAAK;AAC9D;AAUO,MAAM,yBAAyB,MAA2B;AAC/D,QAAM,cAAU,mCAAqB,uCAAqB,wCAAsB,sCAAoB;AACpG,SAAO,QAAQ;AACjB;","names":[]}
@@ -0,0 +1,28 @@
1
+ import { NavigationDirection } from './entryState.cjs';
2
+
3
+ /**
4
+ * Read the value this app left on the current history entry, and queue the value to
5
+ * leave on the entry the next navigation departs from.
6
+ *
7
+ * `value` is `undefined` on an ordinary forward navigation — there is no bookmark for
8
+ * a page being visited for the first time — and on a traversal to an entry that was
9
+ * stamped by nothing (a navigation the app did not initiate).
10
+ *
11
+ * `save` queues; it does not send. The queued value travels with the next
12
+ * `navigate()`, which is the only moment the app knows an entry is being left.
13
+ */
14
+ declare const useEntryState: <T>(key: string) => {
15
+ value: T | undefined;
16
+ save: (value: T) => void;
17
+ };
18
+ /**
19
+ * How the browser reached the page being rendered: `push` for an ordinary
20
+ * navigation, `back`/`forward` for a history traversal.
21
+ *
22
+ * An app that resets its own view on arrival — scrolling a container to the top is
23
+ * the usual one — should stand down on a traversal, so a restored position is not
24
+ * immediately thrown away.
25
+ */
26
+ declare const useNavigationDirection: () => NavigationDirection;
27
+
28
+ export { useEntryState, useNavigationDirection };
@@ -0,0 +1,28 @@
1
+ import { NavigationDirection } from './entryState.js';
2
+
3
+ /**
4
+ * Read the value this app left on the current history entry, and queue the value to
5
+ * leave on the entry the next navigation departs from.
6
+ *
7
+ * `value` is `undefined` on an ordinary forward navigation — there is no bookmark for
8
+ * a page being visited for the first time — and on a traversal to an entry that was
9
+ * stamped by nothing (a navigation the app did not initiate).
10
+ *
11
+ * `save` queues; it does not send. The queued value travels with the next
12
+ * `navigate()`, which is the only moment the app knows an entry is being left.
13
+ */
14
+ declare const useEntryState: <T>(key: string) => {
15
+ value: T | undefined;
16
+ save: (value: T) => void;
17
+ };
18
+ /**
19
+ * How the browser reached the page being rendered: `push` for an ordinary
20
+ * navigation, `back`/`forward` for a history traversal.
21
+ *
22
+ * An app that resets its own view on arrival — scrolling a container to the top is
23
+ * the usual one — should stand down on a traversal, so a restored position is not
24
+ * immediately thrown away.
25
+ */
26
+ declare const useNavigationDirection: () => NavigationDirection;
27
+
28
+ export { useEntryState, useNavigationDirection };
@@ -0,0 +1,17 @@
1
+ import "./chunk-VHAA22YE.js";
2
+ import { useCallback, useSyncExternalStore } from "react";
3
+ import { getArrivedNavigation, saveEntryState, subscribeNavigation } from "./entryState";
4
+ const useEntryState = (key) => {
5
+ const arrived = useSyncExternalStore(subscribeNavigation, getArrivedNavigation, getArrivedNavigation);
6
+ const save = useCallback((value) => saveEntryState(key, value), [key]);
7
+ return { value: arrived.state?.[key], save };
8
+ };
9
+ const useNavigationDirection = () => {
10
+ const arrived = useSyncExternalStore(subscribeNavigation, getArrivedNavigation, getArrivedNavigation);
11
+ return arrived.direction;
12
+ };
13
+ export {
14
+ useEntryState,
15
+ useNavigationDirection
16
+ };
17
+ //# sourceMappingURL=useEntryState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useEntryState.ts"],"sourcesContent":["// React surface over the per-history-entry scratch (R3-627). The store itself is in\n// `entryState.ts` and is framework-free; this file is the thin binding so a component\n// re-renders when the host tells the frame which entry it just arrived at.\n\nimport { useCallback, useSyncExternalStore } from 'react';\n\nimport { getArrivedNavigation, saveEntryState, subscribeNavigation, type NavigationDirection } from './entryState';\n\n/**\n * Read the value this app left on the current history entry, and queue the value to\n * leave on the entry the next navigation departs from.\n *\n * `value` is `undefined` on an ordinary forward navigation — there is no bookmark for\n * a page being visited for the first time — and on a traversal to an entry that was\n * stamped by nothing (a navigation the app did not initiate).\n *\n * `save` queues; it does not send. The queued value travels with the next\n * `navigate()`, which is the only moment the app knows an entry is being left.\n */\nexport const useEntryState = <T>(key: string): { value: T | undefined; save: (value: T) => void } => {\n const arrived = useSyncExternalStore(subscribeNavigation, getArrivedNavigation, getArrivedNavigation);\n const save = useCallback((value: T) => saveEntryState(key, value), [key]);\n return { value: arrived.state?.[key] as T | undefined, save };\n};\n\n/**\n * How the browser reached the page being rendered: `push` for an ordinary\n * navigation, `back`/`forward` for a history traversal.\n *\n * An app that resets its own view on arrival — scrolling a container to the top is\n * the usual one — should stand down on a traversal, so a restored position is not\n * immediately thrown away.\n */\nexport const useNavigationDirection = (): NavigationDirection => {\n const arrived = useSyncExternalStore(subscribeNavigation, getArrivedNavigation, getArrivedNavigation);\n return arrived.direction;\n};\n"],"mappings":";AAIA,SAAS,aAAa,4BAA4B;AAElD,SAAS,sBAAsB,gBAAgB,2BAAqD;AAa7F,MAAM,gBAAgB,CAAI,QAAoE;AACnG,QAAM,UAAU,qBAAqB,qBAAqB,sBAAsB,oBAAoB;AACpG,QAAM,OAAO,YAAY,CAAC,UAAa,eAAe,KAAK,KAAK,GAAG,CAAC,GAAG,CAAC;AACxE,SAAO,EAAE,OAAO,QAAQ,QAAQ,GAAG,GAAoB,KAAK;AAC9D;AAUO,MAAM,yBAAyB,MAA2B;AAC/D,QAAM,UAAU,qBAAqB,qBAAqB,sBAAsB,oBAAoB;AACpG,SAAO,QAAQ;AACjB;","names":[]}
package/dist/version.cjs CHANGED
@@ -21,7 +21,7 @@ __export(version_exports, {
21
21
  SDK_VERSION: () => SDK_VERSION
22
22
  });
23
23
  module.exports = __toCommonJS(version_exports);
24
- const SDK_VERSION = "0.64.1";
24
+ const SDK_VERSION = "0.66.0";
25
25
  // Annotate the CommonJS export names for ESM import in node:
26
26
  0 && (module.exports = {
27
27
  SDK_VERSION
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.64.1';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIO,MAAM,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.66.0';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIO,MAAM,cAAc;","names":[]}
@@ -1,4 +1,4 @@
1
1
  /** This SDK's package version, baked from package.json at build (SP2-6). */
2
- declare const SDK_VERSION = "0.64.1";
2
+ declare const SDK_VERSION = "0.66.0";
3
3
 
4
4
  export { SDK_VERSION };
package/dist/version.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  /** This SDK's package version, baked from package.json at build (SP2-6). */
2
- declare const SDK_VERSION = "0.64.1";
2
+ declare const SDK_VERSION = "0.66.0";
3
3
 
4
4
  export { SDK_VERSION };
package/dist/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import "./chunk-VHAA22YE.js";
2
- const SDK_VERSION = "0.64.1";
2
+ const SDK_VERSION = "0.66.0";
3
3
  export {
4
4
  SDK_VERSION
5
5
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.64.1';\n"],"mappings":";AAIO,MAAM,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.66.0';\n"],"mappings":";AAIO,MAAM,cAAc;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@immediately-run/sdk",
3
- "version": "0.64.1",
3
+ "version": "0.66.0",
4
4
  "description": "Runtime SDK for code executing inside an immediately.run sandbox.",
5
5
  "license": "MIT",
6
6
  "repository": "github:immediately-run/immediately-run-sdk",
@@ -67,12 +67,12 @@
67
67
  "@immediately-run/mdx-plugins": "0.5.0",
68
68
  "@immediately-run/platform-constants": "0.2.0",
69
69
  "@immediately-run/safe-content": "0.1.0",
70
- "@immediately-run/sandbox-protocol": "0.10.1",
70
+ "@immediately-run/sandbox-protocol": "0.10.2",
71
71
  "react-error-boundary": "^6.0.0"
72
72
  },
73
73
  "devDependencies": {
74
74
  "@immediately-run/prettier-config": "0.1.0",
75
- "@immediately-run/verify-checks": "0.2.0",
75
+ "@immediately-run/verify-checks": "0.3.0",
76
76
  "@testing-library/dom": "^10.4.1",
77
77
  "@testing-library/react": "^16.3.2",
78
78
  "@types/jest": "^29.5.0",