胡飞
9475328efd
test: 发布契约跟上 nsh-host feature 新增的侧栏依赖
...
plugin-ai-tools M2 给 universal-plugins 的 nsh-host feature 追加了三个
可选依赖(ai_chat_view/one-ui/serde,插件 AI 侧边栏用)。CI 的
script/test-release-packaging.mjs 把 feature 数组写成字面量断言,元素
间不容差异,linux job 十秒就红。断言改成元素间允许空白分隔的形式。
验证:node --test script/test-release-packaging.mjs 46 passed 0 failed。
2026-11-04 19:07:25 +08:00
胡飞
4fe4122c22
Merge remote-tracking branch 'cnb/dev' into refactor/nsh-host
2026-11-04 18:21:34 +08:00
胡飞
7802bce480
test: 发布契约跟上 nsh-host 的 feature 改名
...
CI 的 `script/test-release-packaging.mjs` 把「默认集里必须有 shell-plugins」
写成了字面量断言,改名成 nsh-host 之后第一个 job 十秒就红了。这次一起把
契约改成新形态,并顺带把 nsh 后端自己的 webview 也纳入同一条规则:
- 默认集断言改成 `["wasm-components","windows-native-rdp","nsh-host","database","shell"]`;
- `universal-plugins` 断言从 `gpui-shell` 换成 `nsh-platform-gpui/nsh-core/nsh-engine`,
并新增一条:workspace 里 `nsh-platform-gpui` 必须是
`default-features = false, features = ["js"]` —— 它的 `webview` 特性会拖进
wry/WebKitGTK,和原来的 embedded-webview 一样不能进默认集。
(`.github/workflows/` 里两处注释还写着 shell-plugins:本机 token 没有
workflow scope,推不了 workflow 文件的改动,留到有权限时顺手改。)
验证:`node --test script/test-release-packaging.mjs` 45 passed;
`cargo test -p navop_app --test i18n_locale_keys_contract` 1 passed。
2026-11-03 15:44:28 +08:00
胡飞
2daa7c51d6
feat(branding): 独立产品图标改成「N + 领域脚标」
...
上一版把 N 换成了终端/数据库字形,产品看起来像另一个牌子。改成保留品牌
N 主图,只在右下角压一枚圆形脚标:Shell 是终端提示符,Data 是数据库柱体。
- 品牌主图取 Navop.icns 里最大的 1024 版本,落到 resources/branding/navop-1024.png,
三个尺寸都从它缩,细节不糊。
- 脚标按画布比例定位(圆心 0.792、半径 0.158),外圈深色描边把脚标和主图
背景分开,缩到 32px 仍能看出「N + 一个亮圆点」。
- 生成脚本去掉按产品重绘主图的分支,只剩「主图 + 脚标」合成;斜向渐变改用
「横条渐变逐行偏移」,避免旋转补出的黑角把脚标啃掉一块。
2026-11-03 13:59:42 +08:00
胡飞
a701121637
feat(branding): Shell 与 Data 换成各自的图标,三个产品不再共用一套
...
之前三个产品共用 resources/windows/navop.ico、macOS 的 Navop.icns 与 Linux 的
三张 navop png(Shell/Data 的 png 只是复制品),装在一起几乎分不出来。
- 新增 script/generate-product-icons.py:同一套视觉语言(深蓝圆角底 + 青蓝渐变
前景),Shell 用终端提示符、Data 用数据库柱体,输出 Linux PNG(128/256/512)、
macOS ICNS、Windows ICO(16~256)。
- macOS:bundle-macos.sh 按产品选 Navop / NavopShell / NavopData 的 icns,
Info.plist 的 CFBundleIconFile 仍是 Navop,拷贝时统一重命名。
- Windows:apps/navop-shell|navop-data/build.rs 嵌入各自的 ico;独立产品的
MSI/卸载项通过 build-product-installer.ps1 的 -IconPath 传入产品图标。
- Linux:package-linux-product.sh 本来就读 <product>-<size>.png,把复制的
png 换成新绘制的一套。
- 打包测试补「每个产品都有自己的图标」:build.rs 的 ico 路径、macOS icns 映射、
Linux 三尺寸 png、独立产品安装包 -IconPath。
2026-11-03 13:50:22 +08:00
胡飞
8e21c5425f
fix(installer): 独立产品安装脚本支持绝对输出目录
...
`build-product-installer.ps1` 里 `$resolvedOutDir = Join-Path $repoRoot $OutDir`,
而 release.yml 传的是 `-OutDir "${{ github.workspace }}\dist-product"`,
Join-Path 拼出 `D:\a\navop\navop\D:\a\navop\navop\dist-product`,New-Item 直接报
「filename, directory name, or volume label syntax is incorrect」,
navop-shell / navop-data 的 Windows 安装包发版失败。
改为:绝对路径原样使用,相对路径才拼仓库根(-IconPath 同理)。
2026-11-02 23:05:47 +08:00
胡飞
e537bc64af
ci: Windows 产品检查拆成独立的 pwsh 步骤
...
`shell:` 不支持 matrix 上下文,写成 `${{ matrix.platform == 'windows' && 'pwsh' || 'bash' }}`
会让整个 workflow 解析失败(Unrecognized named-value: 'matrix'),CI 直接起不来。
改为按平台拆两步、各自写死 shell:Windows 走 pwsh(避免 Git Bash 的 link.exe
盖掉 MSVC 链接器),其余仍是 bash。
2026-11-02 22:24:41 +08:00
胡飞
6680eb586e
ci: Windows 产品检查改走 PowerShell
...
Git Bash 的 PATH 里 `link.exe` 是 GNU coreutils 的 link,会盖掉 MSVC 的链接器,
`cargo check` 编译 build script 时报 `/usr/bin/link: extra operand`,
Windows 的四个产品检查条目全挂。改用 `shell: pwsh`(仅 Windows 条目)。
顺带在 script/test-release-packaging.mjs 里锁住这条:断言 Windows 分支
先跑 ilammy/msvc-dev-cmd、检查步骤用 pwsh 表达式且不含 bash 的 set -o pipefail。
2026-11-02 22:22:19 +08:00
胡飞
512957924d
fix(release): 修掉 macOS 独立产品打包名与 Windows 资源段两处发版失败
...
beta.4 的构建全部通过,但打包阶段失败,两处都不是编译问题:
1) macOS 独立产品:`Resolve platform matrix` 用 `tr ' ' ','` 把两个矩阵拼在
一起,`bundle_name` 里的空格被当成逗号,产物目录变成 `Navop,Shell.app`,
随后的 `tar` 找不到包。改用参数展开各去一层方括号,字节原样保留。
2) Windows 全部四个 job:`navop.exe` 没有 `.rsrc`,校验步骤报
「不可能携带内嵌清单」。产品拆分后资源嵌入留在 `crates/navop_app/build.rs`,
而它现在是库:`cargo:rustc-link-arg` 只作用于本包 targets,嵌出来的资源
到不了 `apps/navop*` 的 exe。改为三个二进制 crate 各自的 build.rs 调用
共享的 `resources/windows/embed_app_resources.rs`。
顺带把这两类回归纳入 `script/test-release-packaging.mjs`:拼接矩阵的脚本会被
真实执行并断言 14 条条目与带空格的 bundle_name;同时断言资源嵌入留在二进制
crate(`crates/navop_app/build.rs` 不得存在)。44 个打包测试全绿。
2026-11-02 22:03:26 +08:00
胡飞
053f1b5dc8
fix(release): beta 发版脚本按 navop_app 名同步 Cargo.lock
...
产品拆分后工作区包名从 main 改成 navop_app,beta-release.sh 还在按 main 找
lock 条目,rsync 阶段直接报 <missing>。改成精确改写 navop_app / navop /
navop-shell / navop-data 四个成员版本(cargo metadata --no-deps 不会重算
成员版本,靠它刷 lock 本来就不成立),并同步更新提示文案。
2026-11-02 18:52:43 +08:00
胡飞
0b1bb9f25c
fix(release): beta 发版脚本默认推送 origin,tag 才能触发 GitHub Actions
...
beta-release.sh 原来默认 REMOTE=cnb,tag 只推到 CNB 不会触发
release-trigger.yml,beta 构建根本不会开始。改为默认 origin,并新增
MIRROR_REMOTE=cnb 在成功后镜像推一份 dev 与 tag(失败只警告):
tag 唯一性预检也同时检查两个远端。
2026-11-01 17:57:34 +08:00
胡飞
96f27ab713
feat(update): 检查更新支持切换镜像源(Cloudflare / CNB / GitHub)
...
- 设置「关于 → 更新」新增「更新镜像源」下拉(自动 / Cloudflare / CNB / GitHub),
切换后立即落盘、提示并按新源重查一次;AppSettings 新增 update_mirror 字段。
- 客户端新增镜像尝试链:默认 Cloudflare → CNB → GitHub,选中的源排到第一位,
失败依次回退;测试通道不回退 GitHub(releases/latest 只给正式版),
「已是最新」不早退,保留 R2 清单慢一拍时 GitHub 兜底的能力。
- CNB 镜像:读 git-upload-pack 广告里的 tag(匿名可读),按通道选最大 tag,
再取 Release 资产 feed-<product>[-beta].json,并把清单里的下载地址改写到
CNB 同名资产。CNB 没有 latest 别名、仓库文件直链匿名不可读,故走这条路。
- CI:upload-r2 在清单就绪后把三份清单复制成扁平 feed-*.json 资产上传到 Release,
稳定发布同时刷新 beta 键位与 feed-*-beta.json(beta 用户可平滑过渡到正式版),
CNB 同步改为由 upload-r2 末尾派发;release.yml 不再并发派发 sync。
- 文档:新增 docs/design/update-mirror.md;RELEASE.md 补充 feed 资产与 CNB 镜像。
- 测试:app_kit update 88 项、one-core update_ 26 项、navop_app settings/update_mirror、
setting_tab 38 项、i18n 契约、node script/test-release-packaging.mjs 43 项全绿。
2026-11-01 10:37:25 +08:00
胡飞
46a5978443
refactor(product-split): main 目录归位 crates/navop_app,包名统一 navop_app
...
产品装配移出后 main 已只剩库,却仍占着仓库根目录、与 apps/* 并列,看起来像一个
产品。本次把它落回 crate 层:
- git mv main crates/navop_app;package name 由 main 改为 navop_app(与 lib 名
一致),删掉冗余的 [lib] 块;根 members 同步。
- 三个 app 改为直接相对路径依赖 navop_app = { path = "../../crates/navop_app" },
不进 workspace.dependencies(Cargo 不允许再关继承来的 default-features)。
- 26 处逃出 crate 根的 include_str! 各加一层 ../(resources/*、core/build.rs、
apps/* 与 crates/*_view 的 Cargo.toml、app_kit 源码);build.rs 的 icon/manifest
路径同步。
- env_file.rs 与 i18n_locale_keys_contract 的「工作区根」改为上两级;
cross-crate 源码断言(core/window_close、db_view、one_ui、windows_rdp_host)指向
新路径。
- locales/main.yml 保持原名:i18n 命名空间与全仓 t!() key 绑定,改名是另一件事。
- 发布链路同步:release_pr.py(MANIFEST_PATH、lock_app_version、报错文案)、
beta-release.sh 版本包名集合、release-tag.sh / ci.yml / release.yml /
.github/RELEASE.md / AGENTS.md / CLAUDE.md 等文档的路径与 -p navop_app。
- 顺带修两处未推送提交遗留的 CI 红线:app_kit::install 的
expected_binary_name cfg 过宽(macOS dead_code)、测试里多余的
is_supported_app_bundle import;release PR 测试夹具补齐四个 manifest;
windows_rdp_host 的 ALLOWED_FILES 断言对齐 release_pr.py 现状。
验证:cargo test -p navop_app(595 lib + 4 集成契约)、-p app_kit(149)、
one-ui/windows_rdp_host 契约、三个 app cargo build、cargo check --all-targets
全通过;script/tests 32 项、release-packaging 与 scripts/ai 的 node 测试全绿。
2026-11-01 00:52:04 +08:00
胡飞
b696f4ddb5
feat(release): 统一 tag 三产品发布,独立产品接入自更新链路
...
一个 tag 同时发布全量 navop、navop-shell、navop-data 三个产品:
- release.yml 矩阵加 product 维度(products 输入 all/full/standalone 可裁剪),
独立产品覆盖四个主力平台,复用全量的构建/打包/上传步骤;独立产物公开命名
统一 {product}-{version}-{label},Windows 补裸 exe zip 供自更新器直连。
- 版本校验(prepare job / release-tag.sh / beta-release.sh / release_pr.py)
全部扩成四个 manifest(main + 三个 app crate)统一比对,app 版本对齐 0.22.0。
- upload-r2.yml 为三个产品各生成一份更新清单:全量 updates/latest.json,
独立产品 updates/navop-shell/shell/latest.json 与 updates/navop-data/data/
latest.json(beta 通道各插 beta/ 段),键位与 config.rs feed_url() 一致。
- install.rs 自更新安装器按运行时 identity 参数化 bundle/exe/binary 候选名,
独立产品可自更新,全量保留 OnetCli.app / onetcli.exe legacy 兼容。
- 删除 standalone-products.yml(能力并入 release.yml);package-linux-product.sh
支持显式产物命名并入统一发布;RELEASE.md 补统一发布与 products 输入文档。
验证:cargo test -p app_kit --lib 149 passed;clippy 无新告警;
node --test script/test-release-packaging.mjs 40/40;工作流 YAML 解析通过。
2026-10-31 23:24:48 +08:00
胡飞
87a6c0b9f1
feat(update): 新增 beta 测试通道,测试版与稳定版分流
...
服务端把测试版清单单独落在 updates/beta/latest.json,稳定清单
updates/latest.json 完全不动,老客户端行为不变:
- release.yml 从 tag 形态推导通道,beta 发布给 GitHub Release 加
--prerelease,并把 channel 显式派发给 upload-r2.yml;
- upload-r2.yml 新增 channel 输入(留空按 tag 推导 + 白名单校验),
清单按通道落位;prune 额外保留 beta 清单指向的安装包,避免 404;
- script/beta-release.sh 一条命令完成 beta 版本号 + changelog +
commit + tag + push,--dry-run 可预演;
- script/test-release-packaging.mjs 补 beta 清单与 prune 场景断言。
客户端新增「更新通道」设置(稳定版/测试版,默认跟随构建版本):
- AppSettings.update_channel,预发布构建默认 Beta;
- feed 由通道派生,stable 永远只接受正式版本号,beta 才接受预发布;
- beta 通道不回落 GitHub Releases(该端点语义排除 prerelease),
beta 清单 404 视为「还没有 beta 版本」而非检查失败;
- 「跳过此版本」按通道分别记录(beta:/stable: 前缀),互不影响;
- 关于页更新分组加通道下拉(切 Beta 先确认)、预发布构建版本号加
Beta 标签,切回稳定通道时给出说明条;更新弹窗标题与徽标区分测试版。
文档:docs/design/beta-channel.md 记录分层、推导规则与客户端消费方式;
.github/RELEASE.md 补 beta 发版流程。
2026-10-31 22:21:38 +08:00
胡飞
9a32158399
test(release): 对齐目标包默认 feature 与 update 模块新路径
...
- main 默认 feature 追加 database/shell 后,同步 script/test-release-packaging.mjs 断言
- update/install.rs 已下沉到 crates/app_kit,修正契约脚本读取路径
2026-10-30 16:28:29 +08:00
胡飞
b0dc6b3792
refactor(product-split): 裁剪 main 的领域依赖并加依赖隔离契约测试
...
- main/Cargo.toml 删除 6 个已无引用的 optional 领域依赖:
db、ssh、sftp_view、known_hosts_view、port_forwarding、port_forwarding_view
(builtin-duckdb 改由 db_view 转发)
- 修 shell-only 构建下的 Redis 资源池 helper cfg、外观页误加的 cfg
- 新增 script/check-product-deps.sh(本地依赖树守卫)
- 新增两个 manifest 契约测试:独立产品只开自己领域 feature、
database/shell feature 互不包含对方领域依赖
- 文档补 §27(main 已无领域 use、cfg 161→57、验证与剩余项)
2026-10-30 16:21:39 +08:00
navop
2c474a42f7
docs(product-split): 增加三产品内部依赖图与生成脚本
2026-10-29 22:58:08 +08:00
胡飞
ed7614d225
feat(packaging): 参数化 Shell/Data 的 Windows 安装器身份
2026-10-29 03:08:43 +08:00
胡飞
355be3cf71
ci(packaging): 增加 Shell/Data 的 macOS 与 Linux 构建矩阵
2026-10-29 03:07:36 +08:00
胡飞
04fdc8c95d
feat(packaging): 增加 Shell/Data 的 Linux 包资源与独立打包脚本
2026-10-29 03:06:52 +08:00
胡飞
5974b0d3aa
feat(packaging): macOS 应用包按产品生成独立身份
2026-10-29 03:06:07 +08:00
胡飞
8480e16b71
test(packaging): 校验 navop 产品包与主包版本一致
2026-10-29 02:37:10 +08:00
胡飞
1f19032001
chore: 对齐 main 抽 lib 后的构建与文档命令
2026-10-29 02:35:39 +08:00
胡飞
4a9c41b57e
refactor(main): 抽出 navop_app 库并新增 apps/navop 全量包
2026-10-29 02:34:56 +08:00
胡飞
4685eded34
fix(ci): 发布打包检查跟随 gpui-shell 合并更新断言
...
23cb37bc6 把 gpui-component-shell 并进 gpui-shell,universal-plugins 的
shell-plugins 只剩 dep:gpui-shell;script/test-release-packaging.mjs 仍在断言旧
的两个 crate,Linux Test job 的第一步「Test release packaging」因此失败。
2026-10-27 19:08:23 +08:00
胡飞
fa6ca0d1b1
fix(windows): 宿主自嵌 Common-Controls v6 清单,修 v0.20.1 启动即死
...
v0.20.1 的 Windows 产物(zip / portable / MSI 里装的是同一个 navop.exe)启动即失败:
「navop.exe - 无法找到入口 / 无法定位程序输入点 TaskDialogIndirect 于动态链接库
…\AppData\Local\Temp\navop-update\staged-<pid>-<ms>\navop.exe 上」。加载器在 `main`
之前就以 STATUS_ENTRYPOINT_NOT_FOUND(0xC0000139) 退出,所以更新表现成「什么都没发生」。
根因不是缺 comctl32.dll,而是产物**没有任何应用程序清单**:
- gpui 的 Windows 层在 `PlatformWindow::prompt` 中静态导入 `comctl32!TaskDialogIndirect`
(实测 v0.20.1 的 navop.exe:IMPORT 表里有该符号、DELAY_IMPORT 目录为空 ⇒ load-time 导入);
- 该导出只存在于 Common-Controls v6 的 SxS 程序集,只有 exe 自己的清单声明了
`Microsoft.Windows.Common-Controls 6.0.0.0` 才会激活 v6;没有清单就挂 System32 里的
5.82(不导出该符号)⇒ 加载器失败;
- 实测 v0.20.1 的 navop.exe 全文件搜 `Common-Controls` / `<assembly` /
`requestedExecutionLevel` 零命中 ⇒ 清单确实没被嵌进去。
清单的责任方在 v0.20.1 变了:`gpui-fast 0.1.8` 的 build.rs 是
`embed_resource_unless_host_provides_it()`,而 gpui-kit 声明了
`windows-manifest-provided-by-host`(`cargo tree -e features -p main -i gpui-fast` 可核)。
Cargo 特性只做并集、下游关不掉 ⇒ 这份清单只能由宿主 navop 自己提供。
v0.20.0 走旧门面 `gpui-pre`,它是无条件 embed 的,所以此前没暴露。
- `resources/windows/navop.manifest.xml`(新增):宿主清单,与上游 Zed 的
`crates/gpui/resources/windows/gpui.manifest.xml` 对齐(asInvoker + supportedOS Win10 +
Common-Controls 6.0.0.0 + `dpiAware true/pm` / `dpiAwareness PerMonitorV2` /
`heapType SegmentHeap`)。DPI 与堆这一段刻意与 v0.20.0 保持一致:gpui 两代都**不**在运行时
调 `SetProcessDpiAwareness`(全仓 grep 无命中),清单是 DPI 感知的唯一来源,漏掉会退回
DPI-unaware(高 DPI 下整窗被系统位图拉伸)。文件不带 XML 声明:winresource 是把清单按 RC
字符串逐行写成 `" <line> "`,声明前出现空白不是合法的 XML prolog。
- `main/build.rs`:加 `set_version_info(VersionInfo::FILETYPE, 1)` +
`set_manifest(include_str!("../resources/windows/navop.manifest.xml"))`。前者不是可选项 ——
winresource 只在 `VersionInfo::FILETYPE` 存在时才写清单块(`winresource-0.1.31/lib.rs:578`),
漏了会**静默不嵌清单**。用 `set_manifest` 而非 `set_manifest_file`,避开 rc 的路径转义。
- `script/validate-windows-exe.mjs`(新增):PE 层门禁,解析节表定位 `.rsrc` 并在其原始区间内查
三条必需串,另外判别「静态导入 TaskDialogIndirect 却无清单」这一真实故障组合;失败退出码 1
并打印含 `0xC0000139` 的修法。判据**必须同时搜 ASCII 与 UTF-16LE**:实测 llvm-rc 对纯 ASCII
清单写的是 8 位,MSVC rc.exe 才写 UTF-16,只搜一种编码必漏。
- `.github/workflows/release.yml`:Windows 腿在 `Build release binary (Windows)` 之后、
`Package (Windows)` 之前插入 `Validate Windows executable manifest`。这段代码在 macOS/Linux 上
根本不参与编译,而「清单没嵌进去」在编译期没有任何信号,只能在真实产物上校验。
- `script/test-release-packaging.mjs`:加合成 PE fixture(`peFixtureWithResource`)与两个用例
—— 断言清单内容、`main/build.rs` 的三处调用、workflow 步骤的存在与顺序;以及门禁对
正样本 / 负样本 / 非 PE 三种输入的退出码与报错内容。
验证(本机为 macOS,无法编译 Windows target):
- `node --test script/test-release-packaging.mjs` = 33 passed / 0 failed
- `cfg(target_os = "windows")` 块在 macOS 上不参与编译,`cargo check` 给不了信号。因此建探针
工程把 `main/build.rs` **逐字复制、只删掉那行 `#[cfg]`**,配
`[build-dependencies] winresource = "0.1"` ⇒ 该块**编译通过**(`set_version_info` /
`set_manifest` 的签名与 `winresource 0.1.31` 一致)。
- 取 winresource 真实生成的 `resource.rc` 手工喂 `llvm-rc`:退出码 0,`.res` 资源目录读出
`VERSION 1 / ICON 1..6 / GROUP_ICON 1 / RT_MANIFEST 1(1120 字节)`—— 清单落在加载器唯一认的
「type 24 / id 1」位置。
- 把该 `.res` 当 `.rsrc` 负荷塞进合成 PE:门禁判 OK;对**真实 v0.20.1 的 navop.exe** 判 FAIL
(退出码 1,报错含 `TaskDialogIndirect` 与 `0xC0000139`)。
- 未在本机验证:MSVC rc.exe / link.exe 真实产物能否正常启动、DPI 表现如何(需 Windows)。
CI 的 `windows-2022` 腿会编译这段代码。
2026-10-13 19:54:41 +08:00
胡飞
95ae24f351
test: packaging test follows the gpui-webview pin to the gpui-kit fork
...
The workspace no longer aliases upstream's pre-rename gpui-wry; the
embedded webview now rides the same gpui-kit rev (and GPUI Fast
backend) as the rest of the UI stack.
2026-10-11 09:15:11 +08:00
胡飞
7d66a491ed
test(release-packaging): 跟随 gpui-webview 重命名更新断言
...
上游 #3395 把 gpui-wry 改名为 gpui-webview 后,ai_chat_view 的依赖 key
变成 gpui-webview(workspace 里用 package="gpui-wry" 钉回旧实现)。
打包测试的两处断言仍写死 dep:gpui-wry,Linux 上先跑到这条就挂了;
改成新 key,并补一条断言把 workspace 里的 pin 形态也锁住。CLAUDE.md
里的 crate 名说明一并跟上。
2026-10-08 15:17:38 +08:00
胡飞
24abe96da8
fix(release): Windows MSI 快捷方式不再引用图标表,图标取自 exe 内嵌资源(issue #325)
...
issue #325 报告桌面上的 Navop 快捷方式图标退化成通用白纸。仓库里没有任何隐藏快捷
方式箭头或桌面图标的功能,现象落回安装包给出的快捷方式图标上:navop.wxs 的两个
<Shortcut> 都带 Icon="NavopIcon",即引用 Icon 表。MSI 会把 Icon 表里的图标另存成
一份独立文件作为快捷方式的图标来源(文档还要求这类图标与目标扩展名一致,而我们
是 .ico 对 navop.exe),这份文件一旦被清理或图标缓存失效,快捷方式就只能显示通用
白纸图标;而 navop.exe 自己其实已内嵌同一份图标(main/build.rs 的 set_icon)。
因此两个快捷方式一律不再引用 Icon 表,图标改由目标 exe 的内嵌资源提供,与
file_association.rs 的 DefaultIcon = "<exe>,0" 保持同一口径。控制面板「应用和功能」
的图标仍走 Icon 表,<Icon Id="NavopIcon"> 与 ARPPRODUCTICON 原样保留。
契约收紧:test-release-packaging.mjs 新增用例断言两个 <Shortcut> 不带 Icon=、卸载项
图标仍走 Icon 表、且 build.rs 确实内嵌 resources/windows/navop.ico,保证「留空」不是
「没图标」;validate-windows-msi.ps1 新增 Assert-MsiEmptyValue 并直接校验安装产物里
Shortcut.Icon_ 为空,把这条规则钉在 Windows release 阶段。
验证:
- node --test script/test-release-packaging.mjs:31 passed / 0 failed(含新增用例)
- 变异验证:把 Icon="NavopIcon" 临时加回 Desktop 快捷方式,新增用例转红;还原后转绿
- validate-windows-msi.ps1 未在本机执行:macOS 无 pwsh、无 WindowsInstaller COM,
只在 Windows release CI 里跑;MSI 装完快捷方式图标的真实表现需 Windows 实机验收
2026-10-07 17:35:25 +08:00
胡飞
320bc63c01
fix(ci): R2 清理按真实输出解析版本列表
...
aws s3api list-objects-v2 --query "CommonPrefixes[].Prefix" --output text 对扁平
列表返回的是「一行、tab 拼接」,而清理步骤用 ^...$ 逐行锚定解析,结果恒为空,
于是存在性护栏每次都拦下删除:
Refusing to prune: releases/<tag>/ is not present in R2
扩展仓用 gbase8s-v0.1.25 试点时就是这样失败的:一个对象都没删(护栏按设计保护
住了),但清理也从未真正执行。本地用假 S3 端点 + aws-cli 2.35.21 复现确认。
修法:列表先 tr '\t' '\n' 归一成一行一个再锚定解析(CLI 若真按行输出也不受影响),
并打印实际读到的版本,下次失败能直接从日志看到桶里有什么。
测试桩原来按「一行一个」输出,正好把真实现场的形态掩盖过去;现在桩按真实的
单行/tab 形态输出,并强制要求带 --delimiter。变异校验:去掉 tr 归一化或去掉
--delimiter,测试都会失败。
2026-10-07 09:45:41 +08:00
胡飞
e99ddd18a5
ci(r2): 补回被 c1db9f1cd 覆盖掉的 R2 保留策略
...
c1db9f1cd(docs-site 更新到 v0.19.5)是在一份以 main 为基准的索引上提交的,
顺带把 74d0e0595 的三个文件恢复成了旧内容,清理逻辑与测试一起丢失。
这里按 74d0e0595 原样取回,内容未作改动。
- .github/workflows/upload-r2.yml:上传校验通过后清理其余 releases/<tag>/
- script/test-release-packaging.mjs:契约测试 + 假 aws 行为测试
- .github/RELEASE.md:记录「R2 只保留最新版本」的保留策略
本地 node --test script/test-release-packaging.mjs 30/30 通过。
2026-10-06 23:30:08 +08:00
胡飞
c1db9f1cd1
docs(docs-site): 更新到 v0.19.5
...
首页三语言发布亮点整体更新:个人同步 WebDAV 后端、SSH Agent 转发、表设计器「刷新表结构」、终端粘贴确认弹窗「打开设置 / 不再提示」、外部驱动按调用类别区分请求超时(连接高级设置新增「请求超时(秒)」)、事务提交按钮 loading,以及中文过滤输入崩溃、Linux 退出 SIGABRT、macOS Touch Bar 关窗闪退等修复。
指南章节:teams-sync-security 补 WebDAV 后端与「目录不可用」处理;ssh-terminal 补 ssh-agent 转发与 ForwardAgent;schema-tools 补「刷新表结构」;database-connections 补按调用类别的请求超时与连接级覆盖。
2026-10-06 23:21:51 +08:00
胡飞
74d0e05951
ci(r2): 上传校验通过后只保留最新版本
...
R2 桶里越堆越多的是历次版本的安装包,而下载入口只认最新一份:
updates/latest.json 只指向本次 tag,旧版本另有 GitHub Release 与
CNB 镜像存档。所以在上传并逐对象校验成功之后,清掉其余
releases/<tag>/ 前缀。
护栏:
- 排在验证步骤之后且不带 always(),上传失败时一个对象都不删;
- 只处理 releases/ 前缀,更新清单所在的前缀永不触碰;
- 本次 tag 不在桶里就拒绝清理,宁可留下旧版本也不删最后一份可用包;
- 非稳定 tag(预发布)跳过清理,不会带走上一个稳定版本的安装包。
顺带补上契约测试与行为测试(抽出的脚本配假 aws 跑真实清理逻辑),
并在 .github/RELEASE.md 记录这条保留策略。
2026-10-06 23:21:37 +08:00
胡飞
a246ef8bfd
macos: 关掉「关闭即隐藏」开关,窗口重新走 remove_window()
...
上游 zed#65186(gpui_macos: Fix crash closing windows on Touch Bar Macs)把根因
修掉了:accesskit 不再用 SubclassingAdapter 动态替换内容视图的类,改成挂在
GPUIView 上的普通 Adapter。那次「还原类」正是破坏 AppKit Touch Bar 观察者 KVO
状态、让窗口关闭时抛异常的动作。该提交随 zed upstream/main 合进 fork,发布为
gpui-pre fork-0.3.124。
因此:
- release.yml 不再给 x86_64-apple-darwin 传 macos-touchbar-window-hide,
extra_features 变量与其分支一并删除,三个平台交付同一形态:关闭即销毁。
- script/test-release-packaging.mjs 同步改成断言这一步不再出现 --features /
extra_features,29 项全过。
- 机制整套保留(HIDE_WINDOWS_ON_CLOSE 常量、隐藏路径、关闭漏斗三态、弹窗登记表
与全部契约测试),现在恒为 false;重新打开只需给对应 target 传该 feature。
已验证 --features macos-touchbar-window-hide 下 one-core 仍以 -D warnings
干净编译,回退路径可用。
改动只涉及构建开关、注释与文档,Rust 逻辑未动:one-core 关窗/弹窗契约测试
(8 + 14)、remote_file_editor 契约测试 6 项、main 的 -D warnings 编译均通过。
本次没有真机验证 —— 手上没有 Touch Bar 机型;判定依据是上游把出问题的那条
代码路径整个删掉了。若闪退复现,第一动作是重新打开这个开关。
2026-10-06 17:16:42 +08:00
胡飞
eaad024e62
fix(core): Touch Bar「关闭即隐藏」收进 macos-touchbar-window-hide 开关,只为 Intel Mac 生效
...
关闭窗口时销毁原生 NSWindow 会触发 AppKit 的 Touch Bar 观察者注销路径
(navop#262 / #308 / #314 的 SIGABRT)。前一版修法是「一律不销毁、只隐藏」,
但它对所有平台生效:隐藏的原生窗口会一直占着 NSWindow / CAMetalLayer 直到进程退出,
而 Touch Bar 只存在于 x86_64 机型 —— ARM macOS、Windows、Linux 付了代价却没有任何收益。
- 新增 cargo feature `macos-touchbar-window-hide`(crates/core,main 转发),
整条链路只读一个常量
`HIDE_WINDOWS_ON_CLOSE = cfg!(all(target_os = "macos", feature = "macos-touchbar-window-hide"))`:
`hide_for_reuse` 直接拒绝隐藏、`install_popup_close_routes` 什么都不装、
`open_popup_window_inner` 不登记窗口、编辑器窗口的 `hide_for_reuse` 同样退回销毁。
用常量而不是散落的 `#[cfg]`,是因为这三处必须同时成立,任何一处单独打开都会静默错配
(隐藏了却不登记 = 业务 view 不卸载;登记了却不隐藏 = 条目永远探活失败)。
- 复用键从 `&'static str` 改成 `String`:同类弹窗会为不同目标各开一个(同时编辑两个连接、
同时连两台远程桌面),只按「弹窗种类」复用会把先开的窗口顶掉。
- 发布流水线只为 `x86_64-apple-darwin` 传该 feature(`extra_features`),
打包契约测试钉住三点:feature 只出现一次、只挂在 x86_64 判定下、两条编译命令都消费它。
- 契约测试 `the_hide_switch_gates_every_link_of_the_chain` 门控链上的每一环。
验证:`cargo check -p main`(feature 关)exit 0;`cargo check -p main --features
macos-touchbar-window-hide`(Intel Mac 交付形态)exit 0;`cargo test -p one-core --lib`
两种配置各 698 passed;`cargo test -p remote_file_editor --lib` 70 passed;
`node script/test-release-packaging.mjs` 29 passed。
2026-09-28 20:58:00 +08:00
胡飞
0b3a5c0a9b
build: HTML 预览的 embedded-webview 改为全平台默认关闭
...
- `main` 与 `crates/ai_chat_view` 的 `default` 都去掉 `embedded-webview`,
需要时显式 `--features embedded-webview` 打开
- 删除 `script/linux-release-features.sh`:Linux 不再需要专属 feature 列表,
三个平台发同一种形态,`release.yml` 的 Linux 分支回到默认构建
- 移除 `NAVOP_LINUX_EMBEDDED_WEBVIEW` 开关;Windows 32 位的显式列表
(默认集减 shell-plugins)同步去掉该 feature,避免 win32 成为唯一的另一种形态
- `ci.yml` 补编 opt-in 形态(`cargo check -p main --features embedded-webview`
与 `cargo check -p ai_chat_view --features embedded-webview --tests`):
默认形态由 `cargo test --all` 覆盖,两个半边都得有地方编到
- `test-release-packaging.mjs` 改为锁「没有平台构建打开该 feature」+ opt-in 形态有 CI 覆盖
- 注释同步:`main/Cargo.toml`(原因与降级行为)、`install-linux.sh`、
`package-linux-gpu-stack.py`、`AGENTS.md`
动机:WebKitGTK 4.1 无法随 Linux 发布包提供(带它的发行版都在 glibc 2.39+ 上、
`WebKitWebProcess` 按编译期路径解析),而只让 Linux 关掉会让三个平台各发一种形态,
需要多测一套、多维护一个平台专属列表。关闭后 HTML 预览弹窗降级为
「无法创建 HTML 预览 webview,已保留源码视图」,「用浏览器打开」「下载 HTML」不受影响。
验证:`cargo check -p main`(默认形态)、`cargo check -p main --features embedded-webview`、
`cargo check -p ai_chat_view --features embedded-webview --tests` 三者均通过;
`node --test script/test-release-packaging.mjs` 29/29;两个 workflow YAML 解析通过。
2026-09-22 17:24:06 +08:00
胡飞
ed6d1f3306
build(linux): Linux 发布包不再链接 WebKitGTK 4.1
...
- 新增 `script/linux-release-features.sh` 作为 Linux 发布 feature 集的唯一真源,
默认输出 `wasm-components,windows-native-rdp,shell-plugins`(默认集减去
`embedded-webview`)
- `release.yml` 的 Linux 构建改为 `--no-default-features --features "$(...)"`;
只有设了 `NAVOP_LINUX_EMBEDDED_WEBVIEW=1` 且 pkg-config 真能看到
`webkit2gtk-4.1` 时才把该 feature 加回来
- `ci.yml` 在 Linux 上补编一次发布形态:`cargo test --all` 走的是默认特性全开,
覆盖不到关掉 feature 的那半边代码
- `test-release-packaging.mjs` 锁住默认列表、opt-in 路径(stub pkg-config 的
成功/失败两种结果)以及两个工作流对脚本的接线
- `install-linux.sh`、`package-linux-gpu-stack.py` 的注释同步:WebKitGTK 只服务于
可选的 `embedded-webview`,不再是发布二进制的必然依赖
带 WebKitGTK 4.1 的发行版都构建在 glibc 2.39+ 上、与发布包的 2.28 基线不相配,
且 `WebKitWebProcess` 是按编译期路径解析的独立 ELF,所以这个库无法随依赖包提供。
UOS / 麒麟这类只带 4.0 的发行版此前会直接以
`error while loading shared libraries: libwebkit2gtk-4.1.so.0` 失败。
2026-09-22 16:41:12 +08:00
胡飞
e38a35df99
build(linux): 渲染依赖独立成包,便携版改为同一二进制加标记文件
...
- 新增 script/package-linux-gpu-stack{,.py,-build.sh,-docker.sh} 与
script/linux-gpu-stack-install.sh:在 Rocky 8 容器里收集 Mesa/EGL/LLVM
闭包,产出 navop-<version>-linux-<x64|arm64>-gpu-stack.tar.gz;安装脚本
按 ldconfig 只补宿主缺失的库,支持 --dry-run/--force/--uninstall,卸载
只回放自己安装过的文件
- 删除 Linux portable 变体:musl 启动器 script/linux-portable-launcher.c、
script/package-linux-portable.{py,sh},发布矩阵移除 linux-x64-portable
与 linux-arm64-portable
- Linux 应用包统一为默认 feature 的裸二进制,构建走
cargo zigbuild --target <triple>.2.28,不再拆 --no-default-features 分支
- 便携包回归:Package (Linux) 用同一个二进制平铺、加一个空的
navop.portable 标记打成 -portable.tar.gz,不新增矩阵项、不二次编译
- upload-r2.yml 的 targetFor() 识别 -portable./-gpu-stack. 后缀并映射到
对应 target,避免这两类资产被归入 universal
- 三语言 install-update.md 更新下载表,新增图形依赖包与便携版说明
- 重写 script/test-release-packaging.mjs:退役 portable 打包器用例,新增
依赖包三方契约、便携标记跨平台锁定、install.sh 增量卸载等断言
2026-09-22 13:45:05 +08:00
胡飞
1142f2f843
chore(deps): 依赖地址改为 gpui-kit 仓库
...
上游 longbridge/gpui-component 已改名为 longbridge/gpui-kit,fork 同步改名,
所有 gpui-* 依赖、patch、文档与同步脚本里的仓库地址一并更新。
同步脚本同时修掉了三处与旧仓库名绑定的匹配逻辑:守卫条件改为按 gpui-kit
仓库名匹配、Cargo.lock 校验的 rev 正则允许缺少 ".git"、依赖行允许只锁定
rev 而没有 version 字段。
2026-09-20 10:24:22 +08:00
胡飞
416b81400e
ci: 发布 PR 只跑发布元数据校验,跳过全平台测试
...
只改 CHANGELOG.md / main/Cargo.toml / Cargo.lock 的发布 PR 此前仍要跑
macOS / Linux / Windows 三平台全量测试(Windows job 约 25 分钟),而改动里
没有任何代码,这些测试没有意义。
- ci.yml 新增 classify job:PR 场景下用 script/release_pr.py 判定是否只包含
发布改动,是则跳过 test 与 windows-rdp-probe,改为就地校验发布元数据
- script/release_pr.py:分类规则为「改动文件全部属于发布白名单」且
「Cargo.toml / Cargo.lock 的差异只有 version = "..." 行」;命中后校验
main/Cargo.toml 与 Cargo.lock 中 main 包版本一致,且该版本的双语
changelog 条目完整(更新内容/修复与优化 + What's New/Fixes and
Improvements,含 CNB 镜像行);无法算出 diff 时回退到全量测试
- ci-gate 仍是唯一必需状态检查,分类或校验失败都会拦住合并
- .github/RELEASE.md 记录该快速通道
- windows_rdp_host 契约测试钉住 classify/needs/if 结构与脚本白名单
验证:
- python3 -m unittest discover -s script/tests -p "test_release_pr.py" — 16 passed
- cargo test -p windows_rdp_host --test contract — 40 passed
- ruby -ryaml 解析 ci.yml,核对 classify/test/windows-rdp-probe/ci-gate 的
needs 与 if 表达式
- rustfmt --edition 2024 --check crates/windows_rdp_host/tests/contract.rs
2026-09-19 12:55:35 +08:00
胡飞
1712e6ffe1
ci: sccache 安装失败时退化为无缓存构建,不再让 job 失败
...
mozilla-actions/sccache-action 从 GitHub Release CDN 下载 sccache 二进制失败
(CI run 35347089674 上连续两次 HTTP 504)会直接让整个 test job 变红,
Windows 测试根本没跑到,ci-gate 随之拦截合并。sccache 只是构建加速器,
不应成为测试或发布 job 的可用性依赖。
- Setup sccache 标记为 continue-on-error: true;该 action 的 post step 复用同一个
Pipelines.ActionStep,因此继续性错误同样覆盖 post step 失败
- Initialize sccache 在 steps.sccache.outcome != 'success' 时输出 warning,并向
GITHUB_ENV 写入空 RUSTC_WRAPPER,让后续 cargo 走无缓存构建;空值与 ci.yml
中 windows-rdp-probe job 的 RUSTC_WRAPPER: "" 是同一语义
- 同步 release.yml 的同一段路径,避免发布 job 被同类瞬时故障打断
- script/test-release-packaging.mjs 的共享缓存策略契约补上 best-effort 断言,
防止后续改动把这两个步骤改回硬依赖
验证:
- ruby -ryaml 解析两个 workflow:jobs 与步骤结构不变
- node --test script/test-release-packaging.mjs:26 passed / 0 failed
- cargo test -p windows_rdp_host --test contract:39 passed / 0 failed
- 变异验证:临时删掉 ci.yml 的 continue-on-error,共享缓存策略契约立即转红
- 本地 RUSTC_WRAPPER="" cargo check 正常,确认空值等价于不启用 wrapper
2026-09-19 00:10:02 +08:00
胡飞
224c8c62a6
test(release): 同步发布配置断言到 fat LTO / panic=abort / 180min
...
这三条断言在之前的发布 profile 改造后就没跟上,CI(ci.yml 会跑
`node --test script/test-release-packaging.mjs`)一直是红的:
- 超时:改为断言统一的 `timeout-minutes: 180`(原表达式已删除)
- Cargo.toml:`lto = "thin"`/`codegen-units = 16` → `lto = "fat"`/
`codegen-units = 1`,并补 `panic = "abort"`
- Windows 构建:feature 已收进 `$features` 数组,改断言
`"windows-native-rdp"`(原 `--features windows-native-rdp` 不再出现)
验证:node --test script/test-release-packaging.mjs 全绿。
2026-09-17 16:41:01 +08:00
胡飞
7815ce5a28
perf(deps): gpui-component 升级到 resvg 0.46,去掉重复的 usvg/tiny-skia
...
component fork 的 Windows 原生菜单固定 resvg 0.45.1,gpui-pre 用 0.46.0,
每个下游二进制都链两份 usvg/tiny-skia。fork 侧 6df86ef8 把 resvg 提到 0.46.0,
navop 侧 rev 跟到 6df86ef8(含 rquickjs)。
同时清掉脚本/文档里改名前的 gpui-ce 标识:
- sync-gpui-component.sh 的 -p 包名(原值实跑报 did not match any packages)
- update_gpui_component_revision.py 的包名映射
- gpui-component-external.md 的旧仓库地址与 rev
- gpui-shell-extension-design.md / home-theme-sidebar-refinement.md 的旧包名与 rev
验证:Cargo.lock 里 resvg/usvg/tiny-skia/tiny-skia-path 各只剩 1 个版本;
cargo check -p main 通过;resvg 调用序列探针对 0.46.0 编译通过。
未跑 Windows 构建:唯一调用点是 native_menu/windows.rs(Windows 专用)。
已知遗留:update_gpui_component_revision.py 仍不能跑(依赖行需含
gpui-component.git + version =,SHELL_VERSION_FILES 锚点已不存在)。
2026-09-17 15:51:15 +08:00
胡飞
b1f41f81c7
perf(release): 关掉展开/开 fat LTO,裁剪 wasmtime 未用 feature
...
macOS arm64 二进制 177,369,216B → 112,557,680B(-36.5%)。
1. [profile.release] lto=thin→fat、codegen-units=16→1、panic=abort。
__eh_frame 16.0→0.07MiB、__gcc_except_tab 8.6→0.02MiB、
__unwind_info 2.8→0.29MiB,展开元数据共省约 27MB。
代价:release 下 catch_unwind 不再拦 panic(terminal 录制 observer、
windows_rdp_host event 回调);dev/test profile 不变,测试照样跑。
2. extension-wasm 只用 async/cache/component-model/cranelift/gc/runtime/std/
threads,wasmtime 与 wasmtime-wasi 都关 default-features;
wat 解析器移入 dev-dependencies(只有 fixtures/*.wat 测试用)。
掉包:wiggle、wiggle-generate、wiggle-macro、witx、wast、leb128。
3. bundle-macos-dmg.sh:按 staging 目录实际大小显式 -size(首次 +512MB,
重试逐次放大),制完删 staging 拷贝。hdiutil 自动估算偏小时会报
"No space left on device",与磁盘剩余空间无关(已本地复现)。
2026-09-17 11:24:04 +08:00
胡飞
e65fa85272
fix(release): macOS DMG 前清理 target 并去掉写死的 200m 镜像大小
...
runner 磁盘被 release target 目录占满导致
hdiutil create: No space left on device;DMG 又写在 target/ 下。
打包 app 后清理 deps/build/incremental/.fingerprint 再制 DMG;
同时去掉 -size 200m(app 二进制已约 208MB,写死尺寸不够)。
2026-09-17 09:04:00 +08:00
胡飞
832c259471
fix(release): 版本 bump 走发布 PR,发布脚本只校验并打 tag
...
main 受保护(PR + CI gate 且 enforce_admins),release-tag.sh 原先
直接提交并推送版本变更会被 GH006 拒绝。改为:版本 bump 随 dev -> main
发布 PR 合入,脚本只校验版本/changelog 后创建并推送 tag。
- release-tag.sh: 去掉直推分支与自动 bump,新增 main 分支与版本一致性校验
- 删除 bump-version.sh(会基于未推送的提交直接打 tag,与保护规则冲突)
- RELEASE.md / CONTRIBUTING.md / CLAUDE.md 同步为 PR + tag 流程
2026-09-16 20:18:59 +08:00
user.email
d548f07ab1
build: shell-plugins 进 main 的 default,四个发布产物统一带 Shell 页
...
`shell-plugins` 此前只在自建构建里开,于是 `crates/universal-plugins/src/
shell_plugin_host/**` 在默认构建、CI(`cargo test --all`)与发版里整段不参与
编译——改共享契约漏改构造点会静默通过(2026-09-15 给 `BindingContext` 加
`connection` 字段时就是这样漏的)。改为:
- `main` 的 `default` 追加 `shell-plugins`,于是 macOS/Linux(走 default)与
Windows(`--features windows-native-rdp`,default 仍在)的发布产物都带上;
- portable Linux 用 `--no-default-features`,不继承 default,故显式写成
`--features wasm-components,shell-plugins`;
- `crates/universal-plugins` 的 `default` 保持为空:该开关只关掉当前被选中包
的 default,若给 crate 也设 default,`main --no-default-features` 会把 Shell
页漏回来,feature 门失效。
`script/test-release-packaging.mjs` 里两处按字面量断言 default 行与 portable
命令,一并同步;`AGENTS.md` 那条 lesson 的验证方式改为「默认态与关闭态都必须
各跑一次」——漏编永远发生在没编到的那一侧。
验证:`cargo check -p main --locked` EXIT=0;关闭态 `--no-default-features
--features wasm-components,embedded-webview,windows-native-rdp` EXIT=0;
`node --test script/test-release-packaging.mjs` 26/26。
portable 目标本机无 zig/cargo-zigbuild,未预验证(该 job 现在要编 quickjs 的
C 源码,`quickjs-jit/sys/build.rs` 经 `cc` crate)。
2026-09-15 15:21:48 +08:00
user.email
9ec827f263
docs(publish-gpui-pre-fork): use https fork-url in examples (repo is public)
2026-09-10 20:28:26 +08:00
user.email
6a1e7f6dd0
feat(remote_desktop_view): restore dynamic texture via a self-maintained gpui-pre fork
...
The dynamic-texture API only exists on our zed dynamic-texture branch, so
the published gpui-pre-* snapshot predates it. Instead of a path patch
that ties the build to one host, point the [patch.crates-io] block at a
git fork we own:
feigeCode/gpui-pre, tag fork-0.3.99
The fork is cut from zed@cae01216cb with gpui-component's
script/bump-gpui.ts pipeline. Re-publish with
script/publish-gpui-pre-fork.py and move the patch with
script/migrate-to-git-fork.py; both live in script/.
With the API available again, revert the eda5371ae RenderImage fallback
in remote_desktop_view:
- surface.rs uses DynamicTexture directly; the backing framebuffer
becomes the upload source for paint_dynamic_texture.
- render.rs paints dirty rects via update_dynamic_texture /
paint_dynamic_texture instead of a whole-frame paint_image, and
releases retired handles with drop_dynamic_texture.
- view.rs drops the placeholder handles; the contract tests assert the
dynamic-texture path.
cargo check --workspace --locked: clean. cargo test -p
remote_desktop_view --lib: 241 passed.
2026-09-10 20:17:50 +08:00