@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,796 @@
1
+ <!-- (its CSS injected into the dock's shadow root: see Devtools.svelte) -->
2
+ <svelte:options css="injected" />
3
+
4
+ <script module>
5
+ /** the last session's data + report, kept across tab switches (not across reloads) */
6
+ let last = $state(
7
+ /** @type {{ data: import('./session.js').SessionData; report: import('@ogygia/core/insights').SessionReport } | null} */ (
8
+ null
9
+ )
10
+ );
11
+ </script>
12
+
13
+ <script>
14
+ /**
15
+ * The Record tab: press record, use the page (click, type, scroll, navigate), press stop — and get
16
+ * what that stretch did: slow interactions (with the function behind each), clicks that did
17
+ * nothing or were repeated in frustration, long tasks, shifts nobody caused, failed / slow /
18
+ * repeated requests, islands that woke or broke, errors, DOM churn per island, heap growth,
19
+ * navigations. Every finding points at its elements: hover lights them up, click scrolls there.
20
+ * Engine: session.ts; analysis: session-insights.ts.
21
+ */
22
+ import { onMount } from 'svelte';
23
+ import { start_session, recording, set_last_session, session_names } from './session.js';
24
+ import { run_profile, slim_profile } from './profile-store.js';
25
+ import { analyze_session, session_timeline } from '@ogygia/core/insights';
26
+ import { region_name_by_fp } from './regions.js';
27
+ import { unmeasured } from './page.js';
28
+ // (what this browser cannot measure: a session's silence on those is not a clean bill)
29
+ const limits = unmeasured(typeof Profiler === 'undefined' ? 'unsupported' : null);
30
+ import { highlight, clear_highlight } from './highlight.js';
31
+
32
+ let { tick = 0 } = $props();
33
+
34
+ let rec = $state(recording());
35
+ let busy = $state(false);
36
+ // (a session the page before was recording resumes at boot, maybe after this tab mounted: the
37
+ // panel's pulse picks it up)
38
+ const live = $derived.by(() => {
39
+ void tick;
40
+ return rec ?? recording();
41
+ });
42
+ const elapsed = $derived.by(() => {
43
+ void tick;
44
+ return live ? (performance.now() - live.started) / 1000 : 0;
45
+ });
46
+
47
+ // THE SERVER TOO: a profiler window over the same stretch (when the profiler is mounted)
48
+ let profiler_here = $state(false);
49
+ let with_server = $state(true);
50
+ let server_note = $state('');
51
+ let server_window = $state(
52
+ /** @type {import('./profile-store.js').SlimProfile & { window?: any } | null} */ (null)
53
+ );
54
+ /** @type {Promise<boolean> | null} */
55
+ let probe = null;
56
+ const probe_profiler = () =>
57
+ (probe ??= fetch('/__profiler', { redirect: 'manual' })
58
+ .then((r) => (profiler_here = r.status !== 404))
59
+ .catch(() => (profiler_here = false)));
60
+ onMount(() => void probe_profiler());
61
+ let window_on = false;
62
+ /** the window's start in flight (a stop pressed before it answered waits, then stops it) */
63
+ let window_starting = /** @type {Promise<void> | null} */ (null);
64
+ /** '' | 'starting' | 'on' | 'off' — shown beside the recording (the server may wait out a background sample) */
65
+ let window_state = $state('');
66
+ function start() {
67
+ last = null;
68
+ server_window = null;
69
+ server_note = '';
70
+ clear_highlight();
71
+ window_on = false;
72
+ // the browser side at once (the next click counts); the server window alongside
73
+ rec = start_session();
74
+ window_state = '';
75
+ if (!with_server) return;
76
+ window_state = 'starting';
77
+ window_starting = (async () => {
78
+ if (!(await probe_profiler())) return void (window_state = '');
79
+ try {
80
+ const r = await fetch('/__profiler/window/start', { method: 'POST' });
81
+ window_on = r.ok;
82
+ window_state = r.ok ? 'on' : 'off';
83
+ if (!r.ok)
84
+ server_note = `The server window did not start (${r.status}): ${(await r.text()).slice(0, 120)}`;
85
+ } catch (e) {
86
+ window_state = 'off';
87
+ server_note = `The server window did not start: ${e instanceof Error ? e.message : String(e)}`;
88
+ }
89
+ })();
90
+ }
91
+ async function stop() {
92
+ const r0 = rec ?? recording();
93
+ if (!r0) return;
94
+ busy = true;
95
+ try {
96
+ const resumed = !rec;
97
+ const data = await r0.stop();
98
+ rec = null;
99
+ if (window_starting) await window_starting;
100
+ window_starting = null;
101
+ // a resumed session's window was started by the page before: stop it too (a 400 = none)
102
+ if (window_on || (resumed && with_server && (await probe_profiler()))) {
103
+ window_on = false;
104
+ try {
105
+ const r = await fetch('/__profiler/window/stop', { method: 'POST' });
106
+ if (r.ok) {
107
+ const json = await r.json();
108
+ server_window = { ...slim_profile(json), window: json.window };
109
+ } else if (!(resumed && r.status === 400))
110
+ server_note = `The server window failed (${r.status}).`;
111
+ } catch (e) {
112
+ server_note = `The server window failed: ${e instanceof Error ? e.message : String(e)}`;
113
+ }
114
+ }
115
+ last = {
116
+ data,
117
+ report: analyze_session(
118
+ data,
119
+ session_names(data, (fp) => region_name_by_fp(fp)),
120
+ location.href
121
+ )
122
+ };
123
+ // (the answer key and e2e read the same report the tab shows; every tab reads the session)
124
+ window.__ogygia_session = last.report;
125
+ set_last_session(last);
126
+ server = {};
127
+ } finally {
128
+ busy = false;
129
+ }
130
+ }
131
+
132
+ /** the elements a finding's refs point at, now */
133
+ function els_of(refs) {
134
+ if (!last) return [];
135
+ const out = [];
136
+ for (const r of refs) {
137
+ let el = null;
138
+ if (r.kind === 'interaction') el = last.data.interactions[r.i]?.el?.deref() ?? null;
139
+ else if (r.kind === 'click') el = last.data.clicks[r.i]?.el?.deref() ?? null;
140
+ else if (r.kind === 'shift') el = last.data.shifts[r.i]?.el?.deref() ?? null;
141
+ else if (r.kind === 'island')
142
+ el = document.querySelector(`ogygia-region[data-og-fp="${r.fp}"]`);
143
+ if (el && el.isConnected) out.push(el);
144
+ }
145
+ return out;
146
+ }
147
+ // THE SESSION ON ONE CLOCK: lanes of what happened, the requests split into server / upstream
148
+ let tlW = $state(520);
149
+ let hover = $state('');
150
+ const tl = $derived.by(() => {
151
+ if (!last) return null;
152
+ const lanes = session_timeline(
153
+ last.data,
154
+ session_names(last.data, (fp) => region_name_by_fp(fp))
155
+ );
156
+ const span = Math.max(1, last.data.to - last.data.from);
157
+ return { lanes, span };
158
+ });
159
+ const LANE_W = 88;
160
+ const x_of = (t, span) => Math.max(0, Math.min(1, t / span)) * Math.max(100, tlW - LANE_W - 8);
161
+ function tl_enter(it) {
162
+ hover = it.label;
163
+ if (it.ref) {
164
+ const els = els_of([it.ref]);
165
+ if (els.length) highlight(els, it.label.slice(0, 80));
166
+ }
167
+ }
168
+ function tl_leave() {
169
+ hover = '';
170
+ clear_highlight();
171
+ }
172
+
173
+ // CLIENT → SERVER in one click: a slow request found in the session, profiled on the server
174
+ let server = $state(
175
+ /** @type {Record<string, { busy?: boolean; error?: string; p?: import('./profile-store.js').SlimProfile }>} */ ({})
176
+ );
177
+ async function profile_request(url) {
178
+ const u = new URL(url);
179
+ const path = u.pathname + u.search;
180
+ server = { ...server, [url]: { busy: true } };
181
+ try {
182
+ const p = await run_profile(path, 3);
183
+ server = { ...server, [url]: { p } };
184
+ } catch (e) {
185
+ server = { ...server, [url]: { error: e instanceof Error ? e.message : String(e) } };
186
+ }
187
+ }
188
+ const ms = (n) => (n >= 1000 ? (n / 1000).toFixed(2) + ' s' : Math.round(n) + ' ms');
189
+ const kb = (n) =>
190
+ n < 1024
191
+ ? n + ' B'
192
+ : n < 1048576
193
+ ? (n / 1024).toFixed(1) + ' KB'
194
+ : (n / 1048576).toFixed(1) + ' MB';
195
+
196
+ onMount(() => () => clear_highlight());
197
+ </script>
198
+
199
+ <div class="rec" data-og-session>
200
+ <div class="bar">
201
+ {#if live}
202
+ <button class="stop" data-og-session-stop disabled={busy} onclick={stop}>■ Stop</button>
203
+ <span class="live"
204
+ ><i class="dot"></i>recording {elapsed.toFixed(0)} s — use the page: click, type, scroll, navigate</span
205
+ >
206
+ {#if window_state}<span class="muted" data-og-session-server-state={window_state}
207
+ >· server {window_state === 'on'
208
+ ? 'too'
209
+ : window_state === 'starting'
210
+ ? 'starting…'
211
+ : 'not recorded'}</span
212
+ >{/if}
213
+ {:else}
214
+ <button class="go" data-og-session-start onclick={start}>● Record</button>
215
+ {#if profiler_here}<label class="srvtoggle"
216
+ ><input type="checkbox" bind:checked={with_server} data-og-session-server-toggle /> and the
217
+ server</label
218
+ >{/if}
219
+ <span class="muted"
220
+ >{last
221
+ ? `last session: ${ms(last.report.duration_ms)}`
222
+ : 'Press record, use the page, press stop.'}</span
223
+ >
224
+ {/if}
225
+ </div>
226
+ {#if limits.length}
227
+ <p class="muted" data-og-session-browser-limits>
228
+ This browser does not report {limits.join(', ')}: a session here cannot find what needs them.
229
+ Record in a Chromium browser for those.
230
+ </p>
231
+ {/if}
232
+
233
+ {#if last && !live}
234
+ {@const r = last.report}
235
+ <div class="stats">
236
+ <div>
237
+ <b>{r.counts.interactions}</b><span
238
+ >interactions{r.counts.slow ? ` · ${r.counts.slow} slow` : ''}</span
239
+ >
240
+ </div>
241
+ <div><b>{ms(r.counts.longtask_ms)}</b><span>{r.counts.longtasks} long tasks</span></div>
242
+ <div><b>{r.counts.requests}</b><span>requests · {kb(r.counts.request_bytes)}</span></div>
243
+ <div><b>{r.counts.shifts}</b><span>shifts by themselves</span></div>
244
+ <div><b>{r.counts.mutations}</b><span>DOM changes</span></div>
245
+ <div class:bad={r.counts.errors > 0}><b>{r.counts.errors}</b><span>errors</span></div>
246
+ </div>
247
+
248
+ {#if server_note}<p class="fix">{server_note}</p>{/if}
249
+ {#if server_window}
250
+ {@const sw = server_window}
251
+ {@const hot = sw.hot.filter((h) => h.ms >= 1)}
252
+ <h3>
253
+ On the server, meanwhile <span class="muted"
254
+ >{sw.window?.requests?.length ?? 0} requests · the profiler's window</span
255
+ >
256
+ </h3>
257
+ <div class="srvprof" data-og-session-window>
258
+ {#if sw.window?.requests?.length}
259
+ <table>
260
+ <tbody>
261
+ {#each sw.window.requests.slice(0, 8) as q, i (q.path + i)}
262
+ <tr
263
+ ><td class="tgt"
264
+ >{q.method}
265
+ {q.path}{#if q.route}<span class="muted"> · {q.route}</span>{/if}</td
266
+ ><td class="num">{ms(q.ms)}</td><td class="num">{q.status ?? ''}</td></tr
267
+ >
268
+ {/each}
269
+ </tbody>
270
+ </table>
271
+ {/if}
272
+ {#if hot.length}
273
+ <p class="muted">the server's hottest code across them (own time):</p>
274
+ <ul>
275
+ {#each hot.slice(0, 5) as h, k (k)}<li>
276
+ {h.name}
277
+ <span class="muted"
278
+ >{h.file ? h.file.slice(h.file.lastIndexOf('/') + 1) : ''}{h.line !== null
279
+ ? `:${h.line}`
280
+ : ''} · {h.kind}</span
281
+ >
282
+ {ms(h.ms)}
283
+ </li>{/each}
284
+ </ul>
285
+ {/if}
286
+ {#if sw.network?.calls.length}
287
+ <p class="muted">outbound calls:</p>
288
+ <ul>
289
+ {#each [...sw.network.calls]
290
+ .sort((a, b) => b.wait_ms - a.wait_ms)
291
+ .slice(0, 4) as c, k (k)}<li>
292
+ {c.method}
293
+ {c.url.split('?')[0]}
294
+ {ms(c.wait_ms)}
295
+ </li>{/each}
296
+ </ul>
297
+ {/if}
298
+ {#each sw.findings.filter((x) => x.severity !== 'info').slice(0, 3) as x, k (k)}<p
299
+ class="fix"
300
+ >
301
+ {x.message}
302
+ </p>{/each}
303
+ {#if sw.links}<a class="full" href={sw.links.html} target="_blank" rel="noopener"
304
+ >full server report ↗</a
305
+ >{/if}
306
+ </div>
307
+ {/if}
308
+
309
+ {#if tl && tl.lanes.length}
310
+ <h3>Timeline <span class="muted">{ms(tl.span)} · hover for what it was</span></h3>
311
+ <div class="tl" data-og-session-timeline bind:clientWidth={tlW}>
312
+ {#each tl.lanes as lane (lane.key)}
313
+ <div class="lane">
314
+ <span class="ll" style:width="{LANE_W}px">{lane.label}</span>
315
+ <span class="track">
316
+ {#each lane.items as it, i (i)}
317
+ {@const x0 = x_of(it.t0, tl.span)}
318
+ {@const w = Math.max(3, x_of(it.t1, tl.span) - x0)}
319
+ <i
320
+ class="it {it.tone}"
321
+ class:point={it.t1 - it.t0 < 1}
322
+ style:left="{x0}px"
323
+ style:width="{w}px"
324
+ role="presentation"
325
+ onmouseenter={() => tl_enter(it)}
326
+ onmouseleave={tl_leave}
327
+ >
328
+ {#if it.server}
329
+ {@const total = Math.max(1, it.t1 - it.t0)}
330
+ <b class="srvpart" style:width="{Math.min(100, (it.server.ms / total) * 100)}%"
331
+ ><b
332
+ class="uppart"
333
+ style:width="{it.server.ms
334
+ ? Math.min(100, (it.server.up / it.server.ms) * 100)
335
+ : 0}%"
336
+ ></b></b
337
+ >
338
+ {/if}
339
+ </i>
340
+ {/each}
341
+ </span>
342
+ </div>
343
+ {/each}
344
+ <p class="tlhover">{hover || ' '}</p>
345
+ </div>
346
+ {/if}
347
+
348
+ <h3>Findings <span class="muted">{r.findings.length || 'none'}</span></h3>
349
+ {#if r.findings.length}
350
+ <ul class="findings" data-og-session-findings>
351
+ {#each r.findings as f, i (f.code + i)}
352
+ <li
353
+ class={f.severity}
354
+ data-code={f.code}
355
+ onmouseenter={() => f.refs.length && highlight(els_of(f.refs), f.message.slice(0, 80))}
356
+ onmouseleave={() => clear_highlight()}
357
+ >
358
+ <p class="msg">{f.message}</p>
359
+ {#if f.fix}<p class="fix">{f.fix}</p>{/if}
360
+ {#if f.refs.length && els_of(f.refs).length}
361
+ <button
362
+ class="chip"
363
+ onclick={() => highlight(els_of(f.refs), f.message.slice(0, 80), true)}
364
+ >show on the page ({els_of(f.refs).length})</button
365
+ >
366
+ {/if}
367
+ {#if f.url}
368
+ {@const sv = server[f.url]}
369
+ <button
370
+ class="chip srv"
371
+ data-og-session-profile
372
+ disabled={sv?.busy}
373
+ onclick={() => profile_request(f.url)}
374
+ >
375
+ {sv?.busy
376
+ ? 'profiling on the server…'
377
+ : sv?.p
378
+ ? 'profile again'
379
+ : 'profile it on the server'}
380
+ </button>
381
+ {#if sv?.error}<p class="fix">{sv.error}</p>{/if}
382
+ {#if sv?.p}
383
+ {@const p = sv.p}
384
+ {@const hot = p.hot.filter((h) => h.ms >= 1)}
385
+ <div class="srvprof" data-og-session-server>
386
+ <p>
387
+ <b>{ms(p.render_ms)}</b> per render on the server{#if p.forecast && p.forecast.after_ms < p.forecast.now_ms - 1},
388
+ <b class="ok">{ms(p.forecast.after_ms)}</b> after its fixes{/if}.
389
+ </p>
390
+ {#if hot.length}
391
+ <p class="muted">hottest code:</p>
392
+ <ul>
393
+ {#each hot.slice(0, 3) as h, k (k)}<li>
394
+ {h.name}
395
+ <span class="muted"
396
+ >{h.file ? h.file.slice(h.file.lastIndexOf('/') + 1) : ''}{h.line !==
397
+ null
398
+ ? `:${h.line}`
399
+ : ''}</span
400
+ >
401
+ {ms(h.ms)}
402
+ </li>{/each}
403
+ </ul>
404
+ {:else}
405
+ <p class="muted">Almost no CPU: the server spent the time waiting.</p>
406
+ {/if}
407
+ {#if p.network?.calls.length}
408
+ <p class="muted">waited on:</p>
409
+ <ul>
410
+ {#each [...p.network.calls]
411
+ .sort((a, b) => b.wait_ms - a.wait_ms)
412
+ .slice(0, 3) as c, k (k)}<li>
413
+ {c.method}
414
+ {c.url.split('?')[0]}
415
+ {ms(c.wait_ms)}
416
+ </li>{/each}
417
+ </ul>
418
+ {/if}
419
+ {#each p.findings.filter((x) => x.severity !== 'info').slice(0, 2) as x, k (k)}<p
420
+ class="fix"
421
+ >
422
+ {x.message}
423
+ </p>{/each}
424
+ {#if p.links}<a class="full" href={p.links.html} target="_blank" rel="noopener"
425
+ >full report ↗</a
426
+ >{/if}
427
+ </div>
428
+ {/if}
429
+ {/if}
430
+ </li>
431
+ {/each}
432
+ </ul>
433
+ {:else}
434
+ <p class="muted ok">Nothing in this session looks wrong.</p>
435
+ {/if}
436
+
437
+ {#if r.interactions.length}
438
+ <h3>Slowest interactions</h3>
439
+ <table>
440
+ <tbody>
441
+ {#each r.interactions as x (x.i)}
442
+ <tr
443
+ onmouseenter={() => highlight(els_of([{ kind: 'interaction', i: x.i }]), x.target)}
444
+ onmouseleave={() => clear_highlight()}
445
+ >
446
+ <td>{x.name}</td>
447
+ <td class="tgt" title={x.target}
448
+ >{x.target}{#if x.fp}<span class="muted">
449
+ · {last?.data.names?.[x.fp] || region_name_by_fp(x.fp)}</span
450
+ >{/if}</td
451
+ >
452
+ <td class="num" class:warnc={x.duration >= 200}>{ms(x.duration)}</td>
453
+ </tr>
454
+ {/each}
455
+ </tbody>
456
+ </table>
457
+ {/if}
458
+
459
+ {#if r.requests.length}
460
+ <h3>
461
+ Requests <span class="muted">slowest first · the server's own split where it sent one</span>
462
+ </h3>
463
+ <table data-og-session-requests>
464
+ <tbody>
465
+ {#each r.requests as q, i (q.url + i)}
466
+ <tr>
467
+ <td class="tgt" title={q.url}
468
+ >{q.url}{#if q.status && q.status >= 400}<span class="warnc">
469
+ {q.status}</span
470
+ >{/if}</td
471
+ >
472
+ <td class="num">{ms(q.ms)}</td>
473
+ <td class="split">
474
+ {#if q.server_ms !== null}
475
+ {@const w = Math.max(q.ms, q.server_ms, 1)}
476
+ <span
477
+ class="bar"
478
+ title="server {ms(q.server_ms)}{q.net_ms !== null
479
+ ? ` (waiting on calls ${ms(q.net_ms)})`
480
+ : ''} · network {ms(Math.max(0, q.ms - q.server_ms))}"
481
+ >
482
+ <i class="srv" style:width="{((q.server_ms - (q.net_ms ?? 0)) / w) * 100}%"
483
+ ></i>{#if q.net_ms}<i class="up" style:width="{(q.net_ms / w) * 100}%"
484
+ ></i>{/if}<i
485
+ class="netw"
486
+ style:width="{(Math.max(0, q.ms - q.server_ms) / w) * 100}%"
487
+ ></i>
488
+ </span>
489
+ <span class="muted">{ms(q.server_ms)} server{q.up ? ` · ${q.up}` : ''}</span>
490
+ {/if}
491
+ </td>
492
+ </tr>
493
+ {/each}
494
+ </tbody>
495
+ </table>
496
+ {/if}
497
+
498
+ {#if r.cpu}
499
+ <h3>Main thread in the session <span class="muted">{ms(r.cpu.busy_ms)} busy</span></h3>
500
+ <table>
501
+ <tbody>
502
+ {#each r.cpu.fns.slice(0, 8) as f, i (f.name + f.file + i)}
503
+ <tr
504
+ ><td>{f.name}</td><td class="tgt"
505
+ >{f.file ? f.file.slice(f.file.lastIndexOf('/') + 1) : ''}{f.line !== null
506
+ ? `:${f.line}`
507
+ : ''}</td
508
+ ><td class="num">{ms(f.self_ms)}</td></tr
509
+ >
510
+ {/each}
511
+ </tbody>
512
+ </table>
513
+ {:else if last.data.cpu_off}
514
+ <p class="muted">
515
+ No CPU samples: {last.data.cpu_off === 'no-policy'
516
+ ? 'the document needs the Document-Policy: js-profiling header (the dev server sends it), or another recording held the sampler'
517
+ : 'this browser has no JS sampler'}. Long animation frames still name the scripts where
518
+ the browser supports them.
519
+ </p>
520
+ {/if}
521
+ {/if}
522
+ </div>
523
+
524
+ <style>
525
+ .rec {
526
+ display: flex;
527
+ flex-direction: column;
528
+ gap: 8px;
529
+ }
530
+ .bar {
531
+ display: flex;
532
+ align-items: center;
533
+ gap: 10px;
534
+ }
535
+ .go,
536
+ .stop {
537
+ padding: 6px 14px;
538
+ border-radius: 8px;
539
+ font: inherit;
540
+ font-weight: 700;
541
+ cursor: pointer;
542
+ }
543
+ .go {
544
+ border: 1px solid #be185d;
545
+ background: #db2777;
546
+ color: #fff;
547
+ }
548
+ .stop {
549
+ border: 1px solid rgba(148, 163, 184, 0.4);
550
+ background: #0d1526;
551
+ color: #fca5a5;
552
+ }
553
+ .live {
554
+ color: #fbcfe8;
555
+ display: inline-flex;
556
+ align-items: center;
557
+ gap: 6px;
558
+ }
559
+ .dot {
560
+ width: 8px;
561
+ height: 8px;
562
+ border-radius: 50%;
563
+ background: #ef4444;
564
+ animation: pulse 1s infinite alternate;
565
+ }
566
+ @keyframes pulse {
567
+ to {
568
+ opacity: 0.3;
569
+ }
570
+ }
571
+ .muted {
572
+ color: #94a3b8;
573
+ }
574
+ .ok {
575
+ color: #86efac;
576
+ }
577
+ .stats {
578
+ display: grid;
579
+ grid-template-columns: repeat(3, minmax(0, 1fr));
580
+ gap: 6px;
581
+ }
582
+ .stats div {
583
+ display: flex;
584
+ flex-direction: column;
585
+ padding: 5px 9px;
586
+ border: 1px solid rgba(148, 163, 184, 0.22);
587
+ border-radius: 8px;
588
+ }
589
+ .stats b {
590
+ font-size: 14px;
591
+ }
592
+ .stats span {
593
+ color: #94a3b8;
594
+ font-size: 10px;
595
+ }
596
+ .stats .bad b {
597
+ color: #f87171;
598
+ }
599
+ h3 {
600
+ margin: 8px 0 0;
601
+ font-size: 11px;
602
+ color: #cbd5e1;
603
+ text-transform: uppercase;
604
+ letter-spacing: 0.06em;
605
+ }
606
+ .findings {
607
+ list-style: none;
608
+ margin: 0;
609
+ padding: 0;
610
+ display: flex;
611
+ flex-direction: column;
612
+ gap: 6px;
613
+ }
614
+ .findings > li {
615
+ padding: 7px 9px;
616
+ border-radius: 8px;
617
+ border-left: 3px solid #38bdf8;
618
+ background: rgba(148, 163, 184, 0.07);
619
+ }
620
+ .findings > li.warn {
621
+ border-left-color: #f59e0b;
622
+ }
623
+ .findings > li.error {
624
+ border-left-color: #ef4444;
625
+ }
626
+ .msg,
627
+ .fix {
628
+ margin: 0;
629
+ line-height: 1.5;
630
+ }
631
+ .fix {
632
+ color: #94a3b8;
633
+ margin-top: 3px;
634
+ }
635
+ .chip {
636
+ margin-top: 5px;
637
+ font: inherit;
638
+ font-size: 10px;
639
+ padding: 1px 8px;
640
+ border-radius: 999px;
641
+ border: 1px solid rgba(244, 114, 182, 0.5);
642
+ background: none;
643
+ color: #f9a8d4;
644
+ cursor: pointer;
645
+ }
646
+ table {
647
+ border-collapse: collapse;
648
+ width: 100%;
649
+ }
650
+ td {
651
+ padding: 2px 4px;
652
+ border-bottom: 1px solid rgba(148, 163, 184, 0.08);
653
+ }
654
+ tr:hover td {
655
+ background: rgba(148, 163, 184, 0.08);
656
+ }
657
+ .tgt {
658
+ max-width: 260px;
659
+ overflow: hidden;
660
+ text-overflow: ellipsis;
661
+ white-space: nowrap;
662
+ }
663
+ .num {
664
+ text-align: right;
665
+ white-space: nowrap;
666
+ }
667
+ .warnc {
668
+ color: #fbbf24;
669
+ }
670
+ .srvtoggle {
671
+ display: inline-flex;
672
+ align-items: center;
673
+ gap: 4px;
674
+ color: #94a3b8;
675
+ }
676
+ .tl {
677
+ display: flex;
678
+ flex-direction: column;
679
+ gap: 3px;
680
+ }
681
+ .lane {
682
+ display: flex;
683
+ align-items: center;
684
+ }
685
+ .ll {
686
+ flex: none;
687
+ color: #94a3b8;
688
+ font-size: 10px;
689
+ }
690
+ .lane .track {
691
+ position: relative;
692
+ flex: 1;
693
+ height: 12px;
694
+ background: rgba(148, 163, 184, 0.06);
695
+ border-radius: 3px;
696
+ }
697
+ .it {
698
+ position: absolute;
699
+ top: 2px;
700
+ height: 8px;
701
+ border-radius: 2px;
702
+ display: block;
703
+ overflow: hidden;
704
+ cursor: default;
705
+ }
706
+ .it.point {
707
+ top: 0;
708
+ height: 12px;
709
+ width: 3px !important;
710
+ }
711
+ .it.ok {
712
+ background: #22c55e;
713
+ }
714
+ .it.info {
715
+ background: #38bdf8;
716
+ }
717
+ .it.warn {
718
+ background: #f59e0b;
719
+ }
720
+ .it.bad {
721
+ background: #ef4444;
722
+ }
723
+ .srvpart {
724
+ display: block;
725
+ height: 100%;
726
+ background: #14b8a6;
727
+ }
728
+ .uppart {
729
+ display: block;
730
+ height: 100%;
731
+ background: #a78bfa;
732
+ }
733
+ .tlhover {
734
+ margin: 2px 0 0;
735
+ min-height: 1.4em;
736
+ color: #e2e8f0;
737
+ font-size: 10.5px;
738
+ }
739
+ .chip.srv {
740
+ margin-left: 4px;
741
+ border-color: rgba(20, 184, 166, 0.55);
742
+ color: #5eead4;
743
+ }
744
+ .srvprof {
745
+ margin-top: 6px;
746
+ padding: 6px 9px;
747
+ border-radius: 7px;
748
+ background: rgba(20, 184, 166, 0.08);
749
+ border: 1px solid rgba(20, 184, 166, 0.25);
750
+ }
751
+ .srvprof p {
752
+ margin: 2px 0;
753
+ }
754
+ .srvprof ul {
755
+ margin: 0 0 4px;
756
+ padding-left: 16px;
757
+ }
758
+ .srvprof b {
759
+ color: #5eead4;
760
+ }
761
+ .srvprof b.ok {
762
+ color: #4ade80;
763
+ }
764
+ .full {
765
+ color: #94a3b8;
766
+ font-size: 10px;
767
+ }
768
+ .split {
769
+ display: flex;
770
+ align-items: center;
771
+ gap: 6px;
772
+ white-space: nowrap;
773
+ }
774
+ .bar {
775
+ display: inline-flex;
776
+ width: 90px;
777
+ height: 7px;
778
+ border-radius: 3px;
779
+ overflow: hidden;
780
+ background: rgba(148, 163, 184, 0.1);
781
+ flex: none;
782
+ }
783
+ .bar i {
784
+ display: block;
785
+ height: 100%;
786
+ }
787
+ .bar .srv {
788
+ background: #14b8a6;
789
+ }
790
+ .bar .up {
791
+ background: #a78bfa;
792
+ }
793
+ .bar .netw {
794
+ background: #475569;
795
+ }
796
+ </style>