@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,308 @@
1
+ <script>import { fmt_ms, CATEGORY_LABEL } from "./format.js";
2
+ let { f, colspan, base = "", dev = false } = $props();
3
+ const is_abs = (p) => p.startsWith("/") || /^[A-Za-z]:[\\/]/.test(p);
4
+ const location = $derived(f.path ? f.path + (f.line > 0 ? ":" + f.line + (f.col > 0 ? ":" + f.col : "") : "") : "");
5
+ // An absolute path (a dev server, a sourcemapped build on the same machine) opens in the editor.
6
+ const editor_href = $derived(f.path && is_abs(f.path) ? "vscode://file/" + f.path + (f.line > 0 ? ":" + f.line + (f.col > 0 ? ":" + f.col : "") : "") : "");
7
+ const stacks = $derived(f.stacks ?? []);
8
+ // THE HOT LINES: where inside the function the self time landed (V8's per-line ticks)
9
+ const lines = $derived(f.lines ?? []);
10
+ const lines_max = $derived(Math.max(...lines.map((l) => l.ms), .01));
11
+ const heat = $derived(new Map(lines.map((l) => [l.line, l.ms / lines_max])));
12
+ const last_hot = $derived(lines.length ? Math.max(...lines.map((l) => l.line)) : 0);
13
+ const ms_at = (n) => lines.find((l) => l.line === n)?.ms ?? 0;
14
+ let copied = $state(false);
15
+ async function copy() {
16
+ try {
17
+ await navigator.clipboard.writeText(location);
18
+ copied = true;
19
+ setTimeout(() => copied = false, 1200);
20
+ } catch {}
21
+ }
22
+ // SOURCE PEEK: the code around the hot lines. The report carries it (`f.src`, captured at report
23
+ // time from the sourcemap's embedded copy), so it shows on a deployed host, in an export and in a
24
+ // share link. Only when the report has none does the dev server read the local file.
25
+ let fetched = $state(null);
26
+ const peek = $derived(f.src ? {
27
+ start: f.src.start,
28
+ line: f.line,
29
+ lines: f.src.lines
30
+ } : fetched);
31
+ $effect(() => {
32
+ if (f.src || !dev || !base || !f.path || !is_abs(f.path) || f.line <= 0) return;
33
+ // widen the peek to the hot lines when they sit below the function's first line
34
+ const to = last_hot > f.line ? `&to=${Math.min(last_hot, f.line + 60)}` : "";
35
+ const url = `${base}/source?p=${encodeURIComponent(f.path)}&l=${f.line}${to}`;
36
+ let live = true;
37
+ fetch(url, { headers: { accept: "application/json" } }).then((r) => r.ok ? r.json() : null).then((j) => {
38
+ if (live && j && Array.isArray(j.lines)) fetched = j;
39
+ }).catch(() => {});
40
+ return () => {
41
+ live = false;
42
+ };
43
+ });
44
+ </script>
45
+
46
+ <tr class="details">
47
+ <td {colspan}>
48
+ <div class="where">
49
+ <span class="k">where</span>
50
+ {#if location}
51
+ <code class="loc">{location}</code>
52
+ <button class="mini" onclick={copy}>{copied ? 'copied' : 'copy'}</button>
53
+ {#if editor_href}<a class="mini" href={editor_href}>open in editor</a>{/if}
54
+ {:else}
55
+ <span class="hint">native — no source position</span>
56
+ {/if}
57
+ <span class="k">kind</span>
58
+ <span>{f.pkg ? `${CATEGORY_LABEL[f.category]} · ${f.pkg}` : CATEGORY_LABEL[f.category]}</span>
59
+ </div>
60
+ {#if lines.length}
61
+ <div class="hotlines">
62
+ <span class="k">hot lines</span>
63
+ {#each lines as l (l.line)}
64
+ <span class="hl" title="{fmt_ms(l.ms)} ms of self time on line {l.line}">
65
+ <i style="width:{Math.max(4, (l.ms / lines_max) * 60)}px"></i>
66
+ <code>:{l.line}</code> <span class="dim">{fmt_ms(l.ms)} ms</span>
67
+ </span>
68
+ {/each}
69
+ </div>
70
+ {/if}
71
+ {#if f.callees?.length}
72
+ <div class="callees">
73
+ <span class="k">inside it</span>
74
+ <ul>
75
+ {#if f.self_ms > 0 && f.total_ms > 0}
76
+ <li>
77
+ <i style="width:{Math.max(2, (f.self_ms / f.total_ms) * 80)}px"></i><b
78
+ >its own lines</b
79
+ ><span class="dim"
80
+ >{fmt_ms(f.self_ms)} ms · {Math.round((f.self_ms / f.total_ms) * 100)}%</span
81
+ >
82
+ </li>
83
+ {/if}
84
+ {#each f.callees as c (c.key)}
85
+ <li class:faint={c.category !== 'app' && c.category !== 'component'}>
86
+ <i style="width:{Math.max(2, c.share * 80)}px"></i>
87
+ <b>{c.name}</b>
88
+ {#if c.file}<code class="cf">{c.file}</code>{/if}
89
+ <span class="dim">{fmt_ms(c.ms)} ms · {Math.round(c.share * 100)}%</span>
90
+ </li>
91
+ {/each}
92
+ </ul>
93
+ </div>
94
+ {/if}
95
+ {#if peek}
96
+ <pre class="peek">{#each peek.lines as ln, i (i)}<span
97
+ class="ln"
98
+ class:hot={peek.start + i === peek.line}
99
+ style={heat.has(peek.start + i)
100
+ ? `background:rgba(232,115,74,${(0.12 + 0.5 * (heat.get(peek.start + i) ?? 0)).toFixed(2)})`
101
+ : ''}
102
+ ><span class="no">{peek.start + i}</span><span class="lms"
103
+ >{heat.has(peek.start + i) ? fmt_ms(ms_at(peek.start + i)) : ''}</span
104
+ >{ln}
105
+ </span>{/each}</pre>
106
+ {/if}
107
+ {#if stacks.length}
108
+ <div class="stacks">
109
+ {#each stacks as st, i (i)}
110
+ <div class="stack">
111
+ <div class="stack-h">
112
+ <b>{fmt_ms(st.ms)} ms</b> came through this path
113
+ {#if st.frames.length === 0}<span class="hint">(called at the top level)</span>{/if}
114
+ </div>
115
+ <ol>
116
+ {#each st.frames as fr, j (j)}
117
+ <li class:dim={fr.c !== 'app' && fr.c !== 'component'} title={fr.c}>
118
+ <span class="n">{fr.n}</span>{#if fr.f}<span class="f">{fr.f}</span>{/if}
119
+ </li>
120
+ {/each}
121
+ </ol>
122
+ </div>
123
+ {/each}
124
+ </div>
125
+ {:else}
126
+ <p class="hint">
127
+ No call stacks recorded for this row (an older report, or a frame with no callers).
128
+ </p>
129
+ {/if}
130
+ </td>
131
+ </tr>
132
+
133
+ <style>
134
+ tr.details td {
135
+ background: var(--bg-raised);
136
+ padding: 10px 14px 12px;
137
+ border-bottom: 1px solid var(--line);
138
+ }
139
+ .where {
140
+ display: flex;
141
+ flex-wrap: wrap;
142
+ gap: 6px 10px;
143
+ align-items: center;
144
+ font-size: 12.5px;
145
+ margin-bottom: 8px;
146
+ }
147
+ .k {
148
+ color: var(--text-faint);
149
+ font-size: 11px;
150
+ text-transform: uppercase;
151
+ letter-spacing: 0.04em;
152
+ }
153
+ .loc {
154
+ font-family: ui-monospace, monospace;
155
+ font-size: 12px;
156
+ color: var(--text);
157
+ user-select: all;
158
+ word-break: break-all;
159
+ }
160
+ .mini {
161
+ font: inherit;
162
+ font-size: 11px;
163
+ color: var(--c-blue);
164
+ background: var(--bg-hover);
165
+ border: 1px solid var(--line);
166
+ border-radius: 4px;
167
+ padding: 1px 7px;
168
+ cursor: pointer;
169
+ text-decoration: none;
170
+ }
171
+ .mini:hover {
172
+ background: var(--bg-hover);
173
+ }
174
+ .peek {
175
+ margin: 0 0 10px;
176
+ padding: 6px 0;
177
+ background: var(--bg-sunken);
178
+ border: 1px solid var(--line);
179
+ border-radius: 6px;
180
+ font-size: 11.5px;
181
+ line-height: 1.5;
182
+ overflow-x: auto;
183
+ }
184
+ .peek .ln {
185
+ display: block;
186
+ padding: 0 10px;
187
+ white-space: pre;
188
+ }
189
+ .peek .ln.hot {
190
+ background: var(--warn-deep);
191
+ }
192
+ .peek .no {
193
+ display: inline-block;
194
+ width: 3.5em;
195
+ color: var(--text-faint);
196
+ user-select: none;
197
+ }
198
+ .peek .lms {
199
+ display: inline-block;
200
+ width: 5em;
201
+ color: var(--c-orange);
202
+ font-size: 10.5px;
203
+ user-select: none;
204
+ }
205
+ .hotlines {
206
+ display: flex;
207
+ flex-wrap: wrap;
208
+ gap: 4px 14px;
209
+ align-items: center;
210
+ margin: 0 0 8px;
211
+ font-size: 12px;
212
+ }
213
+ .hl {
214
+ display: inline-flex;
215
+ align-items: center;
216
+ gap: 5px;
217
+ }
218
+ .hl i {
219
+ display: inline-block;
220
+ height: 8px;
221
+ background: var(--c-orange);
222
+ border-radius: 2px;
223
+ }
224
+ .dim {
225
+ color: var(--text-faint);
226
+ }
227
+ .callees {
228
+ display: flex;
229
+ gap: 10px;
230
+ align-items: baseline;
231
+ margin: 0 0 8px;
232
+ font-size: 12px;
233
+ }
234
+ .callees ul {
235
+ list-style: none;
236
+ margin: 0;
237
+ padding: 0;
238
+ display: grid;
239
+ gap: 2px;
240
+ min-width: 0;
241
+ }
242
+ .callees li {
243
+ display: flex;
244
+ flex-wrap: wrap;
245
+ align-items: center;
246
+ gap: 2px 8px;
247
+ }
248
+ .callees li i {
249
+ display: inline-block;
250
+ height: 8px;
251
+ background: var(--c-blue);
252
+ border-radius: 2px;
253
+ }
254
+ .callees li.faint b {
255
+ font-weight: 500;
256
+ color: var(--text-dim);
257
+ }
258
+ .cf {
259
+ font-family: ui-monospace, monospace;
260
+ font-size: 11px;
261
+ color: var(--text-faint);
262
+ word-break: break-all;
263
+ }
264
+ .stacks {
265
+ display: grid;
266
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
267
+ gap: 10px;
268
+ }
269
+ .stack {
270
+ background: var(--bg-sunken);
271
+ border: 1px solid var(--line);
272
+ border-radius: 6px;
273
+ padding: 8px 10px;
274
+ min-width: 0;
275
+ }
276
+ .stack-h {
277
+ font-size: 12px;
278
+ color: var(--text-dim);
279
+ margin-bottom: 4px;
280
+ }
281
+ ol {
282
+ margin: 0;
283
+ padding: 0 0 0 18px;
284
+ font-family: ui-monospace, monospace;
285
+ font-size: 11.5px;
286
+ line-height: 1.55;
287
+ }
288
+ li {
289
+ white-space: nowrap;
290
+ overflow: hidden;
291
+ text-overflow: ellipsis;
292
+ }
293
+ .n {
294
+ color: var(--text);
295
+ }
296
+ /* framework / runtime frames between your own: kept (they are the truth of the path), dimmed */
297
+ li.dim .n {
298
+ color: var(--text-faint);
299
+ }
300
+ .f {
301
+ color: var(--text-faint);
302
+ margin-left: 8px;
303
+ }
304
+ .hint {
305
+ color: var(--text-faint);
306
+ font-size: 12px;
307
+ }
308
+ </style>
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The expanded row under a function / component: WHERE it is (the full path, with line and
3
+ * column, openable in the editor when the path is absolute; on a dev server the source lines
4
+ * themselves) and HOW it was reached (its heaviest call stacks, nearest caller first).
5
+ * Rendered inside the table islands; plain markup.
6
+ */
7
+ import type { FrameStat } from '../analyze.js';
8
+ type $$ComponentProps = {
9
+ f: FrameStat;
10
+ colspan: number;
11
+ base?: string;
12
+ dev?: boolean;
13
+ };
14
+ declare const FrameDetails: import("svelte").Component<$$ComponentProps, {}, "">;
15
+ type FrameDetails = ReturnType<typeof FrameDetails>;
16
+ export default FrameDetails;
@@ -0,0 +1,156 @@
1
+ <script>import { fmt_ms, fmt_dur, fmt_bytes, CATEGORY_COLOR, CATEGORY_LABEL, ink_on } from "./format.js";
2
+ import { sortable } from "./sort.svelte.js";
3
+ import { pressable } from "./press.js";
4
+ import FrameDetails from "./FrameDetails.svelte";
5
+ import { row_id, follow_hash } from "./row-anchor.svelte.js";
6
+ let { rows, hasAlloc, base = "", dev = false } = $props();
7
+ let query = $state("");
8
+ const filtered = $derived.by(() => {
9
+ const q = query.trim().toLowerCase();
10
+ if (!q) return rows;
11
+ return rows.filter((r) => r.name.toLowerCase().includes(q) || r.url.toLowerCase().includes(q) || (r.path ?? "").toLowerCase().includes(q) || (r.pkg ?? "").toLowerCase().includes(q));
12
+ });
13
+ const s = sortable(() => filtered, "self_ms");
14
+ const row_key = (f) => f.key ?? f.name + " " + f.url;
15
+ let open = $state(null);
16
+ const cols = $derived(hasAlloc ? 7 : 6);
17
+ // a finding's `#fn=<key>` link opens + scrolls to its row
18
+ $effect(() => follow_hash("fn", () => rows.map(row_key), (k) => {
19
+ query = "";
20
+ open = k;
21
+ }));
22
+ </script>
23
+
24
+ <div class="tools">
25
+ <input
26
+ type="search"
27
+ placeholder="filter by function, file or package…"
28
+ bind:value={query}
29
+ aria-label="filter functions"
30
+ />
31
+ <span class="hint"
32
+ >{filtered.length} of {rows.length} · click a row for its location and call stacks</span
33
+ >
34
+ </div>
35
+ <table>
36
+ <thead>
37
+ <tr>
38
+ <th>function</th>
39
+ <th>where</th>
40
+ <th><span class="sr-only">kind of code</span></th>
41
+ <th
42
+ class="num sort"
43
+ class:active={s.key === 'self_ms'}
44
+ onclick={() => s.click('self_ms')}
45
+ {@attach pressable}
46
+ aria-sort={s.aria('self_ms')}>self ms<span class="arr">{s.arrow('self_ms')}</span></th
47
+ >
48
+ <th
49
+ class="num sort"
50
+ class:active={s.key === 'total_ms'}
51
+ onclick={() => s.click('total_ms')}
52
+ {@attach pressable}
53
+ aria-sort={s.aria('total_ms')}>total ms<span class="arr">{s.arrow('total_ms')}</span></th
54
+ >
55
+ <th
56
+ class="num sort"
57
+ class:active={s.key === 'per'}
58
+ title="one call: its time per render ÷ its calls in one render"
59
+ onclick={() => s.click('per')}
60
+ {@attach pressable}
61
+ aria-sort={s.aria('per')}>per call<span class="arr">{s.arrow('per')}</span></th
62
+ >
63
+ {#if hasAlloc}
64
+ <th
65
+ class="num sort"
66
+ class:active={s.key === 'alloc'}
67
+ onclick={() => s.click('alloc')}
68
+ {@attach pressable}
69
+ aria-sort={s.aria('alloc')}>alloc<span class="arr">{s.arrow('alloc')}</span></th
70
+ >
71
+ {/if}
72
+ </tr>
73
+ </thead>
74
+ <tbody>
75
+ {#each s.sorted as f (row_key(f))}
76
+ <tr
77
+ class="row"
78
+ id={row_id('fn', row_key(f))}
79
+ class:open={open === row_key(f)}
80
+ onclick={() => (open = open === row_key(f) ? null : row_key(f))}
81
+ {@attach pressable}
82
+ >
83
+ <td class="fn">
84
+ <span class="caret">{open === row_key(f) ? '▾' : '▸'}</span>
85
+ {#if f.label}<b title="an anonymous function: its own first line">fn</b>
86
+ <code class="fnlabel">{f.label}</code>{:else}<b>{f.name}</b>{/if}
87
+ {#if f.count > 1}
88
+ <span class="hint" title="{f.count} calls in one render, {fmt_ms(f.per)} ms each"
89
+ >×{f.count}</span
90
+ >
91
+ {/if}
92
+ </td>
93
+ <td class="file" title={f.path || ''}>
94
+ {#if f.url}{f.url}{#if f.line > 0}:{f.line}{#if f.col > 0}:{f.col}{/if}{/if}{:else}<span
95
+ class="hint">native</span
96
+ >{/if}
97
+ </td>
98
+ <td
99
+ ><span
100
+ class="chip"
101
+ style="background:{CATEGORY_COLOR[f.category]};color:{ink_on(
102
+ CATEGORY_COLOR[f.category]
103
+ )}"
104
+ title={f.pkg ? `${CATEGORY_LABEL[f.category]} · ${f.pkg}` : CATEGORY_LABEL[f.category]}
105
+ >{f.pkg ?? CATEGORY_LABEL[f.category]}</span
106
+ ></td
107
+ >
108
+ <td class="num"><b>{fmt_ms(f.self_ms)}</b></td>
109
+ <td class="num">{fmt_ms(f.total_ms)}</td>
110
+ <td class="num">{f.count > 0 ? fmt_dur(f.per) : '—'}</td>
111
+ {#if hasAlloc}<td class="num">{f.alloc ? fmt_bytes(f.alloc) : '—'}</td>{/if}
112
+ </tr>
113
+ {#if open === row_key(f)}
114
+ <FrameDetails {f} colspan={cols} {base} {dev} />
115
+ {/if}
116
+ {/each}
117
+ </tbody>
118
+ </table>
119
+
120
+ <style>
121
+ .tools {
122
+ display: flex;
123
+ gap: 12px;
124
+ align-items: center;
125
+ flex-wrap: wrap;
126
+ margin: 6px 0 8px;
127
+ }
128
+ .tools input {
129
+ font: inherit;
130
+ font-size: 13px;
131
+ background: var(--bg-raised);
132
+ color: var(--text);
133
+ border: 1px solid var(--line);
134
+ border-radius: 6px;
135
+ padding: 5px 10px;
136
+ min-width: 280px;
137
+ }
138
+ .tools .hint {
139
+ color: var(--text-faint);
140
+ font-size: 12px;
141
+ }
142
+ tr.row {
143
+ cursor: pointer;
144
+ }
145
+ tr.row:hover td {
146
+ background: var(--bg-raised);
147
+ }
148
+ tr.row.open td {
149
+ border-bottom-color: transparent;
150
+ }
151
+ .caret {
152
+ color: var(--text-faint);
153
+ font-size: 10px;
154
+ margin-right: 4px;
155
+ }
156
+ </style>
@@ -0,0 +1,17 @@
1
+ /** Hot-functions table — a `wake:'load'` island with reactive column sort, a name/file filter,
2
+ * and an expandable row per function (full location + its heaviest call stacks). */
3
+ import type { FrameStat } from '../analyze.js';
4
+ type Row = FrameStat & {
5
+ per: number;
6
+ count: number;
7
+ alloc: number | null;
8
+ };
9
+ type $$ComponentProps = {
10
+ rows: Row[];
11
+ hasAlloc: boolean;
12
+ base?: string;
13
+ dev?: boolean;
14
+ };
15
+ declare const FunctionsTable: import("svelte").Component<$$ComponentProps, {}, "">;
16
+ type FunctionsTable = ReturnType<typeof FunctionsTable>;
17
+ export default FunctionsTable;
@@ -0,0 +1,151 @@
1
+ <script>import { fmt_bytes, fmt_ms } from "./format.js";
2
+ import { pressable } from "./press.js";
3
+ let { rows, visit = null } = $props();
4
+ const firsts = $derived(new Map((visit?.firsts ?? []).map((f) => [f.fp, f.t])));
5
+ const cls_by = $derived.by(() => {
6
+ const m = new Map();
7
+ for (const s of visit?.shifts ?? []) if (s.fp) m.set(s.fp, (m.get(s.fp) ?? 0) + s.value);
8
+ return m;
9
+ });
10
+ const visit_islands = $derived(new Map((visit?.islands ?? []).map((i) => [i.fp, i])));
11
+ const all_cols = $derived([
12
+ {
13
+ key: "ssr",
14
+ label: "server ms",
15
+ hint: "render time on the server, per render",
16
+ get: (r) => r.ssr_ms,
17
+ fmt: (n) => `${fmt_ms(n)}`
18
+ },
19
+ {
20
+ key: "markup",
21
+ label: "markup",
22
+ hint: "the island’s HTML bytes",
23
+ get: (r) => r.canonical_bytes || null,
24
+ fmt: fmt_bytes
25
+ },
26
+ {
27
+ key: "props",
28
+ label: "props",
29
+ hint: "the props tail bytes the browser downloads for it",
30
+ get: (r) => r.props_bytes || null,
31
+ fmt: fmt_bytes
32
+ },
33
+ {
34
+ key: "js",
35
+ label: "JS",
36
+ hint: "the JS its wake loads (shared code included)",
37
+ get: (r) => r.js_bytes,
38
+ fmt: fmt_bytes
39
+ },
40
+ {
41
+ key: "js_only",
42
+ label: "JS only it",
43
+ hint: "the JS no other island on the page needs: what dropping or deferring it saves",
44
+ get: (r) => r.js_only,
45
+ fmt: fmt_bytes
46
+ },
47
+ {
48
+ key: "hydrate",
49
+ label: "hydrate ms",
50
+ hint: "wake to hydrated in your browser, p50",
51
+ get: (r) => r.client?.p50_ms ?? null,
52
+ fmt: (n) => `${fmt_ms(n)}`
53
+ },
54
+ {
55
+ key: "woke",
56
+ label: "woke at",
57
+ hint: "when it finished hydrating in this visit, ms after the click",
58
+ get: (r) => visit_islands.get(r.fp)?.done ?? null,
59
+ fmt: (n) => `${fmt_ms(n)}`
60
+ },
61
+ {
62
+ key: "first",
63
+ label: "first touch",
64
+ hint: "the first interaction inside it in this visit, ms after the click",
65
+ get: (r) => firsts.get(r.fp) ?? null,
66
+ fmt: (n) => `${fmt_ms(n)}`
67
+ },
68
+ {
69
+ key: "cls",
70
+ label: "shift",
71
+ hint: "the layout shift its nodes caused in this visit (CLS share)",
72
+ get: (r) => cls_by.get(r.fp) ?? null,
73
+ fmt: (n) => n.toFixed(3)
74
+ }
75
+ ]);
76
+ // drop columns with no data at all (dev has no JS weights; the browser columns need a logged-in
77
+ // visit) — a whole column of dashes is noise, not information
78
+ const cols = $derived(all_cols.filter((c) => rows.some((r) => c.get(r) != null)));
79
+ const max = $derived(cols.map((c) => Math.max(...rows.map((r) => c.get(r) ?? 0), 0)));
80
+ let sort = $state(null);
81
+ const scored = $derived.by(() => {
82
+ const score = (r) => cols.reduce((s, c, i) => s + (max[i] ? (c.get(r) ?? 0) / max[i] : 0), 0);
83
+ const list = rows.map((r) => ({
84
+ r,
85
+ score: score(r)
86
+ }));
87
+ if (sort) {
88
+ const c = cols.find((x) => x.key === sort);
89
+ list.sort((a, b) => (c.get(b.r) ?? -1) - (c.get(a.r) ?? -1));
90
+ } else list.sort((a, b) => b.score - a.score);
91
+ return list;
92
+ });
93
+ // (capped at half strength: the cell's number stays 4.5:1 on its own shade in both themes)
94
+ const shade = (v, i) => v === null || !max[i] ? "transparent" : `rgba(240, 136, 62, ${.06 + .44 * (v / max[i])})`;
95
+ const shown = $derived(scored.slice(0, 40));
96
+ </script>
97
+
98
+ <table class="heat">
99
+ <thead>
100
+ <tr>
101
+ <th>island</th>
102
+ {#each cols as c (c.key)}
103
+ <th
104
+ class="num col"
105
+ class:on={sort === c.key}
106
+ title={c.hint}
107
+ onclick={() => (sort = sort === c.key ? null : c.key)}
108
+ {@attach pressable}
109
+ aria-sort={sort === c.key ? 'descending' : 'none'}>{c.label}</th
110
+ >
111
+ {/each}
112
+ <th
113
+ class="num"
114
+ title="how many of the columns it leads, summed as shares of each column’s max">cost</th
115
+ >
116
+ </tr>
117
+ </thead>
118
+ <tbody>
119
+ {#each shown as { r, score } (r.entry)}
120
+ <tr>
121
+ <td class="fn"
122
+ ><b>{r.name}</b>
123
+ <span class="hint">{r.wake}{r.copies > 1 ? ` ×${r.copies}` : ''}</span></td
124
+ >
125
+ {#each cols as c, i (c.key)}
126
+ {@const v = c.get(r)}
127
+ <td class="num cell" style="background:{shade(v, i)}">{v === null ? '—' : c.fmt(v)}</td>
128
+ {/each}
129
+ <td class="num"><b>{score.toFixed(1)}</b></td>
130
+ </tr>
131
+ {/each}
132
+ </tbody>
133
+ </table>
134
+ <p class="hint">
135
+ Darker is more of that cost, each column against its own biggest. An island dark across the row is
136
+ the one to make static or wake later; one dark only in JS wants a smaller closure; one dark only
137
+ in “first touch” could wake on interaction. Browser columns need a visit from this browser with
138
+ the profiler logged in.
139
+ </p>
140
+
141
+ <style>
142
+ .heat .col {
143
+ cursor: pointer;
144
+ }
145
+ .heat .col.on {
146
+ text-decoration: underline;
147
+ }
148
+ .heat .cell {
149
+ font-variant-numeric: tabular-nums;
150
+ }
151
+ </style>
@@ -0,0 +1,16 @@
1
+ /**
2
+ * THE ISLAND COST HEATMAP — one row per island, one column per cost: server render, markup
3
+ * bytes, props bytes, unique JS, hydration in the browser, time to its first interaction, the
4
+ * layout shift it caused. Each cell is shaded against its column's max, so the eye finds the
5
+ * island that is expensive in several ways at once. A `wake:'load'` island; click a column to
6
+ * sort by it.
7
+ */
8
+ import type { IslandRow } from './report-data.js';
9
+ import type { Visit } from '../visit.js';
10
+ type $$ComponentProps = {
11
+ rows: IslandRow[];
12
+ visit?: Visit | null;
13
+ };
14
+ declare const IslandHeatmap: import("svelte").Component<$$ComponentProps, {}, "">;
15
+ type IslandHeatmap = ReturnType<typeof IslandHeatmap>;
16
+ export default IslandHeatmap;