@ogygia/devtools 0.12.0 → 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 (79) hide show
  1. package/dist/app.d.ts +3 -0
  2. package/dist/app.js +9 -0
  3. package/dist/devtools/BoundaryOverlay.svelte +10 -6
  4. package/dist/devtools/Devtools.svelte +24 -18
  5. package/dist/devtools/IslandDetail.svelte +7 -5
  6. package/dist/devtools/PageTab.svelte +4 -2
  7. package/dist/devtools/ProfilerTab.svelte +8 -2
  8. package/dist/devtools/SessionTab.svelte +21 -8
  9. package/dist/devtools/highlight.js +14 -7
  10. package/dist/devtools/ledger-dom.js +6 -9
  11. package/dist/devtools/names.js +18 -17
  12. package/dist/devtools/page.d.ts +2 -0
  13. package/dist/devtools/page.js +37 -17
  14. package/dist/devtools/profile-store.d.ts +4 -9
  15. package/dist/devtools/profile-store.js +26 -21
  16. package/dist/devtools/session.d.ts +2 -0
  17. package/dist/devtools/session.js +48 -42
  18. package/dist/devtools/testing-engine.js +10 -1
  19. package/dist/devtools/ui-app.d.ts +3 -1
  20. package/dist/devtools/ui-app.js +9 -3
  21. package/dist/devtools/ui.js +83 -40
  22. package/dist/messages/build.d.ts +21 -0
  23. package/dist/messages/build.js +56 -0
  24. package/dist/messages/index.d.ts +3 -0
  25. package/dist/messages/index.js +3 -0
  26. package/dist/messages/runtime.d.ts +63 -0
  27. package/dist/messages/runtime.js +122 -0
  28. package/dist/profiler/async-io.d.ts +12 -7
  29. package/dist/profiler/async-io.js +94 -82
  30. package/dist/profiler/beacon-standalone.js +15 -6
  31. package/dist/profiler/busy.d.ts +13 -0
  32. package/dist/profiler/busy.js +45 -0
  33. package/dist/profiler/client.js +5 -24
  34. package/dist/profiler/embedded-maps.js +9 -5
  35. package/dist/profiler/gc.d.ts +5 -0
  36. package/dist/profiler/gc.js +75 -11
  37. package/dist/profiler/index.d.ts +23 -2
  38. package/dist/profiler/index.js +648 -373
  39. package/dist/profiler/net.d.ts +14 -6
  40. package/dist/profiler/net.js +61 -45
  41. package/dist/profiler/patterns.js +29 -1
  42. package/dist/profiler/source-scan.js +24 -12
  43. package/dist/profiler/storage/index.d.ts +14 -3
  44. package/dist/profiler/storage/index.js +41 -41
  45. package/dist/profiler/storage/postgres.d.ts +18 -5
  46. package/dist/profiler/storage/postgres.js +124 -85
  47. package/dist/profiler/storage/redis.d.ts +18 -5
  48. package/dist/profiler/storage/redis.js +160 -131
  49. package/dist/profiler/storage/sqlite.d.ts +15 -2
  50. package/dist/profiler/storage/sqlite.js +54 -48
  51. package/dist/profiler/ui/ComponentsTable.svelte +2 -2
  52. package/dist/profiler/ui/Dashboard.svelte +2 -3
  53. package/dist/profiler/ui/DownloadHtml.svelte +1 -0
  54. package/dist/profiler/ui/ExportButton.svelte +1 -0
  55. package/dist/profiler/ui/Flame.svelte +10 -4
  56. package/dist/profiler/ui/Flame.svelte.d.ts +0 -5
  57. package/dist/profiler/ui/FrameDetails.svelte +13 -2
  58. package/dist/profiler/ui/FrameDetails.svelte.d.ts +0 -6
  59. package/dist/profiler/ui/FunctionsTable.svelte +2 -2
  60. package/dist/profiler/ui/IslandsTable.svelte +2 -2
  61. package/dist/profiler/ui/OnThisPage.svelte +12 -4
  62. package/dist/profiler/ui/RunView.svelte +31 -27
  63. package/dist/profiler/ui/ShareLink.svelte +6 -1
  64. package/dist/profiler/ui/Sidebar.svelte +14 -9
  65. package/dist/profiler/ui/Treemap.svelte +11 -4
  66. package/dist/profiler/ui/Treemap.svelte.d.ts +0 -6
  67. package/dist/profiler/ui/WaitingTable.svelte +2 -2
  68. package/dist/profiler/ui/fake-progress.svelte.d.ts +7 -12
  69. package/dist/profiler/ui/fake-progress.svelte.js +57 -36
  70. package/dist/profiler/ui/press.js +4 -2
  71. package/dist/profiler/ui/row-anchor.svelte.d.ts +0 -6
  72. package/dist/profiler/ui/row-anchor.svelte.js +13 -8
  73. package/dist/profiler/ui/sort.svelte.d.ts +9 -6
  74. package/dist/profiler/ui/sort.svelte.js +34 -28
  75. package/dist/profiler/visit.d.ts +0 -1
  76. package/dist/profiler/visit.js +1 -2
  77. package/dist/server.js +27 -10
  78. package/dist/vite.js +3 -2
  79. package/package.json +13 -4
@@ -1,4 +1,5 @@
1
1
  import { all_regions, region_name } from "./regions.js";
2
+ import { STALE, flights, machine } from "@ogygia/machine";
2
3
  import { add_sink, snapshot } from "@ogygia/core/events";
3
4
  // src/devtools/session.ts
4
5
  /**
@@ -126,6 +127,17 @@ function describe_el(el) {
126
127
  const text = (el.textContent ?? "").trim().slice(0, 24);
127
128
  return text ? `${s} "${text}${(el.textContent ?? "").trim().length > 24 ? "…" : ""}"` : s;
128
129
  }
130
+ /** One recording's run. Leaving `recording`, whichever way, takes its watchers off, once. */
131
+ const run_definition = machine()({
132
+ name: "session-recorder",
133
+ initial: "recording",
134
+ steps: {
135
+ recording: ["saved", "stopped"],
136
+ saved: [],
137
+ stopped: []
138
+ }
139
+ });
140
+ /** The recording now: the newest started and not yet stopped. */
129
141
  let active = null;
130
142
  /** the last finished session (its data + report), for every tab: the Record tab writes it, the
131
143
  * island detail reads its island's part (the report type lives in session-insights.ts) */
@@ -147,6 +159,8 @@ function recording() {
147
159
  /** Start recording (or go on with the parts earlier pages saved — `resume_session_if_any`). */
148
160
  function start_session(prior) {
149
161
  if (active) return active;
162
+ const run = run_definition.create();
163
+ const s = run.scope;
150
164
  const from = performance.now();
151
165
  const seq0 = snapshot().at(-1)?.seq ?? -1;
152
166
  const interactions = [];
@@ -160,20 +174,21 @@ function start_session(prior) {
160
174
  for (const r of all_regions()) if (r.fp && !names[r.fp]) names[r.fp] = region_name(r.entry);
161
175
  };
162
176
  note_names();
163
- const off_nav = add_sink((e) => {
177
+ s.defer(add_sink((e) => {
164
178
  if (e.name === "nav.start") note_names();
165
- });
166
- const observers = [];
179
+ }));
167
180
  const watch = (type, cb, extra = {}) => {
168
181
  try {
169
- const po = new PerformanceObserver((l) => cb(l.getEntries()));
182
+ const po = s.watch(new PerformanceObserver((l) => cb(l.getEntries())));
170
183
  po.observe({
171
184
  type,
172
185
  ...extra
173
186
  });
174
- observers.push({
175
- po,
176
- cb
187
+ s.defer(() => {
188
+ try {
189
+ const left = po.takeRecords();
190
+ if (left.length) cb(left);
191
+ } catch {}
177
192
  });
178
193
  } catch {}
179
194
  };
@@ -234,7 +249,7 @@ function start_session(prior) {
234
249
  }
235
250
  });
236
251
  const mut_log = [];
237
- const mo = new MutationObserver((records) => {
252
+ const mo = s.watch(new MutationObserver((records) => {
238
253
  const now = r1(performance.now());
239
254
  const seen = /* @__PURE__ */ new Set();
240
255
  for (const m of records) {
@@ -249,7 +264,7 @@ function start_session(prior) {
249
264
  });
250
265
  }
251
266
  }
252
- });
267
+ }));
253
268
  const clicks = [];
254
269
  const on_click = (e) => {
255
270
  if (clicks.length >= 2e3) return;
@@ -263,7 +278,7 @@ function start_session(prior) {
263
278
  ...el && looks_clickable(el) ? { clickable: true } : {}
264
279
  });
265
280
  };
266
- addEventListener("click", on_click, {
281
+ s.on(globalThis, "click", on_click, {
267
282
  capture: true,
268
283
  passive: true
269
284
  });
@@ -278,13 +293,12 @@ function start_session(prior) {
278
293
  until: performance.now() + 50
279
294
  };
280
295
  };
281
- const INPUTS = [
296
+ for (const type of [
282
297
  "click",
283
298
  "submit",
284
299
  "keydown",
285
300
  "change"
286
- ];
287
- for (const type of INPUTS) addEventListener(type, on_input, {
301
+ ]) s.on(globalThis, type, on_input, {
288
302
  capture: true,
289
303
  passive: true
290
304
  });
@@ -303,7 +317,7 @@ function start_session(prior) {
303
317
  fp: by ? island_of(by)?.getAttribute("data-og-fp") ?? null : input?.fp ?? null
304
318
  });
305
319
  };
306
- navigation?.addEventListener("navigate", on_navigate);
320
+ if (navigation) s.on(navigation, "navigate", on_navigate);
307
321
  const requests = [];
308
322
  watch("resource", (es) => {
309
323
  for (const r of es) {
@@ -340,9 +354,10 @@ function start_session(prior) {
340
354
  t: r1(performance.now()),
341
355
  message: "unhandled rejection: " + String(e.reason?.message ?? e.reason).slice(0, 280)
342
356
  });
343
- addEventListener("error", on_error);
344
- addEventListener("unhandledrejection", on_rej);
357
+ s.on(globalThis, "error", on_error);
358
+ s.on(globalThis, "unhandledrejection", on_rej);
345
359
  const console_error = console.error;
360
+ s.defer(() => console.error = console_error);
346
361
  console.error = (...args) => {
347
362
  if (errors.length < 200) errors.push({
348
363
  t: r1(performance.now()),
@@ -363,23 +378,6 @@ function start_session(prior) {
363
378
  cpu_off = "no-policy";
364
379
  }
365
380
  const heap0 = performance.memory?.usedJSHeapSize ?? null;
366
- /** stop watching (the entries still queued count too) */
367
- const detach = () => {
368
- for (const { po, cb } of observers) try {
369
- const left = po.takeRecords();
370
- if (left.length) cb(left);
371
- po.disconnect();
372
- } catch {}
373
- mo.disconnect();
374
- off_nav();
375
- removeEventListener("error", on_error);
376
- removeEventListener("unhandledrejection", on_rej);
377
- removeEventListener("click", on_click, { capture: true });
378
- for (const type of INPUTS) removeEventListener(type, on_input, { capture: true });
379
- navigation?.removeEventListener("navigate", on_navigate);
380
- removeEventListener("pagehide", on_hide);
381
- console.error = console_error;
382
- };
383
381
  /** this document's part of the session */
384
382
  const collect = (cpu) => {
385
383
  const heap1 = performance.memory?.usedJSHeapSize ?? null;
@@ -416,9 +414,7 @@ function start_session(prior) {
416
414
  };
417
415
  };
418
416
  const on_hide = (e) => {
419
- if (e.persisted || active !== rec) return;
420
- detach();
421
- active = null;
417
+ if (e.persisted || !run.move("recording", "saved")) return;
422
418
  const strip = (s) => JSON.parse(JSON.stringify({
423
419
  ...s,
424
420
  cpu: null
@@ -430,12 +426,14 @@ function start_session(prior) {
430
426
  }]));
431
427
  } catch {}
432
428
  };
433
- addEventListener("pagehide", on_hide);
434
- const rec = {
435
- started: prior?.length ? prior[0].data.from + (prior[0].origin - performance.timeOrigin) : from,
436
- async stop() {
437
- active = null;
438
- detach();
429
+ s.on(globalThis, "pagehide", on_hide);
430
+ s.defer(() => {
431
+ if (active === rec) active = null;
432
+ });
433
+ const stopping = flights({
434
+ name: "session stop",
435
+ run: async () => {
436
+ run.move("recording", "stopped");
439
437
  let cpu = null;
440
438
  if (prof) try {
441
439
  cpu = await prof.stop();
@@ -448,6 +446,14 @@ function start_session(prior) {
448
446
  } catch {}
449
447
  return prior?.length ? merge_parts(prior, here) : here;
450
448
  }
449
+ });
450
+ const stop = async () => {
451
+ const data = await stopping.get();
452
+ return data === STALE ? stop() : data;
453
+ };
454
+ const rec = {
455
+ started: prior?.length ? prior[0].data.from + (prior[0].origin - performance.timeOrigin) : from,
456
+ stop
451
457
  };
452
458
  active = rec;
453
459
  return rec;
@@ -5,6 +5,7 @@ import { region_name_by_fp } from "./regions.js";
5
5
  import { ensure_region_names } from "./names.js";
6
6
  import { TESTING_ENGINE_VERSION, analyze_session, apply_attr_rules, session_timeline, testing_lean } from "@ogygia/core/insights";
7
7
  import { charge, nearest_attr, own_cost } from "@ogygia/core/internal/instruments";
8
+ import { scope } from "@ogygia/machine";
8
9
  import { snapshot } from "@ogygia/core/events";
9
10
  // src/devtools/testing-engine.ts
10
11
  /**
@@ -202,6 +203,14 @@ async function record_stop(blocked = []) {
202
203
  /** Resolves when no island is mid-hydration and nothing woke for `quiet` ms (or `timeout` passed). */
203
204
  async function settled(quiet = 300, timeout = 1e4) {
204
205
  const until = performance.now() + timeout;
206
+ const steps = scope();
207
+ try {
208
+ return await settle_poll(steps, quiet, until);
209
+ } finally {
210
+ steps.close();
211
+ }
212
+ }
213
+ async function settle_poll(steps, quiet, until) {
205
214
  for (;;) {
206
215
  const ev = snapshot();
207
216
  const started = /* @__PURE__ */ new Set();
@@ -214,7 +223,7 @@ async function settled(quiet = 300, timeout = 1e4) {
214
223
  }
215
224
  if (!started.size && performance.now() - last >= quiet && document.readyState === "complete") return true;
216
225
  if (performance.now() > until) return false;
217
- await new Promise((r) => setTimeout(r, 50));
226
+ await steps.task(() => new Promise((r) => steps.timeout(r, 50)));
218
227
  }
219
228
  }
220
229
  export { hydration_json, note_engine_files, page_json, record_start, record_stop, settled };
@@ -1,5 +1,7 @@
1
+ /** Mounts the dock into `root`. Returns its teardown: the app (its Alt+O listener and timers with
2
+ * it) unmounted and its observers off. */
1
3
  export declare function mount_app(root: ShadowRoot, opts: {
2
4
  csr_true: boolean;
3
5
  start_open: boolean;
4
6
  hide?: () => void;
5
- }): void;
7
+ }): () => void;
@@ -1,7 +1,8 @@
1
1
  import { install_page_hook } from "./page.js";
2
2
  import { set_highlight_root } from "./highlight.js";
3
+ import { scope } from "@ogygia/machine";
3
4
  import { names } from "virtual:ogygia/devtools-meta";
4
- import { mount } from "svelte";
5
+ import { mount, unmount } from "svelte";
5
6
  import Devtools from "./Devtools.svelte";
6
7
  // src/devtools/ui-app.ts
7
8
  /**
@@ -10,6 +11,8 @@ import Devtools from "./Devtools.svelte";
10
11
  * only the launcher in the runtime chunk: this file, the components and their CSS load when someone
11
12
  * opens the dock (a dynamic import the bundler splits off). On the dev server it loads at once.
12
13
  */
14
+ /** Mounts the dock into `root`. Returns its teardown: the app (its Alt+O listener and timers with
15
+ * it) unmounted and its observers off. */
13
16
  function mount_app(root, opts) {
14
17
  install_page_hook();
15
18
  set_highlight_root(root);
@@ -17,7 +20,7 @@ function mount_app(root, opts) {
17
20
  ...names,
18
21
  ...window.__ogygia_region_names
19
22
  };
20
- mount(Devtools, {
23
+ const app = mount(Devtools, {
21
24
  target: root,
22
25
  props: {
23
26
  csrTrue: opts.csr_true,
@@ -26,12 +29,15 @@ function mount_app(root, opts) {
26
29
  }
27
30
  });
28
31
  adopt_scoped_styles(root);
29
- const mo = new MutationObserver(() => adopt_scoped_styles(root));
32
+ const s = scope();
33
+ s.defer(() => void unmount(app));
34
+ const mo = s.watch(new MutationObserver(() => adopt_scoped_styles(root)));
30
35
  mo.observe(document.head, { childList: true });
31
36
  mo.observe(root, {
32
37
  childList: true,
33
38
  subtree: true
34
39
  });
40
+ return () => s.close();
35
41
  }
36
42
  /** the dock's `<style>`s a page's Content-Security-Policy refused, already re-made as sheets */
37
43
  const remade = /* @__PURE__ */ new WeakSet();
@@ -1,4 +1,7 @@
1
+ import { OgygiaError } from "../messages/runtime.js";
1
2
  import { TESTING_ENGINE_VERSION, devtools_switched_on, switch_in_url, testing_lean, without_switch, write_switch } from "@ogygia/core/insights";
3
+ import { import_again } from "@ogygia/core/internal/instruments";
4
+ import { STALE, flights, machine } from "@ogygia/machine";
2
5
  // src/devtools/ui.ts
3
6
  /**
4
7
  * The devtools LAUNCHER — the only part of devtools the runtime chunk carries. Called from the runtime
@@ -17,13 +20,30 @@ import { TESTING_ENGINE_VERSION, devtools_switched_on, switch_in_url, testing_le
17
20
  * The switch's cookie also starts the rest, for that browser only: the server writes its event
18
21
  * side-channel and the page is measured from the start (runtime/beacon.ts).
19
22
  */
23
+ /** The dock's code: after a failed download, asked for again under a fresh URL. */
24
+ const import_app = import_again(() => import("./ui-app.js"));
20
25
  const DEVTOOLS = typeof __OGYGIA_DEVTOOLS__ !== "undefined" ? __OGYGIA_DEVTOOLS__ : false;
21
26
  const LAZY = typeof __OGYGIA_DEVTOOLS_LAZY__ !== "undefined" ? __OGYGIA_DEVTOOLS_LAZY__ : false;
22
27
  const LAYOUT_KEY = "ogygia:devtools:layout:v4";
23
- let mounted = false;
28
+ const dock_definition = machine()({
29
+ name: "devtools-dock",
30
+ initial: "button",
31
+ steps: {
32
+ button: ["loading", "off"],
33
+ loading: [
34
+ "button",
35
+ "mounted",
36
+ "off"
37
+ ],
38
+ mounted: ["off"],
39
+ off: []
40
+ }
41
+ });
42
+ /** This module's dock, until devtools is turned off. */
43
+ let dock = null;
24
44
  let launch_opts;
25
45
  function install_devtools_ui(opts) {
26
- if (!DEVTOOLS || typeof document === "undefined" || mounted) return;
46
+ if (!DEVTOOLS || typeof document === "undefined" || dock) return;
27
47
  if (document.querySelector("meta[name=\"ogygia-devtools\"][content=\"off\"]")) return;
28
48
  launch_opts = opts;
29
49
  install_testing_api();
@@ -37,10 +57,9 @@ function install_devtools_ui(opts) {
37
57
  }
38
58
  /** The launcher (and, on the dev server or for a dock left open, the dock). */
39
59
  function mount_launcher(opts) {
40
- if (mounted || document.querySelector("[data-ogygia-devtools-host]")) return;
41
- mounted = true;
60
+ if (dock || document.querySelector("[data-ogygia-devtools-host]")) return;
42
61
  try {
43
- if (sessionStorage.getItem("ogygia:devtools:session")) import("./session.js").then((m) => m.resume_session_if_any());
62
+ if (sessionStorage.getItem("ogygia:devtools:session")) import("./session.js").then((m) => m.resume_session_if_any(), (error) => console.error(new OgygiaError("devtools_dock_load_failed"), error));
44
63
  } catch {}
45
64
  const host = document.createElement("div");
46
65
  host.setAttribute("data-ogygia-devtools-host", "");
@@ -48,66 +67,85 @@ function mount_launcher(opts) {
48
67
  document.documentElement.appendChild(host);
49
68
  const root = host.attachShadow({ mode: "open" });
50
69
  const csr_true = opts?.csr_true ?? false;
51
- let loading = false;
70
+ const here = {
71
+ machine: dock_definition.create(),
72
+ host,
73
+ launcher: new AbortController()
74
+ };
75
+ dock = here;
76
+ const state = here.machine;
52
77
  let want_open = false;
53
- let button = null;
54
- let on_key = null;
78
+ const button = document.createElement("button");
55
79
  const load = () => {
56
- if (loading) return;
57
- loading = true;
58
- import("./ui-app.js").then((m) => {
59
- button?.remove();
60
- if (on_key) removeEventListener("keydown", on_key);
61
- m.mount_app(root, {
80
+ if (!state.move("button", "loading")) return;
81
+ state.scope.task(() => import_app()).then((app) => {
82
+ if (app === STALE || !state.move("loading", "mounted")) return;
83
+ here.launcher.abort();
84
+ button.remove();
85
+ state.scope.defer(app.mount_app(root, {
62
86
  csr_true,
63
87
  start_open: want_open,
64
88
  ...LAZY ? { hide: devtools_off } : {}
65
- });
89
+ }));
90
+ }, (error) => {
91
+ if (!state.move("loading", "button")) return;
92
+ want_open = false;
93
+ button.textContent = "og devtools";
94
+ console.error(new OgygiaError("devtools_dock_load_failed"), error);
66
95
  });
67
96
  };
68
- button = document.createElement("button");
69
97
  button.setAttribute("data-og-panel-toggle", "");
70
98
  button.title = "ogygia devtools (Alt+O)";
71
99
  button.textContent = "og devtools";
72
100
  button.style.cssText = "position:fixed;right:24px;bottom:8px;padding:7px 12px;border-radius:999px;border:1px solid rgba(148,163,184,.35);background:#0b1220;color:#e2e8f0;font:600 12px/1 ui-monospace,SFMono-Regular,Menlo,monospace;cursor:pointer;opacity:.55;box-shadow:0 4px 16px rgba(0,0,0,.4)";
73
- const b = button;
74
- b.onmouseenter = () => b.style.opacity = "1";
75
- b.onmouseleave = () => b.style.opacity = ".55";
101
+ button.onmouseenter = () => button.style.opacity = "1";
102
+ button.onmouseleave = () => button.style.opacity = ".55";
76
103
  const open = () => {
77
104
  want_open = true;
78
- b.textContent = "loading…";
105
+ button.textContent = "loading…";
79
106
  load();
80
107
  };
81
- b.onclick = open;
82
- on_key = (e) => {
108
+ button.onclick = open;
109
+ addEventListener("keydown", (e) => {
83
110
  if (e.altKey && (e.key === "o" || e.key === "O")) {
84
111
  e.preventDefault();
85
112
  open();
86
113
  }
87
- };
88
- addEventListener("keydown", on_key);
89
- root.appendChild(b);
114
+ }, { signal: here.launcher.signal });
115
+ root.appendChild(button);
90
116
  if (was_open()) load();
91
- else if (!LAZY) after_load_idle(load);
117
+ else if (!LAZY) after_load_idle(state.scope, load);
92
118
  }
93
- function after_load_idle(fn) {
119
+ /** Runs `fn` once the page is loaded and the browser idle, unless `s` closes first. */
120
+ function after_load_idle(s, fn) {
94
121
  const idle = () => {
95
- const ric = globalThis.requestIdleCallback;
96
- if (ric) ric(fn, { timeout: 3e3 });
97
- else setTimeout(fn, 1e3);
122
+ const host = globalThis;
123
+ if (!host.requestIdleCallback) {
124
+ s.timeout(fn, 1e3);
125
+ return;
126
+ }
127
+ const id = host.requestIdleCallback(fn, { timeout: 3e3 });
128
+ s.defer(() => host.cancelIdleCallback?.(id));
98
129
  };
99
- if (document.readyState === "complete") setTimeout(idle, 500);
100
- else addEventListener("load", () => setTimeout(idle, 500), { once: true });
130
+ if (document.readyState === "complete") s.timeout(idle, 500);
131
+ else s.on(globalThis, "load", () => s.timeout(idle, 500), { once: true });
101
132
  }
102
133
  function install_testing_api() {
103
134
  if (window.__ogygia_testing) return;
104
- let loading = null;
105
- const engine = () => loading ??= (async () => {
106
- const from = performance.now();
107
- const m = await import("./testing-engine.js");
108
- m.note_engine_files(from, performance.now());
109
- return m;
110
- })();
135
+ const import_engine = import_again(() => import("./testing-engine.js"));
136
+ const load_engine = flights({
137
+ name: "testing engine",
138
+ run: async () => {
139
+ const from = performance.now();
140
+ const m = await import_engine();
141
+ m.note_engine_files(from, performance.now());
142
+ return m;
143
+ }
144
+ });
145
+ const engine = async () => {
146
+ const m = await load_engine.get();
147
+ return m === STALE ? engine() : m;
148
+ };
111
149
  window.__ogygia_testing = {
112
150
  version: TESTING_ENGINE_VERSION,
113
151
  page: async (cpu_fns) => (await engine()).page_json(cpu_fns),
@@ -142,8 +180,13 @@ function devtools_off() {
142
180
  try {
143
181
  localStorage.removeItem(LAYOUT_KEY);
144
182
  } catch {}
183
+ if (dock) {
184
+ dock.machine.go("off");
185
+ dock.launcher.abort();
186
+ dock.host.remove();
187
+ dock = null;
188
+ }
145
189
  document.querySelector("[data-ogygia-devtools-host]")?.remove();
146
- mounted = false;
147
190
  }
148
191
  function install_console_api() {
149
192
  const say = (on) => on ? "ogygia devtools: on in this browser (stays on; ogygia.devtools.off() to hide it)" : "ogygia devtools: off in this browser";
@@ -0,0 +1,21 @@
1
+ import { MessagesOf } from "@ogygia/core/internal/messages";
2
+ /** The errors: `throw new OgygiaError(code, args)`. */
3
+ export declare const DEVTOOLS_BUILD_ERRORS: {};
4
+ /** The warnings: `warn(code, args, ...rest)`. */
5
+ export declare const DEVTOOLS_BUILD_WARNINGS: {
6
+ devtools_on_in_build: {
7
+ text: () => string;
8
+ details: () => string;
9
+ };
10
+ devtools_profiler_secret_empty: {
11
+ text: () => string;
12
+ details: () => string;
13
+ };
14
+ };
15
+ type Own = MessagesOf<typeof DEVTOOLS_BUILD_ERRORS, typeof DEVTOOLS_BUILD_WARNINGS>;
16
+ /** `throw new OgygiaError(code, args)`; `warn(code, args, ...rest)` logs; `line(code, args)` is the
17
+ * line as a string. */
18
+ export declare const OgygiaError: Own["OgygiaError"];
19
+ export declare const warn: Own["warn"];
20
+ export declare const line: Own["line"];
21
+ export {};
@@ -0,0 +1,56 @@
1
+ import { define_messages, prose } from "@ogygia/core/internal/messages";
2
+ // src/messages/build.ts
3
+ /**
4
+ * EVERY MESSAGE THE DEVTOOLS' VITE PLUGIN PRINTS, in one place: its code, its wording, what to do
5
+ * about it (core's `@ogygia/core/internal/messages` says how the system works).
6
+ *
7
+ * warn('devtools_on_in_build');
8
+ *
9
+ * The plugin runs in Node, never in a page, so the wording always prints, then the code's page. The
10
+ * profiler's server half has its own (./runtime.ts).
11
+ */
12
+ /** The errors: `throw new OgygiaError(code, args)`. */
13
+ const DEVTOOLS_BUILD_ERRORS = {};
14
+ /** The warnings: `warn(code, args, ...rest)`. */
15
+ const DEVTOOLS_BUILD_WARNINGS = {
16
+ devtools_on_in_build: {
17
+ text: () => prose`
18
+ devtools is ON for this build: its pages carry the devtools launcher (the dock loads only
19
+ when opened). Keep it off for production: list \`devtools()\` only when
20
+ \`mode !== 'production'\`.
21
+ `,
22
+ details: () => prose`
23
+ A build ran with the devtools dock on, so every page it serves carries the devtools
24
+ launcher. The dock itself loads only when someone opens it. Said once per build, so a
25
+ dock left on by accident is seen before it ships.
26
+
27
+ To keep it out of production, list \`devtools()\` only when
28
+ \`mode !== 'production'\`.
29
+ `
30
+ },
31
+ devtools_profiler_secret_empty: {
32
+ text: () => prose`
33
+ profiler: \`profiler.secret\` is EMPTY in this build — the variable it reads is not set in
34
+ the BUILD environment. The UI will be DISABLED in production unless OGYGIA_PROFILER_SECRET
35
+ exists at runtime (on Amplify / Lambda it does not): /__profiler will answer "installed but
36
+ disabled". Set the variable where the build runs, and keep the \`profiler\` option
37
+ unconditional.
38
+ `,
39
+ details: () => prose`
40
+ The profiler was given a \`secret\`, but it is empty while the app builds: the environment
41
+ variable it reads is not set where the build runs. The secret is baked in at build time,
42
+ so the profiler's UI stays disabled in production unless \`OGYGIA_PROFILER_SECRET\` is
43
+ set on the running server, which some hosts never do.
44
+
45
+ Set the variable in the build environment, and keep the \`profiler\` option
46
+ unconditional.
47
+ `
48
+ }
49
+ };
50
+ const own = define_messages(DEVTOOLS_BUILD_ERRORS, DEVTOOLS_BUILD_WARNINGS);
51
+ /** `throw new OgygiaError(code, args)`; `warn(code, args, ...rest)` logs; `line(code, args)` is the
52
+ * line as a string. */
53
+ const OgygiaError = own.OgygiaError;
54
+ const warn = own.warn;
55
+ const line = own.line;
56
+ export { DEVTOOLS_BUILD_ERRORS, DEVTOOLS_BUILD_WARNINGS, OgygiaError, line, warn };
@@ -0,0 +1,3 @@
1
+ import { DEVTOOLS_BUILD_ERRORS, DEVTOOLS_BUILD_WARNINGS } from "./build.js";
2
+ import { DEVTOOLS_ERRORS, DEVTOOLS_WARNINGS } from "./runtime.js";
3
+ export { DEVTOOLS_BUILD_ERRORS, DEVTOOLS_BUILD_WARNINGS, DEVTOOLS_ERRORS, DEVTOOLS_WARNINGS };
@@ -0,0 +1,3 @@
1
+ import { DEVTOOLS_BUILD_ERRORS, DEVTOOLS_BUILD_WARNINGS } from "./build.js";
2
+ import { DEVTOOLS_ERRORS, DEVTOOLS_WARNINGS } from "./runtime.js";
3
+ export { DEVTOOLS_BUILD_ERRORS, DEVTOOLS_BUILD_WARNINGS, DEVTOOLS_ERRORS, DEVTOOLS_WARNINGS };
@@ -0,0 +1,63 @@
1
+ import { MessagesOf } from "@ogygia/core/internal/messages";
2
+ /** The errors: `throw new OgygiaError(code, args)`, or `console.error(new OgygiaError(…), err)` for a
3
+ * failure that is caught. */
4
+ export declare const DEVTOOLS_ERRORS: {
5
+ devtools_profiler_store_init_failed: {
6
+ text: () => string;
7
+ details: () => string;
8
+ };
9
+ devtools_profiler_mount_failed: {
10
+ text: () => string;
11
+ details: () => string;
12
+ };
13
+ devtools_profiler_alloc_sampling_unavailable: {
14
+ text: () => string;
15
+ details: () => string;
16
+ };
17
+ devtools_profiler_gc_attribution_failed: {
18
+ text: () => string;
19
+ details: () => string;
20
+ };
21
+ devtools_profiler_put_report_failed: {
22
+ text: () => string;
23
+ details: () => string;
24
+ };
25
+ devtools_profiler_get_report_failed: {
26
+ text: () => string;
27
+ details: () => string;
28
+ };
29
+ devtools_dock_load_failed: {
30
+ text: () => string;
31
+ details: () => string;
32
+ };
33
+ };
34
+ /** The warnings: `warn(code, args, ...rest)`. */
35
+ export declare const DEVTOOLS_WARNINGS: {
36
+ devtools_profiler_session_rejected: {
37
+ text: (a: {
38
+ path: string;
39
+ diagnosis: string;
40
+ reason: string;
41
+ }) => string;
42
+ details: () => string;
43
+ };
44
+ devtools_profiler_sink_undelivered: {
45
+ text: (a: {
46
+ rows: string;
47
+ }) => string;
48
+ details: () => string;
49
+ };
50
+ devtools_profiler_background_stopped: {
51
+ text: (a: {
52
+ loop: string;
53
+ }) => string;
54
+ details: () => string;
55
+ };
56
+ };
57
+ type Own = MessagesOf<typeof DEVTOOLS_ERRORS, typeof DEVTOOLS_WARNINGS>;
58
+ /** `throw new OgygiaError(code, args)`; `warn(code, args, ...rest)` logs; `line(code, args)` is the
59
+ * line as a string. Wording and link in dev and while prerendering, the link alone otherwise. */
60
+ export declare const OgygiaError: Own["OgygiaError"];
61
+ export declare const warn: Own["warn"];
62
+ export declare const line: Own["line"];
63
+ export {};