Skip to content
Merged
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
78 changes: 48 additions & 30 deletions apps/desktop/src/renderer/src/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* preview / topbar) so a single crash never blanks the entire window.
*/

import { useT } from '@open-codesign/i18n';
import { Button } from '@open-codesign/ui';
import { Component, type ErrorInfo, type ReactNode } from 'react';

Expand Down Expand Up @@ -79,41 +80,58 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
if (this.props.fallback) {
return this.props.fallback({ error, reset: this.reset });
}
const scope = this.props.scope ?? 'this view';
return (
<div className="h-full w-full flex items-center justify-center p-6 bg-[var(--color-background)]">
<div className="max-w-md w-full rounded-[var(--radius-2xl)] border border-[var(--color-border)] bg-[var(--color-surface)] shadow-[var(--shadow-card)] p-6">
<div className="text-xs uppercase tracking-wide text-[var(--color-error)] font-semibold mb-2">
{scope} crashed
</div>
<h2 className="text-lg font-semibold text-[var(--color-text-primary)] mb-1">
{error.name}: {error.message}
</h2>
<p className="text-sm text-[var(--color-text-secondary)] mb-4">
The rest of the app is still running. Reload this view, or copy the stack to file a bug.
</p>
<pre className="text-[11px] leading-snug font-mono text-[var(--color-text-muted)] bg-[var(--color-surface-active)] border border-[var(--color-border-muted)] rounded-[var(--radius-md)] p-3 max-h-40 overflow-auto whitespace-pre-wrap">
{error.stack ?? '(no stack)'}
</pre>
<div className="mt-4 flex gap-2 justify-end">
<Button
type="button"
variant="secondary"
size="md"
onClick={() => void this.copyStack()}
>
Copy stack
</Button>
<Button type="button" size="md" onClick={this.reset}>
Reload
</Button>
</div>
</div>
</div>
<ErrorBoundaryFallback
error={error}
scope={this.props.scope}
onReset={this.reset}
onCopyStack={() => void this.copyStack()}
/>
);
}
}

interface ErrorBoundaryFallbackProps {
error: Error;
scope?: string | undefined;
onReset: () => void;
onCopyStack: () => void;
}

function ErrorBoundaryFallback({
error,
scope,
onReset,
onCopyStack,
}: ErrorBoundaryFallbackProps): ReactNode {
const t = useT();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Minor] Please add a Vitest regression test for this fallback path now that copy is i18n-driven. This guards against accidental reintroduction of hardcoded strings and missing locale keys for the error UI.

Suggested fix:

it('renders localized fallback copy when child throws', () => {
  const Boom = () => {
    throw new Error('boom');
  };

  render(
    <I18nProvider locale="en">
      <ErrorBoundary scope="Preview">
        <Boom />
      </ErrorBoundary>
    </I18nProvider>
  );

  expect(screen.getByText('Preview crashed')).toBeInTheDocument();
  expect(screen.getByRole('button', { name: 'Reload' })).toBeInTheDocument();
  expect(screen.getByRole('button', { name: 'Copy stack' })).toBeInTheDocument();
});

const scopeLabel = scope ?? t('errorBoundary.scopeFallback');
return (
<div className="h-full w-full flex items-center justify-center p-6 bg-[var(--color-background)]">
<div className="max-w-md w-full rounded-[var(--radius-2xl)] border border-[var(--color-border)] bg-[var(--color-surface)] shadow-[var(--shadow-card)] p-6">
<div className="text-xs uppercase tracking-wide text-[var(--color-error)] font-semibold mb-2">
{t('errorBoundary.crashedSuffix', { scope: scopeLabel })}
</div>
<h2 className="text-lg font-semibold text-[var(--color-text-primary)] mb-1">
{error.name}: {error.message}
</h2>
<p className="text-sm text-[var(--color-text-secondary)] mb-4">{t('errorBoundary.body')}</p>
<pre className="text-[11px] leading-snug font-mono text-[var(--color-text-muted)] bg-[var(--color-surface-active)] border border-[var(--color-border-muted)] rounded-[var(--radius-md)] p-3 max-h-40 overflow-auto whitespace-pre-wrap">
{error.stack ?? t('errorBoundary.noStack')}
</pre>
<div className="mt-4 flex gap-2 justify-end">
<Button type="button" variant="secondary" size="md" onClick={onCopyStack}>
{t('errorBoundary.copyStack')}
</Button>
<Button type="button" size="md" onClick={onReset}>
{t('errorBoundary.reload')}
</Button>
</div>
</div>
</div>
);
}

function ErrorBoundaryChildren({ children }: { children: ReactNode }): ReactNode {
return children;
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ function InlineCommentComposerCard({ selectedElement }: InlineCommentComposerCar
<div className="min-w-0">
<div className="inline-flex items-center gap-2 text-[var(--text-xs)] font-medium text-[var(--color-text-primary)]">
<MessageSquareText className="h-4 w-4 text-[var(--color-accent)]" />
{t('inlineComment.title')} <code className="text-[var(--text-xs)]">{selectedElement.tag}</code>
{t('inlineComment.title')}{' '}
<code className="text-[var(--text-xs)]">{selectedElement.tag}</code>
</div>
<p
className="mt-1 truncate text-[var(--text-xs)] text-[var(--color-text-muted)]"
Expand Down
8 changes: 8 additions & 0 deletions packages/i18n/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,14 @@
"enterBothModels": "Both model fields are required",
"ollamaComingSoon": "Ollama integration is coming in v0.2"
},
"errorBoundary": {
"scopeFallback": "this view",
"crashedSuffix": "{{scope}} crashed",
"body": "The rest of the app is still running. Reload this view, or copy the stack to file a bug.",
"noStack": "(no stack)",
"copyStack": "Copy stack",
"reload": "Reload"
},
"errors": {
"generic": "Something went wrong.",
"providerAuthMissing": "No API key configured. Open Settings to add one.",
Expand Down
8 changes: 8 additions & 0 deletions packages/i18n/src/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,14 @@
"enterBothModels": "两个模型字段均为必填",
"ollamaComingSoon": "Ollama 集成将在 v0.2 中支持"
},
"errorBoundary": {
"scopeFallback": "这个视图",
"crashedSuffix": "{{scope}}崩溃了",
"body": "应用其余部分仍在运行。可以重新加载该视图,或复制堆栈用于反馈问题。",
"noStack": "(无堆栈信息)",
"copyStack": "复制堆栈",
"reload": "重新加载"
},
"errors": {
"generic": "出错了。",
"providerAuthMissing": "还没配置 API Key。打开设置去添加一个。",
Expand Down
Loading