@ogygia/devtools 0.0.0 → 0.11.1

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 +99 -6
@@ -0,0 +1,690 @@
1
+ // src/profiler/timeline.ts
2
+ const PHASE_LABEL = {
3
+ hooks: "hooks",
4
+ load: "load functions",
5
+ remote: "remote functions",
6
+ render: "component render",
7
+ ogygia: "ogygia transform",
8
+ kit: "Kit routing + response",
9
+ other: "other"
10
+ };
11
+ const round2 = (n) => Math.round(n * 100) / 100;
12
+ const KIT_LOAD_RE = /\/runtime\/server\/page\/load_data\.js/;
13
+ /** Kit remote functions (`query` / `command` / `form` / `prerender` from a `.remote.ts`): the app's
14
+ * remote file itself, or Kit's remote runtime that runs it. A remote awaited during SSR — an
15
+ * island's top-level `await getX()`, a `load` calling one — is on the critical path like a load. */
16
+ const KIT_REMOTE_RE = /\/runtime\/(?:app\/server\/remote|server\/remote)\//;
17
+ const APP_REMOTE_FILE_RE = /\.remote\.[jt]s(?:\.js)?$/;
18
+ const KIT_RENDER_RE = /\/runtime\/server\/page\/render\.js/;
19
+ const KIT_RUNTIME_RE = /@sveltejs\/kit\/src\/runtime\/server\//;
20
+ const APP_LOAD_FILE_RE = /(?:^|[/\\])(?:\+(?:page|layout)(?:\.server)?\.[jt]s|_(?:page|layout)(?:\.server)?\.[jt]s\.js)$/;
21
+ const APP_HOOKS_FILE_RE = /(?:^|[/\\])hooks\.server\.[jt]s(?:\.js)?$/;
22
+ /** A load file inside a resolved caller (`load (routes/x/+page.server.ts:44)`) or a frame url. */
23
+ const LOAD_FILE_IN_TEXT_RE = /((?:[^\s()]*[/\\])?[+_](page|layout|error)(\.server)?\.[jt]s(?:\.js)?)(?::\d+)?/;
24
+ const SRC_REL_RE = /[/\\]src[/\\](.*)$/;
25
+ /** The lane a load frame / caller belongs to: `{ file, level, kind }`, or null. */
26
+ function load_lane_of(text) {
27
+ const m = LOAD_FILE_IN_TEXT_RE.exec(text);
28
+ if (!m) return null;
29
+ let file = m[1].replace(/\\/g, "/");
30
+ const rel = SRC_REL_RE.exec(file);
31
+ file = rel ? rel[1] : file.split("/").filter(Boolean).slice(-3).join("/");
32
+ file = file.replace(/(^|\/)_(page|layout|error)((?:\.server)?\.[jt]s)\.js$/, (_, pre, l, ext) => `${pre}+${l}${ext}`);
33
+ return {
34
+ file,
35
+ level: m[2],
36
+ kind: m[3] ? "server" : "universal"
37
+ };
38
+ }
39
+ const OGYGIA_SERVER_RE = /\/(?:ogygia|@ogygia\/core|packages\/core)\/(?:src|dist)\/(?!profiler\/)/;
40
+ /** The Kit phase of ONE frame, or null when the frame says nothing about it. */
41
+ function phase_of_frame(f, hooks) {
42
+ if (f.name === "inject_client_seeds") return "ogygia";
43
+ const url = f.url;
44
+ if (!url) return null;
45
+ const stem = hooks?.get(f.name);
46
+ if (stem && is_in_file(url, stem)) return "hooks";
47
+ if (OGYGIA_SERVER_RE.test(url)) return "ogygia";
48
+ if (APP_REMOTE_FILE_RE.test(url) || KIT_REMOTE_RE.test(url)) return "remote";
49
+ if (f.category === "component" || f.category === "svelte" || KIT_RENDER_RE.test(url)) return "render";
50
+ if (KIT_LOAD_RE.test(url) || APP_LOAD_FILE_RE.test(url)) return "load";
51
+ if (APP_HOOKS_FILE_RE.test(url)) return "hooks";
52
+ if (KIT_RUNTIME_RE.test(url)) return "kit";
53
+ return null;
54
+ }
55
+ /** Walk a stack leaf → root; the first frame that names a phase wins (the deepest decision). */
56
+ function phase_of_stack(frames, hooks) {
57
+ for (const f of frames) {
58
+ const p = phase_of_frame(f, hooks);
59
+ if (p) return p;
60
+ }
61
+ if (hooks?.size) for (const f of frames) {
62
+ if (!f.url) continue;
63
+ for (const stem of hooks.values()) if (is_in_file(f.url, stem)) return "hooks";
64
+ }
65
+ return "other";
66
+ }
67
+ /** does `url` name a file whose stem is `stem` (`…/ds-ssr.ts`, `…/ds-ssr.js`, `ds-ssr.ts`)? */
68
+ function is_in_file(url, stem) {
69
+ const at = url.lastIndexOf(stem + ".");
70
+ if (at === -1) return false;
71
+ const before = url.charCodeAt(at - 1);
72
+ return at === 0 || before === 47 || before === 92;
73
+ }
74
+ /** The frame a CPU segment is charged to: the deepest component, else the deepest named app
75
+ * function, else the deepest dependency, else the runtime bucket the leaf is in. */
76
+ function owner_of_stack(frames) {
77
+ const named = (f) => f.name && f.name !== "(anonymous)" && !f.name.startsWith("(");
78
+ const file_of = (f) => f.url ? `${short(f.url)}:${f.line}` : "";
79
+ const leaf = frames.find((f) => named(f) && f.category !== "v8" && f.category !== "gc");
80
+ const pick = (f, label) => ({
81
+ label,
82
+ file: file_of(f),
83
+ category: f.category,
84
+ detail: leaf && leaf !== f ? leaf.name + (leaf.pkg ? ` (${leaf.pkg})` : "") : void 0
85
+ });
86
+ let comp;
87
+ let app;
88
+ let dep;
89
+ for (const f of frames) {
90
+ if (!comp && f.category === "component") comp = f;
91
+ if (!app && f.category === "app" && named(f)) app = f;
92
+ if (!dep && f.category === "dependency" && named(f)) dep = f;
93
+ }
94
+ if (comp) return pick(comp, comp.name);
95
+ if (app) return pick(app, app.name);
96
+ if (dep) return pick(dep, dep.pkg ? `${dep.name} (${dep.pkg})` : dep.name);
97
+ const anon = frames.find((f) => f.category === "app" && f.url);
98
+ if (anon) return pick(anon, anon.line <= 1 ? `module load (${short(anon.url)})` : `fn @ ${short(anon.url)}:${anon.line}`);
99
+ const anon_dep = frames.find((f) => f.category === "dependency" && f.pkg);
100
+ if (anon_dep) return pick(anon_dep, `${anon_dep.line <= 1 ? "module load" : "(anonymous)"} (${anon_dep.pkg})`);
101
+ const top = frames[0];
102
+ if (!top) return {
103
+ label: "unknown",
104
+ file: "",
105
+ category: "unknown"
106
+ };
107
+ if (top.category === "gc") return {
108
+ label: "garbage collection",
109
+ file: "",
110
+ category: "gc"
111
+ };
112
+ if (top.category === "svelte") return pick(top, "svelte internals");
113
+ if (top.category === "node") return pick(top, `node core: ${top.name || "(native)"}`);
114
+ if (top.category === "profiler") return pick(top, "profiler overhead");
115
+ return pick(top, top.name || "v8");
116
+ }
117
+ function short(url) {
118
+ const nm = url.lastIndexOf("node_modules/");
119
+ if (nm !== -1) return url.slice(nm + 13);
120
+ const parts = url.split("/");
121
+ return parts.length > 4 ? parts.slice(-4).join("/") : url;
122
+ }
123
+ /**
124
+ * Build the timeline. `frame_of` resolves a profile node to its frame facts, `parent_of` the
125
+ * tree; both come from `analyze`'s own tables so the timeline agrees with every other view.
126
+ */
127
+ function build_timeline(profile, frame_of, parent_of, input) {
128
+ const { start: w0, end: w1 } = input.window;
129
+ const window_ms = Math.max(w1 - w0, .01);
130
+ const stack_memo = /* @__PURE__ */ new Map();
131
+ const describe = (id) => {
132
+ const hit = stack_memo.get(id);
133
+ if (hit) return hit;
134
+ const frames = [];
135
+ let cur = id;
136
+ while (cur !== void 0 && frames.length < 64) {
137
+ frames.push(frame_of(cur));
138
+ cur = parent_of(cur);
139
+ }
140
+ const leaf = frames[0];
141
+ const idle = !!leaf && (leaf.category === "idle" || leaf.name === "(root)" || leaf.name === "(program)");
142
+ let lane = null;
143
+ let in_parent = false;
144
+ /** packages app code calls into on this stack, and the outermost package when no app code is on it */
145
+ const entered = [];
146
+ let root_pkg;
147
+ let has_app = false;
148
+ let inner;
149
+ for (let k = 0; k < frames.length; k++) {
150
+ const f = frames[k];
151
+ if (!lane && f.url && APP_LOAD_FILE_RE.test(f.url)) lane = load_lane_of(f.url);
152
+ if (f.name === "parent" && KIT_LOAD_RE.test(f.url)) in_parent = true;
153
+ if (f.category === "app" || f.category === "component") {
154
+ if (!has_app) inner = f;
155
+ has_app = true;
156
+ const child = frames[k - 1];
157
+ if (child?.category === "dependency" && child.pkg && !entered.includes(child.pkg)) entered.push(child.pkg);
158
+ } else if (f.category === "dependency" && f.pkg) root_pkg = f.pkg;
159
+ }
160
+ const d = {
161
+ owner: owner_of_stack(frames),
162
+ phase: phase_of_stack(frames, input.hooks),
163
+ idle,
164
+ lane,
165
+ in_parent,
166
+ entered,
167
+ orphan_pkg: has_app ? void 0 : root_pkg,
168
+ inner
169
+ };
170
+ stack_memo.set(id, d);
171
+ return d;
172
+ };
173
+ const cpu = [];
174
+ const lanes = /* @__PURE__ */ new Map();
175
+ /** a lane's wait intervals, unioned at the end (parallel calls overlap) */
176
+ const lane_waits = /* @__PURE__ */ new Map();
177
+ const lane_row = (l) => {
178
+ let r = lanes.get(l.file);
179
+ if (!r) lanes.set(l.file, r = {
180
+ ...l,
181
+ t0: Infinity,
182
+ t1: -Infinity,
183
+ cpu_ms: 0,
184
+ wait_ms: 0,
185
+ awaited_parent: false
186
+ });
187
+ return r;
188
+ };
189
+ const samples = profile.samples ?? [];
190
+ const deltas = profile.timeDeltas ?? [];
191
+ let t_us = profile.startTime;
192
+ /** package → the phase of the code that last called into it. A library that goes on working after
193
+ * an await (its own promise chain, a timer loop) has no app frame under it; that work belongs to
194
+ * whoever last called in — the same rule the functions table uses for a library's async work */
195
+ const entry_phase = /* @__PURE__ */ new Map();
196
+ /** owner label → `name\0file:line` of the deepest app function → ms */
197
+ const inner_ms = /* @__PURE__ */ new Map();
198
+ for (let i = 0; i < samples.length; i++) {
199
+ const d = deltas[i] ?? 0;
200
+ t_us += d;
201
+ const t = input.perf_start + (t_us - profile.startTime) / 1e3;
202
+ if (t > w1 || d <= 0) continue;
203
+ const described = describe(samples[i]);
204
+ if (described.idle) continue;
205
+ for (const pkg of described.entered) entry_phase.set(pkg, described.phase);
206
+ if (t < w0) continue;
207
+ const inherited = described.phase === "other" && described.orphan_pkg ? entry_phase.get(described.orphan_pkg) : void 0;
208
+ const info = inherited ? {
209
+ ...described,
210
+ phase: inherited
211
+ } : described;
212
+ const s0 = Math.max(t - d / 1e3, w0) - w0;
213
+ const s1 = t - w0;
214
+ const o = info.owner;
215
+ if (info.inner && (o.category === "app" || o.category === "component")) {
216
+ const m = inner_ms.get(o.label) ?? inner_ms.set(o.label, /* @__PURE__ */ new Map()).get(o.label);
217
+ const k = `${info.inner.name}\0${short(info.inner.url)}:${info.inner.line}`;
218
+ m.set(k, (m.get(k) ?? 0) + s1 - s0);
219
+ }
220
+ if (info.lane) {
221
+ const row = lane_row(info.lane);
222
+ row.t0 = Math.min(row.t0, s0);
223
+ row.t1 = Math.max(row.t1, s1);
224
+ if (info.owner.category !== "profiler") row.cpu_ms += s1 - s0;
225
+ if (info.in_parent) row.awaited_parent = true;
226
+ }
227
+ const last = cpu[cpu.length - 1];
228
+ if (last && last.owner.label === info.owner.label && last.phase === info.phase && last.lane === info.lane?.file && s0 - last.t1 <= d / 1e3 * 2) last.t1 = s1;
229
+ else cpu.push({
230
+ t0: s0,
231
+ t1: s1,
232
+ owner: info.owner,
233
+ phase: info.phase,
234
+ ...info.lane ? { lane: info.lane.file } : {}
235
+ });
236
+ }
237
+ const waits = [];
238
+ for (const c of input.calls) {
239
+ if (c.ms < 0) continue;
240
+ const a = Math.max(c.start, w0) - w0;
241
+ const b = Math.min(c.start + c.ms, w1) - w0;
242
+ if (b - a < .05) continue;
243
+ waits.push({
244
+ t0: a,
245
+ t1: b,
246
+ call: c
247
+ });
248
+ if (c.caller) {
249
+ const l = load_lane_of(c.caller);
250
+ if (l) {
251
+ const row = lane_row(l);
252
+ row.t0 = Math.min(row.t0, a);
253
+ row.t1 = Math.max(row.t1, b);
254
+ (lane_waits.get(l.file) ?? lane_waits.set(l.file, []).get(l.file)).push([a, b]);
255
+ }
256
+ }
257
+ }
258
+ const bounds = /* @__PURE__ */ new Set([0, window_ms]);
259
+ for (const c of cpu) {
260
+ bounds.add(c.t0);
261
+ bounds.add(c.t1);
262
+ }
263
+ for (const w of waits) {
264
+ bounds.add(w.t0);
265
+ bounds.add(w.t1);
266
+ }
267
+ for (const p of input.pending ?? []) {
268
+ const a = p.start - w0;
269
+ const b = p.end - w0;
270
+ if (a > 0 && a < window_ms) bounds.add(a);
271
+ if (b > 0 && b < window_ms) bounds.add(b);
272
+ }
273
+ const ticks = [...bounds].sort((a, b) => a - b);
274
+ const segments = [];
275
+ let last_phase = "other";
276
+ let ci = 0;
277
+ for (let i = 0; i + 1 < ticks.length; i++) {
278
+ const a = ticks[i];
279
+ const b = ticks[i + 1];
280
+ if (b - a <= 0) continue;
281
+ const mid = (a + b) / 2;
282
+ while (ci < cpu.length && cpu[ci].t1 <= a) ci++;
283
+ const cpu_here = ci < cpu.length && cpu[ci].t0 <= mid && mid < cpu[ci].t1 ? cpu[ci] : null;
284
+ const all_active = waits.filter((w) => w.t0 <= mid && mid < w.t1);
285
+ const on_cpu = cpu_here && !(cpu_here.owner.category === "profiler" && all_active.length) ? cpu_here : null;
286
+ const open_spans = all_active.filter((w) => w.call.kind === "span");
287
+ const inner = open_spans.length ? open_spans.reduce((m, w) => w.call.ms < m.call.ms ? w : m, open_spans[0]) : null;
288
+ let seg;
289
+ if (on_cpu) {
290
+ last_phase = on_cpu.phase;
291
+ seg = {
292
+ t0: a,
293
+ t1: b,
294
+ kind: "cpu",
295
+ label: on_cpu.owner.label,
296
+ detail: on_cpu.owner.detail,
297
+ file: on_cpu.owner.file,
298
+ category: on_cpu.owner.category,
299
+ phase: on_cpu.phase,
300
+ ...on_cpu.lane ? { lane: on_cpu.lane } : {},
301
+ ...inner ? { within: inner.call.label } : {}
302
+ };
303
+ } else {
304
+ const active = all_active.filter((w) => w.call.kind !== "span");
305
+ if (active.length) {
306
+ const calls = active.map((w) => ({
307
+ label: w.call.label,
308
+ ms: round2(w.call.ms),
309
+ caller: w.call.caller,
310
+ ...w.call.callers && w.call.callers.length > 1 ? { callers: w.call.callers } : {},
311
+ ...w.call.timings ? { timings: w.call.timings } : {},
312
+ ...w.call.body_ms ? { body_ms: w.call.body_ms } : {}
313
+ }));
314
+ const owner = active.reduce((m, w) => w.call.ms > m.call.ms ? w : m, active[0]);
315
+ seg = {
316
+ t0: a,
317
+ t1: b,
318
+ kind: "wait",
319
+ label: calls.length === 1 ? calls[0].label : `${calls.length} calls in parallel`,
320
+ category: "idle",
321
+ phase: owner.call.phase ?? inner?.call.phase ?? last_phase,
322
+ calls,
323
+ ...inner ? { within: inner.call.label } : {}
324
+ };
325
+ } else if (inner) seg = {
326
+ t0: a,
327
+ t1: b,
328
+ kind: "wait",
329
+ label: `in ${inner.call.label}`,
330
+ category: "idle",
331
+ phase: inner.call.phase ?? last_phase,
332
+ calls: [{
333
+ label: inner.call.label,
334
+ ms: round2(inner.call.ms),
335
+ caller: inner.call.caller
336
+ }],
337
+ within: inner.call.label
338
+ };
339
+ else {
340
+ const pend = (input.pending ?? []).filter((p) => p.start - w0 <= mid && p.end - w0 >= mid).map((p) => p.label);
341
+ const uniq = [...new Set(pend)];
342
+ seg = {
343
+ t0: a,
344
+ t1: b,
345
+ kind: "gap",
346
+ label: uniq.length ? `nothing recorded — pending: ${uniq[0]}${uniq.length > 1 ? ` (+${uniq.length - 1})` : ""}` : "nothing recorded",
347
+ category: "unknown",
348
+ phase: last_phase,
349
+ ...uniq.length ? { pending: uniq.slice(0, 6) } : {}
350
+ };
351
+ }
352
+ }
353
+ const prev = segments[segments.length - 1];
354
+ if (prev && prev.kind === seg.kind && prev.label === seg.label && prev.phase === seg.phase && prev.detail === seg.detail && prev.within === seg.within && prev.lane === seg.lane && (prev.pending?.join() ?? "") === (seg.pending?.join() ?? "") && call_id(prev) === call_id(seg)) prev.t1 = b;
355
+ else segments.push(seg);
356
+ }
357
+ for (const s of segments) {
358
+ s.t0 = round2(s.t0);
359
+ s.t1 = round2(s.t1);
360
+ }
361
+ for (let i = segments.length - 1; i >= 0; i--) if (segments[i].t1 <= segments[i].t0 && segments.length > 1) segments.splice(i, 1);
362
+ let cpu_ms = 0, wait_ms = 0, gap_ms = 0;
363
+ const phase_map = /* @__PURE__ */ new Map();
364
+ const phase_row = (p) => {
365
+ let r = phase_map.get(p);
366
+ if (!r) phase_map.set(p, r = {
367
+ phase: p,
368
+ cpu_ms: 0,
369
+ wait_ms: 0
370
+ });
371
+ return r;
372
+ };
373
+ /** phase → owner label → CPU ms: who a phase's CPU was */
374
+ const phase_owners = /* @__PURE__ */ new Map();
375
+ let overhead_ms = 0;
376
+ for (const s of segments) {
377
+ const len = s.t1 - s.t0;
378
+ if (s.kind === "cpu" && s.category === "profiler") overhead_ms += len;
379
+ else if (s.kind === "cpu") {
380
+ cpu_ms += len;
381
+ phase_row(s.phase).cpu_ms += len;
382
+ let m = phase_owners.get(s.phase);
383
+ if (!m) phase_owners.set(s.phase, m = /* @__PURE__ */ new Map());
384
+ const open = s.label.lastIndexOf(" (");
385
+ const who = s.category === "dependency" && open !== -1 && s.label.endsWith(")") ? s.label.slice(open + 2, -1) : s.category === "node" ? "node core" : s.label;
386
+ m.set(who, (m.get(who) ?? 0) + len);
387
+ } else if (s.kind === "wait") {
388
+ wait_ms += len;
389
+ phase_row(s.phase).wait_ms += len;
390
+ } else gap_ms += len;
391
+ }
392
+ const phases = [...phase_map.values()].map((r) => {
393
+ const top = [...phase_owners.get(r.phase) ?? []].filter(([, ms]) => ms >= r.cpu_ms * .1 && ms >= .5).sort((a, b) => b[1] - a[1]).slice(0, 3).map(([label, ms]) => ({
394
+ label,
395
+ ms: round2(ms)
396
+ }));
397
+ return {
398
+ phase: r.phase,
399
+ cpu_ms: round2(r.cpu_ms),
400
+ wait_ms: round2(r.wait_ms),
401
+ ...top.length ? { top } : {}
402
+ };
403
+ }).sort((x, y) => y.cpu_ms + y.wait_ms - (x.cpu_ms + x.wait_ms));
404
+ const timeline = {
405
+ window_ms: round2(window_ms),
406
+ cpu_ms: round2(cpu_ms),
407
+ wait_ms: round2(wait_ms),
408
+ gap_ms: round2(gap_ms),
409
+ overhead_ms: round2(overhead_ms),
410
+ segments,
411
+ phases,
412
+ parallelizable: []
413
+ };
414
+ timeline.parallelizable = find_parallelizable(timeline);
415
+ timeline.awaits = await_graph(input, w0, w1);
416
+ const inner = {};
417
+ for (const [owner, m] of inner_ms) {
418
+ const list = [...m].map(([k, ms]) => {
419
+ const z = k.indexOf("\0");
420
+ return {
421
+ name: k.slice(0, z),
422
+ file: k.slice(z + 1),
423
+ ms: round2(ms)
424
+ };
425
+ }).filter((f) => f.ms > 0).sort((a, b) => b.ms - a.ms).slice(0, 12);
426
+ if (list.some((f) => f.name !== owner)) inner[owner] = list;
427
+ }
428
+ if (Object.keys(inner).length) timeline.inner = inner;
429
+ for (const [file, list] of lane_waits) {
430
+ list.sort((x, y) => x[0] - y[0]);
431
+ let total = 0;
432
+ let cur = null;
433
+ for (const [a, b] of list) if (cur && a <= cur[1]) cur[1] = Math.max(cur[1], b);
434
+ else {
435
+ if (cur) total += cur[1] - cur[0];
436
+ cur = [a, b];
437
+ }
438
+ if (cur) total += cur[1] - cur[0];
439
+ lanes.get(file).wait_ms = total;
440
+ }
441
+ const lane_list = [...lanes.values()].filter((l) => Number.isFinite(l.t0)).map((l) => ({
442
+ ...l,
443
+ t0: round2(l.t0),
444
+ t1: round2(l.t1),
445
+ cpu_ms: round2(l.cpu_ms),
446
+ wait_ms: round2(l.wait_ms)
447
+ })).sort((x, y) => x.t0 - y.t0);
448
+ if (lane_list.length) {
449
+ timeline.lanes = lane_list;
450
+ const page = lane_list.find((l) => l.level === "page");
451
+ const layout = lane_list.filter((l) => l.level === "layout" && l.kind === page?.kind && l.t1 - l.t0 >= 2 && (l.wait_ms > 0 || l.cpu_ms >= 2)).sort((x, y) => y.t1 - x.t1)[0];
452
+ if (page && layout && (page.awaited_parent || page.t0 >= layout.t1 - .25)) timeline.chain = {
453
+ layout: layout.file,
454
+ page: page.file,
455
+ serial_ms: round2(Math.max(0, Math.min(layout.t1, page.t0) - layout.t0)),
456
+ explicit: page.awaited_parent
457
+ };
458
+ }
459
+ return timeline;
460
+ }
461
+ /**
462
+ * THE CAUSALITY GRAPH: every call (net + I/O primitives, not spans) as a box on the render's
463
+ * clock, laid into lanes so overlapping calls sit on different rows, and an edge from a call to
464
+ * the one that started right after it finished while nothing else was in flight — that start
465
+ * waited for that end, and `at` is the line that did the waiting. Edges are what the
466
+ * "awaits in a row" callouts are made of; the graph shows them with the exact await site.
467
+ */
468
+ function await_graph(input, w0, w1) {
469
+ const calls = input.calls.filter((c) => c.kind !== "span" && c.ms >= 0 && c.start <= w1 && c.start + c.ms >= w0).map((c) => ({
470
+ c,
471
+ t0: round2(Math.max(c.start, w0) - w0),
472
+ t1: round2(Math.min(c.start + c.ms, w1) - w0)
473
+ })).sort((x, y) => x.t0 - y.t0 || x.t1 - y.t1).slice(0, 200);
474
+ const lane_end = [];
475
+ const nodes = calls.map(({ c, t0, t1 }) => {
476
+ let lane = lane_end.findIndex((e) => e <= t0);
477
+ if (lane === -1) lane = lane_end.push(0) - 1;
478
+ lane_end[lane] = t1;
479
+ return {
480
+ label: c.label,
481
+ t0,
482
+ t1,
483
+ lane,
484
+ kind: c.kind,
485
+ ...c.caller ? { caller: c.caller } : {}
486
+ };
487
+ });
488
+ const edges = [];
489
+ for (let i = 0; i < calls.length; i++) {
490
+ const cur = calls[i];
491
+ let prev = null;
492
+ for (let j = 0; j < i; j++) {
493
+ const p = calls[j];
494
+ if (p.t1 <= cur.t0 + .05 && cur.t0 - p.t1 <= 5 && (!prev || p.t1 > prev.t1)) prev = p;
495
+ }
496
+ if (!prev) continue;
497
+ if (calls.some((o, j) => j !== i && o !== prev && o.t0 < cur.t0 - .05 && o.t1 > cur.t0 + .05)) continue;
498
+ edges.push({
499
+ from: prev.c.label,
500
+ to: cur.c.label,
501
+ gap_ms: round2(Math.max(0, cur.t0 - prev.t1)),
502
+ ...cur.c.caller ? { at: cur.c.caller } : {},
503
+ ...cur.c.callers && cur.c.callers.length > 1 ? { callers: cur.c.callers } : {}
504
+ });
505
+ }
506
+ return {
507
+ nodes,
508
+ edges
509
+ };
510
+ }
511
+ /** A wait's call, told apart from another call of the same label by its whole duration (the label
512
+ * groups by route — `GET /api?name=a` and `?name=b` read the same). A segment that is not one
513
+ * call's wait is its label. */
514
+ function call_id(s) {
515
+ return s.kind === "wait" && s.calls?.length === 1 ? `${s.label}\0${s.calls[0].ms}` : s.label;
516
+ }
517
+ /**
518
+ * Awaits in a row: consecutive single-call waits on DIFFERENT calls with little CPU between them —
519
+ * 5 ms, or 5% of the chain's longest wait when that is more. Read off the COALESCED view
520
+ * (`coalesce`), where a wait chopped by microtask slivers is one block again — on the raw segments
521
+ * the same call reappearing after a sliver broke every chain. `at` indexes that view (the same one
522
+ * the UI draws).
523
+ *
524
+ * (The allowance scales: between a 1 s timer and a 700 ms call, the CPU of the server answering the
525
+ * page's own call to itself, or a collection, ran 3 to 9 ms run by run — the fixed 5 ms broke the
526
+ * chain in some runs and not others, and a 2.2 s finding came and went between two profiles of the
527
+ * same code. Next to waits that long, tens of ms of CPU are still nothing between.)
528
+ */
529
+ function find_parallelizable(t) {
530
+ const view = coalesce(t);
531
+ const parallelizable = [];
532
+ let group = null;
533
+ const limit = () => group ? Math.max(5, Math.max(...group.lens) * .05) : 5;
534
+ const flush = () => {
535
+ if (group && group.at.length >= 2) {
536
+ const sum = group.lens.reduce((a, c) => a + c, 0);
537
+ const max = Math.max(...group.lens);
538
+ if (sum - max >= 1) parallelizable.push({
539
+ calls: group.labels,
540
+ ms: round2(sum),
541
+ save_ms: round2(sum - max),
542
+ at: group.at
543
+ });
544
+ }
545
+ group = null;
546
+ };
547
+ let cpu_between = 0;
548
+ for (let i = 0; i < view.length; i++) {
549
+ const s = view[i];
550
+ if (s.kind === "wait" && s.calls && s.calls.length === 1) {
551
+ const label = s.calls[0].label;
552
+ const id = call_id(s);
553
+ if (group && group.ids[group.ids.length - 1] === id && cpu_between <= limit()) {
554
+ group.lens[group.lens.length - 1] += s.t1 - s.t0;
555
+ cpu_between = 0;
556
+ continue;
557
+ }
558
+ if (group && (cpu_between > limit() || group.ids.includes(id))) flush();
559
+ if (!group) group = {
560
+ at: [],
561
+ labels: [],
562
+ ids: [],
563
+ lens: []
564
+ };
565
+ group.at.push(i);
566
+ group.labels.push(label);
567
+ group.ids.push(id);
568
+ group.lens.push(s.t1 - s.t0);
569
+ cpu_between = 0;
570
+ } else if (s.kind === "cpu") {
571
+ cpu_between += s.t1 - s.t0;
572
+ if (cpu_between > limit()) flush();
573
+ } else if (s.kind === "wait") flush();
574
+ }
575
+ flush();
576
+ return parallelizable;
577
+ }
578
+ function coalesce(t, min_ms = Math.max(t.window_ms * .012, 1)) {
579
+ const out = [];
580
+ let small = [];
581
+ const len = (s) => s.t1 - s.t0;
582
+ const same = (a, b) => a.kind === b.kind && a.label === b.label && a.within === b.within && call_id(a) === call_id(b);
583
+ const fold = (list) => {
584
+ const by = /* @__PURE__ */ new Map();
585
+ const kind_ms = {
586
+ cpu: 0,
587
+ wait: 0,
588
+ gap: 0
589
+ };
590
+ for (const s of list) {
591
+ const k = s.kind + "\0" + s.label;
592
+ const e = by.get(k) ?? {
593
+ label: s.label,
594
+ kind: s.kind,
595
+ ms: 0,
596
+ seg: s
597
+ };
598
+ e.ms += len(s);
599
+ by.set(k, e);
600
+ kind_ms[s.kind] += len(s);
601
+ }
602
+ const inside = [...by.values()].sort((a, b) => b.ms - a.ms);
603
+ const kind = Object.entries(kind_ms).sort((a, b) => b[1] - a[1])[0][0];
604
+ const top = inside.find((i) => i.kind === kind) ?? inside[0];
605
+ const phases = /* @__PURE__ */ new Map();
606
+ for (const s of list) phases.set(s.phase, (phases.get(s.phase) ?? 0) + len(s));
607
+ const phase = [...phases.entries()].sort((a, b) => b[1] - a[1])[0][0];
608
+ const withins = /* @__PURE__ */ new Map();
609
+ for (const s of list) withins.set(s.within, (withins.get(s.within) ?? 0) + len(s));
610
+ const within = [...withins.entries()].sort((a, b) => b[1] - a[1])[0][0];
611
+ const others = inside.filter((i) => i.kind === kind).length - 1;
612
+ return {
613
+ t0: list[0].t0,
614
+ t1: list[list.length - 1].t1,
615
+ kind,
616
+ label: list.length === 1 ? top.label : `mostly ${top.label}${others > 0 ? ` (+${others} more)` : ""}`,
617
+ detail: list.length === 1 ? top.seg.detail : void 0,
618
+ file: top.seg.file,
619
+ category: top.seg.category,
620
+ phase,
621
+ calls: kind === "wait" ? top.seg.calls : void 0,
622
+ within,
623
+ parts: list.length,
624
+ inside: list.length === 1 ? void 0 : inside.map(({ label, kind, ms }) => ({
625
+ label,
626
+ kind,
627
+ ms: Math.round(ms * 100) / 100
628
+ }))
629
+ };
630
+ };
631
+ const flush_small = () => {
632
+ if (!small.length) return;
633
+ const total = small.reduce((s, x) => s + len(x), 0);
634
+ const last = out[out.length - 1];
635
+ if (last && total < min_ms && small.every((s) => s.kind !== "wait" || same(s, last))) {
636
+ last.t1 = small[small.length - 1].t1;
637
+ last.parts += small.length;
638
+ } else out.push(fold(small));
639
+ small = [];
640
+ };
641
+ for (const s of t.segments) if (len(s) >= min_ms) {
642
+ const last = out[out.length - 1];
643
+ if (last && same(s, last) && small.every((x) => len(x) < min_ms)) {
644
+ last.t1 = s.t1;
645
+ last.parts += small.length + 1;
646
+ small = [];
647
+ continue;
648
+ }
649
+ const total = small.reduce((a, x) => a + len(x), 0);
650
+ if (small.length && !last && total < min_ms) {
651
+ out.push({
652
+ ...s,
653
+ t0: small[0].t0,
654
+ parts: small.length + 1
655
+ });
656
+ small = [];
657
+ continue;
658
+ }
659
+ flush_small();
660
+ out.push({
661
+ ...s,
662
+ parts: 1
663
+ });
664
+ } else small.push(s);
665
+ flush_small();
666
+ return out;
667
+ }
668
+ /** A net call's timeline label: `GET host/path` (query dropped, long paths cut). */
669
+ function label_call(method, url) {
670
+ try {
671
+ const u = new URL(url);
672
+ const p = u.pathname.length > 40 ? u.pathname.slice(0, 37) + "…" : u.pathname;
673
+ return `${method} ${u.host}${p}`;
674
+ } catch {
675
+ return `${method} ${url.length > 48 ? url.slice(0, 45) + "…" : url}`;
676
+ }
677
+ }
678
+ /** The steps worth listing: every coalesced segment at or above `min_pct` of the window, in time
679
+ * order (`i` indexes the coalesced list). */
680
+ function chain_steps(t, min_pct = 2, max = 14) {
681
+ const rows = coalesce(t).map((seg, i) => ({
682
+ i,
683
+ seg,
684
+ ms: round2(seg.t1 - seg.t0),
685
+ pct: (seg.t1 - seg.t0) / t.window_ms * 100
686
+ })).filter((r) => r.pct >= min_pct);
687
+ if (rows.length <= max) return rows;
688
+ return rows.sort((a, b) => b.ms - a.ms).slice(0, max).sort((a, b) => a.i - b.i);
689
+ }
690
+ export { PHASE_LABEL, await_graph, build_timeline, chain_steps, coalesce, find_parallelizable, label_call, load_lane_of, owner_of_stack, phase_of_frame, phase_of_stack };