@ogygia/core 0.11.3 → 0.11.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 (53) hide show
  1. package/README.md +1 -1
  2. package/dist/KeepHost.svelte +1 -1
  3. package/dist/LiveHost.svelte +1 -1
  4. package/dist/NestedProvider.svelte +1 -1
  5. package/dist/SlotBoundary.svelte +1 -1
  6. package/dist/compiler/driver.js +53 -11
  7. package/dist/compiler/ids.d.ts +3 -0
  8. package/dist/compiler/ids.js +4 -1
  9. package/dist/compiler/index.d.ts +2 -2
  10. package/dist/compiler/index.js +2 -2
  11. package/dist/compiler/kit.d.ts +7 -0
  12. package/dist/compiler/kit.js +22 -10
  13. package/dist/compiler/link/runtime-entry.d.ts +19 -2
  14. package/dist/compiler/link/runtime-entry.js +34 -1
  15. package/dist/compiler/program.d.ts +4 -0
  16. package/dist/compiler/program.js +5 -1
  17. package/dist/context.d.ts +3 -12
  18. package/dist/context.js +1 -16
  19. package/dist/hooks.d.ts +2 -1
  20. package/dist/internal.d.ts +2 -1
  21. package/dist/internal.js +2 -1
  22. package/dist/kit-handle.d.ts +20 -0
  23. package/dist/kit-handle.js +0 -0
  24. package/dist/nested.d.ts +11 -0
  25. package/dist/nested.js +19 -0
  26. package/dist/plugin/index.d.ts +3 -4
  27. package/dist/plugin/server.d.ts +3 -2
  28. package/dist/ref-sink.d.ts +15 -0
  29. package/dist/ref-sink.js +19 -0
  30. package/dist/ref.d.ts +2 -2
  31. package/dist/ref.js +1 -11
  32. package/dist/runtime/core.js +7 -5
  33. package/dist/runtime/full.js +5 -1
  34. package/dist/runtime/hydrate-core.d.ts +4 -4
  35. package/dist/runtime/hydrate-core.js +8 -5
  36. package/dist/runtime/nav-handle.d.ts +3 -0
  37. package/dist/runtime/nav-handle.js +12 -5
  38. package/dist/runtime/region-ids.js +2 -2
  39. package/dist/runtime/remote-seeds.js +14 -3
  40. package/dist/runtime/seeds.d.ts +10 -5
  41. package/dist/runtime/seeds.js +16 -12
  42. package/dist/runtime/slots.d.ts +16 -0
  43. package/dist/runtime/slots.js +2 -0
  44. package/dist/runtime/wire-classes.d.ts +2 -0
  45. package/dist/runtime/wire-classes.js +10 -1
  46. package/dist/runtime/wire-format.d.ts +4 -1
  47. package/dist/runtime/wire-format.js +6 -1
  48. package/dist/seed-ref-reviver.d.ts +24 -0
  49. package/dist/seed-ref-reviver.js +44 -0
  50. package/dist/seed-refs.d.ts +2 -16
  51. package/dist/seed-refs.js +1 -33
  52. package/dist/server/props-wire.js +2 -1
  53. package/package.json +3 -3
@@ -0,0 +1,20 @@
1
+ import { RequestEvent } from "@sveltejs/kit";
2
+ type MaybePromise<T> = T | Promise<T>;
3
+ /** Kit's `resolve()` options. */
4
+ export interface ResolveOptions {
5
+ transformPageChunk?(input: {
6
+ html: string;
7
+ done: boolean;
8
+ }): MaybePromise<string | undefined>;
9
+ filterSerializedResponseHeaders?(name: string, value: string): boolean;
10
+ preload?(input: {
11
+ type: "font" | "css" | "js" | "asset";
12
+ path: string;
13
+ }): boolean;
14
+ }
15
+ /** Kit's `Handle` hook. */
16
+ export type Handle = (input: {
17
+ event: RequestEvent;
18
+ resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>;
19
+ }) => MaybePromise<Response>;
20
+ export {};
File without changes
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Mark the current subtree's hydration state. `true` = inside a hydrated island (nested islands
3
+ * degrade). `false` = a LAKE resets its subtree to "dead", so an island inside the lake self-
4
+ * hydrates again (the nearest-boundary rule — DESIGN.md).
5
+ */
6
+ export declare function setNested(value?: boolean): void;
7
+ /**
8
+ * True when an ancestor island wrapper already marked the subtree. `getContext` returns `undefined`
9
+ * when no ancestor set it (a top-level island) — that absence is exactly "not nested".
10
+ */
11
+ export declare function isNested(): boolean;
package/dist/nested.js ADDED
@@ -0,0 +1,19 @@
1
+ import { getContext, setContext } from "svelte";
2
+ // src/nested.ts
3
+ const NESTED_KEY = Symbol.for("ogygia.nested-island");
4
+ /**
5
+ * Mark the current subtree's hydration state. `true` = inside a hydrated island (nested islands
6
+ * degrade). `false` = a LAKE resets its subtree to "dead", so an island inside the lake self-
7
+ * hydrates again (the nearest-boundary rule — DESIGN.md).
8
+ */
9
+ function setNested(value = true) {
10
+ setContext(NESTED_KEY, value);
11
+ }
12
+ /**
13
+ * True when an ancestor island wrapper already marked the subtree. `getContext` returns `undefined`
14
+ * when no ancestor set it (a top-level island) — that absence is exactly "not nested".
15
+ */
16
+ function isNested() {
17
+ return getContext(NESTED_KEY) === true;
18
+ }
19
+ export { isNested, setNested };
@@ -1,3 +1,4 @@
1
+ import { Handle, ResolveOptions } from "../kit-handle.js";
1
2
  import { RequestFacts } from "./facts.js";
2
3
  import { Ask, Notify, ServerHooks } from "./hooks.js";
3
4
  import { DevtoolsEventInput } from "../events/schema.js";
@@ -5,7 +6,7 @@ import { DeferredRegion, og_html_region, region } from "../region.js";
5
6
  import { RouteConfig, RouteConfigMatcher, register_route_config } from "./routes.js";
6
7
  import { LateRegion, open_late_scope, take_late_regions } from "../late-region-registry.js";
7
8
  import { LATE_BOOT_SCRIPT, late_chunk, late_slot } from "../server/late-wire.js";
8
- import { Handle, RequestEvent } from "@sveltejs/kit";
9
+ import { RequestEvent } from "@sveltejs/kit";
9
10
  /** The server process, as one plugin sees it (handed to `setup`, and on every `ctx.app`). */
10
11
  export interface ServerApp<Options = undefined> {
11
12
  /** The plugin's name (its package name). */
@@ -81,8 +82,6 @@ export interface RequestContext<Options = undefined, State = undefined> {
81
82
  perVisitor(reason: string): void;
82
83
  }
83
84
  type MaybePromise<T> = T | Promise<T>;
84
- /** Kit's `resolve()` options (a `transformPageChunk`, `filterSerializedResponseHeaders`, …). */
85
- export type ResolveOptions = NonNullable<Parameters<Parameters<Handle>[0]["resolve"]>[1]>;
86
85
  /** A request as an AROUND hook sees it: before anything is decided about it. */
87
86
  export interface HandleContext<Options = undefined> {
88
87
  /** Kit's own event, untouched. */
@@ -124,4 +123,4 @@ export declare const STORED_DOCUMENT_META = "<meta name=\"ogygia-stored\" conten
124
123
  /** A deferred region value whose HTML the browser fetches from `url` (a capability the plugin minted,
125
124
  * `''` for an inert one). HTML only: whatever islands the answer carries wake themselves. */
126
125
  export declare function deferredRegion(id: string, url: string): DeferredRegion;
127
- export { type DevtoolsEventInput, LATE_BOOT_SCRIPT, type LateRegion, type RequestFacts, type RouteConfig, type RouteConfigMatcher, type ServerHooks, og_html_region as htmlRegion, late_chunk as lateChunk, late_slot as lateSlot, open_late_scope as openLateScope, region, register_route_config as registerRouteConfig, take_late_regions as takeLateRegions };
126
+ export { type DevtoolsEventInput, type Handle, LATE_BOOT_SCRIPT, type LateRegion, type RequestFacts, type ResolveOptions, type RouteConfig, type RouteConfigMatcher, type ServerHooks, og_html_region as htmlRegion, late_chunk as lateChunk, late_slot as lateSlot, open_late_scope as openLateScope, region, register_route_config as registerRouteConfig, take_late_regions as takeLateRegions };
@@ -1,7 +1,8 @@
1
- import { RequestContext, ResolveOptions, ServerApp, ServerPlugin } from "./index.js";
1
+ import { Handle, ResolveOptions } from "../kit-handle.js";
2
+ import { RequestContext, ServerApp, ServerPlugin } from "./index.js";
2
3
  import { RequestFacts } from "./facts.js";
3
4
  import { CapabilitySigner } from "./capability.js";
4
- import { Handle, RequestEvent } from "@sveltejs/kit";
5
+ import { RequestEvent } from "@sveltejs/kit";
5
6
  /** One plugin's server half as the build lists it (`virtual:ogygia/server-plugins`). */
6
7
  export interface ServerPluginEntry {
7
8
  name: string;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * RESOLVE SINK (reconciler R3) — a hook the runtime installs during an island's hydration to learn
3
+ * which page-scoped ids that island resolves, WITHOUT the hub knowing anything about the DOM.
4
+ * `resolve()` (ref.ts) calls the current sink with each id it freshly remembers in the page scope;
5
+ * the runtime's sink attributes it to the island currently hydrating (region → id-set), so a
6
+ * reconcile nav can dispose exactly the ids owned by REMOVED regions. Null when no island is
7
+ * hydrating.
8
+ *
9
+ * A leaf of its own (ref.ts re-exports the setter): the hydrate core installs the sink around every
10
+ * island, wired values or not, and shares modules with Kit's client graph, which uses the rest of
11
+ * the hub. A shared module ships whole, so reaching the sink through ref.ts sent every island page
12
+ * the hub's registry and minting.
13
+ */
14
+ export declare let resolve_sink: ((id: string) => void) | null;
15
+ export declare function set_resolve_sink(fn: ((id: string) => void) | null): void;
@@ -0,0 +1,19 @@
1
+ // src/ref-sink.ts
2
+ /**
3
+ * RESOLVE SINK (reconciler R3) — a hook the runtime installs during an island's hydration to learn
4
+ * which page-scoped ids that island resolves, WITHOUT the hub knowing anything about the DOM.
5
+ * `resolve()` (ref.ts) calls the current sink with each id it freshly remembers in the page scope;
6
+ * the runtime's sink attributes it to the island currently hydrating (region → id-set), so a
7
+ * reconcile nav can dispose exactly the ids owned by REMOVED regions. Null when no island is
8
+ * hydrating.
9
+ *
10
+ * A leaf of its own (ref.ts re-exports the setter): the hydrate core installs the sink around every
11
+ * island, wired values or not, and shares modules with Kit's client graph, which uses the rest of
12
+ * the hub. A shared module ships whole, so reaching the sink through ref.ts sent every island page
13
+ * the hub's registry and minting.
14
+ */
15
+ let resolve_sink = null;
16
+ function set_resolve_sink(fn) {
17
+ resolve_sink = fn;
18
+ }
19
+ export { resolve_sink, set_resolve_sink };
package/dist/ref.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { set_resolve_sink } from "./ref-sink.js";
1
2
  import { batch, notify, watch, watcher_count } from "./ref-watch.js";
2
3
  /**
3
4
  * THE HUB — ogygia's identity layer. One primitive carries everything that crosses an
@@ -78,7 +79,6 @@ export declare function register_kind(kind: RefKind): void;
78
79
  * silently mis-read as one). Same instance → same id, always.
79
80
  */
80
81
  export declare function mint(value: unknown, only?: ReadonlySet<string>): Ref | undefined;
81
- export declare function set_resolve_sink(fn: ((id: string) => void) | null): void;
82
82
  /**
83
83
  * DISPOSE a whole scope (hub v2, phase D) — tear down every instance in its bucket via the
84
84
  * instance's `kind.dispose`, run any registered scope-disposers (side-effect cleanups like the
@@ -121,4 +121,4 @@ export declare const REF_WIRE_KEY = "OgygiaRef";
121
121
  export declare function ref_reducer(only?: ReadonlySet<string>): (value: unknown) => Ref | undefined;
122
122
  /** Devalue reviver for a seam. Browser passes `remember: true`; the server must pass `false`. */
123
123
  export declare function ref_reviver(remember: boolean): (ref: Ref) => unknown;
124
- export { batch, notify, watch, watcher_count };
124
+ export { batch, notify, set_resolve_sink, watch, watcher_count };
package/dist/ref.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { emit } from "./events/bus.js";
2
2
  import { batch, notify, watch, watcher_count, watchers } from "./ref-watch.js";
3
+ import { resolve_sink, set_resolve_sink } from "./ref-sink.js";
3
4
  // src/ref.ts
4
5
  const DEVTOOLS = typeof __OGYGIA_DEVTOOLS__ !== "undefined" ? __OGYGIA_DEVTOOLS__ : false;
5
6
  const REGISTRY_KEY = Symbol.for("ogygia.hub");
@@ -62,17 +63,6 @@ function mint(value, only) {
62
63
  return ref;
63
64
  }
64
65
  }
65
- /**
66
- * RESOLVE SINK (reconciler R3) — a hook the runtime installs during an island's hydration to learn
67
- * which page-scoped ids that island resolves, WITHOUT the hub knowing anything about the DOM.
68
- * `resolve()` calls the current sink with each id it freshly remembers in the page scope; the
69
- * runtime's sink attributes it to the island currently hydrating (region → id-set), so a reconcile
70
- * nav can dispose exactly the ids owned by REMOVED regions. Null when no island is hydrating.
71
- */
72
- let resolve_sink = null;
73
- function set_resolve_sink(fn) {
74
- resolve_sink = fn;
75
- }
76
66
  /** The live bucket for a scope (never 'request' — that scope is never stored). */
77
67
  function bucket(reg, scope) {
78
68
  let b = reg.instances.get(scope);
@@ -1,7 +1,6 @@
1
1
  import { frameAddress } from "../frame.js";
2
2
  import { kit_hydrates_page } from "./kit-boot.js";
3
3
  import { init_shadow_registries, parse_region_html, restore_adopt_sheets, restore_markup } from "./parse-html.js";
4
- import { wire_classes_for } from "./wire-classes.js";
5
4
  import { mark_region_changed, region_changed, unwatch_region, watch_region } from "./drift-watch.js";
6
5
  import { runtime_session } from "./session.js";
7
6
  import { RegionAnswerRefused, capability_expired, deny_of, is_allowed_region_endpoint, is_document_answer, is_redirected_answer, is_same_origin_response, island_module_url, renewal_url } from "./region-endpoint-url.js";
@@ -10,7 +9,6 @@ import { is_awake, is_deferred, is_frozen, ours_on_kit_document, phase2_hydrate_
10
9
  import { slots } from "./slots.js";
11
10
  import { KEEP_FALLBACK_HTML } from "../keep-fallback-marker.js";
12
11
  import { PAGE_FACTS_SELECTOR } from "../page-facts-script.js";
13
- import { NAV_HANDLE_KEY, mpa_nav, publish_nav } from "./nav-handle.js";
14
12
  import { link_boot } from "./boot-link.js";
15
13
  import { background_start, hydrate_settled, hydrate_started, hydrate_turn, register_region, unregister_region, yield_task } from "./schedule.js";
16
14
  import { once_visible } from "./observe.js";
@@ -24,6 +22,7 @@ import { on_svelte_warning, set_hydrating, tap_svelte_warnings } from "./svelte-
24
22
  import { emit } from "../events/bus.js";
25
23
  import { ingest_server_events, install_window_sink } from "../events/sinks.js";
26
24
  import { CLIENT_PLUGINS, boot_client_plugins, client_region_mount, client_region_unmount } from "./client-plugins.js";
25
+ import { FRAMES } from "virtual:ogygia/runtime-flags";
27
26
  // src/runtime/core.ts
28
27
  const DEVTOOLS = typeof __OGYGIA_DEVTOOLS__ !== "undefined" ? __OGYGIA_DEVTOOLS__ : false;
29
28
  const BEACON = typeof __OGYGIA_BEACON__ !== "undefined" ? __OGYGIA_BEACON__ : true;
@@ -353,7 +352,7 @@ var OgygiaRegion = class OgygiaRegion extends HTMLElement {
353
352
  connected_regions.add(this);
354
353
  pin_region_urls(this);
355
354
  if (this.hasAttribute("live")) return;
356
- if (this.#minted_endpoint === null && is_deferred(this)) {
355
+ if (FRAMES && this.#minted_endpoint === null && is_deferred(this)) {
357
356
  const minted = this.getAttribute("endpoint");
358
357
  if (minted) this.#minted_endpoint = minted;
359
358
  else {
@@ -427,6 +426,7 @@ var OgygiaRegion = class OgygiaRegion extends HTMLElement {
427
426
  * nothing left to warm.
428
427
  */
429
428
  #prefetch_html() {
429
+ if (!FRAMES) return;
430
430
  if (this.#done || this.#fetching || !this.isConnected) return;
431
431
  const endpoint = this.#endpoint();
432
432
  if (!endpoint || !is_allowed_region_endpoint(endpoint)) return;
@@ -592,6 +592,7 @@ var OgygiaRegion = class OgygiaRegion extends HTMLElement {
592
592
  * island), schedule phase-2 hydrate — coalescing matching schedules to immediate load.
593
593
  */
594
594
  async #server() {
595
+ if (!FRAMES) return;
595
596
  const endpoint = this.#endpoint();
596
597
  if (endpoint && !this.#frame_unsub) {
597
598
  const address = this.#frame_address = frameAddress(endpoint);
@@ -718,6 +719,7 @@ var OgygiaRegion = class OgygiaRegion extends HTMLElement {
718
719
  * not (the endpoint answers `cache-control: private, max-age=30`, and stale is the whole point).
719
720
  */
720
721
  async #fetch_html(opts = {}) {
722
+ if (!FRAMES) return;
721
723
  if (this.#fetching || this.#done && !opts.revalidate) return;
722
724
  const endpoint = this.#endpoint();
723
725
  if (!endpoint) return;
@@ -879,7 +881,7 @@ var OgygiaRegion = class OgygiaRegion extends HTMLElement {
879
881
  [mod, core] = await Promise.all([
880
882
  load_island(entry, cls()),
881
883
  hydrate_core(),
882
- wire_classes_for(this)
884
+ slots.wire_classes?.(this)
883
885
  ]);
884
886
  } finally {
885
887
  release();
@@ -952,6 +954,7 @@ var OgygiaRegion = class OgygiaRegion extends HTMLElement {
952
954
  * - **Later tick, module changed:** unmount, swap, re-hydrate.
953
955
  */
954
956
  async applyLive(desc) {
957
+ if (!FRAMES) return;
955
958
  this.setAttribute("render", "defer");
956
959
  this.setAttribute("entry", desc.module || desc.id);
957
960
  if (desc.url) this.setAttribute("endpoint", desc.url);
@@ -1122,7 +1125,6 @@ function apply_dev_head_region_css() {
1122
1125
  });
1123
1126
  }
1124
1127
  function boot(installers = [], client_plugins = []) {
1125
- if (typeof document !== "undefined" && !(NAV_HANDLE_KEY in globalThis)) publish_nav(mpa_nav());
1126
1128
  runtime_session.adopt_document_head();
1127
1129
  link_boot();
1128
1130
  for (const install of installers) install();
@@ -5,13 +5,15 @@ import { install as install$2 } from "./morph.js";
5
5
  import { install as install$3 } from "./interaction.js";
6
6
  import { install as install$4 } from "./form-continuity.js";
7
7
  import { install as install$5 } from "./router.js";
8
+ import { install as install$6 } from "./wire-classes.js";
9
+ import { install as install$7 } from "./nav-handle.js";
8
10
  // src/runtime/full.ts
9
11
  /**
10
12
  * Kitchen-sink browser runtime — every BOOT-phase feature installed. The dev / no-marks entry: the
11
13
  * ogygia compiler injects `bootDev()` (see the plugin's `virtual:ogygia-runtime`); a per-app production
12
14
  * build instead emits a generated entry (see `vite/runtime-entry.ts`) that boots only the features it
13
15
  * uses. Both boot through {@link ./core.js core} in the same {@link ../vite/runtime-entry.js FEATURE_ORDER}.
14
- * HYDRATE-phase features (context, live, wire, remote-seeds) are never in the boot, dev included: the
16
+ * HYDRATE-phase features (context, live, wire, remote-seeds, page) are never in the boot, dev included: the
15
17
  * hydrate core installs them from `virtual:ogygia/hydrate-features` (every one, in dev).
16
18
  *
17
19
  * Exposed as an explicit FUNCTION, not a top-level side effect: the compiler always calls it, so the
@@ -21,11 +23,13 @@ import { install as install$5 } from "./router.js";
21
23
  * Idempotent via {@link ./core.js boot}. */
22
24
  function bootDev(client_plugins = []) {
23
25
  boot([
26
+ install$6,
24
27
  install,
25
28
  install$1,
26
29
  install$2,
27
30
  install$3,
28
31
  install$4,
32
+ install$7,
29
33
  install$5
30
34
  ], client_plugins);
31
35
  }
@@ -1,6 +1,7 @@
1
- import { seed_page_once, seed_remote_once } from "./seeds.js";
2
- import { kit_page_thread } from "./kit-page-thread.svelte.js";
3
1
  import { Component } from "svelte";
2
+ export declare const seed_page_once: () => void;
3
+ export declare const seed_remote_once: () => void;
4
+ export declare const kit_page_thread: () => Promise<void> | null;
4
5
  /** A hydrated island as the element holds it: how to tear it down, and (keep / live hosts) how to
5
6
  * push the next page's props into it. */
6
7
  export type IslandHandle = {
@@ -97,5 +98,4 @@ export declare function hydrate_island(region: HTMLElement, entry: string, mod:
97
98
  * HTML comes from svelte `render()` (both envelope layers) — same as the deferred swap path, so it
98
99
  * is NOT wrapped in extra `[..]` hydration comments. `null` when the live feature is absent.
99
100
  */
100
- export declare function hydrate_live(region: HTMLElement, entry: string, mod: IslandModule, props: Record<string, unknown>): IslandHandle | null;
101
- export { kit_page_thread, seed_page_once, seed_remote_once };
101
+ export declare function hydrate_live(region: HTMLElement, entry: string, mod: IslandModule, props: Record<string, unknown>): IslandHandle | null;
@@ -1,13 +1,12 @@
1
1
  import { set_current_region, set_foreign_hydrate } from "../current-region.js";
2
2
  import { capture_region_ids } from "./region-ids.js";
3
3
  import { foreign_region_prop_revivers } from "./foreign-props.js";
4
- import { SEED_REF_KEY, seed_ref_reviver } from "../seed-refs.js";
5
- import { parse_sidecar_text, seed_data_of, seed_page_once, seed_remote_once } from "./seeds.js";
4
+ import { SEED_REF_KEY, seed_ref_reviver } from "../seed-ref-reviver.js";
5
+ import { parse_sidecar_text } from "./wire-format.js";
6
6
  import { is_deferred, ours_on_kit_document, region_ssr_truncated } from "./region-attrs.js";
7
7
  import { boot_link, slots } from "./slots.js";
8
8
  import { keep_host_classes, note_host_classes } from "./host-classes.js";
9
9
  import { emit } from "../events/bus.js";
10
- import { kit_page_thread } from "./kit-page-thread.svelte.js";
11
10
  import { hydrate, unmount } from "svelte";
12
11
  import NestedProvider from "../NestedProvider.svelte";
13
12
  import { install } from "virtual:ogygia/hydrate-features";
@@ -24,6 +23,9 @@ import { install } from "virtual:ogygia/hydrate-features";
24
23
  * Stateless towards the element: the element owns its handle (`IslandHandle`) and its flags; this
25
24
  * module owns the how.
26
25
  */
26
+ const seed_page_once = () => slots.page?.seed_page_once();
27
+ const seed_remote_once = () => slots.remoteSeeds?.seed_once();
28
+ const kit_page_thread = () => slots.page?.kit_page_thread() ?? null;
27
29
  install();
28
30
  const kit_hydrates_page = () => boot_link().kit_hydrates_page();
29
31
  const props_sidecar_of = (region) => boot_link().props_sidecar_of(region);
@@ -335,9 +337,10 @@ function read_region_props(region, foreign = false) {
335
337
  if (!sidecar) return {};
336
338
  const base = foreign ? foreign_region_prop_revivers() : region_prop_revivers();
337
339
  const doc = sidecar.ownerDocument;
338
- const revivers = foreign ? base : {
340
+ const page = slots.page;
341
+ const revivers = foreign || !page ? base : {
339
342
  ...base,
340
- [SEED_REF_KEY]: seed_ref_reviver(() => seed_data_of(doc))
343
+ [SEED_REF_KEY]: seed_ref_reviver(() => page.seed_data_of(doc))
341
344
  };
342
345
  return parse_sidecar_text(sidecar, revivers);
343
346
  }
@@ -16,6 +16,9 @@ export type NavHandle = {
16
16
  afterNavigate(fn: AfterNavigateCallback): () => void;
17
17
  bust_page_cache(): void;
18
18
  };
19
+ /** Feature entry (`mpa-nav`, boot phase): the MPA handle, first — the router's install, later in
20
+ * the feature order, replaces it. */
21
+ export declare function install(): void;
19
22
  /** Publish the document's navigation handle (the router's replaces the MPA one). */
20
23
  export declare function publish_nav(handle: NavHandle): void;
21
24
  /**
@@ -13,16 +13,23 @@ import { speculate_url } from "./speculate-hint.js";
13
13
  * from the global realm (the same pattern as the Kit page bridge, `Symbol.for('ogygia.kit-page')`),
14
14
  * and `test/runtime-boot-svelte-free.test.ts` pins that no island-side module imports a boot module.
15
15
  *
16
- * Who publishes it, always, on a document ogygia boots:
16
+ * Who publishes it, on a document ogygia boots:
17
17
  * - the ROUTER feature (./router.ts `install`) — the SPA router's API;
18
- * - otherwise core (`boot`) publishes {@link mpa_nav}: the browser navigates, preloads become
19
- * Speculation Rules hints, and `invalidateAll` is still the soft seed refresh (./router-nav.ts,
20
- * loaded on first use) — exactly what `router: false` apps got before.
18
+ * - otherwise the `mpa-nav` feature ({@link install}) publishes {@link mpa_nav}: the browser
19
+ * navigates, preloads become Speculation Rules hints, and `invalidateAll` is still the soft seed
20
+ * refresh (./router-nav.ts, loaded on first use) — exactly what `router: false` apps got before.
21
+ * Installed only when island code can reach the handle (the `page` mark: `$app/navigation`,
22
+ * `ogygia/app`, or anything the build could not follow): with none of those, nothing reads it.
21
23
  *
22
24
  * Island code reads it as `globalThis[Symbol.for('ogygia.nav')]` (the key is repeated there on
23
25
  * purpose: importing {@link NAV_HANDLE_KEY} would be the very edge this module exists to avoid).
24
26
  */
25
27
  const NAV_HANDLE_KEY = Symbol.for("ogygia.nav");
28
+ /** Feature entry (`mpa-nav`, boot phase): the MPA handle, first — the router's install, later in
29
+ * the feature order, replaces it. */
30
+ function install() {
31
+ if (typeof document !== "undefined" && !(NAV_HANDLE_KEY in globalThis)) publish_nav(mpa_nav());
32
+ }
26
33
  /** Publish the document's navigation handle (the router's replaces the MPA one). */
27
34
  function publish_nav(handle) {
28
35
  globalThis[NAV_HANDLE_KEY] = handle;
@@ -68,4 +75,4 @@ function mpa_nav() {
68
75
  bust_page_cache() {}
69
76
  };
70
77
  }
71
- export { NAV_HANDLE_KEY, mpa_nav, publish_nav };
78
+ export { NAV_HANDLE_KEY, install, mpa_nav, publish_nav };
@@ -1,8 +1,8 @@
1
- import { set_resolve_sink } from "../ref.js";
1
+ import { set_resolve_sink } from "../ref-sink.js";
2
2
  // src/runtime/region-ids.ts
3
3
  /**
4
4
  * R3 OWNERSHIP: which page-scoped hub ids each region's island resolved during hydration. The
5
- * hydrate core installs a resolve-sink (ref.set_resolve_sink) around each island's sync prop +
5
+ * hydrate core installs a resolve-sink (ref-sink.ts `set_resolve_sink`) around each island's sync prop +
6
6
  * context decode that funnels ids here, keyed by the region element; the reconciler (a navigation)
7
7
  * disposes exactly the ids of REMOVED regions. WeakMap → a removed element's set is GC'd with it.
8
8
  * Its own module so the boot-time reconciler and the lazily loaded hydrate core share one map
@@ -1,4 +1,4 @@
1
- import { slots } from "./slots.js";
1
+ import { boot_link, slots } from "./slots.js";
2
2
  import { seed_query_responses } from "../shims/kit-remote/client-stub.js";
3
3
  import { clear_remote_instances, clear_remote_seeds } from "../shims/kit-remote/remote-cache.js";
4
4
  // src/runtime/remote-seeds.ts
@@ -7,12 +7,23 @@ import { clear_remote_instances, clear_remote_seeds } from "../shims/kit-remote/
7
7
  * `application/ogygia-remote` side-channel (zero-flash hydration), and clear it across SPA swaps.
8
8
  *
9
9
  * A HYDRATE-phase feature (link/runtime-entry.ts): installed by the hydrate core, never the boot —
10
- * only `seeds.ts` (hydrate core) reads the slot, and the decoder imports the APP's transport codecs
11
- * (its own `src/hooks.ts`), which reach Svelte.
10
+ * only the hydrate core (and the navigation's seeds.ts) reads the slot, and the decoder imports the
11
+ * APP's transport codecs (its own `src/hooks.ts`), which reach Svelte. Installed only in an app that
12
+ * has a remote function at all.
12
13
  */
14
+ const REMOTE_SEED_SELECTOR = "script[type=\"application/ogygia-remote\"]";
15
+ function seed_once() {
16
+ const session = boot_link().runtime_session;
17
+ if (session.remote_seeded) return;
18
+ session.mark_remote_seeded();
19
+ if (typeof document === "undefined") return;
20
+ const text = document.querySelector(REMOTE_SEED_SELECTOR)?.textContent;
21
+ if (text) seed_query_responses(text);
22
+ }
13
23
  /** Feature entry: fill the `remoteSeeds` slot. */
14
24
  function install() {
15
25
  slots.remoteSeeds = {
26
+ seed_once,
16
27
  seed_query_responses,
17
28
  clear_remote_seeds,
18
29
  clear_remote_instances
@@ -1,8 +1,14 @@
1
+ import { parse_sidecar_text } from "./wire-format.js";
1
2
  import { PageSnapshot } from "../shims/page-store.svelte.js";
2
3
  export declare const PAGE_SEED_SELECTOR = "script[type=\"application/ogygia-page\"]";
3
- /** A seed or props script's payload, in its lane (wire-format.ts): JSON when the server marked
4
- * it plain, devalue with `revivers` otherwise. Every seed / props read goes through here. */
5
- export declare function parse_sidecar_text(el: Element, revivers?: Record<string, (d: never) => unknown>): unknown;
4
+ /**
5
+ * The PAGE feature (link/runtime-entry.ts, hydrate phase): what island code sees of its page — the
6
+ * page seed behind `$app/state` / `$app/stores`, the seed references in island props, and, on a
7
+ * Kit-hydrated document, the wait for Kit's own page. Installed only when island code can reach
8
+ * Kit's world (the driver's island-closure walk, failing open on anything it cannot follow): an app
9
+ * whose islands never read their page gets no seed from the server and ships none of this.
10
+ */
11
+ export declare function install(): void;
6
12
  type PageSeed = Partial<Omit<PageSnapshot, "url"> & {
7
13
  url: string | URL;
8
14
  }>;
@@ -19,7 +25,6 @@ export declare function page_seed_of(doc: Document): PageSeed | null;
19
25
  * on first ask), the once-parsed seed for a foreign one. `undefined` when the document has no seed.
20
26
  */
21
27
  export declare function seed_data_of(doc: Document): unknown;
22
- export declare function seed_remote_once(): void;
23
28
  /** Document-level page seed (one script from ogygiaHandle) — once per document; then any page facts
24
29
  * an answer brought since. Every hydrate calls it, before the island reads `page`. */
25
30
  export declare function seed_page_once(): void;
@@ -47,4 +52,4 @@ export declare function prepare_spa_document(): void;
47
52
  * already spent (`once`) — that reuse never opens SSE again and leaves "connecting…".
48
53
  */
49
54
  export declare function finish_spa_document(): void;
50
- export {};
55
+ export { parse_sidecar_text };
@@ -1,8 +1,9 @@
1
- import { parse_wire_text, wire_is_json } from "./wire-format.js";
1
+ import { parse_sidecar_text, parse_wire_text } from "./wire-format.js";
2
2
  import { page_state, reset_page, set_page } from "../shims/page-store.svelte.js";
3
3
  import { kit_bridge } from "../shims/kit-bridge.js";
4
4
  import { install_page_defer, page_defer_revivers } from "./page-defer.js";
5
5
  import { transport_decoders } from "./app-transport.js";
6
+ import { kit_page_thread } from "./kit-page-thread.svelte.js";
6
7
  import { boot_link, slots } from "./slots.js";
7
8
  // src/runtime/seeds.ts
8
9
  /**
@@ -38,10 +39,19 @@ const session = () => boot_link().runtime_session;
38
39
  const DEVTOOLS = typeof __OGYGIA_DEVTOOLS__ !== "undefined" ? __OGYGIA_DEVTOOLS__ : false;
39
40
  const PAGE_SEED_SELECTOR = "script[type=\"application/ogygia-page\"]";
40
41
  const REMOTE_SEED_SELECTOR = "script[type=\"application/ogygia-remote\"]";
41
- /** A seed or props script's payload, in its lane (wire-format.ts): JSON when the server marked
42
- * it plain, devalue with `revivers` otherwise. Every seed / props read goes through here. */
43
- function parse_sidecar_text(el, revivers) {
44
- return parse_wire_text(el.textContent ?? "", wire_is_json(el), revivers);
42
+ /**
43
+ * The PAGE feature (link/runtime-entry.ts, hydrate phase): what island code sees of its page — the
44
+ * page seed behind `$app/state` / `$app/stores`, the seed references in island props, and, on a
45
+ * Kit-hydrated document, the wait for Kit's own page. Installed only when island code can reach
46
+ * Kit's world (the driver's island-closure walk, failing open on anything it cannot follow): an app
47
+ * whose islands never read their page gets no seed from the server and ships none of this.
48
+ */
49
+ function install() {
50
+ slots.page = {
51
+ seed_page_once,
52
+ seed_data_of,
53
+ kit_page_thread
54
+ };
45
55
  }
46
56
  let parses = 0;
47
57
  /** Test seam: how many page-seed scripts were parsed since boot (one per document is the law). */
@@ -106,12 +116,6 @@ function apply_remote_seed_text(text) {
106
116
  if (!text) return;
107
117
  slots.remoteSeeds?.seed_query_responses(text);
108
118
  }
109
- function seed_remote_once() {
110
- if (session().remote_seeded) return;
111
- session().mark_remote_seeded();
112
- if (typeof document === "undefined") return;
113
- apply_remote_seed_text(document.querySelector(REMOTE_SEED_SELECTOR)?.textContent);
114
- }
115
119
  /** Document-level page seed (one script from ogygiaHandle) — once per document; then any page facts
116
120
  * an answer brought since. Every hydrate calls it, before the island reads `page`. */
117
121
  function seed_page_once() {
@@ -205,4 +209,4 @@ function prepare_spa_document() {
205
209
  function finish_spa_document() {
206
210
  slots.remoteSeeds?.clear_remote_instances();
207
211
  }
208
- export { PAGE_SEED_SELECTOR, apply_soft_invalidate_doc, finish_spa_document, page_seed_of, parse_sidecar_text, prepare_spa_document, seed_data_of, seed_page_once, seed_parse_count, seed_remote_once };
212
+ export { PAGE_SEED_SELECTOR, apply_soft_invalidate_doc, finish_spa_document, install, page_seed_of, parse_sidecar_text, prepare_spa_document, seed_data_of, seed_page_once, seed_parse_count };
@@ -64,10 +64,22 @@ export type WireOps = {
64
64
  resolve: (ref: never, remember: boolean) => unknown;
65
65
  };
66
66
  export type RemoteSeedOps = {
67
+ /** this document's remote seed into the query cache, once per document */
68
+ seed_once(): void;
67
69
  seed_query_responses(text: string): void;
68
70
  clear_remote_seeds(): void;
69
71
  clear_remote_instances(): void;
70
72
  };
73
+ /** The page as island code sees it, filled by the `page` feature (./seeds.ts). Null when no island
74
+ * code can read its page: the server ships no seed then, so there is nothing to parse or wait for. */
75
+ export type PageOps = {
76
+ /** the document's page seed into the page store (once), then any page facts answers brought */
77
+ seed_page_once(): void;
78
+ /** the `page.data` a seed reference in island props resolves against */
79
+ seed_data_of(doc: Document): unknown;
80
+ /** on a Kit-hydrated document: the wait for Kit to apply its page (`null` once applied) */
81
+ kit_page_thread(): Promise<void> | null;
82
+ };
71
83
  /**
72
84
  * The client frame store, filled by the `frames` feature. Core reads it ONLY on the deferred /
73
85
  * live / SWR-lake paths (a region with a signed `endpoint`), so a plain load-hydrated app never
@@ -156,7 +168,11 @@ export type Slots = {
156
168
  /** a kept island's host (the live feature fills it beside `live`) */
157
169
  keep: Component<Record<string, unknown>> | null;
158
170
  wire: WireOps | null;
171
+ /** The transportable classes an island's props, context and seeds need, loaded before it
172
+ * decodes (./wire-classes.ts). Null in an app without transportable classes: nothing to load. */
173
+ wire_classes: ((region: Element) => Promise<unknown> | null) | null;
159
174
  remoteSeeds: RemoteSeedOps | null;
175
+ page: PageOps | null;
160
176
  frames: FrameOps | null;
161
177
  nav: NavOps | null;
162
178
  /**
@@ -22,7 +22,9 @@ const slots = {
22
22
  live: null,
23
23
  keep: null,
24
24
  wire: null,
25
+ wire_classes: null,
25
26
  remoteSeeds: null,
27
+ page: null,
26
28
  frames: null,
27
29
  nav: null,
28
30
  context: null,
@@ -1,3 +1,5 @@
1
+ /** Feature entry: fill the `wire_classes` slot. */
2
+ export declare function install(): void;
1
3
  /** The loads this island's props, context and page seeds need, or `null` when none (the common
2
4
  * case: an app without transportables, or an island that carries none). */
3
5
  export declare function wire_classes_for(region: Element): Promise<unknown> | null;
@@ -1,5 +1,6 @@
1
1
  import { props_sidecar_of } from "./sidecar.js";
2
2
  import { yield_task } from "./schedule.js";
3
+ import { slots } from "./slots.js";
3
4
  import { wire_all, wire_loaders } from "virtual:ogygia/transportables";
4
5
  // src/runtime/wire-classes.ts
5
6
  /**
@@ -14,7 +15,15 @@ import { wire_all, wire_loaders } from "virtual:ogygia/transportables";
14
15
  * The small texts are checked for each class's tag path (`"<path>#`). An app that sends wired values
15
16
  * through Kit's `transport` (`wire_all`: remote functions, loads) gets every class with every island,
16
17
  * as before: those values decode synchronously whenever they arrive. Each module loads once.
18
+ *
19
+ * A BOOT-phase feature (link/runtime-entry.ts), installed only when the app has transportable
20
+ * classes: without one the loader map is empty, but the bundler cannot see that through the virtual
21
+ * module, so this code shipped to every island page for a lookup that always found nothing.
17
22
  */
23
+ /** Feature entry: fill the `wire_classes` slot. */
24
+ function install() {
25
+ slots.wire_classes = wire_classes_for;
26
+ }
18
27
  const loaders = wire_loaders;
19
28
  const paths = Object.keys(loaders);
20
29
  const loading = /* @__PURE__ */ new Map();
@@ -66,4 +75,4 @@ function wire_classes_for(region) {
66
75
  for (const p of want) pending.push(load(p));
67
76
  return Promise.all(pending);
68
77
  }
69
- export { wire_classes_for };
78
+ export { install, wire_classes_for };
@@ -4,4 +4,7 @@ export declare const WIRE_FORMAT_JSON = "json";
4
4
  export declare function wire_is_json(el: Element): boolean;
5
5
  /** Parse a side-channel's text in its lane: `JSON.parse` for the JSON lane (nothing to revive by
6
6
  * construction), devalue `parse` with the caller's revivers otherwise. */
7
- export declare function parse_wire_text(text: string, json: boolean, revivers?: Record<string, (value: never) => unknown>): unknown;
7
+ export declare function parse_wire_text(text: string, json: boolean, revivers?: Record<string, (value: never) => unknown>): unknown;
8
+ /** A seed or props script's payload, in its lane: JSON when the server marked it plain, devalue
9
+ * with `revivers` otherwise. Every seed / props read goes through here. */
10
+ export declare function parse_sidecar_text(el: Element, revivers?: Record<string, (d: never) => unknown>): unknown;
@@ -19,4 +19,9 @@ function wire_is_json(el) {
19
19
  function parse_wire_text(text, json, revivers) {
20
20
  return json ? JSON.parse(text) : parse(text, revivers);
21
21
  }
22
- export { WIRE_FORMAT_ATTR, WIRE_FORMAT_JSON, parse_wire_text, wire_is_json };
22
+ /** A seed or props script's payload, in its lane: JSON when the server marked it plain, devalue
23
+ * with `revivers` otherwise. Every seed / props read goes through here. */
24
+ function parse_sidecar_text(el, revivers) {
25
+ return parse_wire_text(el.textContent ?? "", wire_is_json(el), revivers);
26
+ }
27
+ export { WIRE_FORMAT_ATTR, WIRE_FORMAT_JSON, parse_sidecar_text, parse_wire_text, wire_is_json };