Files
goodbuddy/docs/features/in-app-feedback/technical-design.md
T
mesalogo ca4a58c762 fix: keep full-window dialogs responsive to mouse input
Render feedback, parsing diagnostics, knowledge confirmations, WeChat binding and image preview overlays through body portals so native titlebar drag regions cannot intercept their controls. Preserve themed form styles and modal focus isolation.

Validation: 320 targeted tests, typecheck and lint passed. Native Windows hover and close checks passed for feedback and parsing diagnostics in light and dark themes at two sizes. Full suite timed out without a summary.

Release note: 修复反馈等弹窗与窗口标题栏重叠时,关闭按钮无法正常悬停和点击的问题。
2026-09-13 14:19:46 +08:00

713 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# GoodBuddy 应用内反馈系统设计与对接
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 文档类型 | 跨功能产品与技术架构 |
| 状态 | 已实施并完成生产联调,待发布 |
| 版本 | 1.0 |
| 日期 | 2026-08-25 |
| 适用产品 | GoodBuddy 桌面端 |
| 目标平台 | Windows、macOS、Linux,x64 与 arm64 |
| 相关基线 | [统一界面设计系统](../../../UI-DESIGN.md) |
本文定义 GoodBuddy 桌面端的应用内反馈入口、数据边界、Renderer / Preload /
Main 进程职责、公共 API 对接、失败处理和验收方法。反馈平台服务端、管理后台、
数据库、附件存储、部署和运营流程由独立项目维护,不在本文复制其内部设计。
本文是 **GoodBuddy 客户端实现的权威设计**。服务端字段、状态码和限制以反馈平台
发布的版本化公共 API 契约为准;两边的同名契约必须通过兼容性测试保持一致,不能各自
扩展后继续沿用相同的 `schemaVersion`。
---
## 1. 摘要与核心决策
1. 用户在“设置 → 关于与更新”内直接提交问题、功能建议、体验意见或其他反馈,不再跳转
GitHub Issue 页面。
2. Renderer 只负责表单、截图预览和状态展示。所有网络请求、安装标识、应用版本和系统
环境组装都在 Main 中完成。
3. Preload 只暴露用途明确的 `feedback.submit()` 方法,不暴露任意 URL、通用
`fetch`、Electron API、文件路径或服务端管理能力。
4. 公共请求固定使用版本化接口 `POST /api/v1/feedback`,GoodBuddy 的
`productKey` 固定为 `goodbuddy`。
5. 反馈服务的生产 HTTPS Origin 是客户端源代码中的受信产品常量,不是用户设置,不从
Renderer、项目、Runtime、环境变量、远程配置或服务端重定向中获取。
6. GoodBuddy 当前的全局 TLS 策略允许模型和内网服务使用自签名或过期证书。反馈可能包含
用户输入和截图,因此 **反馈客户端不得复用全局不校验证书的 Dispatcher 或
`globalThis.fetch`**,必须使用独立的 Undici Dispatcher 并显式启用正常证书校验。
7. 首版允许一张可选截图。截图只能由用户主动选择或粘贴,发送前必须预览并允许移除;
GoodBuddy 不自动截屏。原始输入和 PNG 标准化结果都不能超过 5 MiB。
8. Main 在发送前解码并重编码截图为 PNG,确保原始 EXIF/XMP 等元数据不会离开设备;
服务端仍独立执行自己的图片验证和标准化。
9. 一次新反馈生成一个 `clientRequestId`。网络失败后的手动重试必须复用原 ID,服务端
以此保证幂等;创建另一条反馈时才生成新 ID。
10. GoodBuddy 使用单独的随机安装 UUID 做服务端限流。该 UUID 不得复用 Agent
installation ID、硬件 ID、机器名、用户名、项目 ID 或任何设备指纹。
11. 默认不发送对话、提示词、模型回复、诊断、日志、文件、项目或工作区路径、知识库、模型
配置、Runtime 配置、API Key、Token、Cookie、SSH 信息、剪贴板或屏幕内容。用户可通过
默认关闭的 Checkbox 主动附加有界的最近桌面诊断摘要。
12. 反馈提交失败时保留全部表单内容和截图,成功后显示可复制的服务端反馈编号。
13. 首版不提供反馈历史、状态查询、编辑、撤回、自动日志收集或服务端管理入口。
14. 生产接口固定为 `https://imp.mesalogo.com/api/v1/feedback`。域名健康检查、
TLS、v1 请求校验和客户端调用链必须在发布前一起通过。
---
## 2. 目标与非目标
### 2.1 用户目标
- 不离开 GoodBuddy 即可报告问题或提出建议。
- 在发送前明确知道哪些字段会离开设备。
- 只在主动选择后上传截图,并能检查、替换或移除截图。
- 网络失败后无需重新填写内容。
- 提交成功后获得稳定编号,便于后续沟通。
### 2.2 产品目标
- 让 GoodBuddy 成为独立多产品反馈平台的第一个客户端。
- 保持 Electron 上下文隔离和 Main-only 网络边界。
- 使用小而稳定的版本化契约,不把服务端工单、数据库和管理员概念泄漏到客户端。
- 对 Windows、macOS、Linux 的 x64 与 arm64 使用相同行为。
- 以数据最小化为默认,不把应用内反馈演变为隐式遥测或日志上传。
### 2.3 非目标
首版不包含:
- GitHub Issue 创建、GitHub Token 或 GitHub 网页附件上传。
- GoodBuddy 内的管理员后台、处理状态修改或内部备注。
- 用户账户、登录、反馈列表、远程状态查询或推送通知。
- 自动附加当前对话、桌面诊断、日志、错误堆栈、项目、文件、模型或 Runtime 信息。
- 远端 Agent 日志或通用日志、诊断包和附件框架。
- 自动截取全屏、当前窗口、浏览器、远程桌面或其他应用窗口。
- 多截图、视频、录屏、任意文件附件或压缩包。
- 离线队列、后台自动重试、指数退避、跨重启草稿或发送历史。
- 用户可编辑的反馈 Endpoint、证书豁免、代理凭据或服务端 API Key。
- 由 GoodBuddy 推断优先级、自动分类、自动生成描述或代表用户提交。
---
## 3. 用户体验
### 3.1 入口
入口放在现有“关于与更新”页面,不增加新的设置一级分类。
在当前版本与更新卡片之后增加“帮助改进 GoodBuddy”能力卡:
- 标题:`帮助改进 GoodBuddy`
- 说明:`报告问题、提出建议或反馈使用体验,我们会在独立反馈系统中处理。`
- 主操作:`提交反馈`
该卡片不显示反馈平台内部状态、管理员信息或 GitHub 链接。服务不可用时仍允许用户打开
表单和保留输入;提交失败后提供重试,而不是把用户重定向到另一个渠道。
### 3.2 反馈对话框
对话框字段按以下顺序排列:
1. **反馈类型**,必填:
- 问题:`bug`
- 功能建议:`feature`
- 使用体验:`experience`
- 其他:`other`
2. **标题**,必填,1 至 120 字符。
3. **详细描述**,必填,去除首尾空白后 10 至 5,000 字符;选择桌面诊断时为摘要预留空间,
用户描述上限为 3,398 字符。
4. **附加最近桌面诊断记录**,可选的原生 Checkbox,默认关闭。
5. **联系邮箱**,可选,最多 254 字符。
6. **截图**,可选,最多一张,支持选择 PNG/JPEG/WebP 或在对话框中粘贴图片。
7. **将发送的信息**,只读显示应用版本、系统、架构和界面语言。
8. **隐私说明**,紧邻提交操作。
隐私说明使用明确文案:
> 将发送反馈类型、标题、描述、可选邮箱、GoodBuddy 版本、操作系统、架构、界面语言,
> 以及你主动添加的截图。默认不上传桌面诊断;只有勾选后才会把有界诊断摘要追加到描述。
> 不会发送对话、Prompt、凭据、文件内容、路径、Provider 原始响应或远端 Agent 日志。
Checkbox 下方持续说明:诊断摘要只含时间、组件、阶段、稳定错误码、错误类型和固定短消息。
Renderer 不预览、不读取也不接收诊断内容或路径。勾选后详细描述上限为 3,398 字符,为附加
摘要预留 1,600 字符以及分隔换行;超长草稿保留在表单中并显示字段错误,不静默截断。
截图区域必须包含:
- 缩略预览。
- 尺寸和所选文件大小;Main 仍在发送前独立执行 PNG 标准化和大小检查。
- `替换截图` 与 `移除截图`。
- `截图可能包含画面中可见的个人信息,请在发送前检查。`
### 3.3 提交状态
| 状态 | 行为 |
| --- | --- |
| 未填写 | 字段保持可编辑,提交按钮按字段有效性启用 |
| 校验失败 | 在对应字段旁显示错误并把焦点移到第一个无效字段 |
| 提交中 | 禁止重复提交、关闭和修改字段,按钮显示“正在提交…” |
| 网络或服务失败 | 保留字段和截图,在表单内显示可操作错误及“重试” |
| 成功 | 对话框切换为确认面板,显示并允许复制反馈编号 |
提交成功属于必须保留编号的上下文结果,不再同时发送一条重复的全局成功通知。异步失败也
保留在对话框内,因为错误与当前草稿和重试操作直接关联。
### 3.4 可访问性
- 对话框使用 `role="dialog"`、`aria-modal="true"` 和可见标题。
- 整窗遮罩通过 React portal 挂到 `document.body`,遵循
[全局遮罩与原生拖动区域规则](../../../UI-DESIGN.md#610-应用顶栏与全局操作)。
- 复用 `activateModalFocus()` 与 `trapTabFocus()`,打开后聚焦反馈类型,关闭后恢复入口
按钮焦点。
- 非提交状态支持 Escape 关闭;提交中保持对话框打开,直到请求完成或到达有界超时。
- 字段使用原生 `label`、`select`、`input` 和 `textarea`。
- 图片预览提供说明性替代文本,删除和替换按钮有明确可访问名称。
- 加载、失败和成功状态使用文字,不依赖颜色。
- 浅色、深色和 `prefers-reduced-motion` 行为遵循 `UI-DESIGN.md`。
---
## 4. 数据与隐私边界
### 4.1 明确发送的数据
| 数据 | 来源 | 用途 |
| --- | --- | --- |
| `schemaVersion` | Main 常量 | 选择公共契约版本 |
| `productKey` | Main 常量 `goodbuddy` | 服务端产品路由 |
| `category` | 用户选择 | 分流反馈 |
| `title` | 用户输入 | 摘要 |
| `description` | 用户输入;勾选时由 Main 追加桌面诊断摘要 | 问题、建议和主动提供的有界诊断详情 |
| `contactEmail` | 用户可选输入 | 必要时联系用户 |
| `environment.appVersion` | `app.getVersion()` | 判断受影响版本 |
| `environment.platform` | `process.platform` 映射 | 判断受影响平台 |
| `environment.architecture` | `process.arch` 映射 | 判断受影响架构 |
| `environment.locale` | 当前 GoodBuddy UI 语言 | 理解用户语言环境 |
| `installationId` | Main 本地随机 UUID | 服务端安装实例限流 |
| `clientRequestId` | 当前草稿随机 UUID | 幂等和安全重试 |
| `screenshot` | 用户主动选择或粘贴 | 复现视觉问题 |
客户端不主动发送来源 IP;IP 是 HTTPS 请求到达服务端时产生的网络元数据。服务端如何保存、
散列和保留该元数据由独立反馈平台的隐私与运维策略负责。
用户填写联系邮箱后,邮箱会作为业务字段通过 HTTPS 发送并由反馈平台保存,不属于端到端
加密内容。用户不希望提供联系方式时应留空,GoodBuddy 不尝试从系统账户或其他设置推断
邮箱。
### 4.2 默认不发送的数据
以下数据不能因为“有助于复现”而隐式加入请求:
- 当前或历史对话、提示词、模型回复和 Supervisor 内容。
- GoodBuddy、Runtime、Agent、SSH、消息通道、浏览器或操作系统日志。
- 文件内容、附件、知识库、Magic Notes、成果和工作区改动。
- 文件名、项目名、本地路径、远程路径、主机名、用户名或 IP 配置。
- LLM、向量模型、语音模型、OCR、MCP、Skill、Runtime 或通道配置。
- API Key、Access Token、Cookie、SSH 密码、私钥、代理凭据和其他秘密。
- 剪贴板、屏幕、摄像头、麦克风或其他应用内容。
- Remote Agent installation ID、Runtime ID、Conversation ID、Task ID 或 Project ID。
除上述用户主动选择、追加到描述且有界的桌面诊断摘要外,未来若增加日志、诊断包或其他
附件,必须单独设计字段、预览、大小限制、用户选择和服务端保留策略,不能复用截图字段
静默上传。
### 4.3 本地数据生命周期
- 表单草稿、邮箱和截图只保存在当前 Renderer 内存中。
- 首版不把草稿、失败请求、成功响应或截图写入 GoodBuddy 数据库和设置文件。
- Main 只在请求期间持有标准化图片字节,请求结束后释放引用,不创建临时图片文件。
- 成功后关闭确认面板时清空草稿。
- `installationId` 单独保存在用户数据目录中的
`feedback-identity.json`,格式为版本化 JSON,文件权限沿用私有原子设置文件机制。
- “清除本地数据”必须中止进行中的反馈请求并删除该身份文件;下次提交会生成新 UUID。
---
## 5. 公共 API 对接
### 5.1 外部契约所有权
反馈平台负责维护公共 API 的权威定义。GoodBuddy 只固定客户端实际需要的版本 1 子集:
```text
POST https://imp.mesalogo.com/api/v1/feedback
```
当前生产 Endpoint 已作为 Main-only 产品常量固定。实现和发布验证必须持续保证:
- 使用 `https:`。
- URL 不含用户名、密码、查询参数或 fragment。
- Path 精确为 `/api/v1/feedback`。
- 对应产品 `goodbuddy` 已在服务端启用。
- 桌面端所在网络能够直接访问该 Origin。
Renderer、Preload、用户设置、环境变量和远程配置都不能覆盖该 Endpoint。域名迁移必须
同时修改客户端受信常量、测试、服务端 Origin 配置和部署文档。
### 5.2 请求 JSON
无截图时发送 `application/json`:
```json
{
"schemaVersion": 1,
"productKey": "goodbuddy",
"category": "bug",
"title": "魔法笔记页签不可见",
"description": "平台功能中的魔法笔记页签被布局压缩,无法看到或点击。",
"contactEmail": "user@example.com",
"environment": {
"appVersion": "0.11.0",
"platform": "windows",
"architecture": "x64",
"locale": "zh-CN"
},
"installationId": "00000000-0000-4000-8000-000000000101",
"clientRequestId": "00000000-0000-4000-8000-000000000102"
}
```
有截图时发送 `multipart/form-data`:
- `payload`:同一 JSON 的字符串形式。
- `screenshot`:一张标准化 PNG,文件名固定为 `feedback.png`。
`contactEmail` 为空时省略,不发送空字符串。请求对象不附加服务端未声明字段。
### 5.3 成功响应
HTTP 201 表示首次创建,HTTP 200 表示相同 `clientRequestId` 的幂等结果:
```json
{
"reference": "GOODBUDDY-000001",
"duplicate": false
}
```
客户端对两种成功状态使用相同确认界面。`duplicate: true` 不能显示为失败,它表示先前请求
已经成功落库。
### 5.4 错误映射
| HTTP / 错误 | 用户结果 |
| --- | --- |
| 400 | 提示提交内容无效,保留草稿;同时记录为客户端契约缺陷 |
| 403 | 提示客户端与反馈服务配置不匹配 |
| 404 | 提示反馈服务暂不可用 |
| 413 | 提示截图或请求过大,允许移除截图后重试 |
| 429 | 提示提交过于频繁,保留草稿并让用户稍后手动重试 |
| 500–599 | 提示服务暂时异常,保留草稿 |
| 超时 / DNS / TLS / 离线 | 提示无法连接反馈服务,保留草稿 |
| 响应格式错误 | 提示服务返回无效结果,不能猜测成功 |
| 本地诊断读取失败 | 不发送反馈,保留草稿、勾选状态和请求 ID,提示重试或取消勾选 |
首版不自动重试。自动重试容易让用户无法判断是否已经发送,也会在服务持续异常时制造额外
流量。用户点击“重试”时复用同一个 `clientRequestId`。
---
## 6. 进程与模块设计
### 6.1 调用链
```text
UpdateSettingsSection / FeedbackDialog
→ window.goodbuddy.feedback.submit(input)
→ preload: ipcRenderer.invoke(feedbackSubmit)
→ Main IPC: trusted sender + Zod parse
→ FeedbackService: identity + environment + screenshot normalization
→ StrictFeedbackHttpClient
→ POST /api/v1/feedback
→ response schema parse
→ Renderer confirmation or inline retry state
```
### 6.2 Shared
新增 `src/shared/feedback-contracts.ts`,包含:
- `feedbackCategorySchema`
- `feedbackDraftSchema`
- `feedbackScreenshotInputSchema`
- `feedbackSubmitInputSchema`
- `feedbackSubmitResultSchema`
- `feedbackPublicPayloadSchema`
- 公共字段长度和截图限制常量
推荐客户端 IPC 输入:
```ts
type FeedbackSubmitInput = {
category: 'bug' | 'feature' | 'experience' | 'other'
title: string
description: string
includeDiagnostics: boolean
contactEmail?: string
locale: 'zh-CN' | 'en-US'
clientRequestId: string
screenshot?: {
data: Uint8Array
mimeType: 'image/png' | 'image/jpeg' | 'image/webp'
}
}
```
Renderer 不能传入 `productKey`、`appVersion`、`platform`、`architecture` 或
`installationId`,也不能传入诊断内容或路径。这些字段由 Main 从受信运行环境生成。
### 6.3 IPC 与 Preload
新增:
```text
ipcChannels.feedbackSubmit = "feedback:submit"
```
`DesktopApi` 新增必选窄接口:
```ts
feedback: {
submit: (input: FeedbackSubmitInput) => Promise<FeedbackSubmitResult>
}
```
Main handler 必须:
1. 调用现有 `assertTrustedSender(event, window)`。
2. 使用 Shared Zod schema 解析 `unknown` 输入。
3. 不接收 URL、文件路径、Cookie、Header 或任意请求选项。
4. 由 `FeedbackService` 跟踪活动 Promise,应用关闭或清除数据时先取消再有界等待。
Preload 只转发已声明输入,不做网络请求或图片文件读取。
反馈 handler 独立放在 `feedback/feedback-ipc.ts`,复用共享的
`assertTrustedSender()`,避免继续扩展通用 `registerIpcHandlers()` 的位置参数列表。
### 6.4 FeedbackIdentityStore
新增 Main-only `FeedbackIdentityStore`:
- 文件:`<userData>/feedback-identity.json`
- Schema:
```json
{
"version": 1,
"installationId": "00000000-0000-4000-8000-000000000101"
}
```
- 缺失时使用 `crypto.randomUUID()` 创建。
- 使用 `writeJsonFileAtomically()` 写入 0600 临时文件并原子替换。
- 读取时使用严格 schema。
- 损坏时隔离原文件并生成新身份,不读取或复用 Remote Agent 身份。
- 并发首次读取必须合并为同一个 Promise,不能生成两个 UUID。
- `clear()` 删除文件和内存缓存,供“清除本地数据”调用。
该 UUID 是可重置的应用安装级随机标识,不声称代表真实设备或用户。
### 6.5 FeedbackService
Main-only `FeedbackService` 负责:
- 读取或创建安装 UUID。
- 映射应用版本、平台和架构。
- 验证并标准化可选截图。
- 组装严格的公共 payload。
- 仅在 `includeDiagnostics: true` 时通过注入的 DesktopDiagnostics provider 读取最多 20 条
最近记录;默认提交完全不调用诊断 provider。
- 将诊断格式化为带
`[GOODBUDDY_DESKTOP_DIAGNOSTICS_V1_BEGIN]` /
`[GOODBUDDY_DESKTOP_DIAGNOSTICS_V1_END]` 明确分隔、最多 1,600 字符且只保留完整 JSON
行的摘要。无记录时写入固定 `{"status":"no-recent-diagnostics"}` 事实。
- 调用 HTTP Client。
- 维持单次提交的 AbortController,并在 dispose / 清除本地数据时取消。
平台映射固定为:
| Node 值 | API 值 |
| --- | --- |
| `win32` | `windows` |
| `darwin` | `macos` |
| `linux` | `linux` |
| 其他 | `unknown` |
架构只保留 `x64` 和 `arm64`,其他值映射为 `unknown`。
公共 API 继续使用 `schemaVersion: 1` 和原有固定字段。`includeDiagnostics` 只属于客户端
IPC 输入,不进入公共 payload,也不新增 multipart 字段;Main 把摘要追加到
`payload.description`。默认未勾选时,公共 payload 与既有实现逐字段一致。诊断读取失败时
不得发送部分摘要或声称已附加,必须返回可本地化的 `diagnostics-unavailable` 错误。
### 6.6 严格 HTTPS Client
反馈客户端使用 `undici` 的独立 `Agent`:
```ts
new Agent({
connect: {
rejectUnauthorized: true
}
})
```
这是必要边界,不是额外配置。GoodBuddy 的 `GlobalTlsPolicy` 会给模型、MCP 和内网服务
安装不校验证书的全局 Dispatcher;反馈内容不能经过该 Dispatcher。
每次请求必须满足:
- 固定、预先校验的 HTTPS URL。
- 使用 Undici 原始 `request()`,不安装重定向处理器;任何 3xx 都按服务不可用处理。
- 不使用 Cookie Jar、缓存、Referrer 或浏览器凭据模式。
- `Accept: application/json`。
- 固定、非敏感 `User-Agent`,例如 `GoodBuddy-Feedback/<version>`。
- 不发送 `Authorization`、Cookie、API Key 或自定义代理凭据。
- 总超时建议 15 秒,最大不得超过 30 秒。
- 成功响应体最多 16 KiB,并检查流式实际字节数,不能只相信
`Content-Length`。
- 只接受 JSON `Content-Type` 和 JSON 响应体,并使用
`feedbackSubmitResultSchema` 解析。
- 日志只记录有界错误类别和 HTTP 状态,不记录请求正文、邮箱、截图、安装 UUID、
`clientRequestId` 或响应编号。
测试可以注入 Endpoint、Dispatcher 和 Request Transport。生产构造路径不得读取测试覆盖值或
环境变量。
---
## 7. 截图处理
### 7.1 Renderer
- 文件输入限制为 `.png,.jpg,.jpeg,.webp`。
- 原始文件上限固定为 5 MiB,超过后不读取完整内容或调用 IPC。
- 粘贴事件只处理图片,不拦截普通文本粘贴。
- 读取 `File.arrayBuffer()` 后先执行 Shared 字节上限校验。
- 使用临时 Object URL 预览,替换、移除或关闭对话框时立即
`URL.revokeObjectURL()`。
- 不调用 `webUtils.getPathForFile()`,不把本地文件路径传给 Main 或服务端。
### 7.2 Main
Main 不信任 Renderer 声明的 MIME:
1. 校验 PNG、JPEG、WebP 文件签名。
2. 使用 `nativeImage.createFromBuffer()` 实际解码。
3. 拒绝空图片、异常尺寸、超过 API 公开上限的像素数。
4. 使用 `toPNG()` 重编码,去除原始元数据。
5. 检查标准化后字节数不超过 5 MiB,最大边长不超过 8,192 像素,总像素数不超过
3,200 万。
6. 仅把标准化 PNG 放入当前网络请求,不写入磁盘。
客户端和服务端都执行图片验证属于有意的纵深防御:客户端保证原始元数据不离开设备,
服务端保证不能信任任意客户端。
---
## 8. Renderer 实施落点
### 8.1 组件
- `UpdateSettingsSection.tsx`
- 继续负责应用信息和更新卡片。
- 渲染反馈入口卡片。
- `FeedbackDialog.tsx`
- 独立管理草稿、预览、校验、提交、重试和成功编号。
- 关闭后销毁草稿状态。
对话框不与更新检查状态共用 `saving`、`checking` 或 `error`,避免一个功能失败覆盖另一个
功能的状态。
### 8.2 国际化
在 `settingsSections` 的 `zh-CN` 与 `en-US` 资源中增加同构键:
- 入口卡片标题、说明和按钮。
- 类型名称。
- 字段标签、占位、帮助和校验。
- 截图选择、替换、移除和隐私提醒。
- 提交中、失败、重试、成功和复制编号。
- 数据发送说明。
组件不得内嵌只存在于一种语言的产品文案。
### 8.3 样式
- 复用 `capability-card`、`field`、`primary-button`、`secondary-button` 和现有
对话框层级。
- 新样式使用 `UI-DESIGN.md` 的语义颜色、间距、圆角和动效令牌。
- 对话框桌面端最大宽度建议 600px;窄窗口占可用宽度并保持 16px 页面边距。
- 不为反馈入口创建新的一级页面、页签视觉或高饱和营销卡片。
---
## 9. 失败、生命周期与恢复
### 9.1 幂等
- 打开新草稿时生成 `clientRequestId`。
- 字段编辑、截图替换、网络失败和手动重试都不改变 ID。
- 成功或显式丢弃草稿后,下一条反馈生成新 ID。
- 同一 ID 返回原编号时按成功处理。
### 9.2 并发
- 对话框同一时间只允许一个提交。
- Main 同一 `clientRequestId` 只运行一个请求。
- Main 全局同一时间只发送一条反馈;相同 ID 的重复调用合并,不同 ID 在已有请求期间返回
有界 `busy` 结果,不能并行保留多份 5 MiB 截图。
- 读取截图使用选择代次;旧文件后完成时必须丢弃并释放 Object URL,不能覆盖较新的截图。
### 9.3 应用退出与清除数据
- 反馈请求有独立 AbortController。
- 应用退出时取消进行中的请求并关闭严格 TLS Dispatcher。
- “清除本地数据”先取消请求,再删除 `feedback-identity.json`。
- 失败或取消不写入本地“已发送”状态,服务端是否已接收由同 ID 重试确认。
### 9.4 服务不可用
反馈平台不可用时:
- 保留表单和截图。
- 显示明确网络或服务错误。
- 不静默丢弃、不自动切换 GitHub、不打开网页、不自动重试。
- 其他 GoodBuddy 功能继续可用。
---
## 10. 实施文件
当前实现涉及:
| 文件或目录 | 变更 |
| --- | --- |
| `src/shared/feedback-contracts.ts` | 输入、公共 payload、响应和限制 schema |
| `src/shared/ipc-channels.ts` | `feedbackSubmit` channel |
| `src/shared/contracts.ts` | `DesktopApi.feedback` |
| `src/main/feedback/feedback-identity-store.ts` | 随机安装 ID |
| `src/main/feedback/feedback-screenshot.ts` | 签名、解码和 PNG 标准化 |
| `src/main/feedback/feedback-http-client.ts` | 固定 Endpoint、严格 TLS、有界响应 |
| `src/main/feedback/feedback-service.ts` | payload 组装与生命周期 |
| `src/main/feedback/feedback-ipc.ts` | 独立 trusted sender、schema parse 和 submit handler |
| `src/main/trusted-ipc-sender.ts` | Main IPC 共享 sender / main Frame 校验 |
| `src/main/index.ts` | Main service 构造、清理和 IPC 注入 |
| `src/preload/index.ts` | 窄 `feedback.submit()` bridge |
| `src/renderer/src/FeedbackDialog.tsx` | 表单和成功/失败流程 |
| `src/renderer/src/UpdateSettingsSection.tsx` | About 入口卡片 |
| `src/renderer/src/i18n/locales/*/settingsSections.ts` | 中英文文案 |
| `src/renderer/src/styles.css` | 令牌化布局与响应式样式 |
每个生产文件应有相邻或现有测试文件覆盖。不要把 Endpoint、截图处理或网络逻辑塞进
`ipc.ts` 或 React 组件。
---
## 11. 验证
以下 Shared、Main、IPC、Preload 和 Renderer 行为均由相邻或现有自动化测试覆盖;生产
Endpoint 的健康检查和无副作用 schema 探针也必须在发布前通过。
### 11.1 Shared 合同
- 接受四种反馈类型和有效字段。
- 拒绝未知字段、空标题、短描述、超长描述、错误邮箱、非 UUID 和多截图。
- 对截图按字节限制,而不是字符串长度限制。
- 请求与反馈平台 `schemaVersion: 1` fixture 双向兼容。
### 11.2 Main 单元测试
- 安装 ID 首次创建、并发读取、持久化、损坏隔离和清除。
- 不复用 Agent、Runtime、Project 或硬件身份。
- 平台和架构映射。
- PNG/JPEG/WebP 签名、实际解码、尺寸限制、PNG 重编码和元数据不保留。
- 无截图 JSON、有截图 multipart、请求 Header 和固定 URL。
- 独立 Dispatcher 显式 `rejectUnauthorized: true`。
- 拒绝重定向、非 HTTPS、错误 Origin、超时、超大响应、非 JSON 和错误 response schema。
- 4xx、429、5xx 和网络错误映射。
- 手动重试复用 `clientRequestId`。
- 不同 ID 的并发请求被有界拒绝,清除本地数据期间不能开始新提交。
### 11.3 IPC 与 Preload
- 未知窗口和非主 Frame 被拒绝。
- Renderer 不能覆盖 `productKey`、版本、平台、架构、安装 ID、Endpoint 或 Header。
- Preload 只暴露 `feedback.submit()`。
- Sandbox 测试验证没有通用网络或文件路径能力泄漏。
### 11.4 Renderer
- About 页面展示反馈卡片。
- 表单字段、字符限制、中英文文案和隐私说明。
- 普通文本粘贴不被图片处理拦截。
- 截图选择、预览、替换、移除和 Object URL 清理。
- 并发截图读取按最新选择生效,关闭对话框后不会留下待处理 Object URL。
- 失败保留字段、诊断勾选状态,重试使用原 `clientRequestId`。
- 默认提交不读取诊断且公共 payload 不变;主动附加、描述预算、无记录和读取失败均有覆盖。
- 成功显示真实编号并清空后续新草稿。
- Dialog role、初始焦点、Tab 循环、Escape 和焦点恢复。
- 浅色、深色、窄窗口和 reduced motion。
### 11.5 真实集成
2026-08-25 已从隔离配置的真实 Electron 生产构建完成一次端到端合成提交:
- 标题:`[GOODBUDDY INTEGRATION TEST] 2026-08-25`
- 类型:`other`
- 联系邮箱:未填写
- 截图:GoodBuddy 公共应用图标,经 Main 重编码后以 PNG multipart 上传
- 服务端编号:`GOODBUDDY-000001`
该结果确认 About UI、Renderer、Preload、trusted IPC、安装身份、Main 图片处理、独立严格
TLS Dispatcher 和生产 v1 API 调用链可以共同工作。发布候选仍应按以下清单复验:
1. 从打包候选的 About 页面提交一条明确标记的合成反馈。
2. 可选添加一张不含个人数据的测试截图。
3. 确认客户端显示的编号与管理后台一致。
4. 确认版本、平台、架构和语言正确。
5. 确认服务端只保存标准化 PNG,不含原始图片元数据。
6. 断网后提交,确认草稿保留;恢复网络后以同一 ID 重试且只产生一条记录。
7. 使用无效证书的测试服务,确认反馈客户端拒绝连接,即使 GoodBuddy 全局内网 TLS
策略允许其他服务继续工作。
8. 检查 Renderer 和 Main 日志不含正文、邮箱、截图、安装 ID 或凭据。
最终仍需运行仓库统一验证:
```text
npm test
npm run typecheck
npm run lint
npm run build
```
---
## 12. 发布与兼容性
- `schemaVersion: 1` 与 `/api/v1/feedback` 是同一版本边界。
- 向请求增加必填字段、修改枚举或改变字段语义属于破坏性变更,必须发布新 schema 或新
API 版本。
- GoodBuddy 对响应执行严格解析。服务端需要扩展响应时,应先协调客户端兼容策略。
- Endpoint 域名变化属于客户端发布变更,不能依赖 301/302 迁移。
- 入口发布与 `goodbuddy` 产品启用状态、生产 TLS 和 v1 契约保持同一发布门槛。
- 此功能不迁移现有用户数据;只新增可重置的随机反馈安装身份。
- 发布说明应明确:用户现在可以在“关于与更新”内直接提交反馈,邮箱和截图可选;桌面诊断
默认不上传,只有用户勾选后才附加有界摘要,并且不会发送对话、Prompt、凭据、文件内容、
路径、Provider 原始响应或远端 Agent 日志。
---
## 13. 当前对接状态
截至 2026-08-25:
1. 生产 Origin 已确定为 `https://imp.mesalogo.com`,HTTPS、HSTS 和健康检查正常。
2. `/api/v1/feedback` 已返回符合 v1 契约的严格校验错误,无副作用探针未创建记录。
3. `goodbuddy` 产品已启用,实际提交返回 `GOODBUDDY-000001`。
4. GoodBuddy 客户端已完成 Shared、Main、IPC、Preload、About UI、双语文案、截图和
生命周期实现。
5. 服务端公开 schema、字段长度和 5 MiB 截图上限与客户端一致。
6. 隔离 Electron 实例已完成浅色、深色、600×800 窄窗口、焦点与背景隔离、截图预览和
真实生产提交验收。