Route icon results through UI events (T-607)

This commit is contained in:
ila
2026-07-17 10:21:44 +08:00
parent fba672381e
commit 0945fe93dc
24 changed files with 2068 additions and 31 deletions
+2 -2
View File
@@ -45,7 +45,7 @@ SoftBox 软件盒子是一个使用 Go + Gio 开发的 Windows 桌面客户端,
## 当前阶段
当前项目已完成 Phase 0~2、T-301 与审核整改 `T-604`~`T-606`。Windows 安全路径阻断项与 Phase 2 首个图标缓存资源整改已关闭;Phase 2 第二个整改 `T-607` 已落成待领取,下一步实现后台图标结果经 application event 回到 UI goroutine 的线程接线,其余审核整改与 Phase 1 中央目录预扫描继续串行处理,T-302 暂后置。
当前项目已完成 Phase 0~2、T-301 与审核整改 `T-604`~`T-607`。Windows 安全路径阻断项、图标缓存资源边界与后台结果回 UI 线程的事件接线均已关闭;下一步按 Phase 2 交叉审核顺序落成 modern/win7 适配器交互契约任务,其余审核整改与 Phase 1 中央目录预扫描继续串行处理,T-302 暂后置。
优先路径:
@@ -53,7 +53,7 @@ SoftBox 软件盒子是一个使用 Go + Gio 开发的 Windows 桌面客户端,
2. 已完成 Phase 1:清单验签、ZIP 安全解压、原子切换回滚原型。
3. 已完成 Phase 2 与 T-301:清单/列表/详情/图标缓存 + 可恢复下载队列。
4. 已完成 T-604:modern/Win7 workspace 与 Gio 版本解析彻底隔离。
5. 已完成 T-606:图标缓存按 key 去重、流式有界读取、memory LRU 与双 shell 图标剪枝;下一步实现 T-607 的 UI 线程事件接线,再串行处理其余整改与 T-302/T-303、Phase 4-6。
5. 已完成 T-606/T-607:图标缓存按 key 去重、流式有界读取、memory LRU、双 shell 图标剪枝,以及 Load/Decode→application event→有界 relay/Invalidate→UI ApplyEvent 的线程接线;下一步落成双适配器交互契约任务,再串行处理其余整改与 T-302/T-303、Phase 4-6。
## 领取任务规则
+1 -1
View File
@@ -61,7 +61,7 @@ UI 固定交互模式:
T-203 已把共享列表状态落在 `core/application.CatalogListModel`:源快照、搜索、单分类、all/installed/updates 视图和 selected app ID 都是无 IO 纯内存状态。两个 Gio 适配分别保存 Editor、`layout.List` 与以 app ID 为键的 Clickable;500 项 viewport 测试验证只布局可见行。主循环或后台用例通过 `SetItems` 替换准备好的快照,Layout 不扫描 installed-app.json、不获取 Catalog。
T-204/T-606 图标链路为 `Catalog icon digest + DPI → 32 MiB/256-key memory LRU → verified disk → 流式 IconFetcher(maxBytes+1) → SHA-256/图片资源限制校验 → 原子磁盘缓存 → 后台 DecodeIcon → application event → UI ApplyIcon(paint.ImageOp)`。同一 key 由一个 in-flight leader 去重,不同 key 的磁盘/网络工作并行;全局锁只保护 memory/LRU/in-flight 元数据。磁盘与远端都重新校验,断网只使用已验证磁盘缓存;Catalog 快照删除 app 时两个 shell 剪枝对应 ImageOp。详情右栏只读取 `CatalogListModel.SelectedItem` 与内存 ImageOp,关闭详情不清空筛选或列表位置。
T-204/T-606/T-607 图标链路为 `Catalog icon digest + DPI → 32 MiB/256-key memory LRU → verified disk → 流式 IconFetcher(maxBytes+1) → SHA-256/图片资源限制校验 → 原子磁盘缓存 → 后台 DecodeIcon → IconReady/IconFailed application event → bounded FIFO relay + Window.Invalidate → Frame/UI ApplyEvent → ApplyIcon(paint.ImageOp)`。同一 key 由一个 in-flight leader 去重,不同 key 的磁盘/网络工作并行;全局锁只保护 memory/LRU/in-flight 元数据。relay 队列满时无损背压且可由 context/close 取消,后台从不修改 shell map。UI 只接受当前 app 最新且 icon_ref/DPI 匹配的 request_id;删除 app、替换 IconRef 或取消会使迟到结果失效,替换 IconRef 同时清除旧 ImageOp。磁盘与远端都重新校验,断网只使用已验证磁盘缓存;详情右栏只读取 `CatalogListModel.SelectedItem` 与内存 ImageOp,关闭详情不清空筛选或列表位置。
## 三、仓库目录结构
+1 -1
View File
@@ -25,7 +25,7 @@
- `core/` 与 `app-win7/` 只使用 **Go 1.20 可编译**的语法与依赖;新增依赖前检查其 go.mod 的 `go` 指令。泛型可用(1.18+),但 1.21+ 的标准库函数(如 `slices`、`maps`、`min/max` 内建)不得进入这两个模块。
- Gio 代码只出现在 `ui/gio/`;Windows 调用只出现在 `platform/windows/`,且必须有非 Windows stub,保证 `go test ./...` 在 Linux CI 可跑。
- 仅 Win10+ 存在的 Windows API 必须 LoadLibrary 动态加载、失败降级,不得成为 EXE 导入表强依赖。
- Gio Layout 每帧禁止 IO(磁盘/网络/哈希);后台任务只发布 application.Event,不直接改控件;控件状态按软件 ID 保存。
- Gio Layout 每帧禁止 IO(磁盘/网络/哈希/图片解码);后台任务只发布 application.Event,不得直接调用 `ApplyIcon` 或改控件/map。后台 event pump 只入有界 relay 并调用 `Window.Invalidate`;只有 Frame/UI goroutine可以 drain `ApplyEvent`。relay 满队列不得静默丢事件,关闭/取消必须解除背压等待。
- 图标 Fetcher 必须返回与 context 绑定的流,由 `IconCache` 在分配完整响应前执行声明长度拒绝与 `maxBytes+1` 有界读取;不得恢复为先读任意大 `[]byte` 再校验。缓存并发只允许按 key 去重,不得用横跨磁盘/网络的全局锁换取去重。
## 3. 安全纪律(违反即安全事故)
+6 -2
View File
@@ -90,7 +90,9 @@ Catalog `icon` v1 是 `sha256:<64 hex>` 内容引用,不是可直接请求的 UR
5. 远端和磁盘字节都必须复核 SHA-256,并通过图片完整解码、2 MiB 默认字节上限与 2048×2048 默认尺寸上限。
6. 只有验证成功的远端字节可用同目录临时文件原子写入磁盘;损坏的普通缓存文件删除后可重新获取,symlink/非普通文件按不安全布局拒绝。
7. 新进程断网时可读取再次验证成功的磁盘缓存;缓存损坏且远端不可用时返回 `no valid icon available`,UI 使用稳定占位图。
8. 后台完成 `IconCache.Load` 与 `DecodeIcon` 后只发布事件;Gio UI goroutine 消费事件后调用 `ApplyIcon`/Invalidate。Layout 只复用内存 `paint.ImageOp`,Catalog 移除 app 时两个 shell 同步剪枝对应 ImageOp。
8. `IconEventDelivery` 在调用方拥有的后台 context 中完成 `IconCache.Load` 与 `DecodeIcon`,成功发布 `IconReady`,失败只发布稳定分类的 `IconFailed`;取消直接结束且不发布迟到失败。事件身份为 request_id + app_id + icon_ref + DPI,不得把原始 URL/query 或 Gio 类型放进 payload。
9. application event relay 是有界 FIFO,队列满时执行可取消的 lossless backpressure,不静默丢图标结果。后台 pump 成功入队后只调用并发安全的 `Window.Invalidate`;Gio Frame/UI goroutine 在 Layout 前 drain 并执行 `ApplyEvent`/`ApplyIcon`。
10. shell 只接受当前 app 最新且 icon_ref/DPI 匹配的 request_id;删除 app、替换 IconRef、DPI 变化或取消请求后丢弃迟到 ready/failed。同一 AppID 更换 IconRef 时先清除旧 ImageOp,Layout 始终只复用内存 `paint.ImageOp`。
## 2. 标准软件包协议 v1(ZIP)
@@ -279,8 +281,10 @@ phase 只允许:`prepared`、`current_backed_up`、`staging_activated`、`rollba
| InstallRolledBack | 切换失败恢复 backup | app_id, error_code | 状态 → rollback 完成提示 |
| AppStarted / AppExited | 进程启动/退出检测 | app_id, pid | 状态 → running / installed |
| LicenseChanged | 许可证导入/撤销 | products | 授权视图刷新 |
| IconReady | 图标已完成可信加载与后台解码 | request_id, app_id, icon_ref, DPI, image.Image | 有界 relay 唤醒窗口;UI Frame 验证仍为最新请求后创建 ImageOp |
| IconFailed | 图标加载、校验或解码失败(取消不发布) | request_id, app_id, icon_ref, DPI, error_code | UI 记录诊断;仅保留同 icon_ref/DPI 的既有可信图标,否则继续占位 |
错误码为稳定英文枚举(如 `hash_mismatch`, `zip_path_escape`, `disk_full`, `app_running`, `signature_invalid`),UI 负责本地化文案。
错误码为稳定英文枚举(如 `hash_mismatch`, `zip_path_escape`, `disk_full`, `app_running`, `signature_invalid`),UI 负责本地化文案。图标事件只使用 `unavailable`、`invalid_content`、`unsafe_cache`,原始网络错误只返回后台调用方/日志,不得进入 UI payload。
## 5. CLI 参数合约
+10 -10
View File
@@ -13,26 +13,26 @@
## 当前快照
- 日期:2026-07-17
- 阶段:Phase 2 已完成(T-201~T-204);Phase 3 的 T-301 可恢复下载队列已完成;审核整改 T-604~T-606 已完成,T-607 已落成待领取,T-302 继续暂后置
- 阶段:Phase 2 已完成(T-201~T-204);Phase 3 的 T-301 可恢复下载队列已完成;审核整改 T-604~T-607 已完成,T-302 继续暂后置
- 技术栈:根 Go 1.25 workspace 只纳入 core/app-modern,`app-win7/go.work` 独立纳入 core/app-win7;版本闸门证明 modern Gio v0.10.1 与 win7 Gio v0.6.0 不交叉解析
- 生产代码:core 已有 Catalog/本地状态/存储、共享 Windows 安全相对路径策略、安全 ZIP 解压/回滚原型、无 IO 软件列表模型、按 key in-flight + 流式有界读取 + 32 MiB/256-key LRU 的可信图标缓存,以及默认并发 2 的持久可恢复下载队列;modern/win7 AppShell 已实现搜索/分类/视图、惰性列表、详情右栏与随 Catalog 剪枝的内存图标
- 测试:core 覆盖 Catalog、SemVer/12 状态、本地安装记录、Windows dot-space/设备名/Unicode 折叠路径攻击、ZIP destination 包含性、列表/图标并发/取消/读取边界/LRU、下载并发/暂停/取消/重试/Range/断连/恢复/事件失败与文件身份替换;两个 app 覆盖 500 项虚拟列表、ID 控件/图标剪枝稳定性、详情/ApplyIcon 与平台 stub;安装恢复矩阵保持通过
- 生产代码:core 已有 Catalog/本地状态/存储、共享 Windows 安全相对路径策略、安全 ZIP 解压/回滚原型、无 IO 软件列表模型、按 key in-flight + 流式有界读取 + 32 MiB/256-key LRU 的可信图标缓存、图标 Load/Decode 事件发布用例、有界 application event relay,以及默认并发 2 的持久可恢复下载队列;modern/win7 主循环已接 relay/Invalidate,AppShell 已实现搜索/分类/视图、惰性列表、详情右栏、图标请求身份与 UI-only ApplyEvent/ApplyIcon
- 测试:core 覆盖 Catalog、SemVer/12 状态、本地安装记录、Windows dot-space/设备名/Unicode 折叠路径攻击、ZIP destination 包含性、列表/图标并发/取消/读取边界/LRU、图标事件身份/失败分类/relay 背压与关闭、下载并发/暂停/取消/重试/Range/断连/恢复/事件失败与文件身份替换;两个 app 覆盖 500 项虚拟列表、ID 控件/图标剪枝稳定性、详情、UI drain 前后、最新/取消/换引用图标结果与平台 stub;安装恢复矩阵保持通过
- 数据:`schemas/` 已有 manifest/app.json/installed-app.json/download-task.json v1 Schema并注明 Windows 路径运行时权威规则;`testdata/catalog/` 有公开虚构清单样例;`testdata/zip/` 与 `testdata/download/` 记录运行时生成的攻击/传输矩阵
- 标准启动路径:`./init.sh` / `./init.ps1`(同步依赖、执行完整 Phase 0 闸门、打印双目标构建命令)
- 标准验证路径:`bash scripts/verify_phase0.sh` / `./scripts/verify_phase0.ps1`
- 版本管理:git 已初始化,main 分支,远端 origin 为 Gitea `opc/soft_quay`;harness 文档已提交
- 当前 blocker:无;下一步领取 T-607,建立图标后台 Load/Decode 经 application event、有界 relay 与 Invalidate 回到 Gio UI goroutine 的接线;适配器契约、VisibleItems 快照、Phase 1 中央目录预扫描等继续串行,T-302 继续后置
- 当前 blocker:无;下一步按 `docs/review/phase2-review.md` 最终顺序落成 modern/win7 适配器交互契约任务;VisibleItems 快照、Phase 1 中央目录预扫描等继续串行,T-302 继续后置
## 当前目录要点
| 路径 | 状态 | 说明 |
| --- | --- | --- |
| `docs/` | 已有 | harness coding 文档集(本次初始化完成) |
| `docs/tasks/` | 已有 | Phase 0~2、T-301 与 T-604~T-606 已完成;T-607 已落成待领取,其余审核整改尚未编号,T-302 暂后置 |
| `docs/tasks/` | 已有 | Phase 0~2、T-301 与 T-604~T-607 已完成;其余审核整改尚未编号,T-302 暂后置 |
| `scripts/` | 已有 | harness 治理、core 边界、Go 版本检查与 Phase 0 双平台验证入口 |
| `core/` | 已建 | Go 1.20 兼容;已有正式 Catalog、本地状态/存储、共享 Windows safepath、列表模型、有界并发图标缓存、可恢复下载队列与 Phase 1 安装安全原型 |
| `app-modern/` | 已建 | Go 1.25.0 + Gio v0.10.1;Modern AppShell 已接入虚拟列表、详情和随 Catalog 剪枝的内存图标 |
| `app-win7/` | 已建 | Go 1.20 + Gio v0.6.0;Legacy AppShell 已接入低成本列表、详情和随 Catalog 剪枝的内存图标 |
| `core/` | 已建 | Go 1.20 兼容;已有正式 Catalog、本地状态/存储、共享 Windows safepath、列表模型、有界并发图标缓存、图标事件/relay、可恢复下载队列与 Phase 1 安装安全原型 |
| `app-modern/` | 已建 | Go 1.25.0 + Gio v0.10.1;Modern AppShell 已接入虚拟列表、详情、图标事件 drain/过期拒绝和内存 ImageOp |
| `app-win7/` | 已建 | Go 1.20 + Gio v0.6.0;Legacy AppShell 已接入低成本列表、详情、图标事件 drain/过期拒绝和内存 ImageOp |
| `schemas/` | 已建 | `manifest.schema.json`、`app.schema.json`、`installed-app.schema.json` 与 `download-task.schema.json` |
| `testdata/` | 已建 | 包含 Catalog 假数据、ZIP 恶意矩阵与下载协议测试说明;后续任务继续扩展 |
@@ -40,9 +40,9 @@
任务状态以 `docs/tasks/` 各任务文件 frontmatter 的 `status` 为准。本节只写项目级摘要:
- 已完成:Phase 0 的 `T-001`~`T-004`;Phase 1 的 `T-101`、`T-102`、`T-103`;Phase 2 的 `T-201`~`T-204`;Phase 3 的 `T-301`;审核整改 `T-604`~`T-606`。
- 已完成:Phase 0 的 `T-001`~`T-004`;Phase 1 的 `T-101`、`T-102`、`T-103`;Phase 2 的 `T-201`~`T-204`;Phase 3 的 `T-301`;审核整改 `T-604`~`T-607`。
- 正在进行:无。
- 下一个可领取任务:`T-607`(建立图标后台结果的 UI 线程事件投递),依赖 `T-606` 已完成。
- 下一个可领取任务:无;先按 `docs/review/phase2-review.md` 最终顺序落成 modern/win7 适配器交互契约任务。
## 当前可运行内容
+2 -2
View File
@@ -284,5 +284,5 @@ modern/win7 的 `ApplyIcon` 都直接写 `shell.icons` map,Layout 同时读取
## 任务落地追踪
- `T-606` 已完成最终处理顺序第 1 项:按 key in-flight 去重、Fetcher 流式有界读取、32 MiB/256-key memory LRU 与 modern/win7 `shell.icons` 剪枝已实现并通过完整双 workspace 闸门。
- `T-607` 已按最终处理顺序第 2 项落成待领取:后台图标 Load/Decode 只发布 application event,有界 relay 请求重绘,由 Gio UI goroutine drain 后执行 `ApplyEvent`/`ApplyIcon`,并拒绝过期结果回写。
- 适配器交互契约、`VisibleItems` 快照、`shell.go` 拆分和 unsafe cache 诊断尚未编号;必须等待 T-607 完成并提交后再按顺序落成。
- `T-607` 已完成最终处理顺序第 2 项:后台图标 Load/Decode 只发布强类型 application event,有界 FIFO relay 无损背压并请求重绘,由 Gio Frame/UI goroutine drain 后执行 `ApplyEvent`/`ApplyIcon`;最新请求、删除 app、IconRef/DPI 变化和取消都阻断迟到结果回写。core 与双 workspace 定向测试及完整闸门通过。
- 适配器交互契约、`VisibleItems` 快照、`shell.go` 拆分和 unsafe cache 诊断尚未编号;下一任务从双适配器交互契约开始,继续按顺序串行落成。
+2 -2
View File
@@ -47,7 +47,7 @@
## 交互规则(硬约束)
- 每帧先 drain 点击事件,再提交用例;后台只发布事件,UI 在 ApplyEvent 中更新 ViewModel 后 `Window.Invalidate`。
- 每帧先 drain application relay 与点击事件,再提交用例;后台只把事件放入有界 relay 并调用并发安全的 `Window.Invalidate`,Frame/UI goroutine 在 `ApplyEvent` 中更新 ViewModel/ImageOp。
- Layout 中不得读磁盘、访问网络、计算哈希;图标走内存 + 磁盘缓存(按 DPI)。
- 数百个软件项滚动不卡顿是验收标准,不是优化项。
- 未验证/不兼容的软件显示原因,操作按钮禁用,不静默失败。
@@ -65,7 +65,7 @@ T-204 已落地的详情/图标约束:
- 点击软件行用 selected app ID 打开右侧详情,关闭后回到同一列表/筛选/滚动上下文。
- modern 与 Legacy 均显示版本、分类、简介、tags、状态、不可用原因、教程和主页文本;尚未接入的安装/启动/授权不伪装为已可执行操作。
- 后台把已验证图标解码为 `image.Image` 后发布完成事件;UI goroutine 消费事件再调用 `ApplyIcon`/Invalidate。`ApplyIcon` 预建 `paint.ImageOp`,列表与详情 Layout 只绘制内存操作;`SetItems` 在 Catalog 移除 app 时剪枝对应 ImageOp。
- T-607 由 `IconEventDelivery` 在后台完成可信加载/解码并发布强类型 `IconReady`/`IconFailed`;有界 FIFO relay 只请求 Invalidate,Frame/UI goroutine drain 后才调用 `ApplyEvent`/`ApplyIcon`。相同 app 只接受最新且 IconRef/DPI 匹配的 request_id;Catalog 删除、IconRef 变化或取消后丢弃迟到结果,IconRef 变化同时清除旧 ImageOp。
- 图标未命中或离线缓存不可用时显示非 emoji 的字母占位,不阻塞列表或详情。
## 导航规则
+13 -5
View File
@@ -3,12 +3,12 @@ id: T-607
title: 建立图标后台结果的 UI 线程事件投递
phase: 2
deps: [T-606]
status: TODO
status: DONE
created: 2026-07-17
issue: null
context_ref: null
context_ref: fba672381e9be69d8423026245ddf46ee52b16f6
claim_branch: null
work_branch: null
work_branch: agent/codex/T-607
write_paths:
- docs/tasks/T-607.md
- core/application/
@@ -44,7 +44,7 @@ T-606 已关闭图标缓存的全局锁 convoy、远端响应无界读取和内
- 提供统一构造/发布入口并校验空 ID、非法 `sha256:` 引用、非法 DPI、nil image 和 payload 类型,避免生产者各自拼装 `any`。
2. 在 `core/catalog` 增加可注入的后台结果发布用例:
- 接受已经完整确定的 `IconFetchRequest`、AppID/RequestID 和 application event publisher,在调用方提供的后台 context 中执行 `IconCache.Load` 与 `DecodeIcon`。
- 成功发布 ready;加载、校验或解码失败发布 failed;事件发布失败显式返回/报告,不得改变 T-606 的缓存可信性语义。
- 成功发布 ready;加载、校验或解码失败发布 failed;调用 context 取消/超时直接结束且不发布可能过期的 failed;事件发布失败显式返回/报告,不得改变 T-606 的缓存可信性语义。
- 用例本身不创建无限 goroutine或无界 worker 队列;并发/生命周期由上层调度者和 context 控制。
- 继续使用 T-606 的流式 Fetcher 合约,不新增图标 URL/CDN 规则。
3. 为 modern/win7 建立相同的 application event relay:
@@ -66,7 +66,7 @@ T-606 已关闭图标缓存的全局锁 convoy、远端响应无界读取和内
## 验收要点
- `EventIconReady`/`EventIconFailed` 是有效 application event,强类型 payload 不 import Gio,非法事件身份或 payload 被稳定拒绝。
- 后台图标用例调用现有 `IconCache.Load` 与 `DecodeIcon`,成功/失败均发布带 RequestID、AppID、IconRef、DPI 的对应事件;发布失败可观察且 context 取消能及时结束。
- 后台图标用例调用现有 `IconCache.Load` 与 `DecodeIcon`,成功/非取消失败发布带 RequestID、AppID、IconRef、DPI 的对应事件;发布失败可观察,context 取消及时结束且不发布迟到失败。
- modern/win7 后台 relay 收到事件时只入队并请求 invalidate,不会直接改变 `shell.icons`;Frame/UI goroutine drain 后才创建 `paint.ImageOp`。
- relay 使用有界 FIFO 队列和可取消的 lossless backpressure,满队列不静默丢事件;window/runtime 退出后无 goroutine 泄漏、死锁或 busy loop。
- 已删除 app、IconRef/DPI 已变化、RequestID 已被新请求取代的迟到 ready/failed 均被忽略;不会在 `SetItems` 剪枝后重新插入旧图标。相同 AppID 的 IconRef 变化会清除旧 ImageOp,直到新图标就绪前显示占位。
@@ -98,3 +98,11 @@ T-606 已关闭图标缓存的全局锁 convoy、远端响应无界读取和内
- 2026-07-17:根据 `docs/review/phase2-review.md` 交叉复核定稿的第二优先级整改落成任务;现有全局最大任务为 T-606,因此取 T-607,依赖已完成的 T-606。
- 2026-07-17:任务冻结后台 Load/Decode→application event→有界 relay/Invalidate→Gio UI drain/ApplyEvent 的线程边界;真实 URL 映射、viewport 调度、适配器全量契约、VisibleItems 快照和文件拆分继续按审核顺序串行处理。
- 2026-07-17:用两个隔离 workspace 的本地 `go doc gioui.org/app.Window.Invalidate` 核对 Gio v0.10.1 与 v0.6.0,两者都明确 Invalidate 可并发调用;因此只允许后台 relay 调 Invalidate,所有 shell/ImageOp 更新仍限定在 UI goroutine。
- 2026-07-17:在 `agent/codex/T-607` 分支领取任务,基线为 `fba672381e9be69d8423026245ddf46ee52b16f6`;保持单 Agent 串行执行。
- 2026-07-17:基线 `./init.ps1` 通过,包含治理/上下文/边界/依赖版本检查、Go 1.20.14 core vet/test、modern Go 1.25 与 win7 Go 1.20.14 的测试和 Windows amd64 构建。
- 2026-07-17:`core/application` 新增 `IconReady`/`IconFailed` 强类型事件、规范化 request/app/icon_ref/DPI 身份、三类非敏感失败码,以及带 FIFO lossless backpressure 的有界 `EventRelay`。relay 的 slot+mutex 关闭线性化保证 Close 后不再接收新事件,满队列等待可由 context/close 解除;`PumpEvents` 只入队并调用 Invalidate。
- 2026-07-17:`core/catalog.IconEventDelivery` 在调用方拥有的后台 context 中调用现有 `IconCache.Load` 与 `DecodeIcon`,成功发布 ready,非取消失败发布分类后的 failed,原始网络错误不进入 payload;取消不发布迟到失败,发布错误和非致命磁盘 warning 都返回调用方观察。
- 2026-07-17:modern/win7 主循环均接入 32-entry runtime/relay 与 Frame 前 drain;后台 pump 退出时由 context/runtime/relay 确定收尾。两个 AppShell 只在 UI goroutine `ApplyEvent`/`ApplyIcon`,按最新 RequestID + AppID + IconRef + DPI 拒绝迟到结果;删除 app、IconRef 变化、DPI 变化和取消会失效旧请求,IconRef 变化清理旧 ImageOp。
- 2026-07-17:定向验证通过:Go 1.20.14 `go vet ./application ./catalog`、`go test -count=10 ./application ./catalog`;modern/win7 `go test -count=5 ./ui/gio`,并验证对应 cmd 包编译。用例覆盖事件/payload 拒绝、后台 submit 在 UI drain 前不改 shell、FIFO 背压、关闭/取消、失败分类、发布失败、加载中取消、最新请求、IconRef/DPI 变化、删除 app 与取消后的迟到结果。
- 2026-07-17:尝试 Go 1.20.14 `go test -race -count=1 ./application ./catalog`;当前 Windows 环境缺少 GCC(`cgo: C compiler "gcc" not found`),race detector 不可用。按任务边界如实记录;确定性线程边界测试在 core 重复 20 次、双 UI 重复 5 次通过。
- 2026-07-17:完整 `./scripts/verify_phase0.ps1` 通过,包含治理/上下文/边界/版本校验、Go 1.20.14 core vet/test、modern Go 1.25 与 win7 Go 1.20.14 的 UI/平台测试及 Windows amd64 构建;协议、架构、路由、编码规则、审核追踪和当前状态已同步。