Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions diagnostic/build-351208e6.json
Original file line number Diff line number Diff line change
@@ -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 <outdir> --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."
}
Binary file added diagnostic/build-351208e6.logd
Binary file not shown.
29 changes: 24 additions & 5 deletions frontend/src/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import Header from './Header';
import { SectionFallback } from './SectionFallback';
import Sidebar from './Sidebar';

interface LayoutProps {
Expand All @@ -11,13 +12,31 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {

return (
<div className="app-layout">
<Header
onMenuToggle={() => setSidebarOpen((prev) => !prev)}
/>
<SectionFallback
title="Header unavailable"
message="The top navigation could not be displayed."
className="app-header-fallback"
>
<Header
onMenuToggle={() => setSidebarOpen((prev) => !prev)}
/>
</SectionFallback>
<div className="app-body">
<Sidebar isOpen={sidebarOpen} />
<SectionFallback
title="Sidebar unavailable"
message="The sidebar navigation could not be displayed."
className="app-sidebar-fallback"
>
<Sidebar isOpen={sidebarOpen} />
</SectionFallback>
<main className="app-content">
{children}
<SectionFallback
title="Page unavailable"
message="This page could not be displayed."
className="app-content-fallback"
>
{children}
</SectionFallback>
</main>
</div>
</div>
Expand Down
68 changes: 68 additions & 0 deletions frontend/src/components/SectionFallback.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={fallbackClassName} role="alert" aria-live="polite">
<h2 className="section-fallback-title">{title}</h2>
<p className="section-fallback-message">{message}</p>
<button
type="button"
className="section-fallback-action btn btn-secondary"
onClick={this.handleReset}
>
{resetLabel}
</button>
</div>
);
}

return children;
}
}

export default SectionFallback;
73 changes: 43 additions & 30 deletions frontend/src/pages/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { SectionFallback } from '../components/SectionFallback';
import { useDashboardStats } from '../hooks';

const statCards = [
Expand Down Expand Up @@ -39,41 +40,53 @@ const Dashboard: React.FC = () => {
</p>
</div>

<div className="stats-grid">
{statCards.map((card) => (
<div key={card.key} className="stat-card">
<div
className="stat-card-indicator"
style={{ backgroundColor: card.color }}
/>
<div className="stat-card-content">
<span className="stat-card-label">{card.label}</span>
<span
className="stat-card-value"
style={{ color: card.color }}
>
{String((stats as any)?.[card.key] ?? ' - ')}
{card.suffix || ''}
</span>
<SectionFallback
title="Stats unavailable"
message="The dashboard metrics could not be displayed."
className="dashboard-stats-fallback"
>
<div className="stats-grid">
{statCards.map((card) => (
<div key={card.key} className="stat-card">
<div
className="stat-card-indicator"
style={{ backgroundColor: card.color }}
/>
<div className="stat-card-content">
<span className="stat-card-label">{card.label}</span>
<span
className="stat-card-value"
style={{ color: card.color }}
>
{String((stats as any)?.[card.key] ?? ' - ')}
{card.suffix || ''}
</span>
</div>
</div>
</div>
))}
</div>
))}
</div>
</SectionFallback>

<div className="dashboard-panels">
<div className="panel">
<h3>Recent Activity</h3>
<div className="panel-placeholder">
Activity feed will appear here
<SectionFallback
title="Dashboard panels unavailable"
message="The dashboard panels could not be displayed."
className="dashboard-panels-fallback"
>
<div className="dashboard-panels">
<div className="panel">
<h3>Recent Activity</h3>
<div className="panel-placeholder">
Activity feed will appear here
</div>
</div>
</div>
<div className="panel">
<h3>System Health</h3>
<div className="panel-placeholder">
Health metrics will appear here
<div className="panel">
<h3>System Health</h3>
<div className="panel-placeholder">
Health metrics will appear here
</div>
</div>
</div>
</div>
</SectionFallback>
</div>
);
};
Expand Down
55 changes: 55 additions & 0 deletions frontend/src/styles/legacy.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}