@vitalscope/cli 0.0.0-stage → 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @vitalscope/cli
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e5ad578: First release of the local collector and panel. `npx vitalscope dev` collects what `@vitalscope/sdk` measures into a SQLite file in `.vitalscope/` and serves the panel at `http://localhost:8787`; `vitalscope report` prints the same numbers in the terminal. Needs Node 24.15 or newer.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Emircan Bakar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,61 @@
1
- # Temporary Holding Version
1
+ # @vitalscope/cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The local collector and panel for [`@vitalscope/sdk`](https://www.npmjs.com/package/@vitalscope/sdk). While you develop, it stores the Core Web Vitals and JavaScript errors your browser measures in a SQLite file in your project. You see them in a panel at `http://localhost:8787` or in your terminal. There is no server, account or Docker.
4
+
5
+ Vitalscope is a local tool: it shows what your browser measures, not what real users experience.
6
+
7
+ ## Install
8
+
9
+ Needs Node 24.15 or newer.
10
+
11
+ ```bash
12
+ npm install --save-dev @vitalscope/cli @vitalscope/sdk
13
+ ```
14
+
15
+ Load the SDK in development (see the [SDK README](https://www.npmjs.com/package/@vitalscope/sdk) for React, Next.js and the script tag):
16
+
17
+ ```ts
18
+ if (import.meta.env.DEV) {
19
+ void import("@vitalscope/sdk").then(({ init }) => init());
20
+ }
21
+ ```
22
+
23
+ Start the collector next to your dev server:
24
+
25
+ ```bash
26
+ npx vitalscope dev
27
+ ```
28
+
29
+ Open your app on `localhost`, then open `http://localhost:8787` for the panel.
30
+
31
+ ## Commands
32
+
33
+ | Command | What it does |
34
+ | --- | --- |
35
+ | `vitalscope dev` | Starts the collector and the panel. Ctrl+C writes what is still in memory, then stops |
36
+ | `vitalscope report` | Prints p75 per page and the grouped errors in the terminal. Works while `dev` is running |
37
+
38
+ | Option | Default |
39
+ | --- | --- |
40
+ | `--dir <path>` | `.vitalscope` |
41
+ | `--port <port>` (`dev` only) | `8787`. Pass the same port to the SDK: `init({ endpoint: "http://localhost:9000/v1/e" })` |
42
+
43
+ ## The panel
44
+
45
+ - **Overview:** p75 and the good / needs improvement / poor split for each metric, and the p75 of each page, most visited first. Open a metric for its trend and distribution.
46
+ - **Errors:** the same error is counted once, even when line numbers, build hashes or ids in the message change. The detail shows the last stack and which pages, devices and releases it appeared on.
47
+ - **Releases:** pass a `release` to the SDK, such as a commit hash, and compare two releases side by side. Below 5 measurements, or when the difference is too small, the panel gives no verdict.
48
+ - **Filters:** environment, page, device and time range. The panel opens on the last 30 days. The view is kept in the address, so you can bookmark it.
49
+ - **Live:** the panel updates as measurements arrive.
50
+
51
+ Development and production builds are shown separately and never mixed. The [SDK README](https://www.npmjs.com/package/@vitalscope/sdk#measuring-a-production-build) explains how to measure a production build.
52
+
53
+ ## Your data
54
+
55
+ - Everything is stored in `.vitalscope/vitalscope.db`. The folder contains its own `.gitignore`, so measurements are not committed. Delete the folder to start over.
56
+ - The collector listens only on `127.0.0.1`, and accepts measurements only from local pages (`localhost`, `127.0.0.1`, `[::1]`, `*.localhost`). Other sites open in your browser cannot send to it or read the panel.
57
+ - Nothing is sent anywhere else.
58
+
59
+ ## Verifying a release
60
+
61
+ Every version is published only from GitHub Actions, through npm trusted publishing (no long-lived tokens).
package/SECURITY.md ADDED
@@ -0,0 +1,16 @@
1
+ # Security policy
2
+
3
+ ## Supported versions
4
+
5
+ Only the latest published version of `@vitalscope/sdk` and `@vitalscope/protocol` receives security fixes.
6
+
7
+ ## Reporting a vulnerability
8
+
9
+ Please do not report vulnerabilities in public. Email **ecbakar.dev@gmail.com** with:
10
+
11
+ - the affected package and version
12
+ - what an attacker can do, and how to reproduce it
13
+
14
+ You will get a first response within 72 hours.
15
+
16
+ When a vulnerability is confirmed, a fixed version is published and the affected versions are deprecated on npm with a message pointing to the fix.
package/dist/app.js ADDED
@@ -0,0 +1,70 @@
1
+ import { LIMITS, PROTOCOL_VERSION } from "@vitalscope/protocol";
2
+ import { Batch } from "@vitalscope/protocol/schema";
3
+ import { Hono } from "hono";
4
+ import { bodyLimit } from "hono/body-limit";
5
+ import { isAllowedOrigin, isLocalHost } from "./origin.js";
6
+ import { addPanelRoutes } from "./panel.js";
7
+ /** Tarayıcının preflight yanıtını önbelleğe alacağı süre (sn): 24 saat. */
8
+ const PREFLIGHT_MAX_AGE = "86400";
9
+ /** sendBeacon string gövdeyi `text/plain` gönderir; ikisi de JSON olarak ayrıştırılır. */
10
+ const JSON_BODY_TYPE = /^(text\/plain|application\/json)\s*(;|$)/i;
11
+ /** Yanıtların hepsi boş gövdelidir; hata detayı istemciye dönmez. */
12
+ const empty = (status) => new Response(null, { status });
13
+ export function createApp({ buffer, now = Date.now, panel }) {
14
+ const app = new Hono();
15
+ app.use(async (c, next) => {
16
+ await next();
17
+ c.res.headers.set("X-Content-Type-Options", "nosniff");
18
+ });
19
+ app.use(async (c, next) => {
20
+ if (!isLocalHost(c.req.header("Host")))
21
+ return empty(403);
22
+ return next();
23
+ });
24
+ app.onError(() => empty(500));
25
+ app.notFound(() => new Response(null, { status: 404 }));
26
+ app.get("/healthz", () => new Response(null, { status: 204 }));
27
+ if (panel)
28
+ addPanelRoutes(app, { ...panel, now });
29
+ // Çerez izni hiç verilmez.
30
+ app.options("/v1/e", (c) => {
31
+ const origin = c.req.header("Origin");
32
+ const res = new Response(null, { status: 204 });
33
+ if (isAllowedOrigin(origin))
34
+ res.headers.set("Access-Control-Allow-Origin", origin);
35
+ res.headers.set("Access-Control-Allow-Methods", "POST");
36
+ res.headers.set("Access-Control-Allow-Headers", "Content-Type");
37
+ res.headers.set("Access-Control-Max-Age", PREFLIGHT_MAX_AGE);
38
+ res.headers.set("Vary", "Origin");
39
+ return res;
40
+ });
41
+ app.post("/v1/e", bodyLimit({ maxSize: LIMITS.maxBodyBytes, onError: () => empty(413) }), async (c) => {
42
+ if (!JSON_BODY_TYPE.test(c.req.header("Content-Type") ?? ""))
43
+ return empty(415);
44
+ let body;
45
+ try {
46
+ body = JSON.parse(await c.req.text());
47
+ }
48
+ catch {
49
+ return empty(400);
50
+ }
51
+ const version = body?.v;
52
+ if (typeof version === "number" && version !== PROTOCOL_VERSION)
53
+ return empty(422);
54
+ const parsed = Batch.safeParse(body);
55
+ if (!parsed.success)
56
+ return empty(400);
57
+ const { sdk, ctx, e } = parsed.data;
58
+ const origin = c.req.header("Origin");
59
+ if (!isAllowedOrigin(origin))
60
+ return empty(403);
61
+ const receivedAt = now();
62
+ buffer.add(e.map((event) => ({ receivedAt, sdk, ctx, event })));
63
+ const res = empty(202);
64
+ res.headers.set("Access-Control-Allow-Origin", origin);
65
+ res.headers.set("Vary", "Origin");
66
+ return res;
67
+ });
68
+ return app;
69
+ }
70
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
package/dist/buffer.js ADDED
@@ -0,0 +1,115 @@
1
+ const DEFAULTS = {
2
+ flushIntervalMs: 1_000,
3
+ flushAt: 5_000,
4
+ /** Sınır dolunca yeni olaylar düşürülür; servis bellek taşmasıyla çökmez. */
5
+ maxSize: 50_000,
6
+ maxBackoffMs: 30_000,
7
+ };
8
+ /** Kapanışta yeniden denemeler daha sık yapılır: tanınan süre kısadır. */
9
+ const DRAIN_RETRY_MS = 100;
10
+ function sleep(ms) {
11
+ let timer;
12
+ const done = new Promise((resolve) => {
13
+ timer = setTimeout(resolve, ms);
14
+ });
15
+ return { done, cancel: () => clearTimeout(timer) };
16
+ }
17
+ /**
18
+ * Olayları bellekte biriktirip toplu yazar: istek işlerken eşzamanlı disk yazması yapılmaz.
19
+ * Süreç çökerse tampondaki en fazla ≈ 1 saniyelik veri kaybolur; ölçüm verisi için kabul edilen risk.
20
+ * Yazma hata verirse (ör. veritabanı kilitli) parti üstel geri çekilmeyle aynen yeniden denenir.
21
+ */
22
+ export function createEventBuffer(sink, options = {}) {
23
+ const { flushIntervalMs, flushAt, maxSize, maxBackoffMs } = { ...DEFAULTS, ...options };
24
+ let queue = [];
25
+ let failed = [];
26
+ let dropped = 0;
27
+ let failures = 0;
28
+ let inFlight;
29
+ let writing = 0;
30
+ let timer;
31
+ let draining = false;
32
+ const size = () => queue.length + failed.length;
33
+ function schedule(delay) {
34
+ if (draining || timer)
35
+ return;
36
+ timer = setTimeout(() => {
37
+ timer = undefined;
38
+ void flush();
39
+ }, delay);
40
+ // Bekleyen zamanlayıcı süreci tek başına ayakta tutmaz.
41
+ timer.unref();
42
+ }
43
+ function flush() {
44
+ if (inFlight)
45
+ return inFlight;
46
+ clearTimeout(timer);
47
+ timer = undefined;
48
+ if (size() === 0)
49
+ return Promise.resolve();
50
+ const batch = failed.length > 0 ? failed : queue;
51
+ if (batch === queue)
52
+ queue = [];
53
+ else
54
+ failed = [];
55
+ writing = batch.length;
56
+ inFlight = Promise.resolve()
57
+ .then(() => sink.write(batch))
58
+ .then(() => {
59
+ failures = 0;
60
+ }, () => {
61
+ failures++;
62
+ failed = batch;
63
+ // Arada gelenlerle sınır aşılırsa fazlası en yenilerden düşürülür.
64
+ const excess = Math.max(size() - maxSize, 0);
65
+ dropped += excess;
66
+ queue.length -= excess;
67
+ })
68
+ .finally(() => {
69
+ inFlight = undefined;
70
+ writing = 0;
71
+ // Yazma sürerken eklenen olayın zamanlayıcısı geri çekilme süresini kısaltmasın.
72
+ clearTimeout(timer);
73
+ timer = undefined;
74
+ if (size() > 0)
75
+ schedule(failures ? Math.min(flushIntervalMs * 2 ** failures, maxBackoffMs) : flushIntervalMs);
76
+ });
77
+ return inFlight;
78
+ }
79
+ return {
80
+ add(events) {
81
+ const accepted = events.slice(0, Math.max(maxSize - size(), 0));
82
+ dropped += events.length - accepted.length;
83
+ queue.push(...accepted);
84
+ // Geri çekilme sırasında sınıra ulaşmak yeni deneme başlatmaz; bekleme süresi korunur.
85
+ if (queue.length >= flushAt && failures === 0)
86
+ void flush();
87
+ else
88
+ schedule(flushIntervalMs);
89
+ return accepted.length;
90
+ },
91
+ flush,
92
+ async drain(timeoutMs) {
93
+ draining = true;
94
+ clearTimeout(timer);
95
+ timer = undefined;
96
+ const deadline = Date.now() + timeoutMs;
97
+ for (let remaining = timeoutMs; remaining > 0 && (inFlight || size() > 0); remaining = deadline - Date.now()) {
98
+ const expired = sleep(remaining);
99
+ await Promise.race([flush(), expired.done]);
100
+ expired.cancel();
101
+ remaining = deadline - Date.now();
102
+ if (failures > 0 && size() > 0 && remaining > 0)
103
+ await sleep(Math.min(DRAIN_RETRY_MS * 2 ** failures, remaining)).done;
104
+ }
105
+ // Süre dolarken yazılmakta olanların sonucu bilinmez; onlar da kayıp sayılır.
106
+ const lost = size() + writing;
107
+ dropped += lost;
108
+ queue = [];
109
+ failed = [];
110
+ return lost;
111
+ },
112
+ stats: () => ({ size: size(), dropped }),
113
+ };
114
+ }
115
+ //# sourceMappingURL=buffer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buffer.js","sourceRoot":"","sources":["../src/buffer.ts"],"names":[],"mappings":"AAEA,MAAM,QAAQ,GAAG;IACf,eAAe,EAAE,KAAK;IACtB,OAAO,EAAE,KAAK;IACd,6EAA6E;IAC7E,OAAO,EAAE,MAAM;IACf,YAAY,EAAE,MAAM;CACrB,CAAC;AAEF,0EAA0E;AAC1E,MAAM,cAAc,GAAG,GAAG,CAAC;AAE3B,SAAS,KAAK,CAAC,EAAU;IACvB,IAAI,KAAoC,CAAC;IACzC,MAAM,IAAI,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QACzC,KAAK,GAAG,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;AACrD,CAAC;AAcD;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAe,EAAE,UAAyB,EAAE;IAC5E,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC;IACxF,IAAI,KAAK,GAAkB,EAAE,CAAC;IAC9B,IAAI,MAAM,GAAkB,EAAE,CAAC;IAC/B,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,QAAmC,CAAC;IACxC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,KAAgD,CAAC;IACrD,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAEhD,SAAS,QAAQ,CAAC,KAAa;QAC7B,IAAI,QAAQ,IAAI,KAAK;YAAE,OAAO;QAC9B,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YACtB,KAAK,GAAG,SAAS,CAAC;YAClB,KAAK,KAAK,EAAE,CAAC;QACf,CAAC,EAAE,KAAK,CAAC,CAAC;QACV,wDAAwD;QACxD,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;IAED,SAAS,KAAK;QACZ,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAC9B,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,KAAK,GAAG,SAAS,CAAC;QAClB,IAAI,IAAI,EAAE,KAAK,CAAC;YAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAE3C,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;QACjD,IAAI,KAAK,KAAK,KAAK;YAAE,KAAK,GAAG,EAAE,CAAC;;YAC3B,MAAM,GAAG,EAAE,CAAC;QACjB,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC;QACvB,QAAQ,GAAG,OAAO,CAAC,OAAO,EAAE;aACzB,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;aAC7B,IAAI,CACH,GAAG,EAAE;YACH,QAAQ,GAAG,CAAC,CAAC;QACf,CAAC,EACD,GAAG,EAAE;YACH,QAAQ,EAAE,CAAC;YACX,MAAM,GAAG,KAAK,CAAC;YACf,mEAAmE;YACnE,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC;YAC7C,OAAO,IAAI,MAAM,CAAC;YAClB,KAAK,CAAC,MAAM,IAAI,MAAM,CAAC;QACzB,CAAC,CACF;aACA,OAAO,CAAC,GAAG,EAAE;YACZ,QAAQ,GAAG,SAAS,CAAC;YACrB,OAAO,GAAG,CAAC,CAAC;YACZ,iFAAiF;YACjF,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,KAAK,GAAG,SAAS,CAAC;YAClB,IAAI,IAAI,EAAE,GAAG,CAAC;gBAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,GAAG,CAAC,IAAI,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;QACjH,CAAC,CAAC,CAAC;QACL,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,OAAO;QACL,GAAG,CAAC,MAAM;YACR,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;YAChE,OAAO,IAAI,MAAM,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;YAC3C,KAAK,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC;YACxB,uFAAuF;YACvF,IAAI,KAAK,CAAC,MAAM,IAAI,OAAO,IAAI,QAAQ,KAAK,CAAC;gBAAE,KAAK,KAAK,EAAE,CAAC;;gBACvD,QAAQ,CAAC,eAAe,CAAC,CAAC;YAC/B,OAAO,QAAQ,CAAC,MAAM,CAAC;QACzB,CAAC;QACD,KAAK;QACL,KAAK,CAAC,KAAK,CAAC,SAAS;YACnB,QAAQ,GAAG,IAAI,CAAC;YAChB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,KAAK,GAAG,SAAS,CAAC;YAClB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YACxC,KAAK,IAAI,SAAS,GAAG,SAAS,EAAE,SAAS,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,EAAE,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;gBAC7G,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;gBACjC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC5C,OAAO,CAAC,MAAM,EAAE,CAAC;gBACjB,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAClC,IAAI,QAAQ,GAAG,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC,IAAI,SAAS,GAAG,CAAC;oBAAE,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,cAAc,GAAG,CAAC,IAAI,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;YACzH,CAAC;YACD,8EAA8E;YAC9E,MAAM,IAAI,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;YAC9B,OAAO,IAAI,IAAI,CAAC;YAChB,KAAK,GAAG,EAAE,CAAC;YACX,MAAM,GAAG,EAAE,CAAC;YACZ,OAAO,IAAI,CAAC;QACd,CAAC;QACD,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC;KACzC,CAAC;AACJ,CAAC"}
package/dist/cli.js ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import { MIN_NODE, isSupportedNode } from "./node-version.js";
3
+ // Komutlar sürüm kontrolünden sonra yüklenir: eski Node'da `node:sqlite` importu anlaşılmaz bir hata verir.
4
+ if (isSupportedNode(process.versions.node)) {
5
+ const { run } = await import("./commands.js");
6
+ process.exitCode = await run(process.argv.slice(2));
7
+ }
8
+ else {
9
+ console.error(`vitalscope needs Node ${MIN_NODE} or newer (found ${process.version}).`);
10
+ process.exitCode = 1;
11
+ }
12
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAE9D,4GAA4G;AAC5G,IAAI,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;IAC3C,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;IAC9C,OAAO,CAAC,QAAQ,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACtD,CAAC;KAAM,CAAC;IACN,OAAO,CAAC,KAAK,CAAC,yBAAyB,QAAQ,oBAAoB,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC;IACxF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC"}
@@ -0,0 +1,92 @@
1
+ import { resolve } from "node:path";
2
+ import { parseArgs } from "node:util";
3
+ import { buildReport } from "./report.js";
4
+ import { start } from "./start.js";
5
+ const DEFAULT_PORT = 8787;
6
+ const DEFAULT_DIR = ".vitalscope";
7
+ const USAGE = `Usage: vitalscope <command> [options]
8
+
9
+ Commands:
10
+ dev Start the local collector and panel
11
+ report Print p75 metrics and errors from collected data
12
+
13
+ Options:
14
+ --dir <path> Data folder (default: ${DEFAULT_DIR})
15
+ --port <port> Collector port, dev only (default: ${DEFAULT_PORT})
16
+ -h, --help Show this help
17
+ `;
18
+ function parsePort(value) {
19
+ if (value === undefined)
20
+ return DEFAULT_PORT;
21
+ const port = Number(value);
22
+ return /^\d+$/.test(value) && port <= 65_535 ? port : null;
23
+ }
24
+ async function dev(port, dir) {
25
+ let server;
26
+ try {
27
+ server = await start({ port, dir });
28
+ }
29
+ catch (error) {
30
+ if (error.code !== "EADDRINUSE")
31
+ throw error;
32
+ console.error(`Port ${port} is already in use. Pick another one with --port.`);
33
+ return 1;
34
+ }
35
+ console.log(`Vitalscope collector is running.
36
+
37
+ Panel http://localhost:${server.port}/
38
+ Endpoint http://localhost:${server.port}/v1/e
39
+ Data ${resolve(dir)}
40
+
41
+ Press Ctrl+C to stop. Run "vitalscope report" to see the results.`);
42
+ await new Promise((done) => {
43
+ const stop = () => {
44
+ void server.shutdown().then((lost) => {
45
+ if (lost > 0)
46
+ console.error(`${lost} events could not be written.`);
47
+ done();
48
+ });
49
+ };
50
+ process.once("SIGINT", stop);
51
+ process.once("SIGTERM", stop);
52
+ });
53
+ return 0;
54
+ }
55
+ /** Çıkış kodunu döner: 0 başarılı, 1 çalışma hatası, 2 yanlış kullanım. */
56
+ export async function run(argv) {
57
+ let parsed;
58
+ try {
59
+ parsed = parseArgs({
60
+ args: argv,
61
+ allowPositionals: true,
62
+ options: { dir: { type: "string" }, port: { type: "string" }, help: { type: "boolean", short: "h" } },
63
+ });
64
+ }
65
+ catch (error) {
66
+ console.error(`${error.message}\n\n${USAGE}`);
67
+ return 2;
68
+ }
69
+ const { values, positionals } = parsed;
70
+ const [command, ...rest] = positionals;
71
+ if (values.help || command === undefined) {
72
+ console.log(USAGE);
73
+ return 0;
74
+ }
75
+ const dir = values.dir ?? DEFAULT_DIR;
76
+ const port = parsePort(values.port);
77
+ if ((command !== "dev" && command !== "report") || rest.length > 0 || port === null) {
78
+ console.error(`${port === null ? "--port must be a number from 0 to 65535." : `Unknown command: ${positionals.join(" ")}`}\n\n${USAGE}`);
79
+ return 2;
80
+ }
81
+ try {
82
+ if (command === "dev")
83
+ return await dev(port, dir);
84
+ console.log(buildReport(dir));
85
+ return 0;
86
+ }
87
+ catch (error) {
88
+ console.error(error instanceof Error ? error.message : "Unknown error");
89
+ return 1;
90
+ }
91
+ }
92
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import { createHash } from "node:crypto";
2
+ const UUID = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi;
3
+ const LONG_HEX = /\b(?=[a-f]*\d)(?:0x)?[0-9a-f]{8,}\b/gi;
4
+ const NUMBER = /\b\d+(?:\.\d+)?\b/g;
5
+ /** Bundler'ın dosya adına eklediği içerik özeti: `index-CyA1sY9x.js`, `main.a1b2c3d4.js`. */
6
+ const BUILD_HASH = /[-.]([\w-]{8,})(\.[cm]?js)$/;
7
+ /** Mesajdaki değişken kısımlar: "User 42 not found" ile "User 43 not found" aynı hatadır. */
8
+ function normalizeMessage(message) {
9
+ return message.replace(UUID, "#").replace(LONG_HEX, "#").replace(NUMBER, "#");
10
+ }
11
+ /** Sunucu adresi, port, `?t=` gibi eklentiler ve build özeti düşer; kaynak aynıysa yol aynı kalır. */
12
+ function normalizePath(location) {
13
+ let path;
14
+ try {
15
+ path = new URL(location).pathname;
16
+ }
17
+ catch {
18
+ path = location.split(/[?#]/, 1)[0];
19
+ }
20
+ return path.replace(BUILD_HASH, (match, hash, extension) => (/[\dA-Z]/.test(hash) ? extension : match));
21
+ }
22
+ /** Chrome: `at fn (url:1:2)`, `at url:1:2`. Firefox ve Safari: `fn@url:1:2`, `@url:1:2`. Yeri olmayan satır (`map@[native code]`) kare sayılmaz. */
23
+ function parseFrame(frame) {
24
+ let name = "";
25
+ let location = frame;
26
+ if (frame.startsWith("at ")) {
27
+ const text = frame.slice(3).replace(/^async /, "");
28
+ const call = /^(.*?) \((.+)\)$/.exec(text);
29
+ [name, location] = call ? [call[1], call[2]] : ["", text];
30
+ }
31
+ else if (!/^[a-z-]+:\/\//.test(frame) && frame.includes("@")) {
32
+ const at = frame.indexOf("@");
33
+ [name, location] = [frame.slice(0, at), frame.slice(at + 1)];
34
+ }
35
+ const position = /:\d+(?::\d+)?$/.exec(location);
36
+ if (!position)
37
+ return undefined;
38
+ return {
39
+ // Adsız fonksiyon her tarayıcıda başka yazılır: `HTMLButtonElement.<anonymous>`, `later/<`, boş.
40
+ name: name.includes("<") ? "" : name,
41
+ path: normalizePath(location.slice(0, position.index)),
42
+ };
43
+ }
44
+ /**
45
+ * Aynı hatanın olayları için ortak anahtar: tür, temizlenmiş mesaj ve uygulama kodundaki ilk kare.
46
+ * Satır ve sütun bilerek dışarıda: kod düzenlenince kayar, tarayıcılar arasında da farklıdır.
47
+ */
48
+ export function errorGroup(type, message, stack) {
49
+ const frames = stack.flatMap((frame) => parseFrame(frame) ?? []);
50
+ const top = frames.find((frame) => !frame.path.includes("/node_modules/")) ?? frames[0];
51
+ const location = top && (top.name ? `${top.name} (${top.path})` : top.path);
52
+ const key = JSON.stringify([type, normalizeMessage(message), location]);
53
+ // Adrese konulabilen kısa kimlik; panelde hata detayı bu kimlikle açılır.
54
+ return { key, id: createHash("sha256").update(key).digest("hex").slice(0, 16), location };
55
+ }
56
+ //# sourceMappingURL=fingerprint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../src/fingerprint.ts"],"names":[],"mappings":"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"}
package/dist/live.js ADDED
@@ -0,0 +1,37 @@
1
+ const encoder = new TextEncoder();
2
+ /** Yorum satırı: yanıt başlıklarının hemen gitmesini sağlar, tarayıcı bağlantıyı açık sayar. */
3
+ const OPENED = encoder.encode(": connected\n\n");
4
+ const CHANGE = encoder.encode("event: change\ndata: \n\n");
5
+ export function createLive() {
6
+ const streams = new Set();
7
+ return {
8
+ notify() {
9
+ for (const stream of streams)
10
+ stream.enqueue(CHANGE);
11
+ },
12
+ stream() {
13
+ let controller;
14
+ const body = new ReadableStream({
15
+ start(c) {
16
+ controller = c;
17
+ streams.add(c);
18
+ c.enqueue(OPENED);
19
+ },
20
+ cancel() {
21
+ streams.delete(controller);
22
+ },
23
+ });
24
+ // Akış bitince bağlantı da kapanmalı; canlı kalırsa kapanış, zorla kapatma süresini bekler.
25
+ return new Response(body, { headers: { "Content-Type": "text/event-stream; charset=utf-8", Connection: "close" } });
26
+ },
27
+ close() {
28
+ for (const stream of streams)
29
+ stream.close();
30
+ streams.clear();
31
+ },
32
+ get connections() {
33
+ return streams.size;
34
+ },
35
+ };
36
+ }
37
+ //# sourceMappingURL=live.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live.js","sourceRoot":"","sources":["../src/live.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;AAClC,gGAAgG;AAChG,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;AACjD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,2BAA2B,CAAC,CAAC;AAY3D,MAAM,UAAU,UAAU;IACxB,MAAM,OAAO,GAAG,IAAI,GAAG,EAA+C,CAAC;IACvE,OAAO;QACL,MAAM;YACJ,KAAK,MAAM,MAAM,IAAI,OAAO;gBAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACvD,CAAC;QACD,MAAM;YACJ,IAAI,UAAuD,CAAC;YAC5D,MAAM,IAAI,GAAG,IAAI,cAAc,CAAa;gBAC1C,KAAK,CAAC,CAAC;oBACL,UAAU,GAAG,CAAC,CAAC;oBACf,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;oBACf,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;gBACpB,CAAC;gBACD,MAAM;oBACJ,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;gBAC7B,CAAC;aACF,CAAC,CAAC;YACH,4FAA4F;YAC5F,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kCAAkC,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;QACtH,CAAC;QACD,KAAK;YACH,KAAK,MAAM,MAAM,IAAI,OAAO;gBAAE,MAAM,CAAC,KAAK,EAAE,CAAC;YAC7C,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,CAAC;QACD,IAAI,WAAW;YACb,OAAO,OAAO,CAAC,IAAI,CAAC;QACtB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ /** `node:sqlite` bu sürümden beri release candidate; daha eskisinde deneysel veya hiç yok. */
2
+ export const MIN_NODE = "24.15.0";
3
+ export function isSupportedNode(version) {
4
+ const [major = 0, minor = 0] = version.split(".").map(Number);
5
+ return major > 24 || (major === 24 && minor >= 15);
6
+ }
7
+ //# sourceMappingURL=node-version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-version.js","sourceRoot":"","sources":["../src/node-version.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,MAAM,CAAC,MAAM,QAAQ,GAAG,SAAS,CAAC;AAElC,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,MAAM,CAAC,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9D,OAAO,KAAK,GAAG,EAAE,IAAI,CAAC,KAAK,KAAK,EAAE,IAAI,KAAK,IAAI,EAAE,CAAC,CAAC;AACrD,CAAC"}
package/dist/origin.js ADDED
@@ -0,0 +1,26 @@
1
+ const LOCAL_HOST_HEADER = /^(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/i;
2
+ /**
3
+ * İstek bu makinedeki bir sayfadan mı geliyor? Bilgisayarda açık herhangi bir site toplayıcıya istek atmayı
4
+ * deneyebilir; başlık yoksa veya `null` ise de reddedilir. Not: Origin tarayıcı dışından sahte gönderilebilir,
5
+ * ama toplayıcıya sadece bu makineden ulaşılır.
6
+ */
7
+ export function isAllowedOrigin(origin) {
8
+ if (!origin)
9
+ return false;
10
+ let url;
11
+ try {
12
+ url = new URL(origin);
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ if (url.protocol !== "http:" && url.protocol !== "https:")
18
+ return false;
19
+ const hostname = url.hostname.toLowerCase();
20
+ return LOCAL_HOST_HEADER.test(hostname) || hostname.endsWith(".localhost");
21
+ }
22
+ /** DNS rebinding'e karşı: kötü niyetli bir alan adı 127.0.0.1'e çözülse de `Host` başlığı o alan adı kalır. */
23
+ export function isLocalHost(host) {
24
+ return host !== undefined && LOCAL_HOST_HEADER.test(host);
25
+ }
26
+ //# sourceMappingURL=origin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"origin.js","sourceRoot":"","sources":["../src/origin.ts"],"names":[],"mappings":"AAAA,MAAM,iBAAiB,GAAG,4CAA4C,CAAC;AAEvE;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,MAA0B;IACxD,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACxE,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;IAC5C,OAAO,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;AAC7E,CAAC;AAED,+GAA+G;AAC/G,MAAM,UAAU,WAAW,CAAC,IAAwB;IAClD,OAAO,IAAI,KAAK,SAAS,IAAI,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC5D,CAAC"}
@@ -0,0 +1 @@
1
+ .vs-chart{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--vs-surface:#fcfcfb;--vs-text:#0b0b0b;--vs-text-secondary:#52514e;--vs-muted:#898781;--vs-grid:#e1e0d9;--vs-baseline:#c3c2b7;--vs-border:#0b0b0b1a;--vs-series-1:#2a78d6;--vs-series-2:#eb6834;--vs-series-3:#1baf7a;--vs-series-4:#eda100;--vs-series-5:#e87ba4;--vs-series-6:#008300;--vs-series-7:#6250d6;--vs-series-8:#e34948;--vs-good:#0ca30c;--vs-needs:#fab219;--vs-poor:#d03b3b;background:var(--vs-surface);color:var(--vs-text);margin:0;padding:16px;font:13px/1.4 system-ui,-apple-system,Segoe UI,sans-serif}@media (prefers-color-scheme:dark){:root:where(:not([data-theme=light])) .vs-chart{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--vs-surface:#1a1a19;--vs-text:#f0efec;--vs-text-secondary:#c3c2b7;--vs-grid:#2c2c2a;--vs-baseline:#383835;--vs-border:#ffffff1a;--vs-series-1:#3987e5;--vs-series-2:#d95926;--vs-series-3:#199e70;--vs-series-4:#c98500;--vs-series-5:#d55181;--vs-series-7:#9085e9;--vs-series-8:#e66767}}:root[data-theme=dark] .vs-chart{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--vs-surface:#1a1a19;--vs-text:#f0efec;--vs-text-secondary:#c3c2b7;--vs-grid:#2c2c2a;--vs-baseline:#383835;--vs-border:#ffffff1a;--vs-series-1:#3987e5;--vs-series-2:#d95926;--vs-series-3:#199e70;--vs-series-4:#c98500;--vs-series-5:#d55181;--vs-series-7:#9085e9;--vs-series-8:#e66767}.vs-title{font-size:14px;font-weight:600}.vs-sr-only{clip-path:inset(50%);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.vs-summary{color:var(--vs-text-secondary);margin:2px 0 0}.vs-legend{color:var(--vs-text-secondary);flex-wrap:wrap;gap:4px 16px;margin:12px 0 0;padding:0;font-size:12px;list-style:none;display:flex}.vs-legend li{align-items:center;gap:6px;display:flex}.vs-chart-legend{background:0 0;padding:0}.vs-chart-legend .vs-legend{margin:0}.vs-key{border-radius:2px;flex:none;display:inline-block}.vs-key-line{width:14px;height:2px}.vs-key-block{width:12px;height:12px}.vs-plot{touch-action:pan-y;margin-top:12px;position:relative}.vs-plot:focus-visible,.vs-rating:focus-visible{outline:2px solid var(--vs-series-1);outline-offset:2px;border-radius:4px}.vs-plot svg,.vs-canvas{display:block;position:absolute;inset:0}.vs-axis{fill:var(--vs-muted);font-variant-numeric:tabular-nums;font-size:11px}.vs-grid{stroke:var(--vs-grid);stroke-width:1px}.vs-baseline{stroke:var(--vs-baseline);stroke-width:1px}.vs-line{fill:none;stroke-width:2px;stroke-linejoin:round;stroke-linecap:round}.vs-dot{stroke:var(--vs-surface);stroke-width:2px}.vs-end-label{fill:var(--vs-text-secondary);font-variant-numeric:tabular-nums;font-size:11px}.vs-crosshair{stroke:var(--vs-muted);stroke-width:1px}.vs-hit{fill:#0000}.vs-bar-active{filter:brightness(1.12)}.vs-marker{stroke-width:1px}.vs-marker-threshold{stroke:var(--vs-muted)}.vs-marker-percentile{stroke:var(--vs-text)}.vs-marker-label{fill:var(--vs-text-secondary);font-size:11px}.vs-tooltip{z-index:1;border:1px solid var(--vs-border);background:var(--vs-surface);white-space:nowrap;pointer-events:none;border-radius:6px;padding:6px 8px;font-size:12px;position:absolute;top:8px;box-shadow:0 2px 8px #00000026}.vs-tooltip-title{color:var(--vs-text-secondary)}.vs-tooltip-row{color:var(--vs-text-secondary);align-items:center;gap:6px;display:flex}.vs-tooltip-row strong{color:var(--vs-text);font-variant-numeric:tabular-nums}.vs-rating{margin-top:12px;position:relative}.vs-rating-bar{gap:2px;height:28px;display:flex}.vs-rating-part{color:#fff;border-radius:4px;flex:1 1 0;min-width:2px}.vs-on-light{color:#0b0b0b}.vs-rating-label{padding:0 8px;font-size:12px;font-weight:600;line-height:28px;display:block}.vs-pattern-rising{background-image:repeating-linear-gradient(45deg,#00000024 0 2px,#0000 2px 6px)}.vs-pattern-falling{background-image:repeating-linear-gradient(135deg,#00000024 0 2px,#0000 2px 6px)}.vs-table{color:var(--vs-text-secondary);margin-top:12px;font-size:12px}.vs-table summary{cursor:pointer}.vs-table table{border-collapse:collapse;font-variant-numeric:tabular-nums;width:100%;margin-top:8px}.vs-table th,.vs-table td{border-bottom:1px solid var(--vs-grid);text-align:right;padding:4px 8px;font-weight:400}.vs-table th:first-child{text-align:left}.vs-table thead th{color:var(--vs-text);font-weight:600}.vs-plot,.vs-rating-bar,.vs-key{-webkit-print-color-adjust:exact;print-color-adjust:exact}@media (forced-colors:active){.vs-plot svg,.vs-canvas,.vs-rating-bar,.vs-key{forced-color-adjust:none}}:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;--page:#f9f9f7;--surface:#fcfcfb;--text:#0b0b0b;--text-secondary:#52514e;--border:#e1e0d9;--accent:#256abf;--good-text:#006300;--needs-text:#8a5d00;--poor-text:#b02a2a;--warning-surface:#fdf3dc;background:var(--page);color:var(--text);font:14px/1.5 system-ui,-apple-system,Segoe UI,sans-serif}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial;--page:#0d0d0d;--surface:#1a1a19;--text:#f0efec;--text-secondary:#c3c2b7;--border:#2c2c2a;--accent:#86b6ef;--good-text:#4cc44c;--needs-text:#fab219;--poor-text:#f08080;--warning-surface:#2e2510}}body{background:var(--page);margin:0}a{color:var(--accent)}h1,h2,h3,p{margin:0}.header{border-bottom:1px solid var(--border);background:var(--surface);flex-wrap:wrap;align-items:center;gap:8px 24px;padding:12px 16px;display:flex}.header h1{font-size:16px}.header h1 a{color:var(--text);text-decoration:none}.tabs{flex-wrap:wrap;gap:4px;display:flex}.tabs a{color:var(--text-secondary);border-radius:6px;padding:4px 10px;text-decoration:none}.tabs a:hover{color:var(--text)}.tabs a[aria-current=page]{background:var(--border);color:var(--text);font-weight:600}main{grid-template-columns:minmax(0,1fr);gap:16px;max-width:1200px;margin:0 auto;padding:16px;display:grid}.filters{flex-wrap:wrap;align-items:end;gap:8px 12px;display:flex}.filter{color:var(--text-secondary);gap:2px;font-size:12px;display:grid}select,button{border:1px solid var(--border);background:var(--surface);max-width:220px;min-height:32px;color:var(--text);font:inherit;border-radius:6px;padding:4px 8px;font-size:13px}button{cursor:pointer}.warning{background:var(--warning-surface);border-radius:6px;padding:10px 12px}.note{color:var(--text-secondary);font-size:12px}.notice{background:var(--surface);border-radius:6px;padding:24px}.notice h2{margin-bottom:8px;font-size:16px}.notice pre{background:var(--page);border-radius:6px;margin:12px 0;padding:12px;overflow-x:auto}.stale{opacity:.6}.cards{grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:12px;display:grid}.card{background:var(--surface);border-radius:6px;padding:16px 16px 0}.card h3{font-size:14px}.card-title,.card-label,.card-empty{color:var(--text-secondary);font-size:12px}.card-value{margin-top:8px;font-size:28px;font-weight:600;line-height:1.2}.card .vs-chart{margin:8px -16px 0}.rating-good{color:var(--good-text)}.rating-needs-improvement{color:var(--needs-text)}.rating-poor{color:var(--poor-text)}.table-section{background:var(--surface);border-radius:6px;padding:16px}.table-section h2{margin-bottom:8px;font-size:14px}.table-scroll{overflow-x:auto}.table-section table{border-collapse:collapse;font-variant-numeric:tabular-nums;width:100%}.table-section th,.table-section td{border-bottom:1px solid var(--border);text-align:right;white-space:nowrap;padding:6px 8px;font-weight:400}.table-section th:first-child{text-align:left;text-overflow:ellipsis;max-width:340px;overflow:hidden}.table-section thead th{font-weight:600}.detail,.overview{grid-template-columns:minmax(0,1fr);gap:16px;display:grid}.detail-value strong{font-size:28px;font-weight:600}.breakdowns{grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px;display:grid}@media (width<=480px){.cards,.breakdowns{grid-template-columns:minmax(0,1fr)}select{max-width:150px}}.error-title{overflow-wrap:anywhere;white-space:normal}.error-location{color:var(--text-secondary);overflow-wrap:anywhere;font-size:12px}.table-section th.error-cell{white-space:normal;max-width:640px}.stack{background:var(--page);border-radius:6px;margin:0;padding:12px;font-size:12px;overflow-x:auto}.facts{color:var(--text-secondary);flex-wrap:wrap;gap:4px 24px;margin:8px 0 0;font-size:12px;display:flex}.facts dt{display:inline}.facts dd{color:var(--text);margin:0 0 0 4px;display:inline}.detail-header{background:var(--surface);border-radius:6px;padding:16px}.detail-header h2{overflow-wrap:anywhere;font-size:16px;font-weight:600}.compare-picker{flex-wrap:wrap;gap:8px 12px;display:flex}.change-worse{color:var(--poor-text)}.change-better{color:var(--good-text)}.live{color:var(--text-secondary);margin-left:auto;font-size:12px}.live-live:before{content:"";background:var(--good-text);border-radius:50%;width:8px;height:8px;margin-right:6px;display:inline-block}.live-offline{color:var(--poor-text)}