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.
- package/backend/dist/db.js +33 -11
- package/backend/dist/index.js +29 -3
- package/backend/dist/routes/reports.js +106 -55
- package/backend/public/assets/index-BpODc0fS.css +1 -0
- package/backend/public/assets/index-zKyZPsv1.js +118 -0
- package/backend/public/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/backend/public/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/backend/public/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
- package/backend/public/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
- package/backend/public/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/backend/public/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/backend/public/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
- package/backend/public/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
- package/backend/public/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/backend/public/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/backend/public/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/backend/public/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- package/backend/public/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/backend/public/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/backend/public/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
- package/backend/public/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
- package/backend/public/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/backend/public/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
- package/backend/public/assets/tajawal-arabic-300-normal-Bq0yWa0Z.woff +0 -0
- package/backend/public/assets/tajawal-arabic-300-normal-By07C9pa.woff2 +0 -0
- package/backend/public/assets/tajawal-arabic-400-normal-CyCXRvzh.woff2 +0 -0
- package/backend/public/assets/tajawal-arabic-400-normal-DCQxawbB.woff +0 -0
- package/backend/public/assets/tajawal-arabic-500-normal-BZ8ojJNu.woff2 +0 -0
- package/backend/public/assets/tajawal-arabic-500-normal-CbVEaYEW.woff +0 -0
- package/backend/public/assets/tajawal-arabic-700-normal-9L7Zusdl.woff +0 -0
- package/backend/public/assets/tajawal-arabic-700-normal-D2-eand5.woff2 +0 -0
- package/backend/public/assets/tajawal-latin-300-normal-C0-xR3ms.woff +0 -0
- package/backend/public/assets/tajawal-latin-300-normal-CeEKeOxZ.woff2 +0 -0
- package/backend/public/assets/tajawal-latin-400-normal-BVNSOH3d.woff2 +0 -0
- package/backend/public/assets/tajawal-latin-400-normal-BdYcZznU.woff +0 -0
- package/backend/public/assets/tajawal-latin-500-normal-CoYeBiSI.woff2 +0 -0
- package/backend/public/assets/tajawal-latin-500-normal-DU9v6xgj.woff +0 -0
- package/backend/public/assets/tajawal-latin-700-normal-BypgxfGb.woff2 +0 -0
- package/backend/public/assets/tajawal-latin-700-normal-CV3bxpHe.woff +0 -0
- package/backend/public/favicon.svg +1 -0
- package/backend/public/icons.svg +24 -0
- package/backend/public/index.html +254 -0
- package/backend/src/db.ts +42 -18
- package/backend/src/index.ts +31 -3
- package/backend/src/routes/reports.ts +141 -52
- package/cli/dist/commands/full.js +82 -48
- package/cli/src/commands/full.ts +161 -115
- package/dashboard/index.html +253 -0
- package/dashboard/package-lock.json +913 -0
- package/dashboard/package.json +19 -0
- package/dashboard/public/favicon.svg +1 -0
- package/dashboard/public/icons.svg +24 -0
- package/dashboard/src/api.js +107 -0
- package/dashboard/src/assets/hero.png +0 -0
- package/dashboard/src/assets/javascript.svg +1 -0
- package/dashboard/src/assets/vite.svg +1 -0
- package/dashboard/src/css/main.css +441 -0
- package/dashboard/src/data.js +202 -0
- package/dashboard/src/main.js +53 -0
- package/dashboard/src/pages.js +797 -0
- package/dashboard/src/router.js +77 -0
- package/dashboard/src/utils.js +163 -0
- package/dashboard/vite.config.js +19 -0
- package/data/screenshots/5--fcp.png +0 -0
- package/data/screenshots/5--fullLoad.png +0 -0
- package/data/screenshots/5-docs-fullLoad.png +0 -0
- package/data/screenshots/5-white-fullLoad.png +0 -0
- package/data/screenshots/6--fcp.png +0 -0
- package/data/screenshots/6--fullLoad.png +0 -0
- package/data/screenshots/6-docs-fullLoad.png +0 -0
- package/data/screenshots/6-white-fullLoad.png +0 -0
- package/package.json +4 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// js/data.js — static mock data (mirrors a real finalreport.json)
|
|
3
|
+
// When you wire up the backend, replace REPORT_DATA with the
|
|
4
|
+
// response from GET /api/reports/:id and HISTORY_DATA from GET /api/reports
|
|
5
|
+
// ─────────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
export const REPORT_DATA = {
|
|
8
|
+
projectName: "my-react-app",
|
|
9
|
+
analyzedAt: "2025-03-29T09:24:25.438Z",
|
|
10
|
+
performanceScore: 62,
|
|
11
|
+
|
|
12
|
+
static: {
|
|
13
|
+
timestamp: "2025-03-29T09:24:20.000Z",
|
|
14
|
+
grade: "C",
|
|
15
|
+
componentCount: 14,
|
|
16
|
+
filesAnalyzed: 14,
|
|
17
|
+
filesFailed: 0,
|
|
18
|
+
issues: [
|
|
19
|
+
{ id: "console-log-1", component: "App", file: "src/App.jsx", line: 12, severity: "warning", message: "console.log() left in production code", suggestion: "Remove or replace with a proper logging library" },
|
|
20
|
+
{ id: "console-log-2", component: "UserList", file: "src/components/UserList.jsx", line: 8, severity: "warning", message: "console.log() left in production code", suggestion: "Remove or replace with a proper logging library" },
|
|
21
|
+
{ id: "large-comp-1", component: "Dashboard", file: "src/pages/Dashboard.jsx", line: 1, severity: "critical", message: "Component is 487 lines — too large to maintain or optimize", suggestion: "Split into smaller focused components" },
|
|
22
|
+
{ id: "inline-fn-1", component: "UserCard", file: "src/components/UserCard.jsx", line: 34, severity: "warning", message: "Inline arrow function passed as prop causes re-renders", suggestion: "Extract to a named callback with useCallback" },
|
|
23
|
+
{ id: "inline-fn-2", component: "FilterBar", file: "src/components/FilterBar.jsx",line: 22, severity: "warning", message: "Inline arrow function passed as prop causes re-renders", suggestion: "Extract to a named callback with useCallback" },
|
|
24
|
+
{ id: "inline-style-1", component: "Header", file: "src/components/Header.jsx", line: 18, severity: "info", message: "Inline style object recreated on every render", suggestion: "Move to a CSS class or useMemo" },
|
|
25
|
+
{ id: "missing-key-1", component: "UserList", file: "src/components/UserList.jsx", line: 41, severity: "critical", message: "List items rendered without unique key prop", suggestion: "Add a stable unique key prop to each list element" },
|
|
26
|
+
{ id: "missing-memo-1", component: "UserCard", file: "src/components/UserCard.jsx", line: 1, severity: "warning", message: "Component re-renders on every parent update (not memoized)", suggestion: "Wrap with React.memo() to prevent unnecessary re-renders" },
|
|
27
|
+
{ id: "missing-memo-2", component: "Sidebar", file: "src/components/Sidebar.jsx", line: 1, severity: "warning", message: "Component re-renders on every parent update (not memoized)", suggestion: "Wrap with React.memo() to prevent unnecessary re-renders" },
|
|
28
|
+
{ id: "effect-loop-1", component: "DataFetcher", file: "src/components/DataFetcher.jsx",line:19, severity: "critical", message: "useEffect dependency array contains an object — causes infinite loop", suggestion: "Wrap the object in useMemo or extract primitive deps" },
|
|
29
|
+
{ id: "dead-code-1", component: "OldWidget", file: "src/components/OldWidget.jsx", line: 1, severity: "info", message: "Component is defined but never imported", suggestion: "Delete the file or add it to the component tree" },
|
|
30
|
+
{ id: "prop-drilling-1", component: "App", file: "src/App.jsx", line: 44, severity: "info", message: "Prop 'userId' passed through 4 component levels", suggestion: "Move to React Context or a state manager like Zustand" },
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
runtime: {
|
|
35
|
+
"/::desktop": {
|
|
36
|
+
url: "http://localhost:5173/",
|
|
37
|
+
deviceType: "desktop",
|
|
38
|
+
timestamp: "2025-03-29T09:24:22.000Z",
|
|
39
|
+
performanceScore: 71,
|
|
40
|
+
cpuThrottling: 1,
|
|
41
|
+
networkThrottle: "No throttle",
|
|
42
|
+
metrics: { lcp: 2180, fcp: 820, cls: 0.04, inp: 95, ttfb: 310 },
|
|
43
|
+
rerenders: { App: 3, Dashboard: 8, UserCard: 22, Sidebar: 3, Header: 2 },
|
|
44
|
+
commitDurations: [12.4, 8.2, 34.1, 6.8, 15.3, 9.1, 42.7],
|
|
45
|
+
renderTime: 1240,
|
|
46
|
+
stats: { domNodes: 1420, jsHeapMB: "38.4", payloadMB: "2.1", topOffender: { name: "lodash.js", size: 68400 } },
|
|
47
|
+
errors: [
|
|
48
|
+
{ type: "warning", message: "Each child in a list should have a unique 'key' prop.", source: "console" }
|
|
49
|
+
],
|
|
50
|
+
screenshots: [
|
|
51
|
+
{ label: "fcp", dataUrl: "/screenshots/1-desktop-fcp.png", takenAt: 820 },
|
|
52
|
+
{ label: "fullLoad", dataUrl: "/screenshots/1-desktop-fullLoad.png", takenAt: 2800 }
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"/::mobile": {
|
|
56
|
+
url: "http://localhost:5173/",
|
|
57
|
+
deviceType: "mobile",
|
|
58
|
+
timestamp: "2025-03-29T09:24:23.500Z",
|
|
59
|
+
performanceScore: 53,
|
|
60
|
+
cpuThrottling: 4,
|
|
61
|
+
networkThrottle: "Slow 3G",
|
|
62
|
+
metrics: { lcp: 4150, fcp: 1620, cls: 0.09, inp: 245, ttfb: 680 },
|
|
63
|
+
rerenders: { App: 3, Dashboard: 8, UserCard: 22, Sidebar: 3, Header: 2 },
|
|
64
|
+
commitDurations: [22.1, 18.4, 67.3, 14.5, 28.7],
|
|
65
|
+
renderTime: 2180,
|
|
66
|
+
stats: { domNodes: 1420, jsHeapMB: "38.4", payloadMB: "2.1", topOffender: { name: "lodash.js", size: 68400 } },
|
|
67
|
+
errors: [
|
|
68
|
+
{ type: "warning", message: "Each child in a list should have a unique 'key' prop.", source: "console" },
|
|
69
|
+
{ type: "error", message: "Cannot read properties of undefined (reading 'map')", source: "pageerror" }
|
|
70
|
+
],
|
|
71
|
+
screenshots: [
|
|
72
|
+
{ label: "fcp", dataUrl: "/screenshots/1-mobile-fcp.png", takenAt: 1620 },
|
|
73
|
+
{ label: "fullLoad", dataUrl: "/screenshots/1-mobile-fullLoad.png", takenAt: 4800 }
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
"/about::desktop": {
|
|
77
|
+
url: "http://localhost:5173/about",
|
|
78
|
+
deviceType: "desktop",
|
|
79
|
+
timestamp: "2025-03-29T09:24:24.200Z",
|
|
80
|
+
performanceScore: 91,
|
|
81
|
+
cpuThrottling: 1,
|
|
82
|
+
networkThrottle: "No throttle",
|
|
83
|
+
metrics: { lcp: 960, fcp: 420, cls: 0.01, inp: 48, ttfb: 210 },
|
|
84
|
+
rerenders: { App: 1, Header: 1, Footer: 1 },
|
|
85
|
+
commitDurations: [4.2, 3.8, 5.1],
|
|
86
|
+
renderTime: 380,
|
|
87
|
+
stats: { domNodes: 320, jsHeapMB: "12.1", payloadMB: "0.8", topOffender: null },
|
|
88
|
+
errors: [],
|
|
89
|
+
screenshots: [
|
|
90
|
+
{ label: "fcp", dataUrl: "/screenshots/1-about-fcp.png", takenAt: 420 },
|
|
91
|
+
{ label: "fullLoad", dataUrl: "/screenshots/1-about-fullLoad.png", takenAt: 1100 }
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
suggestions: [
|
|
97
|
+
{
|
|
98
|
+
id: "missing-list-keys",
|
|
99
|
+
title: "Add key props to list items",
|
|
100
|
+
description: "React uses key props to efficiently reconcile list updates. Without keys, React re-renders every list item on every change, causing severe performance degradation for long lists.",
|
|
101
|
+
severity: "critical",
|
|
102
|
+
affectedComponent: "UserList",
|
|
103
|
+
fix: "Add key={item.id} to each list element in UserList.jsx line 41"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: "effect-infinite-loop",
|
|
107
|
+
title: "useEffect dependency causes infinite loop",
|
|
108
|
+
description: "An object in the useEffect dependency array is recreated on every render, causing the effect to run endlessly and hammering your API.",
|
|
109
|
+
severity: "critical",
|
|
110
|
+
affectedComponent: "DataFetcher",
|
|
111
|
+
fix: "Wrap the dep object in useMemo, or extract primitive values as deps"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: "large-component",
|
|
115
|
+
title: "Split oversized Dashboard component",
|
|
116
|
+
description: "The Dashboard component is 487 lines — far beyond the recommended 150-200 line limit. Large components are harder to memoize, test, and tree-shake.",
|
|
117
|
+
severity: "critical",
|
|
118
|
+
affectedComponent: "Dashboard",
|
|
119
|
+
fix: "Extract into DashboardHeader, DashboardMetrics, and DashboardTable sub-components"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: "slow-lcp-mobile",
|
|
123
|
+
title: "LCP too high on mobile (4.15s)",
|
|
124
|
+
description: "Largest Contentful Paint on mobile is 4.15s — well above the 2.5s target. The main culprit is likely an unoptimised hero image or render-blocking resource.",
|
|
125
|
+
severity: "critical",
|
|
126
|
+
affectedComponent: null,
|
|
127
|
+
fix: "Use lazy loading for hero images, preload critical assets, and check for render-blocking scripts"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: "inline-callbacks",
|
|
131
|
+
title: "Extract inline callback props",
|
|
132
|
+
description: "Inline arrow functions as props create a new function reference every render, causing child components that use React.memo to re-render unnecessarily.",
|
|
133
|
+
severity: "warning",
|
|
134
|
+
affectedComponent: "UserCard",
|
|
135
|
+
fix: "Extract to useCallback(fn, [deps]) in the parent component"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: "missing-memo",
|
|
139
|
+
title: "Memoize pure components",
|
|
140
|
+
description: "UserCard and Sidebar re-render on every parent update even when their props haven't changed. Wrapping them with React.memo will prevent this.",
|
|
141
|
+
severity: "warning",
|
|
142
|
+
affectedComponent: "UserCard, Sidebar",
|
|
143
|
+
fix: "export default React.memo(UserCard)"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: "console-logs",
|
|
147
|
+
title: "Remove console.log statements",
|
|
148
|
+
description: "console.log calls in App.jsx and UserList.jsx will ship to production, slowing serialisation and leaking internal data to users' browsers.",
|
|
149
|
+
severity: "warning",
|
|
150
|
+
affectedComponent: "App, UserList",
|
|
151
|
+
fix: "Delete console.log calls or replace with a conditional logger"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
id: "high-inp-mobile",
|
|
155
|
+
title: "INP too high on mobile (245ms)",
|
|
156
|
+
description: "Interaction to Next Paint on mobile is 245ms, above the 200ms target. Long tasks are blocking the main thread after user interactions.",
|
|
157
|
+
severity: "warning",
|
|
158
|
+
affectedComponent: null,
|
|
159
|
+
fix: "Break long tasks with scheduler.postTask or setTimeout(0), defer non-critical work"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: "excessive-rerenders",
|
|
163
|
+
title: "Dashboard component re-renders 8 times",
|
|
164
|
+
description: "React Profiler captured 8 commits on Dashboard during a single page load. This suggests state or context updates are cascading unnecessarily.",
|
|
165
|
+
severity: "warning",
|
|
166
|
+
affectedComponent: "Dashboard",
|
|
167
|
+
fix: "Use React DevTools Profiler to identify which state change triggers each commit"
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: "inline-styles",
|
|
171
|
+
title: "Avoid inline style objects",
|
|
172
|
+
description: "Inline style objects in Header are recreated on every render. While minor, at scale these small allocations add up.",
|
|
173
|
+
severity: "info",
|
|
174
|
+
affectedComponent: "Header",
|
|
175
|
+
fix: "Move static styles to a CSS class; dynamic styles to useMemo"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: "prop-drilling",
|
|
179
|
+
title: "Reduce prop drilling depth",
|
|
180
|
+
description: "userId is passed through 4 levels of components. This makes refactoring painful and means every intermediate component re-renders when userId changes.",
|
|
181
|
+
severity: "info",
|
|
182
|
+
affectedComponent: "App",
|
|
183
|
+
fix: "Create a UserContext and consume it with useContext where needed"
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// History list — mirrors GET /api/reports
|
|
189
|
+
export const HISTORY_DATA = [
|
|
190
|
+
{ id: 1, project: "my-react-app", score: 62, grade: "C", analyzed_at: "2025-03-29T09:24:25.438Z", created_at: "2025-03-29T09:24:26.000Z" },
|
|
191
|
+
{ id: 2, project: "my-react-app", score: 71, grade: "C+", analyzed_at: "2025-03-27T14:11:00.000Z", created_at: "2025-03-27T14:11:01.000Z" },
|
|
192
|
+
{ id: 3, project: "my-react-app", score: 55, grade: "D", analyzed_at: "2025-03-25T08:45:00.000Z", created_at: "2025-03-25T08:45:01.000Z" },
|
|
193
|
+
{ id: 4, project: "portfolio-site", score: 94, grade: "A", analyzed_at: "2025-03-24T19:30:00.000Z", created_at: "2025-03-24T19:30:01.000Z" },
|
|
194
|
+
{ id: 5, project: "portfolio-site", score: 88, grade: "B+", analyzed_at: "2025-03-22T11:00:00.000Z", created_at: "2025-03-22T11:00:01.000Z" },
|
|
195
|
+
{ id: 6, project: "admin-panel", score: 48, grade: "D", analyzed_at: "2025-03-21T16:20:00.000Z", created_at: "2025-03-21T16:20:01.000Z" },
|
|
196
|
+
{ id: 7, project: "admin-panel", score: 37, grade: "F", analyzed_at: "2025-03-19T10:05:00.000Z", created_at: "2025-03-19T10:05:01.000Z" },
|
|
197
|
+
{ id: 8, project: "my-react-app", score: 44, grade: "D", analyzed_at: "2025-03-17T08:00:00.000Z", created_at: "2025-03-17T08:00:01.000Z" },
|
|
198
|
+
{ id: 9, project: "landing-page", score: 97, grade: "A+", analyzed_at: "2025-03-16T13:45:00.000Z", created_at: "2025-03-16T13:45:01.000Z" },
|
|
199
|
+
{ id: 10, project: "landing-page", score: 92, grade: "A", analyzed_at: "2025-03-14T09:20:00.000Z", created_at: "2025-03-14T09:20:01.000Z" },
|
|
200
|
+
{ id: 11, project: "ecommerce-app", score: 58, grade: "D+", analyzed_at: "2025-03-13T15:30:00.000Z", created_at: "2025-03-13T15:30:01.000Z" },
|
|
201
|
+
{ id: 12, project: "ecommerce-app", score: 63, grade: "C", analyzed_at: "2025-03-11T11:10:00.000Z", created_at: "2025-03-11T11:10:01.000Z" },
|
|
202
|
+
];
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Chart, registerables } from 'chart.js';
|
|
2
|
+
Chart.register(...registerables);
|
|
3
|
+
|
|
4
|
+
// Fonts
|
|
5
|
+
import '@fontsource/tajawal/300.css';
|
|
6
|
+
import '@fontsource/tajawal/400.css';
|
|
7
|
+
import '@fontsource/tajawal/500.css';
|
|
8
|
+
import '@fontsource/tajawal/700.css';
|
|
9
|
+
import '@fontsource/jetbrains-mono/400.css';
|
|
10
|
+
import '@fontsource/jetbrains-mono/500.css';
|
|
11
|
+
|
|
12
|
+
import './css/main.css';
|
|
13
|
+
import { REPORT_DATA, HISTORY_DATA } from './data.js';
|
|
14
|
+
import api from './api.js';
|
|
15
|
+
import { initRouter } from './router.js';
|
|
16
|
+
import { formatDate } from './utils.js';
|
|
17
|
+
|
|
18
|
+
// ── Update sidebar with live data ────────────────────────────
|
|
19
|
+
async function updateSidebar() {
|
|
20
|
+
try {
|
|
21
|
+
const report = await api.getLatestReport();
|
|
22
|
+
document.getElementById('nav-score').textContent = report.performanceScore;
|
|
23
|
+
document.getElementById('nav-issues-count').textContent = report.static.issues.length;
|
|
24
|
+
document.getElementById('nav-sug-count').textContent = report.suggestions.length;
|
|
25
|
+
document.getElementById('sidebar-project').textContent = report.project;
|
|
26
|
+
document.getElementById('sidebar-date').textContent = formatDate(report.analyzedAt);
|
|
27
|
+
|
|
28
|
+
// Also update history count
|
|
29
|
+
const { reports } = await api.listReports();
|
|
30
|
+
document.getElementById('nav-hist-count').textContent = reports.length;
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.warn('⚠️ Using static fallback data for sidebar:', err.message);
|
|
33
|
+
// Fallback to static data
|
|
34
|
+
document.getElementById('nav-score').textContent = REPORT_DATA.performanceScore;
|
|
35
|
+
document.getElementById('nav-issues-count').textContent = REPORT_DATA.static.issues.length;
|
|
36
|
+
document.getElementById('nav-sug-count').textContent = REPORT_DATA.suggestions.length;
|
|
37
|
+
document.getElementById('nav-hist-count').textContent = HISTORY_DATA.length;
|
|
38
|
+
document.getElementById('sidebar-project').textContent = REPORT_DATA.projectName;
|
|
39
|
+
document.getElementById('sidebar-date').textContent = formatDate(REPORT_DATA.analyzedAt);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ── Clock ─────────────────────────────────────────────────────
|
|
44
|
+
function tick() {
|
|
45
|
+
document.getElementById('topbar-time').textContent = new Date().toLocaleTimeString('en-GB');
|
|
46
|
+
}
|
|
47
|
+
tick();
|
|
48
|
+
setInterval(tick, 1000);
|
|
49
|
+
|
|
50
|
+
// ── Init ─────────────────────────────────────────────────────
|
|
51
|
+
updateSidebar().then(() => {
|
|
52
|
+
initRouter();
|
|
53
|
+
});
|