Skip to content

Commit 96ca9ae

Browse files
committed
feat(renderer): add Report on GitHub button to ErrorBoundary
When the renderer crashes, the fallback card now shows three actions: Copy stack | Report on GitHub | Reload Clicking "Report on GitHub" opens a pre-filled GitHub issue in the system browser (routed via setWindowOpenHandler → shell.openExternal). The issue body is pre-populated with app version (__APP_VERSION__), platform (navigator.platform), timestamp, error message, and the first 3 000 characters of the stack trace. GITHUB_REPO_URL is hardcoded from package.json repository.url so the ErrorBoundary has no IPC dependency and keeps rendering even when the main process is unresponsive. i18n keys added: errorBoundary.reportOnGitHub (en + zh-CN).
1 parent 8140687 commit 96ca9ae

3 files changed

Lines changed: 47 additions & 0 deletions

File tree

apps/desktop/src/renderer/src/components/ErrorBoundary.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* - the error message (loud, not hidden in console)
77
* - "Reload" — re-mounts the children by bumping a key
88
* - "Copy stack" — puts message+stack into the clipboard for bug reports
9+
* - "Report on GitHub" — opens a pre-filled GitHub issue in the browser
910
*
1011
* Used both at the app shell (whole renderer) and per-pane (sidebar /
1112
* preview / topbar) so a single crash never blanks the entire window.
@@ -15,6 +16,11 @@ import { useT } from '@open-codesign/i18n';
1516
import { Button } from '@open-codesign/ui';
1617
import { Component, type ErrorInfo, type ReactNode } from 'react';
1718

19+
// Source: apps/desktop/package.json → repository.url
20+
// Kept as a constant here so the ErrorBoundary has no runtime dependency on
21+
// the main process — it must render even when IPC is broken.
22+
const GITHUB_REPO_URL = 'https://github.com/OpenCoworkAI/open-codesign';
23+
1824
export interface ErrorBoundaryProps {
1925
children: ReactNode;
2026
/** Human-readable label used in the fallback heading: "Sidebar crashed". */
@@ -71,6 +77,39 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
7177
}
7278
};
7379

80+
reportOnGitHub = (): void => {
81+
const err = this.state.error;
82+
if (!err) return;
83+
84+
const version = typeof __APP_VERSION__ !== 'undefined' ? String(__APP_VERSION__) : 'unknown';
85+
const platform = navigator.platform;
86+
const timestamp = new Date().toISOString();
87+
const stackSnippet = (err.stack ?? '(no stack)').slice(0, 3000);
88+
89+
const title = `[Renderer] ${err.name}: ${err.message.slice(0, 60)}`;
90+
const body = [
91+
`**Version:** ${version}`,
92+
`**Platform:** ${platform}`,
93+
`**Timestamp:** ${timestamp}`,
94+
'',
95+
'**Error message:**',
96+
'```',
97+
err.message,
98+
'```',
99+
'',
100+
'**Stack trace:**',
101+
'```',
102+
stackSnippet,
103+
'```',
104+
].join('\n');
105+
106+
const url = `${GITHUB_REPO_URL}/issues/new?template=bug_report.yml&title=${encodeURIComponent(title)}&description=${encodeURIComponent(body)}`;
107+
108+
// setWindowOpenHandler in main/index.ts routes window.open to shell.openExternal,
109+
// so this safely opens the system browser without a new Electron window.
110+
window.open(url, '_blank');
111+
};
112+
74113
override render(): ReactNode {
75114
const { error, resetKey } = this.state;
76115
if (!error) {
@@ -86,6 +125,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
86125
scope={this.props.scope}
87126
onReset={this.reset}
88127
onCopyStack={() => void this.copyStack()}
128+
onReportOnGitHub={this.reportOnGitHub}
89129
/>
90130
);
91131
}
@@ -96,13 +136,15 @@ interface ErrorBoundaryFallbackProps {
96136
scope?: string | undefined;
97137
onReset: () => void;
98138
onCopyStack: () => void;
139+
onReportOnGitHub: () => void;
99140
}
100141

101142
function ErrorBoundaryFallback({
102143
error,
103144
scope,
104145
onReset,
105146
onCopyStack,
147+
onReportOnGitHub,
106148
}: ErrorBoundaryFallbackProps): ReactNode {
107149
const t = useT();
108150
const scopeLabel = scope ?? t('errorBoundary.scopeFallback');
@@ -123,6 +165,9 @@ function ErrorBoundaryFallback({
123165
<Button type="button" variant="secondary" size="md" onClick={onCopyStack}>
124166
{t('errorBoundary.copyStack')}
125167
</Button>
168+
<Button type="button" variant="secondary" size="md" onClick={onReportOnGitHub}>
169+
{t('errorBoundary.reportOnGitHub')}
170+
</Button>
126171
<Button type="button" size="md" onClick={onReset}>
127172
{t('errorBoundary.reload')}
128173
</Button>

packages/i18n/src/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,7 @@
568568
"body": "The rest of the app is still running. Reload this view, or copy the stack to file a bug.",
569569
"noStack": "(no stack)",
570570
"copyStack": "Copy stack",
571+
"reportOnGitHub": "Report on GitHub",
571572
"reload": "Reload"
572573
},
573574
"errors": {

packages/i18n/src/locales/zh-CN.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,7 @@
571571
"body": "应用其余部分仍在运行。可以重新加载该视图,或复制堆栈用于反馈问题。",
572572
"noStack": "(无堆栈信息)",
573573
"copyStack": "复制堆栈",
574+
"reportOnGitHub": "在 GitHub 反馈",
574575
"reload": "重新加载"
575576
},
576577
"errors": {

0 commit comments

Comments
 (0)