# 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"` 和可见标题。 - 复用 `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 } ``` 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`: - 文件:`/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/`。 - 不发送 `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 窄窗口、焦点与背景隔离、截图预览和 真实生产提交验收。