@ogygia/devtools 0.0.0 → 0.11.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 (310) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +24 -1
  3. package/dist/client.d.ts +3 -0
  4. package/dist/client.js +24 -0
  5. package/dist/devtools/BoundaryOverlay.svelte +396 -0
  6. package/dist/devtools/BoundaryOverlay.svelte.d.ts +19 -0
  7. package/dist/devtools/Devtools.svelte +663 -0
  8. package/dist/devtools/Devtools.svelte.d.ts +15 -0
  9. package/dist/devtools/HubTab.svelte +206 -0
  10. package/dist/devtools/HubTab.svelte.d.ts +11 -0
  11. package/dist/devtools/HydrationTab.svelte +237 -0
  12. package/dist/devtools/HydrationTab.svelte.d.ts +13 -0
  13. package/dist/devtools/IslandDetail.svelte +615 -0
  14. package/dist/devtools/IslandDetail.svelte.d.ts +15 -0
  15. package/dist/devtools/LedgerTab.svelte +287 -0
  16. package/dist/devtools/LedgerTab.svelte.d.ts +11 -0
  17. package/dist/devtools/LensTab.svelte +290 -0
  18. package/dist/devtools/LensTab.svelte.d.ts +19 -0
  19. package/dist/devtools/NavTab.svelte +229 -0
  20. package/dist/devtools/NavTab.svelte.d.ts +11 -0
  21. package/dist/devtools/PageTab.svelte +1122 -0
  22. package/dist/devtools/PageTab.svelte.d.ts +15 -0
  23. package/dist/devtools/ProfilerTab.svelte +755 -0
  24. package/dist/devtools/ProfilerTab.svelte.d.ts +15 -0
  25. package/dist/devtools/PropsTree.svelte +155 -0
  26. package/dist/devtools/PropsTree.svelte.d.ts +15 -0
  27. package/dist/devtools/SessionTab.svelte +796 -0
  28. package/dist/devtools/SessionTab.svelte.d.ts +11 -0
  29. package/dist/devtools/TimelineTab.svelte +267 -0
  30. package/dist/devtools/TimelineTab.svelte.d.ts +11 -0
  31. package/dist/devtools/WireTab.svelte +149 -0
  32. package/dist/devtools/WireTab.svelte.d.ts +11 -0
  33. package/dist/devtools/bytes.d.ts +70 -0
  34. package/dist/devtools/bytes.js +82 -0
  35. package/dist/devtools/cache-headers.d.ts +20 -0
  36. package/dist/devtools/cache-headers.js +81 -0
  37. package/dist/devtools/highlight.d.ts +7 -0
  38. package/dist/devtools/highlight.js +83 -0
  39. package/dist/devtools/hydration.d.ts +48 -0
  40. package/dist/devtools/hydration.js +93 -0
  41. package/dist/devtools/index.d.ts +3 -0
  42. package/dist/devtools/index.js +3 -0
  43. package/dist/devtools/ledger-dom.d.ts +7 -0
  44. package/dist/devtools/ledger-dom.js +151 -0
  45. package/dist/devtools/leftovers.d.ts +77 -0
  46. package/dist/devtools/leftovers.js +319 -0
  47. package/dist/devtools/names.d.ts +1 -0
  48. package/dist/devtools/names.js +34 -0
  49. package/dist/devtools/page.d.ts +96 -0
  50. package/dist/devtools/page.js +957 -0
  51. package/dist/devtools/profile-anchors.d.ts +22 -0
  52. package/dist/devtools/profile-anchors.js +135 -0
  53. package/dist/devtools/profile-store.d.ts +175 -0
  54. package/dist/devtools/profile-store.js +239 -0
  55. package/dist/devtools/regions.d.ts +77 -0
  56. package/dist/devtools/regions.js +132 -0
  57. package/dist/devtools/session.d.ts +31 -0
  58. package/dist/devtools/session.js +455 -0
  59. package/dist/devtools/since-load.d.ts +50 -0
  60. package/dist/devtools/since-load.js +89 -0
  61. package/dist/devtools/source-lines.d.ts +24 -0
  62. package/dist/devtools/source-lines.js +106 -0
  63. package/dist/devtools/styles.d.ts +52 -0
  64. package/dist/devtools/styles.js +248 -0
  65. package/dist/devtools/testing-engine.d.ts +83 -0
  66. package/dist/devtools/testing-engine.js +220 -0
  67. package/dist/devtools/ui-app.d.ts +5 -0
  68. package/dist/devtools/ui-app.js +69 -0
  69. package/dist/devtools/ui.d.ts +50 -0
  70. package/dist/devtools/ui.js +175 -0
  71. package/dist/profiler/alloc.d.ts +75 -0
  72. package/dist/profiler/alloc.js +76 -0
  73. package/dist/profiler/analyze.d.ts +437 -0
  74. package/dist/profiler/analyze.js +1941 -0
  75. package/dist/profiler/app-path.d.ts +7 -0
  76. package/dist/profiler/app-path.js +26 -0
  77. package/dist/profiler/async-io.d.ts +58 -0
  78. package/dist/profiler/async-io.js +187 -0
  79. package/dist/profiler/beacon-standalone.d.ts +4 -0
  80. package/dist/profiler/beacon-standalone.js +409 -0
  81. package/dist/profiler/browser-findings.d.ts +25 -0
  82. package/dist/profiler/browser-findings.js +260 -0
  83. package/dist/profiler/byte-strip.d.ts +58 -0
  84. package/dist/profiler/byte-strip.js +228 -0
  85. package/dist/profiler/client-files.d.ts +16 -0
  86. package/dist/profiler/client-files.js +60 -0
  87. package/dist/profiler/client-windows.d.ts +49 -0
  88. package/dist/profiler/client-windows.js +95 -0
  89. package/dist/profiler/client.d.ts +40 -0
  90. package/dist/profiler/client.js +82 -0
  91. package/dist/profiler/compare.d.ts +308 -0
  92. package/dist/profiler/compare.js +733 -0
  93. package/dist/profiler/compress.d.ts +10 -0
  94. package/dist/profiler/compress.js +36 -0
  95. package/dist/profiler/contention.d.ts +60 -0
  96. package/dist/profiler/contention.js +86 -0
  97. package/dist/profiler/crypto.d.ts +19 -0
  98. package/dist/profiler/crypto.js +101 -0
  99. package/dist/profiler/doc-diff.d.ts +47 -0
  100. package/dist/profiler/doc-diff.js +214 -0
  101. package/dist/profiler/dom-diff.d.ts +43 -0
  102. package/dist/profiler/dom-diff.js +121 -0
  103. package/dist/profiler/drill.d.ts +89 -0
  104. package/dist/profiler/drill.js +553 -0
  105. package/dist/profiler/embedded-maps.d.ts +37 -0
  106. package/dist/profiler/embedded-maps.js +135 -0
  107. package/dist/profiler/env.d.ts +6 -0
  108. package/dist/profiler/env.js +10 -0
  109. package/dist/profiler/forecast.d.ts +76 -0
  110. package/dist/profiler/forecast.js +225 -0
  111. package/dist/profiler/frames.d.ts +11 -0
  112. package/dist/profiler/frames.js +42 -0
  113. package/dist/profiler/gc.d.ts +144 -0
  114. package/dist/profiler/gc.js +271 -0
  115. package/dist/profiler/hole-slots.d.ts +31 -0
  116. package/dist/profiler/hole-slots.js +90 -0
  117. package/dist/profiler/index.d.ts +288 -0
  118. package/dist/profiler/index.js +4921 -0
  119. package/dist/profiler/insights.d.ts +96 -0
  120. package/dist/profiler/insights.js +239 -0
  121. package/dist/profiler/ledger.d.ts +121 -0
  122. package/dist/profiler/ledger.js +440 -0
  123. package/dist/profiler/lineage.d.ts +81 -0
  124. package/dist/profiler/lineage.js +359 -0
  125. package/dist/profiler/module-map.d.ts +12 -0
  126. package/dist/profiler/module-map.js +94 -0
  127. package/dist/profiler/net.d.ts +169 -0
  128. package/dist/profiler/net.js +586 -0
  129. package/dist/profiler/on-demand.d.ts +11 -0
  130. package/dist/profiler/on-demand.js +15 -0
  131. package/dist/profiler/own-requests.d.ts +9 -0
  132. package/dist/profiler/own-requests.js +17 -0
  133. package/dist/profiler/patterns.d.ts +476 -0
  134. package/dist/profiler/patterns.js +2203 -0
  135. package/dist/profiler/profiler-router.d.ts +245 -0
  136. package/dist/profiler/profiler-router.js +113 -0
  137. package/dist/profiler/refresh-stub.d.ts +2 -0
  138. package/dist/profiler/refresh-stub.js +37 -0
  139. package/dist/profiler/report.d.ts +1404 -0
  140. package/dist/profiler/report.js +2638 -0
  141. package/dist/profiler/river.d.ts +65 -0
  142. package/dist/profiler/river.js +298 -0
  143. package/dist/profiler/route-files.d.ts +22 -0
  144. package/dist/profiler/route-files.js +55 -0
  145. package/dist/profiler/route-imports.d.ts +14 -0
  146. package/dist/profiler/route-imports.js +83 -0
  147. package/dist/profiler/saving.d.ts +22 -0
  148. package/dist/profiler/saving.js +42 -0
  149. package/dist/profiler/score.d.ts +109 -0
  150. package/dist/profiler/score.js +261 -0
  151. package/dist/profiler/session-cookie.d.ts +28 -0
  152. package/dist/profiler/session-cookie.js +45 -0
  153. package/dist/profiler/sink.d.ts +81 -0
  154. package/dist/profiler/sink.js +124 -0
  155. package/dist/profiler/site-fixes.d.ts +25 -0
  156. package/dist/profiler/site-fixes.js +62 -0
  157. package/dist/profiler/source-scan.d.ts +210 -0
  158. package/dist/profiler/source-scan.js +1724 -0
  159. package/dist/profiler/span.d.ts +90 -0
  160. package/dist/profiler/span.js +109 -0
  161. package/dist/profiler/stacks.d.ts +43 -0
  162. package/dist/profiler/stacks.js +107 -0
  163. package/dist/profiler/standalone.d.ts +27 -0
  164. package/dist/profiler/standalone.js +98 -0
  165. package/dist/profiler/steps.d.ts +39 -0
  166. package/dist/profiler/steps.js +74 -0
  167. package/dist/profiler/storage/index.d.ts +87 -0
  168. package/dist/profiler/storage/index.js +76 -0
  169. package/dist/profiler/storage/postgres.d.ts +12 -0
  170. package/dist/profiler/storage/postgres.js +94 -0
  171. package/dist/profiler/storage/redis.d.ts +19 -0
  172. package/dist/profiler/storage/redis.js +148 -0
  173. package/dist/profiler/storage/sqlite.d.ts +2 -0
  174. package/dist/profiler/storage/sqlite.js +85 -0
  175. package/dist/profiler/storage/visits.d.ts +4 -0
  176. package/dist/profiler/storage/visits.js +6 -0
  177. package/dist/profiler/stream-tail.d.ts +26 -0
  178. package/dist/profiler/stream-tail.js +74 -0
  179. package/dist/profiler/timeline.d.ts +279 -0
  180. package/dist/profiler/timeline.js +690 -0
  181. package/dist/profiler/ui/AllocStrip.svelte +146 -0
  182. package/dist/profiler/ui/AllocStrip.svelte.d.ts +17 -0
  183. package/dist/profiler/ui/Awaits.svelte +363 -0
  184. package/dist/profiler/ui/Awaits.svelte.d.ts +16 -0
  185. package/dist/profiler/ui/ByteStrip.svelte +182 -0
  186. package/dist/profiler/ui/ByteStrip.svelte.d.ts +18 -0
  187. package/dist/profiler/ui/Compare.svelte +24 -0
  188. package/dist/profiler/ui/Compare.svelte.d.ts +16 -0
  189. package/dist/profiler/ui/CompareBody.svelte +429 -0
  190. package/dist/profiler/ui/CompareBody.svelte.d.ts +8 -0
  191. package/dist/profiler/ui/ComponentsTable.svelte +265 -0
  192. package/dist/profiler/ui/ComponentsTable.svelte.d.ts +20 -0
  193. package/dist/profiler/ui/Dashboard.svelte +492 -0
  194. package/dist/profiler/ui/Dashboard.svelte.d.ts +31 -0
  195. package/dist/profiler/ui/DataRiver.svelte +206 -0
  196. package/dist/profiler/ui/DataRiver.svelte.d.ts +13 -0
  197. package/dist/profiler/ui/DomTravel.svelte +207 -0
  198. package/dist/profiler/ui/DomTravel.svelte.d.ts +9 -0
  199. package/dist/profiler/ui/DownloadHtml.svelte +52 -0
  200. package/dist/profiler/ui/DownloadHtml.svelte.d.ts +10 -0
  201. package/dist/profiler/ui/Drill.svelte +276 -0
  202. package/dist/profiler/ui/Drill.svelte.d.ts +12 -0
  203. package/dist/profiler/ui/ExportButton.svelte +59 -0
  204. package/dist/profiler/ui/ExportButton.svelte.d.ts +8 -0
  205. package/dist/profiler/ui/Flame.svelte +222 -0
  206. package/dist/profiler/ui/Flame.svelte.d.ts +12 -0
  207. package/dist/profiler/ui/FrameDetails.svelte +308 -0
  208. package/dist/profiler/ui/FrameDetails.svelte.d.ts +16 -0
  209. package/dist/profiler/ui/FunctionsTable.svelte +156 -0
  210. package/dist/profiler/ui/FunctionsTable.svelte.d.ts +17 -0
  211. package/dist/profiler/ui/IslandHeatmap.svelte +151 -0
  212. package/dist/profiler/ui/IslandHeatmap.svelte.d.ts +16 -0
  213. package/dist/profiler/ui/IslandsTable.svelte +448 -0
  214. package/dist/profiler/ui/IslandsTable.svelte.d.ts +16 -0
  215. package/dist/profiler/ui/KeepLocal.svelte +46 -0
  216. package/dist/profiler/ui/KeepLocal.svelte.d.ts +8 -0
  217. package/dist/profiler/ui/Ledger.svelte +196 -0
  218. package/dist/profiler/ui/Ledger.svelte.d.ts +18 -0
  219. package/dist/profiler/ui/LocalCompare.svelte +48 -0
  220. package/dist/profiler/ui/LocalCompare.svelte.d.ts +8 -0
  221. package/dist/profiler/ui/LocalReports.svelte +68 -0
  222. package/dist/profiler/ui/LocalReports.svelte.d.ts +7 -0
  223. package/dist/profiler/ui/Login.svelte +13 -0
  224. package/dist/profiler/ui/Login.svelte.d.ts +12 -0
  225. package/dist/profiler/ui/LoginForm.svelte +42 -0
  226. package/dist/profiler/ui/LoginForm.svelte.d.ts +7 -0
  227. package/dist/profiler/ui/Message.svelte +10 -0
  228. package/dist/profiler/ui/Message.svelte.d.ts +12 -0
  229. package/dist/profiler/ui/OnThisPage.svelte +137 -0
  230. package/dist/profiler/ui/OnThisPage.svelte.d.ts +3 -0
  231. package/dist/profiler/ui/OneClock.svelte +297 -0
  232. package/dist/profiler/ui/OneClock.svelte.d.ts +27 -0
  233. package/dist/profiler/ui/Panel.svelte +10 -0
  234. package/dist/profiler/ui/Panel.svelte.d.ts +16 -0
  235. package/dist/profiler/ui/Paths.svelte +269 -0
  236. package/dist/profiler/ui/Paths.svelte.d.ts +15 -0
  237. package/dist/profiler/ui/Patterns.svelte +364 -0
  238. package/dist/profiler/ui/Patterns.svelte.d.ts +15 -0
  239. package/dist/profiler/ui/PermalinkGate.svelte +143 -0
  240. package/dist/profiler/ui/PermalinkGate.svelte.d.ts +10 -0
  241. package/dist/profiler/ui/RenderSteps.svelte +218 -0
  242. package/dist/profiler/ui/RenderSteps.svelte.d.ts +13 -0
  243. package/dist/profiler/ui/Report.svelte +28 -0
  244. package/dist/profiler/ui/Report.svelte.d.ts +20 -0
  245. package/dist/profiler/ui/ReportBody.svelte +2592 -0
  246. package/dist/profiler/ui/ReportBody.svelte.d.ts +21 -0
  247. package/dist/profiler/ui/Run.svelte +17 -0
  248. package/dist/profiler/ui/Run.svelte.d.ts +14 -0
  249. package/dist/profiler/ui/RunView.svelte +146 -0
  250. package/dist/profiler/ui/RunView.svelte.d.ts +10 -0
  251. package/dist/profiler/ui/ScoreCard.svelte +197 -0
  252. package/dist/profiler/ui/ScoreCard.svelte.d.ts +7 -0
  253. package/dist/profiler/ui/Scrub.svelte +293 -0
  254. package/dist/profiler/ui/Scrub.svelte.d.ts +18 -0
  255. package/dist/profiler/ui/SeedExplainer.svelte +193 -0
  256. package/dist/profiler/ui/SeedExplainer.svelte.d.ts +16 -0
  257. package/dist/profiler/ui/ShareLink.svelte +148 -0
  258. package/dist/profiler/ui/ShareLink.svelte.d.ts +8 -0
  259. package/dist/profiler/ui/Shell.svelte +50 -0
  260. package/dist/profiler/ui/Shell.svelte.d.ts +27 -0
  261. package/dist/profiler/ui/Sidebar.svelte +312 -0
  262. package/dist/profiler/ui/Sidebar.svelte.d.ts +7 -0
  263. package/dist/profiler/ui/Site.svelte +20 -0
  264. package/dist/profiler/ui/Site.svelte.d.ts +23 -0
  265. package/dist/profiler/ui/SiteView.svelte +385 -0
  266. package/dist/profiler/ui/SiteView.svelte.d.ts +27 -0
  267. package/dist/profiler/ui/ThemeSwitch.svelte +78 -0
  268. package/dist/profiler/ui/ThemeSwitch.svelte.d.ts +3 -0
  269. package/dist/profiler/ui/Timeline.svelte +672 -0
  270. package/dist/profiler/ui/Timeline.svelte.d.ts +22 -0
  271. package/dist/profiler/ui/Treemap.svelte +278 -0
  272. package/dist/profiler/ui/Treemap.svelte.d.ts +20 -0
  273. package/dist/profiler/ui/Upload.svelte +19 -0
  274. package/dist/profiler/ui/Upload.svelte.d.ts +10 -0
  275. package/dist/profiler/ui/UploadForm.svelte +59 -0
  276. package/dist/profiler/ui/UploadForm.svelte.d.ts +6 -0
  277. package/dist/profiler/ui/WaitingTable.svelte +55 -0
  278. package/dist/profiler/ui/WaitingTable.svelte.d.ts +14 -0
  279. package/dist/profiler/ui/Waterfall.svelte +371 -0
  280. package/dist/profiler/ui/Waterfall.svelte.d.ts +7 -0
  281. package/dist/profiler/ui/WhatIf.svelte +87 -0
  282. package/dist/profiler/ui/WhatIf.svelte.d.ts +18 -0
  283. package/dist/profiler/ui/fake-progress.svelte.d.ts +15 -0
  284. package/dist/profiler/ui/fake-progress.svelte.js +38 -0
  285. package/dist/profiler/ui/format.d.ts +20 -0
  286. package/dist/profiler/ui/format.js +71 -0
  287. package/dist/profiler/ui/permalink.d.ts +9 -0
  288. package/dist/profiler/ui/permalink.js +108 -0
  289. package/dist/profiler/ui/press.d.ts +5 -0
  290. package/dist/profiler/ui/press.js +19 -0
  291. package/dist/profiler/ui/profiler.css +650 -0
  292. package/dist/profiler/ui/report-data.d.ts +189 -0
  293. package/dist/profiler/ui/report-data.js +393 -0
  294. package/dist/profiler/ui/report-request.d.ts +6 -0
  295. package/dist/profiler/ui/report-request.js +50 -0
  296. package/dist/profiler/ui/row-anchor.svelte.d.ts +12 -0
  297. package/dist/profiler/ui/row-anchor.svelte.js +44 -0
  298. package/dist/profiler/ui/sort.svelte.d.ts +14 -0
  299. package/dist/profiler/ui/sort.svelte.js +37 -0
  300. package/dist/profiler/ui/store.d.ts +45 -0
  301. package/dist/profiler/ui/store.js +92 -0
  302. package/dist/profiler/visit.d.ts +389 -0
  303. package/dist/profiler/visit.js +1002 -0
  304. package/dist/report.d.ts +5 -0
  305. package/dist/report.js +4 -0
  306. package/dist/server.d.ts +11 -0
  307. package/dist/server.js +38 -0
  308. package/dist/vite.d.ts +12 -0
  309. package/dist/vite.js +78 -0
  310. package/package.json +105 -6
@@ -0,0 +1,957 @@
1
+ import { page_ledger } from "./ledger-dom.js";
2
+ import { all_regions, region_name, region_names, region_props_sidecar, region_transitive } from "./regions.js";
3
+ import { leftover_state, owned_leftovers, owned_scroll_listeners, scroll_listeners, unload_listeners } from "./leftovers.js";
4
+ import { profile_for } from "./profile-store.js";
5
+ import { with_source_lines } from "./source-lines.js";
6
+ import { since_load } from "./since-load.js";
7
+ import { analyze_cpu, analyze_page, browser_of, defer_keys, dev_compile_ms, fp_compare, is_trace, prebundled_names, vital_parts } from "@ogygia/core/insights";
8
+ import { PAGE_DEFER_KEY, PAGE_DEFER_REGISTRY_KEY, PAGE_SEED_SELECTOR, beacon_page, hole_request_times, is_headless, tool_fetch } from "@ogygia/core/internal/instruments";
9
+ import { snapshot } from "@ogygia/core/events";
10
+ // src/devtools/page.ts
11
+ /**
12
+ * The Page tab's DOM side: reads the regions off the page and the failures off the bus, hands them
13
+ * with the beacon's visit to the pure {@link ./page-insights.js analyzer}. Also publishes
14
+ * `window.__ogygia_page()` (dev-only, devtools builds) so the planted-problem answer key and e2e
15
+ * read the same report the tab shows without opening the panel.
16
+ */
17
+ const FPS_KEY = "ogygia:devtools:fps";
18
+ /** props text kept per island (a longer one is compared by its fingerprint alone) */
19
+ const FPS_PROPS_CAP = 8e3;
20
+ /** pages kept per tab */
21
+ const FPS_PAGES_CAP = 20;
22
+ let fps_memo = null;
23
+ /** This page load's islands against this tab's last load of the same page (fp-drift.ts): once per
24
+ * load — the first call records this load for the next one. `nav` — the in-app navigation it follows. */
25
+ function fp_drift(nav = 0) {
26
+ const page = location.pathname + location.search;
27
+ const at = `${performance.timeOrigin}:${nav}:${page}`;
28
+ if (fps_memo?.at === at) return fps_memo.drift;
29
+ const now = [];
30
+ for (const r of all_regions()) {
31
+ if (r.kind !== "island" || !r.entry || !r.fp || r.rides) continue;
32
+ const text = region_props_sidecar(r.el);
33
+ now.push({
34
+ entry: r.entry,
35
+ fp: r.fp,
36
+ ...text !== null && text.length <= FPS_PROPS_CAP ? { props: text } : {}
37
+ });
38
+ }
39
+ let store = {};
40
+ try {
41
+ store = JSON.parse(sessionStorage.getItem(FPS_KEY) ?? "{}");
42
+ } catch {
43
+ store = {};
44
+ }
45
+ const drift = fp_compare(store[page] ?? [], now);
46
+ store[page] = now;
47
+ const pages = Object.keys(store);
48
+ for (let i = 0; i < pages.length - FPS_PAGES_CAP; i++) delete store[pages[i]];
49
+ try {
50
+ sessionStorage.setItem(FPS_KEY, JSON.stringify(store));
51
+ } catch {}
52
+ fps_memo = {
53
+ at,
54
+ drift
55
+ };
56
+ return drift;
57
+ }
58
+ function region_facts() {
59
+ const sy = typeof scrollY === "number" ? scrollY : 0;
60
+ const out = [];
61
+ const own = /* @__PURE__ */ new Map();
62
+ for (const row of page_ledger()?.rows ?? []) if (row.unique > 0) own.set(row.entry, row.unique);
63
+ const abs = (e) => {
64
+ try {
65
+ return e ? new URL(e, location.href).href : "";
66
+ } catch {
67
+ return "";
68
+ }
69
+ };
70
+ for (const r of all_regions()) {
71
+ if (!r.fp) continue;
72
+ const box = r.el.getBoundingClientRect();
73
+ const own_bytes = own.size && r.kind === "island" ? own.get(abs(r.entry)) : void 0;
74
+ const hidden = r.kind === "island" && !box.height && !box.width && !has_child_box(r.el);
75
+ const headless = hidden && is_headless(r.el);
76
+ let shows_at = null;
77
+ if (hidden && !headless && EAGER_WAKES.has(r.wake)) {
78
+ const key = `${r.fp}:${document.styleSheets.length}`;
79
+ const seen = media_seen.get(key);
80
+ shows_at = seen !== void 0 ? seen : shown_under_media(r.el);
81
+ if (seen === void 0) media_seen.set(key, shows_at);
82
+ }
83
+ out.push({
84
+ fp: r.fp,
85
+ name: region_name(r.entry),
86
+ kind: r.kind,
87
+ wake: r.wake,
88
+ hydrated: r.hydrated,
89
+ ...own_bytes ? { own_bytes } : {},
90
+ top: Math.round((box.height || box.width ? box.top : first_child_top(r.el)) + sy),
91
+ height: Math.round(box.height),
92
+ ...hidden ? { hidden: true } : {},
93
+ ...headless ? { headless: true } : {},
94
+ ...shows_at ? { shows_at } : {}
95
+ });
96
+ }
97
+ return out;
98
+ }
99
+ const EAGER_WAKES = /* @__PURE__ */ new Set(["load", "idle"]);
100
+ const media_seen = /* @__PURE__ */ new Map();
101
+ function has_child_box(el) {
102
+ for (const c of el.children) {
103
+ const b = c.getBoundingClientRect();
104
+ if (b.height || b.width) return true;
105
+ }
106
+ return false;
107
+ }
108
+ /** The media query under which the page's CSS shows this hidden island (or a child of it): a rule
109
+ * inside an `@media` this screen does not match giving it a `display` other than `none` — or, the
110
+ * other way round, one inside an `@media` this screen matches hiding it (shown where that query is
111
+ * not: `not all and …`). The element read is what is hidden: the island's own child, or the
112
+ * nearest hidden wrapper around it (a sidebar island inside an aside a phone hides). Null when no
113
+ * readable sheet says (a cross-origin sheet, a hide by script, an empty island). */
114
+ function shown_under_media(el) {
115
+ const none = (e) => getComputedStyle(e).display === "none";
116
+ let hider = null;
117
+ for (const c of el.children) if (none(c)) {
118
+ hider = c;
119
+ break;
120
+ }
121
+ for (let up = el; !hider && up && up !== document.body; up = up.parentElement) if (none(up)) hider = up;
122
+ if (!hider) return null;
123
+ const target = hider;
124
+ const matches = (sel) => {
125
+ try {
126
+ return target.matches(sel);
127
+ } catch {
128
+ return false;
129
+ }
130
+ };
131
+ let budget = 2e4;
132
+ let hidden_by = null;
133
+ const walk = (rules, off, on) => {
134
+ for (const rule of rules) {
135
+ if (--budget < 0) return null;
136
+ if (rule instanceof CSSMediaRule) {
137
+ const q = rule.conditionText || rule.media.mediaText;
138
+ const now = typeof matchMedia === "function" && matchMedia(q).matches;
139
+ const hit = walk(rule.cssRules, now ? off : q, now ? q : on);
140
+ if (hit) return hit;
141
+ } else if (rule instanceof CSSStyleRule) {
142
+ const d = rule.style.display;
143
+ if (!d || !off && !on || !matches(rule.selectorText)) continue;
144
+ if (off && d !== "none") return off;
145
+ if (on && !off && d === "none" && !hidden_by) hidden_by = on;
146
+ } else if ("cssRules" in rule && rule.cssRules) {
147
+ const hit = walk(rule.cssRules, off, on);
148
+ if (hit) return hit;
149
+ }
150
+ }
151
+ return null;
152
+ };
153
+ for (const sheet of document.styleSheets) {
154
+ let rules;
155
+ try {
156
+ rules = sheet.cssRules;
157
+ } catch {
158
+ continue;
159
+ }
160
+ const hit = walk(rules, null, null);
161
+ if (hit) return hit;
162
+ }
163
+ const by = hidden_by;
164
+ return by ? by.startsWith("(") ? `not all and ${by}` : `not ${by}` : null;
165
+ }
166
+ /** THE DEV SERVER'S PRE-BUNDLED PACKAGES, read once from its `.vite/deps/_metadata.json` (the folder of
167
+ * a pre-bundled script the page loaded — no guess at the app's base): the names its `optimized` keys
168
+ * belong to. Null until read; never off the dev server, nor on a page that loaded no pre-bundled file. */
169
+ let prebundled = null;
170
+ let prebundled_asked = false;
171
+ function prebundled_packages(script_urls) {
172
+ if (!import.meta.env.DEV || prebundled_asked) return prebundled;
173
+ const one = script_urls.find((u) => u.includes("/node_modules/.vite/deps/"));
174
+ if (!one) return null;
175
+ prebundled_asked = true;
176
+ const dir = one.slice(0, one.indexOf("/node_modules/.vite/deps/") + 25);
177
+ tool_fetch(dir + "_metadata.json").then((r) => r.ok ? r.json() : null).then((m) => {
178
+ prebundled = prebundled_names(Object.keys(m?.optimized ?? {}));
179
+ }).catch(() => {});
180
+ return null;
181
+ }
182
+ /** A FILE THE BROWSER DOWNLOADED AGAIN: its answer's `Vary` header, asked once per URL with a HEAD
183
+ * (a tool's request, out of every measurement). Null until it is in, or when the answer has none. */
184
+ const varies = /* @__PURE__ */ new Map();
185
+ function vary_of(url) {
186
+ if (varies.has(url)) return varies.get(url) ?? null;
187
+ varies.set(url, null);
188
+ tool_fetch(url, {
189
+ method: "HEAD",
190
+ cache: "no-store"
191
+ }).then((r) => varies.set(url, r.headers.get("vary"))).catch(() => {});
192
+ return null;
193
+ }
194
+ function first_child_top(el) {
195
+ for (const c of el.children) {
196
+ const b = c.getBoundingClientRect();
197
+ if (b.height || b.width) return b.top;
198
+ }
199
+ return el.getBoundingClientRect().top;
200
+ }
201
+ /** This page's hydration failures, each island once: after an in-app navigation, the ones since it,
202
+ * and an earlier one only while its region is still on the page and still not awake (a kept
203
+ * island). Every visit to a page with a broken island failed it again: without this, the tab listed
204
+ * one failure per visit, by fingerprint, on pages that do not have the island at all. */
205
+ function failures(since = last_nav()?.t ?? -Infinity) {
206
+ const out = [];
207
+ const ok = /* @__PURE__ */ new Set();
208
+ const seen = /* @__PURE__ */ new Set();
209
+ const ev = snapshot();
210
+ for (let i = ev.length - 1; i >= 0; i--) {
211
+ const e = ev[i];
212
+ if (e.name === "region.hydrate.done" && e.fp) ok.add(e.fp);
213
+ else if (e.name === "region.hydrate.failed" && !(e.fp && ok.has(e.fp))) {
214
+ const key = e.fp ?? e.message;
215
+ if (seen.has(key)) continue;
216
+ if (e.t < since && !(e.fp && document.querySelector(`ogygia-region[data-og-fp="${CSS.escape(e.fp)}"]:not([data-hydrated])`))) continue;
217
+ seen.add(key);
218
+ let start;
219
+ for (let j = i - 1; j >= 0 && e.fp; j--) {
220
+ const s = ev[j];
221
+ if (s.name === "region.hydrate.start" && s.fp === e.fp) {
222
+ start = s.t;
223
+ break;
224
+ }
225
+ }
226
+ const turn = e.turn;
227
+ out.push({
228
+ fp: e.fp,
229
+ message: e.message,
230
+ ...start !== void 0 ? { span: [start, e.t] } : {},
231
+ ...typeof turn === "number" ? { turn } : {}
232
+ });
233
+ }
234
+ }
235
+ return out;
236
+ }
237
+ /** Each island on the page with its dev code breakdown (the dev server's module graph: heaviest
238
+ * modules, barrels still imported whole). Once per component: copies share a module graph. */
239
+ function island_code() {
240
+ const out = [];
241
+ const seen = /* @__PURE__ */ new Set();
242
+ for (const r of all_regions()) {
243
+ if (r.kind !== "island" || !r.entry || seen.has(r.entry)) continue;
244
+ seen.add(r.entry);
245
+ const t = region_transitive(r.entry);
246
+ if (!t?.top?.length && !t?.barrels?.length && !t?.hazards?.length) continue;
247
+ out.push({
248
+ fp: r.fp ?? void 0,
249
+ name: region_name(r.entry),
250
+ bytes: t.bytes,
251
+ top: t.top ?? [],
252
+ barrels: t.barrels ?? [],
253
+ ...t.hazards?.length ? { hazards: t.hazards } : {}
254
+ });
255
+ }
256
+ return out;
257
+ }
258
+ /** How long each hole's fallback showed before its first answer (since the last navigation), and
259
+ * whether it sits on the first screen. */
260
+ function hole_waits(since = last_nav()?.t ?? -Infinity, fcp = 0) {
261
+ const out = [];
262
+ const seen = /* @__PURE__ */ new Set();
263
+ const batched = batch_parts(since);
264
+ for (const e of snapshot()) {
265
+ if (e.name !== "region.server.applied" || e.wait_ms === void 0 || e.t < since) continue;
266
+ const key = e.endpoint ?? e.entry ?? "";
267
+ if (seen.has(key)) continue;
268
+ seen.add(key);
269
+ const el = e.endpoint ? document.querySelector(`ogygia-region[endpoint="${CSS.escape(e.endpoint)}"]`) : null;
270
+ let id = "";
271
+ try {
272
+ id = e.endpoint ? new URL(e.endpoint, location.href).searchParams.get("id") ?? "" : "";
273
+ } catch {
274
+ id = "";
275
+ }
276
+ const rect = el?.getBoundingClientRect();
277
+ const start = Math.max(fcp, since, e.t - e.wait_ms);
278
+ const part = e.endpoint ? batched.get(e.endpoint) : void 0;
279
+ const times = part ? {
280
+ left: part.left,
281
+ first: part.at,
282
+ end: part.at
283
+ } : e.endpoint ? hole_request_times(e.endpoint, e.t) : null;
284
+ out.push({
285
+ name: id && region_names()[id] || (e.entry ? region_name(e.entry) : "a hole"),
286
+ wait_ms: Math.max(0, Math.round(e.t - start)),
287
+ below_fold: !!rect && rect.top + scrollY > innerHeight,
288
+ ...e.endpoint ? { endpoint: e.endpoint } : {},
289
+ shown_at: Math.round(start),
290
+ ...times ? {
291
+ left_at: times.left,
292
+ first_at: times.first,
293
+ end_at: times.end
294
+ } : {},
295
+ ...times && "render" in times && times.render !== void 0 ? { server_ms: times.render } : {},
296
+ ...times && "queue" in times && times.queue !== void 0 ? { server_queue_ms: times.queue } : {},
297
+ ...part ? { batch_size: part.size } : {}
298
+ });
299
+ }
300
+ return out;
301
+ }
302
+ /** Each hole a batch request carried (since `since`): endpoint → the batch's start (the browser's
303
+ * timing of the POST, else the send), its own part's landing, and the batch's size. */
304
+ function batch_parts(since) {
305
+ const out = /* @__PURE__ */ new Map();
306
+ const sent = /* @__PURE__ */ new Map();
307
+ for (const e of snapshot()) {
308
+ if (e.t < since) continue;
309
+ if (e.name === "region.batch.sent") sent.set(e.batch, {
310
+ t: e.t,
311
+ size: e.endpoints.length
312
+ });
313
+ else if (e.name === "region.batch.part") {
314
+ const b = sent.get(e.batch);
315
+ if (!b) continue;
316
+ b.left ??= batch_request_start(e.endpoint, b.t);
317
+ out.set(e.endpoint, {
318
+ left: Math.round(b.left),
319
+ at: Math.round(e.t),
320
+ size: b.size
321
+ });
322
+ }
323
+ }
324
+ return out;
325
+ }
326
+ /** When the browser sent the batch POST that left at about `sent` (its Resource Timing), or `sent`. */
327
+ function batch_request_start(endpoint, sent) {
328
+ try {
329
+ const u = new URL(endpoint, location.href);
330
+ let best;
331
+ for (const r of performance.getEntriesByName(u.origin + u.pathname, "resource")) if (r.startTime >= sent - 5 && (!best || r.startTime < best.startTime)) best = r;
332
+ if (best) return best.requestStart || best.startTime;
333
+ } catch {}
334
+ return sent;
335
+ }
336
+ /** Batch requests that carried fewer holes than they were sent for (since `since`). */
337
+ function hole_batches(since = last_nav()?.t ?? -Infinity) {
338
+ const out = [];
339
+ const sent = /* @__PURE__ */ new Map();
340
+ for (const e of snapshot()) {
341
+ if (e.t < since) continue;
342
+ if (e.name === "region.batch.sent") sent.set(e.batch, e.endpoints);
343
+ else if (e.name === "region.batch.done" && e.delivered < e.sent) {
344
+ const names = (sent.get(e.batch) ?? []).map((ep) => {
345
+ let id = "";
346
+ try {
347
+ id = new URL(ep, location.href).searchParams.get("id") ?? "";
348
+ } catch {
349
+ id = "";
350
+ }
351
+ return id && region_names()[id] || "a hole";
352
+ });
353
+ out.push({
354
+ sent: e.sent,
355
+ delivered: e.delivered,
356
+ status: e.status,
357
+ ...e.refused ? { refused: e.refused } : {},
358
+ ...e.final_url ? { final_url: e.final_url } : {},
359
+ names
360
+ });
361
+ }
362
+ }
363
+ return out;
364
+ }
365
+ /** Holes whose answer never came: the last failure of each (no retry after it), unless an answer
366
+ * arrived since. In a build the runtime says nothing and the page just keeps the fallback. */
367
+ function hole_failures(since = last_nav()?.t ?? -Infinity) {
368
+ const out = [];
369
+ const answered = /* @__PURE__ */ new Set();
370
+ const seen = /* @__PURE__ */ new Set();
371
+ const ev = snapshot();
372
+ for (let i = ev.length - 1; i >= 0; i--) {
373
+ const e = ev[i];
374
+ if (e.name !== "region.server.applied" && e.name !== "region.server.failed") continue;
375
+ const key = e.endpoint ?? e.entry ?? "";
376
+ if (e.name === "region.server.applied") answered.add(key);
377
+ else if (e.final && !answered.has(key) && !seen.has(key) && e.t >= since) {
378
+ seen.add(key);
379
+ const el = e.endpoint ? document.querySelector(`ogygia-region[endpoint="${CSS.escape(e.endpoint)}"]`) : e.entry ? document.querySelector(`ogygia-region[entry="${CSS.escape(e.entry)}"]`) : null;
380
+ let id = "";
381
+ try {
382
+ id = e.endpoint ? new URL(e.endpoint, location.href).searchParams.get("id") ?? "" : "";
383
+ } catch {
384
+ id = "";
385
+ }
386
+ out.push({
387
+ fp: el?.getAttribute("data-og-fp") ?? void 0,
388
+ name: id && region_names()[id] || (e.entry ? region_name(e.entry) : "a hole"),
389
+ endpoint: e.endpoint,
390
+ reason: e.reason,
391
+ ...e.final_url ? { final_url: e.final_url } : {},
392
+ ...e.message ? { message: e.message } : {},
393
+ attempts: e.attempt
394
+ });
395
+ }
396
+ }
397
+ return out;
398
+ }
399
+ const cpu_memo = /* @__PURE__ */ new WeakMap();
400
+ /** The CPU summary of a kept trace, against this page's island windows. */
401
+ function cpu_of(page, trace) {
402
+ if (!is_trace(trace)) return null;
403
+ const n = `${page.islands.length}|${page.longtasks.length}`;
404
+ const hit = cpu_memo.get(trace);
405
+ if (hit && hit.n === n) return hit.out;
406
+ const windows = page.islands.map((i) => ({
407
+ fp: i.fp,
408
+ from: i.turn ?? i.loaded,
409
+ to: Math.max(i.done, i.fx ?? 0)
410
+ }));
411
+ const out = analyze_cpu(trace, windows, page.longtasks, location.href);
412
+ cpu_memo.set(trace, {
413
+ n,
414
+ out
415
+ });
416
+ return out;
417
+ }
418
+ /** The last in-app navigation (the router's body swap): where to, and when it started. */
419
+ /** This document's streamed promises, each by its `page.data` key (the seed) and when its resolve
420
+ * arrived (the inline bootstrap's stamp): what held the document open. Null: nothing streamed. */
421
+ function held_open() {
422
+ const t = globalThis[PAGE_DEFER_REGISTRY_KEY]?.t;
423
+ if (!t) return null;
424
+ const names = defer_keys(document.querySelector(PAGE_SEED_SELECTOR)?.textContent ?? "", PAGE_DEFER_KEY);
425
+ const keys = Object.entries(t).map(([id, at]) => ({
426
+ key: names.get(Number(id)) ?? null,
427
+ at: Math.round(at)
428
+ }));
429
+ return keys.length ? {
430
+ side: "browser",
431
+ keys: keys.sort((a, b) => a.at - b.at)
432
+ } : null;
433
+ }
434
+ /** The router's last navigation that SWAPPED the page (its start time, its address). One that started
435
+ * and never finished — it fell back to a full load the browser did not make (a test holding document
436
+ * loads back), or a later one overtook it — changed nothing: this is still the page it was. */
437
+ function last_nav() {
438
+ const ev = snapshot();
439
+ let own = null;
440
+ let finished = null;
441
+ for (let i = ev.length - 1; i >= 0; i--) {
442
+ const e = ev[i];
443
+ if (e.realm !== "client") continue;
444
+ if (e.name === "nav.finish" && finished === null) finished = e.to;
445
+ else if (e.name === "nav.start" && finished !== null) {
446
+ own = {
447
+ to: finished,
448
+ t: e.t
449
+ };
450
+ break;
451
+ }
452
+ }
453
+ const path = (to) => to.split("?")[0];
454
+ const starts = ev.filter((e) => e.name === "nav.start" && e.realm === "client").map((e) => ({
455
+ t: e.t,
456
+ path: path(e.to)
457
+ }));
458
+ const moved = url_moves.filter((m) => !starts.some((s) => s.t <= m.t && s.path === path(m.to))).at(-1);
459
+ if (moved && (!own || moved.t > own.t)) return moved;
460
+ return own;
461
+ }
462
+ /** each change of the page's path, as the dock saw it (history.pushState, a back or forward) */
463
+ const url_moves = [];
464
+ let watching_url = false;
465
+ function watch_url() {
466
+ if (watching_url || typeof history === "undefined") return;
467
+ watching_url = true;
468
+ let path = location.pathname;
469
+ const seen = () => {
470
+ if (location.pathname === path) return;
471
+ path = location.pathname;
472
+ url_moves.push({
473
+ to: location.pathname + location.search,
474
+ t: performance.now()
475
+ });
476
+ if (url_moves.length > 20) url_moves.shift();
477
+ };
478
+ const push = history.pushState;
479
+ history.pushState = function(...args) {
480
+ push.apply(this, args);
481
+ seen();
482
+ };
483
+ addEventListener("popstate", seen);
484
+ }
485
+ /**
486
+ * After an in-app navigation the beacon's visit keeps the whole document's story on one clock: the
487
+ * first page's islands, shifts and long tasks, then this page's. The tab is about THIS page, so its
488
+ * events are cut to those since the navigation and shifted to start there; the paints and the
489
+ * vitals stay the document's (a soft navigation paints nothing new the browser reports).
490
+ */
491
+ function since_nav(page, t) {
492
+ const shift = (xs) => xs.filter((x) => x.t >= t).map((x) => ({
493
+ ...x,
494
+ t: x.t - t
495
+ }));
496
+ const visit = page.visit;
497
+ const interaction = visit?.interaction && visit.interaction.t >= t ? {
498
+ ...visit.interaction,
499
+ t: visit.interaction.t - t
500
+ } : void 0;
501
+ return {
502
+ vitals: {},
503
+ visit: visit ? {
504
+ ...visit,
505
+ paints: {},
506
+ nav: {},
507
+ resources: [],
508
+ preload_misses: [],
509
+ preloads_unused: [],
510
+ uncompressed: [],
511
+ font_faces: [],
512
+ refetched: [],
513
+ scripts: [],
514
+ warnings: (visit.warnings ?? []).filter((w) => (w.t ?? 0) >= t),
515
+ interaction,
516
+ forced_layout: (visit.forced_layout ?? []).filter((f) => f.start >= t).map((f) => ({
517
+ ...f,
518
+ start: f.start - t,
519
+ end: f.end - t
520
+ })),
521
+ scroll_jank: (visit.scroll_jank ?? []).filter((j) => j.start >= t).map((j) => ({
522
+ ...j,
523
+ start: j.start - t
524
+ })),
525
+ navs: (page.visit?.navs ?? []).filter((n) => n.t >= t - 1).map((n) => ({
526
+ ...n,
527
+ t: n.t - t,
528
+ fetched: n.fetched - t,
529
+ styled: n.styled - t,
530
+ swapped: n.swapped - t
531
+ }))
532
+ } : null,
533
+ islands: page.islands.filter((i) => i.t0 >= t).map((i) => ({
534
+ ...i,
535
+ t0: i.t0 - t,
536
+ loaded: i.loaded - t,
537
+ done: i.done - t,
538
+ ...i.turn !== void 0 ? { turn: i.turn - t } : {},
539
+ ...i.fx !== void 0 ? { fx: i.fx - t } : {}
540
+ })),
541
+ firsts: shift(page.firsts),
542
+ shifts: shift(page.shifts),
543
+ longtasks: shift(page.longtasks),
544
+ ...page.longtasks_from ? { longtasks_from: page.longtasks_from } : {},
545
+ ...page.shifts_from ? { shifts_from: page.shifts_from } : {},
546
+ snapshots: page.snapshots
547
+ };
548
+ }
549
+ /** Awake islands whose children slot (`<ogygia-slot>`) holds nothing: children the server never
550
+ * rendered (the island adopts the server's HTML; it never renders them itself). */
551
+ function empty_slots() {
552
+ const out = [];
553
+ for (const slot of document.querySelectorAll("ogygia-slot")) {
554
+ let content = false;
555
+ for (const n of slot.childNodes) if (n.nodeType === 1 || n.nodeType === 3 && (n.textContent ?? "").trim()) {
556
+ content = true;
557
+ break;
558
+ }
559
+ if (content) continue;
560
+ const fp = slot.closest("ogygia-region[data-hydrated]")?.getAttribute("data-og-fp");
561
+ if (fp) out.push(fp);
562
+ }
563
+ return out;
564
+ }
565
+ /** Other origins' URLs an element of the live document names (script src, link href, in the head
566
+ * and body alike): the third-party scripts no element names came by an import or a fetch. */
567
+ function other_origin_refs() {
568
+ const out = /* @__PURE__ */ new Set();
569
+ for (const el of document.querySelectorAll("script[src],link[href]")) {
570
+ const url = el.src || el.href;
571
+ if (url && !url.startsWith(location.origin + "/")) out.add(url);
572
+ }
573
+ return [...out];
574
+ }
575
+ /**
576
+ * What THIS browser cannot measure (Safari has no layout-shift, long-task or interaction timing;
577
+ * only Chromium has the JS sampler and long animation frames). A finding that needs one of them
578
+ * cannot appear here — its absence is not a clean bill, and the tab says so.
579
+ */
580
+ function unmeasured(cpu_off) {
581
+ let types = [];
582
+ try {
583
+ types = PerformanceObserver.supportedEntryTypes ?? [];
584
+ } catch {
585
+ types = [];
586
+ }
587
+ const out = [];
588
+ if (!types.includes("layout-shift")) out.push("layout shifts (CLS; a hydration that moved the layout is estimated from the island's box)");
589
+ if (!types.includes("largest-contentful-paint")) out.push("the largest paint (LCP)");
590
+ if (!types.includes("event")) out.push("interaction timing (INP, slow interactions)");
591
+ if (!types.includes("longtask")) out.push("long tasks (the stalls are estimated from frames drawn late)");
592
+ if (!types.includes("long-animation-frame")) out.push("which script held a frame");
593
+ if (cpu_off === "unsupported") out.push("the main-thread CPU (the JS sampler)");
594
+ return out;
595
+ }
596
+ /** The slowest interaction's trace (taken after the load, kept in the page), read over its two
597
+ * spans — the same cut the profiler makes on its copy. Memoized per trace. */
598
+ const icpu_memo = /* @__PURE__ */ new WeakMap();
599
+ function interaction_cpu_of(page) {
600
+ let kept = page.cpu.traces.find((t) => t.label === "interaction" && t.spans);
601
+ if (!kept) {
602
+ const i = page.visit?.interaction;
603
+ const load = page.cpu.traces.find((t) => t.label === "page load");
604
+ if (!i || i.ms < 200 || !load || i.t < load.from || i.t + i.ms > load.to) return null;
605
+ const at = i.t + i.delay;
606
+ kept = {
607
+ ...load,
608
+ spans: {
609
+ t: i.t,
610
+ wait: [i.t, at],
611
+ handler: [at, at + i.processing]
612
+ }
613
+ };
614
+ }
615
+ if (!kept.spans || !is_trace(kept.trace)) return null;
616
+ const hit = icpu_memo.get(kept.trace);
617
+ if (hit && hit.t === kept.spans.t) return interaction_lines(hit);
618
+ const { t, wait, handler } = kept.spans;
619
+ const MIN = 20;
620
+ const windows = [...wait[1] - wait[0] >= MIN ? [{
621
+ fp: "wait",
622
+ from: wait[0],
623
+ to: wait[1]
624
+ }] : [], ...handler[1] - handler[0] >= MIN ? [{
625
+ fp: "handler",
626
+ from: handler[0],
627
+ to: handler[1]
628
+ }] : []];
629
+ const s = analyze_cpu(kept.trace, windows, [], location.href);
630
+ const out = {
631
+ t,
632
+ wait: s.islands.wait ?? null,
633
+ handler: s.islands.handler ?? null
634
+ };
635
+ icpu_memo.set(kept.trace, out);
636
+ return interaction_lines(out);
637
+ }
638
+ /** the interaction's functions with the source's lines (a served line is not the source's): read
639
+ * from the files' inline maps; `mapped` once every app function has its line */
640
+ function interaction_lines(i) {
641
+ const w = i.wait ? with_source_lines(i.wait.top) : null;
642
+ const h = i.handler ? with_source_lines(i.handler.top) : null;
643
+ return {
644
+ ...i,
645
+ mapped: (w?.mapped ?? true) && (h?.mapped ?? true),
646
+ wait: i.wait && w ? {
647
+ ...i.wait,
648
+ top: w.fns
649
+ } : null,
650
+ handler: i.handler && h ? {
651
+ ...i.handler,
652
+ top: h.fns
653
+ } : null
654
+ };
655
+ }
656
+ /** the load's CPU with the source's lines, in every list the tabs and findings quote */
657
+ function summary_lines(s) {
658
+ if (!s) return s;
659
+ const islands = {};
660
+ for (const [fp, v] of Object.entries(s.islands)) islands[fp] = {
661
+ ...v,
662
+ top: with_source_lines(v.top).fns
663
+ };
664
+ return {
665
+ ...s,
666
+ fns: with_source_lines(s.fns).fns,
667
+ islands,
668
+ outside: {
669
+ ...s.outside,
670
+ top: with_source_lines(s.outside.top).fns
671
+ }
672
+ };
673
+ }
674
+ /** THE PROFILER'S LAST RUN OF A PAGE, in brief: from the dock's profile store (the Profiler tab's
675
+ * runs, shared data — never the report re-fetched), for the Page tab's server-side sentences. */
676
+ function server_brief(path) {
677
+ const p = profile_for(path);
678
+ if (!p) return void 0;
679
+ const TIME_CODES = [
680
+ "mostly-waiting",
681
+ "phases",
682
+ "top-component",
683
+ "top-cpu"
684
+ ];
685
+ let said;
686
+ for (const code of TIME_CODES) {
687
+ said = p.findings.find((f) => f.code === code)?.message;
688
+ if (said) break;
689
+ }
690
+ const trim = (m) => {
691
+ const s = m.length > 200 ? m.slice(0, 200) + "…" : m;
692
+ return s.endsWith(".") ? s.slice(0, -1) : s;
693
+ };
694
+ const comp = [...p.components].sort((a, b) => b.self_ms - a.self_ms)[0];
695
+ const no_store = p.cache_control && p.cache_control.toLowerCase().split(",").some((d) => d.trim() === "no-store") ? p.cache_control : void 0;
696
+ return {
697
+ ago_min: Math.max(0, (Date.now() - p.at) / 6e4),
698
+ render_ms: p.render_ms,
699
+ top: said ? trim(said) : comp ? `${comp.name} (${Math.round(comp.self_ms)} ms of its own)` : null,
700
+ calls: p.network?.count ?? 0,
701
+ calls_ms: p.network?.total_ms ?? 0,
702
+ ...no_store ? { no_store } : {},
703
+ ...p.prerendered ? { prerendered: p.prerendered } : {}
704
+ };
705
+ }
706
+ /** The island a script file belongs to, when it is one's own file (by its location or identity). */
707
+ /** When this document came from Back or Forward and was NOT restored from the back/forward cache,
708
+ * the browser's reasons (`notRestoredReasons`, where it gives them), the first 6. */
709
+ function not_restored_reasons() {
710
+ try {
711
+ const n = performance.getEntriesByType("navigation")[0];
712
+ if (!n || n.type !== "back_forward") return [];
713
+ return (n.notRestoredReasons?.reasons ?? []).map((r) => String(r?.reason ?? "")).filter(Boolean).slice(0, 6);
714
+ } catch {
715
+ return [];
716
+ }
717
+ }
718
+ function island_of_file(url) {
719
+ const base = (u) => {
720
+ const q = u.indexOf("?");
721
+ const p = q === -1 ? u : u.slice(0, q);
722
+ return p.slice(p.lastIndexOf("/") + 1);
723
+ };
724
+ const file = base(url);
725
+ if (!file) return {};
726
+ for (const r of all_regions()) {
727
+ const src = r.el.getAttribute("src");
728
+ if (r.entry && (src && base(src) === file || base(r.entry) === file)) return { island: region_name(r.entry) };
729
+ }
730
+ if (file.endsWith(".svelte")) {
731
+ const stem = file.slice(0, -7);
732
+ for (const r of all_regions()) if (r.kind === "island" && r.entry && region_name(r.entry) === stem) return { island: stem };
733
+ }
734
+ return {};
735
+ }
736
+ function read_page() {
737
+ const page = beacon_page();
738
+ if (!page) return null;
739
+ const regions = region_facts();
740
+ const nav = last_nav();
741
+ const load = page.cpu.traces.find((t) => t.label === "page load");
742
+ const cpu = load && !nav ? summary_lines(cpu_of(page, load.trace)) : null;
743
+ const base = nav ? since_nav(page, nav.t) : page;
744
+ const in_page = other_origin_refs();
745
+ const preload_misses = base.visit?.preload_misses?.map((m) => {
746
+ const vary = m.type === "font" ? vary_of(m.url) : null;
747
+ return vary ? {
748
+ ...m,
749
+ vary
750
+ } : m;
751
+ });
752
+ const fallbacks = base.visit?.entry_fallbacks?.map((f) => ({
753
+ ...f,
754
+ name: region_name(f.entry)
755
+ }));
756
+ const refetched = base.visit?.refetched?.map((f) => f.entry ? {
757
+ ...f,
758
+ name: region_name(f.entry)
759
+ } : f);
760
+ const interaction = base.visit?.interaction ? {
761
+ ...base.visit.interaction,
762
+ scripts: base.visit.interaction.scripts?.map((s) => ({
763
+ ...s,
764
+ ...island_of_file(s.url)
765
+ }))
766
+ } : void 0;
767
+ const scroll_jank = base.visit?.scroll_jank?.map((j) => ({
768
+ ...j,
769
+ scripts: j.scripts.map((s) => ({
770
+ ...s,
771
+ ...island_of_file(s.url)
772
+ }))
773
+ }));
774
+ const with_visit = base.visit ? {
775
+ ...base,
776
+ visit: {
777
+ ...base.visit,
778
+ origin: location.origin,
779
+ in_page,
780
+ ...preload_misses ? { preload_misses } : {},
781
+ ...fallbacks ? { entry_fallbacks: fallbacks } : {},
782
+ ...refetched ? { refetched } : {},
783
+ ...interaction ? { interaction } : {},
784
+ ...scroll_jank ? { scroll_jank } : {}
785
+ }
786
+ } : base;
787
+ const holes = hole_failures();
788
+ const code = island_code();
789
+ const waits = hole_waits(nav?.t ?? -Infinity, nav ? 0 : with_visit.visit?.paints?.fcp ?? 0);
790
+ const batches = hole_batches(nav?.t ?? -Infinity);
791
+ const restores = (window.__og_restore_log ?? []).filter((e) => e.t >= (nav?.t ?? -Infinity));
792
+ const drift = fp_drift(nav ? Math.round(nav.t) : 0).map((d) => ({
793
+ name: region_name(d.entry),
794
+ fp: d.fp_now,
795
+ ...d.path ? { path: d.path } : {},
796
+ ...d.was !== void 0 ? {
797
+ was: d.was,
798
+ now: d.now
799
+ } : {}
800
+ }));
801
+ const icpu = interaction_cpu_of(page);
802
+ const { regs, islands: seen } = leftover_state();
803
+ const leftovers = owned_leftovers(regs, seen, region_name, performance.now()).map(({ entry: _entry, ...l }) => l);
804
+ const scroll_blockers = owned_scroll_listeners(scroll_listeners(), seen, region_name);
805
+ const unload_owners = [...new Set(owned_scroll_listeners(unload_listeners(), seen, region_name).map((o) => o.owner))];
806
+ const not_restored = nav ? [] : not_restored_reasons();
807
+ const bfcache = unload_owners.length || not_restored.length ? {
808
+ unload: unload_owners,
809
+ ...not_restored.length ? { not_restored } : {}
810
+ } : null;
811
+ const server_profiles = {};
812
+ for (const n of with_visit.visit?.navs ?? []) {
813
+ const path = n.to.split("?")[0];
814
+ const b = server_brief(path);
815
+ if (b) server_profiles[path] = b;
816
+ }
817
+ const server_profile = server_brief(nav ? nav.to.split("?")[0] : location.pathname);
818
+ const held = nav ? null : held_open();
819
+ const input = {
820
+ ...with_visit,
821
+ ...held ? { held_open: held } : {},
822
+ empty_slots: empty_slots(),
823
+ ...holes.length ? { hole_failures: holes } : {},
824
+ ...code.length ? { island_code: code } : {},
825
+ ...waits.length ? { hole_waits: waits } : {},
826
+ ...batches.length ? { hole_batches: batches } : {},
827
+ ...restores.length ? { restore_events: restores } : {},
828
+ ...drift.length ? { fp_drift: drift } : {},
829
+ ...leftovers.length ? { leftovers } : {},
830
+ ...scroll_blockers.length ? { scroll_blockers } : {},
831
+ ...bfcache ? { bfcache } : {},
832
+ ...icpu ? { interaction_cpu: icpu } : {},
833
+ server_profiles,
834
+ ...server_profile ? { server_profile } : {},
835
+ ...import.meta.env.DEV ? { dev: true } : {},
836
+ ...typeof navigator !== "undefined" && browser_of(navigator.userAgent) ? { browser: browser_of(navigator.userAgent) } : {},
837
+ ...(() => {
838
+ const at = globalThis[Symbol.for("ogygia.dock-open-at")];
839
+ return typeof at === "number" && !last_nav() ? { dock_open_at: at } : {};
840
+ })(),
841
+ ...(() => {
842
+ const pre = import.meta.env.DEV ? prebundled_packages((with_visit.visit?.resources ?? []).filter((r) => r.type === "script").map((r) => r.url)) : null;
843
+ return pre?.length ? { prebundled: pre } : {};
844
+ })()
845
+ };
846
+ const view = {
847
+ page,
848
+ regions,
849
+ cpu,
850
+ nav,
851
+ unmeasured: unmeasured(page.cpu.off),
852
+ since: null,
853
+ ...waits.length ? { holes: waits } : {},
854
+ ...holes.length ? { holes_failed: holes } : {},
855
+ report: analyze_page(input, regions, failures(), nav ? performance.now() - nav.t : performance.now(), cpu)
856
+ };
857
+ if (nav) {
858
+ const woke = new Set(input.islands.map((i) => i.fp));
859
+ const seen = /* @__PURE__ */ new Set();
860
+ view.kept = regions.filter((r) => r.kind === "island" && r.hydrated && !woke.has(r.fp) && !seen.has(r.fp) && !!seen.add(r.fp)).map((r) => ({
861
+ fp: r.fp,
862
+ name: r.name
863
+ }));
864
+ }
865
+ if (!nav) {
866
+ const parts = {};
867
+ for (const k of [
868
+ "ttfb",
869
+ "fcp",
870
+ "lcp",
871
+ "inp"
872
+ ]) {
873
+ const p = vital_parts(input, k);
874
+ if (p) parts[k] = p;
875
+ }
876
+ view.parts = parts;
877
+ }
878
+ const prev = nav ? null : previous_load();
879
+ if (prev) view.since = since_load(prev, snapshot_of(view));
880
+ return view;
881
+ }
882
+ function install_page_hook() {
883
+ if (typeof window !== "undefined" && !window.__ogygia_page) window.__ogygia_page = read_page;
884
+ if (typeof window !== "undefined") watch_url();
885
+ if (typeof window !== "undefined" && !window.__ogygia_styles) window.__ogygia_styles = async () => {
886
+ const { read_styles, scan_unscoped, styles_findings } = await import("./styles.js");
887
+ const report = read_styles(document, { match: true });
888
+ report.unscoped = await scan_unscoped(document);
889
+ return {
890
+ report,
891
+ findings: styles_findings(report)
892
+ };
893
+ };
894
+ if (typeof window !== "undefined" && !window.__ogygia_cache) window.__ogygia_cache = async () => {
895
+ const { probe_cache, cache_findings } = await import("./cache-headers.js");
896
+ const probes = await probe_cache(document);
897
+ return {
898
+ probes,
899
+ findings: cache_findings(probes)
900
+ };
901
+ };
902
+ if (typeof window !== "undefined" && !hooked_hide) {
903
+ hooked_hide = true;
904
+ addEventListener("pagehide", () => {
905
+ try {
906
+ const v = read_page();
907
+ if (v && !v.nav) sessionStorage.setItem(LOAD_KEY + location.pathname, JSON.stringify(snapshot_of(v)));
908
+ } catch {}
909
+ });
910
+ }
911
+ }
912
+ const LOAD_KEY = "ogygia:devtools:load:";
913
+ let hooked_hide = false;
914
+ /** the previous load's picture of THIS page, read once per document (before this load overwrites it) */
915
+ let prev_load;
916
+ function previous_load() {
917
+ if (prev_load !== void 0) return prev_load;
918
+ try {
919
+ const raw = sessionStorage.getItem(LOAD_KEY + location.pathname);
920
+ prev_load = raw ? JSON.parse(raw) : null;
921
+ } catch {
922
+ prev_load = null;
923
+ }
924
+ return prev_load;
925
+ }
926
+ function snapshot_of(v) {
927
+ const name = new Map(v.regions.map((r) => [r.fp, r.name]));
928
+ return {
929
+ path: location.pathname,
930
+ at: Date.now(),
931
+ findings: v.report.findings.map((f) => ({
932
+ code: f.code,
933
+ names: f.fps.map((fp) => name.get(fp) ?? fp),
934
+ ...f.dev_compile ? { dev: true } : {}
935
+ })),
936
+ islands: v.report.rows.map((r) => {
937
+ const compile_ms = Math.round(dev_compile_ms({
938
+ dev: import.meta.env.DEV ? true : void 0,
939
+ visit: v.page.visit
940
+ }, r));
941
+ return {
942
+ name: r.name,
943
+ load_ms: r.load_ms,
944
+ hydrate_ms: r.hydrate_ms,
945
+ ...compile_ms ? { compile_ms } : {}
946
+ };
947
+ }),
948
+ vitals: v.report.vitals.map((x) => ({
949
+ key: x.key,
950
+ value: x.value
951
+ })),
952
+ ...v.parts ? { parts: v.parts } : {},
953
+ ...v.report.findings.some((f) => f.dev_compile && (f.code === "slow-ttfb" || f.code === "vital-ttfb")) ? { page_compiled: true } : {},
954
+ ...v.report.findings.some((f) => f.dev_compile && (f.code === "shift-cause" || f.code === "vital-cls")) ? { cls_dev: true } : {}
955
+ };
956
+ }
957
+ export { cpu_of, failures, held_open, hole_batches, hole_failures, hole_waits, install_page_hook, last_nav, read_page, region_facts, since_nav, summary_lines, unmeasured };