- Added functionality to copy error details to clipboard and create GitHub issue reports directly from the error boundary. - Introduced a new state variable to manage the copy confirmation status. - Enhanced UI with buttons for copying error details and reporting bugs, improving user experience during error handling. - Updated the rendering logic to display additional context about the error and the copied status. - Refactored the component to ensure proper cleanup of timeouts on unmount.
157 lines
4.8 KiB
TypeScript
157 lines
4.8 KiB
TypeScript
import packageJson from '../../../package.json';
|
|
|
|
const GITHUB_BUG_REPORT_URL = 'https://github.com/777genius/claude_agent_teams_ui/issues/new';
|
|
const MAX_TITLE_LENGTH = 120;
|
|
const URL_MAX_STACK_LENGTH = 1800;
|
|
const URL_MAX_COMPONENT_STACK_LENGTH = 1200;
|
|
const COPY_MAX_STACK_LENGTH = 12000;
|
|
const COPY_MAX_COMPONENT_STACK_LENGTH = 8000;
|
|
|
|
export interface BugReportContext {
|
|
activeTabType?: string | null;
|
|
activeTabLabel?: string | null;
|
|
activeTeamName?: string | null;
|
|
selectedTeamName?: string | null;
|
|
taskId?: string | null;
|
|
sessionId?: string | null;
|
|
projectId?: string | null;
|
|
}
|
|
|
|
export interface BugReportOptions {
|
|
error: Error | null;
|
|
componentStack?: string | null;
|
|
context?: BugReportContext;
|
|
}
|
|
|
|
const truncate = (value: string, maxLength: number): string => {
|
|
if (value.length <= maxLength) {
|
|
return value;
|
|
}
|
|
|
|
return `${value.slice(0, maxLength)}\n...[truncated]`;
|
|
};
|
|
|
|
const buildIssueTitle = (error: Error | null): string => {
|
|
const baseTitle = error ? `[BUG] ${error.name}: ${error.message}` : '[BUG] Application crash';
|
|
return truncate(baseTitle, MAX_TITLE_LENGTH);
|
|
};
|
|
|
|
const getRuntimeLabel = (): string => (window.electronAPI ? 'Electron renderer' : 'Web browser');
|
|
|
|
const formatOptional = (value: string | null | undefined): string => {
|
|
if (!value) {
|
|
return 'Not available';
|
|
}
|
|
|
|
const trimmed = value.trim();
|
|
return trimmed.length > 0 ? trimmed : 'Not available';
|
|
};
|
|
|
|
const getOperatingSystemLabel = (): string => {
|
|
const { userAgent } = window.navigator;
|
|
|
|
if (userAgent.includes('Mac OS X')) return 'macOS';
|
|
if (userAgent.includes('Windows')) return 'Windows';
|
|
if (userAgent.includes('Linux')) return 'Linux';
|
|
|
|
return 'Unknown';
|
|
};
|
|
|
|
const formatActiveTab = (context?: BugReportContext): string => {
|
|
if (!context?.activeTabType) {
|
|
return 'Not available';
|
|
}
|
|
|
|
if (!context.activeTabLabel) {
|
|
return context.activeTabType;
|
|
}
|
|
|
|
return `${context.activeTabType} (${context.activeTabLabel})`;
|
|
};
|
|
|
|
const buildBugReportMarkdown = (
|
|
{ error, componentStack, context }: BugReportOptions,
|
|
stackLimits: { js: number; react: number }
|
|
): string => {
|
|
const message = error?.message ?? 'Unknown application crash';
|
|
const jsStack = error?.stack ? truncate(error.stack, stackLimits.js) : 'Not available';
|
|
const reactComponentStack = componentStack
|
|
? truncate(componentStack, stackLimits.react)
|
|
: 'Not available';
|
|
|
|
return [
|
|
'**Describe the bug**',
|
|
'The app crashed and showed the global error screen.',
|
|
'',
|
|
'**What happened**',
|
|
`- Error: \`${message}\``,
|
|
`- Error type: \`${error?.name ?? 'UnknownError'}\``,
|
|
`- Active tab: ${formatActiveTab(context)}`,
|
|
`- Active team tab: ${formatOptional(context?.activeTeamName)}`,
|
|
`- Selected team: ${formatOptional(context?.selectedTeamName)}`,
|
|
`- Current task: ${formatOptional(context?.taskId)}`,
|
|
`- Session ID: ${formatOptional(context?.sessionId)}`,
|
|
`- Project ID: ${formatOptional(context?.projectId)}`,
|
|
'',
|
|
'**Steps to reproduce**',
|
|
'1. Open the app and navigate to the screen where the crash happened.',
|
|
'2. Repeat the action that triggered the error.',
|
|
'3. Observe the global error screen.',
|
|
'',
|
|
'**Expected behavior**',
|
|
'The app should continue working instead of crashing.',
|
|
'',
|
|
'**Screenshots**',
|
|
'Attach a screenshot if you have one.',
|
|
'',
|
|
'**Environment**',
|
|
`- OS: ${getOperatingSystemLabel()}`,
|
|
`- Runtime: ${getRuntimeLabel()}`,
|
|
`- App version: ${packageJson.version}`,
|
|
'',
|
|
'**Diagnostics**',
|
|
'```text',
|
|
`Timestamp: ${new Date().toISOString()}`,
|
|
`Current URL: ${window.location.href}`,
|
|
`User Agent: ${window.navigator.userAgent}`,
|
|
`Error name: ${error?.name ?? 'UnknownError'}`,
|
|
`Error message: ${message}`,
|
|
`Active tab: ${formatActiveTab(context)}`,
|
|
`Active team tab: ${formatOptional(context?.activeTeamName)}`,
|
|
`Selected team: ${formatOptional(context?.selectedTeamName)}`,
|
|
`Current task: ${formatOptional(context?.taskId)}`,
|
|
`Session ID: ${formatOptional(context?.sessionId)}`,
|
|
`Project ID: ${formatOptional(context?.projectId)}`,
|
|
'```',
|
|
'',
|
|
'**JavaScript stack trace**',
|
|
'```text',
|
|
jsStack,
|
|
'```',
|
|
'',
|
|
'**React component stack**',
|
|
'```text',
|
|
reactComponentStack,
|
|
'```',
|
|
].join('\n');
|
|
};
|
|
|
|
export const buildBugReportText = (options: BugReportOptions): string =>
|
|
buildBugReportMarkdown(options, {
|
|
js: COPY_MAX_STACK_LENGTH,
|
|
react: COPY_MAX_COMPONENT_STACK_LENGTH,
|
|
});
|
|
|
|
export const buildGitHubBugReportUrl = (options: BugReportOptions): string => {
|
|
const params = new URLSearchParams({
|
|
template: 'bug_report.md',
|
|
labels: 'bug',
|
|
title: buildIssueTitle(options.error),
|
|
body: buildBugReportMarkdown(options, {
|
|
js: URL_MAX_STACK_LENGTH,
|
|
react: URL_MAX_COMPONENT_STACK_LENGTH,
|
|
}),
|
|
});
|
|
|
|
return `${GITHUB_BUG_REPORT_URL}?${params.toString()}`;
|
|
};
|