@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,371 @@
1
+ <script>/**
2
+ * The network waterfall — an island (`wake:'load'`). Each bar shows its response size; clicking a
3
+ * bar opens a side panel with the full request detail (trigger, sizes, timing, headers, payload).
4
+ * Replaces the old expand-in-place request list. No `$effect`: the panel is pure `selected` state
5
+ * toggled by click / Escape (see reactivity-dom-preferences). Styles live in style.ts (the shipped
6
+ * profiler UI uses one global sheet, not scoped CSS — see style.ts).
7
+ */
8
+ import { fmt_ms, fmt_bytes } from "./format.js";
9
+ let { rows } = $props();
10
+ let selected = $state(null);
11
+ </script>
12
+
13
+ <svelte:window
14
+ onkeydown={(e) => {
15
+ if (e.key === 'Escape') selected = null;
16
+ }}
17
+ />
18
+
19
+ <div class="wf">
20
+ <div class="wf-track">
21
+ {#each rows as r}
22
+ <div class="wf-row">
23
+ <button
24
+ type="button"
25
+ class="wf-bar"
26
+ class:err={r.err}
27
+ class:sel={selected === r.call}
28
+ style="left:{r.left}%;width:{r.width}%"
29
+ title={r.title}
30
+ onclick={() => (selected = r.call)}
31
+ >
32
+ {#if r.bodyPct > 5}<span class="body" style="width:{r.bodyPct}%"></span>{/if}
33
+ {#if r.sizeLabel}<span class="wf-size">{r.sizeLabel}</span>{/if}
34
+ </button>
35
+ <span
36
+ class="wf-label"
37
+ style={r.rightAnchored
38
+ ? `right:calc(${(100 - r.left).toFixed(1)}% + 6px)`
39
+ : `left:calc(${(r.left + r.width).toFixed(1)}% + 6px)`}>{r.label}</span
40
+ >
41
+ </div>
42
+ {/each}
43
+ </div>
44
+ </div>
45
+
46
+ {#if selected}
47
+ {@const c = selected}
48
+ <!-- Non-modal side panel — no full-screen dimming backdrop (it caused a compositing repaint glitch,
49
+ and blocked seeing the waterfall). Close with ✕ or Escape; click another bar to switch. -->
50
+ <div class="wf-drawer" role="dialog" aria-label="Request detail">
51
+ <div class="wf-drawer-head">
52
+ <span class="rm">{c.method}</span>
53
+ <span class="ru" title={c.url}>{c.url}</span>
54
+ <span class="rs" class:warn={c.error}>{c.error ? 'ERR' : c.status || '—'}</span>
55
+ <button type="button" class="wf-close" aria-label="Close" onclick={() => (selected = null)}
56
+ >✕</button
57
+ >
58
+ </div>
59
+ <div class="wf-drawer-body">
60
+ <dl>
61
+ <dt>Size</dt>
62
+ <dd>
63
+ {#if c.bytes != null}<b>{fmt_bytes(c.bytes)}</b> decoded{/if}{#if c.transfer_bytes != null && c.transfer_bytes !== c.bytes}{#if c.bytes != null},
64
+ {/if}{fmt_bytes(c.transfer_bytes)} on the wire{#if c.encoding}
65
+ <span class="dim">({c.encoding})</span
66
+ >{/if}{/if}{#if c.bytes == null && c.transfer_bytes == null}—{/if}
67
+ </dd>
68
+ <dt>Timing</dt>
69
+ <dd>
70
+ {fmt_ms(c.ms)} to headers{#if c.body_ms}
71
+ · {fmt_ms(c.body_ms)} reading the body{/if}
72
+ </dd>
73
+ <dt>Response</dt>
74
+ <dd>
75
+ {c.error ? 'error' : c.status || '—'}{#if c.type}
76
+ · {c.type}{/if}{#if c.error}
77
+ · {c.error}{/if}
78
+ </dd>
79
+ {#if c.route ?? c.path}<dt>Triggered by</dt>
80
+ <dd class="brk">{c.route ?? c.path}</dd>{/if}
81
+ {#if c.caller}<dt>Caller</dt>
82
+ <dd class="brk">{c.caller}</dd>{/if}
83
+ {#if c.callers && c.callers.length > 1}<dt>Call path</dt>
84
+ <dd class="brk">
85
+ {#each c.callers as k, i (i)}{#if i > 0}<span class="dim"> ← </span>{/if}{k}{/each}
86
+ </dd>{/if}
87
+ {#if c.timings?.length}
88
+ {@const theirs = c.timings.reduce((a, t) => a + t.ms, 0)}
89
+ <dt>Their side</dt>
90
+ <dd>
91
+ {#each c.timings as t, i (t.name + '\0' + i)}<span class="st"
92
+ ><b>{t.desc ?? t.name}</b> {fmt_ms(t.ms)} ms</span
93
+ >{/each}
94
+ {#if theirs > 0}<span class="dim"
95
+ >— {fmt_ms(theirs)} ms measured on their side, {fmt_ms(Math.max(0, c.ms - theirs))} ms
96
+ network + framework (from their Server-Timing)</span
97
+ >{/if}
98
+ </dd>
99
+ {/if}
100
+ {#if c.trace}
101
+ {@const t = c.trace}
102
+ {@const rest = Math.max(0, t.ms - t.cpu_ms - t.wait_ms)}
103
+ <dt>Inside the upstream</dt>
104
+ <dd>
105
+ <span class="st"
106
+ ><b>{fmt_ms(t.ms)} ms</b> on their profiler's clock{#if t.route}
107
+ · route <code>{t.route}</code>{/if}</span
108
+ >
109
+ <span
110
+ class="trace-bar"
111
+ title="{fmt_ms(t.cpu_ms)} ms CPU · {fmt_ms(
112
+ t.wait_ms
113
+ )} ms waiting on {t.calls} call{t.calls === 1 ? '' : 's'} · {fmt_ms(rest)} ms other"
114
+ >
115
+ <i class="tb-cpu" style="width:{(t.cpu_ms / Math.max(t.ms, 0.01)) * 100}%"></i><i
116
+ class="tb-wait"
117
+ style="width:{(t.wait_ms / Math.max(t.ms, 0.01)) * 100}%"
118
+ ></i><i class="tb-rest" style="width:{(rest / Math.max(t.ms, 0.01)) * 100}%"></i>
119
+ </span>
120
+ <span class="dim"
121
+ >{fmt_ms(t.cpu_ms)} ms CPU · {fmt_ms(t.wait_ms)} ms waiting on {t.calls} call{t.calls ===
122
+ 1
123
+ ? ''
124
+ : 's'} · {fmt_ms(rest)} ms other · {fmt_ms(Math.max(0, c.ms - t.ms))} ms network between
125
+ us</span
126
+ >
127
+ {#if t.top?.length}
128
+ <span class="dim"
129
+ >their own calls: {#each t.top as u, i (i)}{#if i > 0},
130
+ {/if}{u.url.replace(/^https?:\/\/[^/]+/, '')}
131
+ {fmt_ms(u.ms)} ms{/each}</span
132
+ >
133
+ {/if}
134
+ {#if t.profiler}<span class="dim">their profiler: <code>{t.profiler}</code></span>{/if}
135
+ </dd>
136
+ {/if}
137
+ </dl>
138
+
139
+ <h4>URL</h4>
140
+ <pre class="wf-url">{c.url}</pre>
141
+
142
+ {#if c.headers && Object.keys(c.headers).length}
143
+ <h4>Response headers</h4>
144
+ <dl class="hdrs">
145
+ {#each Object.entries(c.headers) as [k, v]}
146
+ <dt>{k}</dt>
147
+ <dd class="brk">{v}</dd>
148
+ {/each}
149
+ </dl>
150
+ {/if}
151
+ </div>
152
+ </div>
153
+ {/if}
154
+
155
+ <style>
156
+ /* The shell scrolls; the track keeps a usable minimum width so a narrow viewport (or the open
157
+ drawer) pans the timeline instead of crushing the % -positioned bars into unreadability. */
158
+ .wf {
159
+ background: var(--bg-sunken);
160
+ border: 1px solid var(--line);
161
+ border-radius: 8px;
162
+ margin: 8px 0;
163
+ overflow-x: auto;
164
+ }
165
+ .wf-track {
166
+ position: relative;
167
+ min-width: 640px;
168
+ padding: 8px 0;
169
+ }
170
+ .wf-row {
171
+ position: relative;
172
+ height: 22px;
173
+ }
174
+ .wf-bar {
175
+ position: absolute;
176
+ height: 16px;
177
+ top: 3px;
178
+ border: 0;
179
+ padding: 0;
180
+ border-radius: 3px;
181
+ background: var(--c-blue);
182
+ min-width: 2px;
183
+ cursor: pointer;
184
+ overflow: hidden;
185
+ display: block;
186
+ transition: filter 0.1s;
187
+ }
188
+ .wf-bar:hover {
189
+ filter: brightness(1.18);
190
+ }
191
+ .wf-bar.sel {
192
+ box-shadow:
193
+ 0 0 0 2px var(--c-blue),
194
+ 0 0 0 4px var(--bg-sunken);
195
+ }
196
+ .wf-bar.err {
197
+ background: var(--bad);
198
+ }
199
+ .wf-bar .body {
200
+ position: absolute;
201
+ right: 0;
202
+ top: 0;
203
+ height: 100%;
204
+ background: #3a5d8f;
205
+ }
206
+ .wf-size {
207
+ position: absolute;
208
+ left: 5px;
209
+ top: 0;
210
+ height: 100%;
211
+ display: flex;
212
+ align-items: center;
213
+ font:
214
+ 10px ui-monospace,
215
+ monospace;
216
+ /* on the blue bar: the theme's ink for its accent fills (4.5:1 in both themes) */
217
+ color: var(--text-on-accent);
218
+ font-variant-numeric: tabular-nums;
219
+ white-space: nowrap;
220
+ pointer-events: none;
221
+ text-shadow: 0 1px 1px rgba(0, 0, 0, 0.35);
222
+ }
223
+ .wf-label {
224
+ position: absolute;
225
+ font:
226
+ 11px ui-monospace,
227
+ monospace;
228
+ color: var(--text-dim);
229
+ top: 4px;
230
+ white-space: nowrap;
231
+ pointer-events: none;
232
+ }
233
+ .st {
234
+ display: inline-block;
235
+ margin-right: 10px;
236
+ }
237
+ .dim {
238
+ color: #5c636e;
239
+ }
240
+ /* Request side panel — slides in when a waterfall bar is clicked. */
241
+ .wf-drawer {
242
+ position: fixed;
243
+ top: 0;
244
+ right: 0;
245
+ bottom: 0;
246
+ z-index: 40;
247
+ width: min(460px, 92vw);
248
+ background: var(--bg-sunken);
249
+ border-left: 1px solid var(--line);
250
+ box-shadow: -12px 0 32px rgba(0, 0, 0, 0.4);
251
+ display: flex;
252
+ flex-direction: column;
253
+ }
254
+ .wf-drawer-head {
255
+ display: flex;
256
+ align-items: center;
257
+ gap: 10px;
258
+ padding: 12px 14px;
259
+ border-bottom: 1px solid #1c222c;
260
+ }
261
+ .wf-drawer-head .rm {
262
+ font-family: ui-monospace, monospace;
263
+ color: var(--text-dim);
264
+ font-weight: 600;
265
+ }
266
+ .wf-drawer-head .ru {
267
+ font-family: ui-monospace, monospace;
268
+ color: var(--text-faint);
269
+ font-size: 12px;
270
+ overflow: hidden;
271
+ text-overflow: ellipsis;
272
+ white-space: nowrap;
273
+ flex: 1;
274
+ min-width: 0;
275
+ }
276
+ .wf-drawer-head .rs {
277
+ color: #8b93a0;
278
+ font-variant-numeric: tabular-nums;
279
+ font-size: 12.5px;
280
+ }
281
+ .wf-close {
282
+ margin-left: auto;
283
+ border: 0;
284
+ background: transparent;
285
+ color: var(--text-faint);
286
+ font-size: 15px;
287
+ cursor: pointer;
288
+ padding: 2px 7px;
289
+ border-radius: 5px;
290
+ line-height: 1;
291
+ }
292
+ .wf-close:hover {
293
+ background: var(--bg-hover);
294
+ color: var(--text);
295
+ }
296
+ .wf-drawer-body {
297
+ padding: 12px 14px 20px;
298
+ overflow: auto;
299
+ }
300
+ .wf-drawer-body h4 {
301
+ font-size: 11px;
302
+ color: var(--text-faint);
303
+ margin: 16px 0 5px;
304
+ font-weight: 600;
305
+ text-transform: uppercase;
306
+ letter-spacing: 0.04em;
307
+ }
308
+ .wf-drawer-body dl {
309
+ display: grid;
310
+ grid-template-columns: 96px minmax(0, 1fr);
311
+ gap: 4px 12px;
312
+ margin: 0;
313
+ font-size: 12.5px;
314
+ }
315
+ .wf-drawer-body dl.hdrs {
316
+ grid-template-columns: minmax(0, 42%) minmax(0, 1fr);
317
+ font-family: ui-monospace, monospace;
318
+ font-size: 12px;
319
+ gap: 3px 12px;
320
+ }
321
+ .wf-drawer-body dt {
322
+ color: var(--text-faint);
323
+ }
324
+ .wf-drawer-body dd {
325
+ color: var(--text);
326
+ margin: 0;
327
+ min-width: 0;
328
+ }
329
+ .wf-drawer-body dd.brk {
330
+ font-family: ui-monospace, monospace;
331
+ font-size: 12px;
332
+ word-break: break-all;
333
+ }
334
+ .wf-url {
335
+ margin: 4px 0 0;
336
+ padding: 8px 10px;
337
+ background: var(--bg-sunken);
338
+ border: 1px solid #1c222c;
339
+ border-radius: 6px;
340
+ font:
341
+ 12px ui-monospace,
342
+ monospace;
343
+ color: var(--text-dim);
344
+ word-break: break-all;
345
+ white-space: pre-wrap;
346
+ }
347
+ /* the nested trace: the upstream's own request as a three-part bar */
348
+ .trace-bar {
349
+ display: flex;
350
+ height: 10px;
351
+ width: 100%;
352
+ max-width: 420px;
353
+ background: var(--bg-sunken);
354
+ border-radius: 3px;
355
+ overflow: hidden;
356
+ margin: 4px 0;
357
+ }
358
+ .trace-bar i {
359
+ display: block;
360
+ height: 100%;
361
+ }
362
+ .tb-cpu {
363
+ background: var(--c-orange);
364
+ }
365
+ .tb-wait {
366
+ background: var(--c-blue);
367
+ }
368
+ .tb-rest {
369
+ background: #374151;
370
+ }
371
+ </style>
@@ -0,0 +1,7 @@
1
+ import type { WfRow } from './report-data.js';
2
+ type $$ComponentProps = {
3
+ rows: WfRow[];
4
+ };
5
+ declare const Waterfall: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type Waterfall = ReturnType<typeof Waterfall>;
7
+ export default Waterfall;
@@ -0,0 +1,87 @@
1
+ <script>// PICK YOUR FIXES: the forecast's parts as a checklist — tick the ones you will make, and the
2
+ // render after them is counted the forecast's way (each saving once, each resource capped by
3
+ // what the render used), right here in the browser. A fix another covers comes with it.
4
+ import { saving_of } from "../saving.js";
5
+ import { fmt_ms } from "./format.js";
6
+ let { parts, now_ms, caps } = $props();
7
+ // all ticked at first: the forecast's own number
8
+ // svelte-ignore state_referenced_locally
9
+ let on = $state(parts.map(() => true));
10
+ const picked = $derived(parts.filter((_, i) => on[i]));
11
+ const result = $derived(saving_of(picked, caps ?? {}, now_ms));
12
+ const pct = $derived(now_ms > 0 ? Math.round((now_ms - result.after) / now_ms * 100) : 0);
13
+ </script>
14
+
15
+ <div class="whatif">
16
+ <p class="sum">
17
+ With the <b>{picked.length}</b> fix{picked.length === 1 ? '' : 'es'} ticked: about
18
+ <b>{fmt_ms(result.after)} ms</b>
19
+ a render
20
+ <span class="hint">(from {fmt_ms(now_ms)} ms, −{pct}%)</span>
21
+ {#if result.clamped}<span class="hint"
22
+ >· they claim more than the render used: held at what it used</span
23
+ >{/if}
24
+ </p>
25
+ <ul>
26
+ {#each parts as p, i}
27
+ <li>
28
+ <label>
29
+ <input type="checkbox" bind:checked={on[i]} />
30
+ <span class="t">{p.title}</span>
31
+ <span class="ms"
32
+ >{p.wait ? 'waiting' : p.kind === 'unread-work' ? 'deleted' : 'CPU'} ~{fmt_ms(p.ms)} ms</span
33
+ >
34
+ </label>
35
+ {#if p.with?.length}<span class="hint covers">also covers {p.with.join('; ')}</span>{/if}
36
+ </li>
37
+ {/each}
38
+ </ul>
39
+ <p class="hint">
40
+ <button type="button" onclick={() => (on = parts.map(() => true))}>Tick all</button>
41
+ <button type="button" onclick={() => (on = parts.map(() => false))}>Clear</button>
42
+ </p>
43
+ </div>
44
+
45
+ <style>
46
+ .whatif {
47
+ margin: 8px 0 12px;
48
+ }
49
+ .sum {
50
+ margin: 0 0 6px;
51
+ }
52
+ ul {
53
+ list-style: none;
54
+ margin: 0;
55
+ padding: 0;
56
+ display: grid;
57
+ gap: 2px;
58
+ }
59
+ li {
60
+ font-size: 13px;
61
+ }
62
+ label {
63
+ display: flex;
64
+ gap: 8px;
65
+ align-items: baseline;
66
+ cursor: pointer;
67
+ }
68
+ .t {
69
+ flex: 1;
70
+ }
71
+ .ms {
72
+ font-family: var(--font-mono, ui-monospace, monospace);
73
+ font-size: 12px;
74
+ opacity: 0.8;
75
+ white-space: nowrap;
76
+ }
77
+ .covers {
78
+ display: block;
79
+ margin-left: 24px;
80
+ font-size: 12px;
81
+ }
82
+ button {
83
+ font: inherit;
84
+ font-size: 12px;
85
+ margin-right: 6px;
86
+ }
87
+ </style>
@@ -0,0 +1,18 @@
1
+ type Part = {
2
+ title: string;
3
+ kind: string;
4
+ ms: number;
5
+ wait: boolean;
6
+ with?: string[];
7
+ };
8
+ type $$ComponentProps = {
9
+ parts: Part[];
10
+ now_ms: number;
11
+ caps?: {
12
+ wait?: number;
13
+ cpu?: number;
14
+ };
15
+ };
16
+ declare const WhatIf: import("svelte").Component<$$ComponentProps, {}, "">;
17
+ type WhatIf = ReturnType<typeof WhatIf>;
18
+ export default WhatIf;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * A Windows-style "determinate-ish" progress: crawls forward fast at first, eases toward 95% with the
3
+ * occasional burst, then snaps to 100% when the real work finishes. There is no true percentage to
4
+ * report — the profile endpoint just works until it's done — so this is a timed illusion, the standard
5
+ * "something is happening" reassurance bar. Call `start()` when the request goes out, `finish()` when
6
+ * it resolves (it awaits one paint at 100% before returning), `fail()` on error.
7
+ */
8
+ export interface FakeProgress {
9
+ readonly value: number;
10
+ readonly running: boolean;
11
+ start(): void;
12
+ finish(): Promise<void>;
13
+ fail(): void;
14
+ }
15
+ export declare function fake_progress(): FakeProgress;
@@ -0,0 +1,38 @@
1
+ // src/profiler/ui/fake-progress.svelte.ts
2
+ function fake_progress() {
3
+ let value = $state(0);
4
+ let running = $state(false);
5
+ let timer;
6
+ function tick() {
7
+ if (value >= 95) return;
8
+ const gap = 95 - value;
9
+ const burst = Math.random() < .18 ? gap * .14 : 0;
10
+ value = Math.min(95, value + gap * .045 + burst + .5);
11
+ }
12
+ return {
13
+ get value() {
14
+ return value;
15
+ },
16
+ get running() {
17
+ return running;
18
+ },
19
+ start() {
20
+ clearInterval(timer);
21
+ value = 0;
22
+ running = true;
23
+ timer = setInterval(tick, 170);
24
+ },
25
+ async finish() {
26
+ clearInterval(timer);
27
+ value = 100;
28
+ await new Promise((r) => setTimeout(r, 420));
29
+ running = false;
30
+ },
31
+ fail() {
32
+ clearInterval(timer);
33
+ running = false;
34
+ value = 0;
35
+ }
36
+ };
37
+ }
38
+ export { fake_progress };
@@ -0,0 +1,20 @@
1
+ import { FrameCategory } from "../analyze.js";
2
+ import { ReportMeta } from "../report.js";
3
+ /** ms with sensible precision: whole numbers over 100, one dp over 10, two dp below. */
4
+ export declare const fmt_ms: (n: number) => string;
5
+ /** A duration in ms, with the unit that keeps it readable: `1.20 ms`, `46 µs`, `8 ns`. For per-call
6
+ * costs, where a hot getter can cost nanoseconds a call and fixed decimals print it as zero. */
7
+ export declare const fmt_dur: (ms: number) => string;
8
+ /** part/whole as a percentage, or an em-dash when whole is 0. */
9
+ export declare const fmt_pct: (part: number, whole: number) => string;
10
+ /** bytes → MB / kB / B. */
11
+ export declare const fmt_bytes: (n: number) => string;
12
+ export declare const CATEGORY_LABEL: Record<FrameCategory, string>;
13
+ export declare const CATEGORY_COLOR: Record<FrameCategory, string>;
14
+ /** The text color for a label on `bg` (`#rrggbb`): near-black or white, whichever contrasts more
15
+ * (a chip's background is a data color, light or dark: one fixed ink failed 4.5:1 on half of them). */
16
+ export declare function ink_on(bg: string): string;
17
+ /** Bar color per I/O kind, for the Waiting-by-function bars. */
18
+ export declare function kind_color(kind: string): string;
19
+ /** A report's one-line label for lists + headers. */
20
+ export declare function label_of(r: ReportMeta): string;
@@ -0,0 +1,71 @@
1
+ // src/profiler/ui/format.ts
2
+ /** ms with sensible precision: whole numbers over 100, one dp over 10, two dp below. */
3
+ const fmt_ms = (n) => n >= 100 ? n.toFixed(0) : n >= 10 ? n.toFixed(1) : n.toFixed(2);
4
+ /** A duration in ms, with the unit that keeps it readable: `1.20 ms`, `46 µs`, `8 ns`. For per-call
5
+ * costs, where a hot getter can cost nanoseconds a call and fixed decimals print it as zero. */
6
+ const fmt_dur = (ms) => {
7
+ if (!Number.isFinite(ms) || ms <= 0) return "—";
8
+ if (ms >= 1) return `${fmt_ms(ms)} ms`;
9
+ const us = ms * 1e3;
10
+ if (us >= 1) return `${us >= 100 ? us.toFixed(0) : us >= 10 ? us.toFixed(1) : us.toFixed(2)} µs`;
11
+ const ns = us * 1e3;
12
+ return `${ns >= 10 ? ns.toFixed(0) : ns.toFixed(1)} ns`;
13
+ };
14
+ /** part/whole as a percentage, or an em-dash when whole is 0. */
15
+ const fmt_pct = (part, whole) => whole > 0 ? (part / whole * 100).toFixed(1) + "%" : "—";
16
+ /** bytes → MB / kB / B. */
17
+ const fmt_bytes = (n) => n >= 1048576 ? (n / 1048576).toFixed(1) + " MB" : n >= 1024 ? (n / 1024).toFixed(0) + " kB" : n + " B";
18
+ const CATEGORY_LABEL = {
19
+ component: "component",
20
+ app: "app code",
21
+ dependency: "dependency",
22
+ svelte: "svelte",
23
+ node: "node core",
24
+ gc: "GC",
25
+ idle: "idle",
26
+ v8: "v8",
27
+ profiler: "profiler",
28
+ unknown: "—"
29
+ };
30
+ const CATEGORY_COLOR = {
31
+ component: "#e8734a",
32
+ app: "#4a9d6e",
33
+ dependency: "#5b8fd6",
34
+ svelte: "#c1544f",
35
+ node: "#8a8f98",
36
+ gc: "#b58a3d",
37
+ idle: "#3a3f47",
38
+ v8: "#6b7280",
39
+ profiler: "#7d6bb0",
40
+ unknown: "#6b7280"
41
+ };
42
+ /** The text color for a label on `bg` (`#rrggbb`): near-black or white, whichever contrasts more
43
+ * (a chip's background is a data color, light or dark: one fixed ink failed 4.5:1 on half of them). */
44
+ function ink_on(bg) {
45
+ if (bg.length !== 7 || bg[0] !== "#") return "var(--text-on-accent)";
46
+ const ch = (i) => {
47
+ const v = parseInt(bg.slice(i, i + 2), 16) / 255;
48
+ return v <= .03928 ? v / 12.92 : ((v + .055) / 1.055) ** 2.4;
49
+ };
50
+ const L = .2126 * ch(1) + .7152 * ch(3) + .0722 * ch(5);
51
+ return 1.05 / (L + .05) >= (L + .05) / .055 ? "#ffffff" : "#06120c";
52
+ }
53
+ /** Bar color per I/O kind, for the Waiting-by-function bars. */
54
+ function kind_color(kind) {
55
+ switch (kind) {
56
+ case "http": return "#5b8fd6";
57
+ case "timer": return "#b58a3d";
58
+ case "file": return "#4a9d6e";
59
+ case "socket": return "#c1544f";
60
+ case "dns": return "#7d6bb0";
61
+ default: return "#8a8f98";
62
+ }
63
+ }
64
+ /** A report's one-line label for lists + headers. */
65
+ function label_of(r) {
66
+ if (r.trigger === "page") return `page ${r.page} ×${r.runs?.length ?? 0}`;
67
+ if (r.trigger === "request") return `request ${r.request?.path ?? ""}`;
68
+ if (r.trigger === "trap") return `caught ${r.request?.path ?? ""} (${Math.round(r.request?.ms ?? 0)} ms)`;
69
+ return `${Math.round(r.duration_ms / 1e3)}s window`;
70
+ }
71
+ export { CATEGORY_COLOR, CATEGORY_LABEL, fmt_bytes, fmt_dur, fmt_ms, fmt_pct, ink_on, kind_color, label_of };
@@ -0,0 +1,9 @@
1
+ /** Encrypt a report dump for a permalink: gzip → AES-GCM(PBKDF2(password)) → base64url. */
2
+ export declare function encode_permalink(dump: unknown, password: string): Promise<string>;
3
+ /** Reverse of {@link encode_permalink}. Throws on a wrong password / tampered blob (GCM tag fails). */
4
+ export declare function decode_permalink(blob: string, password: string): Promise<unknown>;
5
+ /** All-WebKit browsers (desktop Safari + EVERY iOS browser) hard-error past ~80 K URL chars, too small
6
+ * for a real report — so universal links are disabled there. Chromium (~2 MB) and Firefox handle it. */
7
+ export declare function is_webkit_capped(): boolean;
8
+ /** Chromium tops out at a 2 MB URL; keep a safe margin. Fragment chars ≈ blob length (all ASCII). */
9
+ export declare const MAX_PERMALINK_CHARS = 15e5;