@ogygia/devtools 0.0.0-stage → 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 +25 -2
  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 +103 -3
@@ -0,0 +1,89 @@
1
+ import { vitals_moved } from "@ogygia/core/insights";
2
+ // src/devtools/since-load.ts
3
+ /**
4
+ * SINCE YOUR LAST LOAD — the dev loop's question: I changed the code and reloaded; what changed?
5
+ * The Page tab keeps a small picture of each page's load (its findings, each island's timings, the
6
+ * vitals and their parts) in session storage as the page goes away, and the next load of the same
7
+ * page is read against it: findings fixed and new, islands that got faster or slower, vitals that
8
+ * moved and the part of each that moved most. Pure (the storage lives in page.ts); a change must
9
+ * clear the noise floor to count. The vitals use the profiler's own rule (page-insights
10
+ * `vitals_moved`): "since your last load" and "since your last profile" never disagree.
11
+ */
12
+ /** findings a dev server's first compile of the page can raise on its own (its islands' code arrives late) */
13
+ const COMPILE_BORNE = /* @__PURE__ */ new Set([
14
+ "late-interactive",
15
+ "slow-module",
16
+ "queued"
17
+ ]);
18
+ /** …and, when the PAGE compiled on its request, everything that waited on its first byte */
19
+ const PAGE_COMPILE_BORNE = /* @__PURE__ */ new Set([
20
+ ...COMPILE_BORNE,
21
+ "slow-ttfb",
22
+ "slow-fcp",
23
+ "slow-lcp",
24
+ "vital-ttfb",
25
+ "vital-fcp",
26
+ "vital-lcp",
27
+ "render-blocking"
28
+ ]);
29
+ /** an island's step moved when it changed by at least this much (and 30%): a module load is noisier
30
+ * (the network, the dev server compiling), so it needs more */
31
+ const ISLAND_FLOOR_MS = {
32
+ hydrate: 10,
33
+ load: 50
34
+ };
35
+ const label = (f) => f.names.length ? `${f.code} (${[...new Set(f.names)].slice(0, 3).join(", ")})` : f.code;
36
+ function since_load(prev, now, at = Date.now()) {
37
+ if (prev.path !== now.path) return null;
38
+ const key = (f) => `${f.code}|${[...new Set(f.names)].sort().join(",")}`;
39
+ const dev = new Set([...prev.findings, ...now.findings].filter((f) => f.dev).map((f) => f.code));
40
+ const had = new Set(prev.findings.map(key));
41
+ const has = new Set(now.findings.map(key));
42
+ const sped = now.islands.filter((i) => {
43
+ const p = prev.islands.find((x) => x.name === i.name);
44
+ return p && p.load_ms - i.load_ms >= ISLAND_FLOOR_MS.load && i.load_ms <= p.load_ms * .67;
45
+ }).length;
46
+ const all_warmer = now.islands.length >= 2 && sped >= Math.max(2, now.islands.length * .6);
47
+ const compiled = all_warmer || !!prev.page_compiled || prev.islands.some((i) => (i.compile_ms ?? 0) >= i.load_ms * .5 && i.load_ms >= ISLAND_FLOOR_MS.load);
48
+ const cold_page = !!prev.page_compiled || all_warmer;
49
+ const borne = cold_page ? PAGE_COMPILE_BORNE : COMPILE_BORNE;
50
+ const gone = prev.findings.filter((f) => !has.has(key(f)) && !dev.has(f.code));
51
+ const fixed = gone.filter((f) => !(compiled && borne.has(f.code))).map(label);
52
+ const set_aside = gone.length - fixed.length;
53
+ const added = now.findings.filter((f) => !had.has(key(f)) && !dev.has(f.code)).map(label);
54
+ const moved = [];
55
+ const table = (s) => Object.fromEntries(s.vitals.map((v) => [v.key, v.value]));
56
+ for (const m of vitals_moved(table(prev), table(now), (side, k) => (side === "a" ? prev : now).parts?.[k] ?? null)) if (!(cold_page && (m.key === "ttfb" || m.key === "fcp" || m.key === "lcp")) && !(m.key === "cls" && (prev.cls_dev || now.cls_dev))) moved.push({
57
+ what: m.key.toUpperCase(),
58
+ a: m.a,
59
+ b: m.b,
60
+ unit: m.key === "cls" ? "" : "ms",
61
+ better: m.b < m.a,
62
+ ...m.part ? { part: m.part } : {}
63
+ });
64
+ for (const i of now.islands) {
65
+ const p = prev.islands.find((x) => x.name === i.name);
66
+ if (!p) continue;
67
+ for (const step of cold_page ? ["hydrate"] : ["hydrate", "load"]) {
68
+ const was = step === "hydrate" ? p.hydrate_ms : Math.max(0, p.load_ms - (p.compile_ms ?? 0));
69
+ const is = step === "hydrate" ? i.hydrate_ms : Math.max(0, i.load_ms - (i.compile_ms ?? 0));
70
+ const d = is - was;
71
+ if (Math.abs(d) >= ISLAND_FLOOR_MS[step] && Math.abs(d) >= Math.max(was, is) * .3) moved.push({
72
+ what: `${i.name} ${step}`,
73
+ a: was,
74
+ b: is,
75
+ unit: "ms",
76
+ better: d < 0
77
+ });
78
+ }
79
+ }
80
+ moved.sort((x, y) => Math.abs(y.b - y.a) / (Math.abs(y.a) || 1) - Math.abs(x.b - x.a) / (Math.abs(x.a) || 1));
81
+ return {
82
+ ago_ms: Math.max(0, at - prev.at),
83
+ fixed,
84
+ added,
85
+ moved: moved.slice(0, 8),
86
+ ...compiled ? { note: `The last load was the dev server compiling this page's code for the first time: ${cold_page ? "its first byte, its paints and its islands' loads are not compared" : "its islands' loads are compared without that"}${set_aside ? `, and what the compile alone raised (${set_aside === 1 ? "one finding" : `${set_aside} findings`}) is not counted as fixed` : ""}.` } : {}
87
+ };
88
+ }
89
+ export { since_load };
@@ -0,0 +1,24 @@
1
+ /** served line (1-based) → source line (1-based), or 0 where the line maps nowhere */
2
+ type Lines = Int32Array;
3
+ /**
4
+ * Each generated line's first segment that names a source → that source line; only segments of the
5
+ * map's first source (a `.svelte` module maps to itself; helpers it inlined are not its lines).
6
+ */
7
+ export declare function lines_of_map(mappings: string, want_source?: number): Lines;
8
+ /**
9
+ * The source line of `line` in the app file `file` (`src/…`, as the CPU view names it). `undefined`
10
+ * while it is being read, `null` when it cannot be told.
11
+ */
12
+ export declare function source_line(file: string, line: number | null): number | null | undefined;
13
+ /**
14
+ * Functions with the source's lines: each mapped where the file's map says, else its line dropped
15
+ * (a served line is not the source's). `mapped`: every app function among them has its source line.
16
+ */
17
+ export declare function with_source_lines<F extends {
18
+ file: string;
19
+ line: number | null;
20
+ }>(fns: readonly F[]): {
21
+ fns: F[];
22
+ mapped: boolean;
23
+ };
24
+ export {};
@@ -0,0 +1,106 @@
1
+ import { tool_fetch } from "@ogygia/core/internal/instruments";
2
+ // src/devtools/source-lines.ts
3
+ /**
4
+ * THE SOURCE'S LINE FOR A SAMPLED FRAME, IN THE PAGE. The browser's sampler names a frame by the line
5
+ * of the code as served; on the dev server a `.svelte` file is served compiled, so that line is the
6
+ * compiled output's (`save` read as SlowSave.svelte:18, written at line 5). The dev server puts each
7
+ * module's source map inline at its end; this reads it once per file and answers the source line of a
8
+ * served line (its first mapped segment). Asked synchronously: `undefined` while the file is being
9
+ * read (the next look has it), `null` when it cannot say (no inline map: a build's chunk, a file the
10
+ * dev server did not transform). No regex: the map's marker is found by search, the VLQ by table.
11
+ */
12
+ const MARK = "//# sourceMappingURL=data:application/json;base64,";
13
+ const B64_INDEX = new Map([..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"].map((c, i) => [c, i]));
14
+ const ready = /* @__PURE__ */ new Map();
15
+ const pending = /* @__PURE__ */ new Set();
16
+ /**
17
+ * Each generated line's first segment that names a source → that source line; only segments of the
18
+ * map's first source (a `.svelte` module maps to itself; helpers it inlined are not its lines).
19
+ */
20
+ function lines_of_map(mappings, want_source = 0) {
21
+ const rows = mappings.split(";");
22
+ const out = new Int32Array(rows.length + 1);
23
+ let src = 0;
24
+ let src_line = 0;
25
+ for (let r = 0; r < rows.length; r++) {
26
+ const row = rows[r];
27
+ let i = 0;
28
+ let found = 0;
29
+ while (i < row.length) {
30
+ const fields = [];
31
+ while (i < row.length && row[i] !== ",") {
32
+ let value = 0;
33
+ let shift = 0;
34
+ let more = true;
35
+ while (more && i < row.length) {
36
+ const digit = B64_INDEX.get(row[i++]) ?? 0;
37
+ more = (digit & 32) !== 0;
38
+ value += (digit & 31) << shift;
39
+ shift += 5;
40
+ }
41
+ fields.push(value & 1 ? -(value >> 1) : value >> 1);
42
+ }
43
+ i++;
44
+ if (fields.length >= 4) {
45
+ src += fields[1];
46
+ src_line += fields[2];
47
+ if (!found && src === want_source) found = src_line + 1;
48
+ }
49
+ }
50
+ out[r + 1] = found;
51
+ }
52
+ return out;
53
+ }
54
+ /** the served file's source line map, read from its inline map (null when it has none) */
55
+ function parse(text, file) {
56
+ const at = text.lastIndexOf(MARK);
57
+ if (at === -1) return null;
58
+ let end = text.indexOf("\n", at);
59
+ if (end === -1) end = text.length;
60
+ try {
61
+ const map = JSON.parse(atob(text.slice(at + 50, end).trim()));
62
+ if (typeof map.mappings !== "string" || !Array.isArray(map.sources)) return null;
63
+ const base = file.slice(file.lastIndexOf("/") + 1);
64
+ const own = map.sources.findIndex((s) => typeof s === "string" && s.slice(s.lastIndexOf("/") + 1) === base);
65
+ return lines_of_map(map.mappings, own === -1 ? 0 : own);
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
70
+ /**
71
+ * The source line of `line` in the app file `file` (`src/…`, as the CPU view names it). `undefined`
72
+ * while it is being read, `null` when it cannot be told.
73
+ */
74
+ function source_line(file, line) {
75
+ if (line === null || !file.startsWith("src/")) return null;
76
+ const hit = ready.get(file);
77
+ if (hit === null) return null;
78
+ if (hit) return hit[line] || null;
79
+ if (!pending.has(file) && typeof fetch === "function") {
80
+ pending.add(file);
81
+ tool_fetch("/" + file).then((r) => r.ok ? r.text() : "").then((t) => ready.set(file, t ? parse(t, file) : null)).catch(() => ready.set(file, null)).finally(() => pending.delete(file));
82
+ }
83
+ }
84
+ /**
85
+ * Functions with the source's lines: each mapped where the file's map says, else its line dropped
86
+ * (a served line is not the source's). `mapped`: every app function among them has its source line.
87
+ */
88
+ function with_source_lines(fns) {
89
+ let mapped = true;
90
+ return {
91
+ fns: fns.map((f) => {
92
+ const l = source_line(f.file, f.line);
93
+ if (l) return {
94
+ ...f,
95
+ line: l
96
+ };
97
+ if (f.file.startsWith("src/")) mapped = false;
98
+ return {
99
+ ...f,
100
+ line: null
101
+ };
102
+ }),
103
+ mapped
104
+ };
105
+ }
106
+ export { lines_of_map, source_line, with_source_lines };
@@ -0,0 +1,52 @@
1
+ import { UnscopedStyles } from "@ogygia/core/internal/instruments";
2
+ export interface SheetRow {
3
+ /** the file name, or `inline <style>` */
4
+ label: string;
5
+ href: string | null;
6
+ rules: number;
7
+ /** decoded bytes: the response's (resource timing) or the inline text's */
8
+ bytes: number;
9
+ /** the browser waited on it before the first paint (resource timing's own verdict) */
10
+ blocking: boolean;
11
+ /** rules whose selectors match nothing on the page now (null until checked) */
12
+ unmatched: number | null;
13
+ /** their text's size, a stand-in for the bytes they cost */
14
+ unmatched_bytes: number | null;
15
+ /** a region / router stylesheet ogygia linked */
16
+ ogygia: boolean;
17
+ }
18
+ export interface StylesReport {
19
+ sheets: SheetRow[];
20
+ rules: number;
21
+ bytes: number;
22
+ unmatched: number;
23
+ unmatched_bytes: number;
24
+ /** examples of selectors that match nothing, biggest rules first */
25
+ examples: string[];
26
+ unscoped: (UnscopedStyles & {
27
+ sheet: string;
28
+ })[];
29
+ /** sheets whose rules could not be read (another origin without CORS) */
30
+ unreadable: number;
31
+ }
32
+ export interface StylesFinding {
33
+ code: string;
34
+ severity: "warn" | "info";
35
+ message: string;
36
+ fix?: string;
37
+ }
38
+ /** Split a selector list on its top-level commas (`:is(a, b)` stays whole). */
39
+ export declare function split_selectors(list: string): string[];
40
+ /** A selector with its pseudo-elements and momentary states taken off, ready to try against the
41
+ * document. `''` when nothing is left to match (a bare `::selection`): that one counts as used. */
42
+ export declare function matchable(sel: string): string;
43
+ /** Read the page's stylesheets. `match: true` also tries every selector against the document. */
44
+ export declare function read_styles(doc?: Document, opts?: {
45
+ match?: boolean;
46
+ }): StylesReport;
47
+ /** The build's unscoped-fallback markers in the page's sheets. Inline sheets read their node's
48
+ * text; linked ones one same-origin fetch each (the HTTP cache answers it). */
49
+ export declare function scan_unscoped(doc?: Document): Promise<(UnscopedStyles & {
50
+ sheet: string;
51
+ })[]>;
52
+ export declare function styles_findings(s: StylesReport): StylesFinding[];
@@ -0,0 +1,248 @@
1
+ import { find_unscoped, paint_blocker, tool_fetch, tool_made, unscoped_finding } from "@ogygia/core/internal/instruments";
2
+ // src/devtools/styles.ts
3
+ /**
4
+ * THE PAGE'S STYLES, as the browser holds them: every stylesheet (linked or inline), its rules,
5
+ * its size, whether it held the first paint, and two things only the live page can answer:
6
+ *
7
+ * - **unscoped components**: the build marks a component whose scoped CSS it could not compile
8
+ * (unscoped-css.ts). Its rules then apply page-wide. The CSSOM drops comments, so the marker is
9
+ * read from the sheet's text (inline: the node; linked: one fetch, from the HTTP cache).
10
+ * - **rules that match nothing here**: each style rule's selector, with states and pseudo-elements
11
+ * taken off (`:hover`, `::before`), tried against the document. Content that appears later (a
12
+ * menu, a dialog, an island not yet rendered) counts as matching nothing, so it is a hint.
13
+ *
14
+ * Devtools' own UI lives in a shadow root and never shows here. Runs on demand (the Page tab), not
15
+ * on a hot path.
16
+ */
17
+ const STATES = [
18
+ ":hover",
19
+ ":focus-visible",
20
+ ":focus-within",
21
+ ":focus",
22
+ ":active",
23
+ ":visited",
24
+ ":target",
25
+ ":open",
26
+ ":popover-open",
27
+ ":modal",
28
+ ":user-invalid",
29
+ ":user-valid",
30
+ ":autofill"
31
+ ];
32
+ /** Split a selector list on its top-level commas (`:is(a, b)` stays whole). */
33
+ function split_selectors(list) {
34
+ const out = [];
35
+ let depth = 0;
36
+ let from = 0;
37
+ for (let i = 0; i < list.length; i++) {
38
+ const c = list[i];
39
+ if (c === "(" || c === "[") depth++;
40
+ else if (c === ")" || c === "]") depth--;
41
+ else if (c === "," && depth === 0) {
42
+ out.push(list.slice(from, i).trim());
43
+ from = i + 1;
44
+ }
45
+ }
46
+ out.push(list.slice(from).trim());
47
+ return out.filter(Boolean);
48
+ }
49
+ /** A selector with its pseudo-elements and momentary states taken off, ready to try against the
50
+ * document. `''` when nothing is left to match (a bare `::selection`): that one counts as used. */
51
+ function matchable(sel) {
52
+ let s = sel;
53
+ let at = s.indexOf("::");
54
+ while (at !== -1) {
55
+ let end = at + 2;
56
+ let depth = 0;
57
+ while (end < s.length) {
58
+ const c = s[end];
59
+ if (c === "(") depth++;
60
+ else if (c === ")") depth--;
61
+ else if (depth === 0 && (c === " " || c === ">" || c === "+" || c === "~" || c === ",")) break;
62
+ end++;
63
+ }
64
+ s = s.slice(0, at) + s.slice(end);
65
+ at = s.indexOf("::");
66
+ }
67
+ for (const st of STATES) {
68
+ let i = s.indexOf(st);
69
+ while (i !== -1) {
70
+ const next = s[i + st.length];
71
+ if (next === void 0 || !(next === "-" || next >= "a" && next <= "z")) s = s.slice(0, i) + s.slice(i + st.length);
72
+ else i += st.length;
73
+ i = s.indexOf(st, i);
74
+ }
75
+ }
76
+ s = s.trim();
77
+ while (s.endsWith(">") || s.endsWith("+") || s.endsWith("~")) s = s.slice(0, -1).trim();
78
+ return s;
79
+ }
80
+ function file_of(href) {
81
+ const path = href.split("?")[0].split("#")[0];
82
+ return path.slice(path.lastIndexOf("/") + 1) || href;
83
+ }
84
+ /** Devtools' own component styles: on the dev server Vite injects them into the page's <head>
85
+ * (the panel's markup lives in a shadow root, so every one of their rules matches nothing here). */
86
+ function own_sheet(node) {
87
+ const id = node?.getAttribute("data-vite-dev-id");
88
+ return !!id && (id.includes("ogygia") || id.includes("/devtools/src/devtools/")) && id.includes("/devtools/");
89
+ }
90
+ /** A sheet that styles the devtools panel and nothing on the page: in a build the panel's CSS is a
91
+ * linked file in the document, whatever its name. Its rules match inside the panel's shadow root
92
+ * and never in the document. Every rule is tried (which of the panel's parts are showing depends
93
+ * on its open tab), stopping at the first one the page itself uses. */
94
+ function styles_the_panel(rules, doc, panel) {
95
+ if (!panel) return false;
96
+ let in_panel = false;
97
+ let stop = false;
98
+ each_style_rule(rules, (rule) => {
99
+ if (stop) return;
100
+ for (const part of split_selectors(rule.selectorText)) {
101
+ const m = matchable(part);
102
+ if (!m) continue;
103
+ try {
104
+ if (doc.querySelector(m)) {
105
+ stop = true;
106
+ in_panel = false;
107
+ return;
108
+ }
109
+ if (panel.querySelector(m)) in_panel = true;
110
+ } catch {}
111
+ }
112
+ });
113
+ return in_panel;
114
+ }
115
+ function each_style_rule(list, fn) {
116
+ for (let i = 0; i < list.length; i++) {
117
+ const r = list[i];
118
+ if ("selectorText" in r) fn(r);
119
+ const inner = r.cssRules;
120
+ if (inner && inner.length) each_style_rule(inner, fn);
121
+ }
122
+ }
123
+ /** Read the page's stylesheets. `match: true` also tries every selector against the document. */
124
+ function read_styles(doc = document, opts = {}) {
125
+ const res = /* @__PURE__ */ new Map();
126
+ for (const e of performance.getEntriesByType("resource")) if (!tool_made(e)) res.set(e.name, e);
127
+ const sheets = [];
128
+ const examples = [];
129
+ let unreadable = 0;
130
+ const blocks = paint_blocker();
131
+ const panel = doc.querySelector("[data-ogygia-devtools-host]")?.shadowRoot;
132
+ for (const sheet of Array.from(doc.styleSheets)) {
133
+ const node = sheet.ownerNode;
134
+ if (own_sheet(node)) continue;
135
+ const href = sheet.href;
136
+ let rules;
137
+ try {
138
+ rules = sheet.cssRules;
139
+ } catch {
140
+ unreadable++;
141
+ continue;
142
+ }
143
+ if (styles_the_panel(rules, doc, panel)) continue;
144
+ const r = href ? res.get(href) : void 0;
145
+ const row = {
146
+ label: href ? file_of(href) : node?.getAttribute("data-vite-dev-id") ? file_of(node.getAttribute("data-vite-dev-id")) : "inline <style>",
147
+ href,
148
+ rules: 0,
149
+ bytes: href ? r?.decodedBodySize ?? 0 : node?.textContent?.length ?? 0,
150
+ blocking: !!r && blocks(r),
151
+ unmatched: opts.match ? 0 : null,
152
+ unmatched_bytes: opts.match ? 0 : null,
153
+ ogygia: !!node && (node.hasAttribute("data-ogygia-region-css") || node.hasAttribute("data-ogygia-rcss") || (href ?? "").includes("og-rcss"))
154
+ };
155
+ each_style_rule(rules, (rule) => {
156
+ row.rules++;
157
+ if (!opts.match) return;
158
+ const text = rule.selectorText;
159
+ if (text.includes("&")) return;
160
+ let hit = false;
161
+ for (const part of split_selectors(text)) {
162
+ const m = matchable(part);
163
+ if (!m) {
164
+ hit = true;
165
+ break;
166
+ }
167
+ try {
168
+ if (doc.querySelector(m)) {
169
+ hit = true;
170
+ break;
171
+ }
172
+ } catch {
173
+ hit = true;
174
+ break;
175
+ }
176
+ }
177
+ if (!hit) {
178
+ row.unmatched++;
179
+ const len = rule.cssText.length;
180
+ row.unmatched_bytes += len;
181
+ examples.push({
182
+ sel: text,
183
+ len
184
+ });
185
+ }
186
+ });
187
+ sheets.push(row);
188
+ }
189
+ const sum = (k) => sheets.reduce((s, x) => s + (x[k] ?? 0), 0);
190
+ examples.sort((a, b) => b.len - a.len);
191
+ return {
192
+ sheets: sheets.sort((a, b) => b.bytes - a.bytes),
193
+ rules: sum("rules"),
194
+ bytes: sum("bytes"),
195
+ unmatched: sum("unmatched"),
196
+ unmatched_bytes: sum("unmatched_bytes"),
197
+ examples: [...new Set(examples.map((e) => e.sel))].slice(0, 6),
198
+ unscoped: [],
199
+ unreadable
200
+ };
201
+ }
202
+ /** The build's unscoped-fallback markers in the page's sheets. Inline sheets read their node's
203
+ * text; linked ones one same-origin fetch each (the HTTP cache answers it). */
204
+ async function scan_unscoped(doc = document) {
205
+ const out = [];
206
+ const add = (css, sheet) => {
207
+ for (const u of find_unscoped(css)) if (!out.some((x) => x.file === u.file)) out.push({
208
+ ...u,
209
+ sheet
210
+ });
211
+ };
212
+ const jobs = [];
213
+ for (const sheet of Array.from(doc.styleSheets)) {
214
+ const node = sheet.ownerNode;
215
+ if (own_sheet(node)) continue;
216
+ if (!sheet.href) {
217
+ if (node?.textContent) add(node.textContent, "inline <style>");
218
+ continue;
219
+ }
220
+ const href = sheet.href;
221
+ if (new URL(href, location.href).origin !== location.origin) continue;
222
+ jobs.push(tool_fetch(href, { cache: "force-cache" }).then((r) => r.ok ? r.text() : "").then((css) => add(css, file_of(href))).catch(() => {}));
223
+ }
224
+ await Promise.all(jobs);
225
+ return out;
226
+ }
227
+ function styles_findings(s) {
228
+ const out = [];
229
+ if (s.unscoped.length) {
230
+ const f = unscoped_finding(s.unscoped);
231
+ out.push({
232
+ ...f,
233
+ severity: "warn"
234
+ });
235
+ }
236
+ if (s.unmatched_bytes >= 20480 && s.rules && s.unmatched / s.rules >= .5) {
237
+ const kb = Math.round(s.unmatched_bytes / 1024);
238
+ const worst = s.sheets.filter((x) => (x.unmatched_bytes ?? 0) > 0).sort((a, b) => (b.unmatched_bytes ?? 0) - (a.unmatched_bytes ?? 0)).slice(0, 3);
239
+ out.push({
240
+ code: "css-unmatched",
241
+ severity: "info",
242
+ message: `${Math.floor(s.unmatched / s.rules * 100)}% of the page's CSS rules (${s.unmatched} of ${s.rules}, about ${kb} KB of rule text) match nothing on it right now. Most in: ${worst.map((w) => `${w.label} ${w.unmatched} rules`).join(", ")}.`,
243
+ fix: "Styles in a component's own <style> load with that component. Move page-specific rules there, or split a big shared sheet by route. Menus, dialogs and content that appears later count as unmatched here: open them and check again."
244
+ });
245
+ }
246
+ return out;
247
+ }
248
+ export { matchable, read_styles, scan_unscoped, split_selectors, styles_findings };
@@ -0,0 +1,83 @@
1
+ import { PageView } from "./page.js";
2
+ import { hydration_report } from "./hydration.js";
3
+ import { AttrRule, CpuSummary, PageFinding, PageReport, SessionFinding, SessionReport, TimelineItem, TimelineLane } from "@ogygia/core/insights";
4
+ import { AttrChange, BeaconPage } from "@ogygia/core/internal/instruments";
5
+ type EngineStamp = {
6
+ version: number;
7
+ lean: boolean;
8
+ };
9
+ type HydrationIsland = ReturnType<typeof hydration_report>["islands"][number];
10
+ export declare function note_engine_files(from: number, to: number): void;
11
+ /** What ogygia's measuring cost this page, for a test to subtract: the main-thread time of its own work
12
+ * (checking islands as they hydrate, building the visit, the testing reports — runtime/own-cost.ts)
13
+ * and the decoded bytes of the testing engine's files. No visitor with devtools off pays either. */
14
+ declare function self_cost(): {
15
+ script_ms: number;
16
+ bytes: number;
17
+ phases: Record<string, number>;
18
+ };
19
+ export declare function page_json(cpu_fns?: number): Promise<{
20
+ url: string;
21
+ route: string | null;
22
+ nav: PageView["nav"];
23
+ vitals: PageReport["vitals"];
24
+ islands: PageReport["rows"];
25
+ findings: (PageFinding & {
26
+ names: string[];
27
+ })[];
28
+ blocking: PageReport["blocking"];
29
+ bytes: PageReport["bytes"];
30
+ longtask_ms: number;
31
+ cpu: Pick<CpuSummary, "busy_ms" | "by_kind" | "fns"> | null;
32
+ cpu_state: BeaconPage["cpu"]["state"];
33
+ cpu_off: string | null;
34
+ unmeasured: string[];
35
+ self: ReturnType<typeof self_cost>;
36
+ engine: EngineStamp;
37
+ } | null>;
38
+ /** Each island's report, keyed per INSTANCE: the value of `key_attr` on the island or its NEAREST
39
+ * ancestor that has it, across shadow roots (an id the app renders, e.g. its CMS block's), else
40
+ * `Name#n`, the n-th island of that name in document order. Two islands under the same keyed
41
+ * element are told apart as `key`, `key#2`, … in document order. */
42
+ export declare function hydration_json(key_attr?: string, ignore?: AttrRule[]): Promise<{
43
+ measured: boolean;
44
+ engine: EngineStamp;
45
+ islands: ((HydrationIsland | (Omit<HydrationIsland, "attributes"> & {
46
+ attributes: AttrChange[];
47
+ ignored: AttrChange[];
48
+ }) | (Omit<HydrationIsland, "status" | "attributesOnly" | "attributes" | "diff"> & {
49
+ status: "clean";
50
+ attributesOnly: boolean;
51
+ attributes: null;
52
+ diff: null;
53
+ ignored: AttrChange[];
54
+ })) & {
55
+ key: string;
56
+ })[];
57
+ }>;
58
+ export declare function record_start(): boolean;
59
+ /** A navigation the test's driver blocked during the recording: where it went, when (page clock). */
60
+ export interface BlockedNavigation {
61
+ url: string;
62
+ t: number | null;
63
+ }
64
+ export declare function record_stop(blocked?: BlockedNavigation[]): Promise<(Omit<SessionReport, "findings"> & {
65
+ findings: (SessionFinding & {
66
+ names: string[];
67
+ })[];
68
+ navigations: {
69
+ url: string;
70
+ t: number | null;
71
+ after: string | null;
72
+ island: string | null;
73
+ approx: boolean;
74
+ }[];
75
+ engine: EngineStamp;
76
+ timeline: (Omit<TimelineLane, "items"> & {
77
+ items: Omit<TimelineItem, "ref">[];
78
+ })[];
79
+ unmeasured: string[];
80
+ }) | null>;
81
+ /** Resolves when no island is mid-hydration and nothing woke for `quiet` ms (or `timeout` passed). */
82
+ export declare function settled(quiet?: number, timeout?: number): Promise<boolean>;
83
+ export {};