diff --git a/diagnostic/build-351208e6.json b/diagnostic/build-351208e6.json new file mode 100644 index 00000000..84308a90 --- /dev/null +++ b/diagnostic/build-351208e6.json @@ -0,0 +1,24 @@ +{ + "generated_at": "2026-06-23T20:07:49.875563+00:00", + "commit": "351208e6", + "diagnostic_logd": "diagnostic/build-351208e6.logd", + "diagnostic_logd_error": null, + "message_blocker": null, + "chunked": false, + "chunk_size_bytes": null, + "password": "07c5642fac692758929f", + "decrypt_command": "encryptly unpack diagnostic/build-351208e6.logd --password 07c5642fac692758929f", + "total_modules": 1, + "passed": 1, + "failed": 0, + "modules": [ + { + "name": "frontend", + "status": "PASS", + "elapsed_seconds": 6.434, + "artifact": "/repo/frontend/dist", + "output": "> tent-frontend@0.0.0 build\n> tsc -b && vite build\n\nvite v6.4.3 building for production...\ntransforming...\n\u2713 101 modules transformed.\nrendering chunks...\ncomputing gzip size...\ndist/index.html 0.62 kB \u2502 gzip: 0.34 kB\ndist/assets/state-D-FhEj_8.js 8.91 kB \u2502 gzip: 3.54 kB \u2502 map: 57.15 kB\ndist/assets/vendor-OPNiyGRr.js 49.47 kB \u2502 gzip: 17.49 kB \u2502 map: 484.52 kB\ndist/assets/index-CDLR7IeC.js 233.01 kB \u2502 gzip: 72.64 kB \u2502 map: 1,048.62 kB\n\u2713 built in 1.72s\nnpm notice\nnpm notice New major version of npm available! 10.9.8 -> 11.17.0\nnpm notice Changelog: https://github.com/npm/cli/releases/tag/v11.17.0\nnpm notice To update run: npm install -g npm@11.17.0\nnpm notice" + } + ], + "pr_note": "Include the encrypted diagnostic logd artifact(s): diagnostic/build-351208e6.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/diagnostic/build-351208e6.logd b/diagnostic/build-351208e6.logd new file mode 100644 index 00000000..e5912fe8 Binary files /dev/null and b/diagnostic/build-351208e6.logd differ diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 7f23c536..333ed466 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -1,5 +1,6 @@ import React from 'react'; import Header from './Header'; +import { SectionFallback } from './SectionFallback'; import Sidebar from './Sidebar'; interface LayoutProps { @@ -11,13 +12,31 @@ const Layout: React.FC = ({ children }) => { return (
-
setSidebarOpen((prev) => !prev)} - /> + +
setSidebarOpen((prev) => !prev)} + /> +
- + + +
- {children} + + {children} +
diff --git a/frontend/src/components/SectionFallback.tsx b/frontend/src/components/SectionFallback.tsx new file mode 100644 index 00000000..2abeeeaf --- /dev/null +++ b/frontend/src/components/SectionFallback.tsx @@ -0,0 +1,68 @@ +import React from 'react'; + +export interface SectionFallbackProps { + children: React.ReactNode; + title: string; + message?: string; + className?: string; + resetLabel?: string; +} + +interface SectionFallbackState { + hasError: boolean; +} + +export class SectionFallback extends React.Component< + SectionFallbackProps, + SectionFallbackState +> { + state: SectionFallbackState = { + hasError: false, + }; + + static getDerivedStateFromError(): SectionFallbackState { + return { hasError: true }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void { + console.error('SectionFallback caught a render error', error, errorInfo); + } + + handleReset = (): void => { + this.setState({ hasError: false }); + }; + + render(): React.ReactNode { + const { + children, + title, + message = 'Something went wrong in this section.', + className, + resetLabel = 'Try again', + } = this.props; + + if (this.state.hasError) { + const fallbackClassName = ['section-fallback', className] + .filter(Boolean) + .join(' '); + + return ( +
+

{title}

+

{message}

+ +
+ ); + } + + return children; + } +} + +export default SectionFallback; diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 447dcfa3..6f6a9e00 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { SectionFallback } from '../components/SectionFallback'; import { useDashboardStats } from '../hooks'; const statCards = [ @@ -39,41 +40,53 @@ const Dashboard: React.FC = () => {

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

Recent Activity

-
- Activity feed will appear here + +
+
+

Recent Activity

+
+ Activity feed will appear here +
-
-
-

System Health

-
- Health metrics will appear here +
+

System Health

+
+ Health metrics will appear here +
-
+
); }; diff --git a/frontend/src/styles/legacy.css b/frontend/src/styles/legacy.css index ad0399ac..b653f819 100644 --- a/frontend/src/styles/legacy.css +++ b/frontend/src/styles/legacy.css @@ -826,3 +826,58 @@ tbody tr:last-child td { display: none !important; } } + +/* ------------------------------------------------------------------ */ +/* SECTION FALLBACKS */ +/* ------------------------------------------------------------------ */ + +.section-fallback { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + padding: 1rem; + color: #f8fafc; + background-color: #1e293b; + border: 1px solid #334155; + border-left: 3px solid #eab308; + border-radius: 8px; +} + +.section-fallback-title { + margin: 0; + color: #f8fafc; + font-size: 1rem; +} + +.section-fallback-message { + margin: 0; + color: #94a3b8; + font-size: 0.875rem; +} + +.section-fallback-action { + margin-top: 0.25rem; +} + +.app-header-fallback { + margin: 0; + border-radius: 0; +} + +.app-sidebar-fallback { + align-self: stretch; + min-width: 12rem; +} + +.app-content-fallback { + width: 100%; +} + +.dashboard-stats-fallback { + margin-bottom: 1.5rem; +} + +.dashboard-panels-fallback { + margin-top: 1.5rem; +}