react-doctor-cli-dev 1.0.7 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/backend/dist/db.js +33 -11
  2. package/backend/dist/index.js +29 -3
  3. package/backend/dist/routes/reports.js +106 -55
  4. package/backend/public/assets/index-BpODc0fS.css +1 -0
  5. package/backend/public/assets/index-zKyZPsv1.js +118 -0
  6. package/backend/public/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  7. package/backend/public/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  8. package/backend/public/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
  9. package/backend/public/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
  10. package/backend/public/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  11. package/backend/public/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  12. package/backend/public/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
  13. package/backend/public/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
  14. package/backend/public/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  15. package/backend/public/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  16. package/backend/public/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  17. package/backend/public/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  18. package/backend/public/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  19. package/backend/public/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  20. package/backend/public/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
  21. package/backend/public/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
  22. package/backend/public/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  23. package/backend/public/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
  24. package/backend/public/assets/tajawal-arabic-300-normal-Bq0yWa0Z.woff +0 -0
  25. package/backend/public/assets/tajawal-arabic-300-normal-By07C9pa.woff2 +0 -0
  26. package/backend/public/assets/tajawal-arabic-400-normal-CyCXRvzh.woff2 +0 -0
  27. package/backend/public/assets/tajawal-arabic-400-normal-DCQxawbB.woff +0 -0
  28. package/backend/public/assets/tajawal-arabic-500-normal-BZ8ojJNu.woff2 +0 -0
  29. package/backend/public/assets/tajawal-arabic-500-normal-CbVEaYEW.woff +0 -0
  30. package/backend/public/assets/tajawal-arabic-700-normal-9L7Zusdl.woff +0 -0
  31. package/backend/public/assets/tajawal-arabic-700-normal-D2-eand5.woff2 +0 -0
  32. package/backend/public/assets/tajawal-latin-300-normal-C0-xR3ms.woff +0 -0
  33. package/backend/public/assets/tajawal-latin-300-normal-CeEKeOxZ.woff2 +0 -0
  34. package/backend/public/assets/tajawal-latin-400-normal-BVNSOH3d.woff2 +0 -0
  35. package/backend/public/assets/tajawal-latin-400-normal-BdYcZznU.woff +0 -0
  36. package/backend/public/assets/tajawal-latin-500-normal-CoYeBiSI.woff2 +0 -0
  37. package/backend/public/assets/tajawal-latin-500-normal-DU9v6xgj.woff +0 -0
  38. package/backend/public/assets/tajawal-latin-700-normal-BypgxfGb.woff2 +0 -0
  39. package/backend/public/assets/tajawal-latin-700-normal-CV3bxpHe.woff +0 -0
  40. package/backend/public/favicon.svg +1 -0
  41. package/backend/public/icons.svg +24 -0
  42. package/backend/public/index.html +254 -0
  43. package/backend/src/db.ts +42 -18
  44. package/backend/src/index.ts +31 -3
  45. package/backend/src/routes/reports.ts +141 -52
  46. package/cli/dist/commands/full.js +82 -48
  47. package/cli/src/commands/full.ts +161 -115
  48. package/dashboard/index.html +253 -0
  49. package/dashboard/package-lock.json +913 -0
  50. package/dashboard/package.json +19 -0
  51. package/dashboard/public/favicon.svg +1 -0
  52. package/dashboard/public/icons.svg +24 -0
  53. package/dashboard/src/api.js +107 -0
  54. package/dashboard/src/assets/hero.png +0 -0
  55. package/dashboard/src/assets/javascript.svg +1 -0
  56. package/dashboard/src/assets/vite.svg +1 -0
  57. package/dashboard/src/css/main.css +441 -0
  58. package/dashboard/src/data.js +202 -0
  59. package/dashboard/src/main.js +53 -0
  60. package/dashboard/src/pages.js +797 -0
  61. package/dashboard/src/router.js +77 -0
  62. package/dashboard/src/utils.js +163 -0
  63. package/dashboard/vite.config.js +19 -0
  64. package/data/screenshots/5--fcp.png +0 -0
  65. package/data/screenshots/5--fullLoad.png +0 -0
  66. package/data/screenshots/5-docs-fullLoad.png +0 -0
  67. package/data/screenshots/5-white-fullLoad.png +0 -0
  68. package/data/screenshots/6--fcp.png +0 -0
  69. package/data/screenshots/6--fullLoad.png +0 -0
  70. package/data/screenshots/6-docs-fullLoad.png +0 -0
  71. package/data/screenshots/6-white-fullLoad.png +0 -0
  72. package/package.json +4 -1
@@ -0,0 +1,77 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // js/router.js — hash-based SPA router
3
+ // Maps #hash → page element + init function
4
+ // ─────────────────────────────────────────────────────────────
5
+
6
+ import { initOverview, initVitals, initIssues, initSuggestions, initHistory } from './pages.js';
7
+ import { closeLightbox } from './utils.js';
8
+
9
+ const ROUTES = {
10
+ "overview": { id: "page-overview", init: initOverview },
11
+ "vitals": { id: "page-vitals", init: initVitals },
12
+ "issues": { id: "page-issues", init: initIssues },
13
+ "suggestions": { id: "page-suggestions", init: initSuggestions },
14
+ "history": { id: "page-history", init: initHistory },
15
+ };
16
+
17
+ let currentRoute = null;
18
+
19
+ async function navigate(hash) {
20
+ const route = ROUTES[hash] || ROUTES["overview"];
21
+ if (currentRoute === hash) return;
22
+ currentRoute = hash;
23
+
24
+ // Hide all pages
25
+ document.querySelectorAll(".page-content").forEach(el => el.classList.remove("active"));
26
+ // Show target
27
+ const page = document.getElementById(route.id);
28
+ if (page) page.classList.add("active");
29
+
30
+ // Update nav
31
+ document.querySelectorAll(".nav-item").forEach(el => {
32
+ el.classList.toggle("active", el.dataset.route === hash);
33
+ });
34
+
35
+ // Update topbar
36
+ const titles = {
37
+ overview: ["Overview", "Performance summary"],
38
+ vitals: ["Web Vitals", "Core Web Vitals per route & device"],
39
+ issues: ["Code Issues", "Static analysis results"],
40
+ suggestions: ["Suggestions", "Rule engine recommendations"],
41
+ history: ["History", "Previous analysis runs"],
42
+ };
43
+ const [title, sub] = titles[hash] || titles.overview;
44
+ document.getElementById("topbar-title").textContent = title;
45
+ document.getElementById("topbar-sub").textContent = sub;
46
+
47
+ // Run page init (await it!)
48
+ try {
49
+ await route.init();
50
+ } catch (err) {
51
+ console.error('Error initializing page:', err);
52
+ }
53
+ }
54
+
55
+ export function initRouter() {
56
+ // Nav clicks
57
+ document.querySelectorAll(".nav-item[data-route]").forEach(btn => {
58
+ btn.addEventListener("click", () => {
59
+ window.location.hash = btn.dataset.route;
60
+ });
61
+ });
62
+
63
+ // Hash change
64
+ window.addEventListener("hashchange", () => {
65
+ navigate(location.hash.replace("#", "") || "overview");
66
+ });
67
+
68
+ // Lightbox close
69
+ document.getElementById("lightbox").addEventListener("click", e => {
70
+ if (e.target === e.currentTarget) closeLightbox();
71
+ });
72
+ document.getElementById("lb-close").addEventListener("click", closeLightbox);
73
+ document.addEventListener("keydown", e => { if (e.key === "Escape") closeLightbox(); });
74
+
75
+ // Initial route
76
+ navigate(location.hash.replace("#", "") || "overview");
77
+ }
@@ -0,0 +1,163 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // js/utils.js — pure helper functions used across all pages
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ export function scoreColor(score) {
6
+ if (score >= 90) return "var(--green)";
7
+ if (score >= 75) return "var(--blue)";
8
+ if (score >= 50) return "var(--orange)";
9
+ return "var(--red)";
10
+ }
11
+
12
+ export function gradeBadgeClass(grade) {
13
+ const g = grade ? grade[0] : 'N';
14
+ if (g === "A") return "grade-a";
15
+ if (g === "B") return "grade-b";
16
+ if (g === "C") return "grade-c";
17
+ if (g === "D") return "grade-d";
18
+ return "grade-d";
19
+ }
20
+
21
+ export function severityIcon(severity) {
22
+ return { critical: "🔴", warning: "🟠", info: "🔵" }[severity] || "⚪";
23
+ }
24
+
25
+ export function severityLabel(sev) {
26
+ return `<span class="badge ${sev}">${severityIcon(sev)} ${sev}</span>`;
27
+ }
28
+
29
+ // Vital thresholds: [good_max, warn_max] (above warn_max = bad)
30
+ const VITAL_THRESHOLDS = {
31
+ lcp: [2500, 4000],
32
+ fcp: [1800, 3000],
33
+ ttfb: [800, 1800],
34
+ inp: [200, 500],
35
+ cls: [0.1, 0.25],
36
+ render: [2000, 4000], // ← Added Render Time threshold
37
+ };
38
+
39
+ export function vitalClass(metric, value) {
40
+ const [good, warn] = VITAL_THRESHOLDS[metric] || [Infinity, Infinity];
41
+ if (value <= good) return "good";
42
+ if (value <= warn) return "warn";
43
+ return "bad";
44
+ }
45
+
46
+ export function vitalColor(metric, value) {
47
+ const cls = vitalClass(metric, value);
48
+ return cls === "good" ? "var(--green)" : cls === "warn" ? "var(--orange)" : "var(--red)";
49
+ }
50
+
51
+ // Percentage fill for the bar — capped to 100%
52
+ export function vitalBarPct(metric, value) {
53
+ const limits = {
54
+ lcp: 5000,
55
+ fcp: 4000,
56
+ ttfb: 2500,
57
+ inp: 600,
58
+ cls: 0.35,
59
+ render: 6000 // ← Added Render Time limit
60
+ };
61
+ const max = limits[metric] || 100;
62
+ return Math.min((value / max) * 100, 100).toFixed(1);
63
+ }
64
+
65
+ export function formatMs(ms) {
66
+ if (ms === undefined || ms === null) return "–";
67
+ if (ms >= 1000) return (ms / 1000).toFixed(2) + "s";
68
+ return ms.toFixed(0) + "ms";
69
+ }
70
+
71
+ export function formatCls(v) {
72
+ return typeof v === "number" ? v.toFixed(3) : "–";
73
+ }
74
+
75
+ export function formatDate(iso) {
76
+ if (!iso) return "–";
77
+ const d = new Date(iso);
78
+ return d.toLocaleDateString("en-GB", { day: "2-digit", month: "short", year: "numeric" }) +
79
+ " " + d.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
80
+ }
81
+
82
+ export function relativeTime(iso) {
83
+ if (!iso) return "";
84
+ const diff = Date.now() - new Date(iso).getTime();
85
+ const m = Math.floor(diff / 60000);
86
+ if (m < 1) return "just now";
87
+ if (m < 60) return `${m}m ago`;
88
+ const h = Math.floor(m / 60);
89
+ if (h < 24) return `${h}h ago`;
90
+ return `${Math.floor(h / 24)}d ago`;
91
+ }
92
+
93
+ // ── Build the SVG score ring with VISIBLE text ──────────────
94
+ export function buildScoreRing(score, size = 140, strokeWidth = 10) {
95
+ const r = (size - strokeWidth) / 2;
96
+ const c = size / 2;
97
+ const circ = 2 * Math.PI * r;
98
+ const pct = Math.min(Math.max(score / 100, 0), 1);
99
+ const color = scoreColor(score);
100
+
101
+ // Map score to a color for the text
102
+ let textColor;
103
+ if (score >= 90) textColor = '#3FB950'; // green
104
+ else if (score >= 75) textColor = '#58A6FF'; // blue
105
+ else if (score >= 50) textColor = '#F0883E'; // orange
106
+ else textColor = '#FF7B72'; // red
107
+
108
+ return `
109
+ <div class="score-ring" style="width:${size}px;height:${size}px;position:relative;flex-shrink:0;">
110
+ <svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" style="transform:rotate(-90deg);display:block;">
111
+ <circle
112
+ cx="${c}" cy="${c}" r="${r}"
113
+ fill="none"
114
+ stroke="#1C2333"
115
+ stroke-width="${strokeWidth}" />
116
+ <circle
117
+ cx="${c}" cy="${c}" r="${r}"
118
+ fill="none"
119
+ stroke="${textColor}"
120
+ stroke-width="${strokeWidth}"
121
+ stroke-linecap="round"
122
+ stroke-dasharray="${circ}"
123
+ stroke-dashoffset="${circ * (1 - pct)}"
124
+ style="transition: stroke-dashoffset 1.4s cubic-bezier(.4,0,.2,1);" />
125
+ </svg>
126
+ <div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;pointer-events:none;width:100%;z-index:10;">
127
+ <div style="font-size:2.4rem;font-weight:700;font-family:'JetBrains Mono',monospace;line-height:1;color:${textColor};text-shadow:0 0 20px rgba(0,0,0,0.5);">${Math.round(score)}</div>
128
+ <div style="font-size:0.7rem;font-weight:600;color:#8B949E;margin-top:2px;">/ 100</div>
129
+ </div>
130
+ </div>
131
+ `;
132
+ }
133
+
134
+ // Vital bar row HTML
135
+ export function buildVitalRow(label, metric, value) {
136
+ const display = metric === "cls" ? formatCls(value) : formatMs(value);
137
+ const color = vitalColor(metric, value);
138
+ const pct = vitalBarPct(metric, value);
139
+ return `
140
+ <div class="vital-row">
141
+ <div class="vital-row-head">
142
+ <span class="vname">${label}</span>
143
+ <span class="vval" style="color:${color}">${display}</span>
144
+ </div>
145
+ <div class="vital-track">
146
+ <div class="vital-fill" style="width:${pct}%;background:${color}"></div>
147
+ </div>
148
+ </div>`;
149
+ }
150
+
151
+ // Lightbox
152
+ export function openLightbox(src, caption) {
153
+ const lb = document.getElementById("lightbox");
154
+ const img = lb.querySelector("img");
155
+ if (img) img.src = src;
156
+ const cap = document.getElementById("lb-caption");
157
+ if (cap) cap.textContent = caption || "";
158
+ lb.classList.add("open");
159
+ }
160
+
161
+ export function closeLightbox() {
162
+ document.getElementById("lightbox").classList.remove("open");
163
+ }
@@ -0,0 +1,19 @@
1
+ import { defineConfig } from 'vite';
2
+ import path from 'path';
3
+
4
+ export default defineConfig({
5
+ root: '.',
6
+ base: '/',
7
+ build: {
8
+ outDir: '../backend/public', // Express serves this as static
9
+ emptyOutDir: true,
10
+ },
11
+ server: {
12
+ port: 5173,
13
+ proxy: {
14
+ // Forward /api/* and /screenshots/* to the backend during dev
15
+ '/api': 'http://localhost:3000',
16
+ '/screenshots': 'http://localhost:3000',
17
+ },
18
+ },
19
+ });
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor-cli-dev",
3
- "version": "1.0.7",
3
+ "version": "1.0.12",
4
4
  "description": "React performance analyzer with static analysis, runtime profiling, rule engine, and dashboard upload",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
@@ -21,9 +21,12 @@
21
21
  "@babel/parser": "^7.29.0",
22
22
  "@babel/traverse": "^7.29.0",
23
23
  "@babel/types": "^7.29.0",
24
+ "@fontsource/jetbrains-mono": "^5.2.8",
25
+ "@fontsource/tajawal": "^5.2.7",
24
26
  "axios": "^1.6.0",
25
27
  "better-sqlite3": "^12.10.0",
26
28
  "chalk": "^4.1.2",
29
+ "chart.js": "^4.5.1",
27
30
  "commander": "^12.0.0",
28
31
  "cors": "^2.8.6",
29
32
  "dotenv": "^17.4.2",