@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,297 @@
1
+ <script>/**
2
+ * ONE CLOCK — the server's render, the HTML arriving, every file the browser fetched, the main
3
+ * thread's long tasks, each island waking, and the paints, on one time axis from the click. A
4
+ * `wake:'load'` island: the visit comes from the report (a long-lived host joined the beacon)
5
+ * or, when the server has none, from this browser's own store (the beacon writes there too —
6
+ * the merge happens here, never on an instance that may be gone). The critical path is the
7
+ * highlighted thread through the lanes.
8
+ */
9
+ import { one_clock } from "../visit.js";
10
+ import { latest_visit } from "./store.js";
11
+ import { fmt_ms } from "./format.js";
12
+ let { server, visit = null, page, names = {}, hole_names = {} } = $props();
13
+ // the report's visit seeds the clock; without one, this browser's store is asked once, at setup
14
+ // svelte-ignore state_referenced_locally
15
+ let v = $state(visit);
16
+ // svelte-ignore state_referenced_locally
17
+ let source = $state(visit ? "server" : "none");
18
+ // svelte-ignore state_referenced_locally
19
+ let looked = $state(!!visit);
20
+ // svelte-ignore state_referenced_locally
21
+ if (!visit) {
22
+ void latest_visit(page).then((rec) => {
23
+ if (rec?.visit) {
24
+ v = rec.visit;
25
+ source = "browser";
26
+ }
27
+ }).finally(() => looked = true);
28
+ }
29
+ const clock = $derived(v ? one_clock(v, server, names, hole_names) : null);
30
+ const W = 1e3;
31
+ const ROW = 16;
32
+ const LEFT = 118;
33
+ const TOP = 22;
34
+ const COLOR = {
35
+ "server-hooks": "#8b5cf6",
36
+ "server-load": "#5b8fd6",
37
+ "server-render": "#4d9c6b",
38
+ "server-ogygia": "#c9a227",
39
+ "server-kit": "#6b7280",
40
+ "server-response": "#6b7280",
41
+ ttfb: "#374151",
42
+ download: "#5b8fd6",
43
+ css: "#d9a03d",
44
+ script: "#f0883e",
45
+ font: "#a371f7",
46
+ img: "#3fb950",
47
+ fetch: "#58a6ff",
48
+ other: "#6b7280",
49
+ task: "#ff7b72",
50
+ "island-load": "#79c0ff",
51
+ "island-hydrate": "#2ea043",
52
+ // a hole's answer (the devtools waterfall's colours)
53
+ "hole-wait": "#475569",
54
+ "hole-before": "#475569",
55
+ "hole-slot": "#f59e0b",
56
+ "hole-render": "#a78bfa",
57
+ "hole-server": "#c4b5fd",
58
+ "hole-rest": "#7dd3fc"
59
+ };
60
+ const MARK = {
61
+ fcp: "#8a94a2",
62
+ lcp: "#d9a03d",
63
+ dcl: "#8a94a2",
64
+ load: "#8a94a2",
65
+ first: "#6ca8e0",
66
+ mark: "#a371f7"
67
+ };
68
+ const color = (b) => COLOR[b.kind] ?? (b.kind.startsWith("server-") ? "#4d9c6b" : "#6b7280");
69
+ const x = (t) => clock ? LEFT + t / clock.end * (W - LEFT - 8) : LEFT;
70
+ const height = $derived(clock ? TOP + clock.lanes.length * ROW + 40 : 0);
71
+ // time grid: a tick every nice step
72
+ const ticks = $derived.by(() => {
73
+ if (!clock) return [];
74
+ const step = [
75
+ 50,
76
+ 100,
77
+ 200,
78
+ 250,
79
+ 500,
80
+ 1e3,
81
+ 2e3,
82
+ 5e3,
83
+ 1e4
84
+ ].find((s) => clock.end / s <= 12) ?? 1e4;
85
+ const out = [];
86
+ for (let t = 0; t <= clock.end; t += step) out.push(t);
87
+ return out;
88
+ });
89
+ let tip = $state(null);
90
+ let only_critical = $state(false);
91
+ // mark labels on three rows, a label dropped (row −1) when it would sit on one already placed
92
+ const label_rows = $derived.by(() => {
93
+ if (!clock) return [];
94
+ const placed = [
95
+ -1e9,
96
+ -1e9,
97
+ -1e9
98
+ ];
99
+ return [...clock.marks].sort((a, b) => a.t - b.t).map((m) => {
100
+ const px = x(m.t);
101
+ let row = -1;
102
+ for (let r = 0; r < 3; r++) {
103
+ if (px - placed[r] > 78) {
104
+ placed[r] = px;
105
+ row = r;
106
+ break;
107
+ }
108
+ }
109
+ return {
110
+ ...m,
111
+ row
112
+ };
113
+ });
114
+ });
115
+ </script>
116
+
117
+ {#if clock && v}
118
+ <div class="legend">
119
+ <span class="hint"
120
+ >{source === 'browser' ? 'from this browser’s own visit' : 'from your visit (beacon)'} · {new Date(
121
+ v.at
122
+ ).toLocaleTimeString()}{v.nav.protocol ? ` · ${v.nav.protocol}` : ''}{v.viewport
123
+ ? ` · ${v.viewport[0]}×${v.viewport[1]}`
124
+ : ''}</span
125
+ >
126
+ <label class="hint"
127
+ ><input type="checkbox" bind:checked={only_critical} /> dim what is not on the critical path</label
128
+ >
129
+ </div>
130
+ <svg
131
+ viewBox="0 0 {W} {height}"
132
+ class="clock"
133
+ role="img"
134
+ aria-label="one clock: server, document, network, main thread, islands"
135
+ onmouseleave={() => (tip = null)}
136
+ >
137
+ {#each ticks as t (t)}
138
+ <line x1={x(t)} x2={x(t)} y1={TOP - 4} y2={height - 26} class="grid" />
139
+ <text x={x(t)} y={TOP - 8} class="tick">{t} ms</text>
140
+ {/each}
141
+ {#each clock.lanes as lane, i (lane.name)}
142
+ {@const y = TOP + i * ROW}
143
+ <text x={LEFT - 6} y={y + ROW - 4} class="lane-name" text-anchor="end">{lane.name}</text>
144
+ <line x1={LEFT} x2={W - 8} y1={y + ROW} y2={y + ROW} class="lane-sep" />
145
+ {#each lane.bars as b (b.id)}
146
+ <rect
147
+ x={x(b.t0)}
148
+ y={y + 2}
149
+ width={Math.max(x(b.t1) - x(b.t0), 1.5)}
150
+ height={ROW - 4}
151
+ fill={color(b)}
152
+ class="bar"
153
+ class:critical={b.critical}
154
+ class:dim={only_critical && !b.critical}
155
+ class:blocking={b.blocking}
156
+ role="presentation"
157
+ onmouseenter={(e) => (tip = { bar: b, x: e.clientX, y: e.clientY })}
158
+ onmousemove={(e) => (tip = { bar: b, x: e.clientX, y: e.clientY })}
159
+ />
160
+ {/each}
161
+ {/each}
162
+ {#each label_rows as m, i (m.kind + m.t + i)}
163
+ <line
164
+ x1={x(m.t)}
165
+ x2={x(m.t)}
166
+ y1={TOP - 2}
167
+ y2={height - 26}
168
+ stroke={MARK[m.kind] ?? '#9aa4b2'}
169
+ class="mark"
170
+ class:soft={m.kind === 'first' || m.kind === 'mark'}
171
+ />
172
+ {#if m.row >= 0}<text
173
+ x={x(m.t) + 3}
174
+ y={height - 16 + m.row * 9}
175
+ class="mark-label"
176
+ fill={MARK[m.kind] ?? '#9aa4b2'}>{m.label} {Math.round(m.t)}</text
177
+ >{/if}
178
+ {/each}
179
+ </svg>
180
+ {#if tip}
181
+ <div class="tip" style="left:{tip.x + 12}px;top:{tip.y + 12}px">
182
+ <b>{tip.bar.label}</b><br />
183
+ {fmt_ms(tip.bar.t0)} → {fmt_ms(tip.bar.t1)} ms ({fmt_ms(tip.bar.t1 - tip.bar.t0)} ms){#if tip.bar.detail}<br
184
+ />{tip.bar.detail}{/if}{#if tip.bar.critical}<br /><i>on the critical path</i>{/if}
185
+ </div>
186
+ {/if}
187
+ {#if clock.critical.length}
188
+ <ol class="path">
189
+ {#each clock.critical as c (c.id)}
190
+ <li>
191
+ <b>{c.label}</b> <span class="hint">{fmt_ms(c.t0)} → {fmt_ms(c.t1)} ms</span> — {c.why}
192
+ </li>
193
+ {/each}
194
+ {#if v.paints.lcp !== undefined}<li>
195
+ <b>largest paint</b>
196
+ <span class="hint">at {fmt_ms(v.paints.lcp)} ms</span>{#if v.paints.lcp_tag}
197
+ — a <code>{v.paints.lcp_tag}</code>{#if v.paints.lcp_fp && names[v.paints.lcp_fp]}
198
+ inside <b>{names[v.paints.lcp_fp]}</b>{/if}{/if}
199
+ </li>{/if}
200
+ </ol>
201
+ {/if}
202
+ {#each clock.notes as n (n)}<p class="hint">{n}</p>{/each}
203
+ <p class="hint">
204
+ The server bar is the profiled render placed so it ends at this visit's first byte; everything
205
+ else is what this browser measured on the same clock. Islands show module load (light) then
206
+ hydration (dark). Each hole's lane is its answer: grey before its request left, amber waiting
207
+ for a render slot on the server, violet the server render, blue the network and the swap (the
208
+ devtools Page tab draws the same). A striped-looking gap before first paint with nothing in the
209
+ lanes is the browser parsing and laying out.
210
+ </p>
211
+ {:else if looked}
212
+ <p class="hint">
213
+ No visit of <code>{page}</code> from this browser yet. Open the page once while logged in to the profiler,
214
+ then come back: the beacon records the visit into this browser and (on a long-lived host) into the
215
+ server.
216
+ </p>
217
+ {:else}
218
+ <p class="hint">Looking for a visit of this page in this browser…</p>
219
+ {/if}
220
+
221
+ <style>
222
+ .legend {
223
+ display: flex;
224
+ gap: 16px;
225
+ align-items: center;
226
+ margin: 4px 0 6px;
227
+ }
228
+ .clock {
229
+ width: 100%;
230
+ height: auto;
231
+ display: block;
232
+ background: var(--bg-sunken);
233
+ border: 1px solid var(--line);
234
+ border-radius: 6px;
235
+ }
236
+ .grid {
237
+ stroke: #1c2129;
238
+ stroke-width: 1;
239
+ }
240
+ .tick {
241
+ fill: var(--text-faint);
242
+ font-size: 9px;
243
+ text-anchor: middle;
244
+ }
245
+ .lane-name {
246
+ fill: var(--text);
247
+ font-size: 10px;
248
+ }
249
+ .lane-sep {
250
+ stroke: var(--bg-raised);
251
+ }
252
+ .bar {
253
+ rx: 2;
254
+ opacity: 0.92;
255
+ }
256
+ .bar.dim {
257
+ opacity: 0.18;
258
+ }
259
+ .bar.critical {
260
+ stroke: #fff;
261
+ stroke-width: 1.2;
262
+ opacity: 1;
263
+ }
264
+ .bar.blocking {
265
+ stroke-dasharray: 2 2;
266
+ stroke: var(--warn);
267
+ }
268
+ .mark {
269
+ stroke-width: 1;
270
+ stroke-dasharray: 3 3;
271
+ }
272
+ .mark.soft {
273
+ opacity: 0.5;
274
+ }
275
+ .mark-label {
276
+ font-size: 9px;
277
+ }
278
+ .tip {
279
+ position: fixed;
280
+ z-index: 20;
281
+ background: var(--bg-raised);
282
+ border: 1px solid var(--line-strong);
283
+ border-radius: 6px;
284
+ padding: 6px 8px;
285
+ font-size: 12px;
286
+ max-width: 360px;
287
+ pointer-events: none;
288
+ }
289
+ .path {
290
+ margin: 8px 0 0;
291
+ padding-left: 20px;
292
+ font-size: 13px;
293
+ }
294
+ .path li {
295
+ margin: 2px 0;
296
+ }
297
+ </style>
@@ -0,0 +1,27 @@
1
+ /**
2
+ * ONE CLOCK — the server's render, the HTML arriving, every file the browser fetched, the main
3
+ * thread's long tasks, each island waking, and the paints, on one time axis from the click. A
4
+ * `wake:'load'` island: the visit comes from the report (a long-lived host joined the beacon)
5
+ * or, when the server has none, from this browser's own store (the beacon writes there too —
6
+ * the merge happens here, never on an instance that may be gone). The critical path is the
7
+ * highlighted thread through the lanes.
8
+ */
9
+ import { type Visit } from '../visit.js';
10
+ type $$ComponentProps = {
11
+ server: {
12
+ window_ms: number;
13
+ phases: {
14
+ phase: string;
15
+ label?: string;
16
+ cpu_ms: number;
17
+ wait_ms: number;
18
+ }[];
19
+ } | null;
20
+ visit?: Visit | null;
21
+ page: string;
22
+ names?: Record<string, string>;
23
+ hole_names?: Record<string, string>;
24
+ };
25
+ declare const OneClock: import("svelte").Component<$$ComponentProps, {}, "">;
26
+ type OneClock = ReturnType<typeof OneClock>;
27
+ export default OneClock;
@@ -0,0 +1,10 @@
1
+ <script>let { id, title, sub = "", wide = false, children } = $props();
2
+ export {};
3
+ </script>
4
+
5
+ <section class="panel" class:wide {id}>
6
+ <h2>
7
+ {title}{#if sub}<span class="hint">{sub}</span>{/if}
8
+ </h2>
9
+ {@render children()}
10
+ </section>
@@ -0,0 +1,16 @@
1
+ /**
2
+ * One dashboard panel: a card with a title, an optional sub-line in the title, and whatever the
3
+ * section renders inside. Static — every section of the report sits in one, so the page reads as
4
+ * a grid of cards instead of a scroll of headings. `wide` spans a two-column row.
5
+ */
6
+ import type { Snippet } from 'svelte';
7
+ type $$ComponentProps = {
8
+ id: string;
9
+ title: string;
10
+ sub?: string;
11
+ wide?: boolean;
12
+ children: Snippet;
13
+ };
14
+ declare const Panel: import("svelte").Component<$$ComponentProps, {}, "">;
15
+ type Panel = ReturnType<typeof Panel>;
16
+ export default Panel;
@@ -0,0 +1,269 @@
1
+ <script>import { fmt_ms, fmt_pct, CATEGORY_COLOR } from "./format.js";
2
+ import { row_href } from "./row-anchor.svelte.js";
3
+ let { paths, busy, fns } = $props();
4
+ // a middle frame (a library's renderer with no time of its own) may have no functions-table row
5
+ const known = $derived(fns ? new Set(fns) : null);
6
+ const COL = 236;
7
+ const ROW = 30;
8
+ const NODE_W = 204;
9
+ const NODE_H = 22;
10
+ const PAD = 8;
11
+ /** a tidy left-to-right layout: leaves take rows in DFS order, a parent sits at its children's middle */
12
+ function layout(root) {
13
+ const nodes = [];
14
+ let row = 0;
15
+ let depth_max = 0;
16
+ const place = (n, depth, parent) => {
17
+ depth_max = Math.max(depth_max, depth);
18
+ const me = {
19
+ n,
20
+ x: PAD + depth * COL,
21
+ y: 0,
22
+ depth,
23
+ parent
24
+ };
25
+ nodes.push(me);
26
+ if (!n.children.length) me.y = PAD + row++ * ROW;
27
+ else {
28
+ const kids = n.children.map((c) => place(c, depth + 1, me));
29
+ me.y = (kids[0].y + kids[kids.length - 1].y) / 2;
30
+ }
31
+ return me;
32
+ };
33
+ place(root, 0, null);
34
+ return {
35
+ nodes,
36
+ width: PAD * 2 + (depth_max + 1) * COL - (COL - NODE_W),
37
+ height: PAD * 2 + Math.max(row, 1) * ROW
38
+ };
39
+ }
40
+ const groups = $derived(paths.map((g) => ({
41
+ g,
42
+ l: layout(g.tree)
43
+ })));
44
+ const link = (a, b) => {
45
+ const x1 = a.x + NODE_W;
46
+ const y1 = a.y + NODE_H / 2;
47
+ const x2 = b.x;
48
+ const y2 = b.y + NODE_H / 2;
49
+ const mx = (x1 + x2) / 2;
50
+ return `M${x1},${y1} C${mx},${y1} ${mx},${y2} ${x2},${y2}`;
51
+ };
52
+ const stroke = (n, total) => 1 + 7 * Math.min(1, n.ms / Math.max(total, .01));
53
+ const href = (n) => !n.key ? null : n.category === "component" ? row_href(`comp:${n.name}`) : known && !known.has(n.key) ? null : row_href(`fn:${n.key}`);
54
+ const short = (s, max = 19) => s.length > max ? s.slice(0, max - 1) + "…" : s;
55
+ </script>
56
+
57
+ {#each groups as { g, l }, gi (g.owner.key)}
58
+ <div class="path">
59
+ <div class="head">
60
+ <span class="no">{gi + 1}</span>
61
+ <b
62
+ >{g.fns.length} hot functions on one path under
63
+ <a href={href(g.tree) ?? '#'}>{g.owner.name}</a></b
64
+ >
65
+ <span class="hint">
66
+ · {fmt_ms(g.ms)} ms together ({fmt_pct(g.ms, busy)} of busy, {Math.round(g.share * 100)}% of
67
+ what {g.owner.name} costs){#if g.owner.calls}
68
+ · {g.owner.name} runs {g.owner.calls}×{/if}
69
+ </span>
70
+ </div>
71
+ <div class="fix">
72
+ Fix <b>{g.owner.name}</b> once — call it less, cache what it computes, or move it out of the
73
+ render — and
74
+ {g.fns.map((f) => f.name).join(', ')} go with it. Chasing them one by one gains the same time in
75
+ {g.fns.length} places.
76
+ </div>
77
+ <div class="legend">
78
+ <span><i class="sw owner"></i> the caller to fix</span>
79
+ <span><i class="sw hot"></i> a hot function</span>
80
+ <span><i class="sw"></i> a call between them</span>
81
+ <span
82
+ ><i class="ln"></i> link width and the thin bar under a node = its share of the path's {fmt_ms(
83
+ g.ms
84
+ )} ms</span
85
+ >
86
+ </div>
87
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
88
+ <div
89
+ class="scroll"
90
+ tabindex="0"
91
+ role="region"
92
+ aria-label="call path from {g.owner.name}, scrolls sideways"
93
+ >
94
+ <!-- a group, not an image: its nodes are links (an image cannot hold anything to press) -->
95
+ <svg
96
+ width={l.width}
97
+ height={l.height}
98
+ viewBox="0 0 {l.width} {l.height}"
99
+ role="group"
100
+ aria-label="the call path from {g.owner.name} to its hot functions"
101
+ >
102
+ {#each l.nodes as n (n.n.key + n.x + n.y)}
103
+ {#if n.parent}
104
+ <path
105
+ d={link(n.parent, n)}
106
+ fill="none"
107
+ stroke={n.n.hot ? '#e8734a' : '#3a4250'}
108
+ stroke-width={stroke(n.n, g.ms)}
109
+ opacity="0.85"
110
+ />
111
+ {/if}
112
+ {/each}
113
+ {#each l.nodes as n (n.n.key + n.x + n.y + 'n')}
114
+ {@const h = href(n.n)}
115
+ <a href={h ?? undefined} class:dead={!h}>
116
+ <g
117
+ class="node"
118
+ class:hot={n.n.hot}
119
+ class:owner={n.depth === 0}
120
+ transform="translate({n.x},{n.y})"
121
+ >
122
+ <title
123
+ >{n.n.name}{n.n.file ? ` (${n.n.file})` : ''} — {fmt_ms(n.n.ms)} ms of hot time through
124
+ here{n.n.hot ? ' · a hot function' : ''}</title
125
+ >
126
+ <rect width={NODE_W} height={NODE_H} rx="5" />
127
+ {#if n.depth > 0}
128
+ <!-- the node's share of the path's time, in its category colour -->
129
+ <rect
130
+ class="bar"
131
+ width={Math.max(2, (NODE_W - 12) * Math.min(1, n.n.ms / Math.max(g.ms, 0.01)))}
132
+ height="2"
133
+ x="6"
134
+ y={NODE_H - 4}
135
+ rx="1"
136
+ fill={CATEGORY_COLOR[n.n.category] ?? '#6b7280'}
137
+ opacity="0.8"
138
+ />
139
+ {/if}
140
+ <text x="8" y="15">{short(n.n.name, n.n.calls ? 13 : 19)}</text>
141
+ {#if n.n.calls}<text class="calls" x={NODE_W - 50} y="15" text-anchor="end"
142
+ >×{n.n.calls}</text
143
+ >{/if}
144
+ <text class="ms" x={NODE_W - 6} y="15" text-anchor="end">{fmt_ms(n.n.ms)}</text>
145
+ </g>
146
+ </a>
147
+ {/each}
148
+ </svg>
149
+ </div>
150
+ </div>
151
+ {/each}
152
+
153
+ <style>
154
+ .path {
155
+ margin: 8px 0 14px;
156
+ padding: 10px 12px;
157
+ background: var(--bg-raised);
158
+ border: 1px solid var(--line);
159
+ border-radius: 8px;
160
+ }
161
+ .head {
162
+ display: flex;
163
+ flex-wrap: wrap;
164
+ gap: 6px 8px;
165
+ align-items: baseline;
166
+ font-size: 13.5px;
167
+ }
168
+ .head .no {
169
+ display: inline-block;
170
+ width: 20px;
171
+ height: 20px;
172
+ line-height: 20px;
173
+ text-align: center;
174
+ border-radius: 50%;
175
+ background: var(--c-orange);
176
+ color: var(--bg-sunken);
177
+ font-weight: 700;
178
+ font-size: 12px;
179
+ }
180
+ .head .hint,
181
+ .fix {
182
+ color: var(--text-dim);
183
+ font-size: 12.5px;
184
+ }
185
+ .fix {
186
+ margin: 6px 0 4px;
187
+ padding: 5px 10px;
188
+ border-left: 3px solid var(--warn);
189
+ background: var(--bg-raised);
190
+ border-radius: 4px;
191
+ }
192
+ .legend {
193
+ display: flex;
194
+ flex-wrap: wrap;
195
+ gap: 4px 14px;
196
+ margin: 4px 0 6px;
197
+ font-size: 11.5px;
198
+ color: var(--text-faint);
199
+ }
200
+ .legend span {
201
+ display: inline-flex;
202
+ align-items: center;
203
+ gap: 5px;
204
+ }
205
+ .legend .sw {
206
+ width: 12px;
207
+ height: 10px;
208
+ border-radius: 3px;
209
+ background: var(--bg-hover);
210
+ border: 1px solid var(--line);
211
+ display: inline-block;
212
+ }
213
+ .legend .sw.owner {
214
+ background: var(--accent-deep);
215
+ border-color: var(--c-blue);
216
+ }
217
+ .legend .sw.hot {
218
+ background: var(--warn-deep);
219
+ border-color: var(--c-orange);
220
+ }
221
+ .legend .ln {
222
+ width: 18px;
223
+ height: 3px;
224
+ background: var(--c-orange);
225
+ border-radius: 2px;
226
+ display: inline-block;
227
+ }
228
+ .scroll {
229
+ overflow-x: auto;
230
+ }
231
+ svg {
232
+ display: block;
233
+ font-family: ui-monospace, monospace;
234
+ font-size: 11.5px;
235
+ }
236
+ .node rect:not(.bar) {
237
+ fill: var(--bg-hover);
238
+ stroke: var(--line);
239
+ }
240
+ .node.hot rect:not(.bar) {
241
+ fill: var(--warn-deep);
242
+ stroke: var(--c-orange);
243
+ }
244
+ .node.owner rect:not(.bar) {
245
+ fill: var(--accent-deep);
246
+ stroke: var(--c-blue);
247
+ stroke-width: 1.5;
248
+ }
249
+ .node text {
250
+ fill: var(--text-faint);
251
+ }
252
+ .node.hot text,
253
+ .node.owner text {
254
+ fill: var(--text);
255
+ }
256
+ .node .ms {
257
+ fill: var(--text-dim);
258
+ }
259
+ .node .calls {
260
+ fill: var(--text-faint);
261
+ font-size: 9.5px;
262
+ }
263
+ a:hover .node rect:not(.bar) {
264
+ stroke: var(--text);
265
+ }
266
+ a.dead {
267
+ pointer-events: none;
268
+ }
269
+ </style>
@@ -0,0 +1,15 @@
1
+ /**
2
+ * THE PATHS TO FIX — one graph per path group (analyze.ts `paths`): the caller to fix on the
3
+ * left, the chain of calls through to every hot function under it on the right. Link width and
4
+ * node bar are the hot time that flowed through; hot functions are lit, the rest of the chain
5
+ * dimmed. Hover a node for its time, click it to jump to its row. A `wake:'load'` island.
6
+ */
7
+ import type { PathGroup } from '../analyze.js';
8
+ type $$ComponentProps = {
9
+ paths: PathGroup[];
10
+ busy: number;
11
+ fns?: string[];
12
+ };
13
+ declare const Paths: import("svelte").Component<$$ComponentProps, {}, "">;
14
+ type Paths = ReturnType<typeof Paths>;
15
+ export default Paths;