@ogygia/core 0.12.3 → 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 (226) 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 +17 -10
  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.js +42 -14
  217. package/dist/vite/style-preprocess.js +10 -3
  218. package/package.json +8 -1
  219. package/dist/KeepHost.svelte +0 -19
  220. package/dist/KeepHost.svelte.d.ts +0 -10
  221. package/dist/LiveHost.svelte +0 -19
  222. package/dist/LiveHost.svelte.d.ts +0 -10
  223. package/dist/NestedProvider.svelte +0 -13
  224. package/dist/NestedProvider.svelte.d.ts +0 -8
  225. package/dist/runtime/page-cache.d.ts +0 -23
  226. package/dist/runtime/page-cache.js +0 -74
@@ -1,6 +1,7 @@
1
1
  import { after_document_painted, yield_task } from "./schedule.js";
2
2
  import { kit_hydrates_page } from "./kit-boot.js";
3
3
  import { emit } from "../events/bus.js";
4
+ import { flights, machine, scope } from "@ogygia/machine";
4
5
  // src/runtime/load-scheduler.ts
5
6
  /**
6
7
  * THE LOAD SCHEDULER — every download ogygia starts on its own goes through one queue.
@@ -43,6 +44,12 @@ const RANK = {
43
44
  ahead: 2,
44
45
  speculative: 3
45
46
  };
47
+ const CLASSES = [
48
+ "user",
49
+ "visible",
50
+ "ahead",
51
+ "speculative"
52
+ ];
46
53
  /** The fetch priority a class downloads at (`fetch(…, { priority })`, `<link fetchpriority>`). */
47
54
  function fetch_priority_of(cls) {
48
55
  return cls === "user" ? "high" : cls === "visible" ? "auto" : "low";
@@ -67,23 +74,74 @@ function on_screen(el, depth) {
67
74
  }
68
75
  return crosses && (r.width > 0 || r.height > 0);
69
76
  }
70
- let input_seen = false;
77
+ /**
78
+ * The gate, as a machine:
79
+ *
80
+ * fresh ─ waiting{t0, resources} ─ settled{report}
81
+ * ╲ touched ───────────────────╱
82
+ *
83
+ * `fresh`: no wait yet. `touched`: the visitor clicked or typed before the wait began, so it waits on
84
+ * nothing. `waiting`: the wait is up; its visit owns the cap and the resources' listeners, so they go
85
+ * as it settles, however it settles. `settled`: open for good, with how it ended (the beacon reads
86
+ * it). One state in place of `input_seen`, `critical_done`, `release_critical` and the report.
87
+ */
88
+ const gate_definition = machine()({
89
+ name: "critical-gate",
90
+ initial: "fresh",
91
+ steps: {
92
+ fresh: [
93
+ "touched",
94
+ "waiting",
95
+ "settled"
96
+ ],
97
+ touched: ["settled"],
98
+ waiting: ["settled"],
99
+ settled: []
100
+ }
101
+ });
102
+ let gate = gate_definition.create();
71
103
  const on_first_input = () => {
72
- input_seen = true;
73
- release_critical?.();
104
+ const kind = gate.now.kind;
105
+ if (kind === "fresh") gate.go("touched");
106
+ else if (kind === "waiting") settle_critical("input");
74
107
  };
75
108
  if (typeof document !== "undefined") for (const type of ["pointerdown", "keydown"]) addEventListener(type, on_first_input, {
76
109
  capture: true,
77
110
  passive: true,
78
111
  once: true
79
112
  });
80
- let critical = null;
81
- let critical_done = false;
82
- let release_critical = null;
83
- let critical_report = null;
113
+ /** The critical wait below, once for the document (a test's reset drops it). */
114
+ const critical = flights({
115
+ name: "critical resources",
116
+ run: () => until_critical()
117
+ });
118
+ /** The gate is open: the critical wait settled. */
119
+ const critical_done = () => gate.now.kind === "settled";
84
120
  /** The critical wait's outcome once it settled (the profiler's beacon reads it), else `null`. */
85
121
  function critical_outcome() {
86
- return critical_report;
122
+ const s = gate.now;
123
+ return s.kind === "settled" ? s.report : null;
124
+ }
125
+ /** The wait ends (once: a gate already settled stays as it settled). */
126
+ function settle_critical(outcome) {
127
+ const s = gate.now;
128
+ if (s.kind === "settled") return;
129
+ const report = s.kind === "waiting" ? {
130
+ outcome,
131
+ resources: s.resources,
132
+ waited_ms: Math.round(now() - s.t0)
133
+ } : {
134
+ outcome,
135
+ resources: 0,
136
+ waited_ms: 0
137
+ };
138
+ gate.go("settled", { report });
139
+ if (DEVTOOLS) emit({
140
+ domain: "runtime",
141
+ name: "load.critical",
142
+ ...report
143
+ });
144
+ drain();
87
145
  }
88
146
  /** Elements carrying the page's critical resources that have not arrived yet: an eager
89
147
  * `img[fetchpriority=high]` not complete, a `link[rel=preload][fetchpriority=high]` not fetched. */
@@ -126,78 +184,97 @@ function preload_urls(link) {
126
184
  * wait. A page with no marked resource resolves right after the paint. Once resolved it stays so.
127
185
  */
128
186
  function critical_settled() {
129
- return critical ??= after_document_painted().then(() => new Promise((resolve) => {
130
- const t0 = now();
131
- const done = (outcome) => {
132
- if (critical_done) return;
133
- critical_done = true;
134
- release_critical = null;
135
- clearTimeout(timer);
136
- critical_report = {
137
- outcome,
138
- resources: waiting.length,
139
- waited_ms: Math.round(now() - t0)
140
- };
141
- if (DEVTOOLS) emit({
142
- domain: "runtime",
143
- name: "load.critical",
144
- ...critical_report
145
- });
146
- resolve();
147
- drain();
148
- };
149
- const waiting = input_seen || document.visibilityState === "hidden" ? [] : pending_critical();
150
- const timer = waiting.length ? setTimeout(() => done("cap"), CRITICAL_CAP_MS) : void 0;
151
- if (!waiting.length) return done("none");
152
- release_critical = () => done("input");
187
+ return critical.get();
188
+ }
189
+ function until_critical() {
190
+ return after_document_painted().then(async () => {
191
+ const s = gate.now;
192
+ if (s.kind === "settled") return;
193
+ const waiting = s.kind === "touched" || document.visibilityState === "hidden" ? [] : pending_critical();
194
+ if (!waiting.length) return settle_critical("none");
195
+ gate.go("waiting", {
196
+ t0: now(),
197
+ resources: waiting.length
198
+ });
199
+ const wait = gate.scope;
200
+ wait.timeout(() => settle_critical("cap"), CRITICAL_CAP_MS);
153
201
  let left = waiting.length;
154
202
  const one = () => {
155
- if (--left === 0) done("arrived");
203
+ if (--left === 0) settle_critical("arrived");
156
204
  };
157
205
  for (const el of waiting) {
158
- el.addEventListener("load", one, { once: true });
159
- el.addEventListener("error", one, { once: true });
206
+ wait.on(el, "load", one, { once: true });
207
+ wait.on(el, "error", one, { once: true });
160
208
  }
161
- }));
209
+ await gate.wait("settled");
210
+ });
162
211
  }
212
+ /** The slots waiting, in the order they were asked for (a pass takes out the ones it started when
213
+ * it ends; `waiting` counts the rest at any moment). */
163
214
  const queue = [];
215
+ let waiting = 0;
164
216
  let in_flight_background = 0;
165
- let next_order = 0;
166
- let scroll_watch = false;
217
+ /** The scroll watch while a queued download waits (its listener and its frame): closed once the
218
+ * queue is empty. */
219
+ let scroll_watch = null;
167
220
  /**
168
- * Ask for a slot. `ready` resolves with a `release` to call when the download is done (settled,
169
- * success or not). `cls` may be a getter, re-read whenever the queue drains (viewport changes).
221
+ * A queued download: asking for one queues it. `ready` resolves with a `release` to call when the
222
+ * download is done (settled, success or not). `cls` may be a getter, re-read whenever the queue
223
+ * drains (viewport changes); `promote` lifts it.
170
224
  */
171
- function load_slot(o) {
172
- let forced = null;
173
- const read = typeof o.cls === "function" ? o.cls : () => o.cls;
174
- const cls = () => {
175
- const c = read();
225
+ var LoadSlot = class {
226
+ kind;
227
+ label;
228
+ queued_at = now();
229
+ ready;
230
+ /** Still in the queue: a drain takes it out when it starts. */
231
+ queued = true;
232
+ #read;
233
+ #forced = null;
234
+ #resolve;
235
+ constructor(o) {
236
+ this.kind = o.kind;
237
+ this.label = o.label;
238
+ this.#read = typeof o.cls === "function" ? o.cls : () => o.cls;
239
+ this.ready = new Promise((r) => this.#resolve = r);
240
+ queue.push(this);
241
+ waiting++;
242
+ if (typeof o.cls === "function") watch_scroll();
243
+ if (!critical_done() && waits_for_critical(this.kind, this.cls())) critical_settled();
244
+ drain();
245
+ }
246
+ /** Its class now: the getter's, or the promoted one when that ranks higher. */
247
+ cls() {
248
+ const c = this.#read();
249
+ const forced = this.#forced;
176
250
  return forced !== null && RANK[forced] < RANK[c] ? forced : c;
177
- };
178
- let resolve;
179
- const ready = new Promise((r) => resolve = r);
180
- const entry = {
181
- kind: o.kind,
182
- cls,
183
- label: o.label,
184
- queued_at: now(),
185
- order: next_order++,
186
- resolve,
187
- background: false
188
- };
189
- queue.push(entry);
190
- if (typeof o.cls === "function") watch_scroll();
191
- if (!critical_done && waits_for_critical(entry.kind, cls())) critical_settled();
192
- drain();
193
- return {
194
- ready,
195
- ticket: { promote(c) {
196
- forced = c;
251
+ }
252
+ /** Lift it (a prefetch the visitor then navigates to). */
253
+ promote(c) {
254
+ this.#forced = c;
255
+ drain();
256
+ }
257
+ /** Out of the queue: the holder's `ready` resolves, counted in the background window when it
258
+ * is one of the guesses. */
259
+ start(cls, background) {
260
+ if (background) in_flight_background++;
261
+ let released = false;
262
+ if (DEVTOOLS) emit({
263
+ domain: "runtime",
264
+ name: "load.started",
265
+ class: cls,
266
+ kind: this.kind,
267
+ label: this.label,
268
+ waited_ms: Math.round(now() - this.queued_at)
269
+ });
270
+ this.#resolve(() => {
271
+ if (released) return;
272
+ released = true;
273
+ if (background) in_flight_background--;
197
274
  drain();
198
- } }
199
- };
200
- }
275
+ });
276
+ }
277
+ };
201
278
  function waits_for_critical(kind, cls) {
202
279
  if (cls === "user") return false;
203
280
  if (kit_hydrates_page()) return false;
@@ -210,91 +287,114 @@ function waits_for_critical(kind, cls) {
210
287
  * task (52 ms on a 4×-throttled runner). After this much work in one task the drain yields.
211
288
  */
212
289
  const START_BUDGET_MS = 16;
213
- let draining = false;
214
- let drain_again = false;
290
+ /**
291
+ * The queue's drain, as a machine: `idle`, `draining`, or `again` (a drain was asked for while one
292
+ * ran: the one running goes round once more, and asks again when it is done). One state in place of
293
+ * `draining` beside `drain_again`.
294
+ */
295
+ const drain_definition = machine()({
296
+ name: "load-drain",
297
+ initial: "idle",
298
+ steps: {
299
+ idle: ["draining"],
300
+ draining: ["idle", "again"],
301
+ again: ["idle", "draining"]
302
+ }
303
+ });
304
+ let drainer = drain_definition.create();
215
305
  function drain() {
216
- if (draining) {
217
- drain_again = true;
306
+ const kind = drainer.now.kind;
307
+ if (kind !== "idle") {
308
+ if (kind === "draining") drainer.go("again");
218
309
  return;
219
310
  }
220
- if (!queue.length) return;
221
- draining = true;
311
+ if (!waiting) return;
312
+ drainer.go("draining");
313
+ const mine = drainer;
222
314
  drain_queue().finally(() => {
223
- draining = false;
224
- if (drain_again) {
225
- drain_again = false;
226
- drain();
227
- }
315
+ if (drainer !== mine) return;
316
+ const again = drainer.now.kind === "again";
317
+ drainer.go("idle");
318
+ if (again) drain();
228
319
  });
229
320
  }
321
+ /**
322
+ * One pass goes through the queue by class, then in the order asked, and then through the slots
323
+ * asked for while it ran, in the order asked. The queue is kept in the order asked, so reading each
324
+ * class once and putting the slot in its class's list is that order (a sort compared two classes per
325
+ * step, every pass: passes × queue × log queue reads on a page whose downloads each end a pass).
326
+ *
327
+ * @heisenbench linear
328
+ */
230
329
  async function drain_queue() {
231
330
  let until = now() + START_BUDGET_MS;
232
331
  for (;;) {
233
- drain_again = false;
234
- queue.sort((a, b) => RANK[a.cls()] - RANK[b.cls()] || a.order - b.order);
332
+ if (drainer.now.kind === "again") drainer.go("draining");
333
+ const asked = queue.length;
334
+ const by_class = [
335
+ [],
336
+ [],
337
+ [],
338
+ []
339
+ ];
340
+ for (let i = 0; i < asked; i++) by_class[RANK[queue[i].cls()]].push(queue[i]);
341
+ let ran = false;
342
+ let started = false;
235
343
  let yielded = false;
236
- for (let i = 0; i < queue.length;) {
237
- const e = queue[i];
238
- const cls = e.cls();
239
- if (!critical_done && waits_for_critical(e.kind, cls)) {
240
- i++;
241
- continue;
242
- }
243
- const background = cls === "ahead" || cls === "speculative";
244
- if (background && in_flight_background >= 3) {
245
- i++;
246
- continue;
247
- }
248
- queue.splice(i, 1);
249
- start(e, cls, background);
250
- await Promise.resolve();
251
- if (now() >= until) {
252
- await yield_task();
253
- until = now() + START_BUDGET_MS;
254
- yielded = true;
255
- break;
344
+ pass: for (let r = 0; r <= 4; r++) {
345
+ const list = r < 4 ? by_class[r] : queue;
346
+ for (let i = r < 4 ? 0 : asked; i < list.length; i++) {
347
+ const e = list[i];
348
+ if (!e.queued) continue;
349
+ const cls = r < 4 && !ran ? CLASSES[r] : e.cls();
350
+ if (!critical_done() && waits_for_critical(e.kind, cls)) continue;
351
+ const background = cls === "ahead" || cls === "speculative";
352
+ if (background && in_flight_background >= 3) continue;
353
+ e.queued = false;
354
+ waiting--;
355
+ started = true;
356
+ e.start(cls, background);
357
+ await Promise.resolve();
358
+ ran = true;
359
+ if (now() >= until) {
360
+ leave_queue();
361
+ await yield_task();
362
+ until = now() + START_BUDGET_MS;
363
+ yielded = true;
364
+ break pass;
365
+ }
256
366
  }
257
367
  }
258
- if (!yielded && !drain_again) return;
368
+ if (!yielded) {
369
+ if (started) leave_queue();
370
+ if (drainer.now.kind !== "again") return;
371
+ }
259
372
  }
260
373
  }
261
- function start(e, cls, background) {
262
- if (background) in_flight_background++;
263
- e.background = background;
264
- let released = false;
265
- if (DEVTOOLS) emit({
266
- domain: "runtime",
267
- name: "load.started",
268
- class: cls,
269
- kind: e.kind,
270
- label: e.label,
271
- waited_ms: Math.round(now() - e.queued_at)
272
- });
273
- e.resolve(() => {
274
- if (released) return;
275
- released = true;
276
- if (background) in_flight_background--;
277
- drain();
278
- });
374
+ /** The started slots out of the queue, the rest kept in the order asked. */
375
+ function leave_queue() {
376
+ let w = 0;
377
+ for (let i = 0; i < queue.length; i++) if (queue[i].queued) queue[w++] = queue[i];
378
+ queue.length = w;
279
379
  }
280
380
  /** While a queued download's class can change with the viewport, re-drain on scroll (one frame). */
281
381
  function watch_scroll() {
282
- if (scroll_watch || typeof addEventListener === "undefined") return;
283
- scroll_watch = true;
382
+ if (scroll_watch || typeof window === "undefined") return;
383
+ const watch = scroll_watch = scope();
284
384
  let framed = false;
285
385
  const on_scroll = () => {
286
386
  if (framed) return;
287
387
  framed = true;
288
- requestAnimationFrame(() => {
388
+ watch.timeout(() => {
289
389
  framed = false;
290
390
  drain();
291
- if (!queue.length) {
292
- removeEventListener("scroll", on_scroll, true);
293
- scroll_watch = false;
391
+ if (!waiting) {
392
+ watch.close();
393
+ scroll_watch = null;
294
394
  }
295
- });
395
+ }, "frame");
296
396
  };
297
- addEventListener("scroll", on_scroll, {
397
+ watch.on(window, "scroll", on_scroll, {
298
398
  capture: true,
299
399
  passive: true
300
400
  });
@@ -304,14 +404,14 @@ function now() {
304
404
  }
305
405
  /** Test seam: a fresh document. */
306
406
  function reset_load_scheduler() {
407
+ for (const s of queue) s.queued = false;
307
408
  queue.length = 0;
409
+ waiting = 0;
308
410
  in_flight_background = 0;
309
- draining = false;
310
- drain_again = false;
311
- critical = null;
312
- critical_done = false;
313
- release_critical = null;
314
- critical_report = null;
315
- input_seen = false;
411
+ drainer.stop();
412
+ drainer = drain_definition.create();
413
+ critical.drop(() => true);
414
+ gate.stop();
415
+ gate = gate_definition.create();
316
416
  }
317
- export { BACKGROUND_WINDOW, CRITICAL_CAP_MS, critical_outcome, critical_settled, fetch_priority_of, load_slot, preload_urls, reset_load_scheduler, viewport_class };
417
+ export { BACKGROUND_WINDOW, CRITICAL_CAP_MS, LoadSlot, critical_outcome, critical_settled, fetch_priority_of, preload_urls, reset_load_scheduler, viewport_class };
@@ -1,4 +1,8 @@
1
+ import { import_again } from "./import-again.js";
1
2
  import { speculate_url } from "./speculate-hint.js";
3
+ import { OgygiaError, warn } from "../messages/core.js";
4
+ import { download } from "../policies.js";
5
+ import { STALE, flights } from "@ogygia/machine";
2
6
  // src/runtime/nav-handle.ts
3
7
  /**
4
8
  * THE NAVIGATION HANDLE — how ISLAND-SIDE code (the `$app/navigation` shim, `ogygia/app`, the
@@ -25,6 +29,21 @@ import { speculate_url } from "./speculate-hint.js";
25
29
  * purpose: importing {@link NAV_HANDLE_KEY} would be the very edge this module exists to avoid).
26
30
  */
27
31
  const NAV_HANDLE_KEY = Symbol.for("ogygia.nav");
32
+ /**
33
+ * THE SOFT REFRESH (./router-nav.ts `invalidate_all`), loaded once, on the first refresh. A
34
+ * download that fails is tried again at once, and a failure is never kept: the next refresh loads
35
+ * it afresh, under a fresh URL (./import-again.ts: the browser keeps the failed one). It once
36
+ * imported the same URL again, which fails at once for the life of the page. (Only the refresh is
37
+ * read from the module, so the bundler leaves the rest of the navigation out of this chunk; the
38
+ * router's own load, ./router.ts, reads all of it.)
39
+ */
40
+ const load_refresh = flights({
41
+ name: "soft refresh",
42
+ run: import_again(() => import("./router-nav.js").then(({ invalidate_all }) => ({ invalidate_all }))),
43
+ retry: download
44
+ });
45
+ /** The refresh, once its module is in (never STALE: nothing drops it). */
46
+ const invalidate_all = () => load_refresh.get().then((n) => n === STALE ? void 0 : n.invalidate_all());
28
47
  /** Feature entry (`mpa-nav`, boot phase): the MPA handle, first — the router's install, later in
29
48
  * the feature order, replaces it. */
30
49
  function install() {
@@ -43,14 +62,14 @@ function mpa_nav() {
43
62
  return {
44
63
  goto(url, opts = {}) {
45
64
  const target = new URL(url, location.href);
46
- if (target.protocol !== "http:" && target.protocol !== "https:") throw new Error("[ogygia] goto() only supports http(s) URLs");
47
- if (target.origin !== location.origin && !opts.external) throw new Error("[ogygia] goto() only supports same-origin URLs (pass { external: true } to leave)");
65
+ if (target.protocol !== "http:" && target.protocol !== "https:") throw new OgygiaError("goto_not_http");
66
+ if (target.origin !== location.origin && !opts.external) throw new OgygiaError("goto_cross_origin");
48
67
  if (opts.replaceState) location.replace(target.href);
49
68
  else location.assign(target.href);
50
69
  return Promise.resolve();
51
70
  },
52
- invalidate: () => import("./router-nav.js").then((n) => n.invalidate_all()),
53
- invalidateAll: () => import("./router-nav.js").then((n) => n.invalidate_all()),
71
+ invalidate: invalidate_all,
72
+ invalidateAll: invalidate_all,
54
73
  preloadData(url) {
55
74
  speculate_url(url, "prerender");
56
75
  return Promise.resolve({
@@ -65,10 +84,10 @@ function mpa_nav() {
65
84
  },
66
85
  disableScrollHandling() {},
67
86
  pushState() {
68
- console.warn("[ogygia] pushState() shallow routing is not supported; use goto().");
87
+ warn("shallow_routing_unsupported", { method: "pushState" });
69
88
  },
70
89
  replaceState() {
71
- console.warn("[ogygia] replaceState() shallow routing is not supported; use goto().");
90
+ warn("shallow_routing_unsupported", { method: "replaceState" });
72
91
  },
73
92
  beforeNavigate: () => NO_UNSUBSCRIBE,
74
93
  afterNavigate: () => NO_UNSUBSCRIBE,
@@ -0,0 +1,24 @@
1
+ import { Machine } from "@ogygia/machine";
2
+ type States = {
3
+ idle: {};
4
+ claimed: {
5
+ href: string;
6
+ };
7
+ loading: {
8
+ href: string;
9
+ };
10
+ settling: {
11
+ href: string;
12
+ };
13
+ };
14
+ declare const steps: {
15
+ readonly idle: readonly ["claimed"];
16
+ readonly claimed: readonly ["claimed", "loading", "idle"];
17
+ readonly loading: readonly ["claimed", "settling", "idle"];
18
+ readonly settling: readonly ["claimed", "idle"];
19
+ };
20
+ /** The navigation machine the router holds. */
21
+ export type Navigation = Machine<States, typeof steps>;
22
+ /** A navigation machine, idle. */
23
+ export declare function create_navigation(): Navigation;
24
+ export {};
@@ -0,0 +1,24 @@
1
+ import { machine } from "@ogygia/machine";
2
+ const definition = machine()({
3
+ name: "navigation",
4
+ initial: "idle",
5
+ steps: {
6
+ idle: ["claimed"],
7
+ claimed: [
8
+ "claimed",
9
+ "loading",
10
+ "idle"
11
+ ],
12
+ loading: [
13
+ "claimed",
14
+ "settling",
15
+ "idle"
16
+ ],
17
+ settling: ["claimed", "idle"]
18
+ }
19
+ });
20
+ /** A navigation machine, idle. */
21
+ function create_navigation() {
22
+ return definition.create();
23
+ }
24
+ export { create_navigation };
@@ -24,7 +24,7 @@
24
24
  * DOM is not the island drifting).
25
25
  *
26
26
  * `data-ogygia-keep` on an `<ogygia-region>` is NOT `foreign`: there it names an island that survives
27
- * navigation (KeepHost) — Svelte owns that island, and it is repaired and hydrated like any other.
27
+ * navigation (a kept island) — Svelte owns that island, and it is repaired and hydrated like any other.
28
28
  *
29
29
  * FACETS (note §11): `owner_of` answers who writes INSIDE an element; the same module answers who
30
30
  * writes its ATTRIBUTES (`attributes_writer`, `REGION_RENDER_ATTRS`), who ORDERS its children
@@ -1,5 +1,5 @@
1
+ import { boot_link } from "./slots.js";
1
2
  import { PAGE_DEFER_BOOTSTRAP, PAGE_DEFER_GLOBAL, PAGE_DEFER_KEY, PAGE_DEFER_REGISTRY_KEY, PAGE_SETTLED_KEY } from "../page-defer.js";
2
- import { parse } from "devalue";
3
3
  // src/runtime/page-defer.ts
4
4
  /**
5
5
  * Client side of streaming `$page.data` promises into islands (see `../page-defer.ts` for the why).
@@ -70,13 +70,13 @@ function install_page_defer(extra_revivers) {
70
70
  const r = reg();
71
71
  if (r.live) return;
72
72
  r.revivers = page_defer_revivers(extra_revivers);
73
- r.live = (id, ok, encoded) => {
73
+ r.live = (id, ok, encoded) => boot_link().with_devalue(({ parse }) => {
74
74
  let value = null;
75
75
  try {
76
76
  value = parse(encoded, r.revivers);
77
77
  } catch {}
78
78
  settle(id, ok, value);
79
- };
79
+ });
80
80
  const queued = r.q;
81
81
  r.q = [];
82
82
  if (queued) for (const [id, ok, encoded] of queued) r.live(id, ok, encoded);
@@ -13,10 +13,21 @@
13
13
  * swap-then-blank-then-paint flicker). "Unsafe" means only "no sanitizer"; a region body is our own
14
14
  * signed, same-origin SSR (HOLE-TRUST), so it is exactly as trusted as the page itself.
15
15
  *
16
- * NOTE the parse happens in a `<template>`'s inert content, so custom elements do NOT upgrade here
17
- * (no `connectedCallback`) — attaching a declarative shadow root is a parse step, not an upgrade. That
18
- * is what `repair_if_drifted` needs: the server copy stays inert, but its light-DOM sequence is now
19
- * DSD-consistent with a live region whose hosts already carry shadow roots.
16
+ * INERT BY CONSTRUCTION (#382). The parse runs in a document with no browsing context (one per
17
+ * runtime, made on first use): it has no custom element registry, so nothing in the answer is built
18
+ * by a component's constructor before the restore (./restore.ts) puts it in shape, in any engine.
19
+ * A page-owned `<template>` was inert by a browser detail only: Firefox 159 (scoped registries)
20
+ * builds custom elements while one parses with `setHTMLUnsafe`, so a host had its constructor's
21
+ * root before the restore reached it. Attaching a declarative shadow root is a parse step, not an
22
+ * upgrade: it still happens here. The fragment's nodes belong to that document until they are
23
+ * inserted (insertion adopts them; they upgrade at connect, constructor then `connectedCallback`,
24
+ * a declarative root kept). `repair_if_drifted` needs this too: the server copy stays inert, and its
25
+ * light-DOM sequence is DSD-consistent with a live region whose hosts already carry shadow roots.
26
+ *
27
+ * (`Document.parseHTMLUnsafe` would also parse without a registry, but makes a whole document per
28
+ * call; one shared document and its template is the cheaper of the two.)
29
+ *
30
+ * In dev and e2e every parse is checked (`rule_parse_upgraded`, ./rules.ts): nothing built.
20
31
  */
21
32
  export declare function parse_region_html(html: string): DocumentFragment;
22
33
  /** The spec's declarative-shadow-DOM attach step, for engines that render DSD on first parse but predate