feat: add in-app feedback submission

GoodBuddy previously had no direct way to send feedback from the desktop app. The About & Updates page now opens a bilingual, accessible feedback dialog for problems, feature requests, experience notes, and other feedback, with an optional email address and one screenshot.

Submissions use the narrow preload and trusted IPC boundary, a fixed production endpoint with strict TLS, bounded screenshot processing, an isolated resettable installation ID, preserved drafts, and idempotent manual retries. GoodBuddy does not automatically attach conversations, logs, files, settings, or credentials.

Release note: 现在可在“关于与更新”中直接提交问题、建议与体验反馈;邮箱和截图均可选,提交失败时会保留草稿供重试。
This commit is contained in:
mesalogo
2026-08-25 08:01:42 +08:00
parent efecf88af7
commit 098f21b47a
31 changed files with 3445 additions and 58 deletions
+11
View File
@@ -146,6 +146,10 @@ import type {
GlobalShortcutSettingsSnapshot,
GlobalShortcutSettingsUpdateResult
} from '../shared/shortcut'
import type {
FeedbackSubmitInput,
FeedbackSubmitResult
} from '../shared/feedback-contracts'
const desktopApi: DesktopApi = {
app: {
@@ -451,6 +455,13 @@ const desktopApi: DesktopApi = {
ipcRenderer.removeListener(ipcChannels.versionCheckResult, handler)
}
},
feedback: {
submit: (input: FeedbackSubmitInput) =>
ipcRenderer.invoke(
ipcChannels.feedbackSubmit,
input
) as Promise<FeedbackSubmitResult>
},
shortcuts: {
getSettings: () =>
ipcRenderer.invoke(
+16
View File
@@ -117,6 +117,22 @@ describe('sandboxed preload', () => {
expect(source).toContain('ipcChannels.releaseNotesAcknowledge')
})
it('exposes only the narrow feedback submission method', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
const feedback =
source.match(
/feedback: \{(?<body>[\s\S]*?)\n {2}\},\n {2}shortcuts:/u
)?.groups?.body ?? ''
expect(feedback).toContain('submit: (input: FeedbackSubmitInput)')
expect(feedback).toContain('ipcChannels.feedbackSubmit')
expect(feedback).not.toMatch(
/\b(?:url|endpoint|header|cookie|authorization|path)\b/iu
)
})
it('exposes only get and validated-update shortcut settings methods', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),