Route crash reports separately from feedback (#2280)

This commit is contained in:
Neil 2026-05-18 15:33:52 -07:00 committed by GitHub
parent aff55442d2
commit c182e09f99
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 58 additions and 2 deletions

View File

@ -129,6 +129,7 @@ describe('registerCrashReportingHandlers', () => {
expect(submitFeedbackMock).toHaveBeenCalledWith(
expect.objectContaining({
feedback: expect.stringContaining('[Crash Report]'),
submissionType: 'crash',
githubLogin: 'me',
githubEmail: 'me@example.com'
})

View File

@ -84,6 +84,7 @@ export function registerCrashReportingHandlers(store: CrashReportStore): void {
try {
const result = await submitFeedback({
feedback: formatCrashReportText(report, args.notes),
submissionType: 'crash',
submitAnonymously: args.submitAnonymously,
githubLogin: args.githubLogin,
githubEmail: args.githubEmail

View File

@ -40,6 +40,7 @@ describe('submitFeedback', () => {
const body = postedBody()
expect(body).toMatchObject({
feedback: 'private bug report',
submissionType: 'feedback',
githubLogin: null,
githubEmail: null,
appVersion: '1.2.3-test'
@ -60,9 +61,27 @@ describe('submitFeedback', () => {
const body = postedBody()
expect(body).toMatchObject({
feedback: 'public bug report',
submissionType: 'feedback',
githubLogin: 'trusted-user',
githubEmail: 'trusted@example.com',
appVersion: '1.2.3-test'
})
})
it('marks crash submissions so the backend can route them separately', async () => {
await submitFeedback({
feedback: '[Crash Report]',
submissionType: 'crash',
submitAnonymously: false,
githubLogin: 'trusted-user',
githubEmail: null
})
expect(postedBody()).toMatchObject({
feedback: '[Crash Report]',
submissionType: 'crash',
githubLogin: 'trusted-user',
githubEmail: null
})
})
})

View File

@ -9,15 +9,19 @@ import { app, ipcMain, net } from 'electron'
const FEEDBACK_API_URL = 'https://api.onorca.dev/v1/feedback'
const FEEDBACK_API_FALLBACK_URL = 'https://www.onorca.dev/v1/feedback'
export type FeedbackSubmissionType = 'feedback' | 'crash'
export type FeedbackSubmitArgs = {
feedback: string
submitAnonymously?: boolean
githubLogin: string | null
githubEmail: string | null
submissionType?: FeedbackSubmissionType
}
type FeedbackSubmitBody = {
feedback: string
submissionType: FeedbackSubmissionType
githubLogin: string | null
githubEmail: string | null
appVersion: string
@ -44,6 +48,7 @@ function buildSubmitBody(args: FeedbackSubmitArgs): FeedbackSubmitBody {
// stale renderer state or future identity-shaped fields cannot leak upstream.
return {
feedback: args.feedback,
submissionType: args.submissionType ?? 'feedback',
...identity,
appVersion: app.getVersion(),
platform: process.platform,

View File

@ -11,6 +11,7 @@ import {
DialogTitle
} from '@/components/ui/dialog'
import { formatCrashReportText, type CrashReportRecord } from '../../../../shared/crash-reporting'
import type { GitHubViewer } from '../../../../shared/types'
function formatSummary(report: CrashReportRecord): string {
return `${report.processType} ${report.reason}${
@ -25,6 +26,7 @@ export function CrashReportDialog(): React.JSX.Element {
const [notes, setNotes] = useState('')
const [loading, setLoading] = useState(false)
const [submitting, setSubmitting] = useState(false)
const [viewer, setViewer] = useState<GitHubViewer | null>(null)
const diagnosticText = useMemo(
() => (report ? formatCrashReportText(report, notes) : ''),
[notes, report]
@ -70,6 +72,32 @@ export function CrashReportDialog(): React.JSX.Element {
})
}, [])
useEffect(() => {
if (!open) {
setViewer(null)
return
}
let cancelled = false
void window.api.gh
.viewer()
.then((nextViewer) => {
if (!cancelled) {
setViewer(nextViewer)
}
})
.catch((error) => {
if (!cancelled) {
setViewer(null)
console.error('Failed to load GitHub viewer for crash report:', error)
}
})
return () => {
cancelled = true
}
}, [open])
const handleCopy = async (): Promise<void> => {
const result = await window.api.crashReports.copyLatestDiagnostics(
report ? { reportId: report.id, notes } : {}
@ -102,8 +130,10 @@ export function CrashReportDialog(): React.JSX.Element {
const result = await window.api.crashReports.submit({
reportId: report.id,
notes,
submitAnonymously: true,
githubLogin: null,
// Why: crash reporting must degrade to anonymous if gh is unavailable;
// identity lookup is best-effort and never blocks report creation.
submitAnonymously: !viewer,
githubLogin: viewer?.login ?? null,
githubEmail: null
})
if (!result.ok) {