@hydration-audit/dashboard 0.2.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/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @hydration-audit/dashboard
2
+
3
+ > Interactive web dashboard for visualizing JavaScript hydration costs.
4
+
5
+ Part of the [Hydration Cost Visibility Platform](../../README.md).
6
+
7
+ ## Features
8
+
9
+ - **Treemap Visualization** — Islands sized by gzip cost, colored by severity
10
+ - **Sortable Island Table** — Click any column header to sort
11
+ - **Budget Gauges** — Visual meters for page and site-wide budgets
12
+ - **Issue List** — All detected issues with actionable recommendations
13
+ - **Live Reload** — Dashboard updates automatically via WebSocket when the report file changes
14
+
15
+ ## Usage
16
+
17
+ ### Via CLI (Recommended)
18
+
19
+ ```bash
20
+ npx @hydration-audit/cli dashboard
21
+ npx @hydration-audit/cli dashboard --port 3000
22
+ ```
23
+
24
+ ### Programmatic API
25
+
26
+ ```typescript
27
+ import { startDashboard } from '@hydration-audit/dashboard';
28
+
29
+ const { url, close } = await startDashboard(
30
+ '.hydration-audit-report.json', // path to report
31
+ 4173, // port
32
+ );
33
+
34
+ console.log(`Dashboard at ${url}`);
35
+
36
+ // Later...
37
+ close();
38
+ ```
39
+
40
+ ## How It Works
41
+
42
+ 1. Reads the `.hydration-audit-report.json` file generated by the analyzer
43
+ 2. Serves a lightweight SPA (vanilla JS, no framework runtime) on a local HTTP server
44
+ 3. Opens a WebSocket connection to watch for report file changes
45
+ 4. When the report changes (e.g., after a rebuild), all connected browsers update instantly
46
+
47
+ ## Dashboard Views
48
+
49
+ ### Treemap
50
+ Islands are displayed as rectangles proportional to their gzip size. Colors indicate severity:
51
+ - **Green** — Under 20KB, no issues
52
+ - **Blue** — 20-50KB, within budget
53
+ - **Yellow** — 50-100KB, warning
54
+ - **Red** — Over 100KB or has errors
55
+
56
+ ### Island Table
57
+ Sortable table with columns:
58
+ - Name, Directive, Framework, Gzip size, Brotli size, Issues, Pages
59
+
60
+ ### Budget Gauges
61
+ Visual progress bars showing:
62
+ - Per-page budget usage
63
+ - Total site budget usage
64
+
65
+ ## License
66
+
67
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ startDashboard: () => startDashboard
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/server.ts
38
+ var import_node_http = __toESM(require("http"), 1);
39
+ var import_node_fs = __toESM(require("fs"), 1);
40
+ var import_node_path = __toESM(require("path"), 1);
41
+ var import_ws = require("ws");
42
+ var import_meta = {};
43
+ var MIME_TYPES = {
44
+ ".html": "text/html",
45
+ ".js": "application/javascript",
46
+ ".css": "text/css",
47
+ ".json": "application/json",
48
+ ".svg": "image/svg+xml"
49
+ };
50
+ async function startDashboard(reportPath, port = 4173) {
51
+ const absoluteReportPath = import_node_path.default.resolve(reportPath);
52
+ let currentReport = null;
53
+ try {
54
+ const content = import_node_fs.default.readFileSync(absoluteReportPath, "utf-8");
55
+ currentReport = JSON.parse(content);
56
+ } catch {
57
+ console.warn(`Could not read report at ${absoluteReportPath}`);
58
+ }
59
+ const staticDir = import_node_path.default.resolve(
60
+ import_node_path.default.dirname(new URL(import_meta.url).pathname),
61
+ "app"
62
+ );
63
+ const server = import_node_http.default.createServer((req, res) => {
64
+ const url = req.url ?? "/";
65
+ if (url === "/api/report") {
66
+ res.writeHead(200, { "Content-Type": "application/json" });
67
+ res.end(JSON.stringify(currentReport ?? { error: "No report loaded" }));
68
+ return;
69
+ }
70
+ let filePath = import_node_path.default.join(staticDir, url === "/" ? "index.html" : url);
71
+ const ext = import_node_path.default.extname(filePath);
72
+ if (!ext) {
73
+ filePath = import_node_path.default.join(staticDir, "index.html");
74
+ }
75
+ try {
76
+ const content = import_node_fs.default.readFileSync(filePath);
77
+ const mime = MIME_TYPES[import_node_path.default.extname(filePath)] ?? "text/plain";
78
+ res.writeHead(200, { "Content-Type": mime });
79
+ res.end(content);
80
+ } catch {
81
+ res.writeHead(404);
82
+ res.end("Not found");
83
+ }
84
+ });
85
+ const wss = new import_ws.WebSocketServer({ server });
86
+ let watcher = null;
87
+ try {
88
+ watcher = import_node_fs.default.watch(absoluteReportPath, () => {
89
+ try {
90
+ const content = import_node_fs.default.readFileSync(absoluteReportPath, "utf-8");
91
+ currentReport = JSON.parse(content);
92
+ for (const client of wss.clients) {
93
+ if (client.readyState === 1) {
94
+ client.send(JSON.stringify({ type: "update", report: currentReport }));
95
+ }
96
+ }
97
+ } catch {
98
+ }
99
+ });
100
+ } catch {
101
+ console.warn("Could not watch report file for changes");
102
+ }
103
+ return new Promise((resolve) => {
104
+ server.listen(port, () => {
105
+ const url = `http://localhost:${port}`;
106
+ console.log(`
107
+ Dashboard running at ${url}
108
+ `);
109
+ resolve({
110
+ url,
111
+ close: () => {
112
+ watcher?.close();
113
+ wss.close();
114
+ server.close();
115
+ }
116
+ });
117
+ });
118
+ });
119
+ }
120
+ // Annotate the CommonJS export names for ESM import in node:
121
+ 0 && (module.exports = {
122
+ startDashboard
123
+ });
124
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/server.ts"],"sourcesContent":["export { startDashboard } from './server.js';\n","import http from 'node:http';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { WebSocketServer } from 'ws';\nimport type { AnalysisReport } from '@hydration-audit/core';\n\nconst MIME_TYPES: Record<string, string> = {\n '.html': 'text/html',\n '.js': 'application/javascript',\n '.css': 'text/css',\n '.json': 'application/json',\n '.svg': 'image/svg+xml',\n};\n\n/**\n * Start the dashboard server.\n * Serves a static SPA and provides a WebSocket for live report updates.\n */\nexport async function startDashboard(\n reportPath: string,\n port = 4173,\n): Promise<{ url: string; close: () => void }> {\n const absoluteReportPath = path.resolve(reportPath);\n\n // Read the initial report\n let currentReport: AnalysisReport | null = null;\n try {\n const content = fs.readFileSync(absoluteReportPath, 'utf-8');\n currentReport = JSON.parse(content);\n } catch {\n console.warn(`Could not read report at ${absoluteReportPath}`);\n }\n\n const staticDir = path.resolve(\n path.dirname(new URL(import.meta.url).pathname),\n 'app',\n );\n\n const server = http.createServer((req, res) => {\n const url = req.url ?? '/';\n\n // API endpoint for report data\n if (url === '/api/report') {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(currentReport ?? { error: 'No report loaded' }));\n return;\n }\n\n // Serve static files\n let filePath = path.join(staticDir, url === '/' ? 'index.html' : url);\n const ext = path.extname(filePath);\n\n // SPA fallback — serve index.html for non-file routes\n if (!ext) {\n filePath = path.join(staticDir, 'index.html');\n }\n\n try {\n const content = fs.readFileSync(filePath);\n const mime = MIME_TYPES[path.extname(filePath)] ?? 'text/plain';\n res.writeHead(200, { 'Content-Type': mime });\n res.end(content);\n } catch {\n res.writeHead(404);\n res.end('Not found');\n }\n });\n\n // WebSocket for live reload\n const wss = new WebSocketServer({ server });\n\n // Watch report file for changes\n let watcher: fs.FSWatcher | null = null;\n try {\n watcher = fs.watch(absoluteReportPath, () => {\n try {\n const content = fs.readFileSync(absoluteReportPath, 'utf-8');\n currentReport = JSON.parse(content);\n // Notify all connected clients\n for (const client of wss.clients) {\n if (client.readyState === 1) {\n client.send(JSON.stringify({ type: 'update', report: currentReport }));\n }\n }\n } catch {\n // Ignore parse errors during write\n }\n });\n } catch {\n console.warn('Could not watch report file for changes');\n }\n\n return new Promise((resolve) => {\n server.listen(port, () => {\n const url = `http://localhost:${port}`;\n console.log(`\\n Dashboard running at ${url}\\n`);\n resolve({\n url,\n close: () => {\n watcher?.close();\n wss.close();\n server.close();\n },\n });\n });\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,uBAAiB;AACjB,qBAAe;AACf,uBAAiB;AACjB,gBAAgC;AAHhC;AAMA,IAAM,aAAqC;AAAA,EACzC,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AACV;AAMA,eAAsB,eACpB,YACA,OAAO,MACsC;AAC7C,QAAM,qBAAqB,iBAAAA,QAAK,QAAQ,UAAU;AAGlD,MAAI,gBAAuC;AAC3C,MAAI;AACF,UAAM,UAAU,eAAAC,QAAG,aAAa,oBAAoB,OAAO;AAC3D,oBAAgB,KAAK,MAAM,OAAO;AAAA,EACpC,QAAQ;AACN,YAAQ,KAAK,4BAA4B,kBAAkB,EAAE;AAAA,EAC/D;AAEA,QAAM,YAAY,iBAAAD,QAAK;AAAA,IACrB,iBAAAA,QAAK,QAAQ,IAAI,IAAI,YAAY,GAAG,EAAE,QAAQ;AAAA,IAC9C;AAAA,EACF;AAEA,QAAM,SAAS,iBAAAE,QAAK,aAAa,CAAC,KAAK,QAAQ;AAC7C,UAAM,MAAM,IAAI,OAAO;AAGvB,QAAI,QAAQ,eAAe;AACzB,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,iBAAiB,EAAE,OAAO,mBAAmB,CAAC,CAAC;AACtE;AAAA,IACF;AAGA,QAAI,WAAW,iBAAAF,QAAK,KAAK,WAAW,QAAQ,MAAM,eAAe,GAAG;AACpE,UAAM,MAAM,iBAAAA,QAAK,QAAQ,QAAQ;AAGjC,QAAI,CAAC,KAAK;AACR,iBAAW,iBAAAA,QAAK,KAAK,WAAW,YAAY;AAAA,IAC9C;AAEA,QAAI;AACF,YAAM,UAAU,eAAAC,QAAG,aAAa,QAAQ;AACxC,YAAM,OAAO,WAAW,iBAAAD,QAAK,QAAQ,QAAQ,CAAC,KAAK;AACnD,UAAI,UAAU,KAAK,EAAE,gBAAgB,KAAK,CAAC;AAC3C,UAAI,IAAI,OAAO;AAAA,IACjB,QAAQ;AACN,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI,WAAW;AAAA,IACrB;AAAA,EACF,CAAC;AAGD,QAAM,MAAM,IAAI,0BAAgB,EAAE,OAAO,CAAC;AAG1C,MAAI,UAA+B;AACnC,MAAI;AACF,cAAU,eAAAC,QAAG,MAAM,oBAAoB,MAAM;AAC3C,UAAI;AACF,cAAM,UAAU,eAAAA,QAAG,aAAa,oBAAoB,OAAO;AAC3D,wBAAgB,KAAK,MAAM,OAAO;AAElC,mBAAW,UAAU,IAAI,SAAS;AAChC,cAAI,OAAO,eAAe,GAAG;AAC3B,mBAAO,KAAK,KAAK,UAAU,EAAE,MAAM,UAAU,QAAQ,cAAc,CAAC,CAAC;AAAA,UACvE;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF,CAAC;AAAA,EACH,QAAQ;AACN,YAAQ,KAAK,yCAAyC;AAAA,EACxD;AAEA,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,WAAO,OAAO,MAAM,MAAM;AACxB,YAAM,MAAM,oBAAoB,IAAI;AACpC,cAAQ,IAAI;AAAA,yBAA4B,GAAG;AAAA,CAAI;AAC/C,cAAQ;AAAA,QACN;AAAA,QACA,OAAO,MAAM;AACX,mBAAS,MAAM;AACf,cAAI,MAAM;AACV,iBAAO,MAAM;AAAA,QACf;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH,CAAC;AACH;","names":["path","fs","http"]}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Start the dashboard server.
3
+ * Serves a static SPA and provides a WebSocket for live report updates.
4
+ */
5
+ declare function startDashboard(reportPath: string, port?: number): Promise<{
6
+ url: string;
7
+ close: () => void;
8
+ }>;
9
+
10
+ export { startDashboard };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Start the dashboard server.
3
+ * Serves a static SPA and provides a WebSocket for live report updates.
4
+ */
5
+ declare function startDashboard(reportPath: string, port?: number): Promise<{
6
+ url: string;
7
+ close: () => void;
8
+ }>;
9
+
10
+ export { startDashboard };
package/dist/index.mjs ADDED
@@ -0,0 +1,86 @@
1
+ // src/server.ts
2
+ import http from "http";
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import { WebSocketServer } from "ws";
6
+ var MIME_TYPES = {
7
+ ".html": "text/html",
8
+ ".js": "application/javascript",
9
+ ".css": "text/css",
10
+ ".json": "application/json",
11
+ ".svg": "image/svg+xml"
12
+ };
13
+ async function startDashboard(reportPath, port = 4173) {
14
+ const absoluteReportPath = path.resolve(reportPath);
15
+ let currentReport = null;
16
+ try {
17
+ const content = fs.readFileSync(absoluteReportPath, "utf-8");
18
+ currentReport = JSON.parse(content);
19
+ } catch {
20
+ console.warn(`Could not read report at ${absoluteReportPath}`);
21
+ }
22
+ const staticDir = path.resolve(
23
+ path.dirname(new URL(import.meta.url).pathname),
24
+ "app"
25
+ );
26
+ const server = http.createServer((req, res) => {
27
+ const url = req.url ?? "/";
28
+ if (url === "/api/report") {
29
+ res.writeHead(200, { "Content-Type": "application/json" });
30
+ res.end(JSON.stringify(currentReport ?? { error: "No report loaded" }));
31
+ return;
32
+ }
33
+ let filePath = path.join(staticDir, url === "/" ? "index.html" : url);
34
+ const ext = path.extname(filePath);
35
+ if (!ext) {
36
+ filePath = path.join(staticDir, "index.html");
37
+ }
38
+ try {
39
+ const content = fs.readFileSync(filePath);
40
+ const mime = MIME_TYPES[path.extname(filePath)] ?? "text/plain";
41
+ res.writeHead(200, { "Content-Type": mime });
42
+ res.end(content);
43
+ } catch {
44
+ res.writeHead(404);
45
+ res.end("Not found");
46
+ }
47
+ });
48
+ const wss = new WebSocketServer({ server });
49
+ let watcher = null;
50
+ try {
51
+ watcher = fs.watch(absoluteReportPath, () => {
52
+ try {
53
+ const content = fs.readFileSync(absoluteReportPath, "utf-8");
54
+ currentReport = JSON.parse(content);
55
+ for (const client of wss.clients) {
56
+ if (client.readyState === 1) {
57
+ client.send(JSON.stringify({ type: "update", report: currentReport }));
58
+ }
59
+ }
60
+ } catch {
61
+ }
62
+ });
63
+ } catch {
64
+ console.warn("Could not watch report file for changes");
65
+ }
66
+ return new Promise((resolve) => {
67
+ server.listen(port, () => {
68
+ const url = `http://localhost:${port}`;
69
+ console.log(`
70
+ Dashboard running at ${url}
71
+ `);
72
+ resolve({
73
+ url,
74
+ close: () => {
75
+ watcher?.close();
76
+ wss.close();
77
+ server.close();
78
+ }
79
+ });
80
+ });
81
+ });
82
+ }
83
+ export {
84
+ startDashboard
85
+ };
86
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts"],"sourcesContent":["import http from 'node:http';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { WebSocketServer } from 'ws';\nimport type { AnalysisReport } from '@hydration-audit/core';\n\nconst MIME_TYPES: Record<string, string> = {\n '.html': 'text/html',\n '.js': 'application/javascript',\n '.css': 'text/css',\n '.json': 'application/json',\n '.svg': 'image/svg+xml',\n};\n\n/**\n * Start the dashboard server.\n * Serves a static SPA and provides a WebSocket for live report updates.\n */\nexport async function startDashboard(\n reportPath: string,\n port = 4173,\n): Promise<{ url: string; close: () => void }> {\n const absoluteReportPath = path.resolve(reportPath);\n\n // Read the initial report\n let currentReport: AnalysisReport | null = null;\n try {\n const content = fs.readFileSync(absoluteReportPath, 'utf-8');\n currentReport = JSON.parse(content);\n } catch {\n console.warn(`Could not read report at ${absoluteReportPath}`);\n }\n\n const staticDir = path.resolve(\n path.dirname(new URL(import.meta.url).pathname),\n 'app',\n );\n\n const server = http.createServer((req, res) => {\n const url = req.url ?? '/';\n\n // API endpoint for report data\n if (url === '/api/report') {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(currentReport ?? { error: 'No report loaded' }));\n return;\n }\n\n // Serve static files\n let filePath = path.join(staticDir, url === '/' ? 'index.html' : url);\n const ext = path.extname(filePath);\n\n // SPA fallback — serve index.html for non-file routes\n if (!ext) {\n filePath = path.join(staticDir, 'index.html');\n }\n\n try {\n const content = fs.readFileSync(filePath);\n const mime = MIME_TYPES[path.extname(filePath)] ?? 'text/plain';\n res.writeHead(200, { 'Content-Type': mime });\n res.end(content);\n } catch {\n res.writeHead(404);\n res.end('Not found');\n }\n });\n\n // WebSocket for live reload\n const wss = new WebSocketServer({ server });\n\n // Watch report file for changes\n let watcher: fs.FSWatcher | null = null;\n try {\n watcher = fs.watch(absoluteReportPath, () => {\n try {\n const content = fs.readFileSync(absoluteReportPath, 'utf-8');\n currentReport = JSON.parse(content);\n // Notify all connected clients\n for (const client of wss.clients) {\n if (client.readyState === 1) {\n client.send(JSON.stringify({ type: 'update', report: currentReport }));\n }\n }\n } catch {\n // Ignore parse errors during write\n }\n });\n } catch {\n console.warn('Could not watch report file for changes');\n }\n\n return new Promise((resolve) => {\n server.listen(port, () => {\n const url = `http://localhost:${port}`;\n console.log(`\\n Dashboard running at ${url}\\n`);\n resolve({\n url,\n close: () => {\n watcher?.close();\n wss.close();\n server.close();\n },\n });\n });\n });\n}\n"],"mappings":";AAAA,OAAO,UAAU;AACjB,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,uBAAuB;AAGhC,IAAM,aAAqC;AAAA,EACzC,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AACV;AAMA,eAAsB,eACpB,YACA,OAAO,MACsC;AAC7C,QAAM,qBAAqB,KAAK,QAAQ,UAAU;AAGlD,MAAI,gBAAuC;AAC3C,MAAI;AACF,UAAM,UAAU,GAAG,aAAa,oBAAoB,OAAO;AAC3D,oBAAgB,KAAK,MAAM,OAAO;AAAA,EACpC,QAAQ;AACN,YAAQ,KAAK,4BAA4B,kBAAkB,EAAE;AAAA,EAC/D;AAEA,QAAM,YAAY,KAAK;AAAA,IACrB,KAAK,QAAQ,IAAI,IAAI,YAAY,GAAG,EAAE,QAAQ;AAAA,IAC9C;AAAA,EACF;AAEA,QAAM,SAAS,KAAK,aAAa,CAAC,KAAK,QAAQ;AAC7C,UAAM,MAAM,IAAI,OAAO;AAGvB,QAAI,QAAQ,eAAe;AACzB,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,iBAAiB,EAAE,OAAO,mBAAmB,CAAC,CAAC;AACtE;AAAA,IACF;AAGA,QAAI,WAAW,KAAK,KAAK,WAAW,QAAQ,MAAM,eAAe,GAAG;AACpE,UAAM,MAAM,KAAK,QAAQ,QAAQ;AAGjC,QAAI,CAAC,KAAK;AACR,iBAAW,KAAK,KAAK,WAAW,YAAY;AAAA,IAC9C;AAEA,QAAI;AACF,YAAM,UAAU,GAAG,aAAa,QAAQ;AACxC,YAAM,OAAO,WAAW,KAAK,QAAQ,QAAQ,CAAC,KAAK;AACnD,UAAI,UAAU,KAAK,EAAE,gBAAgB,KAAK,CAAC;AAC3C,UAAI,IAAI,OAAO;AAAA,IACjB,QAAQ;AACN,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI,WAAW;AAAA,IACrB;AAAA,EACF,CAAC;AAGD,QAAM,MAAM,IAAI,gBAAgB,EAAE,OAAO,CAAC;AAG1C,MAAI,UAA+B;AACnC,MAAI;AACF,cAAU,GAAG,MAAM,oBAAoB,MAAM;AAC3C,UAAI;AACF,cAAM,UAAU,GAAG,aAAa,oBAAoB,OAAO;AAC3D,wBAAgB,KAAK,MAAM,OAAO;AAElC,mBAAW,UAAU,IAAI,SAAS;AAChC,cAAI,OAAO,eAAe,GAAG;AAC3B,mBAAO,KAAK,KAAK,UAAU,EAAE,MAAM,UAAU,QAAQ,cAAc,CAAC,CAAC;AAAA,UACvE;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF,CAAC;AAAA,EACH,QAAQ;AACN,YAAQ,KAAK,yCAAyC;AAAA,EACxD;AAEA,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,WAAO,OAAO,MAAM,MAAM;AACxB,YAAM,MAAM,oBAAoB,IAAI;AACpC,cAAQ,IAAI;AAAA,yBAA4B,GAAG;AAAA,CAAI;AAC/C,cAAQ;AAAA,QACN;AAAA,QACA,OAAO,MAAM;AACX,mBAAS,MAAM;AACf,cAAI,MAAM;AACV,iBAAO,MAAM;AAAA,QACf;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH,CAAC;AACH;","names":[]}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@hydration-audit/dashboard",
3
+ "version": "0.2.0",
4
+ "description": "Web dashboard for visualizing JavaScript hydration costs",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.mjs",
10
+ "require": "./dist/index.cjs"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "license": "MIT",
17
+ "engines": {
18
+ "node": ">=18"
19
+ },
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "dev": "vite",
23
+ "test": "vitest run",
24
+ "typecheck": "tsc --noEmit",
25
+ "clean": "rm -rf dist"
26
+ },
27
+ "dependencies": {
28
+ "@hydration-audit/core": "workspace:*",
29
+ "preact": "^10.24.0",
30
+ "d3-hierarchy": "^3.1.2",
31
+ "ws": "^8.18.0"
32
+ },
33
+ "devDependencies": {
34
+ "@preact/preset-vite": "^2.9.0",
35
+ "@types/node": "^22.0.0",
36
+ "@types/ws": "^8.5.0",
37
+ "vite": "^6.0.0",
38
+ "tsup": "^8.3.0",
39
+ "typescript": "^5.7.0",
40
+ "vitest": "^2.1.0"
41
+ }
42
+ }