Perfowl 工作台 UI 信息架构精简与设备交互修复方案
> 执行定位:本方案可直接交给后续 AI 实施。用户已经逐项确认本轮 UI 需求。本批目标是减少工作台中的内部实现噪音、统一指标卡片布局、重构截图与启动时间表达、修正设备提示位置,并专项排查 Wi‑Fi 设备列表偶现/消失。 > > 边界:本批优先修改 SwiftUI 展示层和必要的展示模型/设备发现状态管理。除“条件指标可用性”和“Wi‑Fi 设备稳定性”确有需要外,不改变 Collector 原始采集、指标公式、Session 数据格式、报告统计算法和既有原始字段。P95/Jitter/Refresh Rate 等数据可以继续采集、保存和进入报告,但不再作为实时工作台曲线展示。 > > 禁止:不得为了 UI 简化删除底层诊断数据;不得把缺失值填 0;不得用 UI 猜测替代 Collector 的真实状态;不得破坏已有 Session 回放与报告兼容。
1. 现状与本轮目标
当前工作台存在三类问题:
- 展示层泄露内部实现:如
ActivityTrace FrameLifetime、candidate、profile、valid、阶段覆盖数、丢事件标记、source/scope/quality 等,大量属于研发/诊断信息,不应长期占据用户主界面。 - 信息重复和卡片过重:截图每张一张深色卡片;启动总时间重复展示;Display Performance 同时展示 5 个实时项;Memory 等多指标使用大块 readout,和 CPU Usage 的紧凑布局不一致。
- 设备交互不稳定/位置不合理:连接/断开提示嵌在截图区域;Wi‑Fi 设备记录偶尔出现、偶尔消失。
本轮最终 UI 原则:
- 主界面只表达“这是什么指标、当前值是多少、曲线是什么”。
- 内部实现、source、candidate、profile、quality、诊断原因进入 Debug/日志/Session 数据,不在常规 UI 常驻。
- 同类卡片使用一套布局和一套曲线开关交互。
- 正常“不适用/不存在”的数据不制造告警;真正关键缺口才用非常明确的视觉异常符号。
- UI 简化不能改变采集真实性。
2. 已确认产品需求
2.1 截图区域:从“大卡片列表”改成紧凑时间轴截图带
当前代码位置:
MacApp/Sources/PerfowlApp/PerfowlWorkbenchView.swiftscreenshotTimelinePreviewLivePreviewScreenshotCardMacApp/Sources/PerfowlCore/LiveScreenshotPreviewItem.swiftMacApp/Sources/PerfowlCore/LiveScreenshotIndex.swift
当前问题:
- 每张截图外层都有深色卡片、padding、边框,占空间。
- 每张图重复显示
elapsedSeconds、quality.rawValue(如valid)、sequence、bytes。 - 图片大小/字节数对常规查看没有业务价值。
- 时间关系应该由统一时间轴表达,而不是每张图片下面重复一段文本。
2.1.1 新布局
改为“水平截图带 + 共用时间刻度轴”:
- 截图缩略图本身成为主体,不再使用整张黑色/深色大卡片容器。
- 图片之间保持紧凑间距。
- 图片 hover/选中时可以出现轻量描边,不要常驻厚边框。
- 统一时间轴使用
item.elapsedSeconds。 - 时间轴必须表达截图之间真实的时间关系;不要给每张图单独重复显示“0.000s/1.000s”作为主要信息。
- 长 Session 必须使用惰性布局/虚拟化思路,不得创建“每一秒一个占位 View”。
- 时间刻度应根据可视窗口/总时长自适应(例如短测密、长测疏),避免 30 分钟测试产生不可接受的布局开销。
建议结构:
ScreenShot
0s 1s 2s 3s 4s
|---------|---------|---------|---------|
[图] [图] [图] [图]
真实间隔缺口应能从时间轴看出来,但实现不能通过大量空白 View 堆积造成性能问题。推荐以统一坐标映射/Canvas/overlay 或有界 spacer 计算实现。
2.1.2 删除的常驻信息
从单张截图 UI 删除:
valid/ 其他 quality rawValue;#sequence;xxx bytes;- 图片尺寸;
- 每张截图独立的大容器背景/边框;
- 其他仅用于调试的数据。
底层 quality、sequence、bytes 继续保留在数据对象/Session/日志中,不得删除数据字段。
2.1.3 异常截图
如果缩略图无法解码或截图失败:
- 在该时间位置保留轻量异常占位;
- 可以使用
camera.slash/ 红色警示符号; - 不显示“valid/missing”等原始枚举;
- 不用大段说明文字。
2.2 移除 Scene 01
当前:
PerfowlWorkbenchView.sceneHeader固定显示Scene 01。
要求:
- 完全移除
Scene 01用户可见文字。 - 内部如仍需要 scene 标识做数据组织,可以保留模型字段,但不展示。
sceneHeader中已有“标注”功能不得因删除Scene 01丢失;将标注按钮移动到合适的工作台顶部轻量区域,或让sceneHeader仅承担必要操作而不再占一个“Scene 卡片”。
2.3 移除默认“常规测试”模式卡片/Tab
当前:
- 左侧固定显示“常规测试 / 深度分析 / 网络测试”。
- “常规测试”永远 active,另外两个只是占位。
要求:
- 删除整个默认测试模式 Tab 区。
- “常规测试”是默认行为,不再以卡片/Tab/标签重复展示。
- “深度分析”“网络测试”当前不涉及,入口一并删除。
- 不保留“后续阶段开放”“敬请期待”之类占位 UI。
- 未来功能真正开发时再重新设计入口。
直接目标:
- 删除
modeTabs、modeTab(...)及其调用。 - 不影响设备选择、App 选择和 Session 启动流程。
3. 启动时间区域重构
涉及:
MacApp/Sources/PerfowlApp/PerfowlWorkbenchView.swift- StartupTiming 特殊 Panel
LaunchTotalReadoutLaunchTraceTimelinelaunchPhaseRowMacApp/Sources/PerfowlCore/WorkbenchModels.swift- StartupTiming panel series
MacApp/Sources/PerfowlCore/StartupTracePresentation.swift
3.1 删除 Running / Foreground Ready 顶部指标
当前 StartupTiming panel 额外声明:
RunningForeground Ready
要求:
- 两项从常规工作台右上角/卡片读数中移除。
- 相关底层值如果报告、诊断、导出仍需要,继续保留数据,不删除 Collector 字段。
- 启动区域只突出一个“启动总时间”。
3.2 启动总时间只出现一次
当前同一结果会出现在:
App Launch Total- Lifecycle 标题右侧
Total xxx ms - 其他 candidate/profile/status 文本中再次暗示总值。
新规则:
- 主区域只展示一次总启动时间,例如
1.84 s或1840 ms。 - 不再显示:
candidate totalcandidate profileprofile candidate6/9 phases candidatevalid/candidate/partialrawValue- algorithm version
- decoder profile version
- source/scope
- 上述字段仍保留在 Debug/日志/Session 审计数据。
3.3 启动阶段只展示“阶段名称 + 耗时 + 时间轴”
阶段主 UI 只需要:
- 阶段名称;
- 阶段耗时;
- 时间轴/瀑布位置。
删除阶段行中的:
phase.status.rawValue;phase.reason常驻文本;#1/#2occurrence 等实现/审计标记;- profile 选择理由;
- coverage disclosure;
- device lost event marker 文本;
- repaired/candidate 的长说明。
需要保留多 occurrence 数据时,可以在内部数据/诊断详情中保留,不在常规视图重复展开。
3.4 正常不存在的阶段:完全不显示
规则必须严格区分:
- App 本身没有触发某可选启动阶段 / notApplicable:正常情况,直接不显示。
- 不显示
N/A、--、“未采集到”、阶段空槽、固定 9 阶段占位。 - 当前
visiblePhases“只展示有真实 duration 的阶段”方向保留,但 UI 不再显示coverageDisclosure。
3.5 关键起点/终点缺失:红叉表达,不写长说明
用户确认的异常表达:
- 如果启动起点缺失:时间轴左边界用红色
×。 - 如果启动终点缺失:时间轴右边界用红色
×。 - 其他已经采集到的阶段继续正常显示。
- 主界面不附加“本轮捕获 x/9”“检测到丢事件标记 x 条”等说明。
- hover 红叉允许显示一句非常短的 Tooltip:
- “启动起点缺失”
- “启动终点缺失”
实现要求:
- 不得通过“阶段数少于 9”推断起点/终点缺失。
- 如果当前数据模型没有可靠的 endpoint 状态,需要在
CollectorLaunchTracePresentation增加明确的 presentation-only 边界状态,例如: startBoundaryMissingendBoundaryMissing- 该状态必须从 Collector 已有真实 endpoint/span/total 证据推导,不允许 UI 猜测。
- “正常 notApplicable 阶段”绝不能触发红叉。
3.6 丢事件标记仍保留在诊断层
deviceLostEventMarkerCount、deviceLossDisplay、profile reason、totalRepairDisplay 等:
- 数据保留;
- 可继续进入日志、Debug、Session 审计/导出;
- 常规工作台不展示。
4. 全部性能卡片统一为 CPU Usage 风格
用户明确:当前 CPU Usage 的布局就是目标布局。
目标样式:
- 左上:卡片标题。
- 右上:一个或多个紧凑的“指标名 + 当前值 + 曲线状态点”。
- 点击右上指标:显示/隐藏对应曲线。
- 下方:统一曲线区域。
- 不再在卡片内部放第二层大 readout 卡片。
4.1 通用交互
多指标卡片:
- 每个 series 在右上角都有紧凑 readout。
- readout 同时是 toggle。
- 选中:颜色点/文字使用该曲线 accent。
- 未选中:弱化显示,但数值仍可见。
- 点击只改变曲线 visibility,不改变采集状态,不触发 Collector 重新 prepare/start。
- 允许全部曲线都隐藏;不要强制“至少保留一条”,因为用户明确要求“显示/取消显示曲线”。
单指标卡片:
- 右上显示当前值;
- 曲线默认显示;
- 如现有交互需要,也可点击隐藏,但视觉必须与多指标卡片一致。
4.2 建议抽取共享组件
不要让 Memory / Display / GPU / CPU Core 每个各写一套大卡片 UI。
建议抽取:
CompactMetricHeaderMetricSeriesToggleReadoutCompactMetricPanel
或者同等职责的共享组件。
特殊指标只负责:
- 提供 series 定义;
- 提供格式化方法;
- 提供默认选中的 series 集合。
这样后续新指标不会再次产生不同布局。
4.3 清理现有 Memory 大 readout
当前 MemoryUsagePanel 的 5 个指标使用两排大块 Button。
要求:
- 改成统一右上角紧凑 readout。
- 保留用户此前确认的五个内存值及曲线切换能力。
- 不再常驻显示
APP/整机 · quality这类内部信息。 - quality 仍保留在数据层;必要时 Debug 使用。
5. Display Performance 专项调整
涉及:
WorkbenchModels.swiftPerfowlWorkbenchView.DisplayPerformancePanel- 报告/SessionReview 数据保留逻辑。
5.1 删除内部实现描述
删除标题旁:
ActivityTrace FrameLifetime前台呈现
不要在常规 UI 显示 Collector source。
5.2 实时卡片只保留 FPS + Frame Time
当前 live panel 有:
- FPS
- FTime
- P95
- Jitter
- Refresh Rate
新规则:
- 实时工作台只显示:
FPSFrame Time- 正式 UI 名称使用
Frame Time,不再显示FTime缩写。
5.3 P95 / Jitter 移到报告
P95 与 Jitter 是基于 Frame Time 序列派生的统计值:
- 从实时卡片 readout 删除;
- 从实时曲线 series 删除;
- 不删除
PerformanceSample.frameTimeP95Ms/framePacingJitterMs等数据字段; - 不删除 report/frame-report 中已有统计;
- Session Review / 报告层继续呈现这些统计结果。
本批必须检查:UI 删除 live series 后,报告仍能读取数据,不能误删 Registry/Collector/JSON/CSV 合同。
5.4 Refresh Rate 不画曲线
Refresh Rate 作为刷新环境/设备能力值:
- 从 Display Performance 实时曲线删除;
- 不占一个曲线 toggle;
- 本轮主卡片只保留 FPS / Frame Time,因此 Refresh Rate 不再常驻此卡片。
- 底层值继续保留给报告/设备环境信息,避免破坏历史 Session。
5.5 删除“FPS / 帧耗时 / 全部”模式切换器
当前 DisplayChartMode 是为 5 series 设计的。
新布局只剩两个 series,应删除模式切换器:
- 右上 FPS toggle;
- 右上 Frame Time toggle;
- 两个 toggle 独立控制曲线;
- 不需要“全部”模式。
6. “添加指标”列表精简
涉及:
WorkbenchMetricCatalog.optionsMetricSelectorPopoverWorkbenchMetricSupportState/ 可用性判定。
6.1 每项只显示“名称 + 含义”
删除:
- 数据源说明;
- modeNote;
DTX/CoreProfile/ActivityTrace;- candidate/foreground-device/source-native 等内部术语;
- support 标签;
- 条件解释长文;
- “本次未采集 · 下次测试生效”等与条目定义混在一起的实现说明。
每个条目只保留:
- 指标名称;
- 一句用户能理解的指标含义。
建议统一中文含义,示例:
- ScreenShot:记录测试过程中的设备画面。
- Startup Timing:App 启动总耗时及实际捕获到的启动阶段耗时。
- Display Performance:FPS 与 Frame Time 显示性能。
- CPU Core Usage:各 CPU 核心的使用率。
- CPU Usage:App 与设备整体 CPU 使用情况。
- Memory Usage:App 与设备相关内存占用。
- GPU Usage:前台画面的 GPU 使用情况。
- Thread Count:App 当前线程数量。
- CSwitch:App 每秒上下文切换次数。
- Wakeups:App 每秒线程唤醒次数。
- Battery Temperature:设备电池温度。
- Disk:App 磁盘读写速率。
- Thermal Status:设备当前热状态。
- Network:网络发送与接收速率。
- Battery:设备放电电流、电压与功率。
- Energy Usage:App 能耗相关指标。
- GPU Counter:设备提供的 GPU 硬件计数器。
如 .fps / .frameTime 已经只是向 .displayPerformance 迁移的兼容 ID,实施 AI 必须检查它们是否仍需要单独出现在 picker。若没有独立产品语义,则 picker 只保留 Display Performance,但 enum/历史偏好迁移兼容不可删除。
6.2 条件指标:条件不满足直接置灰
当前 WorkbenchMetricSupportState.conditional.selectionEnabled 实际仍返回 true,不符合新要求。
新规则:
- 条件满足:正常可选。
- 条件不满足:置灰、不可点击。
- 主列表不常驻解释原因。
- 如确有必要,hover Tooltip 只给一句简短原因,例如“当前设备不支持”。
实现要求:
- 可用性必须来自真实的设备/连接/Collector readiness 状态,不能靠 UI 硬编码猜测。
- 建议增加
MetricAvailabilityContext或等价 runtime availability 层,将: - 设备 iOS 支持;
- Wi‑Fi/USB 路由;
- 电源/充电状态;
- 已准备的数据源能力;
- 动态 GPU Counter schema
等转换成 enabled/disabled。
- 暂时无法在测试前可靠判断的能力,不得伪造“支持”;应以 Collector prepare 后的真实能力刷新 UI。
7. 设备连接/断开提示改成顶部 Banner
当前:
USBStatusNotice已有连接/断开事件模型;PerfowlWorkbenchView将 notice 渲染在截图 preview 内,导致提示出现在截图位置。
新规则:
- 提示属于整个 App 的全局状态,不属于截图。
- 从截图区域删除所有
activeUSBStatusNoticeoverlay。 - 在 Workbench 根 View 顶部建立全局 overlay/banner。
- 从软件顶部向下进入,消失时向上/淡出。
- 居中或靠顶部合适位置,不改变主体布局高度,不推挤卡片。
- 连接和断开使用同一组件:
- “设备 A 已连接”
- “设备 A 已断开”
- 沿用现有自动消失机制即可,不额外增加确认按钮。
建议:
.overlay(alignment: .top) {
if let notice = activeUSBStatusNotice {
USBStatusBanner(notice: notice)
.transition(.move(edge: .top).combined(with: .opacity))
.zIndex(...)
}
}
注意 macOS 标题栏区域和自定义 titleBar 的视觉层级,Banner 必须看起来是“从软件顶部下拉”,而不是出现在内容中部。
8. Wi‑Fi 设备列表稳定性专项修复
涉及:
MacApp/Sources/PerfowlCore/DeviceFoundation.swiftMacApp/Sources/PerfowlCore/PymobiledeviceClient.swiftMacApp/Sources/PerfowlCore/USBMuxDeviceMonitor.swiftMacApp/Sources/PerfowlCore/DeviceConnectionService.swift- 对应 DeviceFoundation / USBMux / Pymobiledevice 测试。
8.1 当前观察到的风险点
当前 refreshDevices() 同时整合 USB discovery 和 listNetworkDeviceIdentifiers(),并在 applyDiscovery 最终用当轮结果替换 discoveredDevicesByID/devices。
代码已经有两项保护:
- 刷新前临时保留上一轮 Network 记录;
- Network identity hydration 失败时可以保留 topology row。
但仍需验证以下情况是否造成“Wi‑Fi 有时有、有时没有”:
listNetworkDeviceIdentifiers()单轮瞬时返回空/超时,最终 merge 是否仍会删掉上一轮 Wi‑Fi;- USB attach/detach 触发
refreshDevices()时,Network discovery 尚未稳定,是否先发布后又覆盖; networkHydrationTask?.cancel()是否会因为单设备 retry/新一轮 refresh 取消其他 Wi‑Fi 设备 hydration;- 同一物理设备 USB/Network route 切换时,是否因 identifier/route 合并时机导致闪烁;
- generation 检查是否会把较早但有效的 Network 结果丢弃;
- 手动刷新和 monitor 事件并发时是否出现旧结果覆盖新结果。
8.2 修复原则
- 一次瞬时 Network 枚举失败不能立即把已知 Wi‑Fi 设备从 UI 删除。
- hydration/probe 失败只应把设备标记为不可达/待重试,不应等价于“设备不存在”。
- USB 与 Wi‑Fi 是同一物理设备的两种 route 时,route 切换应该稳定,不应出现短暂“完全消失”。
- 不无限保留真正已离线的 Wi‑Fi 设备。
建议采用轻量“missing streak / grace”机制:
- 为已知 Network device 维护连续缺失次数或短 grace 状态;
- 单轮完整 discovery 未返回时,先保留上一轮 row;
- 只有连续多轮确认缺失,或存在可靠的不可达/解绑证据后再移除;
- 建议以“连续 2 次完整 network discovery 缺失”为第一版确定性规则,并用测试覆盖;如果现有刷新 cadence 不适合,再基于真实测试调整,不要直接引入长时间后台轮询。
8.3 不增加高频轮询
本修复不能为了“稳定”增加高频 Wi‑Fi scan:
- 继续以事件触发/用户刷新/现有发现流程为主;
- 不新增亚秒级轮询;
- 不新增长期阻塞 probe;
- 并发 probe 必须保持有界。
9. 当前主要代码修改清单
预计涉及:
必改
MacApp/Sources/PerfowlApp/PerfowlWorkbenchView.swift- 删除 mode tabs;
- 删除 Scene 01;
- 重构 screenshot timeline;
- 移动 USB notice 到顶层;
- 重构 MetricSelectorPopover;
- 统一 card header/readout/toggle;
- 精简 Display Performance;
- 精简 Startup UI。
MacApp/Sources/PerfowlCore/WorkbenchModels.swift- 清理指标 summary/modeNote 展示合同;
- 调整 Display Performance live series;
- 移除 Startup Running/Foreground Ready 的常规 panel readout;
- 增加真正 runtime availability 表达(如需要)。
MacApp/Sources/PerfowlCore/StartupTracePresentation.swift- 保留 audit 数据;
- 提供可靠 start/end boundary presentation state;
- 常规 UI 不再依赖 coverage/deviceLoss/profile disclosure。
MacApp/Sources/PerfowlCore/DeviceFoundation.swift- Wi‑Fi discovery 稳定性修复;
- 网络设备缺失 grace/miss streak(若诊断确认);
- 避免 hydration/refresh 竞争导致行闪烁。
可能需要联动
MacApp/Sources/PerfowlCore/PymobiledeviceClient.swiftMacApp/Sources/PerfowlApp/SessionReviewViews.swiftMacApp/Sources/PerfowlCore/SessionReview.swiftMacApp/Sources/PerfowlCore/LiveScreenshotPreviewItem.swift
仅当报告显示或 availability 需要时修改;不得为 UI 清理无理由改变采集协议。
10. 实施顺序
Phase 0:基线与测试保护
先补/更新测试,确认以下现状合同:
- P95/Jitter/Refresh Rate 数据字段仍进入 Session/report;
- startup audit/profile/loss 数据仍存在;
- screenshot item 仍保留 quality/sequence/bytes 等内部元数据;
- Wi‑Fi discovery 当前可构造“单轮网络枚举缺失”场景。
Phase 1:静态 UI 清理
先做无数据合同风险的项目:
- 删除“常规测试 / 深度分析 / 网络测试”;
- 删除 Scene 01;
- USB notice 移到顶部;
- picker 删除 modeNote/support 技术说明。
Phase 2:统一指标卡片布局
- 抽共享 compact header/readout/toggle;
- CPU Usage 作为视觉基准;
- Memory、Display、GPU、CPU Core 等逐项迁移;
- 曲线开关只影响展示。
Phase 3:Display Performance 精简
- live 仅 FPS + Frame Time;
- 删除 chart mode;
- P95/Jitter/Refresh Rate 不再 live 绘制;
- 验证报告仍有派生值。
Phase 4:启动时间表达重构
- total 只显示一次;
- 去掉 Running/Foreground Ready;
- 去掉 candidate/profile/coverage/deviceLoss 常驻文案;
- notApplicable 不显示;
- endpoint 缺失用红叉;
- 不破坏 audit/export。
Phase 5:截图时间轴
- 替换
LivePreviewScreenshotCard; - 共用时间刻度;
- 删除 valid/bytes/sequence;
- 长 Session 性能验证。
Phase 6:Wi‑Fi 稳定性
- 先复现;
- 修正 transient miss / generation / hydration cancellation;
- 建立连续刷新回归测试。
Phase 7:整体验收与真机验证
- Swift 单测;
- UI/模型专项测试;
- 构建;
- 真机 USB + Wi‑Fi;
- 长 Session;
- 最后再打测试包。
11. 自动化测试要求
至少新增/更新以下测试:
UI/模型合同
WorkbenchDisplayContractTests
- Display Performance live series 只有 FPS + Frame Time;
- P95/Jitter/Refresh Rate 数据模型仍存在。
WorkbenchModelsTests
- picker 条目含名称/含义;
- 条件不可用时 selection disabled;
- 不依赖 modeNote 才能决定支持。
StartupTracePresentationTests
- notApplicable phase 不进入 visible phases;
- start boundary missing 与 end boundary missing 独立;
- 不能由“6/9”推断 endpoint missing;
- audit loss/profile 数据仍保留。
- Screenshot 测试
- item 元数据未删除;
- timeline 位置基于 elapsedSeconds;
- invalid screenshot 产生异常占位而非 raw quality 文本。
Wi‑Fi 设备稳定性
增加 DeviceFoundation 场景:
- 第一次发现 Wi‑Fi A;
- 下一次 network list 瞬时空;
- UI 仍保留 Wi‑Fi A;
- 后续 network list 恢复 A,不产生删除+新增闪烁;
- 连续达到移除阈值后,A 才从列表消失;
- USB A 拔线后如果 Network A 可用,selected device 不应短暂清空;
- hydration 失败只标 offline,不移除 row;
- 两台 Network 设备同时 hydration,retry 一台不能取消另一台的有效结果。
12. 真机验收清单(E4)
必须至少执行:
A. 截图
- 5 分钟常规多指标测试;
- 截图区域不再出现每张大黑框;
- 不显示 bytes/尺寸/
valid; - 时间轴清楚;
- 1s 左右截图可连续浏览;
- 出现真实间隔时轴上可观察;
- 横向滚动流畅。
再做 30 分钟长测,确认:
- UI 不因截图数量增长明显卡顿;
- 内存不会因额外时间轴 View 数量线性异常膨胀;
- 最新截图自动定位/用户手动滚动逻辑正常。
B. 启动
至少覆盖:
- 阶段较完整的一轮;
- App 天生不触发某可选阶段;
- 起点缺失样本;
- 终点缺失样本。
验收:
- total 只出现一次;
- 没有 Running / Foreground Ready;
- 没有 candidate/profile/6/9/device loss 文本;
- 正常不存在阶段不显示;
- 起点缺失只在左端红叉;
- 终点缺失只在右端红叉;
- 已采集阶段始终正常显示;
- 不填 0、不伪造阶段。
C. 卡片
- CPU Usage 视觉作为基准;
- Display Performance 右上只有 FPS + Frame Time;
- 两者可独立显示/隐藏曲线;
- Memory/GPU/Network/Disk 等多指标布局统一;
- 不再出现大块二级 readout card;
- 点击曲线开关不触发重新采集/重启 source。
D. 添加指标
- 每项只有名称 + 含义;
- 无 ActivityTrace/CoreProfile/candidate/source 等文字;
- 条件不满足直接置灰;
- 条件满足后可选;
- 不出现无意义 support 标签。
E. 设备提示
- USB 连接:顶部下拉提示;
- USB 断开:顶部下拉提示;
- 截图区域不再出现该提示;
- 提示自动消失,不阻塞操作。
F. Wi‑Fi
至少连续执行 20 次:
- 打开设备选择器;
- 刷新;
- USB 插拔;
- Wi‑Fi route 保持;
- USB → Wi‑Fi promotion;
- Wi‑Fi 临时 probe 失败/恢复。
要求:
- 可用 Wi‑Fi 设备不随机消失;
- 不出现同一设备重复行;
- route 变化后设备身份保持一致;
- 真正不可用后能最终正确移除。
13. 性能门禁
本批属于 UI 重构,但仍需遵守轻量化要求。
截图时间轴
- 不允许每个刷新周期遍历整段 30 分钟截图做昂贵重排;
- 使用
LazyHStack/ Canvas / 有界坐标计算; - 1 FPS、30 分钟约 1800 项时仍需流畅;
- 不重复解码历史缩略图。
卡片
- series toggle 仅改变本地 UI state;
- 不触发 Collector prepare;
- 不重新构造全量 Session 数据。
Wi‑Fi
- 不新增高频轮询;
- network probe 有界并发;
- 单次 refresh 不应因一台离线设备阻塞整表。
实施后记录变更前后:
- Workbench 首屏响应;
- 30 分钟 Session 下截图区域滚动;
- CPU/内存峰值;
- Wi‑Fi refresh 完成时间。
若出现明显退化,本批不得标记 validated。
14. 停止条件与风险
出现以下情况应停止扩大修改范围,优先修正:
- 为隐藏 UI 文本而误删 Collector/Session 原始字段;
- P95/Jitter 从报告数据中消失;
- startup endpoint 红叉只能靠阶段数量猜测;
- 指标 toggle 导致采集 source 重启;
- Wi‑Fi 修复引入永久幽灵设备;
- screenshot 时间轴导致长测 UI 明显卡顿;
- 当前脏工作区中的其他 R8/Build73 改动被混入本批。
当前工作区已有大量未提交修改和 untracked 文件,本批执行时必须严格限定 diff,不得覆盖或清理其他并行批次内容。
15. 完成定义
只有同时满足以下条件才可称为本轮 UI 改造完成:
- 本文 2~8 节全部落地;
- Swift 专项测试通过;
- 全量 Swift 测试通过;
- MacApp 构建通过;
- UI 主界面不再泄露本轮明确要求删除的内部实现文字;
- Wi‑Fi 设备稳定性有自动测试 + 真机证据;
- 5 分钟多指标真机测试通过;
- 30 分钟截图长测无明显 UI/资源退化;
- P95/Jitter/Refresh Rate/Startup audit 等底层数据没有因 UI 精简被误删;
- 按仓库
AGENTS.md补 Implementation Update / Validation Update、提交、双远程推送、交付通知与测试包。
16. Implementation Update(2026-09-20)
范围:§2~§8 全部落地(Phase 1~6)。Phase 7 的真机 E4 未执行——实现 AI 只交付 E2,真机验收(§12 A–F 清单、§13 性能门禁的真机测量)需由用户执行。
基线与隔离:基线 c9a133d(Build 74 已推送 commit)。本批在独立 worktree (分支 workbench-ui,/private/tmp/perfowl-ui)中执行,未触碰共享脏工作区 的任何文件;已核对 c9a133d 与共享工作区 HEAD 之间 UI 层代码完全一致(仅 PerformanceModels.swift 6 行与并行批次相关)。
改动清单(tracked 9 文件 +1217/−620;新增 3 文件 474 行)
| 文件 | 变更 |
|---|---|
MacApp/Sources/PerfowlApp/PerfowlWorkbenchView.swift | +996/−620:删 modeTabs / Scene 01、USB 提示改顶部 Banner、新增 CompactMetricPanel + MetricSeriesToggleReadout、Memory/Display/通用卡片统一、启动时间表达重构、LivePreviewScreenshotCard → ScreenshotTimelineFrame、picker 改运行时可用性 |
MacApp/Sources/PerfowlCore/WorkbenchModels.swift | +185:删 modeNote、删恒真的 selectionEnabled、新增 WorkbenchMetricAvailability / MetricAvailabilityContext / availability(for:in:)、startupTiming series 清空、displayPerformance 收敛为 FPS + Frame Time、新增 displayPerformanceReportOnlySeries |
MacApp/Sources/PerfowlCore/DeviceFoundation.swift | +150:单 hydration 任务 → 每设备任务 + 有界 sweep、retainingMissedNetworkRows(in:) 宽限窗口、networkMissStreak |
MacApp/Sources/PerfowlCore/StartupTracePresentation.swift | +52:startBoundaryMissing / endBoundaryMissing 与私有 isRequiredGap |
MacApp/Sources/PerfowlCore/PerformanceSessionController.swift | +7:发布 startupSourceUnavailable(逐字取自 app_ready) |
MacApp/Sources/PerfowlCore/ScreenshotTimelineLayout.swift | 新增 106 行纯布局模型 |
MacApp/Tests/.../ScreenshotTimelineLayoutTests.swift | 新增 130 行 / 6 项 |
MacApp/Tests/.../DeviceFoundationNetworkStabilityTests.swift | 新增 238 行 / 6 项 |
| 另 4 个既有测试文件 | +497/−…(详见 §17) |
16.1 §2.1 截图:从大卡片列表改成紧凑时间轴截图带
新增 ScreenshotTimelineLayout(纯布局模型,不依赖 SwiftUI 体,因此 “位置由 elapsedSeconds 线性决定”“刻度开销不随 Session 长度增长”这两条可以被 单测钉死):
thumbnailWidth 96/spacing 8/pointsPerSecond = (96 + 8) / cadence;
第 t 秒捕获的帧左沿落在 t × pointsPerSecond,真实掉帧读出真实缺口, 而不是按固定步长把 1Hz 与掉线排成同一种样子。
maximumRulerLabels = 40:rulerSteps按总时长自适应(30 min 步长 60s、
2 h 步长 300s),标签数上限 41,不随 Session 长度增长。
visibleGap(after:before:):间隔上限maximumGapPoints = 64,超出部分**如实
报告 omittedSeconds**(UI 显示 +12s 之类的压缩提示),不假装这段是连续的。
UI 侧:LivePreviewScreenshotCard(160 宽、带容器背景/边框、重复打印 quality.rawValue / #sequence / bytes)→ ScreenshotTimelineFrame(96 宽、 无容器),无法解码的帧保留它在时间轴上的槽位并画 camera.slash。仍用 LazyHStack,间隔由有界 padding(.leading, gap.points) 给出,不每刷新重排 整段、不重复解码历史缩略图、不为每一帧建占位 View。实时带与空闲预览统一 .frame(height: 156),两者切换不再 reflow。
数据未删:LiveScreenshotPreviewItem 的 sequence / quality / thumbnailData 原样保留(ScreenshotTimelineLayoutTests 钉死)。
16.2 §2.2 / §2.3:Scene 01 与测试模式 Tab
sceneHeader→workbenchToolbar:只保留标注按钮与 start/stop,删掉
“Scene 01” 用户可见文字(内部 scene 概念未用于展示)。
- 删除
modeTabs与modeTab(_:active:)(含“后续阶段开放”占位文案)。 - 契约测试钉死:视图源码不再出现
Text("常规测试")/Text("深度分析")/
Text("网络测试") / Scene 01 / func modeTab(。
16.3 §3:启动时间表达
startupTimingpanel 的 series 从[Running, Foreground Ready]改为
[]——这两个“进程观察值”是对同一次启动的第二份、且不完整的复述。 底层数据未删:ios.process.startup.observed_running_ms / foreground_ready_ms 仍在 metricIDsByGroup[.startupTiming],测试钉死它们仍 可被选中并请求。
- 总时间只出现一次:卡片头
App Launch Total一处;LaunchTraceTimeline
头不再重复 Total · N/M phases · status rawValue。
- 删除常驻文案:
coverageDisclosure、totalRepairDisplay、deviceLossDisplay、
Profile <version> 与 profile 选择理由;阶段行只留名称 + 耗时 + 时间轴, 删 status.rawValue、reason、#occurrence。
- 端点红叉:新增 presentation-only 的
startBoundaryMissing/
endBoundaryMissing,只从 Collector 自身端点证据推导—— totalSource/totalRepairReason 含 start-substituted / frame-end-repaired、 launchStartTimestampNs == nil、或必需阶段的 gap;notApplicable 阶段由 私有 isRequiredGap 显式排除,因此“App 没触发这个可选回调”永不触发红叉。 不得由“捕获到 6/9”推断端点缺失(测试用“9 个阶段只捕获 2 个但末端已测量 ⇒ 终点不报缺失”钉死)。
- UI:轴左右边界画红色
×,tooltip 只有“启动起点缺失”/“启动终点缺失”。 - 审计数据保留:
coverageDisclosure/totalRepairDisplay/
deviceLossDisplay / 9 阶段 ledger / decoderProfileVersion / algorithmVersion / profileSelectionReason 全部仍在(测试钉死),只是不在 常规工作台常驻展示。
16.4 §4:统一为 CPU Usage 风格
新增两个共享组件,之后所有指标面板同一套骨架:
CompactMetricPanel(左上标题 + 右上 readouts + 下方统一曲线区);MetricSeriesToggleReadout(紧凑 readout,同时是曲线开关:选中用曲线
accent,未选中弱化但数值仍可读)。
迁移:Memory(5 个两排大块 → 右上 5 个紧凑 toggle,删 APP/整机 · quality)、 Display、以及通用卡片(CPU / CPU Core / Network / Disk / GPU Usage / Energy / Thread / CSwitch / Wakeups / Battery / Thermal)。GPU Counter 保留单线选择器 (100+ counter 不适用逐条 toggle),沿用同一 readout 组件。
曲线开关只改本地 @State:不重新 prepare、不重启 source、允许全部隐藏 (删除旧的“至少保留一条”约束)。
16.5 §5:Display Performance
- live series 由 5 收敛为 2:
FPS+Frame Time(FTime缩写改为正式名
Frame Time)。
- 删除
DisplayChartMode(FPS / 帧耗时 / 全部)——只剩两条曲线时“全部”模式没有
意义;两个 toggle 各自独立。
- 删除标题旁的
ActivityTrace FrameLifetime · 前台呈现。 - P95 / Jitter / Refresh Rate 的 series 定义移入
displayPerformanceReportOnlySeries(定义保留),且它们仍在 metricIDsByGroup[.displayPerformance]——Collector 合同、JSON/CSV schema、 报告统计完全未变;测试钉死三个 product metric ID 仍在请求集合内,且报告层 仍从 PerformanceSample.frameTimeP95Ms 等读值。
16.6 §6:添加指标列表
- 删除
modeNote(原先承载DTX CoreProfile、foreground-device、
source-native、采样频率等实现词汇);每行 = 名称 + 一句中文含义。
- 条件指标按真实运行时事实置灰:新增
WorkbenchMetricAvailability + MetricAvailabilityContext + WorkbenchMetricCatalog.availability(for:in:)。判据全部来自真实状态:无设备 → “未选择设备”;未 prepared → “准备完成后可用”;startupTiming → Collector app_ready 的 startupSourceUnavailable(为此在 PerformanceSessionController 新增发布属性,invalidateCollectorReadiness() 时清空);battery → route == .wirelessUserspace。未报告不等于不支持: 只有显式的 true 才置灰。
- 顺带删掉恒真的
WorkbenchMetricSupportState.selectionEnabled(它返回常量
true,使条件行永远可选、选了又静默什么都不出),其 4 处 no-op 门禁随之删除。
16.7 §7:设备提示改成顶部 Banner
截图区两处 activeUSBStatusNotice overlay 全部删除;根 View 用 .overlay(alignment: .top) 承载新的 USBStatusBanner(胶囊、allowsHitTesting(false)、 zIndex(20)、从自定义标题栏下方 56pt 进入),沿用既有 1s 自动消失机制, 不推挤下方卡片。
16.8 §8:Wi‑Fi 设备列表稳定性
networkHydrationTask(单个共享任务,retry 一台会 cancel 所有设备的在途
探测)→ networkHydrationTasks: [String: Task](每设备)+ networkHydrationSweep (有界批量,并发宽度 maxConcurrentNetworkProbes = 4)。
retainingMissedNetworkRows(in:):listNetworkDeviceIdentifiers()把失败/超时
折叠成空列表,单一样本无法区分“本轮没查到”与“设备没了”。现在单轮缺失保留 上一轮 Network row,连续 discoveryMissStreakLimit = 2 轮缺失才移除; 只对 Network 生效,USB 拔线仍即时。
- hydration 失败只标
.offline,不删 row(“现在验不了”≠“已取消配对”)。 - 未新增任何轮询。
17. Validation Update(2026-09-20)
证据等级 E2 completed / E4 pending。
17.1 测试
Swift 全量:204 tests / 3 skipped / 0 failures(同口径基线 c9a133d 为 182 / 3 skipped / 0,+22 全为本批新增,无一项既有测试被删或放宽)。 新增与改写:
| 套件 | 新增项 | 钉住什么 |
|---|---|---|
ScreenshotTimelineLayoutTests | 6 | 轴线性(t × pointsPerSecond)、一拍漂移不产生间隔而掉帧产生、刻度开销有界(30 min → 31 个 / 2 h → 25 个,上限 41)、超大间隔被压缩并如实报告省略秒数、ruler 刻度判定、item 元数据未删 |
DeviceFoundationNetworkStabilityTests | 6 | 单轮空枚举保留 Wi‑Fi 行;连续 2 轮才移除;USB 拔线仍即时(宽限只给 Network);hydration 失败只标 offline 不删行;retry 一台不取消另一台的在途探测(旧代码会);并发探测 ≤ maxConcurrentNetworkProbes = 4 |
StartupTracePresentationTests | +4 | 起/止边界互相独立且不由阶段数推断;notApplicable 末端阶段永不触发终点红叉;审计数据(coverage / repair / device-loss / 9 阶段 ledger / decoder profile / algorithm version)未丢 |
WorkbenchDisplayContractTests | +4 | 截图带不含 quality.rawValue / #sequence / bytes 且异常用 camera.slash;USB 提示是顶层 Banner 而非截图 overlay;picker 无 modeNote 且用运行时可用性;启动卡 total 只出现一次、边界用红叉且不含 N/M phases |
WorkbenchModelsTests | +1 | 条件指标按真实事实置灰(无设备 / 未 prepared / startupSourceUnavailable / route);picker 无实现词汇;.fps/.frameTime 不再有独立行;defaultSelection 改用 .displayPerformance 后 product metric ID 集合与旧 .fps 默认经 migratePreferences 后完全相等 |
DisplayPerformanceSwiftTests | +1 | live 仅 FPS + Frame Time;P95/Jitter/Refresh Rate 定义与 product metric ID 仍在请求集合内 |
17.2 门禁
- Swift 全量:204 / 3 skipped / 0 failures(见上)。
- MacApp 构建:
swift build --disable-sandboxPASS。 - 知识门禁:
metric-knowledge-gate.pyPASS(66 Registry contracts,本批未触碰
metric 合同)。
- VitePress 构建:PASS。
git diff --check c9a133d..HEAD:干净。
17.3 如实披露的偏差
- 通用卡片的 scope chip(APP/整机)一并移除。方案只对 Memory 明确要求
(§4.3),本批为“一套骨架”把它从所有通用卡片移除。scope 未删除:仍在 PerformanceSample.metricScopes 与 readout 的 hover tooltip 中(unit 同)。
- 启动轴标签 “Raw Span 审计轴 / Collector 启动轴” 删除。方案未逐条列出,
属 §1 “减少内部实现噪音”目标;axisUsesAuditFallback 数据字段保留。
defaultSelection的.fps改为.displayPerformance。这不是采集合同
变化:scopedCapturePlan 恒先 migratePreferences,测试用集合相等钉死 “新旧默认经迁移后请求的 product metric ID 完全一致”。
- 删除恒真的
selectionEnabled及其 4 处门禁。该属性返回常量true,
那些门禁从未真正拦住任何东西;正确的门禁换成 WorkbenchMetricCatalog.availability(for:in:)。
- §13 性能门禁的真机测量未执行(Workbench 首屏响应、30 min 截图滚动、
CPU/内存峰值、Wi‑Fi refresh 耗时)。实现 AI 只交付 E2,真机由用户执行。 有界性已由单测钉死:刻度 ≤ 41、间隔 ≤ 64pt、并发 ≤ 4、LazyHStack 惰性、 不重复解码。
- 一条既有负载敏感 flake(非本批回归):
PerformanceFoundationTests.testSessionControllerPassesNestedCoreSessionDirectory 的等待预算是 100 × 20ms = 2.0s,空载单跑实测 1.75–2.05s(基线 c9a133d 单独跑 3/3 通过,1.751 / 1.869 / 1.904s),已经紧邻预算。本机 被多个 AI 共用、负载均值曾在 19 → 79 摆动,负载 79 时全量套件中该用例 超时失败(本批树同一时间也是全量失败);负载回落到 20 后本批树全量 204 / 3 skipped / 0 failures、基线全量 182 / 3 skipped / 0 failures。 未放宽任何阈值,也未改该用例。
17.4 未做与下一步
- 未执行 §12 真机验收 A–F(截图 / 启动 / 卡片 / 添加指标 / 设备提示 / Wi‑Fi)
与 §13 真机性能测量;未构建测试包;未声明 validated。
- 下一步:独立验收审计 exact commit → 用户执行 **E4(§12 A–F)+ 真机性能对比
(§13,含 30 分钟截图长测与 Wi‑Fi 反复刷新)**,通过后本批才能关门。