@ogygia/devtools 0.0.0-stage → 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 +25 -2
  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 +103 -3
@@ -0,0 +1,1002 @@
1
+ import { HOLE_SEG_LABEL, hole_segments } from "@ogygia/core/insights";
2
+ // src/profiler/visit.ts
3
+ /**
4
+ * ONE VISIT, AS THE BROWSER SAW IT — the beacon's picture of a page load: navigation timing, every
5
+ * resource with its bytes and blocking status, the paints, the long tasks, each island's wake with
6
+ * its module load, the first interaction per island, the layout shifts with the island they hit.
7
+ * Parsed and bounded here (the beacon is an unauthenticated-ish POST from the profiler's own user;
8
+ * the server keeps a few per page). `one_clock` lays a visit out against the server's render on
9
+ * one clock — the report's first picture.
10
+ */
11
+ const MAX_RESOURCES = 200;
12
+ const MAX_LONGTASKS = 100;
13
+ const MAX_ISLANDS = 400;
14
+ const MAX_SHIFTS = 60;
15
+ const MAX_MARKS = 200;
16
+ const MAX_MS = 6e5;
17
+ const num = (v, max = MAX_MS) => {
18
+ const n = Number(v);
19
+ return Number.isFinite(n) && n >= 0 && n <= max ? Math.round(n * 100) / 100 : void 0;
20
+ };
21
+ const str = (v, max) => typeof v === "string" && v.length ? v.slice(0, max) : void 0;
22
+ const rect = (v) => {
23
+ if (!Array.isArray(v) || v.length !== 4) return void 0;
24
+ const r = v.map((n) => Number(n));
25
+ return r.every((n) => Number.isFinite(n)) ? r.map((n) => Math.round(n)) : void 0;
26
+ };
27
+ const FP_RE = /^[0-9a-f]{8,32}$/;
28
+ const KNOWN_TYPES = /* @__PURE__ */ new Set([
29
+ "layout-shift",
30
+ "longtask",
31
+ "event",
32
+ "largest-contentful-paint",
33
+ "long-animation-frame"
34
+ ]);
35
+ /** A beacon body's `visit` → a bounded Visit, or null when it is not one. */
36
+ function parse_visit(page, raw) {
37
+ if (typeof page !== "string" || !page.startsWith("/") || page.length > 500) return null;
38
+ const v = raw;
39
+ if (!v || typeof v !== "object") return null;
40
+ const nav_raw = v.nav;
41
+ if (!nav_raw || typeof nav_raw !== "object") return null;
42
+ const start_hidden = nav_raw.start_hidden === true;
43
+ const res_start = num(nav_raw.res_start) ?? (start_hidden ? num(nav_raw.res_end) ?? 0 : void 0);
44
+ if (res_start === void 0) return null;
45
+ const nav = {
46
+ req_start: start_hidden ? res_start : num(nav_raw.req_start) ?? 0,
47
+ res_start,
48
+ res_end: Math.max(res_start, num(nav_raw.res_end) ?? res_start)
49
+ };
50
+ if (start_hidden) nav.start_hidden = true;
51
+ for (const k of [
52
+ "dom_interactive",
53
+ "dcl",
54
+ "load"
55
+ ]) {
56
+ const n = num(nav_raw[k]);
57
+ if (n !== void 0) nav[k] = n;
58
+ }
59
+ for (const k of ["transfer", "size"]) {
60
+ const n = num(nav_raw[k], 1e9);
61
+ if (n !== void 0) nav[k] = n;
62
+ }
63
+ const proto = str(nav_raw.protocol, 16);
64
+ if (proto) nav.protocol = proto;
65
+ if (nav_raw.raw === true) nav.raw = true;
66
+ const ph = nav_raw.phases;
67
+ if (ph && typeof ph === "object") {
68
+ const phases = {};
69
+ for (const k of [
70
+ "redirect",
71
+ "worker",
72
+ "dns",
73
+ "connect",
74
+ "tls",
75
+ "wait"
76
+ ]) {
77
+ const n = num(ph[k]);
78
+ if (n !== void 0 && n > 0) phases[k] = n;
79
+ }
80
+ if (Object.keys(phases).length) nav.phases = phases;
81
+ }
82
+ const st = [];
83
+ for (const s of (Array.isArray(nav_raw.server_timing) ? nav_raw.server_timing : []).slice(0, 8)) {
84
+ const name = str(s?.name, 40);
85
+ const ms = num(s?.ms);
86
+ if (!name || ms === void 0) continue;
87
+ const desc = str(s.desc, 80);
88
+ st.push({
89
+ name,
90
+ ms,
91
+ ...desc ? { desc } : {}
92
+ });
93
+ }
94
+ if (st.length) nav.server_timing = st;
95
+ const paints_raw = v.paints ?? {};
96
+ const paints = {};
97
+ for (const k of ["fcp", "lcp"]) {
98
+ const n = num(paints_raw[k]);
99
+ if (n !== void 0) paints[k] = n;
100
+ }
101
+ if (typeof paints_raw.lcp_fp === "string" && FP_RE.test(paints_raw.lcp_fp)) paints.lcp_fp = paints_raw.lcp_fp;
102
+ const lcp_url = str(paints_raw.lcp_url, 500);
103
+ if (lcp_url) paints.lcp_url = lcp_url;
104
+ const lcp_tag = str(paints_raw.lcp_tag, 40);
105
+ if (lcp_tag) paints.lcp_tag = lcp_tag;
106
+ if (paints_raw.lcp_replaced === true) paints.lcp_replaced = true;
107
+ if (paints_raw.lcp_lazy === true) paints.lcp_lazy = true;
108
+ if (paints_raw.lcp_priority === "high" || paints_raw.lcp_priority === "low") paints.lcp_priority = paints_raw.lcp_priority;
109
+ const held = paints_raw.held;
110
+ if (held && (held.outcome === "arrived" || held.outcome === "input" || held.outcome === "cap" || held.outcome === "none")) {
111
+ const resources = num(held.resources);
112
+ const waited_ms = num(held.waited_ms);
113
+ if (resources !== void 0 && waited_ms !== void 0) paints.held = {
114
+ outcome: held.outcome,
115
+ resources,
116
+ waited_ms
117
+ };
118
+ }
119
+ const resources = [];
120
+ for (const r of (Array.isArray(v.resources) ? v.resources : []).slice(0, MAX_RESOURCES)) {
121
+ const url = str(r?.url, 500);
122
+ const start = num(r?.start);
123
+ const end = num(r?.end);
124
+ if (!url || start === void 0 || end === void 0) continue;
125
+ const out = {
126
+ url,
127
+ type: str(r.type, 16) ?? "other",
128
+ start,
129
+ end: Math.max(start, end)
130
+ };
131
+ for (const k of ["req_start", "res_start"]) {
132
+ const n = num(r[k]);
133
+ if (n !== void 0) out[k] = n;
134
+ }
135
+ for (const k of ["transfer", "size"]) {
136
+ const n = num(r[k], 1e9);
137
+ if (n !== void 0) out[k] = n;
138
+ }
139
+ if (r.blocking === true) out.blocking = true;
140
+ resources.push(out);
141
+ }
142
+ const longtasks = [];
143
+ for (const l of (Array.isArray(v.longtasks) ? v.longtasks : []).slice(0, MAX_LONGTASKS)) {
144
+ const t = num(l?.t);
145
+ const ms = num(l?.ms);
146
+ if (t !== void 0 && ms !== void 0) longtasks.push({
147
+ t,
148
+ ms
149
+ });
150
+ }
151
+ const islands = [];
152
+ for (const i of (Array.isArray(v.islands) ? v.islands : []).slice(0, MAX_ISLANDS)) {
153
+ const fp = typeof i?.fp === "string" && FP_RE.test(i.fp) ? i.fp : void 0;
154
+ const t0 = num(i?.t0);
155
+ const done = num(i?.done);
156
+ if (!fp || t0 === void 0 || done === void 0) continue;
157
+ const out = {
158
+ fp,
159
+ t0,
160
+ loaded: Math.min(Math.max(t0, num(i.loaded) ?? t0), Math.max(t0, done)),
161
+ done: Math.max(t0, done)
162
+ };
163
+ const turn = num(i.turn);
164
+ if (turn !== void 0 && turn >= out.loaded && turn <= out.done) out.turn = turn;
165
+ const fx = num(i.fx);
166
+ if (fx !== void 0 && fx > out.done) out.fx = fx;
167
+ const entry = str(i.entry, 300);
168
+ if (entry) out.entry = entry;
169
+ if (i.recovered === true) out.recovered = true;
170
+ if (i.changed === true) out.changed = true;
171
+ if (i.healed === true) out.healed = true;
172
+ const sb = num(i.ssr_bytes, 1e8);
173
+ if (sb !== void 0) out.ssr_bytes = sb;
174
+ const mk = Array.isArray(i.markers) ? i.markers : null;
175
+ if (mk && mk.length === 2 && Number.isInteger(mk[0]) && Number.isInteger(mk[1]) && mk[0] >= 0 && mk[1] >= 0 && mk[0] < 1e6 && mk[1] < 1e6 && mk[0] !== mk[1]) out.markers = [mk[0], mk[1]];
176
+ islands.push(out);
177
+ }
178
+ const firsts = [];
179
+ for (const f of (Array.isArray(v.firsts) ? v.firsts : []).slice(0, MAX_ISLANDS)) {
180
+ const fp = typeof f?.fp === "string" && FP_RE.test(f.fp) ? f.fp : void 0;
181
+ const t = num(f?.t);
182
+ if (fp && t !== void 0) firsts.push({
183
+ fp,
184
+ t,
185
+ type: str(f.type, 24) ?? "input"
186
+ });
187
+ }
188
+ const shifts = [];
189
+ for (const s of (Array.isArray(v.shifts) ? v.shifts : []).slice(0, MAX_SHIFTS)) {
190
+ const t = num(s?.t);
191
+ const value = num(s?.value, 100);
192
+ if (t === void 0 || value === void 0) continue;
193
+ const out = {
194
+ t,
195
+ value
196
+ };
197
+ if (typeof s.fp === "string" && FP_RE.test(s.fp)) out.fp = s.fp;
198
+ const tag = str(s.tag, 100);
199
+ if (tag) out.tag = tag;
200
+ const from = rect(s.from);
201
+ const to = rect(s.to);
202
+ if (from) out.from = from;
203
+ if (to) out.to = to;
204
+ shifts.push(out);
205
+ }
206
+ const visit = {
207
+ page,
208
+ at: num(v.at, 0x5af3107a4000) ?? Date.now(),
209
+ nav,
210
+ paints,
211
+ resources,
212
+ longtasks,
213
+ islands,
214
+ firsts,
215
+ shifts
216
+ };
217
+ if (v.longtasks_from === "frames") visit.longtasks_from = "frames";
218
+ if (v.shifts_from === "boxes") visit.shifts_from = "boxes";
219
+ const marks = [];
220
+ for (const m of (Array.isArray(v.marks) ? v.marks : []).slice(0, MAX_MARKS)) {
221
+ const name = str(m?.name, 120);
222
+ const ms = num(m?.ms);
223
+ if (!name || ms === void 0) continue;
224
+ const t0 = num(m.t0);
225
+ marks.push({
226
+ name,
227
+ ms,
228
+ ...t0 !== void 0 ? { t0 } : {}
229
+ });
230
+ }
231
+ if (marks.length) visit.marks = marks;
232
+ const regions = [];
233
+ for (const r of (Array.isArray(v.regions) ? v.regions : []).slice(0, MAX_ISLANDS)) {
234
+ const fp = typeof r?.fp === "string" && FP_RE.test(r.fp) ? r.fp : void 0;
235
+ const top = Number(r?.top);
236
+ const height = num(r?.height, 1e6);
237
+ if (!fp || !Number.isFinite(top) || Math.abs(top) > 1e7 || height === void 0) continue;
238
+ const out = {
239
+ fp,
240
+ top: Math.round(top),
241
+ height
242
+ };
243
+ const entry = str(r.entry, 300);
244
+ if (entry) out.entry = entry;
245
+ const wake = str(r.wake, 24);
246
+ if (wake) out.wake = wake;
247
+ if (r.defer === true) out.defer = true;
248
+ if (r.hydrated === true) out.hydrated = true;
249
+ if (r.hidden === true) out.hidden = true;
250
+ if (r.hidden === true && r.headless === true) out.headless = true;
251
+ if (typeof r.failed === "string") {
252
+ out.failed = r.failed.slice(0, 300);
253
+ const s = r.failed_span;
254
+ if (Array.isArray(s) && s.length === 2 && typeof s[0] === "number" && typeof s[1] === "number" && s[0] >= 0 && s[1] >= s[0] && s[1] < MAX_MS) out.failed_span = [s[0], s[1]];
255
+ if (typeof r.failed_turn === "number" && r.failed_turn >= 0 && r.failed_turn < MAX_MS) out.failed_turn = r.failed_turn;
256
+ }
257
+ regions.push(out);
258
+ }
259
+ if (regions.length) visit.regions = regions;
260
+ const scripts = [];
261
+ for (const s of (Array.isArray(v.scripts) ? v.scripts : []).slice(0, 50)) {
262
+ const url = str(s?.url, 300);
263
+ const ms = num(s?.ms);
264
+ if (!url || ms === void 0) continue;
265
+ scripts.push({
266
+ url,
267
+ ms,
268
+ count: num(s.count, 1e6) ?? 1
269
+ });
270
+ }
271
+ if (scripts.length) visit.scripts = scripts;
272
+ const warnings = [];
273
+ for (const w of (Array.isArray(v.warnings) ? v.warnings : []).slice(0, 50)) {
274
+ const code = str(w?.code, 80);
275
+ const message = str(w?.message, 400);
276
+ if (!code || !message) continue;
277
+ const file = str(w.file, 300);
278
+ const fp = typeof w.fp === "string" && FP_RE.test(w.fp) ? w.fp : void 0;
279
+ warnings.push({
280
+ code,
281
+ message,
282
+ ...file ? { file } : {},
283
+ ...fp ? { fp } : {}
284
+ });
285
+ }
286
+ if (warnings.length) visit.warnings = warnings;
287
+ const holes_failed = [];
288
+ for (const h of (Array.isArray(v.holes_failed) ? v.holes_failed : []).slice(0, 30)) {
289
+ const id = str(h?.id, 40);
290
+ const reason = h?.reason === "redirected" || h?.reason === "document" || h?.reason === "forbidden" || h?.reason === "error" ? h.reason : null;
291
+ if (!id || !reason) continue;
292
+ const final_path = str(h.final_path, 300);
293
+ const message = str(h.message, 200);
294
+ holes_failed.push({
295
+ id,
296
+ reason,
297
+ ...final_path ? { final_path } : {},
298
+ ...message ? { message } : {},
299
+ attempts: num(h.attempts, 10) ?? 1
300
+ });
301
+ }
302
+ if (holes_failed.length) visit.holes_failed = holes_failed;
303
+ const preload_misses = [];
304
+ for (const m of (Array.isArray(v.preload_misses) ? v.preload_misses : []).slice(0, 20)) {
305
+ const url = str(m?.url, 500);
306
+ const type = str(m?.type, 20);
307
+ const bytes = num(m?.bytes, 1e9);
308
+ if (!url || !type || bytes === void 0) continue;
309
+ preload_misses.push({
310
+ url,
311
+ type,
312
+ bytes,
313
+ as: str(m.as, 20) ?? "",
314
+ crossorigin: typeof m.crossorigin === "string" ? m.crossorigin.slice(0, 20) : null,
315
+ ...m.hole === true ? { hole: true } : {}
316
+ });
317
+ }
318
+ if (preload_misses.length) visit.preload_misses = preload_misses;
319
+ const font_faces = [];
320
+ for (const f of (Array.isArray(v.font_faces) ? v.font_faces : []).slice(0, 12)) {
321
+ const family = str(f?.family, 80);
322
+ const display = str(f?.display, 20);
323
+ const urls = (Array.isArray(f?.urls) ? f.urls : []).slice(0, 6).filter((u) => typeof u === "string" && u.length <= 500);
324
+ if (family === void 0 || !display || !urls.length) continue;
325
+ font_faces.push({
326
+ family,
327
+ display,
328
+ urls
329
+ });
330
+ }
331
+ if (font_faces.length) visit.font_faces = font_faces;
332
+ const images_oversized = [];
333
+ const pair = (x, max) => {
334
+ if (!Array.isArray(x) || x.length !== 2) return void 0;
335
+ const a = num(x[0], max);
336
+ const b = num(x[1], max);
337
+ return a && b ? [a, b] : void 0;
338
+ };
339
+ for (const i of (Array.isArray(v.images_oversized) ? v.images_oversized : []).slice(0, 10)) {
340
+ const url = str(i?.url, 500);
341
+ const natural = pair(i?.natural, 1e5);
342
+ const shown = pair(i?.shown, 1e5);
343
+ const dpr = num(i?.dpr, 10);
344
+ const bytes = num(i?.bytes, 1e9);
345
+ if (!url || !natural || !shown || !dpr || !bytes || natural[0] * natural[1] < shown[0] * shown[1] * dpr * dpr * 4) continue;
346
+ const fp = str(i.fp, 40);
347
+ images_oversized.push({
348
+ url,
349
+ natural,
350
+ shown,
351
+ dpr,
352
+ bytes,
353
+ ...fp ? { fp } : {}
354
+ });
355
+ }
356
+ if (images_oversized.length) visit.images_oversized = images_oversized;
357
+ const preloads_unused = [];
358
+ for (const p of (Array.isArray(v.preloads_unused) ? v.preloads_unused : []).slice(0, 30)) {
359
+ const url = str(p?.url, 500);
360
+ const as = p?.as === "image" || p?.as === "font" || p?.as === "style" ? p.as : void 0;
361
+ if (url && as) preloads_unused.push({
362
+ url,
363
+ as,
364
+ bytes: num(p.bytes, 1e9) ?? 0
365
+ });
366
+ }
367
+ if (preloads_unused.length) visit.preloads_unused = preloads_unused;
368
+ const uncompressed = [];
369
+ for (const f of (Array.isArray(v.uncompressed) ? v.uncompressed : []).slice(0, 10)) {
370
+ const url = str(f?.url, 500);
371
+ const type = str(f?.type, 20);
372
+ const bytes = num(f?.bytes, 1e9);
373
+ if (url && type && bytes) uncompressed.push({
374
+ url,
375
+ type,
376
+ bytes
377
+ });
378
+ }
379
+ if (uncompressed.length) visit.uncompressed = uncompressed;
380
+ if (Array.isArray(v.images_unsized)) visit.images_unsized = v.images_unsized.slice(0, 8).filter((u) => typeof u === "string" && u.length > 0).map((u) => u.slice(0, 300));
381
+ const images_eager_below = [];
382
+ for (const i of (Array.isArray(v.images_eager_below) ? v.images_eager_below : []).slice(0, 10)) {
383
+ const url = str(i?.url, 500);
384
+ const top = num(i?.top, 1e7);
385
+ const bytes = num(i?.bytes, 1e9);
386
+ if (!url || top === void 0 || !bytes) continue;
387
+ const fp = str(i.fp, 40);
388
+ images_eager_below.push({
389
+ url,
390
+ top,
391
+ bytes,
392
+ ...fp ? { fp } : {}
393
+ });
394
+ }
395
+ if (images_eager_below.length) visit.images_eager_below = images_eager_below;
396
+ const forced_layout = [];
397
+ for (const f of (Array.isArray(v.forced_layout) ? v.forced_layout : []).slice(0, 30)) {
398
+ const start = num(f?.start);
399
+ const end = num(f?.end);
400
+ const ms = num(f?.ms);
401
+ if (start === void 0 || end === void 0 || end < start || !ms) continue;
402
+ forced_layout.push({
403
+ start,
404
+ end,
405
+ ms,
406
+ url: str(f.url, 300) ?? "",
407
+ fn: str(f.fn, 80) ?? ""
408
+ });
409
+ }
410
+ if (forced_layout.length) visit.forced_layout = forced_layout;
411
+ const scroll_jank = [];
412
+ for (const j of (Array.isArray(v.scroll_jank) ? v.scroll_jank : []).slice(0, 20)) {
413
+ const start = num(j?.start);
414
+ const ms = num(j?.ms);
415
+ if (start === void 0 || !ms) continue;
416
+ const scripts = (Array.isArray(j.scripts) ? j.scripts : []).slice(0, 2).map((s) => ({
417
+ url: str(s?.url, 300) ?? "",
418
+ fn: str(s?.fn, 80) ?? "",
419
+ invoker: str(s?.invoker, 120) ?? "",
420
+ ms: num(s?.ms) ?? 0
421
+ }));
422
+ scroll_jank.push({
423
+ start,
424
+ ms,
425
+ scripts
426
+ });
427
+ }
428
+ if (scroll_jank.length) visit.scroll_jank = scroll_jank;
429
+ const d = v.dom;
430
+ const nodes = num(d?.nodes, 1e7);
431
+ if (d && nodes) {
432
+ const w = d.widest;
433
+ const islands = [];
434
+ for (const i of (Array.isArray(d.islands) ? d.islands : []).slice(0, 3)) {
435
+ const fp = str(i?.fp, 40);
436
+ const n = num(i?.nodes, nodes);
437
+ if (fp && n) islands.push({
438
+ fp,
439
+ nodes: n
440
+ });
441
+ }
442
+ visit.dom = {
443
+ nodes,
444
+ depth: num(d.depth, 1e4) ?? 0,
445
+ deepest: str(d.deepest, 80) ?? "",
446
+ widest: {
447
+ at: str(w?.at, 80) ?? "",
448
+ children: num(w?.children, nodes) ?? 0
449
+ },
450
+ islands
451
+ };
452
+ }
453
+ const entry_fallbacks = [];
454
+ for (const f of (Array.isArray(v.entry_fallbacks) ? v.entry_fallbacks : []).slice(0, 20)) {
455
+ const entry = str(f?.entry, 300);
456
+ const src = str(f?.src, 300);
457
+ if (!entry || !src) continue;
458
+ entry_fallbacks.push({
459
+ entry,
460
+ src,
461
+ recovered: f.recovered === true
462
+ });
463
+ }
464
+ if (entry_fallbacks.length) visit.entry_fallbacks = entry_fallbacks;
465
+ const refetched = [];
466
+ for (const f of (Array.isArray(v.refetched) ? v.refetched : []).slice(0, 40)) {
467
+ const url = str(f?.url, 500);
468
+ const how = f?.how === "revalidated" || f?.how === "downloaded" ? f.how : null;
469
+ const bytes = num(f?.bytes, 1e9);
470
+ if (!url || !how || bytes === void 0) continue;
471
+ const entry = str(f.entry, 300);
472
+ const hole = str(f.hole, 40);
473
+ refetched.push({
474
+ url,
475
+ how,
476
+ bytes,
477
+ ms: num(f.ms, 6e5) ?? 0,
478
+ ...entry ? { entry } : {},
479
+ ...hole ? { hole } : {},
480
+ ...f.runtime === true ? { runtime: true } : {}
481
+ });
482
+ }
483
+ if (refetched.length) visit.refetched = refetched;
484
+ const navs = [];
485
+ for (const n of (Array.isArray(v.navs) ? v.navs : []).slice(0, 20)) {
486
+ const to = str(n?.to, 300);
487
+ const t = num(n?.t, 36e5);
488
+ const fetched = num(n?.fetched, 36e5);
489
+ const styled = num(n?.styled, 36e5);
490
+ const swapped = num(n?.swapped, 36e5);
491
+ if (!to || t === void 0 || fetched === void 0 || styled === void 0 || swapped === void 0) continue;
492
+ if (!(t <= fetched && fetched <= styled && styled <= swapped)) continue;
493
+ const server = num(n.server, 36e5);
494
+ const download = num(n.download, 36e5);
495
+ const bytes = num(n.bytes, 1e9);
496
+ navs.push({
497
+ from: str(n.from, 300) ?? "",
498
+ to,
499
+ type: str(n.type, 20) ?? "link",
500
+ t,
501
+ fetched,
502
+ styled,
503
+ swapped,
504
+ ...server !== void 0 ? { server } : {},
505
+ ...download !== void 0 ? { download } : {},
506
+ ...bytes !== void 0 ? { bytes } : {},
507
+ ...n.prefetched === true ? { prefetched: true } : {}
508
+ });
509
+ }
510
+ if (navs.length) visit.navs = navs;
511
+ const it = v.interaction;
512
+ if (it && typeof it === "object") {
513
+ const name = str(it.name, 40);
514
+ const t = num(it.t);
515
+ const ms = num(it.ms);
516
+ if (name && t !== void 0 && ms !== void 0) {
517
+ const fp = typeof it.fp === "string" && FP_RE.test(it.fp) ? it.fp : void 0;
518
+ const phases = /* @__PURE__ */ new Set([
519
+ "delay",
520
+ "handler",
521
+ "paint"
522
+ ]);
523
+ const scripts = [];
524
+ for (const s of (Array.isArray(it.scripts) ? it.scripts : []).slice(0, 6)) {
525
+ const sms = num(s?.ms);
526
+ if (sms === void 0 || typeof s.phase !== "string" || !phases.has(s.phase)) continue;
527
+ const forced = num(s.forced);
528
+ scripts.push({
529
+ url: str(s.url, 300) ?? "",
530
+ fn: str(s.fn, 80) ?? "",
531
+ invoker: str(s.invoker, 120) ?? "",
532
+ ms: sms,
533
+ phase: s.phase,
534
+ ...forced ? { forced: Math.min(forced, sms) } : {}
535
+ });
536
+ }
537
+ visit.interaction = {
538
+ name,
539
+ t,
540
+ ms,
541
+ delay: num(it.delay) ?? 0,
542
+ processing: num(it.processing) ?? 0,
543
+ presentation: num(it.presentation) ?? 0,
544
+ target: str(it.target, 100) ?? "",
545
+ ...fp ? { fp } : {},
546
+ ...scripts.length ? { scripts } : {}
547
+ };
548
+ }
549
+ }
550
+ const holes_answered = [];
551
+ for (const h of (Array.isArray(v.holes_answered) ? v.holes_answered : []).slice(0, 30)) {
552
+ const id = str(h?.id, 40);
553
+ const start = num(h?.start);
554
+ const t = num(h?.t);
555
+ if (!id || start === void 0 || t === void 0 || t < start) continue;
556
+ const left = num(h.left);
557
+ const first = num(h.first);
558
+ const end = num(h.end);
559
+ const timed = left !== void 0 && first !== void 0 && end !== void 0 && left <= first && first <= end;
560
+ const queue = num(h.queue);
561
+ const render = num(h.render);
562
+ const batch = num(h.batch, 32);
563
+ const p = str(h.p, 16);
564
+ holes_answered.push({
565
+ id,
566
+ n: num(h.n, 1e3) ?? holes_answered.length,
567
+ start,
568
+ t,
569
+ below_fold: h.below_fold === true,
570
+ ...timed ? {
571
+ left,
572
+ first,
573
+ end
574
+ } : {},
575
+ ...queue !== void 0 ? { queue } : {},
576
+ ...render !== void 0 ? { render } : {},
577
+ ...batch !== void 0 && batch >= 1 ? { batch } : {},
578
+ ...p ? { p } : {}
579
+ });
580
+ }
581
+ if (holes_answered.length) visit.holes_answered = holes_answered;
582
+ const hole_batches = [];
583
+ for (const b of (Array.isArray(v.hole_batches) ? v.hole_batches : []).slice(0, 10)) {
584
+ const sent = num(b?.sent, 32);
585
+ const delivered = num(b?.delivered, 32);
586
+ const status = num(b?.status, 999);
587
+ if (sent === void 0 || delivered === void 0 || status === void 0 || delivered > sent) continue;
588
+ const refused = b.refused === "redirected" || b.refused === "document" ? b.refused : void 0;
589
+ const final_url = str(b.final_url, 300);
590
+ const ids = (Array.isArray(b.ids) ? b.ids : []).slice(0, 32).map((x) => str(x, 40) ?? "");
591
+ hole_batches.push({
592
+ sent,
593
+ delivered,
594
+ status,
595
+ ...refused ? { refused } : {},
596
+ ...final_url ? { final_url } : {},
597
+ ids
598
+ });
599
+ }
600
+ if (hole_batches.length) visit.hole_batches = hole_batches;
601
+ const restores = [];
602
+ for (const r of (Array.isArray(v.restores) ? v.restores : []).slice(0, 20)) {
603
+ const kind = r?.kind === "late" || r?.kind === "mismatch" ? r.kind : null;
604
+ const host = str(r?.host, 60);
605
+ const t = num(r?.t);
606
+ if (!kind || !host || t === void 0) continue;
607
+ const diff = str(r.diff, 300);
608
+ const island = str(r.island, 40);
609
+ restores.push({
610
+ kind,
611
+ host,
612
+ t,
613
+ ...diff ? { diff } : {},
614
+ ...island ? { island } : {}
615
+ });
616
+ }
617
+ if (restores.length) visit.restores = restores;
618
+ const unsupported = (Array.isArray(v.unsupported) ? v.unsupported : []).filter((t) => typeof t === "string" && KNOWN_TYPES.has(t));
619
+ if (unsupported.length) visit.unsupported = unsupported;
620
+ const origin = str(v.origin, 200);
621
+ if (origin && (origin.startsWith("http://") || origin.startsWith("https://"))) visit.origin = origin;
622
+ const totals = [];
623
+ for (const t of (Array.isArray(v.resource_totals) ? v.resource_totals : []).slice(0, 12)) {
624
+ const type = str(t?.type, 16);
625
+ const count = num(t?.count, 1e6);
626
+ if (!type || count === void 0) continue;
627
+ totals.push({
628
+ type,
629
+ count,
630
+ transfer: num(t.transfer, 1e11) ?? 0,
631
+ size: num(t.size, 1e11) ?? 0
632
+ });
633
+ }
634
+ const all_n = num(v.resources_all, 1e6);
635
+ if (totals.length && all_n !== void 0 && all_n > resources.length) {
636
+ visit.resource_totals = totals;
637
+ visit.resources_all = all_n;
638
+ }
639
+ const vr = v.vitals;
640
+ if (vr && typeof vr === "object") {
641
+ const vitals = {};
642
+ for (const k of [
643
+ "ttfb",
644
+ "fcp",
645
+ "lcp",
646
+ "inp"
647
+ ]) {
648
+ const n = num(vr[k]);
649
+ if (n !== void 0) vitals[k] = n;
650
+ }
651
+ const cls = num(vr.cls, 100);
652
+ if (cls !== void 0) vitals.cls = cls;
653
+ if (Object.keys(vitals).length) visit.vitals = vitals;
654
+ }
655
+ const vp = rect([
656
+ ...Array.isArray(v.viewport) ? v.viewport : [],
657
+ 0,
658
+ 0
659
+ ]);
660
+ if (vp && vp[0] > 0 && vp[1] > 0) visit.viewport = [vp[0], vp[1]];
661
+ const ua = str(v.ua, 200);
662
+ if (ua) visit.ua = ua;
663
+ const dock = num(v.dock_open_at);
664
+ if (dock !== void 0) visit.dock_open_at = dock;
665
+ return visit;
666
+ }
667
+ /** Two records of the SAME visit (same page, same navigation start) folded into one. The runtime's
668
+ * beacon and the app's `mark()` client can be two module instances on one page, each posting its
669
+ * own half (islands here, marks there); a visit is also posted early and again on hide. Arrays
670
+ * union by identity, the later record's navigation and paints win where it has them. */
671
+ function merge_visits(a, b) {
672
+ const by = (xs, ys, key) => {
673
+ const m = /* @__PURE__ */ new Map();
674
+ for (const x of xs) m.set(key(x), x);
675
+ for (const y of ys) m.set(key(y), y);
676
+ return [...m.values()];
677
+ };
678
+ return {
679
+ page: a.page,
680
+ at: a.at,
681
+ nav: {
682
+ ...a.nav,
683
+ ...b.nav
684
+ },
685
+ paints: {
686
+ ...a.paints,
687
+ ...b.paints
688
+ },
689
+ resources: b.resources.length >= a.resources.length ? b.resources : a.resources,
690
+ ...(b.resources_all ?? 0) >= (a.resources_all ?? 0) ? b.resource_totals ? {
691
+ resource_totals: b.resource_totals,
692
+ resources_all: b.resources_all
693
+ } : {} : {
694
+ resource_totals: a.resource_totals,
695
+ resources_all: a.resources_all
696
+ },
697
+ longtasks: by(a.longtasks, b.longtasks, (l) => `${l.t}|${l.ms}`),
698
+ ...a.longtasks_from || b.longtasks_from ? { longtasks_from: "frames" } : {},
699
+ ...a.shifts_from || b.shifts_from ? { shifts_from: "boxes" } : {},
700
+ islands: by(a.islands, b.islands, (i) => `${i.fp}|${i.t0}`),
701
+ firsts: by(a.firsts, b.firsts, (f) => f.fp),
702
+ shifts: by(a.shifts, b.shifts, (s) => `${s.t}|${s.value}`),
703
+ ...a.marks || b.marks ? { marks: by(a.marks ?? [], b.marks ?? [], (m) => `${m.name}|${m.t0 ?? ""}|${m.ms}`) } : {},
704
+ ...a.regions || b.regions ? { regions: by(a.regions ?? [], b.regions ?? [], (r) => r.fp) } : {},
705
+ ...a.vitals || b.vitals ? { vitals: {
706
+ ...a.vitals,
707
+ ...b.vitals
708
+ } } : {},
709
+ ...b.scripts ?? a.scripts ? { scripts: b.scripts ?? a.scripts } : {},
710
+ ...b.origin ?? a.origin ? { origin: b.origin ?? a.origin } : {},
711
+ ...b.unsupported ?? a.unsupported ? { unsupported: b.unsupported ?? a.unsupported } : {},
712
+ ...b.warnings ?? a.warnings ? { warnings: b.warnings ?? a.warnings } : {},
713
+ ...b.preload_misses ?? a.preload_misses ? { preload_misses: b.preload_misses ?? a.preload_misses } : {},
714
+ ...b.uncompressed ?? a.uncompressed ? { uncompressed: b.uncompressed ?? a.uncompressed } : {},
715
+ ...b.font_faces ?? a.font_faces ? { font_faces: b.font_faces ?? a.font_faces } : {},
716
+ ...b.images_oversized ?? a.images_oversized ? { images_oversized: b.images_oversized ?? a.images_oversized } : {},
717
+ ...b.dom ?? a.dom ? { dom: b.dom ?? a.dom } : {},
718
+ ...b.preloads_unused ?? a.preloads_unused ? { preloads_unused: b.preloads_unused ?? a.preloads_unused } : {},
719
+ ...b.images_eager_below ?? a.images_eager_below ? { images_eager_below: b.images_eager_below ?? a.images_eager_below } : {},
720
+ ...b.images_unsized ?? a.images_unsized ? { images_unsized: b.images_unsized ?? a.images_unsized } : {},
721
+ ...a.forced_layout || b.forced_layout ? { forced_layout: by(a.forced_layout ?? [], b.forced_layout ?? [], (f) => `${f.start}|${f.url}`) } : {},
722
+ ...a.scroll_jank || b.scroll_jank ? { scroll_jank: by(a.scroll_jank ?? [], b.scroll_jank ?? [], (j) => `${j.start}`) } : {},
723
+ ...b.refetched ?? a.refetched ? { refetched: b.refetched ?? a.refetched } : {},
724
+ ...a.navs || b.navs ? { navs: by(a.navs ?? [], b.navs ?? [], (n) => `${n.t}|${n.to}`) } : {},
725
+ ...a.interaction || b.interaction ? { interaction: !a.interaction ? b.interaction : !b.interaction ? a.interaction : b.interaction.ms >= a.interaction.ms ? b.interaction : a.interaction } : {},
726
+ ...a.entry_fallbacks || b.entry_fallbacks ? { entry_fallbacks: by(a.entry_fallbacks ?? [], b.entry_fallbacks ?? [], (f) => f.entry) } : {},
727
+ ...a.holes_failed || b.holes_failed ? { holes_failed: by(a.holes_failed ?? [], b.holes_failed ?? [], (h) => h.id) } : {},
728
+ ...a.holes_answered || b.holes_answered ? { holes_answered: by(a.holes_answered ?? [], b.holes_answered ?? [], (h) => `${h.n}:${h.id}`) } : {},
729
+ ...a.hole_batches || b.hole_batches ? { hole_batches: (b.hole_batches?.length ?? 0) >= (a.hole_batches?.length ?? 0) ? b.hole_batches : a.hole_batches } : {},
730
+ ...a.restores || b.restores ? { restores: (b.restores?.length ?? 0) >= (a.restores?.length ?? 0) ? b.restores : a.restores } : {},
731
+ ...b.viewport ?? a.viewport ? { viewport: b.viewport ?? a.viewport } : {},
732
+ ...b.ua ?? a.ua ? { ua: b.ua ?? a.ua } : {},
733
+ ...b.dock_open_at ?? a.dock_open_at ? { dock_open_at: b.dock_open_at ?? a.dock_open_at } : {}
734
+ };
735
+ }
736
+ const RESOURCE_TYPES = [
737
+ "css",
738
+ "script",
739
+ "font",
740
+ "img",
741
+ "fetch",
742
+ "other"
743
+ ];
744
+ /** Lay a visit out on one clock next to the server's render of the same page. `phases` is the
745
+ * server timeline's phase split (each phase's CPU + wait, in order); `names` maps an island's
746
+ * fingerprint to its component name. */
747
+ function one_clock(visit, server, names = {}, hole_names = {}) {
748
+ const lanes = [];
749
+ const notes = [];
750
+ if (visit.resources_all && visit.resources_all > visit.resources.length) notes.push(`The page loaded ${visit.resources_all} files; the lanes show ${visit.resources.length} of them (every render-blocking file, then the earliest). The counts and bytes by type cover all of them.`);
751
+ const nav = visit.nav;
752
+ let end = Math.max(nav.res_end, nav.load ?? 0, nav.dcl ?? 0, visit.paints.lcp ?? 0);
753
+ let server_at = null;
754
+ if (nav.start_hidden) notes.push("The browser gave no time for the request and the first byte (Safari on a cross-origin-isolated page): the lanes start at the HTML's end, and the server's render is not placed.");
755
+ if (server && server.window_ms > 0 && !nav.start_hidden) {
756
+ const t1 = nav.res_start;
757
+ let t0 = t1 - server.window_ms;
758
+ let clipped = false;
759
+ if (t0 < 0) {
760
+ clipped = true;
761
+ t0 = 0;
762
+ notes.push(`The profiled render (${Math.round(server.window_ms)} ms) is longer than this visit's time to first byte (${Math.round(t1)} ms): the visit hit a cache or a faster instance. The server bar is clipped to fit.`);
763
+ }
764
+ server_at = {
765
+ t0,
766
+ t1,
767
+ clipped
768
+ };
769
+ const scale = (t1 - t0) / server.window_ms;
770
+ const bars = [];
771
+ let at = t0;
772
+ for (const p of server.phases) {
773
+ const len = (p.cpu_ms + p.wait_ms) * scale;
774
+ if (len <= 0) continue;
775
+ bars.push({
776
+ id: `server:${p.phase}`,
777
+ lane: "server",
778
+ label: p.label ?? p.phase,
779
+ t0: at,
780
+ t1: at + len,
781
+ kind: `server-${p.phase}`,
782
+ detail: `${Math.round(p.cpu_ms)} ms CPU · ${Math.round(p.wait_ms)} ms waiting`
783
+ });
784
+ at += len;
785
+ }
786
+ lanes.push({
787
+ name: "server render",
788
+ group: "server",
789
+ bars
790
+ });
791
+ }
792
+ lanes.push({
793
+ name: "document",
794
+ group: "document",
795
+ bars: nav.start_hidden ? [] : [{
796
+ id: "doc:ttfb",
797
+ lane: "document",
798
+ label: "waiting for the first byte",
799
+ t0: nav.req_start,
800
+ t1: nav.res_start,
801
+ kind: "ttfb",
802
+ detail: `${Math.round(nav.res_start - nav.req_start)} ms from request to first byte`
803
+ }, {
804
+ id: "doc:download",
805
+ lane: "document",
806
+ label: "HTML arriving",
807
+ t0: nav.res_start,
808
+ t1: nav.res_end,
809
+ kind: "download",
810
+ detail: `${nav.size ? Math.round(nav.size / 1024) + " KB" : "the document"} over ${Math.round(nav.res_end - nav.res_start)} ms${nav.protocol ? " · " + nav.protocol : ""}`
811
+ }]
812
+ });
813
+ const by_type = /* @__PURE__ */ new Map();
814
+ for (const r of visit.resources) {
815
+ const t = RESOURCE_TYPES.includes(r.type) ? r.type : "other";
816
+ let list = by_type.get(t);
817
+ if (!list) by_type.set(t, list = []);
818
+ list.push(r);
819
+ end = Math.max(end, r.end);
820
+ }
821
+ const res_bars = /* @__PURE__ */ new Map();
822
+ for (const t of RESOURCE_TYPES) {
823
+ const list = by_type.get(t);
824
+ if (!list?.length) continue;
825
+ const bars = list.sort((a, b) => a.start - b.start).map((r, i) => {
826
+ const bar = {
827
+ id: `res:${t}:${i}`,
828
+ lane: t,
829
+ label: r.url.split("/").pop()?.split("?")[0] || r.url,
830
+ t0: r.start,
831
+ t1: r.end,
832
+ kind: t,
833
+ detail: `${r.transfer ? Math.round(r.transfer / 1024) + " KB on the wire" : ""}${r.size ? ` · ${Math.round(r.size / 1024)} KB decoded` : ""}${r.blocking ? " · render-blocking" : ""}`.replace(/^ · /, ""),
834
+ ...r.blocking ? { blocking: true } : {}
835
+ };
836
+ res_bars.set(r.url, bar);
837
+ return bar;
838
+ });
839
+ lanes.push({
840
+ name: `${t} (${bars.length})`,
841
+ group: "network",
842
+ bars
843
+ });
844
+ }
845
+ if (visit.longtasks.length) {
846
+ lanes.push({
847
+ name: "main thread busy",
848
+ group: "main",
849
+ bars: visit.longtasks.map((l, i) => ({
850
+ id: `task:${i}`,
851
+ lane: "main",
852
+ label: `${visit.longtasks_from === "frames" ? "frame late" : "long task"} ${Math.round(l.ms)} ms`,
853
+ t0: l.t,
854
+ t1: l.t + l.ms,
855
+ kind: "task"
856
+ }))
857
+ });
858
+ for (const l of visit.longtasks) end = Math.max(end, l.t + l.ms);
859
+ }
860
+ const island_bars = /* @__PURE__ */ new Map();
861
+ const islands = [...visit.islands].sort((a, b) => a.t0 - b.t0).slice(0, 40);
862
+ if (islands.length) {
863
+ const bars = [];
864
+ for (const i of islands) {
865
+ const name = names[i.fp] ?? (i.entry?.split("/").pop()?.replace(/\.[^.]+$/, "") || i.fp.slice(0, 8));
866
+ if (i.loaded > i.t0) bars.push({
867
+ id: `island:${i.fp}:load`,
868
+ lane: "islands",
869
+ label: `${name} · loading modules`,
870
+ t0: i.t0,
871
+ t1: i.loaded,
872
+ kind: "island-load",
873
+ fp: i.fp
874
+ });
875
+ const own_end = Math.max(i.done, i.fx ?? 0);
876
+ const hyd = {
877
+ id: `island:${i.fp}`,
878
+ lane: "islands",
879
+ label: `${name} · hydrating`,
880
+ t0: i.loaded,
881
+ t1: own_end,
882
+ kind: "island-hydrate",
883
+ fp: i.fp,
884
+ detail: `${Math.round(own_end - i.t0)} ms from wake, ${Math.round(i.loaded - i.t0)} of it loading${i.fx ? `, ${Math.round(i.fx - i.done)} running its effects` : ""}${i.recovered ? " · re-rendered (mismatch)" : ""}${i.changed ? " · markup changed" : ""}`
885
+ };
886
+ bars.push(hyd);
887
+ island_bars.set(i.fp, hyd);
888
+ end = Math.max(end, own_end);
889
+ }
890
+ lanes.push({
891
+ name: `islands (${islands.length})`,
892
+ group: "islands",
893
+ bars
894
+ });
895
+ }
896
+ const holes = [...visit.holes_answered ?? []].sort((a, b) => (a.left ?? a.start) - (b.left ?? b.start)).slice(0, 12);
897
+ const copies = /* @__PURE__ */ new Map();
898
+ for (const h of holes) copies.set(h.id, (copies.get(h.id) ?? 0) + 1);
899
+ const seen = /* @__PURE__ */ new Map();
900
+ for (const h of holes) {
901
+ const k = (seen.get(h.id) ?? 0) + 1;
902
+ seen.set(h.id, k);
903
+ const base_name = hole_names[h.id] ?? `hole ${h.id.slice(0, 8)}`;
904
+ const name = copies.get(h.id) > 1 ? `${base_name} ${k}` : base_name;
905
+ const shown_at = Math.max(visit.paints.fcp ?? 0, h.start);
906
+ const segs = hole_segments({
907
+ shown_at,
908
+ wait_ms: Math.max(0, h.t - shown_at),
909
+ ...h.left !== void 0 ? {
910
+ left_at: h.left,
911
+ first_at: h.first
912
+ } : {},
913
+ ...h.queue !== void 0 ? { server_queue_ms: h.queue } : {},
914
+ ...h.render !== void 0 ? { server_ms: h.render } : {}
915
+ });
916
+ lanes.push({
917
+ name,
918
+ group: "holes",
919
+ bars: segs.map((s) => ({
920
+ id: `hole:${h.id}:${s.k}`,
921
+ lane: "holes",
922
+ label: `${name} · ${HOLE_SEG_LABEL[s.k]}`,
923
+ t0: s.a,
924
+ t1: s.b,
925
+ kind: `hole-${s.k}`,
926
+ detail: `${Math.round(s.b - s.a)} ms · its fallback stood ${Math.round(h.t - shown_at)} ms${h.below_fold ? " (below the first screen)" : ""}`
927
+ }))
928
+ });
929
+ end = Math.max(end, h.t);
930
+ }
931
+ const marks = [];
932
+ if (visit.paints.fcp !== void 0) marks.push({
933
+ label: "first paint",
934
+ t: visit.paints.fcp,
935
+ kind: "fcp"
936
+ });
937
+ if (visit.paints.lcp !== void 0) marks.push({
938
+ label: "largest paint",
939
+ t: visit.paints.lcp,
940
+ kind: "lcp"
941
+ });
942
+ if (nav.dcl !== void 0) marks.push({
943
+ label: "DOM ready",
944
+ t: nav.dcl,
945
+ kind: "dcl"
946
+ });
947
+ if (nav.load !== void 0) marks.push({
948
+ label: "load",
949
+ t: nav.load,
950
+ kind: "load"
951
+ });
952
+ for (const f of visit.firsts.slice(0, 20)) marks.push({
953
+ label: `first ${f.type} on ${names[f.fp] ?? f.fp.slice(0, 8)}`,
954
+ t: f.t,
955
+ kind: "first"
956
+ });
957
+ for (const m of visit.marks ?? []) if (m.t0 !== void 0) marks.push({
958
+ label: m.name,
959
+ t: m.t0 + m.ms,
960
+ kind: "mark"
961
+ });
962
+ const critical = [];
963
+ const mark_critical = (bar, why) => {
964
+ if (!bar) return;
965
+ bar.critical = true;
966
+ critical.push({
967
+ id: bar.id,
968
+ label: bar.label,
969
+ t0: bar.t0,
970
+ t1: bar.t1,
971
+ why
972
+ });
973
+ };
974
+ if (server_at) {
975
+ const longest = [...lanes[0].bars].sort((a, b) => b.t1 - b.t0 - (a.t1 - a.t0))[0];
976
+ mark_critical(longest, "the biggest part of the server render");
977
+ }
978
+ mark_critical(lanes.find((l) => l.group === "document").bars[0], "the browser could do nothing before the first byte");
979
+ const fcp = visit.paints.fcp;
980
+ if (fcp !== void 0) {
981
+ const blocking = visit.resources.filter((r) => (r.blocking || r.type === "css") && r.end <= fcp + 5 && r.start < fcp).sort((a, b) => b.end - a.end)[0];
982
+ if (blocking) mark_critical(res_bars.get(blocking.url), "the last render-blocking file before first paint");
983
+ else mark_critical(lanes.find((l) => l.group === "document").bars[1], "first paint waited for the HTML");
984
+ }
985
+ const lcp = visit.paints.lcp;
986
+ if (lcp !== void 0) {
987
+ if (visit.paints.lcp_url && res_bars.has(visit.paints.lcp_url)) mark_critical(res_bars.get(visit.paints.lcp_url), "the largest paint is this image");
988
+ else if (visit.paints.lcp_fp && island_bars.has(visit.paints.lcp_fp)) {
989
+ const bar = island_bars.get(visit.paints.lcp_fp);
990
+ if (bar.t1 <= lcp + 5) mark_critical(bar, "the largest paint sits in this island, which painted after it hydrated");
991
+ }
992
+ }
993
+ return {
994
+ end: Math.ceil(end + 1),
995
+ lanes,
996
+ marks,
997
+ critical,
998
+ server_at,
999
+ notes
1000
+ };
1001
+ }
1002
+ export { merge_visits, one_clock, parse_visit };