[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) <noreply@anthropic.com>

* Add release notes for PR #7382

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Matiss Janis Aboltins
2026-04-08 07:41:31 +01:00
committed by GitHub
parent 3d5881ea57
commit e0772e24cd
2 changed files with 178 additions and 136 deletions

View File

@@ -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 (
<div key={item.i}>
{widget.type === 'net-worth-card' ? (
<NetWorthCard
widgetId={item.i}
isEditing={isEditing}
accounts={accounts}
meta={widget.meta}
onMetaChange={newMeta => onMetaChange(item, newMeta)}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'crossover-card' &&
crossoverReportEnabled ? (
<CrossoverCard
widgetId={item.i}
isEditing={isEditing}
accounts={accounts}
meta={widget.meta}
onMetaChange={newMeta => onMetaChange(item, newMeta)}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'age-of-money-card' &&
ageOfMoneyReportEnabled ? (
<AgeOfMoneyCard
widgetId={item.i}
isEditing={isEditing}
meta={widget.meta}
onMetaChange={newMeta => onMetaChange(item, newMeta)}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'cash-flow-card' ? (
<CashFlowCard
widgetId={item.i}
isEditing={isEditing}
meta={widget.meta}
onMetaChange={newMeta => onMetaChange(item, newMeta)}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'spending-card' ? (
<SpendingCard
widgetId={item.i}
isEditing={isEditing}
meta={widget.meta}
onMetaChange={newMeta => onMetaChange(item, newMeta)}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'budget-analysis-card' &&
budgetAnalysisReportEnabled ? (
<BudgetAnalysisCard
widgetId={item.i}
isEditing={isEditing}
meta={widget.meta}
onMetaChange={newMeta => onMetaChange(item, newMeta)}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'markdown-card' ? (
<MarkdownCard
isEditing={isEditing}
meta={widget.meta}
onMetaChange={newMeta => onMetaChange(item, newMeta)}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'custom-report' ? (
<CustomReportListCards
isEditing={isEditing}
report={customReportMap.get(widget.meta.id)}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'summary-card' ? (
<SummaryCard
widgetId={item.i}
isEditing={isEditing}
meta={widget.meta}
onMetaChange={newMeta => onMetaChange(item, newMeta)}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'calendar-card' ? (
<CalendarCard
widgetId={item.i}
isEditing={isEditing}
meta={widget.meta}
firstDayOfWeekIdx={firstDayOfWeekIdx}
onMetaChange={newMeta => onMetaChange(item, newMeta)}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'formula-card' && formulaMode ? (
<FormulaCard
widgetId={item.i}
isEditing={isEditing}
meta={widget.meta}
onMetaChange={newMeta => onMetaChange(item, newMeta)}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'sankey-card' && sankeyFeatureFlag ? (
<SankeyCard
widgetId={item.i}
isEditing={isEditing}
meta={widget.meta}
onMetaChange={newMeta => onMetaChange(item, newMeta)}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : null}
<ErrorBoundary
fallbackRender={() => (
<MissingReportCard
isEditing={isEditing}
onRemove={() => onRemoveWidget(item.i)}
>
<Trans>This widget has failed to load.</Trans>
</MissingReportCard>
)}
>
{widget.type === 'net-worth-card' ? (
<NetWorthCard
widgetId={item.i}
isEditing={isEditing}
accounts={accounts}
meta={widget.meta}
onMetaChange={newMeta =>
onMetaChange(item, newMeta)
}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'crossover-card' &&
crossoverReportEnabled ? (
<CrossoverCard
widgetId={item.i}
isEditing={isEditing}
accounts={accounts}
meta={widget.meta}
onMetaChange={newMeta =>
onMetaChange(item, newMeta)
}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'age-of-money-card' &&
ageOfMoneyReportEnabled ? (
<AgeOfMoneyCard
widgetId={item.i}
isEditing={isEditing}
meta={widget.meta}
onMetaChange={newMeta =>
onMetaChange(item, newMeta)
}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'cash-flow-card' ? (
<CashFlowCard
widgetId={item.i}
isEditing={isEditing}
meta={widget.meta}
onMetaChange={newMeta =>
onMetaChange(item, newMeta)
}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'spending-card' ? (
<SpendingCard
widgetId={item.i}
isEditing={isEditing}
meta={widget.meta}
onMetaChange={newMeta =>
onMetaChange(item, newMeta)
}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'budget-analysis-card' &&
budgetAnalysisReportEnabled ? (
<BudgetAnalysisCard
widgetId={item.i}
isEditing={isEditing}
meta={widget.meta}
onMetaChange={newMeta =>
onMetaChange(item, newMeta)
}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'markdown-card' ? (
<MarkdownCard
isEditing={isEditing}
meta={widget.meta}
onMetaChange={newMeta =>
onMetaChange(item, newMeta)
}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'custom-report' ? (
<CustomReportListCards
isEditing={isEditing}
report={customReportMap.get(widget.meta.id)}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'summary-card' ? (
<SummaryCard
widgetId={item.i}
isEditing={isEditing}
meta={widget.meta}
onMetaChange={newMeta =>
onMetaChange(item, newMeta)
}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'calendar-card' ? (
<CalendarCard
widgetId={item.i}
isEditing={isEditing}
meta={widget.meta}
firstDayOfWeekIdx={firstDayOfWeekIdx}
onMetaChange={newMeta =>
onMetaChange(item, newMeta)
}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'formula-card' && formulaMode ? (
<FormulaCard
widgetId={item.i}
isEditing={isEditing}
meta={widget.meta}
onMetaChange={newMeta =>
onMetaChange(item, newMeta)
}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : widget.type === 'sankey-card' &&
sankeyFeatureFlag ? (
<SankeyCard
widgetId={item.i}
isEditing={isEditing}
meta={widget.meta}
onMetaChange={newMeta =>
onMetaChange(item, newMeta)
}
onRemove={() => onRemoveWidget(item.i)}
onCopy={targetDashboardId =>
onCopyWidget(item.i, targetDashboardId)
}
/>
) : null}
</ErrorBoundary>
</div>
);
})}

View File

@@ -0,0 +1,6 @@
---
category: Enhancements
authors: [MatissJanis]
---
Add error boundary to dashboard widgets, displaying fallback UI for rendering failures.