2026-07-17 10:49:58 +08:00
|
|
|
---
|
|
|
|
|
id: T-609
|
|
|
|
|
title: 修正 VisibleItems 快照生命周期
|
|
|
|
|
phase: 2
|
|
|
|
|
deps: [T-608]
|
2026-07-17 17:31:46 +08:00
|
|
|
status: DONE
|
2026-07-17 10:49:58 +08:00
|
|
|
created: 2026-07-17
|
|
|
|
|
issue: null
|
2026-07-17 17:31:46 +08:00
|
|
|
context_ref: ed9ded21109ec36373ed0f13fa42d1ebf55a5bbf
|
2026-07-17 10:49:58 +08:00
|
|
|
claim_branch: null
|
2026-07-17 17:31:46 +08:00
|
|
|
work_branch: agent/codex/T-609
|
2026-07-17 10:49:58 +08:00
|
|
|
write_paths:
|
|
|
|
|
- docs/tasks/T-609.md
|
|
|
|
|
- core/application/
|
|
|
|
|
- docs/routes.md
|
|
|
|
|
- docs/04-architecture.md
|
|
|
|
|
- docs/05-coding-rules.md
|
|
|
|
|
- docs/review/phase2-review.md
|
|
|
|
|
- docs/00-ai-start-here.md
|
|
|
|
|
- docs/06-tasks.md
|
|
|
|
|
- docs/current-state.md
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 问题 / 背景
|
|
|
|
|
|
|
|
|
|
`core/application.CatalogListModel.VisibleItems()` 当前直接返回 `model.visible`,`refilter()` 则从 `model.visible[:0]` 开始 append。Gio Layout 每帧只读当前切片时功能正常,但任何调用者只要跨一次 `SetQuery`、`SetCategory`、`SetView`、`ResetFilters` 或 `SetItems` 保留旧切片,旧切片的 backing array 就可能被新结果覆盖。导出的“immutable-by-convention snapshot”因此没有稳定生命周期,调用方也容易误以为旧快照可以安全保留到下一帧。
|
|
|
|
|
|
|
|
|
|
直接让 `VisibleItems()` 每次返回完整副本可以隐藏该问题,但 modern/win7 Layout 每帧都会调用它;对数百项列表逐帧复制会引入持续分配和复制成本,与虚拟列表性能目标冲突。Phase 2 审核定稿已选择在筛选状态实际变化时构造新 backing array、完成后替换当前快照,让读取继续保持 O(1)。
|
|
|
|
|
|
|
|
|
|
本任务只收紧共享 ViewModel 的快照代际语义:旧快照在后续 model 更新后内容保持不变,当前快照仍由调用方按只读约定使用。它不把 `CatalogListModel` 改成跨 goroutine 并发容器,也不为恶意调用方提供逐字段防御性复制。
|
|
|
|
|
|
|
|
|
|
## 方案
|
|
|
|
|
|
|
|
|
|
1. 修改 `core/application.CatalogListModel.refilter` 的快照构造方式:
|
|
|
|
|
- 先在局部新切片中按现有 category/view/query 规则构造完整结果,不得从 `model.visible[:0]` 复用上一代 backing array。
|
|
|
|
|
- 构造完成后再以一次普通字段赋值替换 `model.visible`;“原子替换”描述的是单 owner goroutine 内不暴露半成品,不引入 `sync/atomic`、mutex 或并发读写承诺。
|
|
|
|
|
- 保持现有筛选顺序、稳定项顺序、空结果语义和 `CatalogListItem` 值不变。
|
|
|
|
|
2. 保持 `VisibleItems()` 为无复制读取:
|
|
|
|
|
- 不在每次调用时 clone 当前切片,不新增逐帧 O(n) 分配。
|
|
|
|
|
- 更新注释明确返回值属于当前 snapshot generation,调用方可在后续 model 变更后继续读取旧 generation,但不得修改切片元素、嵌套 `Tags` 或容量范围。
|
|
|
|
|
- 同一 generation 内重复调用应观察同一 backing array;只有实际执行 refilter 才发布新 generation。setter 因值未变化而提前返回时不强制创建无意义快照。
|
|
|
|
|
3. 在 `core/application/catalog_list_test.go` 增加生命周期回归矩阵:
|
|
|
|
|
- 分别跨 `SetQuery`、`SetCategory`、`SetView`、`ResetFilters` 和 `SetItems` 保留旧快照,验证旧快照的长度、ID、字段及 tags 不被新结果覆盖。
|
|
|
|
|
- 对非空结果验证 refilter 前后 backing array 不同;同一 generation 的重复 `VisibleItems()` 不复制且仍指向同一 backing array。
|
|
|
|
|
- 覆盖空 Catalog、过滤为零项、恢复全部和 stable order,确保修复不改变现有 ViewModel 语义。
|
|
|
|
|
- 测试只通过公开 model 操作触发代际变化,不直接调用 `refilter` 或依赖未导出容量细节。
|
|
|
|
|
4. 保持两个 Gio 适配器调用方式不变,通过双 workspace 测试/构建证明 modern 与 Win7 每帧读取 `VisibleItems()` 不需要迁移到新 API。
|
|
|
|
|
5. 同步架构、路由和编码规则,冻结“变更时分配、读取时只读 O(1)、UI owner goroutine”契约。
|
|
|
|
|
|
|
|
|
|
## 验收要点
|
|
|
|
|
|
|
|
|
|
- `refilter` 不再通过 `model.visible[:0]` 复用上一代 backing array;新结果完整构造后才替换当前快照。
|
|
|
|
|
- 调用者在任一公开 refilter 入口前保存的非空旧快照,在后续 query/category/view/reset/items 变化后长度、顺序、字段和 tags 保持原值。
|
|
|
|
|
- 非空新 generation 与仍被持有的旧 generation 不共享外层 backing array;同一 generation 内重复调用 `VisibleItems()` 共享当前只读快照且不逐次复制。
|
|
|
|
|
- `VisibleItems()` 文档明确只读约定和生命周期,不声称线程安全;调用方修改返回切片或嵌套数据仍属于违反契约。
|
|
|
|
|
- 现有搜索、分类、all/installed/updates、selection、空状态和 stable order 测试保持通过,不改变 UI 可见业务语义。
|
|
|
|
|
- 不增加 `VisibleLen`/`VisibleItem` 第二套 API,不修改 modern/win7 shell 调用代码,不引入锁、`sync/atomic` 或新依赖。
|
|
|
|
|
- Go 1.20.14 + `GOWORK=off` 下 `go vet ./application`、`go test -count=10 ./application` 通过。
|
|
|
|
|
- modern Go 1.25.0 与 win7 Go 1.20.14 的 UI 测试和 Windows amd64 构建通过;`./scripts/verify_phase0.ps1` 全绿。
|
|
|
|
|
- `python scripts/validate_agent_context.py`、`python scripts/validate_harness_governance.py` 与提交前差异检查通过。
|
|
|
|
|
|
|
|
|
|
## 边界(不改什么)
|
|
|
|
|
|
|
|
|
|
- 不把 `CatalogListModel` 改成线程安全对象;所有 setter、selection 与 snapshot 发布继续由同一 application/UI owner goroutine 串行调用。
|
|
|
|
|
- 不在 `VisibleItems()` 每次读取时复制,不深拷贝每个 item/tag,不承诺防御违反只读约定的调用方写入。
|
|
|
|
|
- 不增加 `VisibleLen()`/`VisibleItem(index)`、iterator、泛型集合或其他并行列表 API,不修改公开业务字段和筛选规则。
|
|
|
|
|
- 不修改 modern/win7 的 `shell.go`、Gio 依赖或 T-608 适配器契约;双端只作为不迁移调用方的回归验证。
|
|
|
|
|
- 不拆分 `shell.go`,该维护项按审核顺序另立后续任务。
|
|
|
|
|
- 不处理 `ErrIconCacheUnsafe` quarantine/诊断策略、ZIP 中央目录预扫描、断电耐久、Catalog 签名向量或 T-302 安装整合。
|
|
|
|
|
- 不修改协议/Schema,不升级 Go/Gio,不合并 workspace,不修改、提交或删除用户的 `soft_quay.code-workspace`。
|
|
|
|
|
|
|
|
|
|
## 协作约束
|
|
|
|
|
|
|
|
|
|
- 按仓库当前规则由单 Agent 串行执行,不启动子 Agent。
|
|
|
|
|
- 本任务只允许修改 frontmatter 中的 `write_paths`;若修复需要改变 Gio 调用方式、公共协议或线程模型,必须停止并记录,不得在 T-609 内扩大边界。
|
|
|
|
|
- T-609 完成、完整验证并提交前,不落成或领取 Phase 2 审核顺序中的后续任务。
|
|
|
|
|
|
|
|
|
|
## 执行记录
|
|
|
|
|
|
|
|
|
|
- 2026-07-17:根据 `docs/review/phase2-review.md` 交叉复核定稿的第四优先级整改落成任务;现有全局最大任务为 T-608,因此取 T-609,依赖已完成的 T-608。
|
|
|
|
|
- 2026-07-17:代码图确认 `VisibleItems` 有 core 测试与 modern/win7 Layout/适配器契约等 8 个调用点,当前为直接返回;五个公开 mutation 入口最终调用 `refilter`,而 `refilter` 明确以 `model.visible[:0]` 重用上一代 backing array。
|
|
|
|
|
- 2026-07-17:任务采用审核定稿方案 1:状态变化时构造并替换新外层快照,每帧读取不复制;只读约定覆盖 slice 元素与嵌套 tags,但不把本任务扩展为并发安全或防御性深拷贝。
|
2026-07-17 17:31:46 +08:00
|
|
|
- 2026-07-17:在 `agent/codex/T-609` 分支领取任务,基线为 `ed9ded21109ec36373ed0f13fa42d1ebf55a5bbf`;保持单 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:先增加生命周期回归矩阵并在旧实现运行;query/category/view/reset/items 五条公开 mutation 路径均稳定复现旧 generation 被覆盖,实际状态变化后 backing array 地址也未变化,证明测试能捕获审核指出的问题。
|
|
|
|
|
- 2026-07-17:`refilter` 改为在局部 `visible` 中完整构造新结果后一次赋给 `model.visible`,不再使用 `model.visible[:0]`;`VisibleItems` 仍直接 O(1) 返回当前 generation,注释明确旧 generation 稳定、slice/Tags 只读、model 单 owner 且不支持并发读写。
|
|
|
|
|
- 2026-07-17:测试覆盖旧快照跨 SetQuery/SetCategory/SetView/ResetFilters/SetItems 后字段与 Tags 不变、非空 generation backing 分离、同 generation 重复读取共享 backing 且 `AllocsPerRun=0`、no-op setter 不换 generation、空 Catalog/无匹配/恢复顺序。Go 1.20.14 `go vet ./application` 与 `go test -count=10 ./application` 通过。
|
|
|
|
|
- 2026-07-17:modern Go 1.25.0 与 win7 Go 1.20.14 的 `go test -count=5 ./ui/gio` 分别通过,证明两个 Gio 调用方继续使用原 `VisibleItems()` API,无需修改 shell 或适配器契约。
|
|
|
|
|
- 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 构建;架构、路由、编码规则、审核追踪和当前状态已同步。
|