@ogygia/core 0.12.2 → 0.13.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 (227) hide show
  1. package/dist/LateIsland.svelte +3 -2
  2. package/dist/LateIslandAwait.svelte +6 -2
  3. package/dist/Region.svelte +22 -33
  4. package/dist/boundary.d.ts +0 -2
  5. package/dist/boundary.js +8 -6
  6. package/dist/compiler/debarrel/barrel.d.ts +15 -8
  7. package/dist/compiler/debarrel/barrel.js +92 -45
  8. package/dist/compiler/debarrel/plugin.js +41 -17
  9. package/dist/compiler/dev/dev-hmr.d.ts +3 -0
  10. package/dist/compiler/dev/dev-hmr.js +3 -0
  11. package/dist/compiler/driver.js +11 -4
  12. package/dist/compiler/flags.js +21 -7
  13. package/dist/compiler/fouc-css.js +5 -1
  14. package/dist/compiler/kit.js +5 -1
  15. package/dist/compiler/link/build-output.js +12 -3
  16. package/dist/compiler/macros/bake.js +18 -4
  17. package/dist/compiler/macros/dollar.js +6 -1
  18. package/dist/compiler/macros/source.js +6 -1
  19. package/dist/compiler/macros/store.js +10 -2
  20. package/dist/compiler/macros/wire.js +6 -1
  21. package/dist/compiler/parse/scan.js +10 -3
  22. package/dist/compiler/region/transform.js +188 -74
  23. package/dist/context-bridge.js +13 -5
  24. package/dist/context.d.ts +9 -0
  25. package/dist/context.js +30 -1
  26. package/dist/document.d.ts +1 -1
  27. package/dist/document.js +7 -1
  28. package/dist/early-holes-head.d.ts +5 -0
  29. package/dist/early-holes-head.js +20 -0
  30. package/dist/early-holes.d.ts +34 -0
  31. package/dist/early-holes.js +51 -0
  32. package/dist/entry-hydrate.js +9 -12
  33. package/dist/events/bus.js +7 -2
  34. package/dist/events/schema.d.ts +2 -2
  35. package/dist/fn-transport.js +4 -3
  36. package/dist/hooks.js +94 -163
  37. package/dist/insights/page-assets.js +11 -3
  38. package/dist/insights/page-insights.d.ts +0 -2
  39. package/dist/insights/page-insights.js +14 -10
  40. package/dist/insights/session-insights.js +2 -2
  41. package/dist/internal-instruments.d.ts +3 -1
  42. package/dist/internal-instruments.js +3 -1
  43. package/dist/internal-server.d.ts +3 -2
  44. package/dist/internal-server.js +3 -2
  45. package/dist/internal.d.ts +1 -2
  46. package/dist/internal.js +1 -2
  47. package/dist/keep-fallback.d.ts +0 -2
  48. package/dist/keep-fallback.js +4 -5
  49. package/dist/live-transport.js +3 -2
  50. package/dist/messages/build.d.ts +585 -0
  51. package/dist/messages/build.js +800 -0
  52. package/dist/messages/core.d.ts +464 -0
  53. package/dist/messages/core.js +843 -0
  54. package/dist/messages/define.d.ts +62 -0
  55. package/dist/messages/define.js +49 -0
  56. package/dist/messages/index.d.ts +6 -0
  57. package/dist/messages/index.js +6 -0
  58. package/dist/messages/island.d.ts +4 -0
  59. package/dist/messages/island.js +34 -0
  60. package/dist/messages/lazy.d.ts +3 -0
  61. package/dist/messages/lazy.js +22 -0
  62. package/dist/messages/prose.d.ts +15 -0
  63. package/dist/messages/prose.js +43 -0
  64. package/dist/messages/server.d.ts +142 -0
  65. package/dist/messages/server.js +246 -0
  66. package/dist/nested.d.ts +3 -0
  67. package/dist/nested.js +8 -1
  68. package/dist/plugin/css.js +14 -13
  69. package/dist/plugin/facts.d.ts +2 -0
  70. package/dist/plugin/facts.js +10 -6
  71. package/dist/plugin/hooks.d.ts +7 -17
  72. package/dist/plugin/hooks.js +32 -4
  73. package/dist/plugin/index.d.ts +9 -2
  74. package/dist/plugin/request-scope.d.ts +20 -0
  75. package/dist/plugin/request-scope.js +149 -0
  76. package/dist/plugin/server.d.ts +20 -2
  77. package/dist/plugin/server.js +98 -28
  78. package/dist/plugin/testing.d.ts +6 -0
  79. package/dist/plugin/testing.js +14 -2
  80. package/dist/policies.d.ts +21 -0
  81. package/dist/policies.js +66 -0
  82. package/dist/preference.js +6 -1
  83. package/dist/preload.js +6 -9
  84. package/dist/ref-watch.d.ts +0 -10
  85. package/dist/ref-watch.js +14 -1
  86. package/dist/ref.js +17 -5
  87. package/dist/region-snippet.js +9 -3
  88. package/dist/region.js +2 -1
  89. package/dist/report-thrown.d.ts +9 -0
  90. package/dist/report-thrown.js +15 -0
  91. package/dist/rules.d.ts +17 -0
  92. package/dist/rules.js +57 -0
  93. package/dist/runtime/beacon.d.ts +0 -4
  94. package/dist/runtime/beacon.js +58 -43
  95. package/dist/runtime/boot-link.js +19 -2
  96. package/dist/runtime/client-hooks.js +2 -1
  97. package/dist/runtime/concurrency.d.ts +16 -5
  98. package/dist/runtime/concurrency.js +123 -7
  99. package/dist/runtime/core.d.ts +3 -1
  100. package/dist/runtime/core.js +718 -384
  101. package/dist/runtime/devalue-lane.d.ts +13 -0
  102. package/dist/runtime/devalue-lane.js +49 -0
  103. package/dist/runtime/disclose-version.d.ts +1 -0
  104. package/dist/runtime/disclose-version.js +14 -0
  105. package/dist/runtime/early-holes.d.ts +4 -0
  106. package/dist/runtime/early-holes.js +40 -0
  107. package/dist/runtime/entry-locations.js +40 -49
  108. package/dist/runtime/foreign-props.d.ts +0 -7
  109. package/dist/runtime/foreign-props.js +2 -1
  110. package/dist/runtime/form-continuity.d.ts +2 -2
  111. package/dist/runtime/form-continuity.js +30 -8
  112. package/dist/runtime/frame-nav.d.ts +7 -1
  113. package/dist/runtime/frame-nav.js +84 -18
  114. package/dist/runtime/frame-store.d.ts +25 -16
  115. package/dist/runtime/frame-store.js +124 -98
  116. package/dist/runtime/frames.js +8 -4
  117. package/dist/runtime/hole-facts.js +6 -7
  118. package/dist/runtime/hole-fetch.d.ts +17 -0
  119. package/dist/runtime/hole-fetch.js +47 -0
  120. package/dist/runtime/hydrate-core.d.ts +4 -4
  121. package/dist/runtime/hydrate-core.js +80 -64
  122. package/dist/runtime/import-again.d.ts +33 -0
  123. package/dist/runtime/import-again.js +217 -0
  124. package/dist/runtime/interaction-replay.d.ts +5 -3
  125. package/dist/runtime/interaction-replay.js +6 -6
  126. package/dist/runtime/interaction.d.ts +14 -3
  127. package/dist/runtime/interaction.js +46 -23
  128. package/dist/runtime/kit-page-thread.svelte.d.ts +2 -1
  129. package/dist/runtime/kit-page-thread.svelte.js +44 -20
  130. package/dist/runtime/lakes.d.ts +1 -1
  131. package/dist/runtime/lakes.js +18 -5
  132. package/dist/runtime/live.d.ts +1 -2
  133. package/dist/runtime/live.js +6 -8
  134. package/dist/runtime/load-scheduler.d.ts +29 -18
  135. package/dist/runtime/load-scheduler.js +236 -136
  136. package/dist/runtime/nav-handle.js +25 -6
  137. package/dist/runtime/navigation.d.ts +24 -0
  138. package/dist/runtime/navigation.js +24 -0
  139. package/dist/runtime/ownership.d.ts +1 -1
  140. package/dist/runtime/page-defer.js +3 -3
  141. package/dist/runtime/parse-html.d.ts +15 -4
  142. package/dist/runtime/parse-html.js +24 -5
  143. package/dist/runtime/pushable-props.svelte.d.ts +21 -0
  144. package/dist/runtime/pushable-props.svelte.js +37 -0
  145. package/dist/runtime/region-endpoint-url.js +4 -6
  146. package/dist/runtime/region-machines.d.ts +111 -0
  147. package/dist/runtime/region-machines.js +97 -0
  148. package/dist/runtime/region-sheets.d.ts +31 -0
  149. package/dist/runtime/region-sheets.js +49 -0
  150. package/dist/runtime/restore-report.d.ts +1 -0
  151. package/dist/runtime/restore-report.js +36 -0
  152. package/dist/runtime/restore.d.ts +5 -2
  153. package/dist/runtime/restore.js +33 -14
  154. package/dist/runtime/router-nav.d.ts +32 -12
  155. package/dist/runtime/router-nav.js +320 -228
  156. package/dist/runtime/router.d.ts +83 -19
  157. package/dist/runtime/router.js +297 -129
  158. package/dist/runtime/rules.d.ts +35 -0
  159. package/dist/runtime/rules.js +137 -0
  160. package/dist/runtime/schedule.d.ts +6 -3
  161. package/dist/runtime/schedule.js +172 -57
  162. package/dist/runtime/seeds.d.ts +5 -1
  163. package/dist/runtime/seeds.js +11 -5
  164. package/dist/runtime/session.d.ts +46 -7
  165. package/dist/runtime/session.js +78 -10
  166. package/dist/runtime/slots.d.ts +53 -20
  167. package/dist/runtime/slots.js +2 -3
  168. package/dist/runtime/soft-refresh.d.ts +16 -0
  169. package/dist/runtime/soft-refresh.js +14 -0
  170. package/dist/runtime/svelte-warnings.d.ts +4 -10
  171. package/dist/runtime/svelte-warnings.js +7 -4
  172. package/dist/runtime/wire-classes.js +21 -8
  173. package/dist/runtime/wire-format.js +6 -3
  174. package/dist/script.js +8 -7
  175. package/dist/seed-ref-reviver.d.ts +0 -9
  176. package/dist/seed-ref-reviver.js +2 -1
  177. package/dist/seed-refs.d.ts +46 -36
  178. package/dist/seed-refs.js +131 -92
  179. package/dist/server/document-tail.d.ts +6 -0
  180. package/dist/server/document-tail.js +7 -1
  181. package/dist/server/early-holes-csp.d.ts +14 -0
  182. package/dist/server/early-holes-csp.js +73 -0
  183. package/dist/server/page-facts.d.ts +4 -1
  184. package/dist/server/page-facts.js +4 -1
  185. package/dist/server/page-seed.d.ts +2 -2
  186. package/dist/server/page-seed.js +104 -11
  187. package/dist/server/page-stream.d.ts +14 -1
  188. package/dist/server/page-stream.js +119 -2
  189. package/dist/server/personal-response.d.ts +0 -5
  190. package/dist/server/personal-response.js +6 -2
  191. package/dist/server/props-wire.js +5 -1
  192. package/dist/server/region-endpoint.js +10 -2
  193. package/dist/server/render-cache.d.ts +24 -20
  194. package/dist/server/render-cache.js +99 -40
  195. package/dist/server/render-page.js +46 -15
  196. package/dist/server/request-stats.d.ts +10 -8
  197. package/dist/server/request-stats.js +20 -12
  198. package/dist/server/rules.d.ts +11 -0
  199. package/dist/server/rules.js +64 -0
  200. package/dist/server/signing-key.js +31 -18
  201. package/dist/server/stream-regions.d.ts +14 -1
  202. package/dist/server/stream-regions.js +38 -2
  203. package/dist/shared-state.js +5 -1
  204. package/dist/shims/app-navigation.js +3 -2
  205. package/dist/shims/page-store.svelte.js +16 -2
  206. package/dist/source.js +7 -2
  207. package/dist/store-transport.js +12 -5
  208. package/dist/transport.js +3 -2
  209. package/dist/vite/index.js +83 -74
  210. package/dist/vite/kit-dirs.js +7 -1
  211. package/dist/vite/options.d.ts +2 -1
  212. package/dist/vite/options.js +11 -20
  213. package/dist/vite/package-files.js +13 -3
  214. package/dist/vite/paths.d.ts +2 -0
  215. package/dist/vite/paths.js +4 -4
  216. package/dist/vite/plugin-api.d.ts +6 -5
  217. package/dist/vite/plugin-api.js +42 -14
  218. package/dist/vite/style-preprocess.js +10 -3
  219. package/package.json +8 -1
  220. package/dist/KeepHost.svelte +0 -19
  221. package/dist/KeepHost.svelte.d.ts +0 -10
  222. package/dist/LiveHost.svelte +0 -19
  223. package/dist/LiveHost.svelte.d.ts +0 -10
  224. package/dist/NestedProvider.svelte +0 -13
  225. package/dist/NestedProvider.svelte.d.ts +0 -8
  226. package/dist/runtime/page-cache.d.ts +0 -23
  227. package/dist/runtime/page-cache.js +0 -74
@@ -14,21 +14,40 @@
14
14
  * swap-then-blank-then-paint flicker). "Unsafe" means only "no sanitizer"; a region body is our own
15
15
  * signed, same-origin SSR (HOLE-TRUST), so it is exactly as trusted as the page itself.
16
16
  *
17
- * NOTE the parse happens in a `<template>`'s inert content, so custom elements do NOT upgrade here
18
- * (no `connectedCallback`) — attaching a declarative shadow root is a parse step, not an upgrade. That
19
- * is what `repair_if_drifted` needs: the server copy stays inert, but its light-DOM sequence is now
20
- * DSD-consistent with a live region whose hosts already carry shadow roots.
17
+ * INERT BY CONSTRUCTION (#382). The parse runs in a document with no browsing context (one per
18
+ * runtime, made on first use): it has no custom element registry, so nothing in the answer is built
19
+ * by a component's constructor before the restore (./restore.ts) puts it in shape, in any engine.
20
+ * A page-owned `<template>` was inert by a browser detail only: Firefox 159 (scoped registries)
21
+ * builds custom elements while one parses with `setHTMLUnsafe`, so a host had its constructor's
22
+ * root before the restore reached it. Attaching a declarative shadow root is a parse step, not an
23
+ * upgrade: it still happens here. The fragment's nodes belong to that document until they are
24
+ * inserted (insertion adopts them; they upgrade at connect, constructor then `connectedCallback`,
25
+ * a declarative root kept). `repair_if_drifted` needs this too: the server copy stays inert, and its
26
+ * light-DOM sequence is DSD-consistent with a live region whose hosts already carry shadow roots.
27
+ *
28
+ * (`Document.parseHTMLUnsafe` would also parse without a registry, but makes a whole document per
29
+ * call; one shared document and its template is the cheaper of the two.)
30
+ *
31
+ * In dev and e2e every parse is checked (`rule_parse_upgraded`, ./rules.ts): nothing built.
21
32
  */
22
33
  function parse_region_html(html) {
23
- const tpl = document.createElement("template");
34
+ const tpl = inert_document().createElement("template");
24
35
  const set_html_unsafe = tpl.setHTMLUnsafe;
25
36
  if (typeof set_html_unsafe === "function") set_html_unsafe.call(tpl, html);
26
37
  else {
27
38
  tpl.innerHTML = html;
28
39
  attach_declarative_shadows(tpl.content);
29
40
  }
41
+ globalThis.__og_parsed?.(tpl.content);
30
42
  return tpl.content;
31
43
  }
44
+ let inert = null;
45
+ /** The runtime's one document with no browsing context, made on first use. (A DOM with no
46
+ * `document.implementation`, a server-side DOM a bench runs on, parses in the page's own: it has
47
+ * no custom elements to build.) */
48
+ function inert_document() {
49
+ return inert ??= typeof document.implementation?.createHTMLDocument === "function" ? document.implementation.createHTMLDocument("") : document;
50
+ }
32
51
  /** The spec's declarative-shadow-DOM attach step, for engines that render DSD on first parse but predate
33
52
  * `setHTMLUnsafe`. Recurse into each shadow we attach: `querySelectorAll` stops at shadow boundaries,
34
53
  * and a `<template shadowrootmode>` can nest inside another. First template per host wins (a host already
@@ -0,0 +1,21 @@
1
+ /**
2
+ * PUSHABLE PROPS: the props a mounted island reads reactively, so the next value reaches it in
3
+ * place instead of a re-hydrate — a live region's next tick, a kept island's next page. The island
4
+ * is hydrated with `props` itself (Svelte's documented reactive-props form for `hydrate`), so
5
+ * focus and local state survive and Svelte reconciles the view. Filled into the `Pushable` slot by
6
+ * the live feature (./live.ts); a page with neither live nor kept regions never loads it.
7
+ *
8
+ * The props are a snapshot of data, as every island's are: held RAW (`$state.raw`), never deep-proxied
9
+ * — the island reads the very values pushed (a large nav tree pays no proxy on each read, identity
10
+ * holds, `structuredClone` works), and mutating one is no more reactive than in any other island. A
11
+ * push replaces the whole snapshot; `props` is a plain reader over the current one, so each prop read
12
+ * tracks it.
13
+ */
14
+ export declare class PushableProps {
15
+ #private;
16
+ /** What the island is hydrated with: the current snapshot's props, read live. */
17
+ readonly props: Record<string, unknown>;
18
+ constructor(initial: Record<string, unknown>);
19
+ /** The next snapshot (a live tick, the next page's props): replaces the current one whole. */
20
+ push(next: Record<string, unknown>): void;
21
+ }
@@ -0,0 +1,37 @@
1
+ // src/runtime/pushable-props.svelte.ts
2
+ /**
3
+ * PUSHABLE PROPS: the props a mounted island reads reactively, so the next value reaches it in
4
+ * place instead of a re-hydrate — a live region's next tick, a kept island's next page. The island
5
+ * is hydrated with `props` itself (Svelte's documented reactive-props form for `hydrate`), so
6
+ * focus and local state survive and Svelte reconciles the view. Filled into the `Pushable` slot by
7
+ * the live feature (./live.ts); a page with neither live nor kept regions never loads it.
8
+ *
9
+ * The props are a snapshot of data, as every island's are: held RAW (`$state.raw`), never deep-proxied
10
+ * — the island reads the very values pushed (a large nav tree pays no proxy on each read, identity
11
+ * holds, `structuredClone` works), and mutating one is no more reactive than in any other island. A
12
+ * push replaces the whole snapshot; `props` is a plain reader over the current one, so each prop read
13
+ * tracks it.
14
+ */
15
+ var PushableProps = class {
16
+ #current = $state.raw({});
17
+ /** What the island is hydrated with: the current snapshot's props, read live. */
18
+ props;
19
+ constructor(initial) {
20
+ this.#current = initial;
21
+ this.props = new Proxy({}, {
22
+ get: (_, key) => typeof key === "string" ? this.#current[key] : void 0,
23
+ has: (_, key) => key in this.#current,
24
+ ownKeys: () => Reflect.ownKeys(this.#current),
25
+ getOwnPropertyDescriptor: (_, key) => key in this.#current ? {
26
+ enumerable: true,
27
+ configurable: true,
28
+ value: this.#current[key]
29
+ } : void 0
30
+ });
31
+ }
32
+ /** The next snapshot (a live tick, the next page's props): replaces the current one whole. */
33
+ push(next) {
34
+ this.#current = next;
35
+ }
36
+ };
37
+ export { PushableProps };
@@ -1,6 +1,7 @@
1
1
  import { preload_island_graph } from "./island-graph-preload.js";
2
- import { fetch_priority_of, load_slot } from "./load-scheduler.js";
2
+ import { LoadSlot, fetch_priority_of } from "./load-scheduler.js";
3
3
  import { entry_location } from "./entry-locations.js";
4
+ import { import_file } from "./import-again.js";
4
5
  // src/runtime/region-endpoint-url.ts
5
6
  /** Hoisted (hot paths — connectedCallback/hydrate run per region); shared with core's
6
7
  * foreign-origin checks. */
@@ -147,16 +148,13 @@ function warm_island_module(entry, base, cls = "speculative") {
147
148
  const key = warm_key(url);
148
149
  if (warmed_modules.has(key)) return;
149
150
  warmed_modules.add(key);
150
- load_slot({
151
+ new LoadSlot({
151
152
  kind: "code",
152
153
  cls,
153
154
  label: entry
154
155
  }).ready.then((release) => {
155
156
  preload_island_graph(entry, base, fetch_priority_of(cls));
156
- import(
157
- /* @vite-ignore */
158
- url
159
- ).catch(() => {
157
+ import_file(url).catch(() => {
160
158
  warmed_modules.delete(key);
161
159
  }).finally(release);
162
160
  });
@@ -0,0 +1,111 @@
1
+ import { IslandHandle } from "./hydrate-core.js";
2
+ import { Machine } from "@ogygia/machine";
3
+ type HoleStates = {
4
+ /** Not asked yet, or its fetch ended (answered, or torn down). */
5
+ idle: {};
6
+ /** Its `when` (and `prefetch`) schedule is armed. `attempts`: the fetches that failed before. */
7
+ armed: {
8
+ attempts: number;
9
+ };
10
+ /** Its own fetch is in flight. `revalidate`: past the browser cache (a refresh). */
11
+ fetching: {
12
+ attempts: number;
13
+ revalidate: boolean;
14
+ };
15
+ /** A fetch failed, and it waits to try again. */
16
+ retrying: {
17
+ attempts: number;
18
+ revalidate: boolean;
19
+ };
20
+ /** It gave up: refused, or out of tries. The fallback (or the answer before) stands. */
21
+ failed: {};
22
+ };
23
+ declare const hole_steps: {
24
+ readonly idle: readonly ["armed", "fetching"];
25
+ readonly armed: readonly ["idle", "fetching"];
26
+ readonly fetching: readonly ["idle", "retrying", "failed"];
27
+ readonly retrying: readonly ["idle", "armed", "fetching", "failed"];
28
+ readonly failed: readonly ["idle", "armed", "fetching"];
29
+ };
30
+ export type Hole = Machine<HoleStates, typeof hole_steps>;
31
+ /** A hole's fetch machine, idle. */
32
+ export declare function create_hole(): Hole;
33
+ type AnswerStates = {
34
+ /** Nothing from the hole is on screen: its fallback. */
35
+ none: {};
36
+ /** An answer is swapped in. */
37
+ answered: {};
38
+ /** It answered `keepFallback()`: the fallback stands as its answer. */
39
+ kept: {};
40
+ };
41
+ declare const answer_steps: {
42
+ readonly none: readonly ["answered", "kept"];
43
+ readonly answered: readonly ["kept"];
44
+ readonly kept: readonly ["answered"];
45
+ };
46
+ export type Answer = Machine<AnswerStates, typeof answer_steps>;
47
+ /** A hole's answer machine: nothing on screen yet. */
48
+ export declare function create_answer(): Answer;
49
+ type WakeStates = {
50
+ /** Asleep, nothing armed. */
51
+ cold: {};
52
+ /** Its schedule (and the capture of early input) is armed. */
53
+ armed: {};
54
+ /** Its module and the hydrate core load; then its hydrate step. */
55
+ waking: {};
56
+ /** Hydrated: the island is live. */
57
+ awake: {};
58
+ /** Hydrating threw: its server markup stays as it is. */
59
+ failed: {
60
+ error: unknown;
61
+ };
62
+ };
63
+ declare const wake_steps: {
64
+ readonly cold: readonly ["armed", "waking"];
65
+ readonly armed: readonly ["cold", "waking"];
66
+ readonly waking: readonly ["cold", "awake", "failed"];
67
+ readonly awake: readonly ["cold"];
68
+ readonly failed: readonly ["cold"];
69
+ };
70
+ export type Wake = Machine<WakeStates, typeof wake_steps>;
71
+ /** An island's wake machine, cold. */
72
+ export declare function create_wake(): Wake;
73
+ type LiveStates = {
74
+ /** No tick yet, or its island could not hydrate (the next tick starts over). */
75
+ idle: {};
76
+ /** A static region: the frame store's frames morph into it (it is subscribed while here). */
77
+ static: {};
78
+ /** An interactive tick's HTML is in; its island loads and hydrates. */
79
+ hydrating: {
80
+ module: string;
81
+ };
82
+ /** Its island is live: a tick of the same module pushes props. Leaving disposes it. */
83
+ live: {
84
+ module: string;
85
+ app: IslandHandle;
86
+ };
87
+ };
88
+ declare const live_steps: {
89
+ readonly idle: readonly ["static", "hydrating"];
90
+ readonly static: readonly ["idle", "hydrating"];
91
+ readonly hydrating: readonly ["idle", "static", "hydrating", "live"];
92
+ readonly live: readonly ["idle", "static", "hydrating"];
93
+ };
94
+ export type Live = Machine<LiveStates, typeof live_steps>;
95
+ /** A live region's machine, before its first tick. */
96
+ export declare function create_live(): Live;
97
+ type PaintStates = {
98
+ /** No frame is waiting to paint. */
99
+ idle: {};
100
+ /** A frame waits for its stylesheets, then paints. A newer frame is a new visit: the older one's
101
+ * wait ends with nothing painted. */
102
+ painting: {};
103
+ };
104
+ declare const paint_steps: {
105
+ readonly idle: readonly ["painting"];
106
+ readonly painting: readonly ["painting", "idle"];
107
+ };
108
+ export type Paint = Machine<PaintStates, typeof paint_steps>;
109
+ /** A region's paint machine: which frame (a hole's answer, a static live tick) paints next. */
110
+ export declare function create_paint(): Paint;
111
+ export {};
@@ -0,0 +1,97 @@
1
+ import { machine } from "@ogygia/machine";
2
+ const hole_definition = machine()({
3
+ name: "hole",
4
+ initial: "idle",
5
+ steps: {
6
+ idle: ["armed", "fetching"],
7
+ armed: ["idle", "fetching"],
8
+ fetching: [
9
+ "idle",
10
+ "retrying",
11
+ "failed"
12
+ ],
13
+ retrying: [
14
+ "idle",
15
+ "armed",
16
+ "fetching",
17
+ "failed"
18
+ ],
19
+ failed: [
20
+ "idle",
21
+ "armed",
22
+ "fetching"
23
+ ]
24
+ }
25
+ });
26
+ /** A hole's fetch machine, idle. */
27
+ function create_hole() {
28
+ return hole_definition.create();
29
+ }
30
+ const answer_definition = machine()({
31
+ name: "hole-answer",
32
+ initial: "none",
33
+ steps: {
34
+ none: ["answered", "kept"],
35
+ answered: ["kept"],
36
+ kept: ["answered"]
37
+ }
38
+ });
39
+ /** A hole's answer machine: nothing on screen yet. */
40
+ function create_answer() {
41
+ return answer_definition.create();
42
+ }
43
+ const wake_definition = machine()({
44
+ name: "wake",
45
+ initial: "cold",
46
+ steps: {
47
+ cold: ["armed", "waking"],
48
+ armed: ["cold", "waking"],
49
+ waking: [
50
+ "cold",
51
+ "awake",
52
+ "failed"
53
+ ],
54
+ awake: ["cold"],
55
+ failed: ["cold"]
56
+ }
57
+ });
58
+ /** An island's wake machine, cold. */
59
+ function create_wake() {
60
+ return wake_definition.create();
61
+ }
62
+ const live_definition = machine()({
63
+ name: "live-region",
64
+ initial: "idle",
65
+ steps: {
66
+ idle: ["static", "hydrating"],
67
+ static: ["idle", "hydrating"],
68
+ hydrating: [
69
+ "idle",
70
+ "static",
71
+ "hydrating",
72
+ "live"
73
+ ],
74
+ live: [
75
+ "idle",
76
+ "static",
77
+ "hydrating"
78
+ ]
79
+ }
80
+ });
81
+ /** A live region's machine, before its first tick. */
82
+ function create_live() {
83
+ return live_definition.create();
84
+ }
85
+ const paint_definition = machine()({
86
+ name: "region-paint",
87
+ initial: "idle",
88
+ steps: {
89
+ idle: ["painting"],
90
+ painting: ["painting", "idle"]
91
+ }
92
+ });
93
+ /** A region's paint machine: which frame (a hole's answer, a static live tick) paints next. */
94
+ function create_paint() {
95
+ return paint_definition.create();
96
+ }
97
+ export { create_answer, create_hole, create_live, create_paint, create_wake };
@@ -0,0 +1,31 @@
1
+ import { Machine } from "@ogygia/machine";
2
+ type SheetStates = {
3
+ /** Its download is in flight. */
4
+ loading: {};
5
+ /** In, and styling the page. */
6
+ loaded: {};
7
+ /** Its download failed. It stays failed: a fresh link asks again. */
8
+ failed: {};
9
+ };
10
+ declare const sheet_steps: {
11
+ readonly loading: readonly ["loaded", "failed"];
12
+ readonly loaded: readonly [];
13
+ readonly failed: readonly [];
14
+ };
15
+ export type RegionSheet = Machine<SheetStates, typeof sheet_steps>;
16
+ /**
17
+ * The download of a stylesheet link, watched from the first time an answer looks at it. A link made
18
+ * here is watched before it goes into the head, so it never misses its `load` or `error`. One that was
19
+ * there first (the server's) counts as loaded once it has a `sheet`; one with no `sheet` yet is still
20
+ * loading. (Chromium gives a failed link an empty sheet, so a server link that failed before anyone
21
+ * looked counts as loaded: the page's own sheets are the page's.)
22
+ */
23
+ export declare function sheet_of(link: HTMLLinkElement): RegionSheet;
24
+ /** `region` painted an answer that uses `sheets` (in place of the ones its answer before used). */
25
+ export declare function use_sheets(region: Element, sheets: readonly Element[]): void;
26
+ /**
27
+ * After a navigation's swap: of the region sheets the new page's head does not list (`unlisted`),
28
+ * take out each one no region on the page uses any more.
29
+ */
30
+ export declare function release_sheets(unlisted: readonly Element[]): void;
31
+ export {};
@@ -0,0 +1,49 @@
1
+ import { connected_regions } from "./connected.js";
2
+ import { machine } from "@ogygia/machine";
3
+ const sheet_definition = machine()({
4
+ name: "region-sheet",
5
+ initial: "loading",
6
+ steps: {
7
+ loading: ["loaded", "failed"],
8
+ loaded: [],
9
+ failed: []
10
+ }
11
+ });
12
+ const sheets = /* @__PURE__ */ new WeakMap();
13
+ /**
14
+ * The download of a stylesheet link, watched from the first time an answer looks at it. A link made
15
+ * here is watched before it goes into the head, so it never misses its `load` or `error`. One that was
16
+ * there first (the server's) counts as loaded once it has a `sheet`; one with no `sheet` yet is still
17
+ * loading. (Chromium gives a failed link an empty sheet, so a server link that failed before anyone
18
+ * looked counts as loaded: the page's own sheets are the page's.)
19
+ */
20
+ function sheet_of(link) {
21
+ const known = sheets.get(link);
22
+ if (known !== void 0) return known;
23
+ const s = sheet_definition.create();
24
+ sheets.set(link, s);
25
+ s.scope.on(link, "load", () => s.go("loaded"));
26
+ s.scope.on(link, "error", () => s.go("failed"));
27
+ if (link.sheet !== null) s.go("loaded");
28
+ return s;
29
+ }
30
+ /** The sheets in the head each region's answer on screen uses, by region. */
31
+ const used_by = /* @__PURE__ */ new WeakMap();
32
+ /** `region` painted an answer that uses `sheets` (in place of the ones its answer before used). */
33
+ function use_sheets(region, sheets) {
34
+ used_by.set(region, sheets);
35
+ }
36
+ /**
37
+ * After a navigation's swap: of the region sheets the new page's head does not list (`unlisted`),
38
+ * take out each one no region on the page uses any more.
39
+ */
40
+ function release_sheets(unlisted) {
41
+ if (unlisted.length === 0) return;
42
+ const used = /* @__PURE__ */ new Set();
43
+ for (const region of connected_regions) {
44
+ const sheets = used_by.get(region);
45
+ if (sheets !== void 0) for (const s of sheets) used.add(s);
46
+ }
47
+ for (const s of unlisted) if (!used.has(s)) s.remove();
48
+ }
49
+ export { release_sheets, sheet_of, use_sheets };
@@ -0,0 +1 @@
1
+ export declare function report_restores(): void;
@@ -0,0 +1,36 @@
1
+ import { OgygiaError, warn } from "../messages/core.js";
2
+ // src/runtime/restore-report.ts
3
+ /**
4
+ * SAY WHAT THE RESTORER RECORDED. The restorer (./restore.ts) runs inlined as text, self-contained,
5
+ * so it prints nothing itself: it records each mismatch on `window.__og_restore_log` (the devtools
6
+ * and the beacon read it too). The boot reports what the document's own run recorded, and wraps
7
+ * `window.__og_restore` so every later run — a hole answer, a router swap — reports its own.
8
+ */
9
+ let reported = 0;
10
+ /** The element names a late restore was said for on this page (once each). */
11
+ const late_said = /* @__PURE__ */ new Set();
12
+ function report() {
13
+ const log = window.__og_restore_log;
14
+ if (!log) return;
15
+ if (reported > log.length) reported = 0;
16
+ for (; reported < log.length; reported++) {
17
+ const entry = log[reported];
18
+ if (entry.kind === "mismatch" && entry.diff) console.error(new OgygiaError("restore_markup_broken", { diff: entry.diff }));
19
+ else if (entry.kind === "late" && entry.host && !late_said.has(entry.host)) {
20
+ late_said.add(entry.host);
21
+ warn("restore_late", { host: entry.host });
22
+ }
23
+ }
24
+ }
25
+ function report_restores() {
26
+ if (typeof window === "undefined") return;
27
+ const w = window;
28
+ const run = w.__og_restore;
29
+ if (run) w.__og_restore = (root) => {
30
+ const restored = run(root);
31
+ report();
32
+ return restored;
33
+ };
34
+ report();
35
+ }
36
+ export { report_restores };
@@ -19,8 +19,11 @@
19
19
  * insertion and on an incoming page before a router swap.
20
20
  *
21
21
  * A host that already has a shadow root when it is reached was upgraded first — the restore came
22
- * too late. It is left as served and reported (`ogygia:restore-late` on the host), never restored into
23
- * a root the component's own constructor made (that tree would render twice).
22
+ * too late. The root is the component's own and is left to it; the light DOM still goes back to
23
+ * Svelte's form, and the server's rendered copy is dropped (slotted beside the component's own
24
+ * render it would show twice). It is reported (`ogygia:restore-late` on the host, the log, and
25
+ * `restore_late` in every build). Answers are parsed inert (./parse-html.ts, #382), so this is the
26
+ * page-load case: a component runtime that upgraded first.
24
27
  */
25
28
  export declare function restore(root: Document | DocumentFragment | Element): number;
26
29
  /** Adopt the sheets of roots restored outside the page, now that their hosts are in it (after a hole
@@ -20,8 +20,11 @@
20
20
  * insertion and on an incoming page before a router swap.
21
21
  *
22
22
  * A host that already has a shadow root when it is reached was upgraded first — the restore came
23
- * too late. It is left as served and reported (`ogygia:restore-late` on the host), never restored into
24
- * a root the component's own constructor made (that tree would render twice).
23
+ * too late. The root is the component's own and is left to it; the light DOM still goes back to
24
+ * Svelte's form, and the server's rendered copy is dropped (slotted beside the component's own
25
+ * render it would show twice). It is reported (`ogygia:restore-late` on the host, the log, and
26
+ * `restore_late` in every build). Answers are parsed inert (./parse-html.ts, #382), so this is the
27
+ * page-load case: a component runtime that upgraded first.
25
28
  */
26
29
  function restore(root) {
27
30
  const doc = root.ownerDocument ?? root;
@@ -39,7 +42,9 @@ function restore(root) {
39
42
  * every token it dropped (the component's, the render's `sc-*`), read from the value just before
40
43
  * that write. A component only ever adds and removes its own tokens, so no later state of the
41
44
  * host equals Svelte's alone. One observer for all of them; a host leaves the watch at that
42
- * write, and the observer goes when the last one has.
45
+ * write, or when it leaves the document (Kit dropped it and mounted another: no write is coming;
46
+ * a host moved in one task is back in by the time the observer hears of it), and the observer
47
+ * goes when the last one has.
43
48
  */
44
49
  const watch_classes = live && document.querySelector("meta[name=\"ogygia-csr\"][content=\"true\"]") !== null;
45
50
  const watch = (el) => {
@@ -47,22 +52,34 @@ function restore(root) {
47
52
  if (!w) {
48
53
  const hosts = /* @__PURE__ */ new Set();
49
54
  const mo = new MutationObserver((records) => {
55
+ let removed = false;
50
56
  for (let i = 0; i < records.length; i++) {
57
+ if (records[i].type === "childList") {
58
+ if (records[i].removedNodes.length) removed = true;
59
+ continue;
60
+ }
51
61
  const host = records[i].target;
52
62
  if (!hosts.has(host)) continue;
53
63
  let now;
54
- for (let j = i + 1; j < records.length && now === void 0; j++) if (records[j].target === host) now = records[j].oldValue;
64
+ for (let j = i + 1; j < records.length && now === void 0; j++) if (records[j].target === host && records[j].type === "attributes") now = records[j].oldValue;
55
65
  if (now === void 0) now = host.getAttribute("class");
56
66
  if ((now ?? "") !== host.__og_svelte_class) continue;
57
67
  hosts.delete(host);
58
68
  const own = new Set((now ?? "").split(" "));
59
69
  for (const t of (records[i].oldValue ?? "").split(" ")) if (t && !own.has(t)) host.classList.add(t);
60
70
  }
71
+ if (removed) {
72
+ for (const host of hosts) if (!host.isConnected) hosts.delete(host);
73
+ }
61
74
  if (hosts.size === 0) {
62
75
  mo.disconnect();
63
76
  W.__og_class_watch = void 0;
64
77
  }
65
78
  });
79
+ mo.observe(document, {
80
+ childList: true,
81
+ subtree: true
82
+ });
66
83
  w = W.__og_class_watch = {
67
84
  mo,
68
85
  hosts
@@ -237,7 +254,6 @@ function restore(root) {
237
254
  tpl.innerHTML = want;
238
255
  const diff = differ(tpl.content, host, `<${host.localName}>`);
239
256
  if (!diff) return;
240
- console.error(`[ogygia] the transform broke hydration markup: ${diff}`);
241
257
  host.dispatchEvent(new CustomEvent("ogygia:restore-mismatch", {
242
258
  bubbles: true,
243
259
  detail: {
@@ -276,19 +292,19 @@ function restore(root) {
276
292
  const host = hosts[x];
277
293
  const k = host.getAttribute("og-h");
278
294
  const keys = (host.getAttribute("og-shadow") ?? "").split(" ").filter(Boolean);
279
- if (host.shadowRoot) {
295
+ const late = host.shadowRoot !== null;
296
+ const shadow = late ? null : host.attachShadow({ mode: "open" });
297
+ if (late) {
280
298
  host.dispatchEvent(new CustomEvent("ogygia:restore-late", {
281
299
  bubbles: true,
282
300
  detail: { host: k }
283
301
  }));
284
302
  log("late", host);
285
- continue;
286
303
  }
287
- const shadow = host.attachShadow({ mode: "open" });
288
- if (!live) (W.__og_init ??= []).push(shadow);
304
+ if (shadow && !live) (W.__og_init ??= []).push(shadow);
289
305
  const own = [];
290
- const adoptable = Array.isArray(shadow.adoptedStyleSheets);
291
- for (const key of keys) {
306
+ const adoptable = shadow !== null && Array.isArray(shadow.adoptedStyleSheets);
307
+ if (shadow) for (const key of keys) {
292
308
  const s = sheet_for(key);
293
309
  if (!s) continue;
294
310
  if (s.sheet && adoptable) own.push(s.sheet);
@@ -298,7 +314,7 @@ function restore(root) {
298
314
  shadow.appendChild(style);
299
315
  }
300
316
  }
301
- if (own.length) {
317
+ if (shadow && own.length) {
302
318
  if (live) shadow.adoptedStyleSheets = [...shadow.adoptedStyleSheets, ...own];
303
319
  else {
304
320
  shadow.__og_pending = own;
@@ -330,7 +346,10 @@ function restore(root) {
330
346
  }
331
347
  children.sort((a, b) => a.i - b.i);
332
348
  const mine = new Set(children.map((c) => c.node));
333
- for (const n of Array.from(host.childNodes)) if (!mine.has(n)) shadow.appendChild(n);
349
+ for (const n of Array.from(host.childNodes)) if (!mine.has(n)) {
350
+ if (shadow) shadow.appendChild(n);
351
+ else n.remove();
352
+ }
334
353
  const made = W.__og_rendered ??= /* @__PURE__ */ new WeakSet();
335
354
  for (const c of children) {
336
355
  host.appendChild(c.node);
@@ -347,7 +366,7 @@ function restore(root) {
347
366
  host.removeAttribute("og-shadow");
348
367
  host.removeAttribute("og-keep");
349
368
  verify(host, k);
350
- restored++;
369
+ if (!late) restored++;
351
370
  }
352
371
  return restored;
353
372
  }