diff --git a/diagnostic/build-0dde0a52-part001.logd b/diagnostic/build-0dde0a52-part001.logd new file mode 100644 index 00000000..374181be Binary files /dev/null and b/diagnostic/build-0dde0a52-part001.logd differ diff --git a/diagnostic/build-0dde0a52-part002.logd b/diagnostic/build-0dde0a52-part002.logd new file mode 100644 index 00000000..c46818e4 Binary files /dev/null and b/diagnostic/build-0dde0a52-part002.logd differ diff --git a/diagnostic/build-0dde0a52-part003.logd b/diagnostic/build-0dde0a52-part003.logd new file mode 100644 index 00000000..b2e38366 Binary files /dev/null and b/diagnostic/build-0dde0a52-part003.logd differ diff --git a/diagnostic/build-0dde0a52-part004.logd b/diagnostic/build-0dde0a52-part004.logd new file mode 100644 index 00000000..c014df45 Binary files /dev/null and b/diagnostic/build-0dde0a52-part004.logd differ diff --git a/diagnostic/build-0dde0a52-part005.logd b/diagnostic/build-0dde0a52-part005.logd new file mode 100644 index 00000000..d24ff205 Binary files /dev/null and b/diagnostic/build-0dde0a52-part005.logd differ diff --git a/diagnostic/build-0dde0a52-part006.logd b/diagnostic/build-0dde0a52-part006.logd new file mode 100644 index 00000000..0cf86932 Binary files /dev/null and b/diagnostic/build-0dde0a52-part006.logd differ diff --git a/diagnostic/build-0dde0a52.json b/diagnostic/build-0dde0a52.json new file mode 100644 index 00000000..1352ca76 --- /dev/null +++ b/diagnostic/build-0dde0a52.json @@ -0,0 +1,31 @@ +{ + "generated_at": "2026-06-27T01:04:00.344287+00:00", + "commit": "0dde0a52", + "diagnostic_logd": [ + "diagnostic\\build-0dde0a52-part001.logd", + "diagnostic\\build-0dde0a52-part002.logd", + "diagnostic\\build-0dde0a52-part003.logd", + "diagnostic\\build-0dde0a52-part004.logd", + "diagnostic\\build-0dde0a52-part005.logd", + "diagnostic\\build-0dde0a52-part006.logd" + ], + "diagnostic_logd_error": null, + "message_blocker": null, + "chunked": true, + "chunk_size_bytes": 41943040, + "password": "994378c16a1a357d6bdf", + "decrypt_command": "encryptly unpack diagnostic\\build-0dde0a52.logd --password 994378c16a1a357d6bdf", + "total_modules": 1, + "passed": 1, + "failed": 0, + "modules": [ + { + "name": "frontend", + "status": "PASS", + "elapsed_seconds": 8.478, + "artifact": "C:\\Users\\FLOR\\Documents\\Codex\\2026-06-26\\g\\work\\therapy-tentoftrials-submit\\frontend\\dist", + "output": "> tent-frontend@0.0.0 build\n> tsc -b && vite build\n\n\u001b[36mvite v6.4.3 \u001b[32mbuilding for production...\u001b[36m\u001b[39m\ntransforming...\n\u001b[32m\u2713\u001b[39m 102 modules transformed.\nrendering chunks...\ncomputing gzip size...\n\u001b[2mdist/\u001b[22m\u001b[32mindex.html \u001b[39m\u001b[1m\u001b[2m 0.71 kB\u001b[22m\u001b[1m\u001b[22m\u001b[2m \u2502 gzip: 0.38 kB\u001b[22m\n\u001b[2mdist/\u001b[22m\u001b[35massets/index-C4shL4Bc.css \u001b[39m\u001b[1m\u001b[2m 0.50 kB\u001b[22m\u001b[1m\u001b[22m\u001b[2m \u2502 gzip: 0.23 kB\u001b[22m\n\u001b[2mdist/\u001b[22m\u001b[36massets/state-BkjSKDbY.js \u001b[39m\u001b[1m\u001b[2m 8.91 kB\u001b[22m\u001b[1m\u001b[22m\u001b[2m \u2502 gzip: 3.54 kB\u001b[22m\u001b[2m \u2502 map: 57.15 kB\u001b[22m\n\u001b[2mdist/\u001b[22m\u001b[36massets/vendor-CREcWLHI.js \u001b[39m\u001b[1m\u001b[2m 48.93 kB\u001b[22m\u001b[1m\u001b[22m\u001b[2m \u2502 gzip: 17.25 kB\u001b[22m\u001b[2m \u2502 map: 481.27 kB\u001b[22m\n\u001b[2mdist/\u001b[22m\u001b[36massets/index-BHsknpR9.js \u001b[39m\u001b[1m\u001b[2m232.92 kB\u001b[22m\u001b[1m\u001b[22m\u001b[2m \u2502 gzip: 72.65 kB\u001b[22m\u001b[2m \u2502 map: 1,049.91 kB\u001b[22m\n\u001b[32m\u2713 built in 2.02s\u001b[39m" + } + ], + "pr_note": "Include the encrypted diagnostic logd artifact(s): diagnostic\\build-0dde0a52-part001.logd, diagnostic\\build-0dde0a52-part002.logd, diagnostic\\build-0dde0a52-part003.logd, diagnostic\\build-0dde0a52-part004.logd, diagnostic\\build-0dde0a52-part005.logd, diagnostic\\build-0dde0a52-part006.logd. The encrypted .logd is the required diagnostic content for PR review; this JSON file is metadata. Maintainers may ask you to remove these diagnostic artifacts before merging." +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5e3ad158..cbe4ef3d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Routes, Route, Navigate } from 'react-router-dom'; import Layout from './components/Layout'; +import ErrorBoundary from './components/ErrorBoundary'; import Dashboard from './pages/Dashboard'; import Analytics from './pages/Analytics'; import Settings from './pages/Settings'; @@ -9,7 +10,17 @@ const App: React.FC = () => { return ( - } /> + + + + } + /> } /> } /> } /> diff --git a/frontend/src/components/ErrorBoundary.css b/frontend/src/components/ErrorBoundary.css new file mode 100644 index 00000000..656eaa22 --- /dev/null +++ b/frontend/src/components/ErrorBoundary.css @@ -0,0 +1,37 @@ +.error-boundary-fallback { + background: #1e293b; + border: 1px solid #334155; + border-radius: 8px; + color: #f8fafc; + padding: 1rem; +} + +.error-boundary-fallback.compact { + padding: 0.75rem; +} + +.error-boundary-fallback h2, +.error-boundary-fallback h3 { + color: #f8fafc; + margin: 0 0 0.5rem; +} + +.error-boundary-fallback h2 { + font-size: 1.25rem; +} + +.error-boundary-fallback h3 { + font-size: 1rem; +} + +.error-boundary-fallback p { + color: #94a3b8; + margin: 0; +} + +.error-boundary-fallback .error-boundary-detail { + color: #eab308; + display: block; + font-size: 0.875rem; + margin-top: 0.75rem; +} diff --git a/frontend/src/components/ErrorBoundary.tsx b/frontend/src/components/ErrorBoundary.tsx new file mode 100644 index 00000000..7754ec77 --- /dev/null +++ b/frontend/src/components/ErrorBoundary.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import './ErrorBoundary.css'; + +interface ErrorBoundaryProps { + children: React.ReactNode; + fallbackTitle?: string; + fallbackMessage?: string; + compact?: boolean; +} + +interface ErrorBoundaryState { + error: Error | null; +} + +class ErrorBoundary extends React.Component { + state: ErrorBoundaryState = { + error: null, + }; + + static getDerivedStateFromError(error: Error): ErrorBoundaryState { + return { error }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + console.error('[ErrorBoundary] Render failure isolated:', error, errorInfo); + } + + render() { + if (this.state.error) { + const { + fallbackTitle = 'Section unavailable', + fallbackMessage = 'This part of the interface could not be rendered. The rest of the application is still available.', + compact = false, + } = this.props; + + return ( +
+ {compact ?

{fallbackTitle}

:

{fallbackTitle}

} +

{fallbackMessage}

+ {import.meta.env.DEV ? ( + {this.state.error.message} + ) : null} +
+ ); + } + + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 7f23c536..8d704d73 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -1,6 +1,7 @@ import React from 'react'; import Header from './Header'; import Sidebar from './Sidebar'; +import ErrorBoundary from './ErrorBoundary'; interface LayoutProps { children: React.ReactNode; @@ -11,13 +12,30 @@ const Layout: React.FC = ({ children }) => { return (
-
setSidebarOpen((prev) => !prev)} - /> + +
setSidebarOpen((prev) => !prev)} + /> +
- + + +
- {children} + + {children} +
diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 447dcfa3..0fdd750e 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { useDashboardStats } from '../hooks'; +import ErrorBoundary from '../components/ErrorBoundary'; const statCards = [ { key: 'totalUsers', label: 'Total Users', color: '#4f46e5' }, @@ -10,6 +11,30 @@ const statCards = [ { key: 'uptime', label: 'Uptime', color: '#0891b2', suffix: '%' }, ]; +interface StatCardProps { + card: typeof statCards[number]; + value: unknown; +} + +const StatCard: React.FC = ({ card, value }) => ( +
+
+
+ {card.label} + + {String(value ?? ' - ')} + {card.suffix || ''} + +
+
+); + const Dashboard: React.FC = () => { const { data: stats, isLoading, error } = useDashboardStats(); @@ -39,26 +64,21 @@ const Dashboard: React.FC = () => {

-
- {statCards.map((card) => ( -
-
+
+ {statCards.map((card) => ( + -
- {card.label} - - {String((stats as any)?.[card.key] ?? ' - ')} - {card.suffix || ''} - -
-
- ))} -
+ ))} +
+