From e0772e24cd6258a5aff365eaa6f85f77c9cb90c5 Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Wed, 8 Apr 2026 07:41:31 +0100 Subject: [PATCH] [AI] Add ErrorBoundary around dashboard widgets (#7382) * [AI] Add ErrorBoundary around dashboard widgets (#7273) Wraps each dashboard widget in an ErrorBoundary so a faulty widget degrades to an error card instead of crashing the entire Reports page. Co-Authored-By: Claude Opus 4.6 (1M context) * Add release notes for PR #7382 * [autofix.ci] apply automated fixes --------- Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: github-actions[bot] Co-authored-by: Cursor Agent Co-authored-by: Matiss Janis Aboltins Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../src/components/reports/Overview.tsx | 308 ++++++++++-------- upcoming-release-notes/7382.md | 6 + 2 files changed, 178 insertions(+), 136 deletions(-) create mode 100644 upcoming-release-notes/7382.md diff --git a/packages/desktop-client/src/components/reports/Overview.tsx b/packages/desktop-client/src/components/reports/Overview.tsx index 323eb143ef..1fc3f01940 100644 --- a/packages/desktop-client/src/components/reports/Overview.tsx +++ b/packages/desktop-client/src/components/reports/Overview.tsx @@ -1,5 +1,6 @@ import { useCallback, useMemo, useState } from 'react'; import { Dialog, DialogTrigger } from 'react-aria-components'; +import { ErrorBoundary } from 'react-error-boundary'; import ReactGridLayout from 'react-grid-layout'; import type { Layout } from 'react-grid-layout'; import { useHotkeys } from 'react-hotkeys-hook'; @@ -34,6 +35,7 @@ import { CrossoverCard } from './reports/CrossoverCard'; import { CustomReportListCards } from './reports/CustomReportListCards'; import { FormulaCard } from './reports/FormulaCard'; import { MarkdownCard } from './reports/MarkdownCard'; +import { MissingReportCard } from './reports/MissingReportCard'; import { NetWorthCard } from './reports/NetWorthCard'; import { SankeyCard } from './reports/SankeyCard'; import './overview.scss'; @@ -770,142 +772,176 @@ export function Overview({ dashboard }: OverviewProps) { return (
- {widget.type === 'net-worth-card' ? ( - onMetaChange(item, newMeta)} - onRemove={() => onRemoveWidget(item.i)} - onCopy={targetDashboardId => - onCopyWidget(item.i, targetDashboardId) - } - /> - ) : widget.type === 'crossover-card' && - crossoverReportEnabled ? ( - onMetaChange(item, newMeta)} - onRemove={() => onRemoveWidget(item.i)} - onCopy={targetDashboardId => - onCopyWidget(item.i, targetDashboardId) - } - /> - ) : widget.type === 'age-of-money-card' && - ageOfMoneyReportEnabled ? ( - onMetaChange(item, newMeta)} - onRemove={() => onRemoveWidget(item.i)} - onCopy={targetDashboardId => - onCopyWidget(item.i, targetDashboardId) - } - /> - ) : widget.type === 'cash-flow-card' ? ( - onMetaChange(item, newMeta)} - onRemove={() => onRemoveWidget(item.i)} - onCopy={targetDashboardId => - onCopyWidget(item.i, targetDashboardId) - } - /> - ) : widget.type === 'spending-card' ? ( - onMetaChange(item, newMeta)} - onRemove={() => onRemoveWidget(item.i)} - onCopy={targetDashboardId => - onCopyWidget(item.i, targetDashboardId) - } - /> - ) : widget.type === 'budget-analysis-card' && - budgetAnalysisReportEnabled ? ( - onMetaChange(item, newMeta)} - onRemove={() => onRemoveWidget(item.i)} - onCopy={targetDashboardId => - onCopyWidget(item.i, targetDashboardId) - } - /> - ) : widget.type === 'markdown-card' ? ( - onMetaChange(item, newMeta)} - onRemove={() => onRemoveWidget(item.i)} - onCopy={targetDashboardId => - onCopyWidget(item.i, targetDashboardId) - } - /> - ) : widget.type === 'custom-report' ? ( - onRemoveWidget(item.i)} - onCopy={targetDashboardId => - onCopyWidget(item.i, targetDashboardId) - } - /> - ) : widget.type === 'summary-card' ? ( - onMetaChange(item, newMeta)} - onRemove={() => onRemoveWidget(item.i)} - onCopy={targetDashboardId => - onCopyWidget(item.i, targetDashboardId) - } - /> - ) : widget.type === 'calendar-card' ? ( - onMetaChange(item, newMeta)} - onRemove={() => onRemoveWidget(item.i)} - onCopy={targetDashboardId => - onCopyWidget(item.i, targetDashboardId) - } - /> - ) : widget.type === 'formula-card' && formulaMode ? ( - onMetaChange(item, newMeta)} - onRemove={() => onRemoveWidget(item.i)} - onCopy={targetDashboardId => - onCopyWidget(item.i, targetDashboardId) - } - /> - ) : widget.type === 'sankey-card' && sankeyFeatureFlag ? ( - onMetaChange(item, newMeta)} - onRemove={() => onRemoveWidget(item.i)} - onCopy={targetDashboardId => - onCopyWidget(item.i, targetDashboardId) - } - /> - ) : null} + ( + onRemoveWidget(item.i)} + > + This widget has failed to load. + + )} + > + {widget.type === 'net-worth-card' ? ( + + onMetaChange(item, newMeta) + } + onRemove={() => onRemoveWidget(item.i)} + onCopy={targetDashboardId => + onCopyWidget(item.i, targetDashboardId) + } + /> + ) : widget.type === 'crossover-card' && + crossoverReportEnabled ? ( + + onMetaChange(item, newMeta) + } + onRemove={() => onRemoveWidget(item.i)} + onCopy={targetDashboardId => + onCopyWidget(item.i, targetDashboardId) + } + /> + ) : widget.type === 'age-of-money-card' && + ageOfMoneyReportEnabled ? ( + + onMetaChange(item, newMeta) + } + onRemove={() => onRemoveWidget(item.i)} + onCopy={targetDashboardId => + onCopyWidget(item.i, targetDashboardId) + } + /> + ) : widget.type === 'cash-flow-card' ? ( + + onMetaChange(item, newMeta) + } + onRemove={() => onRemoveWidget(item.i)} + onCopy={targetDashboardId => + onCopyWidget(item.i, targetDashboardId) + } + /> + ) : widget.type === 'spending-card' ? ( + + onMetaChange(item, newMeta) + } + onRemove={() => onRemoveWidget(item.i)} + onCopy={targetDashboardId => + onCopyWidget(item.i, targetDashboardId) + } + /> + ) : widget.type === 'budget-analysis-card' && + budgetAnalysisReportEnabled ? ( + + onMetaChange(item, newMeta) + } + onRemove={() => onRemoveWidget(item.i)} + onCopy={targetDashboardId => + onCopyWidget(item.i, targetDashboardId) + } + /> + ) : widget.type === 'markdown-card' ? ( + + onMetaChange(item, newMeta) + } + onRemove={() => onRemoveWidget(item.i)} + onCopy={targetDashboardId => + onCopyWidget(item.i, targetDashboardId) + } + /> + ) : widget.type === 'custom-report' ? ( + onRemoveWidget(item.i)} + onCopy={targetDashboardId => + onCopyWidget(item.i, targetDashboardId) + } + /> + ) : widget.type === 'summary-card' ? ( + + onMetaChange(item, newMeta) + } + onRemove={() => onRemoveWidget(item.i)} + onCopy={targetDashboardId => + onCopyWidget(item.i, targetDashboardId) + } + /> + ) : widget.type === 'calendar-card' ? ( + + onMetaChange(item, newMeta) + } + onRemove={() => onRemoveWidget(item.i)} + onCopy={targetDashboardId => + onCopyWidget(item.i, targetDashboardId) + } + /> + ) : widget.type === 'formula-card' && formulaMode ? ( + + onMetaChange(item, newMeta) + } + onRemove={() => onRemoveWidget(item.i)} + onCopy={targetDashboardId => + onCopyWidget(item.i, targetDashboardId) + } + /> + ) : widget.type === 'sankey-card' && + sankeyFeatureFlag ? ( + + onMetaChange(item, newMeta) + } + onRemove={() => onRemoveWidget(item.i)} + onCopy={targetDashboardId => + onCopyWidget(item.i, targetDashboardId) + } + /> + ) : null} +
); })} diff --git a/upcoming-release-notes/7382.md b/upcoming-release-notes/7382.md new file mode 100644 index 0000000000..0f4850b0e3 --- /dev/null +++ b/upcoming-release-notes/7382.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [MatissJanis] +--- + +Add error boundary to dashboard widgets, displaying fallback UI for rendering failures.