@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.
- package/LICENSE +21 -0
- package/README.md +25 -2
- package/dist/client.d.ts +3 -0
- package/dist/client.js +24 -0
- package/dist/devtools/BoundaryOverlay.svelte +396 -0
- package/dist/devtools/BoundaryOverlay.svelte.d.ts +19 -0
- package/dist/devtools/Devtools.svelte +663 -0
- package/dist/devtools/Devtools.svelte.d.ts +15 -0
- package/dist/devtools/HubTab.svelte +206 -0
- package/dist/devtools/HubTab.svelte.d.ts +11 -0
- package/dist/devtools/HydrationTab.svelte +237 -0
- package/dist/devtools/HydrationTab.svelte.d.ts +13 -0
- package/dist/devtools/IslandDetail.svelte +615 -0
- package/dist/devtools/IslandDetail.svelte.d.ts +15 -0
- package/dist/devtools/LedgerTab.svelte +287 -0
- package/dist/devtools/LedgerTab.svelte.d.ts +11 -0
- package/dist/devtools/LensTab.svelte +290 -0
- package/dist/devtools/LensTab.svelte.d.ts +19 -0
- package/dist/devtools/NavTab.svelte +229 -0
- package/dist/devtools/NavTab.svelte.d.ts +11 -0
- package/dist/devtools/PageTab.svelte +1122 -0
- package/dist/devtools/PageTab.svelte.d.ts +15 -0
- package/dist/devtools/ProfilerTab.svelte +755 -0
- package/dist/devtools/ProfilerTab.svelte.d.ts +15 -0
- package/dist/devtools/PropsTree.svelte +155 -0
- package/dist/devtools/PropsTree.svelte.d.ts +15 -0
- package/dist/devtools/SessionTab.svelte +796 -0
- package/dist/devtools/SessionTab.svelte.d.ts +11 -0
- package/dist/devtools/TimelineTab.svelte +267 -0
- package/dist/devtools/TimelineTab.svelte.d.ts +11 -0
- package/dist/devtools/WireTab.svelte +149 -0
- package/dist/devtools/WireTab.svelte.d.ts +11 -0
- package/dist/devtools/bytes.d.ts +70 -0
- package/dist/devtools/bytes.js +82 -0
- package/dist/devtools/cache-headers.d.ts +20 -0
- package/dist/devtools/cache-headers.js +81 -0
- package/dist/devtools/highlight.d.ts +7 -0
- package/dist/devtools/highlight.js +83 -0
- package/dist/devtools/hydration.d.ts +48 -0
- package/dist/devtools/hydration.js +93 -0
- package/dist/devtools/index.d.ts +3 -0
- package/dist/devtools/index.js +3 -0
- package/dist/devtools/ledger-dom.d.ts +7 -0
- package/dist/devtools/ledger-dom.js +151 -0
- package/dist/devtools/leftovers.d.ts +77 -0
- package/dist/devtools/leftovers.js +319 -0
- package/dist/devtools/names.d.ts +1 -0
- package/dist/devtools/names.js +34 -0
- package/dist/devtools/page.d.ts +96 -0
- package/dist/devtools/page.js +957 -0
- package/dist/devtools/profile-anchors.d.ts +22 -0
- package/dist/devtools/profile-anchors.js +135 -0
- package/dist/devtools/profile-store.d.ts +175 -0
- package/dist/devtools/profile-store.js +239 -0
- package/dist/devtools/regions.d.ts +77 -0
- package/dist/devtools/regions.js +132 -0
- package/dist/devtools/session.d.ts +31 -0
- package/dist/devtools/session.js +455 -0
- package/dist/devtools/since-load.d.ts +50 -0
- package/dist/devtools/since-load.js +89 -0
- package/dist/devtools/source-lines.d.ts +24 -0
- package/dist/devtools/source-lines.js +106 -0
- package/dist/devtools/styles.d.ts +52 -0
- package/dist/devtools/styles.js +248 -0
- package/dist/devtools/testing-engine.d.ts +83 -0
- package/dist/devtools/testing-engine.js +220 -0
- package/dist/devtools/ui-app.d.ts +5 -0
- package/dist/devtools/ui-app.js +69 -0
- package/dist/devtools/ui.d.ts +50 -0
- package/dist/devtools/ui.js +175 -0
- package/dist/profiler/alloc.d.ts +75 -0
- package/dist/profiler/alloc.js +76 -0
- package/dist/profiler/analyze.d.ts +437 -0
- package/dist/profiler/analyze.js +1941 -0
- package/dist/profiler/app-path.d.ts +7 -0
- package/dist/profiler/app-path.js +26 -0
- package/dist/profiler/async-io.d.ts +58 -0
- package/dist/profiler/async-io.js +187 -0
- package/dist/profiler/beacon-standalone.d.ts +4 -0
- package/dist/profiler/beacon-standalone.js +409 -0
- package/dist/profiler/browser-findings.d.ts +25 -0
- package/dist/profiler/browser-findings.js +260 -0
- package/dist/profiler/byte-strip.d.ts +58 -0
- package/dist/profiler/byte-strip.js +228 -0
- package/dist/profiler/client-files.d.ts +16 -0
- package/dist/profiler/client-files.js +60 -0
- package/dist/profiler/client-windows.d.ts +49 -0
- package/dist/profiler/client-windows.js +95 -0
- package/dist/profiler/client.d.ts +40 -0
- package/dist/profiler/client.js +82 -0
- package/dist/profiler/compare.d.ts +308 -0
- package/dist/profiler/compare.js +733 -0
- package/dist/profiler/compress.d.ts +10 -0
- package/dist/profiler/compress.js +36 -0
- package/dist/profiler/contention.d.ts +60 -0
- package/dist/profiler/contention.js +86 -0
- package/dist/profiler/crypto.d.ts +19 -0
- package/dist/profiler/crypto.js +101 -0
- package/dist/profiler/doc-diff.d.ts +47 -0
- package/dist/profiler/doc-diff.js +214 -0
- package/dist/profiler/dom-diff.d.ts +43 -0
- package/dist/profiler/dom-diff.js +121 -0
- package/dist/profiler/drill.d.ts +89 -0
- package/dist/profiler/drill.js +553 -0
- package/dist/profiler/embedded-maps.d.ts +37 -0
- package/dist/profiler/embedded-maps.js +135 -0
- package/dist/profiler/env.d.ts +6 -0
- package/dist/profiler/env.js +10 -0
- package/dist/profiler/forecast.d.ts +76 -0
- package/dist/profiler/forecast.js +225 -0
- package/dist/profiler/frames.d.ts +11 -0
- package/dist/profiler/frames.js +42 -0
- package/dist/profiler/gc.d.ts +144 -0
- package/dist/profiler/gc.js +271 -0
- package/dist/profiler/hole-slots.d.ts +31 -0
- package/dist/profiler/hole-slots.js +90 -0
- package/dist/profiler/index.d.ts +288 -0
- package/dist/profiler/index.js +4921 -0
- package/dist/profiler/insights.d.ts +96 -0
- package/dist/profiler/insights.js +239 -0
- package/dist/profiler/ledger.d.ts +121 -0
- package/dist/profiler/ledger.js +440 -0
- package/dist/profiler/lineage.d.ts +81 -0
- package/dist/profiler/lineage.js +359 -0
- package/dist/profiler/module-map.d.ts +12 -0
- package/dist/profiler/module-map.js +94 -0
- package/dist/profiler/net.d.ts +169 -0
- package/dist/profiler/net.js +586 -0
- package/dist/profiler/on-demand.d.ts +11 -0
- package/dist/profiler/on-demand.js +15 -0
- package/dist/profiler/own-requests.d.ts +9 -0
- package/dist/profiler/own-requests.js +17 -0
- package/dist/profiler/patterns.d.ts +476 -0
- package/dist/profiler/patterns.js +2203 -0
- package/dist/profiler/profiler-router.d.ts +245 -0
- package/dist/profiler/profiler-router.js +113 -0
- package/dist/profiler/refresh-stub.d.ts +2 -0
- package/dist/profiler/refresh-stub.js +37 -0
- package/dist/profiler/report.d.ts +1404 -0
- package/dist/profiler/report.js +2638 -0
- package/dist/profiler/river.d.ts +65 -0
- package/dist/profiler/river.js +298 -0
- package/dist/profiler/route-files.d.ts +22 -0
- package/dist/profiler/route-files.js +55 -0
- package/dist/profiler/route-imports.d.ts +14 -0
- package/dist/profiler/route-imports.js +83 -0
- package/dist/profiler/saving.d.ts +22 -0
- package/dist/profiler/saving.js +42 -0
- package/dist/profiler/score.d.ts +109 -0
- package/dist/profiler/score.js +261 -0
- package/dist/profiler/session-cookie.d.ts +28 -0
- package/dist/profiler/session-cookie.js +45 -0
- package/dist/profiler/sink.d.ts +81 -0
- package/dist/profiler/sink.js +124 -0
- package/dist/profiler/site-fixes.d.ts +25 -0
- package/dist/profiler/site-fixes.js +62 -0
- package/dist/profiler/source-scan.d.ts +210 -0
- package/dist/profiler/source-scan.js +1724 -0
- package/dist/profiler/span.d.ts +90 -0
- package/dist/profiler/span.js +109 -0
- package/dist/profiler/stacks.d.ts +43 -0
- package/dist/profiler/stacks.js +107 -0
- package/dist/profiler/standalone.d.ts +27 -0
- package/dist/profiler/standalone.js +98 -0
- package/dist/profiler/steps.d.ts +39 -0
- package/dist/profiler/steps.js +74 -0
- package/dist/profiler/storage/index.d.ts +87 -0
- package/dist/profiler/storage/index.js +76 -0
- package/dist/profiler/storage/postgres.d.ts +12 -0
- package/dist/profiler/storage/postgres.js +94 -0
- package/dist/profiler/storage/redis.d.ts +19 -0
- package/dist/profiler/storage/redis.js +148 -0
- package/dist/profiler/storage/sqlite.d.ts +2 -0
- package/dist/profiler/storage/sqlite.js +85 -0
- package/dist/profiler/storage/visits.d.ts +4 -0
- package/dist/profiler/storage/visits.js +6 -0
- package/dist/profiler/stream-tail.d.ts +26 -0
- package/dist/profiler/stream-tail.js +74 -0
- package/dist/profiler/timeline.d.ts +279 -0
- package/dist/profiler/timeline.js +690 -0
- package/dist/profiler/ui/AllocStrip.svelte +146 -0
- package/dist/profiler/ui/AllocStrip.svelte.d.ts +17 -0
- package/dist/profiler/ui/Awaits.svelte +363 -0
- package/dist/profiler/ui/Awaits.svelte.d.ts +16 -0
- package/dist/profiler/ui/ByteStrip.svelte +182 -0
- package/dist/profiler/ui/ByteStrip.svelte.d.ts +18 -0
- package/dist/profiler/ui/Compare.svelte +24 -0
- package/dist/profiler/ui/Compare.svelte.d.ts +16 -0
- package/dist/profiler/ui/CompareBody.svelte +429 -0
- package/dist/profiler/ui/CompareBody.svelte.d.ts +8 -0
- package/dist/profiler/ui/ComponentsTable.svelte +265 -0
- package/dist/profiler/ui/ComponentsTable.svelte.d.ts +20 -0
- package/dist/profiler/ui/Dashboard.svelte +492 -0
- package/dist/profiler/ui/Dashboard.svelte.d.ts +31 -0
- package/dist/profiler/ui/DataRiver.svelte +206 -0
- package/dist/profiler/ui/DataRiver.svelte.d.ts +13 -0
- package/dist/profiler/ui/DomTravel.svelte +207 -0
- package/dist/profiler/ui/DomTravel.svelte.d.ts +9 -0
- package/dist/profiler/ui/DownloadHtml.svelte +52 -0
- package/dist/profiler/ui/DownloadHtml.svelte.d.ts +10 -0
- package/dist/profiler/ui/Drill.svelte +276 -0
- package/dist/profiler/ui/Drill.svelte.d.ts +12 -0
- package/dist/profiler/ui/ExportButton.svelte +59 -0
- package/dist/profiler/ui/ExportButton.svelte.d.ts +8 -0
- package/dist/profiler/ui/Flame.svelte +222 -0
- package/dist/profiler/ui/Flame.svelte.d.ts +12 -0
- package/dist/profiler/ui/FrameDetails.svelte +308 -0
- package/dist/profiler/ui/FrameDetails.svelte.d.ts +16 -0
- package/dist/profiler/ui/FunctionsTable.svelte +156 -0
- package/dist/profiler/ui/FunctionsTable.svelte.d.ts +17 -0
- package/dist/profiler/ui/IslandHeatmap.svelte +151 -0
- package/dist/profiler/ui/IslandHeatmap.svelte.d.ts +16 -0
- package/dist/profiler/ui/IslandsTable.svelte +448 -0
- package/dist/profiler/ui/IslandsTable.svelte.d.ts +16 -0
- package/dist/profiler/ui/KeepLocal.svelte +46 -0
- package/dist/profiler/ui/KeepLocal.svelte.d.ts +8 -0
- package/dist/profiler/ui/Ledger.svelte +196 -0
- package/dist/profiler/ui/Ledger.svelte.d.ts +18 -0
- package/dist/profiler/ui/LocalCompare.svelte +48 -0
- package/dist/profiler/ui/LocalCompare.svelte.d.ts +8 -0
- package/dist/profiler/ui/LocalReports.svelte +68 -0
- package/dist/profiler/ui/LocalReports.svelte.d.ts +7 -0
- package/dist/profiler/ui/Login.svelte +13 -0
- package/dist/profiler/ui/Login.svelte.d.ts +12 -0
- package/dist/profiler/ui/LoginForm.svelte +42 -0
- package/dist/profiler/ui/LoginForm.svelte.d.ts +7 -0
- package/dist/profiler/ui/Message.svelte +10 -0
- package/dist/profiler/ui/Message.svelte.d.ts +12 -0
- package/dist/profiler/ui/OnThisPage.svelte +137 -0
- package/dist/profiler/ui/OnThisPage.svelte.d.ts +3 -0
- package/dist/profiler/ui/OneClock.svelte +297 -0
- package/dist/profiler/ui/OneClock.svelte.d.ts +27 -0
- package/dist/profiler/ui/Panel.svelte +10 -0
- package/dist/profiler/ui/Panel.svelte.d.ts +16 -0
- package/dist/profiler/ui/Paths.svelte +269 -0
- package/dist/profiler/ui/Paths.svelte.d.ts +15 -0
- package/dist/profiler/ui/Patterns.svelte +364 -0
- package/dist/profiler/ui/Patterns.svelte.d.ts +15 -0
- package/dist/profiler/ui/PermalinkGate.svelte +143 -0
- package/dist/profiler/ui/PermalinkGate.svelte.d.ts +10 -0
- package/dist/profiler/ui/RenderSteps.svelte +218 -0
- package/dist/profiler/ui/RenderSteps.svelte.d.ts +13 -0
- package/dist/profiler/ui/Report.svelte +28 -0
- package/dist/profiler/ui/Report.svelte.d.ts +20 -0
- package/dist/profiler/ui/ReportBody.svelte +2592 -0
- package/dist/profiler/ui/ReportBody.svelte.d.ts +21 -0
- package/dist/profiler/ui/Run.svelte +17 -0
- package/dist/profiler/ui/Run.svelte.d.ts +14 -0
- package/dist/profiler/ui/RunView.svelte +146 -0
- package/dist/profiler/ui/RunView.svelte.d.ts +10 -0
- package/dist/profiler/ui/ScoreCard.svelte +197 -0
- package/dist/profiler/ui/ScoreCard.svelte.d.ts +7 -0
- package/dist/profiler/ui/Scrub.svelte +293 -0
- package/dist/profiler/ui/Scrub.svelte.d.ts +18 -0
- package/dist/profiler/ui/SeedExplainer.svelte +193 -0
- package/dist/profiler/ui/SeedExplainer.svelte.d.ts +16 -0
- package/dist/profiler/ui/ShareLink.svelte +148 -0
- package/dist/profiler/ui/ShareLink.svelte.d.ts +8 -0
- package/dist/profiler/ui/Shell.svelte +50 -0
- package/dist/profiler/ui/Shell.svelte.d.ts +27 -0
- package/dist/profiler/ui/Sidebar.svelte +312 -0
- package/dist/profiler/ui/Sidebar.svelte.d.ts +7 -0
- package/dist/profiler/ui/Site.svelte +20 -0
- package/dist/profiler/ui/Site.svelte.d.ts +23 -0
- package/dist/profiler/ui/SiteView.svelte +385 -0
- package/dist/profiler/ui/SiteView.svelte.d.ts +27 -0
- package/dist/profiler/ui/ThemeSwitch.svelte +78 -0
- package/dist/profiler/ui/ThemeSwitch.svelte.d.ts +3 -0
- package/dist/profiler/ui/Timeline.svelte +672 -0
- package/dist/profiler/ui/Timeline.svelte.d.ts +22 -0
- package/dist/profiler/ui/Treemap.svelte +278 -0
- package/dist/profiler/ui/Treemap.svelte.d.ts +20 -0
- package/dist/profiler/ui/Upload.svelte +19 -0
- package/dist/profiler/ui/Upload.svelte.d.ts +10 -0
- package/dist/profiler/ui/UploadForm.svelte +59 -0
- package/dist/profiler/ui/UploadForm.svelte.d.ts +6 -0
- package/dist/profiler/ui/WaitingTable.svelte +55 -0
- package/dist/profiler/ui/WaitingTable.svelte.d.ts +14 -0
- package/dist/profiler/ui/Waterfall.svelte +371 -0
- package/dist/profiler/ui/Waterfall.svelte.d.ts +7 -0
- package/dist/profiler/ui/WhatIf.svelte +87 -0
- package/dist/profiler/ui/WhatIf.svelte.d.ts +18 -0
- package/dist/profiler/ui/fake-progress.svelte.d.ts +15 -0
- package/dist/profiler/ui/fake-progress.svelte.js +38 -0
- package/dist/profiler/ui/format.d.ts +20 -0
- package/dist/profiler/ui/format.js +71 -0
- package/dist/profiler/ui/permalink.d.ts +9 -0
- package/dist/profiler/ui/permalink.js +108 -0
- package/dist/profiler/ui/press.d.ts +5 -0
- package/dist/profiler/ui/press.js +19 -0
- package/dist/profiler/ui/profiler.css +650 -0
- package/dist/profiler/ui/report-data.d.ts +189 -0
- package/dist/profiler/ui/report-data.js +393 -0
- package/dist/profiler/ui/report-request.d.ts +6 -0
- package/dist/profiler/ui/report-request.js +50 -0
- package/dist/profiler/ui/row-anchor.svelte.d.ts +12 -0
- package/dist/profiler/ui/row-anchor.svelte.js +44 -0
- package/dist/profiler/ui/sort.svelte.d.ts +14 -0
- package/dist/profiler/ui/sort.svelte.js +37 -0
- package/dist/profiler/ui/store.d.ts +45 -0
- package/dist/profiler/ui/store.js +92 -0
- package/dist/profiler/visit.d.ts +389 -0
- package/dist/profiler/visit.js +1002 -0
- package/dist/report.d.ts +5 -0
- package/dist/report.js +4 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.js +38 -0
- package/dist/vite.d.ts +12 -0
- package/dist/vite.js +78 -0
- package/package.json +103 -3
|
@@ -0,0 +1,1122 @@
|
|
|
1
|
+
<!-- (its CSS injected into the dock's shadow root: see Devtools.svelte) -->
|
|
2
|
+
<svelte:options css="injected" />
|
|
3
|
+
|
|
4
|
+
<script>
|
|
5
|
+
/**
|
|
6
|
+
* The Page tab: what the BROWSER saw of this visit — the profiler beacon's data, read live (no tag,
|
|
7
|
+
* no server; runtime/beacon.ts records it on a devtools build). The vitals rated, what the page
|
|
8
|
+
* found wrong (each finding names its islands: hover lights them up, click opens the island), a
|
|
9
|
+
* waterfall of every island's wake (module → turn → hydrate, against FCP / LCP / DCL / load), the
|
|
10
|
+
* files that blocked the first paint, and the bytes by type. The analysis is page-insights.ts.
|
|
11
|
+
*/
|
|
12
|
+
import { read_page, cpu_of, summary_lines } from './page.js';
|
|
13
|
+
import { first_difference, rate } from '@ogygia/core/insights';
|
|
14
|
+
import { beacon_record_cpu } from '@ogygia/core/internal/instruments';
|
|
15
|
+
import { read_styles, scan_unscoped, styles_findings } from './styles.js';
|
|
16
|
+
import { hole_segments, HOLE_SEG_LABEL } from '@ogygia/core/insights';
|
|
17
|
+
import { cache_findings, probe_cache } from './cache-headers.js';
|
|
18
|
+
|
|
19
|
+
let { tick = 0, focus = $bindable(null), selected = $bindable(null) } = $props();
|
|
20
|
+
|
|
21
|
+
// THE PAGE'S STYLES: read once the tab opens (selectors tried against the page, the build's
|
|
22
|
+
// unscoped markers fetched from the cache), after each in-app navigation, and on "check again" —
|
|
23
|
+
// not on every tick
|
|
24
|
+
let styles = $state(/** @type {import('./styles.js').StylesReport | null} */ (null));
|
|
25
|
+
let styles_busy = $state(false);
|
|
26
|
+
async function check_styles() {
|
|
27
|
+
styles_busy = true;
|
|
28
|
+
try {
|
|
29
|
+
const s = read_styles(document, { match: true });
|
|
30
|
+
s.unscoped = await scan_unscoped(document);
|
|
31
|
+
styles = s;
|
|
32
|
+
} catch {
|
|
33
|
+
styles = null;
|
|
34
|
+
}
|
|
35
|
+
styles_busy = false;
|
|
36
|
+
}
|
|
37
|
+
// HOW THE HOST CACHES THE ISLANDS' FILES (cache-headers.ts): asked once per page, beside the styles
|
|
38
|
+
let cache = $state(/** @type {import('./cache-headers.js').CacheProbe[] | null} */ (null));
|
|
39
|
+
async function check_cache() {
|
|
40
|
+
try {
|
|
41
|
+
cache = await probe_cache(document);
|
|
42
|
+
} catch {
|
|
43
|
+
cache = null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// again after an in-app navigation (a new page, new sheets): keyed on the navigation's time — a
|
|
47
|
+
// number, so the ticks that rebuild the view do not re-run a check that queries every selector
|
|
48
|
+
const nav_key = $derived(view?.nav?.t ?? 0);
|
|
49
|
+
$effect(() => {
|
|
50
|
+
void nav_key;
|
|
51
|
+
const id = setTimeout(
|
|
52
|
+
() => {
|
|
53
|
+
check_styles();
|
|
54
|
+
check_cache();
|
|
55
|
+
},
|
|
56
|
+
nav_key ? 400 : 60
|
|
57
|
+
);
|
|
58
|
+
return () => clearTimeout(id);
|
|
59
|
+
});
|
|
60
|
+
const findings = $derived.by(() => {
|
|
61
|
+
if (!view) return [];
|
|
62
|
+
const extra = [
|
|
63
|
+
...(styles ? styles_findings(styles) : []),
|
|
64
|
+
...(cache ? cache_findings(cache) : [])
|
|
65
|
+
].map((f) => ({ ...f, fps: /** @type {string[]} */ ([]) }));
|
|
66
|
+
// errors, then warnings, then notes: the report's own order (a stable sort keeps the rest)
|
|
67
|
+
const order = /** @type {Record<string, number>} */ ({ error: 0, warn: 1, info: 2 });
|
|
68
|
+
return [...view.report.findings, ...extra].sort(
|
|
69
|
+
(a, b) => order[a.severity] - order[b.severity]
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
let wrapW = $state(560);
|
|
74
|
+
let diff_fp = $state(/** @type {string | null} */ (null));
|
|
75
|
+
const LABEL_W = 132;
|
|
76
|
+
const trackW = $derived(Math.max(160, wrapW - LABEL_W - 70));
|
|
77
|
+
|
|
78
|
+
const view = $derived.by(() => {
|
|
79
|
+
void tick;
|
|
80
|
+
try {
|
|
81
|
+
return read_page();
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const axis = $derived.by(() => {
|
|
88
|
+
if (!view) return null;
|
|
89
|
+
const { report, page } = view;
|
|
90
|
+
const nav = page.visit?.nav ?? {};
|
|
91
|
+
const paints = page.visit?.paints ?? {};
|
|
92
|
+
const lines = [
|
|
93
|
+
{ k: 'FCP', t: paints.fcp, c: '#38bdf8' },
|
|
94
|
+
{ k: 'LCP', t: paints.lcp, c: '#f472b6' },
|
|
95
|
+
{ k: 'DCL', t: nav.dcl, c: '#94a3b8' },
|
|
96
|
+
{ k: 'load', t: nav.load, c: '#64748b' }
|
|
97
|
+
].filter((l) => typeof l.t === 'number' && l.t > 0);
|
|
98
|
+
let end = 1;
|
|
99
|
+
for (const r of report.rows) end = Math.max(end, r.done);
|
|
100
|
+
for (const h of view.holes ?? []) end = Math.max(end, (h.shown_at ?? 0) + h.wait_ms);
|
|
101
|
+
for (const l of lines) end = Math.max(end, l.t);
|
|
102
|
+
end *= 1.04;
|
|
103
|
+
const x = (/** @type {number} */ t) => Math.max(0, Math.min(trackW, (t / end) * trackW));
|
|
104
|
+
return { end, lines, x };
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// a hole's bar, cut where the browser and the server timed it (the profiler's One clock cuts it
|
|
108
|
+
// the same way: hole-segments.ts)
|
|
109
|
+
const hole_segs = hole_segments;
|
|
110
|
+
const SEG_TITLE = HOLE_SEG_LABEL;
|
|
111
|
+
|
|
112
|
+
const kb = (/** @type {number} */ n) =>
|
|
113
|
+
n < 1024
|
|
114
|
+
? n + ' B'
|
|
115
|
+
: n < 1048576
|
|
116
|
+
? (n / 1024).toFixed(1) + ' KB'
|
|
117
|
+
: (n / 1048576).toFixed(2) + ' MB';
|
|
118
|
+
const ms = (/** @type {number} */ n) =>
|
|
119
|
+
n >= 1000 ? (n / 1000).toFixed(2) + ' s' : Math.round(n) + ' ms';
|
|
120
|
+
|
|
121
|
+
function el_of(/** @type {string} */ fp) {
|
|
122
|
+
return document.querySelector(`ogygia-region[data-og-fp="${fp}"]`);
|
|
123
|
+
}
|
|
124
|
+
function light(/** @type {string[]} */ fps) {
|
|
125
|
+
focus = fps.length ? el_of(fps[0]) : null;
|
|
126
|
+
}
|
|
127
|
+
function open(/** @type {string} */ fp) {
|
|
128
|
+
const el = el_of(fp);
|
|
129
|
+
if (el) selected = el;
|
|
130
|
+
}
|
|
131
|
+
// a hole has no fingerprint: its endpoint finds it
|
|
132
|
+
function hole_el(/** @type {string | undefined} */ endpoint) {
|
|
133
|
+
return endpoint
|
|
134
|
+
? document.querySelector(`ogygia-region[endpoint="${CSS.escape(endpoint)}"]`)
|
|
135
|
+
: null;
|
|
136
|
+
}
|
|
137
|
+
function vital_text(v) {
|
|
138
|
+
return v.key === 'cls' ? String(v.value) : ms(v.value);
|
|
139
|
+
}
|
|
140
|
+
// the server's own account of THIS document (its Server-Timing: ogygia's profiler sends it in dev)
|
|
141
|
+
const doc_server = (() => {
|
|
142
|
+
try {
|
|
143
|
+
const nav = performance.getEntriesByType('navigation')[0];
|
|
144
|
+
const st = nav?.serverTiming ?? [];
|
|
145
|
+
const get = (n) => st.find((x) => x.name === n);
|
|
146
|
+
const ssr = get('ssr');
|
|
147
|
+
if (!ssr) return null;
|
|
148
|
+
const net = get('net');
|
|
149
|
+
const ups = ['up1', 'up2', 'up3'].map(get).filter(Boolean);
|
|
150
|
+
return {
|
|
151
|
+
ssr: ssr.duration,
|
|
152
|
+
net: net?.duration ?? null,
|
|
153
|
+
calls: net?.description ?? '',
|
|
154
|
+
ups: ups.map((u) => ({ desc: u.description, ms: u.duration }))
|
|
155
|
+
};
|
|
156
|
+
} catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
})();
|
|
160
|
+
// the document's vitals (after a navigation the report drops them; they still show, labelled)
|
|
161
|
+
const LABEL = { ttfb: 'TTFB', fcp: 'FCP', lcp: 'LCP', cls: 'CLS', inp: 'INP' };
|
|
162
|
+
const doc_vitals = $derived.by(() => {
|
|
163
|
+
if (!view) return [];
|
|
164
|
+
if (!view.nav) return view.report.vitals;
|
|
165
|
+
const out = [];
|
|
166
|
+
for (const key of ['ttfb', 'fcp', 'lcp', 'cls', 'inp']) {
|
|
167
|
+
const v = view.page.vitals[key];
|
|
168
|
+
if (typeof v === 'number')
|
|
169
|
+
out.push({ key, label: LABEL[key], value: v, rating: rate(key, v) });
|
|
170
|
+
}
|
|
171
|
+
return out;
|
|
172
|
+
});
|
|
173
|
+
// ── the main thread (the browser's sampler): the load trace, or a recording made here ──
|
|
174
|
+
let pick = $state(0);
|
|
175
|
+
let recording = $state(false);
|
|
176
|
+
const traces = $derived(view?.page.cpu.traces ?? []);
|
|
177
|
+
const cpu = $derived.by(() => {
|
|
178
|
+
const t = traces[Math.min(pick, traces.length - 1)];
|
|
179
|
+
// (with the source's lines: the served code's are not; `view` changes on each tick, so a file's
|
|
180
|
+
// map read since the last one shows up then)
|
|
181
|
+
return t && view ? summary_lines(cpu_of(view.page, t.trace)) : null;
|
|
182
|
+
});
|
|
183
|
+
async function record() {
|
|
184
|
+
recording = true;
|
|
185
|
+
try {
|
|
186
|
+
await beacon_record_cpu(5000, `recording ${new Date().toLocaleTimeString()}`);
|
|
187
|
+
pick = 0; // the newest
|
|
188
|
+
} finally {
|
|
189
|
+
recording = false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const KIND_COLOR = {
|
|
193
|
+
app: '#22c55e',
|
|
194
|
+
ogygia: '#14b8a6',
|
|
195
|
+
svelte: '#f97316',
|
|
196
|
+
dependency: '#a78bfa',
|
|
197
|
+
'page script': '#f472b6',
|
|
198
|
+
browser: '#64748b',
|
|
199
|
+
devtools: '#334155'
|
|
200
|
+
};
|
|
201
|
+
const diff = $derived.by(() => {
|
|
202
|
+
if (!view || !diff_fp) return null;
|
|
203
|
+
const s = view.page.snapshots.find((x) => x.fp === diff_fp);
|
|
204
|
+
return s ? first_difference(s.ssr, s.hydrated, 120) : null;
|
|
205
|
+
});
|
|
206
|
+
</script>
|
|
207
|
+
|
|
208
|
+
<div class="pg" bind:clientWidth={wrapW}>
|
|
209
|
+
{#if !view}
|
|
210
|
+
<p class="muted" data-og-page-unmeasured>
|
|
211
|
+
This page load was not measured: in a build, devtools measures only for a browser that opened
|
|
212
|
+
it before the page loaded (so other visitors pay nothing). <b>Reload</b> and it is measured from
|
|
213
|
+
the start.
|
|
214
|
+
</p>
|
|
215
|
+
{:else}
|
|
216
|
+
<div class="cap">
|
|
217
|
+
page <span class="muted"
|
|
218
|
+
>· what the browser saw of this visit (dev server: modules load one by one, so load times
|
|
219
|
+
run high)</span
|
|
220
|
+
>
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
{#if view.since && (view.since.fixed.length || view.since.added.length || view.since.moved.length || view.since.note)}
|
|
224
|
+
{@const s = view.since}
|
|
225
|
+
<!-- the dev loop: changed the code, reloaded — what changed on this page -->
|
|
226
|
+
<div class="since" data-og-page-since>
|
|
227
|
+
<b>Since your last load</b> <span class="muted">({ms(s.ago_ms)} ago)</span>
|
|
228
|
+
{#if s.fixed.length}<p class="good">fixed: {s.fixed.join(' · ')}</p>{/if}
|
|
229
|
+
{#if s.added.length}<p class="bad">new: {s.added.join(' · ')}</p>{/if}
|
|
230
|
+
{#if s.moved.length}
|
|
231
|
+
<p>
|
|
232
|
+
{#each s.moved as m, i (m.what)}{i ? ' · ' : ''}<span class={m.better ? 'good' : 'bad'}
|
|
233
|
+
>{m.what}
|
|
234
|
+
{m.unit ? ms(m.a) : m.a} → {m.unit ? ms(m.b) : m.b}{#if m.part}<span
|
|
235
|
+
class="muted"
|
|
236
|
+
data-og-page-since-part
|
|
237
|
+
>, mostly {m.part.label} {ms(m.part.a)} → {ms(m.part.b)}</span
|
|
238
|
+
>{/if}</span
|
|
239
|
+
>{/each}
|
|
240
|
+
</p>
|
|
241
|
+
{/if}
|
|
242
|
+
{#if s.note}<p class="muted" data-og-page-since-note>{s.note}</p>{/if}
|
|
243
|
+
</div>
|
|
244
|
+
{/if}
|
|
245
|
+
|
|
246
|
+
{#if view.unmeasured.length}
|
|
247
|
+
<p class="navnote" data-og-page-browser-limits>
|
|
248
|
+
This browser does not report {view.unmeasured.join(', ')}. Findings that need them cannot
|
|
249
|
+
appear here, so their absence is not a clean bill: check those in a Chromium browser.
|
|
250
|
+
</p>
|
|
251
|
+
{/if}
|
|
252
|
+
|
|
253
|
+
{#if view.nav}
|
|
254
|
+
<p class="navnote" data-og-page-nav>
|
|
255
|
+
Since the in-app navigation to <code>{view.nav.to}</code> ({ms(
|
|
256
|
+
performance.now() - view.nav.t
|
|
257
|
+
)} ago): the findings and island rows below are this page's. The vitals are the first page's load
|
|
258
|
+
(a navigation paints nothing the browser reports); record the main thread again to see this page's.
|
|
259
|
+
</p>
|
|
260
|
+
{/if}
|
|
261
|
+
<div class="vitals" data-og-vitals>
|
|
262
|
+
{#each doc_vitals as v (v.key)}
|
|
263
|
+
<div
|
|
264
|
+
class="vital {v.rating}"
|
|
265
|
+
title="{v.label}: {v.rating === 'good'
|
|
266
|
+
? 'good'
|
|
267
|
+
: v.rating === 'fair'
|
|
268
|
+
? 'needs work'
|
|
269
|
+
: 'poor'}"
|
|
270
|
+
>
|
|
271
|
+
<span class="k">{v.label}</span><span class="v">{vital_text(v)}</span>
|
|
272
|
+
</div>
|
|
273
|
+
{:else}
|
|
274
|
+
<span class="muted">no vitals yet</span>
|
|
275
|
+
{/each}
|
|
276
|
+
{#if view.report.longtask_ms}
|
|
277
|
+
<div class="vital neutral" title="main-thread time in tasks over 50 ms">
|
|
278
|
+
<span class="k">long tasks</span><span class="v">{ms(view.report.longtask_ms)}</span>
|
|
279
|
+
</div>
|
|
280
|
+
{/if}
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
{#if doc_server}
|
|
284
|
+
<p class="srvline" data-og-page-server>
|
|
285
|
+
The server rendered this page in <b>{ms(doc_server.ssr)}</b>{#if doc_server.net !== null}, {ms(
|
|
286
|
+
doc_server.net
|
|
287
|
+
)} of it waiting on {doc_server.calls.split('(')[1]?.split(')')[0] ?? 'its'} outbound calls{#if doc_server.ups.length}
|
|
288
|
+
— the slowest {doc_server.ups.map((u) => `${u.desc} ${ms(u.ms)}`).join(', ')}{/if}{/if}.
|
|
289
|
+
</p>
|
|
290
|
+
{/if}
|
|
291
|
+
|
|
292
|
+
<h3>Findings <span class="muted">{findings.length || 'none'}</span></h3>
|
|
293
|
+
{#if findings.length}
|
|
294
|
+
<ul class="findings" data-og-page-findings>
|
|
295
|
+
{#each findings as f, i (f.code + i)}
|
|
296
|
+
<li
|
|
297
|
+
class={f.severity}
|
|
298
|
+
data-code={f.code}
|
|
299
|
+
onmouseenter={() => light(f.fps)}
|
|
300
|
+
onmouseleave={() => (focus = null)}
|
|
301
|
+
>
|
|
302
|
+
<p class="msg"><span class="sev">{f.severity}</span>{f.message}</p>
|
|
303
|
+
{#if f.fix}<p class="fix">{f.fix}</p>{/if}
|
|
304
|
+
{#if f.fps.length}
|
|
305
|
+
<p class="acts">
|
|
306
|
+
{#each f.fps.slice(0, 6) as fp (fp)}
|
|
307
|
+
<button class="chip" onclick={() => open(fp)}
|
|
308
|
+
>{view.regions.find((r) => r.fp === fp)?.name ?? fp.slice(0, 8)}</button
|
|
309
|
+
>
|
|
310
|
+
{/each}
|
|
311
|
+
{#if f.code === 'markup-changed'}
|
|
312
|
+
<button
|
|
313
|
+
class="chip alt"
|
|
314
|
+
onclick={() => (diff_fp = diff_fp === f.fps[0] ? null : f.fps[0])}
|
|
315
|
+
>{diff_fp ? 'hide' : 'show'} difference</button
|
|
316
|
+
>
|
|
317
|
+
{/if}
|
|
318
|
+
</p>
|
|
319
|
+
{/if}
|
|
320
|
+
{#if f.code === 'markup-changed' && diff && diff_fp === f.fps[0]}
|
|
321
|
+
<div class="diff">
|
|
322
|
+
<div><span class="lbl">server</span><code>{diff.server}</code></div>
|
|
323
|
+
<div><span class="lbl">browser</span><code>{diff.now}</code></div>
|
|
324
|
+
</div>
|
|
325
|
+
{/if}
|
|
326
|
+
</li>
|
|
327
|
+
{/each}
|
|
328
|
+
</ul>
|
|
329
|
+
{:else}
|
|
330
|
+
<p class="muted ok">Nothing the browser measured looks wrong on this visit.</p>
|
|
331
|
+
{/if}
|
|
332
|
+
|
|
333
|
+
<!-- (a page with no islands — a Kit-hydrated one — has no wakes to show: no empty heading) -->
|
|
334
|
+
{#if view.report.rows.length || view.kept?.length || view.regions.some((r) => r.kind === 'island')}
|
|
335
|
+
<h3>
|
|
336
|
+
Island wakes <span class="muted"
|
|
337
|
+
>{view.report.rows.length}{view.kept?.length ? ` · ${view.kept.length} kept` : ''}</span
|
|
338
|
+
>
|
|
339
|
+
</h3>
|
|
340
|
+
{/if}
|
|
341
|
+
{#if view.kept?.length}
|
|
342
|
+
<!-- the router reused them: the same island with the same props on both pages -->
|
|
343
|
+
<p class="kept" data-og-page-kept>
|
|
344
|
+
Kept from the page before, still awake (no code to load, no hydrate):
|
|
345
|
+
{#each view.kept.slice(0, 12) as k, i (k.fp)}{i ? ', ' : ''}<button
|
|
346
|
+
class="chip"
|
|
347
|
+
onclick={() => open(k.fp)}
|
|
348
|
+
onmouseenter={() => light([k.fp])}
|
|
349
|
+
onmouseleave={() => (focus = null)}>{k.name}</button
|
|
350
|
+
>{/each}{view.kept.length > 12 ? ` and ${view.kept.length - 12} more` : ''}.
|
|
351
|
+
</p>
|
|
352
|
+
{/if}
|
|
353
|
+
{#if axis && view.report.rows.length}
|
|
354
|
+
<div class="legend">
|
|
355
|
+
<span><i class="sw load"></i>module</span>
|
|
356
|
+
<span><i class="sw queue"></i>waiting for its turn</span>
|
|
357
|
+
<span><i class="sw hyd"></i>hydrate</span>
|
|
358
|
+
{#each axis.lines as l (l.k)}<span
|
|
359
|
+
><i class="ln" style:background={l.c}></i>{l.k} {ms(l.t)}</span
|
|
360
|
+
>{/each}
|
|
361
|
+
</div>
|
|
362
|
+
<div class="wf">
|
|
363
|
+
{#each view.report.rows as r (r.fp)}
|
|
364
|
+
{@const lo = r.queue_ms === null ? r.t0 + r.load_ms : r.t0 + r.load_ms + r.queue_ms}
|
|
365
|
+
<div
|
|
366
|
+
class="row"
|
|
367
|
+
class:bad={r.recovered || r.changed || r.early_ms !== null || r.shift >= 0.01}
|
|
368
|
+
role="button"
|
|
369
|
+
tabindex="0"
|
|
370
|
+
onmouseenter={() => light([r.fp])}
|
|
371
|
+
onmouseleave={() => (focus = null)}
|
|
372
|
+
onclick={() => open(r.fp)}
|
|
373
|
+
onkeydown={(e) => e.key === 'Enter' && open(r.fp)}
|
|
374
|
+
>
|
|
375
|
+
<span class="name" style:width="{LABEL_W}px" title={r.name}
|
|
376
|
+
>{r.name}<span class="wake">{r.wake}</span></span
|
|
377
|
+
>
|
|
378
|
+
<span class="track" style:width="{trackW}px">
|
|
379
|
+
{#each axis.lines as l (l.k)}<i
|
|
380
|
+
class="ln abs"
|
|
381
|
+
style:left="{axis.x(l.t)}px"
|
|
382
|
+
style:background={l.c}
|
|
383
|
+
></i>{/each}
|
|
384
|
+
<i
|
|
385
|
+
class="seg load"
|
|
386
|
+
style:left="{axis.x(r.t0)}px"
|
|
387
|
+
style:width="{Math.max(1, axis.x(r.t0 + r.load_ms) - axis.x(r.t0))}px"
|
|
388
|
+
></i>
|
|
389
|
+
{#if r.queue_ms !== null}<i
|
|
390
|
+
class="seg queue"
|
|
391
|
+
style:left="{axis.x(r.t0 + r.load_ms)}px"
|
|
392
|
+
style:width="{Math.max(0, axis.x(lo) - axis.x(r.t0 + r.load_ms))}px"
|
|
393
|
+
></i>{/if}
|
|
394
|
+
<i
|
|
395
|
+
class="seg hyd"
|
|
396
|
+
style:left="{axis.x(lo)}px"
|
|
397
|
+
style:width="{Math.max(2, axis.x(r.done) - axis.x(lo))}px"
|
|
398
|
+
></i>
|
|
399
|
+
</span>
|
|
400
|
+
<span
|
|
401
|
+
class="t"
|
|
402
|
+
title="module {r.load_ms} ms{r.queue_ms !== null
|
|
403
|
+
? ` · waited ${r.queue_ms} ms`
|
|
404
|
+
: ''} · hydrate {r.hydrate_ms} ms">{ms(r.done)}</span
|
|
405
|
+
>
|
|
406
|
+
<span class="tags">
|
|
407
|
+
{#if r.recovered}<b class="tag err">recovered</b>{/if}
|
|
408
|
+
{#if r.changed && !r.recovered}<b class="tag warn">markup changed</b>{/if}
|
|
409
|
+
{#if r.early_ms !== null}<b class="tag warn"
|
|
410
|
+
>clicked {Math.round(r.early_ms)} ms early</b
|
|
411
|
+
>{/if}
|
|
412
|
+
{#if r.shift >= 0.01}<b class="tag warn">CLS {r.shift}</b>{/if}
|
|
413
|
+
{#if r.hydrate_ms >= 50}<b class="tag warn">{Math.round(r.hydrate_ms)} ms task</b
|
|
414
|
+
>{/if}
|
|
415
|
+
{#if r.cpu_ms && r.cpu_ms >= 20}<b
|
|
416
|
+
class="tag info"
|
|
417
|
+
title="main-thread CPU sampled in its hydrate window"
|
|
418
|
+
>{Math.round(r.cpu_ms)} ms CPU</b
|
|
419
|
+
>{/if}
|
|
420
|
+
{#if r.below_fold && (r.wake === 'load' || r.wake === 'idle')}<b class="tag info"
|
|
421
|
+
>below the fold</b
|
|
422
|
+
>{/if}
|
|
423
|
+
</span>
|
|
424
|
+
</div>
|
|
425
|
+
{/each}
|
|
426
|
+
</div>
|
|
427
|
+
{:else}
|
|
428
|
+
<p class="muted">No island has woken yet.</p>
|
|
429
|
+
{/if}
|
|
430
|
+
|
|
431
|
+
{#if axis && (view.holes?.length || view.holes_failed?.length)}
|
|
432
|
+
<!-- each hole's first answer: how long its fallback stood, and where that time went -->
|
|
433
|
+
<h3>
|
|
434
|
+
Hole answers <span class="muted"
|
|
435
|
+
>{view.holes?.length ?? 0}{view.holes_failed?.length
|
|
436
|
+
? ` · ${view.holes_failed.length} never came`
|
|
437
|
+
: ''}</span
|
|
438
|
+
>
|
|
439
|
+
</h3>
|
|
440
|
+
<div class="legend">
|
|
441
|
+
<span><i class="sw h-before"></i>before its request left</span>
|
|
442
|
+
<span><i class="sw h-slot"></i>render slot</span>
|
|
443
|
+
<span><i class="sw h-render"></i>server render</span>
|
|
444
|
+
<span><i class="sw h-server"></i>waiting on the server</span>
|
|
445
|
+
<span><i class="sw h-rest"></i>network + swap</span>
|
|
446
|
+
</div>
|
|
447
|
+
<div class="wf" data-og-page-holes>
|
|
448
|
+
{#each view.holes_failed ?? [] as f, i (f.endpoint ?? i)}
|
|
449
|
+
<!-- no answer: the fallback still stands; the Findings say why and where to look -->
|
|
450
|
+
<div
|
|
451
|
+
class="row bad"
|
|
452
|
+
data-og-hole-failed
|
|
453
|
+
role="button"
|
|
454
|
+
tabindex="0"
|
|
455
|
+
onmouseenter={() => (focus = hole_el(f.endpoint))}
|
|
456
|
+
onmouseleave={() => (focus = null)}
|
|
457
|
+
onclick={() => (selected = hole_el(f.endpoint) ?? selected)}
|
|
458
|
+
onkeydown={(e) => e.key === 'Enter' && (selected = hole_el(f.endpoint) ?? selected)}
|
|
459
|
+
>
|
|
460
|
+
<span class="name" style:width="{LABEL_W}px" title={f.name}>{f.name}</span>
|
|
461
|
+
<span class="track" style:width="{trackW}px"
|
|
462
|
+
><i class="seg h-failed" style:left="0px" style:width="{trackW}px"></i></span
|
|
463
|
+
>
|
|
464
|
+
<span class="t">none</span>
|
|
465
|
+
<span class="tags"
|
|
466
|
+
><b class="tag err"
|
|
467
|
+
>{f.reason === 'error'
|
|
468
|
+
? `failed ${f.attempts}×${f.message ? ` (${f.message})` : ''}`
|
|
469
|
+
: f.reason === 'forbidden'
|
|
470
|
+
? `refused${f.message ? ` (${f.message})` : ''}`
|
|
471
|
+
: f.reason === 'redirected'
|
|
472
|
+
? 'redirected'
|
|
473
|
+
: 'answered with a page'}</b
|
|
474
|
+
></span
|
|
475
|
+
>
|
|
476
|
+
</div>
|
|
477
|
+
{/each}
|
|
478
|
+
{#each view.holes ?? [] as h, i (h.endpoint ?? i)}
|
|
479
|
+
{@const segs = hole_segs(h)}
|
|
480
|
+
<div
|
|
481
|
+
class="row"
|
|
482
|
+
class:bad={!h.below_fold && h.wait_ms >= 1000}
|
|
483
|
+
role="button"
|
|
484
|
+
tabindex="0"
|
|
485
|
+
onmouseenter={() => (focus = hole_el(h.endpoint))}
|
|
486
|
+
onmouseleave={() => (focus = null)}
|
|
487
|
+
onclick={() => (selected = hole_el(h.endpoint) ?? selected)}
|
|
488
|
+
onkeydown={(e) => e.key === 'Enter' && (selected = hole_el(h.endpoint) ?? selected)}
|
|
489
|
+
>
|
|
490
|
+
<span class="name" style:width="{LABEL_W}px" title={h.name}>{h.name}</span>
|
|
491
|
+
<span class="track" style:width="{trackW}px">
|
|
492
|
+
{#each axis.lines as l (l.k)}<i
|
|
493
|
+
class="ln abs"
|
|
494
|
+
style:left="{axis.x(l.t)}px"
|
|
495
|
+
style:background={l.c}
|
|
496
|
+
></i>{/each}
|
|
497
|
+
{#each segs as s (s.k)}<i
|
|
498
|
+
class="seg h-{s.k}"
|
|
499
|
+
title="{SEG_TITLE[s.k]}: {ms(s.b - s.a)}"
|
|
500
|
+
style:left="{axis.x(s.a)}px"
|
|
501
|
+
style:width="{Math.max(1, axis.x(s.b) - axis.x(s.a))}px"
|
|
502
|
+
></i>{/each}
|
|
503
|
+
</span>
|
|
504
|
+
<span
|
|
505
|
+
class="t"
|
|
506
|
+
title="its fallback stood this long{h.below_fold ? ' (below the first screen)' : ''}"
|
|
507
|
+
>{ms(h.wait_ms)}</span
|
|
508
|
+
>
|
|
509
|
+
<span class="tags">
|
|
510
|
+
{#if h.server_queue_ms && h.server_queue_ms >= 200}<b class="tag warn"
|
|
511
|
+
>{ms(h.server_queue_ms)} for a slot</b
|
|
512
|
+
>{/if}
|
|
513
|
+
{#if h.below_fold}<b class="tag info">below the fold</b>{/if}
|
|
514
|
+
</span>
|
|
515
|
+
</div>
|
|
516
|
+
{/each}
|
|
517
|
+
</div>
|
|
518
|
+
{/if}
|
|
519
|
+
|
|
520
|
+
<h3>
|
|
521
|
+
Main thread <span class="muted"
|
|
522
|
+
>{cpu ? `${ms(cpu.busy_ms)} busy in ${ms(cpu.window_ms)}` : ''}</span
|
|
523
|
+
>
|
|
524
|
+
</h3>
|
|
525
|
+
<div class="cpu" data-og-cpu>
|
|
526
|
+
<div class="cpu-bar">
|
|
527
|
+
{#if traces.length > 1}
|
|
528
|
+
<select bind:value={pick} aria-label="which trace">
|
|
529
|
+
{#each traces as t, i (t.from)}<option value={i}>{t.label}</option>{/each}
|
|
530
|
+
</select>
|
|
531
|
+
{:else if traces.length}
|
|
532
|
+
<span class="muted">{traces[0].label}</span>
|
|
533
|
+
{/if}
|
|
534
|
+
<button
|
|
535
|
+
class="chip alt"
|
|
536
|
+
data-og-cpu-record
|
|
537
|
+
disabled={recording ||
|
|
538
|
+
view.page.cpu.off === 'unsupported' ||
|
|
539
|
+
view.page.cpu.off === 'no-policy'}
|
|
540
|
+
onclick={record}
|
|
541
|
+
>
|
|
542
|
+
{recording ? 'recording 5 s… use the page now' : 'record 5 s'}
|
|
543
|
+
</button>
|
|
544
|
+
</div>
|
|
545
|
+
{#if !traces.length}
|
|
546
|
+
<p class="muted">
|
|
547
|
+
{view.page.cpu.state === 'recording'
|
|
548
|
+
? 'Sampling the page load (8 s)…'
|
|
549
|
+
: view.page.cpu.off === 'unsupported'
|
|
550
|
+
? 'This browser has no JS sampler (the JS Self-Profiling API is Chromium-only).'
|
|
551
|
+
: view.page.cpu.off === 'no-policy'
|
|
552
|
+
? 'The browser would not sample this document: it needs the header Document-Policy: js-profiling (the devtools dev server sends it; a proxy may drop it).'
|
|
553
|
+
: 'No trace yet.'}
|
|
554
|
+
</p>
|
|
555
|
+
{:else if cpu}
|
|
556
|
+
<div class="kinds" title="where the busy time went">
|
|
557
|
+
{#each cpu.by_kind as k (k.kind)}
|
|
558
|
+
<i
|
|
559
|
+
style:width="{(k.ms / Math.max(1, cpu.busy_ms)) * 100}%"
|
|
560
|
+
style:background={KIND_COLOR[k.kind]}
|
|
561
|
+
title="{k.kind} {ms(k.ms)}"
|
|
562
|
+
></i>
|
|
563
|
+
{/each}
|
|
564
|
+
</div>
|
|
565
|
+
<div class="legend">
|
|
566
|
+
{#each cpu.by_kind as k (k.kind)}<span
|
|
567
|
+
><i class="sw" style:background={KIND_COLOR[k.kind]}></i>{k.kind} {ms(k.ms)}</span
|
|
568
|
+
>{/each}
|
|
569
|
+
</div>
|
|
570
|
+
<table data-og-cpu-fns>
|
|
571
|
+
<thead
|
|
572
|
+
><tr
|
|
573
|
+
><th>function</th><th>where</th><th class="num">own</th><th class="num"
|
|
574
|
+
>with callees</th
|
|
575
|
+
></tr
|
|
576
|
+
></thead
|
|
577
|
+
>
|
|
578
|
+
<tbody>
|
|
579
|
+
{#each cpu.fns.slice(0, 10) as f, i (f.name + f.file + f.line + i)}
|
|
580
|
+
<tr>
|
|
581
|
+
<td><i class="sw" style:background={KIND_COLOR[f.kind]}></i>{f.name}</td>
|
|
582
|
+
<td class="url" title={f.file}
|
|
583
|
+
>{f.file ? f.file.slice(f.file.lastIndexOf('/') + 1) : '—'}{f.line !== null
|
|
584
|
+
? `:${f.line}`
|
|
585
|
+
: ''}</td
|
|
586
|
+
>
|
|
587
|
+
<td class="num">{ms(f.self_ms)}</td>
|
|
588
|
+
<td class="num">{ms(f.total_ms)}</td>
|
|
589
|
+
</tr>
|
|
590
|
+
{/each}
|
|
591
|
+
</tbody>
|
|
592
|
+
</table>
|
|
593
|
+
<p class="muted">
|
|
594
|
+
Sampled every {cpu.interval_ms} ms by the browser: a function under ~{Math.round(
|
|
595
|
+
cpu.interval_ms * 2
|
|
596
|
+
)} ms may not show.
|
|
597
|
+
</p>
|
|
598
|
+
{/if}
|
|
599
|
+
</div>
|
|
600
|
+
|
|
601
|
+
{#if view.report.blocking.length}
|
|
602
|
+
<h3>Blocked the first paint <span class="muted">{view.report.blocking.length}</span></h3>
|
|
603
|
+
<table>
|
|
604
|
+
<tbody>
|
|
605
|
+
{#each view.report.blocking.slice(0, 8) as b, i (b.url + i)}
|
|
606
|
+
<tr
|
|
607
|
+
><td class="url" title={b.url}>{b.url.split('?')[0].split('/').pop() || b.url}</td><td
|
|
608
|
+
class="num">{ms(b.ms)}</td
|
|
609
|
+
><td class="num">{b.bytes ? kb(b.bytes) : ''}</td></tr
|
|
610
|
+
>
|
|
611
|
+
{/each}
|
|
612
|
+
</tbody>
|
|
613
|
+
</table>
|
|
614
|
+
{/if}
|
|
615
|
+
|
|
616
|
+
<h3>
|
|
617
|
+
Styles
|
|
618
|
+
<span class="muted"
|
|
619
|
+
>{styles
|
|
620
|
+
? `${styles.sheets.length} sheet${styles.sheets.length === 1 ? '' : 's'} · ${styles.rules} rules`
|
|
621
|
+
: styles_busy
|
|
622
|
+
? 'reading…'
|
|
623
|
+
: ''}</span
|
|
624
|
+
>
|
|
625
|
+
<button
|
|
626
|
+
class="chip alt"
|
|
627
|
+
onclick={check_styles}
|
|
628
|
+
disabled={styles_busy}
|
|
629
|
+
title="try every selector against the page as it is now (open a menu first to count its rules)"
|
|
630
|
+
>check again</button
|
|
631
|
+
>
|
|
632
|
+
</h3>
|
|
633
|
+
{#if styles}
|
|
634
|
+
{#if styles.unscoped.length}
|
|
635
|
+
<p class="unscoped" data-og-page-unscoped>
|
|
636
|
+
Unscoped: {#each styles.unscoped as u, i (u.file)}{i ? ', ' : ''}<code>{u.file}</code
|
|
637
|
+
>{/each}. Their rules apply to every element on the page.
|
|
638
|
+
</p>
|
|
639
|
+
{/if}
|
|
640
|
+
<table data-og-page-styles>
|
|
641
|
+
<thead
|
|
642
|
+
><tr
|
|
643
|
+
><th>sheet</th><th class="num">rules</th><th class="num">match nothing</th><th
|
|
644
|
+
class="num">size</th
|
|
645
|
+
></tr
|
|
646
|
+
></thead
|
|
647
|
+
>
|
|
648
|
+
<tbody>
|
|
649
|
+
{#each styles.sheets.slice(0, 10) as s, i (s.label + i)}
|
|
650
|
+
<tr>
|
|
651
|
+
<td class="url" title={s.href ?? 'a style element in the document'}
|
|
652
|
+
>{s.label}{#if s.blocking}
|
|
653
|
+
<span class="tag">blocked paint</span>{/if}{#if s.ogygia}
|
|
654
|
+
<span class="tag og">ogygia</span>{/if}</td
|
|
655
|
+
>
|
|
656
|
+
<td class="num">{s.rules}</td>
|
|
657
|
+
<td class="num"
|
|
658
|
+
>{s.unmatched === null
|
|
659
|
+
? '—'
|
|
660
|
+
: s.rules
|
|
661
|
+
? `${s.unmatched} (${Math.round((s.unmatched / s.rules) * 100)}%)`
|
|
662
|
+
: '0'}</td
|
|
663
|
+
>
|
|
664
|
+
<td class="num">{s.bytes ? kb(s.bytes) : '—'}</td>
|
|
665
|
+
</tr>
|
|
666
|
+
{/each}
|
|
667
|
+
</tbody>
|
|
668
|
+
</table>
|
|
669
|
+
{#if styles.sheets.length > 10}<p class="muted">
|
|
670
|
+
and {styles.sheets.length - 10} more sheets
|
|
671
|
+
</p>{/if}
|
|
672
|
+
{#if styles.examples.length}
|
|
673
|
+
<p class="muted">
|
|
674
|
+
Match nothing now, e.g. {#each styles.examples.slice(0, 4) as e, i (e)}{i
|
|
675
|
+
? ', '
|
|
676
|
+
: ''}<code>{e.length > 60 ? e.slice(0, 60) + '…' : e}</code>{/each}. Content that
|
|
677
|
+
shows up later (menus, dialogs) counts here until it is open.
|
|
678
|
+
</p>
|
|
679
|
+
{/if}
|
|
680
|
+
{#if styles.unreadable}<p class="muted">
|
|
681
|
+
{styles.unreadable} sheet{styles.unreadable === 1 ? '' : 's'} from another origin could not
|
|
682
|
+
be read.
|
|
683
|
+
</p>{/if}
|
|
684
|
+
{/if}
|
|
685
|
+
|
|
686
|
+
{#if view.report.bytes.length}
|
|
687
|
+
<h3>Files by type</h3>
|
|
688
|
+
<table>
|
|
689
|
+
<thead
|
|
690
|
+
><tr
|
|
691
|
+
><th>type</th><th class="num">files</th><th class="num">sent</th><th class="num"
|
|
692
|
+
>size</th
|
|
693
|
+
></tr
|
|
694
|
+
></thead
|
|
695
|
+
>
|
|
696
|
+
<tbody>
|
|
697
|
+
{#each view.report.bytes as b (b.type)}
|
|
698
|
+
<tr
|
|
699
|
+
><td>{b.type}</td><td class="num">{b.count}</td><td class="num"
|
|
700
|
+
>{b.transfer ? kb(b.transfer) : '—'}</td
|
|
701
|
+
><td class="num">{b.size ? kb(b.size) : '—'}</td></tr
|
|
702
|
+
>
|
|
703
|
+
{/each}
|
|
704
|
+
</tbody>
|
|
705
|
+
</table>
|
|
706
|
+
{/if}
|
|
707
|
+
{/if}
|
|
708
|
+
</div>
|
|
709
|
+
|
|
710
|
+
<style>
|
|
711
|
+
.pg {
|
|
712
|
+
display: flex;
|
|
713
|
+
flex-direction: column;
|
|
714
|
+
gap: 8px;
|
|
715
|
+
}
|
|
716
|
+
.cap {
|
|
717
|
+
font-size: 12px;
|
|
718
|
+
color: #5eead4;
|
|
719
|
+
}
|
|
720
|
+
.srvline {
|
|
721
|
+
margin: 0;
|
|
722
|
+
color: #cbd5e1;
|
|
723
|
+
line-height: 1.5;
|
|
724
|
+
}
|
|
725
|
+
.srvline b {
|
|
726
|
+
color: #5eead4;
|
|
727
|
+
}
|
|
728
|
+
.since {
|
|
729
|
+
padding: 6px 9px;
|
|
730
|
+
border-radius: 8px;
|
|
731
|
+
background: rgba(56, 189, 248, 0.08);
|
|
732
|
+
border: 1px solid rgba(56, 189, 248, 0.25);
|
|
733
|
+
}
|
|
734
|
+
.since p {
|
|
735
|
+
margin: 3px 0 0;
|
|
736
|
+
}
|
|
737
|
+
.since .good {
|
|
738
|
+
color: #4ade80;
|
|
739
|
+
}
|
|
740
|
+
.since .bad {
|
|
741
|
+
color: #fca5a5;
|
|
742
|
+
}
|
|
743
|
+
.navnote {
|
|
744
|
+
margin: 0;
|
|
745
|
+
padding: 6px 9px;
|
|
746
|
+
border-radius: 8px;
|
|
747
|
+
background: rgba(236, 72, 153, 0.1);
|
|
748
|
+
border: 1px solid rgba(236, 72, 153, 0.3);
|
|
749
|
+
color: #fbcfe8;
|
|
750
|
+
line-height: 1.5;
|
|
751
|
+
}
|
|
752
|
+
.navnote code {
|
|
753
|
+
color: #f9a8d4;
|
|
754
|
+
}
|
|
755
|
+
.kept {
|
|
756
|
+
margin: 0;
|
|
757
|
+
color: #cbd5e1;
|
|
758
|
+
line-height: 1.8;
|
|
759
|
+
}
|
|
760
|
+
.muted {
|
|
761
|
+
color: #94a3b8;
|
|
762
|
+
}
|
|
763
|
+
.ok {
|
|
764
|
+
color: #86efac;
|
|
765
|
+
}
|
|
766
|
+
h3 {
|
|
767
|
+
margin: 8px 0 0;
|
|
768
|
+
font-size: 11px;
|
|
769
|
+
color: #cbd5e1;
|
|
770
|
+
text-transform: uppercase;
|
|
771
|
+
letter-spacing: 0.06em;
|
|
772
|
+
}
|
|
773
|
+
.vitals {
|
|
774
|
+
display: flex;
|
|
775
|
+
flex-wrap: wrap;
|
|
776
|
+
gap: 6px;
|
|
777
|
+
}
|
|
778
|
+
.vital {
|
|
779
|
+
display: flex;
|
|
780
|
+
flex-direction: column;
|
|
781
|
+
padding: 5px 10px;
|
|
782
|
+
border-radius: 8px;
|
|
783
|
+
border: 1px solid rgba(148, 163, 184, 0.25);
|
|
784
|
+
min-width: 64px;
|
|
785
|
+
}
|
|
786
|
+
.vital .k {
|
|
787
|
+
color: #94a3b8;
|
|
788
|
+
font-size: 10px;
|
|
789
|
+
}
|
|
790
|
+
.vital .v {
|
|
791
|
+
font-size: 13px;
|
|
792
|
+
font-weight: 700;
|
|
793
|
+
}
|
|
794
|
+
.vital.good {
|
|
795
|
+
border-color: rgba(34, 197, 94, 0.5);
|
|
796
|
+
}
|
|
797
|
+
.vital.good .v {
|
|
798
|
+
color: #4ade80;
|
|
799
|
+
}
|
|
800
|
+
.vital.fair {
|
|
801
|
+
border-color: rgba(251, 191, 36, 0.5);
|
|
802
|
+
}
|
|
803
|
+
.vital.fair .v {
|
|
804
|
+
color: #fbbf24;
|
|
805
|
+
}
|
|
806
|
+
.vital.poor {
|
|
807
|
+
border-color: rgba(239, 68, 68, 0.55);
|
|
808
|
+
}
|
|
809
|
+
.vital.poor .v {
|
|
810
|
+
color: #f87171;
|
|
811
|
+
}
|
|
812
|
+
.findings {
|
|
813
|
+
list-style: none;
|
|
814
|
+
margin: 0;
|
|
815
|
+
padding: 0;
|
|
816
|
+
display: flex;
|
|
817
|
+
flex-direction: column;
|
|
818
|
+
gap: 6px;
|
|
819
|
+
}
|
|
820
|
+
.findings li {
|
|
821
|
+
padding: 7px 9px;
|
|
822
|
+
border-radius: 8px;
|
|
823
|
+
border-left: 3px solid #64748b;
|
|
824
|
+
background: rgba(148, 163, 184, 0.07);
|
|
825
|
+
}
|
|
826
|
+
.findings li.error {
|
|
827
|
+
border-left-color: #ef4444;
|
|
828
|
+
}
|
|
829
|
+
.findings li.warn {
|
|
830
|
+
border-left-color: #f59e0b;
|
|
831
|
+
}
|
|
832
|
+
.findings li.info {
|
|
833
|
+
border-left-color: #38bdf8;
|
|
834
|
+
}
|
|
835
|
+
.msg,
|
|
836
|
+
.fix,
|
|
837
|
+
.acts {
|
|
838
|
+
margin: 0;
|
|
839
|
+
line-height: 1.5;
|
|
840
|
+
}
|
|
841
|
+
.fix {
|
|
842
|
+
color: #94a3b8;
|
|
843
|
+
margin-top: 3px;
|
|
844
|
+
}
|
|
845
|
+
.sev {
|
|
846
|
+
display: inline-block;
|
|
847
|
+
font-size: 9px;
|
|
848
|
+
text-transform: uppercase;
|
|
849
|
+
letter-spacing: 0.06em;
|
|
850
|
+
color: #94a3b8;
|
|
851
|
+
margin-right: 6px;
|
|
852
|
+
}
|
|
853
|
+
.acts {
|
|
854
|
+
display: flex;
|
|
855
|
+
flex-wrap: wrap;
|
|
856
|
+
gap: 4px;
|
|
857
|
+
margin-top: 5px;
|
|
858
|
+
}
|
|
859
|
+
.chip {
|
|
860
|
+
font: inherit;
|
|
861
|
+
font-size: 10px;
|
|
862
|
+
padding: 1px 7px;
|
|
863
|
+
border-radius: 999px;
|
|
864
|
+
border: 1px solid rgba(94, 234, 212, 0.4);
|
|
865
|
+
background: none;
|
|
866
|
+
color: #5eead4;
|
|
867
|
+
cursor: pointer;
|
|
868
|
+
}
|
|
869
|
+
.chip.alt {
|
|
870
|
+
border-color: rgba(148, 163, 184, 0.35);
|
|
871
|
+
color: #cbd5e1;
|
|
872
|
+
}
|
|
873
|
+
.diff {
|
|
874
|
+
margin-top: 6px;
|
|
875
|
+
display: flex;
|
|
876
|
+
flex-direction: column;
|
|
877
|
+
gap: 3px;
|
|
878
|
+
}
|
|
879
|
+
.diff div {
|
|
880
|
+
display: flex;
|
|
881
|
+
gap: 6px;
|
|
882
|
+
}
|
|
883
|
+
.diff .lbl {
|
|
884
|
+
width: 52px;
|
|
885
|
+
flex: none;
|
|
886
|
+
color: #94a3b8;
|
|
887
|
+
}
|
|
888
|
+
.diff code {
|
|
889
|
+
white-space: pre-wrap;
|
|
890
|
+
word-break: break-all;
|
|
891
|
+
color: #e2e8f0;
|
|
892
|
+
background: rgba(148, 163, 184, 0.1);
|
|
893
|
+
padding: 2px 5px;
|
|
894
|
+
border-radius: 4px;
|
|
895
|
+
}
|
|
896
|
+
.legend {
|
|
897
|
+
display: flex;
|
|
898
|
+
flex-wrap: wrap;
|
|
899
|
+
gap: 10px;
|
|
900
|
+
color: #94a3b8;
|
|
901
|
+
font-size: 10px;
|
|
902
|
+
}
|
|
903
|
+
.legend span {
|
|
904
|
+
display: inline-flex;
|
|
905
|
+
align-items: center;
|
|
906
|
+
gap: 4px;
|
|
907
|
+
}
|
|
908
|
+
.sw {
|
|
909
|
+
display: inline-block;
|
|
910
|
+
width: 10px;
|
|
911
|
+
height: 6px;
|
|
912
|
+
border-radius: 2px;
|
|
913
|
+
}
|
|
914
|
+
.sw.load,
|
|
915
|
+
.seg.load {
|
|
916
|
+
background: #38bdf8;
|
|
917
|
+
}
|
|
918
|
+
.sw.queue,
|
|
919
|
+
.seg.queue {
|
|
920
|
+
background: repeating-linear-gradient(90deg, #475569 0 3px, transparent 3px 5px);
|
|
921
|
+
}
|
|
922
|
+
.sw.hyd,
|
|
923
|
+
.seg.hyd {
|
|
924
|
+
background: #22c55e;
|
|
925
|
+
}
|
|
926
|
+
/* a hole's answer: before it left, the server's slot wait, its render, the rest */
|
|
927
|
+
.sw.h-before,
|
|
928
|
+
.seg.h-before,
|
|
929
|
+
.seg.h-wait {
|
|
930
|
+
background: repeating-linear-gradient(90deg, #475569 0 3px, transparent 3px 5px);
|
|
931
|
+
}
|
|
932
|
+
.sw.h-slot,
|
|
933
|
+
.seg.h-slot {
|
|
934
|
+
background: #f59e0b;
|
|
935
|
+
}
|
|
936
|
+
.sw.h-render,
|
|
937
|
+
.seg.h-render {
|
|
938
|
+
background: #a78bfa;
|
|
939
|
+
}
|
|
940
|
+
.sw.h-server,
|
|
941
|
+
.seg.h-server {
|
|
942
|
+
background: #c4b5fd;
|
|
943
|
+
}
|
|
944
|
+
.sw.h-rest,
|
|
945
|
+
.seg.h-rest {
|
|
946
|
+
background: #7dd3fc;
|
|
947
|
+
}
|
|
948
|
+
.seg.h-failed {
|
|
949
|
+
background: repeating-linear-gradient(
|
|
950
|
+
90deg,
|
|
951
|
+
rgba(248, 113, 113, 0.55) 0 4px,
|
|
952
|
+
transparent 4px 8px
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
.ln {
|
|
956
|
+
display: inline-block;
|
|
957
|
+
width: 2px;
|
|
958
|
+
height: 10px;
|
|
959
|
+
}
|
|
960
|
+
.wf {
|
|
961
|
+
display: flex;
|
|
962
|
+
flex-direction: column;
|
|
963
|
+
gap: 2px;
|
|
964
|
+
}
|
|
965
|
+
.row {
|
|
966
|
+
display: flex;
|
|
967
|
+
align-items: center;
|
|
968
|
+
gap: 6px;
|
|
969
|
+
cursor: pointer;
|
|
970
|
+
padding: 1px 0;
|
|
971
|
+
border-radius: 4px;
|
|
972
|
+
flex-wrap: wrap;
|
|
973
|
+
}
|
|
974
|
+
.row:hover {
|
|
975
|
+
background: rgba(148, 163, 184, 0.1);
|
|
976
|
+
}
|
|
977
|
+
.row.bad .name {
|
|
978
|
+
color: #fbbf24;
|
|
979
|
+
}
|
|
980
|
+
.name {
|
|
981
|
+
flex: none;
|
|
982
|
+
overflow: hidden;
|
|
983
|
+
text-overflow: ellipsis;
|
|
984
|
+
white-space: nowrap;
|
|
985
|
+
}
|
|
986
|
+
.wake {
|
|
987
|
+
color: #94a3b8;
|
|
988
|
+
margin-left: 5px;
|
|
989
|
+
font-size: 10px;
|
|
990
|
+
}
|
|
991
|
+
.track {
|
|
992
|
+
position: relative;
|
|
993
|
+
height: 12px;
|
|
994
|
+
flex: none;
|
|
995
|
+
background: rgba(148, 163, 184, 0.06);
|
|
996
|
+
border-radius: 3px;
|
|
997
|
+
}
|
|
998
|
+
.seg {
|
|
999
|
+
position: absolute;
|
|
1000
|
+
top: 2px;
|
|
1001
|
+
height: 8px;
|
|
1002
|
+
border-radius: 2px;
|
|
1003
|
+
}
|
|
1004
|
+
.ln.abs {
|
|
1005
|
+
position: absolute;
|
|
1006
|
+
top: 0;
|
|
1007
|
+
height: 12px;
|
|
1008
|
+
width: 1px;
|
|
1009
|
+
opacity: 0.8;
|
|
1010
|
+
}
|
|
1011
|
+
.t {
|
|
1012
|
+
width: 56px;
|
|
1013
|
+
text-align: right;
|
|
1014
|
+
color: #94a3b8;
|
|
1015
|
+
flex: none;
|
|
1016
|
+
}
|
|
1017
|
+
.tags {
|
|
1018
|
+
display: flex;
|
|
1019
|
+
gap: 4px;
|
|
1020
|
+
flex-wrap: wrap;
|
|
1021
|
+
padding-left: 4px;
|
|
1022
|
+
}
|
|
1023
|
+
.tag {
|
|
1024
|
+
font-weight: 400;
|
|
1025
|
+
font-size: 10px;
|
|
1026
|
+
padding: 0 5px;
|
|
1027
|
+
border-radius: 4px;
|
|
1028
|
+
}
|
|
1029
|
+
.tag.err {
|
|
1030
|
+
background: rgba(239, 68, 68, 0.2);
|
|
1031
|
+
color: #fca5a5;
|
|
1032
|
+
}
|
|
1033
|
+
.tag.warn {
|
|
1034
|
+
background: rgba(245, 158, 11, 0.18);
|
|
1035
|
+
color: #fcd34d;
|
|
1036
|
+
}
|
|
1037
|
+
.tag.info {
|
|
1038
|
+
background: rgba(56, 189, 248, 0.15);
|
|
1039
|
+
color: #7dd3fc;
|
|
1040
|
+
}
|
|
1041
|
+
.tag.og {
|
|
1042
|
+
background: rgba(94, 234, 212, 0.14);
|
|
1043
|
+
color: #5eead4;
|
|
1044
|
+
}
|
|
1045
|
+
.url .tag {
|
|
1046
|
+
background: rgba(148, 163, 184, 0.16);
|
|
1047
|
+
color: #cbd5e1;
|
|
1048
|
+
}
|
|
1049
|
+
.url .tag.og {
|
|
1050
|
+
background: rgba(94, 234, 212, 0.14);
|
|
1051
|
+
color: #5eead4;
|
|
1052
|
+
}
|
|
1053
|
+
h3 .chip {
|
|
1054
|
+
margin-left: 6px;
|
|
1055
|
+
text-transform: none;
|
|
1056
|
+
letter-spacing: 0;
|
|
1057
|
+
}
|
|
1058
|
+
.unscoped {
|
|
1059
|
+
margin: 0;
|
|
1060
|
+
padding: 6px 9px;
|
|
1061
|
+
border-radius: 8px;
|
|
1062
|
+
background: rgba(245, 158, 11, 0.1);
|
|
1063
|
+
border: 1px solid rgba(245, 158, 11, 0.35);
|
|
1064
|
+
color: #fde68a;
|
|
1065
|
+
}
|
|
1066
|
+
.unscoped code {
|
|
1067
|
+
color: #fcd34d;
|
|
1068
|
+
}
|
|
1069
|
+
table {
|
|
1070
|
+
border-collapse: collapse;
|
|
1071
|
+
width: 100%;
|
|
1072
|
+
}
|
|
1073
|
+
.cpu {
|
|
1074
|
+
display: flex;
|
|
1075
|
+
flex-direction: column;
|
|
1076
|
+
gap: 6px;
|
|
1077
|
+
}
|
|
1078
|
+
.cpu-bar {
|
|
1079
|
+
display: flex;
|
|
1080
|
+
align-items: center;
|
|
1081
|
+
gap: 8px;
|
|
1082
|
+
}
|
|
1083
|
+
.cpu-bar select {
|
|
1084
|
+
background: #0d1526;
|
|
1085
|
+
color: #e2e8f0;
|
|
1086
|
+
border: 1px solid rgba(148, 163, 184, 0.25);
|
|
1087
|
+
border-radius: 6px;
|
|
1088
|
+
font: inherit;
|
|
1089
|
+
padding: 2px 5px;
|
|
1090
|
+
}
|
|
1091
|
+
.kinds {
|
|
1092
|
+
display: flex;
|
|
1093
|
+
height: 8px;
|
|
1094
|
+
border-radius: 4px;
|
|
1095
|
+
overflow: hidden;
|
|
1096
|
+
background: rgba(148, 163, 184, 0.1);
|
|
1097
|
+
}
|
|
1098
|
+
.kinds i {
|
|
1099
|
+
display: block;
|
|
1100
|
+
height: 100%;
|
|
1101
|
+
}
|
|
1102
|
+
th {
|
|
1103
|
+
text-align: left;
|
|
1104
|
+
color: #94a3b8;
|
|
1105
|
+
font-weight: 600;
|
|
1106
|
+
border-bottom: 1px solid rgba(148, 163, 184, 0.2);
|
|
1107
|
+
padding: 3px 4px;
|
|
1108
|
+
}
|
|
1109
|
+
td {
|
|
1110
|
+
padding: 2px 4px;
|
|
1111
|
+
border-bottom: 1px solid rgba(148, 163, 184, 0.08);
|
|
1112
|
+
}
|
|
1113
|
+
.num {
|
|
1114
|
+
text-align: right;
|
|
1115
|
+
}
|
|
1116
|
+
.url {
|
|
1117
|
+
max-width: 280px;
|
|
1118
|
+
overflow: hidden;
|
|
1119
|
+
text-overflow: ellipsis;
|
|
1120
|
+
white-space: nowrap;
|
|
1121
|
+
}
|
|
1122
|
+
</style>
|