@ogygia/devtools 0.0.0 → 0.11.0

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 +105 -6
@@ -0,0 +1,189 @@
1
+ import { Analysis, FrameCategory, GroupStat } from "../analyze.js";
2
+ import { NetCall, UpstreamTrace } from "../net.js";
3
+ import { IoOp } from "../async-io.js";
4
+ import { ClientIslandStat, MemSample, ReportExtras, ReportMeta, RequestEntry } from "../report.js";
5
+ import { HoleStat, IslandStat, SeedKeyStat } from "@ogygia/core/internal/server";
6
+ export interface IslandRow {
7
+ name: string;
8
+ entry: string;
9
+ fp: string;
10
+ /** distinct fingerprints merged into this row (different props per copy) */
11
+ variants: number;
12
+ copies: number;
13
+ wake: string;
14
+ /** the component's SSR time per render (joined from the components table by name), or null */
15
+ ssr_ms: number | null;
16
+ props_bytes: number;
17
+ canonical_bytes: number;
18
+ json: boolean;
19
+ culprit: string | null;
20
+ refs: number;
21
+ ref_keys: string[];
22
+ /** unique bytes of module + preload hints (null when unweighed: dev, or an unreachable asset) */
23
+ js_bytes: number | null;
24
+ /** of `js_bytes`, what no other waking island uses (what dropping it would save) */
25
+ js_only: number | null;
26
+ /** each module with its bytes, heaviest first, and what the build packed into it */
27
+ /** its JS files; `own`: the island's own entry file (its location, not its identity) */
28
+ modules: {
29
+ url: string;
30
+ bytes: number | null;
31
+ inside: string[] | null;
32
+ own: boolean;
33
+ }[];
34
+ interactivity: IslandStat["interactivity"];
35
+ /** the sum of every interactivity marker; -1 when the build did not scan it */
36
+ marks: number;
37
+ client: ClientIslandStat | null;
38
+ /** what to do about this island, when the numbers say something */
39
+ advice: string | null;
40
+ client_ms: number;
41
+ js_sort: number;
42
+ }
43
+ /** One row per island fingerprint, joined with the server (components), the build (weights,
44
+ * interactivity) and the browser (beacon). */
45
+ export declare function island_rows(a: Analysis, meta: ReportMeta, extras: ReportExtras): IslandRow[];
46
+ export interface SeedRow extends SeedKeyStat {
47
+ readers_names: string[];
48
+ referenced_names: string[];
49
+ pct: number;
50
+ }
51
+ /** The seed explainer's rows: every top-level page.data key, biggest first, with why it ships. */
52
+ export declare function seed_rows(meta: ReportMeta): {
53
+ rows: SeedRow[];
54
+ whole_by: string[];
55
+ total: number;
56
+ } | null;
57
+ export interface HoleRow extends HoleStat {
58
+ hit: number;
59
+ miss: number;
60
+ none: number;
61
+ requests: number;
62
+ avg_ms: number | null;
63
+ verdict: string;
64
+ }
65
+ /** The holes the page rendered, with what their endpoint did during the window. */
66
+ export declare function hole_rows(meta: ReportMeta): HoleRow[];
67
+ /** The size to show for a call: the DECODED body when we measured it (robust — a cloned-stream count),
68
+ * else the wire size from content-length, else undefined. */
69
+ export declare function net_size(c: NetCall): number | undefined;
70
+ export interface TreeNode {
71
+ label: string;
72
+ value: number;
73
+ color: string;
74
+ pct?: number;
75
+ sub?: string;
76
+ children?: TreeNode[];
77
+ }
78
+ /** Root → category → leaf hierarchy of self-time, capped per group (long tail → one "(N more)"). */
79
+ export declare function build_treemap(a: Analysis): TreeNode | null;
80
+ /** Distinct categories present in the treemap, for the legend. */
81
+ export declare function treemap_legend(a: Analysis): FrameCategory[];
82
+ export interface BudgetSeg {
83
+ label: string;
84
+ cat: FrameCategory;
85
+ ms: number;
86
+ pct: number;
87
+ }
88
+ /** The wall-clock triage bar: busy buckets + one canonical idle segment + an "other" remainder. */
89
+ export declare function budget_segments(a: Analysis): BudgetSeg[];
90
+ export interface WaitRow {
91
+ caller: string;
92
+ kind: string;
93
+ count: number;
94
+ ms: number;
95
+ open: number;
96
+ }
97
+ /** Where the server WAITED, attributed to the function that started the I/O (net + async-hooks). */
98
+ export declare function waiting_rows(net: NetCall[], io: IoOp[]): WaitRow[];
99
+ export interface HostRow {
100
+ host: string;
101
+ count: number;
102
+ total: number;
103
+ p50: number;
104
+ max: number;
105
+ errors: number;
106
+ }
107
+ export declare function top_hosts(net: NetCall[]): HostRow[];
108
+ export declare function short_url(url: string): string;
109
+ /** The request detail a waterfall bar opens in the side panel (a serializable slice of NetCall). */
110
+ export interface WfCall {
111
+ method: string;
112
+ url: string;
113
+ status: number;
114
+ ms: number;
115
+ body_ms?: number;
116
+ bytes?: number;
117
+ transfer_bytes?: number;
118
+ encoding?: string;
119
+ type?: string;
120
+ req_bytes?: number;
121
+ req_payload?: string;
122
+ route: string | null;
123
+ path: string | null;
124
+ /** the first-party call path above the caller, nearest first */
125
+ callers?: string[];
126
+ /** the upstream's own Server-Timing entries */
127
+ timings?: {
128
+ name: string;
129
+ ms: number;
130
+ desc?: string;
131
+ }[];
132
+ /** the upstream profiler's own picture of this request (nested trace) */
133
+ trace?: UpstreamTrace;
134
+ caller?: string;
135
+ headers?: Record<string, string>;
136
+ error?: string;
137
+ }
138
+ export interface WfRow {
139
+ left: number;
140
+ width: number;
141
+ bodyPct: number;
142
+ err: boolean;
143
+ label: string;
144
+ title: string;
145
+ rightAnchored: boolean;
146
+ /** Compact size shown INSIDE the bar (decoded, else transfer). Empty when unknown. */
147
+ sizeLabel: string;
148
+ /** Everything the side panel shows when the bar is clicked. */
149
+ call: WfCall;
150
+ }
151
+ /** Network waterfall bars, spanned from the first call to the last call's end. */
152
+ export declare function waterfall_rows(net: NetCall[]): WfRow[];
153
+ export interface RequestRow {
154
+ method: string;
155
+ path: string;
156
+ route: string | null;
157
+ status: number;
158
+ internal: boolean;
159
+ tags: string;
160
+ /** how many identical requests the window saw (page mode: one per run) */
161
+ count: number;
162
+ inflight: number;
163
+ net_count: number;
164
+ net_ms: number;
165
+ cpu_ms: number;
166
+ wait_ms: number;
167
+ ms: number;
168
+ max_ms: number;
169
+ }
170
+ /**
171
+ * The "requests during the window" rows. Page mode renders the same page N times, so the log
172
+ * holds N copies of the page request and N copies of every call it makes: fold identical
173
+ * requests (method, path, route, status, tags) into one row with the count and the MEDIAN of each
174
+ * number, biggest first. A window recording keeps every request as it happened.
175
+ */
176
+ export declare function request_rows(requests: RequestEntry[], fold: boolean): RequestRow[];
177
+ export interface Spark {
178
+ pts: string;
179
+ min: number;
180
+ max: number;
181
+ w: number;
182
+ h: number;
183
+ pad: number;
184
+ first: MemSample;
185
+ last: MemSample;
186
+ }
187
+ /** Sparkline geometry for rss over the window. */
188
+ export declare function spark(mem: MemSample[]): Spark | null;
189
+ export type { GroupStat };
@@ -0,0 +1,393 @@
1
+ import { group_islands, hole_economics, island_js_bytes, island_js_unique, island_name, island_rows_of } from "../report.js";
2
+ import { io_kind } from "../async-io.js";
3
+ import { CATEGORY_COLOR, CATEGORY_LABEL, fmt_bytes } from "./format.js";
4
+ import { hazard_words } from "@ogygia/core/insights";
5
+ // src/profiler/ui/report-data.ts
6
+ /** One row per island fingerprint, joined with the server (components), the build (weights,
7
+ * interactivity) and the browser (beacon). */
8
+ function island_rows(a, meta, extras) {
9
+ const runs = meta.trigger === "page" ? Math.max(meta.runs?.length ?? 1, 1) : 1;
10
+ const by_name = new Map(a.components.map((c) => [c.name, c]));
11
+ const client = new Map((extras.client ?? []).map((c) => [c.entry, c]));
12
+ const beacon_seen = client.size > 0;
13
+ const grouped = group_islands(island_rows_of(meta));
14
+ const only = island_js_unique(grouped, extras.weights);
15
+ return grouped.map((r) => {
16
+ const name = island_name(r);
17
+ const comp = by_name.get(name);
18
+ const cl = client.get(r.entry) ?? null;
19
+ const modules = [...new Set([r.module_url, ...r.hints].filter(Boolean))].map((url) => ({
20
+ url,
21
+ bytes: extras.weights?.[url] ?? null,
22
+ inside: extras.contents?.[url] ?? null,
23
+ own: url === r.module_url
24
+ })).sort((x, y) => (y.bytes ?? -1) - (x.bytes ?? -1));
25
+ const js_bytes = island_js_bytes(r, extras.weights);
26
+ const js_only = only?.get(r.entry) ?? null;
27
+ const i = r.interactivity;
28
+ const marks = i ? i.handlers + i.state + i.effects + i.binds + i.actions + (i.remotes ?? 0) + (i.awaits ?? 0) + (i.shared ?? 0) : -1;
29
+ const lines = extras.hazards?.[r.entry] ?? [];
30
+ const render_read = lines.find((h) => h.kind === "browser" && !h.guard);
31
+ let advice = null;
32
+ if (beacon_seen && !cl && (r.wake === "load" || r.wake === "idle" || r.wake === "visible")) advice = `Never reported hydrating in your visits while other islands did. A '${r.wake}' island that ${r.wake === "visible" ? "never intersects the viewport (can the page scroll? is it hidden?)" : "throws on wake (the browser console has it)"} never wakes.`;
33
+ else if (cl && cl.recovered > 0) advice = `${cl.recovered} of ${cl.n} hydrations threw the server DOM away and re-rendered: the markup the browser found was not what the server sent. It paid for the render twice and flashed.` + (cl.reason ? ` Why: ${cl.reason}.` : "") + (lines.length ? ` ${hazard_words(lines)}` : " Look for a post-SSR pass, or a script that edits it before the wake.");
34
+ else if (render_read) advice = `Its own code reads ${render_read.reads ?? "a browser-only value"} while rendering (${render_read.file.split("/").pop()}:${render_read.line}): the browser draws it differently, so its markup changes as it wakes. Read it after the wake ($effect, onMount), or pass it in as a prop.`;
35
+ else if (marks === 0 && r.wake !== "none") advice = "No handlers, state, effects, binds, actions, remote functions, awaits or shared state in its components: ship it as a lake (wake: 'none') and the JS never loads.";
36
+ else if (r.count >= 10 && (r.wake === "load" || r.wake === "idle" || r.wake === "visible")) advice = `${r.count} copies each wake on ${r.wake}${(r.variants ?? 1) > 1 ? ` with ${r.variants} different props sidecars` : ""}: one island around the list hydrates once, or wake: 'interaction' pays only when touched.`;
37
+ else if (!r.json && r.culprit) advice = `Its props use devalue because of ${r.culprit}: a string or number there puts them on the JSON lane.`;
38
+ else if (js_bytes !== null && js_bytes >= 153600) advice = js_only !== null && js_only < js_bytes * .3 ? `${fmt_bytes(js_bytes)} of JS to wake it, but only ${fmt_bytes(js_only)} is its own: the rest is shared with other islands and loads anyway. Deferring it saves little; slim the shared code instead.` : `${fmt_bytes(js_bytes)} of JS for one island${js_only !== null ? ` (${fmt_bytes(js_only)} of it only this island needs)` : ""}: the heaviest module below is the import to move server-side or behind a dynamic import.`;
39
+ else if (cl && cl.load_p50_ms >= cl.p50_ms * .6 && cl.p50_ms >= 50) advice = `In the browser most of its ${cl.p50_ms.toFixed(0)} ms is module load: an earlier preload or a smaller closure helps more than faster code.`;
40
+ return {
41
+ name,
42
+ entry: r.entry,
43
+ fp: r.fp,
44
+ variants: r.variants ?? 1,
45
+ copies: r.count,
46
+ wake: r.wake,
47
+ ssr_ms: comp ? comp.total_ms / runs : null,
48
+ props_bytes: r.props_bytes,
49
+ canonical_bytes: r.canonical_bytes,
50
+ json: r.json,
51
+ culprit: r.culprit,
52
+ refs: r.refs,
53
+ ref_keys: r.ref_keys,
54
+ js_bytes,
55
+ js_only,
56
+ modules,
57
+ interactivity: i,
58
+ marks,
59
+ client: cl,
60
+ advice,
61
+ client_ms: cl?.p50_ms ?? 0,
62
+ js_sort: js_bytes ?? 0
63
+ };
64
+ });
65
+ }
66
+ /** The seed explainer's rows: every top-level page.data key, biggest first, with why it ships. */
67
+ function seed_rows(meta) {
68
+ const og = meta.requests.find((r) => r.og?.seed)?.og;
69
+ if (!og?.seed) return null;
70
+ const total = og.seed.keys.reduce((s, k) => s + k.bytes, 0) || 1;
71
+ return {
72
+ rows: og.seed.keys.map((k) => ({
73
+ ...k,
74
+ readers_names: k.readers,
75
+ referenced_names: k.referenced_by,
76
+ pct: k.bytes / total * 100
77
+ })),
78
+ whole_by: og.seed.whole_by,
79
+ total
80
+ };
81
+ }
82
+ /** The holes the page rendered, with what their endpoint did during the window. */
83
+ function hole_rows(meta) {
84
+ const og = meta.requests.find((r) => r.og?.hole_rows?.length)?.og;
85
+ const econ = hole_economics(meta);
86
+ return (og?.hole_rows ?? []).map((h) => {
87
+ const e = econ.get(h.id);
88
+ const requests = e ? e.hit + e.miss + e.none : 0;
89
+ const verdict = h.ttl > 0 && requests >= 2 && e.hit === 0 ? "maxAge set, cache never hit" : h.ttl > 0 && e && e.hit > 0 ? `${Math.round(e.hit / requests * 100)}% from cache` : h.ttl > 0 ? "cached (no requests in the window)" : "renders fresh on every visit";
90
+ return {
91
+ ...h,
92
+ hit: e?.hit ?? 0,
93
+ miss: e?.miss ?? 0,
94
+ none: e?.none ?? 0,
95
+ requests,
96
+ avg_ms: e && requests ? e.ms / requests : null,
97
+ verdict
98
+ };
99
+ });
100
+ }
101
+ /** The size to show for a call: the DECODED body when we measured it (robust — a cloned-stream count),
102
+ * else the wire size from content-length, else undefined. */
103
+ function net_size(c) {
104
+ return c.bytes ?? c.transfer_bytes;
105
+ }
106
+ const round1 = (n) => Math.round(n * 10) / 10;
107
+ /** Root → category → leaf hierarchy of self-time, capped per group (long tail → one "(N more)"). */
108
+ function build_treemap(a) {
109
+ const leaves = a.functions.filter((f) => f.self_ms > 0).map((f) => ({
110
+ name: f.name,
111
+ cat: f.category,
112
+ value: f.self_ms,
113
+ url: f.url
114
+ }));
115
+ for (const b of a.buckets) {
116
+ if (b.category === "gc" && b.self_ms > 0) leaves.push({
117
+ name: "garbage collection",
118
+ cat: "gc",
119
+ value: b.self_ms,
120
+ url: ""
121
+ });
122
+ if (b.category === "v8" && b.self_ms > 0) leaves.push({
123
+ name: "v8 internals",
124
+ cat: "v8",
125
+ value: b.self_ms,
126
+ url: ""
127
+ });
128
+ }
129
+ if (!leaves.length) return null;
130
+ const groups = /* @__PURE__ */ new Map();
131
+ for (const l of leaves) {
132
+ const g = groups.get(l.cat) ?? [];
133
+ g.push(l);
134
+ groups.set(l.cat, g);
135
+ }
136
+ const cat_cells = [];
137
+ for (const [cat, list] of groups) {
138
+ list.sort((x, y) => y.value - x.value);
139
+ const keep = list.slice(0, 16);
140
+ const tail = list.slice(16);
141
+ if (tail.length) {
142
+ const sum = tail.reduce((s, i) => s + i.value, 0);
143
+ keep.push({
144
+ name: `(${tail.length} more)`,
145
+ cat,
146
+ value: sum,
147
+ url: ""
148
+ });
149
+ }
150
+ cat_cells.push({
151
+ cat,
152
+ value: list.reduce((s, i) => s + i.value, 0),
153
+ leaves: keep
154
+ });
155
+ }
156
+ cat_cells.sort((x, y) => y.value - x.value);
157
+ const busy = a.busy_ms || 1;
158
+ return {
159
+ label: "all",
160
+ value: cat_cells.reduce((s, c) => s + c.value, 0),
161
+ color: "#6b7280",
162
+ children: cat_cells.map((c) => ({
163
+ label: CATEGORY_LABEL[c.cat],
164
+ value: round1(c.value),
165
+ color: CATEGORY_COLOR[c.cat],
166
+ pct: round1(c.value / busy * 100),
167
+ children: c.leaves.map((l) => ({
168
+ label: l.name,
169
+ sub: l.url,
170
+ value: round1(l.value),
171
+ color: CATEGORY_COLOR[c.cat],
172
+ pct: round1(l.value / busy * 100)
173
+ }))
174
+ }))
175
+ };
176
+ }
177
+ /** Distinct categories present in the treemap, for the legend. */
178
+ function treemap_legend(a) {
179
+ const cats = /* @__PURE__ */ new Set();
180
+ for (const f of a.functions) if (f.self_ms > 0) cats.add(f.category);
181
+ for (const b of a.buckets) if ((b.category === "gc" || b.category === "v8") && b.self_ms > 0) cats.add(b.category);
182
+ return [...cats];
183
+ }
184
+ /** The wall-clock triage bar: busy buckets + one canonical idle segment + an "other" remainder. */
185
+ function budget_segments(a) {
186
+ const dur = a.duration_ms || 1;
187
+ const segs = a.buckets.filter((b) => b.self_ms > 0 && b.category !== "idle").map((b) => ({
188
+ label: b.key,
189
+ cat: b.category,
190
+ ms: b.self_ms
191
+ }));
192
+ if (a.idle_ms > 0) segs.push({
193
+ label: "idle / waiting",
194
+ cat: "idle",
195
+ ms: a.idle_ms
196
+ });
197
+ const acc = segs.reduce((s, x) => s + x.ms, 0);
198
+ if (dur - acc > dur * .02) segs.push({
199
+ label: "other",
200
+ cat: "unknown",
201
+ ms: dur - acc
202
+ });
203
+ segs.sort((x, y) => y.ms - x.ms);
204
+ return segs.map((s) => ({
205
+ ...s,
206
+ pct: s.ms / dur * 100
207
+ }));
208
+ }
209
+ /** Where the server WAITED, attributed to the function that started the I/O (net + async-hooks). */
210
+ function waiting_rows(net, io) {
211
+ const rows = /* @__PURE__ */ new Map();
212
+ const add = (caller, kind, ms, open = false) => {
213
+ const key = caller + "|" + kind;
214
+ const r = rows.get(key) ?? {
215
+ caller,
216
+ kind,
217
+ count: 0,
218
+ ms: 0,
219
+ open: 0
220
+ };
221
+ r.count++;
222
+ r.ms += ms;
223
+ if (open) r.open++;
224
+ rows.set(key, r);
225
+ };
226
+ for (const c of net) if (c.ms >= 0 && c.caller) add(c.caller, "http", c.ms + (c.body_ms ?? 0));
227
+ for (const o of io) if (o.caller && !o.open) add(o.caller, io_kind(o.type), o.ms);
228
+ return [...rows.values()].filter((r) => r.ms >= .5).sort((a, b) => b.ms - a.ms).slice(0, 30);
229
+ }
230
+ function top_hosts(net) {
231
+ const by_host = /* @__PURE__ */ new Map();
232
+ const errors = /* @__PURE__ */ new Map();
233
+ for (const c of net) {
234
+ const t = c.ms + (c.body_ms ?? 0);
235
+ let list = by_host.get(c.host);
236
+ if (!list) by_host.set(c.host, list = []);
237
+ list.push(t);
238
+ if (c.error) errors.set(c.host, (errors.get(c.host) ?? 0) + 1);
239
+ }
240
+ return [...by_host.entries()].map(([host, list]) => {
241
+ const sorted = [...list].sort((a, b) => a - b);
242
+ return {
243
+ host,
244
+ count: list.length,
245
+ total: Math.round(list.reduce((a, c) => a + c, 0) * 100) / 100,
246
+ p50: sorted[Math.floor(sorted.length / 2)] ?? 0,
247
+ max: sorted.at(-1) ?? 0,
248
+ errors: errors.get(host) ?? 0
249
+ };
250
+ }).sort((a, b) => b.total - a.total);
251
+ }
252
+ function short_url(url) {
253
+ try {
254
+ const u = new URL(url);
255
+ const path = u.pathname.length > 48 ? u.pathname.slice(0, 45) + "…" : u.pathname;
256
+ return u.host + path + (u.search ? "?…" : "");
257
+ } catch {
258
+ return url.length > 64 ? url.slice(0, 61) + "…" : url;
259
+ }
260
+ }
261
+ function wf_url(url) {
262
+ try {
263
+ const u = new URL(url);
264
+ const s = u.pathname + u.search;
265
+ return s.length > 44 ? s.slice(0, 43) + "…" : s;
266
+ } catch {
267
+ return url.length > 44 ? url.slice(0, 43) + "…" : url;
268
+ }
269
+ }
270
+ /** Network waterfall bars, spanned from the first call to the last call's end. */
271
+ function waterfall_rows(net) {
272
+ if (net.length < 2 || net.length > 120) return [];
273
+ const sorted = [...net].sort((a, b) => a.epoch - b.epoch);
274
+ const t0 = sorted[0].epoch;
275
+ const last_end = sorted.reduce((m, c) => Math.max(m, c.epoch + c.ms + (c.body_ms ?? 0)), 0);
276
+ const span = Math.max(last_end - t0, 1);
277
+ return sorted.map((c) => {
278
+ const left = Math.max(0, (c.epoch - t0) / span * 100);
279
+ const dur = c.ms + (c.body_ms ?? 0);
280
+ const width = Math.min(100 - left, Math.max(dur / span * 100, .3));
281
+ const sz = net_size(c);
282
+ return {
283
+ left,
284
+ width,
285
+ bodyPct: dur > 0 && c.body_ms ? c.body_ms / dur * 100 : 0,
286
+ err: !!c.error,
287
+ title: c.url,
288
+ label: `${c.method} ${wf_url(c.url)} — ${dur >= 100 ? dur.toFixed(0) : dur >= 10 ? dur.toFixed(1) : dur.toFixed(2)} ms`,
289
+ rightAnchored: left + width > 62,
290
+ sizeLabel: sz != null ? fmt_bytes(sz) : "",
291
+ call: {
292
+ method: c.method,
293
+ url: c.url,
294
+ status: c.status,
295
+ ms: c.ms,
296
+ body_ms: c.body_ms,
297
+ bytes: c.bytes,
298
+ transfer_bytes: c.transfer_bytes,
299
+ encoding: c.encoding,
300
+ type: c.type,
301
+ req_bytes: c.req_bytes,
302
+ req_payload: c.req_payload,
303
+ route: c.route,
304
+ path: c.path,
305
+ caller: c.caller,
306
+ callers: c.callers,
307
+ timings: c.timings,
308
+ trace: c.trace,
309
+ headers: c.headers,
310
+ error: c.error
311
+ }
312
+ };
313
+ });
314
+ }
315
+ const med = (xs) => xs.length ? [...xs].sort((a, b) => a - b)[Math.floor(xs.length / 2)] : 0;
316
+ /**
317
+ * The "requests during the window" rows. Page mode renders the same page N times, so the log
318
+ * holds N copies of the page request and N copies of every call it makes: fold identical
319
+ * requests (method, path, route, status, tags) into one row with the count and the MEDIAN of each
320
+ * number, biggest first. A window recording keeps every request as it happened.
321
+ */
322
+ function request_rows(requests, fold) {
323
+ const tags_of = (e) => e.tags ? Object.entries(e.tags).map(([k, v]) => `${k}=${v}`).join(" ") : "";
324
+ if (!fold) return requests.map((e) => ({
325
+ method: e.method,
326
+ path: e.path,
327
+ route: e.route,
328
+ status: e.status,
329
+ internal: !!e.internal,
330
+ tags: tags_of(e),
331
+ count: 1,
332
+ inflight: e.inflight,
333
+ net_count: e.net_count,
334
+ net_ms: e.net_ms,
335
+ cpu_ms: e.cpu_ms,
336
+ wait_ms: Math.max(0, e.ms - e.cpu_ms),
337
+ ms: e.ms,
338
+ max_ms: e.ms
339
+ }));
340
+ const groups = /* @__PURE__ */ new Map();
341
+ for (const e of requests) {
342
+ const key = [
343
+ e.method,
344
+ e.path,
345
+ e.route ?? "",
346
+ e.status,
347
+ tags_of(e)
348
+ ].join("\0");
349
+ const g = groups.get(key);
350
+ if (g) g.push(e);
351
+ else groups.set(key, [e]);
352
+ }
353
+ return [...groups.values()].map((list) => {
354
+ const e = list[0];
355
+ return {
356
+ method: e.method,
357
+ path: e.path,
358
+ route: e.route,
359
+ status: e.status,
360
+ internal: list.some((x) => x.internal),
361
+ tags: tags_of(e),
362
+ count: list.length,
363
+ inflight: med(list.map((x) => x.inflight)),
364
+ net_count: med(list.map((x) => x.net_count)),
365
+ net_ms: med(list.map((x) => x.net_ms)),
366
+ cpu_ms: med(list.map((x) => x.cpu_ms)),
367
+ wait_ms: med(list.map((x) => Math.max(0, x.ms - x.cpu_ms))),
368
+ ms: med(list.map((x) => x.ms)),
369
+ max_ms: Math.max(...list.map((x) => x.ms))
370
+ };
371
+ }).sort((a, b) => b.ms - a.ms);
372
+ }
373
+ /** Sparkline geometry for rss over the window. */
374
+ function spark(mem) {
375
+ if (mem.length < 3) return null;
376
+ const w = 640, h = 64, pad = 4;
377
+ const t_max = Math.max(mem.at(-1).t, 1);
378
+ const values = mem.map((m) => m.rss);
379
+ const min = Math.min(...values);
380
+ const max = Math.max(...values);
381
+ const range = Math.max(max - min, 1);
382
+ return {
383
+ pts: mem.map((m) => `${pad + m.t / t_max * 632},${60 - (m.rss - min) / range * 56}`).join(" "),
384
+ min,
385
+ max,
386
+ w,
387
+ h,
388
+ pad,
389
+ first: mem[0],
390
+ last: mem.at(-1)
391
+ };
392
+ }
393
+ export { budget_segments, build_treemap, hole_rows, island_rows, net_size, request_rows, seed_rows, short_url, spark, top_hosts, treemap_legend, waiting_rows, waterfall_rows };
@@ -0,0 +1,6 @@
1
+ /** the element the report page carries its `.ogp` bytes in, ONCE (base64): every button reads it
2
+ * here at click time instead of taking a copy as a prop (five buttons held 1.2 MB of copies) */
3
+ export declare const OGP_ELEMENT_ID = "og-report-ogp";
4
+ /** the page's `.ogp` bytes as base64, or '' (a report shown from this browser's kept copy) */
5
+ export declare function page_ogp(): string;
6
+ export declare function report_request(base: string, id: string, as: "html" | "json" | "dump" | "ogp", ogpB64?: string): Promise<Response>;
@@ -0,0 +1,50 @@
1
+ import { get_report } from "./store.js";
2
+ // src/profiler/ui/report-request.ts
3
+ /**
4
+ * Ask the server for a report's representation (`html`, `json`, `dump`) in a way that works on a
5
+ * serverless host, where the instance that made the report is rarely the one a later request lands
6
+ * on. The report rides WITH the request: the page's own `.ogp` bytes when it has them (compressed
7
+ * and encrypted: small, and only the server's key opens them), else this browser's kept copy
8
+ * (IndexedDB), gzipped. Request bodies are capped (512 KB by default on adapter-node), so a report
9
+ * is never sent as raw JSON. With neither, a plain GET by id (a server that still holds it).
10
+ */
11
+ /** the element the report page carries its `.ogp` bytes in, ONCE (base64): every button reads it
12
+ * here at click time instead of taking a copy as a prop (five buttons held 1.2 MB of copies) */
13
+ const OGP_ELEMENT_ID = "og-report-ogp";
14
+ /** the page's `.ogp` bytes as base64, or '' (a report shown from this browser's kept copy) */
15
+ function page_ogp() {
16
+ return typeof document === "undefined" ? "" : (document.getElementById("og-report-ogp")?.textContent ?? "").trim();
17
+ }
18
+ async function carried(id, ogpB64) {
19
+ ogpB64 ||= page_ogp();
20
+ if (ogpB64) {
21
+ const bin = atob(ogpB64);
22
+ const arr = new Uint8Array(bin.length);
23
+ for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
24
+ return {
25
+ body: arr,
26
+ headers: { "content-type": "application/octet-stream" }
27
+ };
28
+ }
29
+ const kept = await get_report(id);
30
+ if (!kept || typeof CompressionStream === "undefined") return null;
31
+ const gz = new Blob([JSON.stringify(kept.dump)]).stream().pipeThrough(new CompressionStream("gzip"));
32
+ return {
33
+ body: await new Response(gz).blob(),
34
+ headers: {
35
+ "content-type": "application/json",
36
+ "x-og-encoding": "gzip"
37
+ }
38
+ };
39
+ }
40
+ async function report_request(base, id, as, ogpB64 = "") {
41
+ const url = `${base}/report/${id}.${as}`;
42
+ const c = await carried(id, ogpB64);
43
+ return c ? fetch(url, {
44
+ method: "POST",
45
+ headers: c.headers,
46
+ body: c.body,
47
+ credentials: "same-origin"
48
+ }) : fetch(url, { credentials: "same-origin" });
49
+ }
50
+ export { OGP_ELEMENT_ID, page_ogp, report_request };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * ROW ANCHORS — a finding links to the row it is about (`#comp=<name>`, `#fn=<key>`); the table
3
+ * island opens that row and scrolls to it, on load and on every hash change. Shared by the two
4
+ * tables: give it the anchor kind and a getter for the open state, get back the row id maker and
5
+ * the effect to install.
6
+ */
7
+ export type RowKind = "comp" | "fn" | "island" | "seed";
8
+ export declare function row_id(kind: RowKind, key: string): string;
9
+ /** The `href` a finding uses to reach a row. */
10
+ export declare function row_href(anchor: string): string;
11
+ /** Install in a table island: opens the row named by the URL hash (if it is one of `keys`). */
12
+ export declare function follow_hash(kind: RowKind, keys: () => string[], open: (key: string) => void): () => void;
@@ -0,0 +1,44 @@
1
+ // src/profiler/ui/row-anchor.svelte.ts
2
+ function row_id(kind, key) {
3
+ let h = 5381;
4
+ for (let i = 0; i < key.length; i++) h = (Math.imul(h, 33) ^ key.charCodeAt(i)) >>> 0;
5
+ return `row-${kind}-${h.toString(36)}`;
6
+ }
7
+ /** The `href` a finding uses to reach a row. */
8
+ function row_href(anchor) {
9
+ const i = anchor.indexOf(":");
10
+ return `#${anchor.slice(0, i)}=${encodeURIComponent(anchor.slice(i + 1))}`;
11
+ }
12
+ /** Install in a table island: opens the row named by the URL hash (if it is one of `keys`). */
13
+ function follow_hash(kind, keys, open) {
14
+ const apply = () => {
15
+ const m = /^#(comp|fn|island|seed)=(.*)$/.exec(location.hash);
16
+ if (!m || m[1] !== kind) return;
17
+ let key;
18
+ try {
19
+ key = decodeURIComponent(m[2]);
20
+ } catch {
21
+ return;
22
+ }
23
+ if (!keys().includes(key)) return;
24
+ open(key);
25
+ setTimeout(() => {
26
+ document.getElementById(row_id(kind, key))?.scrollIntoView({
27
+ block: "center",
28
+ behavior: "smooth"
29
+ });
30
+ }, 30);
31
+ };
32
+ apply();
33
+ window.addEventListener("hashchange", apply);
34
+ const again = (e) => {
35
+ const href = (e.target?.closest?.("a"))?.getAttribute("href");
36
+ if (href && href === location.hash) setTimeout(apply, 0);
37
+ };
38
+ document.addEventListener("click", again);
39
+ return () => {
40
+ window.removeEventListener("hashchange", apply);
41
+ document.removeEventListener("click", again);
42
+ };
43
+ }
44
+ export { follow_hash, row_href, row_id };