@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,364 @@
|
|
|
1
|
+
<script>import { fmt_ms, fmt_bytes } from "./format.js";
|
|
2
|
+
import { row_href } from "./row-anchor.svelte.js";
|
|
3
|
+
// `renders`: how many renders the CPU numbers add up (page mode); a wait pattern is one render's
|
|
4
|
+
let { patterns, busy_ms = 0, renders = 1, fns } = $props();
|
|
5
|
+
// a site links to its function's row only when the functions table has that row
|
|
6
|
+
const known = $derived(fns ? new Set(fns) : null);
|
|
7
|
+
const linkable = (fn) => !!fn && (!known || known.has(fn));
|
|
8
|
+
const per = (ms) => ms / Math.max(1, renders);
|
|
9
|
+
const share = (ms) => busy_ms > 0 ? Math.round(ms / busy_ms * 100) : 0;
|
|
10
|
+
/** the context lines with their common indent removed (tabs shown as two spaces) */
|
|
11
|
+
function dedent(lines) {
|
|
12
|
+
const t = lines.map((l) => l.replaceAll(" ", " "));
|
|
13
|
+
let cut = Infinity;
|
|
14
|
+
for (const l of t) {
|
|
15
|
+
if (!l.trim()) continue;
|
|
16
|
+
cut = Math.min(cut, l.length - l.trimStart().length);
|
|
17
|
+
}
|
|
18
|
+
return Number.isFinite(cut) ? t.map((l) => l.slice(cut)) : t;
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<div class="pats">
|
|
23
|
+
<!-- keyed by kind + title: two library patterns share a kind, and a duplicate key kills the island -->
|
|
24
|
+
{#each patterns as p, i (p.kind + '\0' + p.title)}
|
|
25
|
+
<!-- `pattern-<index>`: a finding that names the same problem links here -->
|
|
26
|
+
<article class="pat" id="pattern-{i}">
|
|
27
|
+
<header>
|
|
28
|
+
<h3>{p.title}</h3>
|
|
29
|
+
{#if p.save_ms >= 0.1}
|
|
30
|
+
<span class="save" title="A rough estimate from what these lines cost">
|
|
31
|
+
{#if p.kind === 'same-document' || p.kind === 'almost-same-document'}saves ~{fmt_ms(
|
|
32
|
+
p.save_ms
|
|
33
|
+
)} ms: the whole render, answered from a cache{:else if p.wait}saves ~{fmt_ms(
|
|
34
|
+
p.save_ms
|
|
35
|
+
)} ms of waiting{:else}saves ~{fmt_ms(per(p.save_ms))} ms{renders > 1
|
|
36
|
+
? ' per render'
|
|
37
|
+
: ''}{#if share(p.save_ms) >= 1} ({share(p.save_ms)}% of CPU){/if}{/if}
|
|
38
|
+
</span>
|
|
39
|
+
{:else if p.seed_bytes}
|
|
40
|
+
<span class="save" title="The page seed, sent to the browser with every page view"
|
|
41
|
+
>ships {fmt_bytes(p.seed_bytes)} per page view</span
|
|
42
|
+
>
|
|
43
|
+
{:else if p.kept_bytes}
|
|
44
|
+
<span
|
|
45
|
+
class="save"
|
|
46
|
+
title="Measured: allocated by one more warm render and still alive after a full collection"
|
|
47
|
+
>
|
|
48
|
+
keeps {fmt_bytes(p.kept_bytes)} per render{#if p.growth === 'grows'}
|
|
49
|
+
· confirmed{:else if p.growth === 'levels-off'}
|
|
50
|
+
· levels off{/if}{#if p.requests_left !== undefined}
|
|
51
|
+
· heap full in ~{p.requests_left.toLocaleString('en-US')} requests{/if}
|
|
52
|
+
</span>
|
|
53
|
+
{/if}
|
|
54
|
+
</header>
|
|
55
|
+
{#if p.evidence}<p class="ev">{p.evidence}</p>{/if}
|
|
56
|
+
<ul class="sites">
|
|
57
|
+
<!-- one line can be several sites: a helper line that calls a different endpoint each time -->
|
|
58
|
+
{#each p.sites as s, i (s.path + ':' + s.line + '\0' + (s.target ?? '') + '\0' + i)}
|
|
59
|
+
<li>
|
|
60
|
+
{#if linkable(s.fn)}
|
|
61
|
+
<a class="loc" href={row_href('fn:' + s.fn)} title={s.path + ':' + s.line}
|
|
62
|
+
>{s.file}:{s.line}</a
|
|
63
|
+
>
|
|
64
|
+
{:else}
|
|
65
|
+
<span class="loc" title={s.path + ':' + s.line}>{s.file}:{s.line}</span>
|
|
66
|
+
{/if}
|
|
67
|
+
{#if s.module}<span
|
|
68
|
+
class="mod"
|
|
69
|
+
title="the build has no sourcemap: the line number is the built chunk's"
|
|
70
|
+
>in {s.module}</span
|
|
71
|
+
>{/if}
|
|
72
|
+
<span class="nums">
|
|
73
|
+
{#if s.wait_ms}{s.calls} call{s.calls === 1 ? '' : 's'} to {s.target} · {fmt_ms(
|
|
74
|
+
s.wait_ms
|
|
75
|
+
)} ms waiting{#if s.in_loop}
|
|
76
|
+
· in a loop{/if}{/if}
|
|
77
|
+
{#if s.answer_bytes !== undefined}
|
|
78
|
+
· the same {fmt_bytes(s.answer_bytes)} every render{/if}
|
|
79
|
+
{#if s.personal}<span
|
|
80
|
+
class="tag"
|
|
81
|
+
title="The request carried a cookie or an authorization header, and the recording rendered as one user: cache it per user, keyed by the session"
|
|
82
|
+
>per user</span
|
|
83
|
+
>{/if}
|
|
84
|
+
<!-- what the service itself says about keeping its answer (its Cache-Control) -->
|
|
85
|
+
{#if s.upstream_cache?.no_store}<span
|
|
86
|
+
class="tag"
|
|
87
|
+
title="The service answered with Cache-Control: no-store — the answer was the same here, but it asks not to be kept. Check with its owner before caching it"
|
|
88
|
+
>service says: don't store</span
|
|
89
|
+
>
|
|
90
|
+
{:else if s.upstream_cache?.no_cache}<span
|
|
91
|
+
class="tag"
|
|
92
|
+
title="The service answered with no-cache (or max-age=0): a copy may be kept, but every reuse must be checked with it first (an ETag or Last-Modified request)"
|
|
93
|
+
>service says: revalidate each time</span
|
|
94
|
+
>
|
|
95
|
+
{:else if s.upstream_cache?.private && !s.upstream_cache.max_age}<span
|
|
96
|
+
class="tag"
|
|
97
|
+
title="The service marks its answer private: one user's, never a shared cache's"
|
|
98
|
+
>service says: private</span
|
|
99
|
+
>
|
|
100
|
+
{:else if s.upstream_cache?.max_age}<span
|
|
101
|
+
class="tag"
|
|
102
|
+
title="The service's own Cache-Control allows keeping a copy this long: caching it that long is within what it promises"
|
|
103
|
+
>service allows {s.upstream_cache.max_age >= 60
|
|
104
|
+
? `${Math.round(s.upstream_cache.max_age / 60)} min`
|
|
105
|
+
: `${s.upstream_cache.max_age} s`} cache</span
|
|
106
|
+
>{/if}
|
|
107
|
+
{#if s.hits !== undefined && s.calls}the cache answered {s.hits} of {s.calls} calls{/if}
|
|
108
|
+
{#if s.kept_bytes}{fmt_bytes(s.kept_bytes)} kept{#if s.mem_via}, inside {s.mem_via}{/if}{#if s.grows === true}<span
|
|
109
|
+
title="Held about one render's worth more after every render of the check. A cache that keeps more entries than the check had renders looks the same."
|
|
110
|
+
>
|
|
111
|
+
· grew every render checked</span
|
|
112
|
+
>{:else if s.grows === false}
|
|
113
|
+
· stopped growing (a bounded cache){/if}{/if}
|
|
114
|
+
{#if s.cpu_ms > 0}{fmt_ms(per(s.cpu_ms))} ms{/if}
|
|
115
|
+
{#if s.lib_ms}{s.cpu_ms > 0 ? ' · ' : ''}{fmt_ms(per(s.lib_ms))} ms in {s.libs?.[0] ??
|
|
116
|
+
'a library'}{/if}
|
|
117
|
+
{#if s.alloc_bytes > 0}{s.cpu_ms > 0 ? ' · ' : ''}{fmt_bytes(per(s.alloc_bytes))}{/if}
|
|
118
|
+
{#if !s.wait_ms && renders > 1 && (s.cpu_ms > 0 || s.lib_ms || s.alloc_bytes > 0)} per
|
|
119
|
+
render{/if}
|
|
120
|
+
{#if s.calls && !s.wait_ms && s.hits === undefined} · ×{s.calls}{/if}
|
|
121
|
+
</span>
|
|
122
|
+
{#if s.context}
|
|
123
|
+
{@const shown = dedent(s.context.lines)}
|
|
124
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
125
|
+
<pre
|
|
126
|
+
class="ctx"
|
|
127
|
+
tabindex="0"
|
|
128
|
+
aria-label="source around line {s.line}">{#each shown as ln, i (i)}<span
|
|
129
|
+
class="cl"
|
|
130
|
+
class:hot={s.context.start + i === s.line}
|
|
131
|
+
><span class="cn">{s.context.start + i}</span>{ln}
|
|
132
|
+
</span>{/each}</pre>
|
|
133
|
+
{:else}
|
|
134
|
+
<code>{s.code}</code>
|
|
135
|
+
{/if}
|
|
136
|
+
{#if s.rewrite}
|
|
137
|
+
<!-- the change, written out: these lines, and what goes in their place -->
|
|
138
|
+
<div class="rewrite">
|
|
139
|
+
<span class="rw-h">The change, at {s.rewrite.file}:{s.rewrite.line}</span>
|
|
140
|
+
<pre class="rw-before">{s.rewrite.before}</pre>
|
|
141
|
+
<pre class="rw-after">{s.rewrite.after}</pre>
|
|
142
|
+
</div>
|
|
143
|
+
{/if}
|
|
144
|
+
{#if s.via?.length}
|
|
145
|
+
<div class="via">
|
|
146
|
+
<span class="vk">called from</span>
|
|
147
|
+
{#each s.via as v (v.path + ':' + v.line)}
|
|
148
|
+
<div class="vrow">
|
|
149
|
+
{#if linkable(v.fn)}<a class="loc" href={row_href('fn:' + v.fn)}
|
|
150
|
+
>{v.file}:{v.line}</a
|
|
151
|
+
>{:else}<span class="loc">{v.file}:{v.line}</span>{/if}
|
|
152
|
+
{#if v.in_loop}<span class="inloop">in a loop</span>{/if}
|
|
153
|
+
<code>{v.code}</code>
|
|
154
|
+
</div>
|
|
155
|
+
{/each}
|
|
156
|
+
</div>
|
|
157
|
+
{/if}
|
|
158
|
+
</li>
|
|
159
|
+
{/each}
|
|
160
|
+
</ul>
|
|
161
|
+
<p class="fix">{p.fix}</p>
|
|
162
|
+
{#if p.example}
|
|
163
|
+
<details>
|
|
164
|
+
<summary>Show an example</summary>
|
|
165
|
+
<div class="ex">
|
|
166
|
+
<span class="exk">before</span>
|
|
167
|
+
<pre>{p.example.before}</pre>
|
|
168
|
+
<span class="exk">after</span>
|
|
169
|
+
<pre>{p.example.after}</pre>
|
|
170
|
+
</div>
|
|
171
|
+
</details>
|
|
172
|
+
{/if}
|
|
173
|
+
</article>
|
|
174
|
+
{/each}
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
<style>
|
|
178
|
+
.pats {
|
|
179
|
+
display: grid;
|
|
180
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr));
|
|
181
|
+
gap: 12px;
|
|
182
|
+
}
|
|
183
|
+
.pat {
|
|
184
|
+
border: 1px solid var(--line);
|
|
185
|
+
border-radius: 8px;
|
|
186
|
+
background: var(--bg-sunken);
|
|
187
|
+
padding: 12px 14px;
|
|
188
|
+
min-width: 0;
|
|
189
|
+
}
|
|
190
|
+
header {
|
|
191
|
+
display: flex;
|
|
192
|
+
flex-wrap: wrap;
|
|
193
|
+
align-items: baseline;
|
|
194
|
+
justify-content: space-between;
|
|
195
|
+
gap: 4px 12px;
|
|
196
|
+
}
|
|
197
|
+
h3 {
|
|
198
|
+
margin: 0;
|
|
199
|
+
font-size: 14px;
|
|
200
|
+
line-height: 1.35;
|
|
201
|
+
}
|
|
202
|
+
.save {
|
|
203
|
+
font-size: 12px;
|
|
204
|
+
font-weight: 600;
|
|
205
|
+
color: var(--c-orange);
|
|
206
|
+
white-space: nowrap;
|
|
207
|
+
}
|
|
208
|
+
.ev {
|
|
209
|
+
margin: 4px 0 8px;
|
|
210
|
+
font-size: 12.5px;
|
|
211
|
+
color: var(--text-dim);
|
|
212
|
+
}
|
|
213
|
+
.sites {
|
|
214
|
+
list-style: none;
|
|
215
|
+
margin: 0 0 8px;
|
|
216
|
+
padding: 0;
|
|
217
|
+
display: grid;
|
|
218
|
+
gap: 6px;
|
|
219
|
+
}
|
|
220
|
+
.sites li {
|
|
221
|
+
display: flex;
|
|
222
|
+
flex-wrap: wrap;
|
|
223
|
+
gap: 2px 10px;
|
|
224
|
+
align-items: baseline;
|
|
225
|
+
font-size: 12px;
|
|
226
|
+
min-width: 0;
|
|
227
|
+
}
|
|
228
|
+
.mod {
|
|
229
|
+
font-size: 12px;
|
|
230
|
+
opacity: 0.75;
|
|
231
|
+
margin-left: 6px;
|
|
232
|
+
}
|
|
233
|
+
.loc {
|
|
234
|
+
font-family: ui-monospace, monospace;
|
|
235
|
+
font-weight: 600;
|
|
236
|
+
color: var(--text);
|
|
237
|
+
text-decoration: none;
|
|
238
|
+
word-break: break-all;
|
|
239
|
+
}
|
|
240
|
+
a.loc:hover {
|
|
241
|
+
text-decoration: underline;
|
|
242
|
+
}
|
|
243
|
+
.nums {
|
|
244
|
+
color: var(--text-faint);
|
|
245
|
+
}
|
|
246
|
+
.sites code {
|
|
247
|
+
flex-basis: 100%;
|
|
248
|
+
font-family: ui-monospace, monospace;
|
|
249
|
+
font-size: 11.5px;
|
|
250
|
+
color: var(--text-dim);
|
|
251
|
+
white-space: pre-wrap;
|
|
252
|
+
word-break: break-word;
|
|
253
|
+
}
|
|
254
|
+
.ctx {
|
|
255
|
+
flex-basis: 100%;
|
|
256
|
+
margin: 4px 0 0;
|
|
257
|
+
padding: 4px 0;
|
|
258
|
+
background: var(--bg-panel);
|
|
259
|
+
border: 1px solid var(--line);
|
|
260
|
+
border-radius: 5px;
|
|
261
|
+
font-size: 11.5px;
|
|
262
|
+
line-height: 1.5;
|
|
263
|
+
overflow-x: auto;
|
|
264
|
+
min-width: 0;
|
|
265
|
+
}
|
|
266
|
+
.cl {
|
|
267
|
+
display: block;
|
|
268
|
+
padding: 0 8px;
|
|
269
|
+
white-space: pre;
|
|
270
|
+
color: var(--text-faint);
|
|
271
|
+
}
|
|
272
|
+
.cl.hot {
|
|
273
|
+
color: var(--text);
|
|
274
|
+
background: color-mix(in srgb, var(--c-orange) 16%, transparent);
|
|
275
|
+
}
|
|
276
|
+
.cn {
|
|
277
|
+
display: inline-block;
|
|
278
|
+
width: 3.2em;
|
|
279
|
+
color: var(--text-faint);
|
|
280
|
+
user-select: none;
|
|
281
|
+
}
|
|
282
|
+
.rewrite {
|
|
283
|
+
margin: 6px 0;
|
|
284
|
+
}
|
|
285
|
+
.rw-h {
|
|
286
|
+
font-size: 11px;
|
|
287
|
+
text-transform: uppercase;
|
|
288
|
+
color: var(--text-dim);
|
|
289
|
+
}
|
|
290
|
+
.rewrite pre {
|
|
291
|
+
margin: 3px 0;
|
|
292
|
+
padding: 6px 8px;
|
|
293
|
+
border-radius: 6px;
|
|
294
|
+
font-size: 12px;
|
|
295
|
+
white-space: pre-wrap;
|
|
296
|
+
overflow-wrap: anywhere;
|
|
297
|
+
}
|
|
298
|
+
.rw-before {
|
|
299
|
+
background: color-mix(in srgb, #dc2626 10%, transparent);
|
|
300
|
+
}
|
|
301
|
+
.rw-after {
|
|
302
|
+
background: color-mix(in srgb, #16a34a 12%, transparent);
|
|
303
|
+
}
|
|
304
|
+
.via {
|
|
305
|
+
flex-basis: 100%;
|
|
306
|
+
margin: 2px 0 0 10px;
|
|
307
|
+
padding-left: 8px;
|
|
308
|
+
border-left: 2px solid var(--line);
|
|
309
|
+
display: grid;
|
|
310
|
+
gap: 3px;
|
|
311
|
+
min-width: 0;
|
|
312
|
+
}
|
|
313
|
+
.vk {
|
|
314
|
+
font-size: 10.5px;
|
|
315
|
+
text-transform: uppercase;
|
|
316
|
+
letter-spacing: 0.04em;
|
|
317
|
+
color: var(--text-faint);
|
|
318
|
+
}
|
|
319
|
+
.vrow {
|
|
320
|
+
display: flex;
|
|
321
|
+
flex-wrap: wrap;
|
|
322
|
+
gap: 2px 8px;
|
|
323
|
+
align-items: baseline;
|
|
324
|
+
min-width: 0;
|
|
325
|
+
}
|
|
326
|
+
.inloop {
|
|
327
|
+
font-size: 11px;
|
|
328
|
+
color: var(--c-orange);
|
|
329
|
+
}
|
|
330
|
+
.fix {
|
|
331
|
+
margin: 0;
|
|
332
|
+
font-size: 12.5px;
|
|
333
|
+
line-height: 1.5;
|
|
334
|
+
}
|
|
335
|
+
details {
|
|
336
|
+
margin-top: 8px;
|
|
337
|
+
}
|
|
338
|
+
summary {
|
|
339
|
+
cursor: pointer;
|
|
340
|
+
font-size: 12px;
|
|
341
|
+
color: var(--text-dim);
|
|
342
|
+
}
|
|
343
|
+
.ex {
|
|
344
|
+
margin-top: 6px;
|
|
345
|
+
display: grid;
|
|
346
|
+
gap: 4px;
|
|
347
|
+
}
|
|
348
|
+
.exk {
|
|
349
|
+
font-size: 10.5px;
|
|
350
|
+
text-transform: uppercase;
|
|
351
|
+
letter-spacing: 0.04em;
|
|
352
|
+
color: var(--text-faint);
|
|
353
|
+
}
|
|
354
|
+
.ex pre {
|
|
355
|
+
margin: 0;
|
|
356
|
+
padding: 6px 8px;
|
|
357
|
+
background: var(--bg-panel);
|
|
358
|
+
border: 1px solid var(--line);
|
|
359
|
+
border-radius: 5px;
|
|
360
|
+
font-size: 11.5px;
|
|
361
|
+
white-space: pre-wrap;
|
|
362
|
+
word-break: break-word;
|
|
363
|
+
}
|
|
364
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SLOW PATTERNS: the known slow shapes found on the costly lines (patterns.ts), one card each,
|
|
3
|
+
* biggest estimated win first. Each says what is happening, the numbers behind it, every place
|
|
4
|
+
* it happens (with the code), and what to do. Static markup; the example sits in a <details>.
|
|
5
|
+
*/
|
|
6
|
+
import type { Pattern } from '../patterns.js';
|
|
7
|
+
type $$ComponentProps = {
|
|
8
|
+
patterns: Pattern[];
|
|
9
|
+
busy_ms?: number;
|
|
10
|
+
renders?: number;
|
|
11
|
+
fns?: string[];
|
|
12
|
+
};
|
|
13
|
+
declare const Patterns: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
14
|
+
type Patterns = ReturnType<typeof Patterns>;
|
|
15
|
+
export default Patterns;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<script>/**
|
|
2
|
+
* The share-link viewer, a `wake:'load'` island. The report rides in the URL `#fragment` (which the
|
|
3
|
+
* browser never sends anywhere); this reads it, asks for the password the sender set, decrypts it
|
|
4
|
+
* IN THE BROWSER (Web Crypto — see permalink.ts), and renders the same {@link ./ReportBody.svelte}.
|
|
5
|
+
* Zero-knowledge: the server serves an empty page; the report never touches it. No `$effect` — the
|
|
6
|
+
* fragment is read once at setup, and unlock is a submit handler (reactivity-dom-preferences).
|
|
7
|
+
*/
|
|
8
|
+
import Shell from "./Shell.svelte";
|
|
9
|
+
import ReportBody from "./ReportBody.svelte";
|
|
10
|
+
import { decode_permalink } from "./permalink.js";
|
|
11
|
+
import { get_report, list_reports } from "./store.js";
|
|
12
|
+
import { page_history } from "../compare.js";
|
|
13
|
+
// ── regexes
|
|
14
|
+
const LEADING_HASH_RE = /^#/;
|
|
15
|
+
let { base, login = null, exists = false } = $props();
|
|
16
|
+
const blob = typeof location !== "undefined" ? location.hash.replace(LEADING_HASH_RE, "") : "";
|
|
17
|
+
let password = $state("");
|
|
18
|
+
let busy = $state(false);
|
|
19
|
+
let error = $state("");
|
|
20
|
+
/** `kept`: this browser's own copy (the owner's report, on an instance that no longer has it),
|
|
21
|
+
* not a link someone shared: its owner can still share it on */
|
|
22
|
+
let report = $state(null);
|
|
23
|
+
/** THE BROWSER STORE: no fragment → this may be a report this browser kept (an ephemeral host
|
|
24
|
+
* no longer has it, or the server restarted). Looked up once, by the id in the URL. */
|
|
25
|
+
const look_in_store = !blob && typeof location !== "undefined";
|
|
26
|
+
let looking = $state(look_in_store);
|
|
27
|
+
if (look_in_store) {
|
|
28
|
+
const id = location.pathname.split("/").filter(Boolean).pop() ?? "";
|
|
29
|
+
void get_report(id).then(async (rec) => {
|
|
30
|
+
const dump = rec?.dump;
|
|
31
|
+
if (!dump?.analysis || !dump.meta) return;
|
|
32
|
+
// this page's history from the other reports this browser kept
|
|
33
|
+
const kept = await list_reports();
|
|
34
|
+
const metas = [dump.meta, ...(await Promise.all(kept.filter((k) => k.id !== dump.meta.id && k.page === dump.meta.page).map((k) => get_report(k.id)))).map((r) => (r?.dump)?.meta).filter((m) => !!m)];
|
|
35
|
+
const history = page_history(metas).find((h) => h.page === dump.meta.page) ?? null;
|
|
36
|
+
const i = history ? history.points.findIndex((p) => p.id === dump.meta.id) : -1;
|
|
37
|
+
report = {
|
|
38
|
+
a: dump.analysis,
|
|
39
|
+
meta: dump.meta,
|
|
40
|
+
extras: dump.extras,
|
|
41
|
+
history,
|
|
42
|
+
prev: i > 0 ? history.points[i - 1].id : null,
|
|
43
|
+
kept: true
|
|
44
|
+
};
|
|
45
|
+
}).finally(() => looking = false);
|
|
46
|
+
}
|
|
47
|
+
async function unlock(e) {
|
|
48
|
+
e.preventDefault();
|
|
49
|
+
if (!blob || busy) return;
|
|
50
|
+
busy = true;
|
|
51
|
+
error = "";
|
|
52
|
+
try {
|
|
53
|
+
const dump = await decode_permalink(blob, password);
|
|
54
|
+
report = {
|
|
55
|
+
a: dump.analysis,
|
|
56
|
+
meta: dump.meta,
|
|
57
|
+
extras: dump.extras
|
|
58
|
+
};
|
|
59
|
+
} catch {
|
|
60
|
+
error = "Wrong password, or the link is corrupted.";
|
|
61
|
+
} finally {
|
|
62
|
+
busy = false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
{#if report}
|
|
68
|
+
<ReportBody
|
|
69
|
+
a={report.a}
|
|
70
|
+
meta={report.meta}
|
|
71
|
+
{base}
|
|
72
|
+
extras={report.extras}
|
|
73
|
+
ogpB64={undefined}
|
|
74
|
+
history={report.history ?? null}
|
|
75
|
+
prev={report.prev ?? null}
|
|
76
|
+
kept={report.kept ?? false}
|
|
77
|
+
/>
|
|
78
|
+
{:else}
|
|
79
|
+
<Shell {base} bare>
|
|
80
|
+
<div class="share-unlock">
|
|
81
|
+
{#if looking}
|
|
82
|
+
<h1>Looking in this browser…</h1>
|
|
83
|
+
<p class="hint">
|
|
84
|
+
The server does not hold this report; checking whether this browser kept it.
|
|
85
|
+
</p>
|
|
86
|
+
{:else if !blob && exists && login}
|
|
87
|
+
<h1>Log in to view this report</h1>
|
|
88
|
+
<p class="hint">
|
|
89
|
+
This report is on this server; you are not logged in on this browser. Log in with the
|
|
90
|
+
profiler secret and you land right back here.
|
|
91
|
+
</p>
|
|
92
|
+
<a class="btn" href={login}>Log in with the secret</a>
|
|
93
|
+
{:else if !blob}
|
|
94
|
+
<h1>No report here</h1>
|
|
95
|
+
<p class="hint">
|
|
96
|
+
This server does not hold it and this browser did not keep it (an ephemeral host keeps a
|
|
97
|
+
report only in the browser that recorded it). Open a share link (it ends in <code>#…</code
|
|
98
|
+
>){#if login}, or <a href={login}>log in with the profiler secret</a> to view your own reports{:else},
|
|
99
|
+
or log in to view your own reports{/if}.
|
|
100
|
+
</p>
|
|
101
|
+
<a class="btn" href={base}>← dashboard</a>
|
|
102
|
+
{:else}
|
|
103
|
+
<h1>Shared profile</h1>
|
|
104
|
+
<p class="hint">
|
|
105
|
+
This report is encrypted in the link. Enter the password the sender gave you.
|
|
106
|
+
</p>
|
|
107
|
+
<form class="share-form" onsubmit={unlock}>
|
|
108
|
+
<input
|
|
109
|
+
type="password"
|
|
110
|
+
bind:value={password}
|
|
111
|
+
placeholder="password"
|
|
112
|
+
autocomplete="off"
|
|
113
|
+
disabled={busy}
|
|
114
|
+
/>
|
|
115
|
+
<button type="submit" disabled={busy || !password}>
|
|
116
|
+
{busy ? 'Decrypting…' : 'View report'}
|
|
117
|
+
</button>
|
|
118
|
+
</form>
|
|
119
|
+
{#if error}<p class="share-err">{error}</p>{/if}
|
|
120
|
+
{/if}
|
|
121
|
+
</div>
|
|
122
|
+
</Shell>
|
|
123
|
+
{/if}
|
|
124
|
+
|
|
125
|
+
<style>
|
|
126
|
+
/* The recipient's unlock page. `.share-form` / `.share-err` base styles are shared (Shell global);
|
|
127
|
+
these are just this page's centered-layout overrides. */
|
|
128
|
+
.share-unlock {
|
|
129
|
+
max-width: 460px;
|
|
130
|
+
margin: 12vh auto 0;
|
|
131
|
+
text-align: center;
|
|
132
|
+
}
|
|
133
|
+
.share-unlock h1 {
|
|
134
|
+
margin: 0 0 8px;
|
|
135
|
+
}
|
|
136
|
+
.share-unlock .share-form {
|
|
137
|
+
max-width: 360px;
|
|
138
|
+
margin: 16px auto 0;
|
|
139
|
+
}
|
|
140
|
+
.share-unlock .share-err {
|
|
141
|
+
text-align: center;
|
|
142
|
+
}
|
|
143
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
base: string;
|
|
3
|
+
/** the login page (with `next` back here) when the UI is secret-gated and the visitor is not in */
|
|
4
|
+
login?: string | null;
|
|
5
|
+
/** this server holds the report — the visitor just is not logged in */
|
|
6
|
+
exists?: boolean;
|
|
7
|
+
};
|
|
8
|
+
declare const PermalinkGate: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
9
|
+
type PermalinkGate = ReturnType<typeof PermalinkGate>;
|
|
10
|
+
export default PermalinkGate;
|