客户端加载与请求排查
证据日期:2026-09-17。代码基线:f3917816;下文“已修复”指本次工作区改动,尚未代表安装版或生产环境。范围包括启动、项目切换、Memory、Guidelines、Reviews、Bundles、Activity、检索诊断、管理页面,以及 App → XPC → daemon → HTTP 的请求链路。服务器资源扩容不在本次范围内。
结论
问题同时存在于界面状态和请求安排。客户端并非完全没有加载状态:Reviews、Bundles、管理页面已有加载与失败分支,Workspace 也有并发限制、版本校验和刷新节流。但占位方式不一致,文档和 Activity 的失败没有在对应内容区域正确结束加载;启动和部分列表还等待了本来可以延后的工作。
Guidelines 预览读取随 App 打包的 Markdown,不发 HTTP 请求。原实现分别维护“是否打开”和可选预览内容,允许弹窗先出现而正文为空。本次改为由非空预览数据直接驱动弹窗。代码证明了这个状态缺口,但没有对截图发生的那一次点击做性能录制,因此不能断言那次空白的全部耗时来自该缺口。
本次已修复
| 位置 | 原问题 | 当前行为 |
|---|---|---|
| 启动窗口 | 加载页沿用登录表单的高窗口 | 加载页高度 360 pt,登录表单仍使用原尺寸;切换保留窗口中心 |
| Guidelines 入口 | 两个主操作争夺空间,文案截断 | 保留 Set Up Guidelines;预览仍是辅助链接 |
| Guidelines 预览 | 展示开关与正文分离 | .sheet(item:) 收到文档后才展示;可预览全部初始化文件 |
| 初始化记忆空间 | 只创建规范正文 | 同一 SQLite 事务创建规范和三个目录 README,失败全部回滚;保留已有规范、目录和内容 |
| Guidelines 检查 | 页面准备重复读取组织版本、目录和草稿;写入前又重复校验 | 首次展示使用已加载元数据,用户采用时重新校验一次,并将该版本交给批量创建 |
| Memory、Reviews、Bundles、Activity、项目准备 | 同一区域叠加转圈和装饰性骨架 | 共用局部原生加载指示器;已有数据的刷新保留内容 |
| 文档正文 | 失败只写入全局错误,正文仍转圈;重复读者拿不到同一失败结果 | 就地错误与重试;相同资源版本共用请求和结果,权限上下文改变会取消旧任务 |
| Activity | 错误被表现为“没有数据”,旧项目结果可能覆盖新状态 | 区分首次加载、成功为空、失败;同上下文请求去重,旧结果失效,刷新失败保留已有内容 |
| 请求观测 | 成功响应缺少 App 端耗时 | server_completed 记录 elapsed_ms,覆盖排队、XPC 和响应返回时间 |
默认骨架为:
CLUMSIES.md
knowledge/README.md
procedures/README.md
lessons/README.mdREADME 只说明目录用途,不虚构项目知识。这些文件进入现有 Draft 和评审流程,不直接发布组织记忆。详见 Memory Guidelines。
9 月 18 日 Activity 后续修复
Activity 已拆分为摘要游标分页和选中会话的任务分页。发现日志在阻塞线程池执行,后续页复用快照;只为当前任务页关联检索详情。首个请求前恢复项目选择,长会话不再静默截断。通用加载组件已移除转圈与骨架叠加,改为一个原生指示器。
具体边界与恢复方式见 Activity。初次发现仍需遍历有界日志头;安装版耗时需另行实测,不能从回归测试通过推断速度。
仍需调整的请求设计
以下是代码确认的结构问题,尚未在本次修改中实现。优先级按用户可用性和数据正确性排序。
| 优先级 | 证据位置 | 问题与下一步 |
|---|---|---|
| 高 | AppDelegate.startAfterNativeSetupCheck;WorkspaceLoader.loadAuthenticatedWorkspaceIdentity、reconcileManagedAgentAdapters | 启动先等 /setup,再等已配置的插件维护,之后才读取账号与 Workspace。set_agent_adapter 的 XPC 预算达 130 秒。把插件维护移到独立状态任务,保留退出登录和离线时也能维护的要求;界面就绪不应等待它完成。setup 探测也需要与离线启动协调。 |
| 高 | RetrievalDiagnosticsModel.loadMore | 分页响应缺少项目/加载代次校验;切换上下文时旧页可能追加到新列表。先分开列表与详情的代次,给分页补齐失效和取消保护;同项目刷新失败保留已显示数据。 |
| 中 | WorkspaceLoader.load | 单项目、组织与项目目录各一页时,首次就绪需要约 9 次 GET:身份 1 次、初始版本/选择 3 次、目录 2 次、最终校验 3 次。部分已并行,但最终组织和项目校验仍串行。保留一致性校验,先并行独立的最终检查,再评估携带版本的聚合快照接口。 |
| 中 | WorkspaceLoader.loadBundles;BundleNavigator | 列表先加载所有 Bundle 的详情,形成 N+1;打开导航还会准备全工作区索引。列表只取展示所需摘要,详情及项目资源选择器按需读取。 |
| 中 | DaemonXPCClient.call;state.rs:HTTP_REQUEST_TIMEOUT、server_request | 普通 XPC 和单次 HTTP 都是 30 秒。daemon 等网络失败才退回缓存,加上凭证刷新或重试,外层可能先超时。建立整个操作的统一期限和取消传递,为缓存回退留预算;只读展示与写入前权威校验应有不同读取策略。 |
| 中 | ServerRequestLimiter | 当前最多 12 个并发请求,但等待槽位的队列不立即响应取消。取消请求等到获准后才退出,不一定发出 HTTP,但会继续占据等待队列。需要可取消排队;跨视图 GET 去重应以相同身份、路径和版本为边界,不能仅按 URL 复用。 |
不能把版本校验、权限检查或写入前确认删掉来缩短时间。当前 Workspace 的状态轮询与数据刷新分别受 2 秒/30 秒节流和在途保护约束;本次没有发现据此认定“无限重复请求风暴”的证据。
本机日志证据及限制
只读检查了当天已安装客户端和 daemon 的日志。以下为北京时间;未导出正文、凭证或项目标识。
| 时间 | 观察 | 能支持的结论 |
|---|---|---|
| 13:26:38 | /setup 返回 200,169 ms | 这一次 setup 请求不是数秒延迟的来源;不能代表其他启动 |
| 13:37:48、13:38:48 | Review 文件加载 5,710/5,873 ms,之后命中本地内容为 0–2 ms | 首次正文路径明显较慢;缓存有收益,但这些数字不能区分服务器执行、传输和排队 |
| 13:38:07 | list_recalls 经 31,282 ms 报 XPC 超时 | 本机会话加载也存在超时;该请求不是服务器下载链路 |
| 13:37–13:38 | 若干 daemon server_request 为约 3.2–5.8 秒 | 网络请求链路确实慢过;没有同请求的上游/传输分段证据,不能归因于带宽 |
这些是单机历史样本,不是 p95、不构成改动后的性能对比,也不能证明所有空白弹窗都由网络造成。
加载状态约定与验收
复用现有状态模型和原生 SwiftUI,不引入新的状态管理框架:首次加载只在等待区域显示一个原生指示器;成功且无数据才显示空状态;首次失败显示就地重试;同上下文刷新保留数据并显示进度或错误;切换项目后丢弃旧结果。指示器提供辅助功能标签,不再叠加装饰性骨架。
本次已运行 macOS 完整测试、daemon 单元与生命周期测试、Rust Clippy,以及文档构建。新增回归覆盖初始化资产与保留策略、Swift/daemon 批量协议、事务回滚、并发文档读取共用失败结果、Activity 旧结果与重复请求、失败重试和启动窗口尺寸。
后续请求架构改动需要在隔离环境补齐慢响应、离线、401 刷新、取消排队和大量会话文件的实测,记录 first-ready 与内容可用时间。当前未完成逐页面视觉验收、修改后安装版性能录制或服务器扩容;不能据此宣称性能问题全部解决。