diff --git a/docs/README.md b/docs/README.md index b276fece..8f43e130 100644 --- a/docs/README.md +++ b/docs/README.md @@ -33,6 +33,7 @@ GoodBuddy 文档按“文档类型 → 功能域”组织。新增文档应先 - [本地文本向量模型与连接设计](./architecture/local-text-embedding-model-design.md) - [全双工实时语音交互设计](./architecture/full-duplex-voice-design.md) - [DeepSeek Harness Runtime 设计](./architecture/deepseek-harness-runtime-design.md) +- [应用内反馈系统设计与对接](./architecture/in-app-feedback-integration-design.md) - [跨平台助手产品设计](./design/cross-platform-assistant-product-design.md) - [长期助手路线图](./roadmap/long-term-assistant-roadmap.md) - [电脑控制实施状态](./status/computer-control-implementation-status.md) diff --git a/docs/architecture/in-app-feedback-integration-design.md b/docs/architecture/in-app-feedback-integration-design.md new file mode 100644 index 00000000..02d5776a --- /dev/null +++ b/docs/architecture/in-app-feedback-integration-design.md @@ -0,0 +1,667 @@ +# GoodBuddy 应用内反馈系统设计与对接 + +## 文档信息 + +| 项目 | 内容 | +| --- | --- | +| 文档类型 | 跨功能产品与技术架构 | +| 状态 | 待实施 | +| 版本 | 0.1 | +| 日期 | 2026-08-24 | +| 适用产品 | GoodBuddy 桌面端 | +| 目标平台 | Windows、macOS、Linux,x64 与 arm64 | +| 相关基线 | [统一界面设计系统](../../UI-DESIGN.md)、[GoodBuddy 文档导航](../README.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 信息、剪贴板或屏幕内容。 +12. 反馈提交失败时保留全部表单内容和截图,成功后显示可复制的服务端反馈编号。 +13. 首版不提供反馈历史、状态查询、编辑、撤回、自动日志收集或服务端管理入口。 +14. 在真实反馈域名和可用服务尚未就绪时,不上线不可用按钮或占位入口。客户端和服务端应 + 在同一发布窗口完成可用性验证。 + +--- + +## 2. 目标与非目标 + +### 2.1 用户目标 + +- 不离开 GoodBuddy 即可报告问题或提出建议。 +- 在发送前明确知道哪些字段会离开设备。 +- 只在主动选择后上传截图,并能检查、替换或移除截图。 +- 网络失败后无需重新填写内容。 +- 提交成功后获得稳定编号,便于后续沟通。 + +### 2.2 产品目标 + +- 让 GoodBuddy 成为独立多产品反馈平台的第一个客户端。 +- 保持 Electron 上下文隔离和 Main-only 网络边界。 +- 使用小而稳定的版本化契约,不把服务端工单、数据库和管理员概念泄漏到客户端。 +- 对 Windows、macOS、Linux 的 x64 与 arm64 使用相同行为。 +- 以数据最小化为默认,不把应用内反馈演变为隐式遥测或日志上传。 + +### 2.3 非目标 + +首版不包含: + +- GitHub Issue 创建、GitHub Token 或 GitHub 网页附件上传。 +- GoodBuddy 内的管理员后台、处理状态修改或内部备注。 +- 用户账户、登录、反馈列表、远程状态查询或推送通知。 +- 自动附加当前对话、日志、错误堆栈、项目、文件、模型或 Runtime 信息。 +- 自动截取全屏、当前窗口、浏览器、远程桌面或其他应用窗口。 +- 多截图、视频、录屏、任意文件附件或压缩包。 +- 离线队列、后台自动重试、指数退避、跨重启草稿或发送历史。 +- 用户可编辑的反馈 Endpoint、证书豁免、代理凭据或服务端 API Key。 +- 由 GoodBuddy 推断优先级、自动分类、自动生成描述或代表用户提交。 + +--- + +## 3. 用户体验 + +### 3.1 入口 + +入口放在现有“关于与更新”页面,不增加新的设置一级分类。 + +在当前版本与更新卡片之后增加“帮助改进 GoodBuddy”能力卡: + +- 标题:`帮助改进 GoodBuddy` +- 说明:`报告问题、提出建议或反馈使用体验,我们会在独立反馈系统中处理。` +- 主操作:`提交反馈` + +该卡片不显示反馈平台内部状态、管理员信息或 GitHub 链接。服务不可用时仍允许用户打开 +表单和保留输入;提交失败后提供重试,而不是把用户重定向到另一个渠道。 + +### 3.2 反馈对话框 + +对话框字段按以下顺序排列: + +1. **反馈类型**,必填: + - 问题:`bug` + - 功能建议:`feature` + - 使用体验:`experience` + - 其他:`other` +2. **标题**,必填,1 至 120 字符。 +3. **详细描述**,必填,去除首尾空白后 10 至 5,000 字符。 +4. **联系邮箱**,可选,最多 254 字符。 +5. **截图**,可选,最多一张,支持选择 PNG/JPEG/WebP 或在对话框中粘贴图片。 +6. **将发送的信息**,只读显示应用版本、系统、架构和界面语言。 +7. **隐私说明**,紧邻提交操作。 + +隐私说明使用明确文案: + +> 将发送反馈类型、标题、描述、可选邮箱、GoodBuddy 版本、操作系统、架构、界面语言, +> 以及你主动添加的截图。不会自动发送对话、日志、文件、项目路径、模型配置或凭据。 + +截图区域必须包含: + +- 缩略预览。 +- 尺寸和标准化后的大小。 +- `替换截图` 与 `移除截图`。 +- `截图可能包含画面中可见的个人信息,请在发送前检查。` + +### 3.3 提交状态 + +| 状态 | 行为 | +| --- | --- | +| 未填写 | 字段保持可编辑,提交按钮按字段有效性启用 | +| 校验失败 | 在对应字段旁显示错误并把焦点移到第一个无效字段 | +| 提交中 | 禁止重复提交、关闭和修改字段,按钮显示“正在提交…” | +| 网络或服务失败 | 保留字段和截图,在表单内显示可操作错误及“重试” | +| 成功 | 对话框切换为确认面板,显示并允许复制反馈编号 | + +提交成功属于必须保留编号的上下文结果,不再同时发送一条重复的全局成功通知。异步失败也 +保留在对话框内,因为错误与当前草稿和重试操作直接关联。 + +### 3.4 可访问性 + +- 对话框使用 `role="dialog"`、`aria-modal="true"` 和可见标题。 +- 复用 `activateModalFocus()` 与 `trapTabFocus()`,打开后聚焦反馈类型,关闭后恢复入口 + 按钮焦点。 +- 非提交状态支持 Escape 关闭;提交中保持对话框打开,直到请求完成或到达有界超时。 +- 字段使用原生 `label`、`select`、`input` 和 `textarea`。 +- 图片预览提供说明性替代文本,删除和替换按钮有明确可访问名称。 +- 加载、失败和成功状态使用文字,不依赖颜色。 +- 浅色、深色和 `prefers-reduced-motion` 行为遵循 `UI-DESIGN.md`。 + +--- + +## 4. 数据与隐私边界 + +### 4.1 明确发送的数据 + +| 数据 | 来源 | 用途 | +| --- | --- | --- | +| `schemaVersion` | Main 常量 | 选择公共契约版本 | +| `productKey` | Main 常量 `goodbuddy` | 服务端产品路由 | +| `category` | 用户选择 | 分流反馈 | +| `title` | 用户输入 | 摘要 | +| `description` | 用户输入 | 问题和建议详情 | +| `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 /api/v1/feedback +``` + +GoodBuddy 实现前必须获得真实生产 Origin,并完成以下检查: + +- 使用 `https:`。 +- URL 不含用户名、密码、查询参数或 fragment。 +- Path 精确为 `/api/v1/feedback`。 +- 对应产品 `goodbuddy` 已在服务端启用。 +- 桌面端所在网络能够直接访问该 Origin。 + +生产 Endpoint 不写入文档示例值后直接发布。域名迁移必须同时修改客户端受信常量、测试、 +服务端 CORS/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 / 离线 | 提示无法连接反馈服务,保留草稿 | +| 响应格式错误 | 提示服务返回无效结果,不能猜测成功 | + +首版不自动重试。自动重试容易让用户无法判断是否已经发送,也会在服务持续异常时制造额外 +流量。用户点击“重试”时复用同一个 `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 + 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 +} +``` + +Main handler 必须: + +1. 调用现有 `assertTrustedSender(event, window)`。 +2. 使用 Shared Zod schema 解析 `unknown` 输入。 +3. 不接收 URL、文件路径、Cookie、Header 或任意请求选项。 +4. 把 Promise 纳入现有 IPC 操作追踪,应用关闭时有界等待或取消。 + +Preload 只转发已声明输入,不做网络请求或图片文件读取。 + +### 6.4 FeedbackIdentityStore + +新增 Main-only `FeedbackIdentityStore`: + +- 文件:`/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。 +- 调用 HTTP Client。 +- 维持单次提交的 AbortController,并在 dispose / 清除本地数据时取消。 + +平台映射固定为: + +| Node 值 | API 值 | +| --- | --- | +| `win32` | `windows` | +| `darwin` | `macos` | +| `linux` | `linux` | +| 其他 | `unknown` | + +架构只保留 `x64` 和 `arm64`,其他值映射为 `unknown`。 + +### 6.6 严格 HTTPS Client + +反馈客户端使用 `undici` 的独立 `Agent`: + +```ts +new Agent({ + connect: { + rejectUnauthorized: true + } +}) +``` + +这是必要边界,不是额外配置。GoodBuddy 的 `GlobalTlsPolicy` 会给模型、MCP 和内网服务 +安装不校验证书的全局 Dispatcher;反馈内容不能经过该 Dispatcher。 + +每次请求必须满足: + +- 固定、预先校验的 HTTPS URL。 +- `redirect: "manual"`,任何 3xx 都失败。 +- `credentials: "omit"`。 +- `cache: "no-store"`。 +- `referrerPolicy: "no-referrer"`。 +- `Accept: application/json`。 +- 固定、非敏感 `User-Agent`,例如 `GoodBuddy-Feedback/`。 +- 不发送 `Authorization`、Cookie、API Key 或自定义代理凭据。 +- 总超时建议 15 秒,最大不得超过 30 秒。 +- 成功响应体最多 16 KiB,并检查流式实际字节数,不能只相信 + `Content-Length`。 +- 只接受 JSON `Content-Type` 和 JSON 响应体,并使用 + `feedbackSubmitResultSchema` 解析。 +- 日志只记录有界错误类别和 HTTP 状态,不记录请求正文、邮箱、截图、安装 UUID、 + `clientRequestId` 或响应编号。 + +测试可以注入 Endpoint、Dispatcher 和 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` 只运行一个请求。 +- 重复 IPC 调用合并或拒绝,不能并行上传两份截图。 + +### 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/index.ts` | Main service 构造、清理和 IPC 注入 | +| `src/main/ipc.ts` | trusted sender、schema parse 和 submit handler | +| `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. 验证计划 + +### 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`。 + +### 11.3 IPC 与 Preload + +- 未知窗口和非主 Frame 被拒绝。 +- Renderer 不能覆盖 `productKey`、版本、平台、架构、安装 ID、Endpoint 或 Header。 +- Preload 只暴露 `feedback.submit()`。 +- Sandbox 测试验证没有通用网络或文件路径能力泄漏。 + +### 11.4 Renderer + +- About 页面展示反馈卡片。 +- 表单字段、字符限制、中英文文案和隐私说明。 +- 普通文本粘贴不被图片处理拦截。 +- 截图选择、预览、替换、移除和 Object URL 清理。 +- 失败保留字段,重试使用原 `clientRequestId`。 +- 成功显示真实编号并清空后续新草稿。 +- Dialog role、初始焦点、Tab 循环、Escape 和焦点恢复。 +- 浅色、深色、窄窗口和 reduced motion。 + +### 11.5 真实集成 + +在反馈平台部署到最终 HTTPS 域名后: + +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` 产品尚未启用,不把入口发布到用户。 +- 此功能不迁移现有用户数据;只新增可重置的随机反馈安装身份。 +- 发布说明应明确:用户现在可以在“关于与更新”内直接提交反馈,邮箱和截图可选,应用不会 + 自动附加对话、日志、文件或凭据。 + +--- + +## 13. 实施前置条件 + +编码开始前必须确认: + +1. 生产反馈 HTTPS Origin。 +2. `/api/v1/feedback` 在该 Origin 可用。 +3. `goodbuddy` 产品已启用。 +4. 服务端公开 schema、截图上限和本文一致。 +5. 反馈平台的数据保留和隐私说明已可供产品文案引用。 +6. 真实集成测试应使用的环境和测试记录处理方式。 + +前置条件不满足时,本文保持“待实施”,不在产品中加入不可用入口。