@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,755 @@
1
+ <!-- (its CSS injected into the dock's shadow root: see Devtools.svelte) -->
2
+ <svelte:options css="injected" />
3
+
4
+ <script>
5
+ /**
6
+ * The Profiler tab — the SSR profiler, native in the dock (no iframe). "Profile this page" asks the
7
+ * profiler for its JSON answer (`/page?format=json`: the page rendered through the real server N
8
+ * times), keeps a slim copy (profile-store.ts) and renders it here: the score, the render time and
9
+ * what it would be after the fixes, the findings, every island's server cost joined to the island
10
+ * on THIS page (hover lights it up, click opens it — its detail card shows the same row), the
11
+ * heaviest components and the outbound calls. The full report is one click away. Dev-only, like
12
+ * the rest of devtools, so the profiler answers without a key.
13
+ */
14
+ import { onMount } from 'svelte';
15
+ import { run_profile, profile_for, profiles_version } from './profile-store.js';
16
+ import { page_anchors } from './profile-anchors.js';
17
+ import { highlight, clear_highlight } from './highlight.js';
18
+
19
+ let { tick = 0, focus = $bindable(null), selected = $bindable(null) } = $props();
20
+
21
+ const BASE = '/__profiler';
22
+ let path = $state('/');
23
+ let runs = $state(5);
24
+ let available = $state(/** @type {boolean | null} */ (null)); // null = probing
25
+ let running = $state(false);
26
+ let started = $state(0);
27
+ let error = $state('');
28
+ let bump = $state(0); // a new profile landed
29
+
30
+ onMount(() => {
31
+ path = location.pathname;
32
+ // Probe: a mounted profiler answers its base (200/redirect); an un-mounted one 404s.
33
+ fetch(BASE, { method: 'GET', redirect: 'manual' })
34
+ .then((r) => (available = r.status !== 404))
35
+ .catch(() => (available = false));
36
+ return () => clear_highlight(); // leaving the tab takes the boxes off the page
37
+ });
38
+
39
+ const prof = $derived.by(() => {
40
+ void tick;
41
+ void bump;
42
+ profiles_version();
43
+ return profile_for(path.startsWith('/') ? path : '/' + path);
44
+ });
45
+ const elapsed = $derived.by(() => {
46
+ void tick; // the panel's pulse moves the counter
47
+ return running ? Math.max(0, (performance.now() - started) / 1000) : 0;
48
+ });
49
+ // a rough length for the next run, from the last one of this page (warm-up + the renders)
50
+ const estimate = $derived(
51
+ prof ? Math.round(((prof.render_ms * (runs + 1)) / 1000 + 2) * 1.3) : null
52
+ );
53
+
54
+ async function start() {
55
+ error = '';
56
+ running = true;
57
+ started = performance.now();
58
+ const p = path.startsWith('/') ? path : '/' + path;
59
+ try {
60
+ await run_profile(p, runs, BASE);
61
+ bump++;
62
+ } catch (e) {
63
+ error = `The profile failed: ${e instanceof Error ? e.message : String(e)}`;
64
+ } finally {
65
+ running = false;
66
+ }
67
+ }
68
+
69
+ const ms = (/** @type {number} */ n) =>
70
+ n >= 1000
71
+ ? (n / 1000).toFixed(2) + ' s'
72
+ : n >= 10
73
+ ? Math.round(n) + ' ms'
74
+ : n.toFixed(1) + ' ms';
75
+ const kb = (/** @type {number} */ n) =>
76
+ n < 1024
77
+ ? n + ' B'
78
+ : n < 1048576
79
+ ? (n / 1024).toFixed(1) + ' KB'
80
+ : (n / 1048576).toFixed(2) + ' MB';
81
+ function el_of(/** @type {string | null} */ fp) {
82
+ return fp ? document.querySelector(`ogygia-region[data-og-fp="${fp}"]`) : null;
83
+ }
84
+ const on_page = $derived.by(() => {
85
+ void tick;
86
+ const set = new Set();
87
+ for (const el of document.querySelectorAll('ogygia-region[data-og-fp]'))
88
+ set.add(el.getAttribute('data-og-fp'));
89
+ return set;
90
+ });
91
+ function short_url(/** @type {string} */ u) {
92
+ try {
93
+ const x = new URL(u, location.href);
94
+ return (x.host === location.host ? '' : x.host) + x.pathname;
95
+ } catch {
96
+ return u;
97
+ }
98
+ }
99
+ const grade_class = (/** @type {number} */ s) => (s >= 80 ? 'good' : s >= 50 ? 'fair' : 'poor');
100
+
101
+ // THE PROFILE ON THIS PAGE: costs with a place here (one DOM pass per profile, not per tick)
102
+ const anchors = $derived.by(() => {
103
+ if (!prof) return [];
104
+ try {
105
+ return page_anchors(prof);
106
+ } catch {
107
+ return [];
108
+ }
109
+ });
110
+ let pinned = $state(/** @type {string | null} */ (null));
111
+ function show(a, pin = false) {
112
+ if (pin) pinned = pinned === a.key ? null : a.key;
113
+ if (pin && pinned === null) return clear_highlight();
114
+ highlight(a.find(), `${a.title} — ${a.detail}`, pin);
115
+ }
116
+ /** the islands on this page with these fingerprints (a browser finding's) */
117
+ function regions_of(fps) {
118
+ const want = new Set(fps);
119
+ return [...document.querySelectorAll('ogygia-region[data-og-fp]')].filter((el) =>
120
+ want.has(el.getAttribute('data-og-fp'))
121
+ );
122
+ }
123
+ function unshow() {
124
+ if (pinned) {
125
+ const a = anchors.find((x) => x.key === pinned);
126
+ if (a) return highlight(a.find(), `${a.title} — ${a.detail}`);
127
+ }
128
+ clear_highlight();
129
+ }
130
+ </script>
131
+
132
+ <div class="pf" data-og-profiler>
133
+ {#if available === false}
134
+ <div class="notice">
135
+ <p class="h">The SSR profiler isn't mounted.</p>
136
+ <p>Turn it on in <code>vite.config.ts</code>:</p>
137
+ <pre>ogygia(&#123; profiler: true &#125;)</pre>
138
+ <p><code>ogygia.handle()</code> mounts it for you — then reload this page.</p>
139
+ </div>
140
+ {:else}
141
+ <div class="row">
142
+ <label>path <input bind:value={path} spellcheck="false" /></label>
143
+ <label>renders <input class="n" type="number" min="1" max="50" bind:value={runs} /></label>
144
+ <button class="go" data-og-profile-run disabled={running} onclick={start}>
145
+ {running
146
+ ? `Profiling… ${elapsed.toFixed(0)} s${estimate ? ` of ~${estimate} s` : ''}`
147
+ : prof
148
+ ? 'Profile again'
149
+ : 'Profile this page'}
150
+ </button>
151
+ {#if prof?.links}
152
+ <a
153
+ class="open"
154
+ href={prof.links.html}
155
+ target="_blank"
156
+ rel="noopener"
157
+ title="the full report, in a new tab">full report ↗</a
158
+ >
159
+ {/if}
160
+ </div>
161
+ {#if running}<div class="bar">
162
+ <i style:width="{estimate ? Math.min(96, (elapsed / estimate) * 100) : 50}%"></i>
163
+ </div>{/if}
164
+ {#if error}<p class="err">{error}</p>{/if}
165
+
166
+ {#if !prof}
167
+ <p class="hint">
168
+ Renders <code>{path}</code> through your real server {runs}× and shows where the time went:
169
+ components, functions, allocations, and outbound calls. The answer lands right here, and
170
+ each island's server cost joins the island on the page.
171
+ </p>
172
+ {:else}
173
+ <div class="head" data-og-profile-head>
174
+ {#if prof.score}
175
+ <div
176
+ class="score {grade_class(prof.score.score)}"
177
+ title="page score{prof.score.was ? ` (was ${prof.score.was.score})` : ''}"
178
+ >
179
+ <b>{prof.score.grade}</b><span
180
+ >{prof.score.score}{#if prof.score.was && prof.score.was.score !== prof.score.score}
181
+ <i class={prof.score.score > prof.score.was.score ? 'up' : 'down'}
182
+ >{prof.score.score > prof.score.was.score ? '▲' : '▼'}{Math.abs(
183
+ prof.score.score - prof.score.was.score
184
+ )}</i
185
+ >{/if}</span
186
+ >
187
+ </div>
188
+ {/if}
189
+ <div class="stat">
190
+ <span class="k">server render</span><span class="v">{ms(prof.render_ms)}</span><span
191
+ class="s">median of {prof.runs.length}</span
192
+ >
193
+ </div>
194
+ {#if prof.forecast && prof.forecast.after_ms < prof.forecast.now_ms - 1}
195
+ <div class="stat">
196
+ <span class="k">after the fixes</span><span class="v ok"
197
+ >{ms(prof.forecast.after_ms)}</span
198
+ ><span class="s">−{ms(prof.forecast.now_ms - prof.forecast.after_ms)}</span>
199
+ </div>
200
+ {/if}
201
+ {#if prof.seed_bytes}
202
+ <div class="stat">
203
+ <span class="k">page seed</span><span class="v">{kb(prof.seed_bytes)}</span>
204
+ </div>
205
+ {/if}
206
+ <span class="when">{new Date(prof.at).toLocaleTimeString()}</span>
207
+ </div>
208
+ {#if prof.score}
209
+ <div class="cats" data-og-profile-score>
210
+ {#each prof.score.categories as c (c.key)}
211
+ <span
212
+ class="cat {grade_class(c.score)}"
213
+ title="{c.value}{c.detail.length ? '\n' + c.detail.join('\n') : ''}"
214
+ >{c.label} <b>{c.score}</b>{#if c.lost >= 1}<i class="lost">
215
+ −{Math.round(c.lost)}</i
216
+ >{/if}</span
217
+ >
218
+ {/each}
219
+ </div>
220
+ {#if prof.score.missing.length}
221
+ <p class="hint">
222
+ Left out of the score: {prof.score.missing.map((m) => m.label).join(', ')} — {prof.score
223
+ .missing[0].why}.
224
+ </p>
225
+ {/if}
226
+ {/if}
227
+
228
+ {#if prof.assets}
229
+ <h3>
230
+ What the page loads <span class="muted"
231
+ >{kb(prof.assets.js)} JS at start · {kb(prof.assets.wire)} on the wire{prof.assets
232
+ .lazy_js
233
+ ? ` · ${kb(prof.assets.lazy_js)} on demand`
234
+ : ''}</span
235
+ >
236
+ </h3>
237
+ <table data-og-profile-assets>
238
+ <tbody>
239
+ {#each prof.assets.files as f, i (f.name + i)}
240
+ <tr class:lazyrow={f.lazy}>
241
+ <td class="url" title={f.name}>{f.name}</td>
242
+ <td class="muted">{f.kind}{f.lazy ? ' · on demand' : ''}</td>
243
+ <td class="num">{kb(f.bytes)}</td>
244
+ <td class="muted holds" title={f.contains.join(', ')}>{f.contains.join(', ')}</td>
245
+ </tr>
246
+ {/each}
247
+ </tbody>
248
+ </table>
249
+ {/if}
250
+
251
+ {#if anchors.length}
252
+ <h3>On this page <span class="muted">hover to light up, click to pin</span></h3>
253
+ <ul class="anchors" data-og-profile-anchors>
254
+ {#each anchors as a (a.key)}
255
+ <li class:pinned={pinned === a.key}>
256
+ <button
257
+ data-anchor={a.key}
258
+ onmouseenter={() => show(a)}
259
+ onmouseleave={unshow}
260
+ onclick={() => show(a, true)}
261
+ >
262
+ <b>{a.title}</b><span class="muted">{a.detail}</span>
263
+ </button>
264
+ </li>
265
+ {/each}
266
+ </ul>
267
+ {/if}
268
+
269
+ {#if prof.forecast?.parts.length}
270
+ <h3>What the fixes save</h3>
271
+ <ul class="parts">
272
+ {#each prof.forecast.parts as p, i (p.kind + p.title + i)}
273
+ <li>
274
+ <span class="pm">−{ms(p.ms)}</span>{p.title}{#if p.wait}<span class="tag"
275
+ >waiting</span
276
+ >{/if}
277
+ </li>
278
+ {/each}
279
+ </ul>
280
+ {/if}
281
+
282
+ {#if prof.hot.length}
283
+ <h3>The server's hottest code <span class="muted">own time, per render</span></h3>
284
+ <table data-og-profile-hot>
285
+ <tbody>
286
+ {#each prof.hot as f, i (f.name + i)}
287
+ <tr
288
+ ><td>{f.name}</td><td class="muted"
289
+ >{f.file ? f.file.slice(f.file.lastIndexOf('/') + 1) : ''}{f.line !== null
290
+ ? `:${f.line}`
291
+ : ''}</td
292
+ ><td class="muted">{f.kind}</td><td class="num">{ms(f.ms)}</td></tr
293
+ >
294
+ {/each}
295
+ </tbody>
296
+ </table>
297
+ {/if}
298
+
299
+ {#if prof.findings.length}
300
+ <h3>
301
+ Findings <span class="muted"
302
+ >{prof.findings.length > 12
303
+ ? `12 of ${prof.findings.length}, the rest in the full report`
304
+ : prof.findings.length}</span
305
+ >
306
+ </h3>
307
+ <ul class="findings" data-og-profile-findings>
308
+ {#each prof.findings.slice(0, 12) as f, i (f.code + i)}
309
+ <li
310
+ class={f.severity}
311
+ class:lit={!!f.fps}
312
+ data-code={f.code}
313
+ onmouseenter={f.fps ? () => highlight(regions_of(f.fps), f.code) : undefined}
314
+ onmouseleave={f.fps ? unshow : undefined}
315
+ >
316
+ <p class="msg">{f.message}</p>
317
+ {#if f.fix}<p class="fix">{f.fix}</p>{/if}
318
+ </li>
319
+ {/each}
320
+ </ul>
321
+ {/if}
322
+
323
+ {#if prof.islands.length}
324
+ <h3>Islands on the server <span class="muted">{prof.islands.length}</span></h3>
325
+ <table data-og-profile-islands>
326
+ <thead
327
+ ><tr
328
+ ><th>island</th><th class="num">render</th><th class="num">props</th><th class="num"
329
+ >browser</th
330
+ ><th></th></tr
331
+ ></thead
332
+ >
333
+ <tbody>
334
+ {#each prof.islands as i, k (i.fp ?? i.name + k)}
335
+ {@const here = i.fp !== null && on_page.has(i.fp)}
336
+ <tr
337
+ class:here
338
+ onmouseenter={() => (focus = el_of(i.fp))}
339
+ onmouseleave={() => (focus = null)}
340
+ onclick={() => {
341
+ const el = el_of(i.fp);
342
+ if (el) selected = el;
343
+ }}
344
+ >
345
+ <td>{i.name}<span class="wake">{i.wake ?? ''}</span></td>
346
+ <td class="num">{i.ssr_ms !== null ? ms(i.ssr_ms) : '—'}</td>
347
+ <td class="num" title={i.culprit ? `not plain JSON: ${i.culprit}` : ''}
348
+ >{i.props_bytes !== null ? kb(i.props_bytes) : '—'}{#if i.culprit}<span
349
+ class="warn">*</span
350
+ >{/if}</td
351
+ >
352
+ <td class="num"
353
+ >{i.client_p50_ms !== null ? ms(i.client_p50_ms) : '—'}{#if i.recovered}<span
354
+ class="warn"
355
+ >
356
+ recovered</span
357
+ >{/if}</td
358
+ >
359
+ <td class="muted">{here ? '' : 'not on this page now'}</td>
360
+ </tr>
361
+ {/each}
362
+ </tbody>
363
+ </table>
364
+ {/if}
365
+
366
+ {#if prof.components.length}
367
+ <h3>Heaviest components</h3>
368
+ <table>
369
+ <thead
370
+ ><tr
371
+ ><th>component</th><th class="num">own</th><th class="num">with children</th><th
372
+ class="num">×</th
373
+ ></tr
374
+ ></thead
375
+ >
376
+ <tbody>
377
+ {#each prof.components.slice(0, 8) as c, i (c.name + c.file + i)}
378
+ <tr
379
+ ><td title={c.file ?? ''}>{c.name}</td><td class="num">{ms(c.self_ms)}</td><td
380
+ class="num">{ms(c.total_ms)}</td
381
+ ><td class="num">{c.instances ?? ''}</td></tr
382
+ >
383
+ {/each}
384
+ </tbody>
385
+ </table>
386
+ {/if}
387
+
388
+ {#if prof.network && prof.network.count}
389
+ <h3>
390
+ Outbound calls <span class="muted"
391
+ >{prof.network.count} · {ms(prof.network.total_ms)}{prof.network.sequential_ms
392
+ ? ` · ${ms(prof.network.sequential_ms)} one after another`
393
+ : ''}</span
394
+ >
395
+ </h3>
396
+ <table>
397
+ <tbody>
398
+ {#each prof.network.calls.slice(0, 10) as c, k (c.url + k)}
399
+ <tr
400
+ ><td class="url" title={c.url}>{c.method} {short_url(c.url)}</td><td class="num"
401
+ >{c.status ?? ''}</td
402
+ ><td class="num">{ms(c.wait_ms)}</td></tr
403
+ >
404
+ {/each}
405
+ </tbody>
406
+ </table>
407
+ {/if}
408
+ {/if}
409
+ {/if}
410
+ </div>
411
+
412
+ <style>
413
+ .pf {
414
+ display: flex;
415
+ flex-direction: column;
416
+ gap: 8px;
417
+ }
418
+ .row {
419
+ display: flex;
420
+ align-items: center;
421
+ gap: 8px;
422
+ flex-wrap: wrap;
423
+ }
424
+ label {
425
+ display: inline-flex;
426
+ align-items: center;
427
+ gap: 5px;
428
+ color: #94a3b8;
429
+ }
430
+ input {
431
+ background: #0d1526;
432
+ border: 1px solid rgba(148, 163, 184, 0.25);
433
+ border-radius: 6px;
434
+ color: #e2e8f0;
435
+ font: inherit;
436
+ padding: 4px 7px;
437
+ }
438
+ input:not(.n) {
439
+ width: 13rem;
440
+ }
441
+ .n {
442
+ width: 3.5rem;
443
+ }
444
+ .go {
445
+ padding: 5px 12px;
446
+ border-radius: 7px;
447
+ border: 1px solid #0d9488;
448
+ background: #14b8a6;
449
+ color: #022;
450
+ font: inherit;
451
+ font-weight: 600;
452
+ cursor: pointer;
453
+ }
454
+ .go:disabled {
455
+ opacity: 0.8;
456
+ cursor: progress;
457
+ }
458
+ .open {
459
+ margin-left: auto;
460
+ color: #94a3b8;
461
+ text-decoration: none;
462
+ border: 1px solid rgba(148, 163, 184, 0.3);
463
+ border-radius: 7px;
464
+ padding: 4px 9px;
465
+ }
466
+ .open:hover {
467
+ color: #e2e8f0;
468
+ }
469
+ .bar {
470
+ height: 4px;
471
+ background: rgba(148, 163, 184, 0.15);
472
+ border-radius: 2px;
473
+ overflow: hidden;
474
+ }
475
+ .bar i {
476
+ display: block;
477
+ height: 100%;
478
+ background: #14b8a6;
479
+ transition: width 0.6s linear;
480
+ }
481
+ .err {
482
+ color: #fca5a5;
483
+ margin: 0;
484
+ }
485
+ .hint,
486
+ .muted {
487
+ color: #94a3b8;
488
+ }
489
+ .hint {
490
+ line-height: 1.6;
491
+ max-width: 56ch;
492
+ }
493
+ code {
494
+ color: #5eead4;
495
+ }
496
+ h3 {
497
+ margin: 8px 0 0;
498
+ font-size: 11px;
499
+ color: #cbd5e1;
500
+ text-transform: uppercase;
501
+ letter-spacing: 0.06em;
502
+ }
503
+ .head {
504
+ display: flex;
505
+ align-items: stretch;
506
+ gap: 8px;
507
+ flex-wrap: wrap;
508
+ }
509
+ .score {
510
+ display: flex;
511
+ flex-direction: column;
512
+ align-items: center;
513
+ justify-content: center;
514
+ width: 54px;
515
+ border-radius: 10px;
516
+ border: 1px solid rgba(148, 163, 184, 0.3);
517
+ }
518
+ .score b {
519
+ font-size: 18px;
520
+ }
521
+ .score span {
522
+ font-size: 10px;
523
+ color: #94a3b8;
524
+ }
525
+ .score.good b {
526
+ color: #4ade80;
527
+ }
528
+ .score.fair b {
529
+ color: #fbbf24;
530
+ }
531
+ .score.poor b {
532
+ color: #f87171;
533
+ }
534
+ .stat {
535
+ display: flex;
536
+ flex-direction: column;
537
+ padding: 5px 10px;
538
+ border-radius: 8px;
539
+ border: 1px solid rgba(148, 163, 184, 0.25);
540
+ }
541
+ .stat .k {
542
+ font-size: 10px;
543
+ color: #94a3b8;
544
+ }
545
+ .stat .v {
546
+ font-size: 14px;
547
+ font-weight: 700;
548
+ }
549
+ .stat .v.ok {
550
+ color: #4ade80;
551
+ }
552
+ .stat .s {
553
+ font-size: 10px;
554
+ color: #94a3b8;
555
+ }
556
+ .when {
557
+ margin-left: auto;
558
+ align-self: flex-end;
559
+ color: #94a3b8;
560
+ font-size: 10px;
561
+ }
562
+ .cats {
563
+ display: flex;
564
+ flex-wrap: wrap;
565
+ gap: 4px;
566
+ }
567
+ .cat {
568
+ font-size: 10px;
569
+ padding: 1px 7px;
570
+ border-radius: 999px;
571
+ border: 1px solid rgba(148, 163, 184, 0.3);
572
+ color: #cbd5e1;
573
+ }
574
+ .cat.good b {
575
+ color: #4ade80;
576
+ }
577
+ .cat .lost {
578
+ color: #fbbf24;
579
+ font-style: normal;
580
+ }
581
+ .score i {
582
+ font-style: normal;
583
+ font-size: 9px;
584
+ }
585
+ .score i.up {
586
+ color: #4ade80;
587
+ }
588
+ .score i.down {
589
+ color: #f87171;
590
+ }
591
+ .lazyrow td {
592
+ opacity: 0.6;
593
+ }
594
+ .anchors {
595
+ list-style: none;
596
+ margin: 0;
597
+ padding: 0;
598
+ display: flex;
599
+ flex-direction: column;
600
+ gap: 3px;
601
+ }
602
+ .anchors button {
603
+ width: 100%;
604
+ display: flex;
605
+ gap: 8px;
606
+ align-items: baseline;
607
+ text-align: left;
608
+ font: inherit;
609
+ color: #e2e8f0;
610
+ background: rgba(148, 163, 184, 0.07);
611
+ border: 1px solid transparent;
612
+ border-radius: 7px;
613
+ padding: 5px 8px;
614
+ cursor: pointer;
615
+ }
616
+ .anchors button:hover,
617
+ .anchors .pinned button {
618
+ border-color: rgba(244, 114, 182, 0.55);
619
+ background: rgba(244, 114, 182, 0.1);
620
+ }
621
+ .anchors b {
622
+ font-weight: 600;
623
+ white-space: nowrap;
624
+ }
625
+ .holds {
626
+ max-width: 220px;
627
+ overflow: hidden;
628
+ text-overflow: ellipsis;
629
+ white-space: nowrap;
630
+ }
631
+ .cat.fair b {
632
+ color: #fbbf24;
633
+ }
634
+ .cat.poor b {
635
+ color: #f87171;
636
+ }
637
+ .parts {
638
+ list-style: none;
639
+ margin: 0;
640
+ padding: 0;
641
+ display: flex;
642
+ flex-direction: column;
643
+ gap: 2px;
644
+ }
645
+ .pm {
646
+ display: inline-block;
647
+ width: 64px;
648
+ color: #4ade80;
649
+ }
650
+ .tag {
651
+ margin-left: 6px;
652
+ font-size: 10px;
653
+ color: #7dd3fc;
654
+ }
655
+ .findings {
656
+ list-style: none;
657
+ margin: 0;
658
+ padding: 0;
659
+ display: flex;
660
+ flex-direction: column;
661
+ gap: 5px;
662
+ }
663
+ .findings li {
664
+ padding: 6px 9px;
665
+ border-radius: 8px;
666
+ border-left: 3px solid #38bdf8;
667
+ background: rgba(148, 163, 184, 0.07);
668
+ }
669
+ .findings li.warn {
670
+ border-left-color: #f59e0b;
671
+ }
672
+ .findings li.error {
673
+ border-left-color: #ef4444;
674
+ }
675
+ .findings li.lit {
676
+ cursor: default;
677
+ }
678
+ .findings li.lit:hover {
679
+ background: rgba(56, 189, 248, 0.12);
680
+ }
681
+ .msg,
682
+ .fix {
683
+ margin: 0;
684
+ line-height: 1.5;
685
+ }
686
+ .fix {
687
+ color: #94a3b8;
688
+ margin-top: 3px;
689
+ }
690
+ table {
691
+ border-collapse: collapse;
692
+ width: 100%;
693
+ }
694
+ th {
695
+ text-align: left;
696
+ color: #94a3b8;
697
+ font-weight: 600;
698
+ border-bottom: 1px solid rgba(148, 163, 184, 0.2);
699
+ padding: 3px 4px;
700
+ }
701
+ td {
702
+ padding: 2px 4px;
703
+ border-bottom: 1px solid rgba(148, 163, 184, 0.08);
704
+ }
705
+ tbody tr.here {
706
+ cursor: pointer;
707
+ }
708
+ tbody tr.here:hover {
709
+ background: rgba(148, 163, 184, 0.1);
710
+ }
711
+ .num {
712
+ text-align: right;
713
+ white-space: nowrap;
714
+ }
715
+ .wake {
716
+ color: #94a3b8;
717
+ margin-left: 5px;
718
+ font-size: 10px;
719
+ }
720
+ .warn {
721
+ color: #fbbf24;
722
+ }
723
+ .url {
724
+ max-width: 300px;
725
+ overflow: hidden;
726
+ text-overflow: ellipsis;
727
+ white-space: nowrap;
728
+ }
729
+ .notice {
730
+ display: flex;
731
+ flex-direction: column;
732
+ gap: 8px;
733
+ max-width: 44ch;
734
+ color: #cbd5e1;
735
+ line-height: 1.6;
736
+ }
737
+ .notice .h {
738
+ color: #e2e8f0;
739
+ font-weight: 600;
740
+ }
741
+ .notice code,
742
+ .notice pre {
743
+ background: rgba(148, 163, 184, 0.16);
744
+ color: #5eead4;
745
+ border-radius: 5px;
746
+ }
747
+ .notice code {
748
+ padding: 1px 5px;
749
+ }
750
+ .notice pre {
751
+ padding: 8px 10px;
752
+ margin: 0;
753
+ overflow-x: auto;
754
+ }
755
+ </style>