beta
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6f9af89583 |
release: OpenLess 2.0.0-Beta.4+build.20260930 (#1126)
Integrate ASR/Omni fixes, API Route, Android IME and confirmation-based vocabulary learning. Complete settings transitions and macOS single-item credential storage. Synchronize all application versions to 2.0.0-Beta.4+build.20260930. Integrated PRs: #1116, #1120, #1121, #1123, #1124, #1067 and #1128. Linux #1117 remains independent and is excluded from automatic release. Validated: full cross-platform CI, 18 UI motion cases, 112 frontend test files, 1245 Core tests (1 ignored) and 572 macOS native tests. Co-authored-by: HKLHaoBin <we3q@qq.com> |
||
|
|
913279c049 |
feat(linux-egui): native EGUI parity host (#1060)
* refactor(linux): move shared runtime assets out of Tauri
* feat(linux-egui): adopt redesigned native shell
* feat(linux-egui): add typed popup process bridge
* feat(linux-egui): add native desktop services
* feat(linux-egui): add verified AppImage replacement
* feat(linux-egui): render native popup processes
* feat(linux-egui): bind vocabulary corrections and styles
* feat(linux-egui): add real history actions
* feat(linux-egui): wire native popup supervisors
* feat(linux-egui): bind marketplace and device login
* fix(linux-egui): do not overreport update capability
* feat(linux-egui): honor minimized startup
* test(linux-egui): lock Tauri-free parity contracts
* fix(core): keep provider URL validation clippy-clean
* refactor(linux-egui): keep view data clippy-clean
* feat(settings): transact launch at login effects
* feat(linux-egui): complete verified AppImage updater
* feat(linux-egui): restore native desktop lifecycle
* feat(linux-egui): restore history audio workflows
* feat(linux-egui): complete local model controls
* feat(linux-egui): add native style pack editor
* feat(linux-egui): complete marketplace workflows
* feat(linux-egui): merge dirty settings on revision conflict
* feat(linux-egui): complete fcitx5 desktop hotkeys
* feat(linux-egui): harden and restore native popups
* feat(linux-egui): bind shared vocabulary presets
* feat(linux-egui): edit style hotkeys without blocking frames
* feat(linux-egui): persist and export diagnostic logs
* feat(linux-egui): edit all fcitx5 hotkeys
* fix(linux-egui): follow terminal capture cancellation
* docs(linux-egui): record parity and device limits
* docs(linux-egui): track latest 1019 head
* fix(linux-egui): reload fcitx5 after addon update
* feat(linux-egui): restore recording and appearance settings
* feat(linux-egui): restore Core-backed overview
* feat(linux-egui): restore native recording feedback
* ci(linux-egui): lock release artifacts and checksums
* feat(linux-egui): localize native interface
* fix(linux-egui): tolerate legacy fcitx addon methods
* [pi] Work in progress
* [pi] Work in progress
* [pi] All tasks are complete. Here's a summary of what w...
* fix(linux-egui): preserve original frontend content geometry
* fix(linux-egui): align beta contracts and slim release
* ci: make Linux PR and release gates reliable
* fix(linux): remove dead frontend artifacts and restore autostart
* fix(settings): handle launch-at-login in Tauri restore
* fix(ci): follow relocated Linux remote input assets
* fix(ci): normalize Windows contract paths
* refactor(linux): rely on packaged fcitx addon
* feat(linux): add audio backend fallback and fcitx clipboard
* ci(linux): use native deb and rpm packaging
* fix(linux): bound addon reload and use app version
* fix(linux-egui): restore input and CI build
* fix(linux-egui): align window input regions
* fix(linux-egui): restore frontend input routing
* wip(linux-egui): 2.0 parity checkpoint before parallel page rewrites
* chore: parity agent orchestration script
* [pi] 我负责的三个功能性 bug 全部修完,全量测试通过。报告如下。
* [pi] 全绿(24 个 test binary 全 ok;`coding_agent` 那条是已知时序 fl...
* [pi] 快捷键与选区做完了,全量验证通过。
* [pi] 三个浮窗按 Tauri 重做完了,旧的臆造实现已整段删除。
* [pi] 四项遗留全部做完,并回答了 Vulkan 的问题。
* [pi] Siri 光效 GPU 化(GLSL 移植)+ 圆角矩形扫光着色器,接胶囊与思考态;Vulkan 结论附证据
* [pi] 实现完成,验证全绿并打好包。
* [pi] Work in progress
* [pi] Work in progress
* [pi] **任务 1(胶囊 wlr-layer-shell 主路径)完成并出包。**
* [pi] 任务 2 完成。**开工时先核对了前任的产物,结果发现任务 1 其实从未真正生效**——两个阻塞性 ...
* [pi] 三个问题里属于我这个工单的(③ 吞键、④ 选区助手)已经修完并出包。**其中「吞键」我现在就把你机器...
* [pi] 任务2:静音失败归一化 + 胶囊终态自动收起
① 不说话报 InvalidArgument:
- 根因 Core `mark_dictation_failed` 把错误码名当 message(format!("{:?}", code)),
宿主原样送进胶囊/状态栏;空音频在 Core 里就是 InvalidArgument。
- 新增 linux-egui/src/dictation_feedback.rs(纯函数):capsule_outcome 分类相位+message,
内部错误码永不作为用户文案;normalize_stop_result 把 InvalidArgument/InvalidState/
Busy/Cancelled 这类预期内停止错误归一为「没有结果」,真失败仍上报。
- 三个停止/取消入口(胶囊 ✕/✓、Core 自动停止)改走 normalize_stop_result。
② 弹窗不自动收回:
- 按 Tauri coordinator::core_capsule_hide_delay 契约落地 capsule_hide_delay:
成功/失败 2s、取消立即、进行中不收;schedule_capsule_dismissal 延时后经
UiResult::CapsuleDismissDue 回主线程,capsule_hide_is_still_current 用会话+相位
双守卫,避免误关用户中途新开的录音。
- 收起=结束弹窗进程(layer surface 只能随进程销毁),并在进行中的相位按需
ensure_popup 重新拉起(phase_shows_capsule 排除终态,避免迟到事件把药丸喊回来)。
- 新增进程级测试:request_shutdown 必须真的让子进程退出且不算 crash。
验证:全量 cargo test 全绿(含 localization 契约)、i18n --check 干净(未新增 key)、
两个发布契约通过;包 2.0.0-Beta.1-28。
* [pi] 修复热键:修饰键触发不再吞键、Shift+符号键匹配(Ctrl+Shift+;),插件更新后自动重启 fcitx5
- 插件侧新增 hotkey_match.h 纯函数:字母大小写折叠 + US 布局 base/shifted 视为同一物理键
(实测 fcitx 的 Key::normalize 对符号键只改 states 不改 sym,所以注册 ';' 永远匹配不到到达的 ':')。
21 条断言的 hotkey_match_contract 覆盖 Ctrl+Shift+; / Ctrl+; 不误触发 / 字母 / 裸修饰键。
- 取消「裸修饰键一律拒绝注册」:改由插件保证不吞修饰键(shouldConsume),
按住修饰键当热键期间若按下其它键则判定组合键并放弃触发。
- 录制器支持录裸修饰键(跨帧看 Modifiers:按住→无其它键→松开),记为 LeftControl 等。
- 新增 /proc 时间戳比较:安装包替换 libopenless.so 后,若插件比运行中的 fcitx5 新,
启动时自动 fcitx5 -r,否则新匹配规则不会生效。
- 诊断日志:命中与「修饰位一致但键不同」的近似未命中(限速 1 次/秒)。
验证:cargo test 全绿;ctest 2/2(input_target_contract + hotkey_match_contract);
deb 内 libopenless.so 与新构建 sha256 一致(66d534f0…)。
* [pi] 任务2:报错路径胶囊收回(Core reset 时序 + 宿主兜底 + 宿主断开自退场)
* [pi] Work in progress
* [pi] 执行完了。取证结论很明确,而且**deb 打包没问题**。
* [pi] Work in progress
* [pi] 修复:启动不再被插件探测/重载阻塞(主窗口必现)+ /usr/local 手工残留不再冒充随包插件 + 指纹持久化(消除每次启动重载);热键注册摘要与取证日志
* fix(linux-egui): 胶囊去掉自造外圈扫光(Tauri 经典药丸只有 1px 中性描边)+ 风格包草稿行不再误改已有行 + 打包 postinst 改 D-Bus 重载
- 胶囊:录音/思考不再画红/黑外圈(用户报「语音输入弹窗有一个红边」),
改成 Tauri 的「1px 中性描边 + 随音量放大 1.8%」;中心声波/流体圆点保留。
两条新测试锁死:录音相位不得出现偏红涂色、GPU 回调数固定 1/1/0。
- 风格直达快捷键:草稿行选择器曾把 draft_pack 当行号下发 StyleHotkeyRepack,
会把已有行的包改掉,且 StyleHotkeyDraftPack 从未构造(编译告警)。
新增 style_pack_pick_action(row: Option<usize>, next) + 测试。
- popup_layer:去掉不必要的 unsafe 与未被使用的 FrameCallback 别名。
- 打包脚本:postinst/postrm 不再 `fcitx5 -r`(会替换 daemon 并前台常驻、装包卡 5s),
改用 D-Bus controller Restart(实测 0.002s 返回)。
* fix(linux-egui): Ctrl+Shift+; 热键被 CapsLock 状态位吃掉——插件按 fcitx 的 normalize 语义只比 ctrl/alt/shift/super;近失日志降到 Info 并加可开关逐键 trace;宿主逐项记录注册的 (sym,states)
* fix(linux-egui): fcitx5 缺席/插件过旧不再拖死后端——启动链对 fcitx5 失败改为警告降级(heatkey listener 变 Option)
* ci(release): 共用一条跨平台发布流水线(-tauri/-egui 按后缀分流)+ 末端聚合 zip
Linux egui 的发布此前是一条从没被调用、也从没跑成功过的独立工作流:
fork 里 dispatch 时它带了一个不以 v 开头的 release_tag,在第 8 步
(依赖安装 + fcitx5 插件构建 + 全量测试之后)报
"release_tag must name an existing v* release",白烧 4.5 分钟 runner。
- release-tauri.yml 变成唯一一条发版流水线,按 tag 后缀决定构建哪些平台:
v*-tauri → macOS/Windows 的 Tauri 包;v*-egui → Linux deb/rpm(egui 原生宿主,
通过 workflow_call 复用 release-linux-egui.yml);手动 dispatch → 两侧都构建。
- 新增 bundle-all-artifacts:把本次 run 的全部 artifact 收成一个
OpenLess-<tag>-all-artifacts.zip(zip 名自带 -tauri/-egui 后缀),
根目录附覆盖全部文件的 SHA256SUMS,tag 推送时挂到同一个 release。
与 build job 同一套渠道判定,beta release 不会被这个只挂 zip 的 job 升格。
- release-linux-egui.yml:tag 校验提到编译之前(几秒出结论);release target
从「release 必须先存在」改为「tag 必须存在」,让 -egui 这个新命名空间
首次推送时能自己建 release;版本推导先剥 -egui(-tauri 仅作历史兼容,
保持 release 契约测试要求的写法);首次为 -egui tag 建 release 时正确标 prerelease。
* fix(linux-egui): 关闭键改为退回托盘(不退出进程)+ 托盘唤起按 最小化恢复→显示→聚焦 回任务栏;QA 热键主键被输入法引擎占用时释放(pinyin QuickPhraseKey,带备份)
* ci(release): Linux egui 发布 job 换到 ubuntu-24.04(与 ci.yml 同因)
实测 run 35120587777:22.04 上冷缓存重编 libspa 0.10.1 直接编译失败
(spa_video_info_raw has no field named flags / missing spa_meta_first),
整条发布流程卡在第 8 步。ci.yml 的 linux-core-contract 早就因为同一个原因
换到 ubuntu-24.04 并留了注释,这条工作流从没跑过所以一直没跟上。
注意:deb/rpm 的 glibc 基线随之抬高到 24.04(glibc 2.39);若需要覆盖
22.04 用户,得在依赖层解决 libspa/PipeWire 版本,而不是换 runner。
* ci(release): 单一 -egui tag 触发同一 run 内四平台并发(macOS arm64/x64 + Windows + Linux egui)
按用户拍板的方案 B 收口 tag 约定:v<version>-egui(例 v2.0.0-Beta.1-egui)
一次推送 → 同一个 run 里四平台并发构建 → 所有产物挂同一个 Release
→ 末尾 bundle-all-artifacts 打成 OpenLess-<tag>-all-artifacts.zip。
- 后缀不再是平台分发开关:-egui tag 下 build(Tauri 三腿)与
release-linux-egui(workflow_call,tag 由编排层传入)都跑,zip job needs 全四者。
- build 里所有 release 相关步骤(签名可用性检查、release body prelude、
Create/update release)从「仅 -tauri」放宽到任何 v* tag,否则 -egui 发布时
dmg/msi/exe 只进 artifacts、不会挂到 Release。
- 历史 v*-tauri tag 兼容保留:仍只跑 Tauri 三平台(旧行为),文件头注释写清
两者关系与迁移注意(Cask URL/README/bump-version.sh/android-apk.yml)。
- Linux 产物命名统一带 egui 标识:deb/rpm 已是
OpenLess-Linux-egui-<ver>-<arch>(脚本原有),校验和文件改名 SHA256SUMS-egui,
避免与其它平台/聚合层那份混淆;版本串仍由传入 tag 推导(剥 -egui)。
- update-homebrew-cask 保持仅 -tauri:cask 下载 URL 写死 v#{version}-tauri,
迁移到 -egui 前不能动,已作为待办写进文件头注释。
* fix(linux-egui): 关闭键真正隐藏(Wayland 塌缩 1x1,不能 unmap/最小化)+ 事件泵用真线程保活 + Ctrl+Shift+; 匹配「Shift 折进符号」
三个实测问题(-40 真机反馈):
1. 点关闭键没反应:winit 在原生 Wayland 上 `set_visible` 是空实现
(winit-0.30 wayland/window/mod.rs「Not possible on Wayland」),
所以 -40 的 `Visible(false)` 等于什么都没做。最小化虽受支持,但最小化的
surface 拿不到 frame callback → eframe 的 update() 停摆 → 热键、托盘、
单实例唤起全部失效(实测心跳停、按热键无反应、重启也拉不回来)。
改为 Wayland 上隐藏 = 临时放开最小内尺寸 + 塌缩到 1x1 并保持映射:
窗口看不见了,事件循环照常跑(实测隐藏后 recording=true,热键仍生效;
再次启动能把窗口按原尺寸恢复)。X11 仍走真 unmap。
2. 事件泵保活必须用真线程:tokio 运行时是 current-thread,spawn 的任务只在
别处 block_on 时才推进,当后台泵用等于没保活。
3. Ctrl+Shift+; 到达插件时是 sym=0x3a(':') 且 states 里【没有 Shift 位】
(trace 实测;字母如 Ctrl+Shift+S 则是 sym=0x53 states=0x05)。注册值
0x3b/0x05 因此永远匹配不上。匹配规则改为:符号相同则严格比修饰位;符号
成 base/shifted 对时,只有注册值本身带 Shift 才接受、并忽略事件里的
Shift 位(Ctrl+; 与 Ctrl+Shift+; 双向仍可区分)。近失日志同步放宽到
「除 Shift 外的修饰位一致」,否则这类失败永远没有日志。
* ci(release): 四平台真正并发——egui job 去掉 needs: build,Linux 资产的 release 写入交给聚合 job
needs: build 会让 Linux 腿等 macOS/Windows 编完才启动,那不是四平台并发。
去掉它之后两个 job 可能同时建同一个 release,所以:
- release-linux-egui.yml 新增 attach_release 输入(默认 true 保持单独 dispatch 行为)
- 编排层传 attach_release: false,Linux 资产改由 bundle-all-artifacts(needs 四者、
最后一个 job)统一挂到 release,顺带和 zip 一起写,写入点唯一。
bundle job 里 Linux 资产那步用 hashFiles 守卫:egui 腿失败时不会让整步失败、
也不会把 zip 一起拖下水(zip 自己有 test -s 硬门禁)。
* ci(release): 聚合 zip 只装 Linux 平台产物(用户口径:all 指 Linux 各产物)
- bundle-all-artifacts 只下载 openless-linux-egui-x86_64 这一个 artifact,
zip 根部就是打包脚本的原始文件名(.deb/.rpm/SHA256SUMS),不再塞 dmg/msi/exe,
也不再另造一层汇总校验和——打包脚本自带的 SHA256SUMS 就是权威。
- Linux 校验和文件命名改回 SHA256SUMS(撤销上一轮的 SHA256SUMS-egui)。
- needs 仍为 [build, release-linux-egui]:它仍是 Linux 资产的唯一 release 写入点
(egui job 保持 attach_release: false)。
- ubuntu-24.04 的分发基线写进注释:glibc ≥ 2.39,支持范围 Ubuntu 24.04+,
22.04 / Debian 12 不在支持范围内(用户已确认,防止被改回去)。
- Homebrew、Cask、README、bump-version.sh、android-apk.yml、macOS/Windows 构建步骤均未动。
* fix(linux-egui): 最小化键改为退回托盘(Wayland 无法取消最小化,最小化的窗口会永久停掉 egui 重绘循环 → 热键消费与弹窗拉起一起失效)
- 有托盘:最小化 → 与关闭键同一条 hide 路径(Wayland 塌缩隐藏,事件循环继续跑)
- 无托盘:保持真正的窗口管理器最小化,否则窗口找不回来
- 新增 minimize_action() 决策函数 + 测试锁死该语义
* fix(linux-egui): 发布门禁单测不再依赖宿主是否装了 deb
a_per_user_copy_only_shadows_when_the_package_has_the_addon 之前直接断言
/usr/lib/x86_64-linux-gnu/fcitx5/libopenless.so 存在:装了 deb 的开发机绿,
干净的 CI runner 上 is_file()=false → 该断言必挂,于是 Linux 发布腿在第 8 步
「Verify framework-independent Linux contract」失败,后续 upload/checksums
与聚合 zip job 级联失败。
改为 tempfile 自建 per-user 文件,覆盖「无文件」与「有文件 + 包内有 addon」两个
分支,并保留「无包内 addon 时不 shadow」的反例断言。用 unshare + tmpfs 隐藏系统
插件目录后该测试仍通过,证明不再依赖宿主文件系统。
* fix(linux-egui): 删除自动改写输入法配置的逻辑,并用契约测试锁死
已实测证伪「拼音的 QuickPhraseKey=semicolon 会吃掉 Ctrl+Shift+;」:
fcitx 的 Key::check 要求修饰位精确相等(semicolon 是 states=0,而
Ctrl+Shift+; 到达时是 states=Ctrl),两者本就能共存。用户真机验证:
保持 QuickPhraseKey=semicolon 时按 Ctrl+Shift+; 面板照常弹出。
- settings.rs: 去掉 apply_hotkeys 里对 release_engine_key_conflict 的调用
- fcitx5.rs: 删除 release_engine_key_conflict{,_in}、release_engine_reserved_key、
engine_reserved_keys、fcitx_key_aliases、fcitx_punctuation_name、
reload_addon_config 及 4 个相关单元测试(-259 行)
- tests/fcitx5_config_contract.rs: 新增不变量契约,断言宿主源码里不出现
输入法配置目录与引擎保留键;已用注入违规验证它非空转
- 真机 A/B:旧二进制会把临时 HOME 的 QuickPhraseKey 清空并留日志,
新二进制字节不变、0 条日志,且启动路径确实跑过(有热键注册日志)
* [pi] Work in progress
* fix(linux-egui): 恢复被清理删掉的 provider/凭据编辑能力,发布门禁不再被拦
`1157fe51` 的界面重写把旧 monolith 的 provider 编辑器一起删了,新设置页只留住
「渠道列表 + 新建 + 启停 + 校验 + 删除」,于是 Linux 版既不能录入/修改/删除云服务
凭据,也不能改名、换 provider 类型、重排渠道、选当前渠道 —— 发布流水线第 8 步的
`check-linux-public-surface.ps1` 正是在拦这个(它的注释写明:只有状态、或只有本地
模型捷径的界面不算可用安装路径)。
- 宿主重新接上 Core 的 provider/凭据 API:rename_channel、set_channel_provider_type、
reorder_channels、set_active_provider、set_credential、remove_credential,以及
provider.validate / provider.list_models(模型目录属于 Core)。
- 编辑器草稿放在宿主侧(ProviderEditorForm)并每帧镜像给 view model:重新拉取渠道
列表不会冲掉正在输入的内容;密钥字段只写,打开编辑器不会把已存密钥读进 egui。
- 界面补回:渠道行的编辑/上移下移/换类型/设为当前,以及按 AuthRequirement 渲染的
编辑器(Endpoint/Model/Resource ID/密钥/鉴权模式 + 读取模型 + 保存 + 清空密钥)。
- 顺手修掉「新建渠道」名称框推的是编辑前的值:宿主会把旧值写回,每敲一个字被回灌。
- 新增本地契约测试 provider_surface_contract.rs,把这组 Core 调用钉住,避免下次合并
或清理再把实现删掉(历史上正是这样丢的)。
* ci(release): 聚合 zip 改成「手动安装」散装包(不再装 deb/rpm)+ 附 install.sh
用户澄清了这个 zip 的用途:不是把安装包再打一层,而是给装不了 deb/rpm 的发行版
手动安装用。所以:
- payload 用 `dpkg-deb -x` 解开 deb 得到精确安装树(usr/bin/openless、
usr/lib/*/fcitx5/libopenless.so、addon conf、desktop、metainfo、icon),
并新增硬门禁:散装清单必须与 `dpkg-deb -c` 的文件清单逐项一致(漏文件最难发现)。
- 新增 `openless-all/app/scripts/linux-egui-manual-install.sh`,随包发布为
install.sh:检查 root、先验 SHA256SUMS、按用途给权限铺到 /、用 D-Bus controller
Restart 重载 fcitx5(与 deb postinst 一致,不用会卡住的 `fcitx5 -r`),最后打印
重启与卸载提示。
- SHA256SUMS 改为对散装文件 + install.sh 重算;zip 内硬门禁断言不得出现 deb/rpm。
- zip 名自解释:OpenLess-Linux-egui-<tag 或 run>-x86_64-manual.zip;job 名/artifact
名与注释一并从 all-artifacts 改为 manual install,避免语义继续误导。
- deb/rpm 与它们自己的 SHA256SUMS 仍作为独立 release 资产保留(那条 attach 步骤没动)。
- bundle job 补 checkout(脚本随仓库版本走,不内联进 workflow)。
* ci(release): Linux 腿内联成平级 job(不再折叠)+ deb/rpm 与 manual zip 合并到一个 job + 升 action 到 node24 版本消掉 node 警告
- 去掉 workflow_call 调用:release-linux-egui.yml 的步骤内联为 release-tauri.yml 里与
build 平级的 linux-egui job,Actions 页面上不再被渲染成嵌套折叠条目
- 同一个 job 内完成 deb/rpm 打包与手动安装 zip 组装(删掉单独的 bundle job)
- release 竞态:内联后与 Tauri 三层并发写同一 release,先用 gh release view||create
幂等地建出来(吞掉并发 422),softprops 只做 attach/update
- manual zip 语义:散装 usr/ 树 + install.sh + 重算 SHA256SUMS,不含 deb/rpm;
deb/rpm 仍作为独立 release 资产
- action 升版(仅这两个发布工作流):checkout v4→v6、setup-node v4→v7、
upload-artifact v4→v7,消除 GitHub action 自身 node20 运行时带来的
DEP0040 punycode / DEP0169 url.parse 弃用警告(用户点名的 node 警告)
- 契约测试加锁:断言 Linux 腿必须是内联普通 job、manual zip 必须在同一 job 产出、
两份副本的关键门禁不能漂移
* ci(release): 统一发布语义——任何 v* 标签都出全平台(Tauri 三平台 + Linux egui + 安卓)
- release-tauri.yml: push.tags 收敛为 'v*';build 与 linux-egui 的 if 不再按 -tauri/-egui
后缀分流(后缀降级为命名约定);Homebrew cask 仍只跟稳定的 -tauri 正式版
(注释写明该边界与影响,避免被顺手放开)
- android-apk.yml: 触发从 'v*-tauri' 扩展为 'v*';is_tag_release 判定不再要求 -tauri;
OPENLESS_RELEASE_CHANNEL 判定与桌面统一(含 -beta-egui);未签名 debug fallback 语义不变;
注释说明为何 Android 是独立 workflow(另一套 JDK/SDK/Gradle 工具链,塞进 matrix 会让
每步都长满 if)以及它的资产如何落到同一个 release
- 顺带把这几个 action 升到 node24 版本(checkout v6 / setup-node v7 / upload-artifact v7),
与发布工作流一致,消掉 action 自身运行时带出的弃用警告
- 契约测试新增:两份工作流都必须对 'v*' 触发、安卓必须挂到同一个 release、
build/linux 两个 job 必须无条件运行、Homebrew 仍锁 -tauri
* feat(linux-egui): 关窗后切到无窗口宿主(窗口进程真退出)+ 事件泵不再依赖窗口绘制
- 抽出与渲染无关的 tick():原生事件、托盘命令、自动更新检查、心跳日志;
eframe 的 update() 与无窗口宿主的 run_headless() 循环共用它,热键消费与
弹窗拉起不再依赖「窗口是否在绘制」(最小化/隐藏/无窗口都照常工作)。
- 关闭键/最小化键:有托盘且没有进行中的会话时切到无窗口宿主。Wayland 下
winit 无法隐藏窗口(set_visible 是空实现),只有窗口进程真的退出,任务栏
与窗口列表里的条目才会消失;会话进行中退回托盘,避免新进程重开后端丢会话。
- 进程形态切换用同可执行文件的新进程 + --takeover:新进程等旧进程释放单实例
锁后接管(不在交接期被转发后直接退出),托盘在接管后重试一次;接管超时降级
为无锁启动,保证托盘/窗口仍可用。
- Core 的 ShowMain/FocusMain 提示在无窗口宿主里不拉起窗口进程(那只是「把已有
窗口推前面」),只有托盘「显示主窗口」才是明确的用户动作。
* feat(linux-egui): 常驻无窗口宿主 + 独立 UI 窗口进程(真实时 IPC,关窗不再重开后端)
形态:宿主进程(无窗口,长期存活)持有 Core 后端 / 数据目录 / 单实例锁 /
热键监听 / 托盘 / 弹窗监督器;UI 窗口进程(--ui-client)只渲染。
- 新增 `src/ui/bridge.rs`:unix socket + JSONL 双向协议。快照带单调递增序号,
UI 只采纳更大序号(重放/乱序/回退一律丢弃);快照可合并(UI 慢时跳帧,
绝不画过期状态),控制帧永远先于待发快照落盘。
- 关窗 = **只有窗口进程退出**(Wayland 下任务栏条目真的消失);托盘「显示主窗口」
或用户再次启动 = 宿主拉起新窗口进程,宿主 PID 与后端状态原地不动
(实测:宿主 PID 不变、开窗约 50ms,不再重开后端)。
- 时序:先 bind socket 再拉 UI(UI 连不上就直接退出);UI 断连立即作废窗口句柄,
会话/录音/弹窗不受影响;宿主退出先发 Shutdown 再释放单实例锁。
- 删除重启式交接:--headless / --takeover / HandoffMode / spawn_handoff_process /
塌缩 1x1 隐藏 / 最小化改道 / 事件泵后台唤醒线程全部移除。
- `FrontendViewModel` + `FrontendAction` 加最小 serde 集:Lang 按 tag 序列化
(i18n.rs 不允许手改),theme_mode 随快照下发,UI 进程据此配色。
- 事件消费与弹窗拉起改由宿主循环驱动(无窗口 ⇒ 必须线程化),另加
OPENLESS_UI_DEBUG=1 诊断开关记录 UI 侧指针点击与动作。
* feat(linux-egui): 设置页排版对齐 Tauri(行=200px 标签列+1fr 控件列/左对齐、卡片与分区标题 14px)+ 浮窗尺寸语义对齐(qa 420×540、preview 640×440 可缩放且抢焦点)
* feat(linux-egui): 补齐设置页剩余排版对齐(行标签 500 字重 / 输入框 32·13.5 / 分段选中片阴影 / 无热键后端隐藏快捷键分区)
* feat(linux-egui): 在 Linux 实现 Less Computer 浮窗(420×540 / 常驻宿主唯一所有者 / 事件序列经 IPC 呈现 + 审批与取消链路)
- 新增 PopupKind::LessComputer(--less-computer)与协议版本 4:HostToPopup::LessComputer
携带宿主事件序列(user/tool/compaction/assistant/error)、working、审批与错误;
PopupToHost 增加 Submit/Approve/Cancel/Dismiss 四条动作。
- 宿主是事件序列唯一所有者:Core 的 LessComputerEventKind 逐条转成面板条目,
流式增量合并为一条助手条目,终局正文替换它;窗口进程重连即收到完整序列。
- HostAction::ShowLessComputer 不再是空实现;面板尺寸取自共享常量(420×540),
位置与 qa 一致(居中卡片);设置里的开关此前只翻转 coding_agent_enabled,现在
有真实 UI 可看。
- 顺手修正 qa 弹窗视口尺寸的硬编码 520×520(应为共享常量 QA_WINDOW_SIZE=420×540)。
* [pi] # 选区读取为什么是「缓冲区里最后一次」——调查结论
* feat(linux-egui): 设置页顶栏/录音行/胶囊设置与试听按 Tauri 补齐 + 模态居中修正
用户实测反馈的七条:
1. 去掉设置页顶部那一层「设置」标题栏(桌面端左右两栏各自通到顶:左栏顶端是
搜索框,右栏顶端才是「分类标题 + 修改后自动保存 + 关闭」,对齐 SettingsModal
的桌面分支)。
2. 通用页第一行「录音快捷键」此前是只读文本,改成真正的 ShortcutRecorder
(行内展开 → 录制 / 停用),并补上行下方的录音方式提示语。
3. 补上「录音提示音」的试听按钮,走与录音开始同一段合成提示音。
4. 补上「录音胶囊」开关与「胶囊样式」选择(siri / classic / typeless),并让样式
真正改变胶囊渲染;关掉开关时胶囊立刻收起。协议 v4 → v5 增加 style 字段。
5. 默认录音方式一行补上可见提示语(Tauri modeDesc)。
6. 麦克风枚举过滤 cpal/PipeWire 混进来的输出监听与重复节点,并在枚举失败/无设备
时显式提示;每次打开设置重新枚举。
7. 设置模态此前稳定偏下 19.5px、卡片被内容撑宽 22px(横向偏 11px),且在窗口缩放
后要等下一帧才居中:改为由 body.center() 显式算位置并修正两栏宽度预算。
* fix(linux-egui): 补齐 P0 —— 窗口边缘拉伸光标 + 锁定胶囊按钮可点
P0(本轮核对结果):
* 胶囊退出/结束转录按钮可点:行为本来就是对的(layer-shell 只给指针输入,两个圆钮
靠 `ui.interact` 命中),但没有任何测试锁住它。新增 `the_capsule_buttons_report_
cancel_and_confirm`:把真实指针事件按到 ✕ / ✓ 的圆心,必须分别返回 Cancel/Confirm。
* resize 光标:**确实缺失** —— 八个拖拽区只发 BeginResize、不设光标,无边框窗口
完全没有「这里能拉伸」的提示。按方向设置 ResizeHorizontal/Vertical/NwSe/NeSw,
并新增 `the_window_edges_show_a_resize_cursor_on_hover` 四角四边逐一断言。
第 8 条(qa / Less Computer 避开任务栏)按决定**不改代码**:Wayland 没有
skip-taskbar 协议,winit 的该开关只在 Windows 生效;换成自研 layer-shell 就必须
自己补 xkb 键码 + zwp_text_input_v3 + wl_data_device,而 winit 在 Wayland 上已经
提供输入法支持(fcitx5 中文输入依赖它)—— 为一个任务栏条目牺牲追问面板的中文输入
不划算。
* test(linux-egui): 锁住两个被报为回归的交互(拖标题栏移窗口 / 分组开合)
用户报「拖标题栏移不动窗口」「设置里可展开分组点不开」。排查结果:**两者都不复现**。
- 无头:`the_titlebar_press_starts_a_window_drag`(标题栏按下必须发出
ViewportCommand::StartDrag)、`the_collapsible_group_header_toggles_on_click`
(点分组标题行后内容消失 = 真的点开了)—— 都通过。
- 真机(uinput 绝对坐标注入,源码版单实例):
* 点侧栏「历史」→ 截图对比 AE=21984,页面确实切到历史记录(点击被送达)
* 拖标题栏(+150,+100)→ KWin 读到的窗口几何 340,118 → **490,218**(窗口真被拖走)
- 结论:两条都不是代码回归。真机不clean 状态(陈旧单实例套接字 / 两个宿主并存)
会让人以为交互失灵 —— 本轮排查时我确实复现过这种假象。
把这两条固化成回归测试,以后真回归会被抓住。
* test(linux-egui): 补上分组开合测试用的 test-only 入口
上一个提交 d69642ac 里的 `the_collapsible_group_header_toggles_on_click` 依赖
settings.rs 里的 `test_group_toggle`(cfg(test) 包装 card_group),漏提交会让
干净检出编译不过。这里补上。
* [pi] Work in progress
* feat(linux-egui): 润色结果并入「选区助手」面板(收尾 9a109965 的半成品)
9a109965 把独立 preview 窗口拆掉了但停在中间态:面板里还留着「可编辑」的
TextEdit、`preview_focus_requested` 只为它服务的字段、以及指向已删窗口的注释与
常量。这次把它做完并定型:
* 润色结果模式 = 选区助手面板里的第二套 UI(`polish_result_mode`),签名收成
`&QaPolishState`:**只读展示**(原先的可编辑 TextEdit 换成可选可滚动的只读
文本区,用户要求不做就地编辑),底部按钮集与 Tauri `SelectionPolishPreview.tsx`
一致:✕ / 取消(`selectionPolishPreview.cancel`,line 72/125)+ ✓ 确认并替换
(`selectionPolishPreview.confirmReplace`,line 139)—— 后者就是把结果写回
原选区的「插入」(→ `PopupToHost::ConfirmPolish` → Core `selection.confirm`)。
* 副标题跟着语义改:Tauri 的 `selectionPolishPreview.subtitle` 原写「可直接编辑」,
与只读冲突,5 个语言一起改成「只读结果;点「确认并替换」才会写回原选区。」
(egui 目录由 `scripts/sync-egui-i18n.mjs` 写入)。
* `HostAction::HideSelectionPreview` 只撤掉「润色结果」模式:面板若正在提问对话
中,不能被润色流程的收尾动作关掉。
* 清掉半成品留下的尾巴:`preview_focus_requested` 字段、`PreviewAction` →
`PolishResultAction`、`selection_preview_visible` → `polish_result_visible`、
已删窗口的常量注释(`PREVIEW_WINDOW_SIZE`/`PREVIEW_MIN_SIZE`/`PREVIEW_PADDING`)。
验证:`cargo test --locked` 1257 passed / 0 failed;`sync-egui-i18n.mjs --check` 干净。
* [pi] Work in progress
* fix(linux-egui): 标题栏拖动补两点 —— 未聚焦时先要回焦点、最大化时先还原再拖
egui-winit 对 ViewportCommand::StartDrag 带 window.has_focus() 前置条件
(egui-winit-0.33.3/src/lib.rs:1403-1409),未聚焦那一帧会被整帧丢掉,而 Wayland
的 xdg_toplevel.move 只认按下那一帧的 serial;另外最大化窗口的 move 请求会被
合成器忽略。现在:未聚焦时同帧补发 Focus,最大化时先 Maximized(false) 再拖。
* fix(linux-egui): 设置弹窗降到 Order::Foreground,修「点开的控件被弹窗盖住」
egui 的 ComboBox 下拉/弹出菜单是 Order::Foreground(popup.rs:150),而同层后创建者
在上、跨 Order 是 Tooltip > Foreground。设置弹窗占着 Tooltip,于是里面所有
ComboBox(渠道 provider / 认证方式 / 新增渠道 / 通用下拉等)展开后都被弹窗挡住,
表现为需要点开的控件都打不开。降到 Foreground:先创建的遮罩输入层仍在其下,
弹窗内再打开的下拉因创建更晚而在其上。
* fix(linux-egui): 设置遮罩改用 Area 排序(不再盖在弹窗上)+ 去掉「模拟粘贴快捷键」选项
- 遮罩原先用 ctx.layer_painter() 建原生图层,同 Order 内相对位置不受控;弹窗从
Tooltip 降到 Foreground 后遮罩反而盖到弹窗上面(用户报「设置整体被阴影层压住」)。
合并进原本就存在的 backdrop-input Area 并在弹窗之前创建,遮罩稳定在下面。
- 按用户要求移除「模拟粘贴快捷键」设置行(Ctrl+V / Ctrl+Shift+V)。
* feat(linux-egui): 接入上游 #1082 的「稳定模式(先录音后识别)」
合并 origin/beta 后唯一的冲突是 linux-egui/src/main.rs:上游往旧的 settings_ui 里加了
一个复选框,而那一整块我们早已重写成前端模块架构 —— 逐块保留 HEAD 侧,并把该设置
按新架构接上(否则上游这个新开关在 Linux 侧等于不存在):
view model 增 stable_transcription、SettingsField::StableTranscription、
SettingsDirty 白名单与 merge、宿主回灌与写回偏好、录音卡片里的开关行;
i18n 两个 key 由 sync 脚本从 Tauri 目录写入(904 keys,--check 干净)。
* fix(linux-egui): 遮罩与卡片合并成同一个 Area(点遮罩不再盖住卡片)+ 条带提到 Tooltip
- 阴影上移的真因:egui 在 Area 被按下时把它抬到同层最上面
(egui-0.33.3/src/containers/area.rs:549 move_to_top)。遮罩原本是独立 Area,
于是点一下遮罩就盖住卡片、设置没法用。现在遮罩、点击拦截与卡片同属一个 Area
且遮罩先画 —— 结构上不可能压住卡片。
- 设置打开时改不了窗口大小:遮罩覆盖 body,而左/右/下三侧拉伸条带落在 body 范围内,
被遮罩吃掉。条带提到 Order::Tooltip(层级高一层,仍只占外圈 6px/18px)。
- 卡片矩形改写进 egui memory,供居中/宽度测试查询(Area 面积已不等于卡片)。
* refactor(tauri): 删除 Tauri Linux 专有文件(linux_fcitx.rs / tauri.linux.conf.json)
Linux 桌面不再由 Tauri 提供(已改用 egui 前端 openless-all/app/linux-egui),先删掉
两个只服务 Linux 的文件:
- src-tauri/src/linux_fcitx.rs(976 行):fcitx5 插件桥 —— 热键同步、CommitText 插入、
PRIMARY 选区读取、aux-down 跟随、AppImage 期插件自动安装。egui 侧有自己的
linux-egui/src/fcitx5.rs,不依赖它。
- src-tauri/tauri.linux.conf.json:Linux 平台覆盖配置。
调用点与编译期门禁在后续提交里一并清理(见 7b778d9b、68fe1442)。
* refactor(tauri): 删除 Tauri 的 Linux 桌面实现(Linux 已改用 egui 前端)
Linux 桌面不再由 Tauri 提供,相关实现整体删除:
- src-tauri/tauri.linux.conf.json(Linux 平台覆盖配置)
- src-tauri/src/linux_fcitx.rs(976 行的 fcitx5 插件桥:热键同步、CommitText 插入、
PRIMARY 选区读取、aux-down 跟随、插件自动安装)
- hotkey.rs 的 Linux 平台 adapter 模块(fcitx5 作为热键源)
- unicode_keystroke.rs 的 Linux impl / enigo 错误变体 / Linux 再导出 / 相关测试助手
- insertion.rs 的 Linux insert(fcitx CommitText + 剪贴板回退)与 3 条 Linux 测试
- selection.rs 的 Linux PRIMARY 选区读取模块与两处 Linux 分支
- lib.rs 的 mod linux_fcitx 与 AppImage 期插件自动安装调用
为免误在 Linux 上构建 Tauri 版时面对一堆“找不到模块”的零散错误,lib.rs 增加一条
显式的 compile_error!(仅 target_os = "linux"),指向 egui 前端与出包脚本。
macOS / Windows / Android(target_os = "android")不受影响。
egui 侧未受影响:cargo check --workspace 通过、openless-linux-egui 测试全绿(181+87…)、
package-linux-egui.sh 出包成功。
* feat(linux-egui): 图标装多档 hicolor 尺寸 + 提示音与 Tauri 逐参数对齐
图标(用户要求:复用 Tauri 的图标集):
- egui 侧不能引用 Tauri 源码树(两条契约都禁止脚本出现该路径),所以把
src-tauri/icons 的 5 档 PNG 以**字节相同的副本**放进 linux-egui/packaging/icons/,
由 release 契约做逐字节一致性断言 —— 素材仍是一份,且不会静默漂移。
- package-linux-egui.sh 由「只装一档、且把 512×512 的图塞进 256x256 目录」改为装
32/64/128/256/512 五档 hicolor(256 取自 128x128@2x.png,512 取自 icon.png)。
- 已核对 public/AppIcon.png 与 src-tauri/icons/icon.png md5 相同(7017a34f…,均 512×512)。
提示音(用户要求:与 Tauri 完全一致):
- 参数本就与 audioCue.ts 逐字段一致;本轮补上两处**语义**差异:
* 包络改为 Web Audio 的两段指数斜坡语义
(0.0001 → 5ms 指数 attack 到 peakGain → 指数 decay 回 0.0001),
原来是线性 4ms attack + 任意 tau 的 exp(-5t) 衰减;
* 去掉 egui 自加的「结束提示音」(下行小三度)—— Tauri 的 stopAudioCue() 只停不发声;
现在 play_cue_stop() 等价于作废当前代次(正在播的提示音静音),与 Tauri 同语义;
* 新增代次机制:新提示音开始时在播的旧音立刻静音,对应 Tauri 排期前的 stopVoices()。
- 设置页「试听」继续复用同一实现(main.rs 的 PreviewAudioCue → play_cue_start())。
* refactor(tauri): 清掉已删除的 linux_fcitx 模块的残留调用点
linux_fcitx.rs 与 Linux 平台实现删除后,仍有多处 Linux 专有路径引用已删的模块/函数。
这些调用只出现在 cfg(target_os = "linux") 分支里,但等于给 Linux 构建留一堆悬空符号,
所以按「Linux 桌面不再支持」一并清理:
- coordinator/capsule_focus.rs:整段 fcitx5「候选词下方显示听写状态」的 aux-down 逻辑
(LAST_AUX / RETRY_GEN / set_aux_down / clear_aux_down)。
- coordinator/hotkey_loops.rs:启动前的插件可用性检查、fcitx 通道准备、
start_dictation_signal_listener 启动块,以及 custom_dictation_key_string /
sync_custom_dictation_to_plugin 两个 Linux 辅助函数。
- coordinator.rs:ensure_modifier_hotkey_monitor 里的插件绑定同步、同样的 fcitx 监听
启动块,以及 combo 热键更新路径里的插件同步调用。
- selection.rs:两处「其它平台」兜底不再排除 Linux(非 Linux 代码不动)。
- core_adapters.rs / unicode_keystroke.rs:流式插入的 Linux 分支不再调用已删的
type_unicode_chunk,改返回显式的 TypeError::UnsupportedPlatform。
macOS / Windows / Android 不受影响(这些分支本就不为它们编译;mac 有自己的
switch_to_ascii / restore_input_source 实现)。egui 侧未受影响:workspace check 通过、
openless-linux-egui 测试全绿。
* refactor: 把 Remote Input TLS 身份实现移到平台中立目录 shared/remote_tls
原先 linux-egui 通过 #[path] 直接引 src-tauri 的源码,egui 无法在 Tauri 之外独立构建。
现在两侧(src-tauri/remote_server、linux-egui/remote_input、backend-tests)都引
shared/remote_tls/tls_identity.rs,egui 对 src-tauri 的引用清零,并收紧
linux-egui-tauri-free-contract(去掉 remote_input.rs 的豁免)。
* fix(linux-egui): 修风格市场对官网 API 的请求放大
对齐 Tauri 市场页(src/pages/Marketplace.tsx):
- 搜索加 300ms 防抖(原先每敲一个字符、以及每次输入法预编辑变化都发请求)
- my_likes 从 load_marketplace 拆出,改为每次会话只取一次(原先每次搜索都带一个认证请求)
- 列表请求加单调序号,丢弃过期响应(对齐 Tauri 的 reqSeqRef,latest-wins)
- limit 100 -> 50
原先一次 4~6 字符的搜索会打出 8~12 个请求(半数为认证请求),输入法预编辑下更多。
* [pi] 盘点完成(只读,未改任何仓库文件)。报告已落盘 `/tmp/ime-inventory-report...
* refactor(fcitx5): 删除插件里已无人调用的候选词下方状态弹窗
SetAuxDown/ClearAuxDown 的实现、焦点切换补写、PostInputMethod 恢复、
lastAuxText_ 缓存全部删除:Tauri 的 Linux 调用点已随 linux_fcitx.rs 一起删除,
egui 从未调用过,全仓库已无调用者(纯死代码)。
* mod(selection): 划词润色预览独立弹窗下线,改为复用选区助手(qa)面板
- 删除独立窗口 selection-polish-preview:窗口创建、能力表条目、前端路由
(main.tsx / App.tsx)与页面 src/pages/SelectionPolishPreview.tsx 一并移除
- 后端 show_selection_polish_preview 改为 show_qa_window(app, "polish-preview")
并向 qa 发 selection-polish-preview:shown;hide 只发 hide 事件,是否收起面板
交给前端判断(面板可能正在提问对话中,不能被润色流程收尾一起关掉;与 egui
侧 HostAction::HideSelectionPreview 的语义一致)
- 新增 SELECTION_POLISH_PREVIEW_PENDING 标志:get_selection_polish_preview 只在
真正处于润色模式时返回负载 —— 面板懒创建后挂载补拉不会误入润色模式
- QaPanel 增加「润色结果」模式:标题/副标题、只读结果框、原文摘要、
取消 / 「确认并替换」(= 写回原选区的插入,走 Core selection.confirm);
Esc 与 ✕ 都是取消并关闭。沿用 selectionPolishPreview.* 文案(5 语言已具备)
- 新增 scripts/selection-polish-panel-contract.test.mjs 锁住合并(npm test 自动发现)
- docs/architecture.md 的窗口清单同步
* docs(linux-egui): 注释改为指向 Tauri 侧合并后的选区助手面板
Tauri 的独立润色预览窗口(SelectionPolishPreview.tsx)已下线,注释不再指向已删文件。
* refactor(tauri): 删除 Tauri 侧剩余的 Linux 专用实现
Linux 桌面已由 egui 前端承担,Tauri 不再构建 Linux。本次清掉残留:
- 纯 Linux cfg 块:audio_mute 的 wpctl/pactl 后端、tauri_coordinator_host 的
gtk 版胶囊窗口与 Linux 早退分支、permissions 的 Linux 麦克风探测、lib.rs 的
gtk 光标穿透与 WebKitGTK nudge、main.rs 的 WebKit 环境变量、core_adapters 的
Linux ASR/插入分支、asr/local 的 Linux 后端与空实现、side_aware_combo 的
「Linux 暂不支持」分支
- 12+ 个文件上的 cfg_attr(target_os = "linux", allow(dead_code, ...)) 抑制
- 保留 lib.rs 的 compile_error! 防线(有意拦住误在 Linux 上构建 Tauri)
- 保留 any(target_os = "macos", target_os = "linux") 共享块(macOS 本地 Qwen ASR)
- 补齐非 mac/win 桌面的中立兜底(hotkey 后端桩、insertion 剪贴板兜底、ASR 不可用),
不引入任何 Linux 命名;删除无调用者的 host 包装 switch_to_ascii/restore_input_source
* chore(tauri): 移除 Linux 专用依赖与 wayland-scanner vendor 补丁
- 删 [target.'cfg(target_os = "linux")'.dependencies](dbus 已零引用、gtk 只被删除的
Linux 代码使用)与 keyring 的 unix-except-macos/android 段(含 linux-native-sync-persistent)
- 删 [patch.crates-io] wayland-scanner 与 src-tauri/vendor/wayland-scanner/(只为 Linux
构建修 issue #801 的 quick-xml 漏洞)
- arboard 去掉 wayland-data-control feature
- Cargo.lock 随 manifest 重解析(-379 行)
* refactor(react): 移除前端里的 Linux 分支与专属文案键
- WindowChrome:OS 类型/UA 嗅探/LinuxTitlebar/ol-linux-* 类名/专用常量全部删除,
外壳圆角恒为 0(交回原生装饰),并清掉随之无用的 import 与 SVG 组件
- RecordingInputSection:去掉 os !== 'linux' 条件与 Linux 专属「流式输入」开关行
- AudioCue / localAsr / platform / ChannelList / CodingAgentSection / ShortcutsSection:
本地引擎与能力判断只保留 macOS / Windows / Android
- global.css 删掉 html[data-ol-platform='linux'] 规则(该属性在所有分支都已不再取 linux)
- i18n 9 个语言删除 streamingInsertTitleLinux / streamingInsertHintLinux(React 与 egui 均无引用),
同步更新 experimentalTitles.test.ts;「仅 Windows / Linux」这类平台说明文案保留(Linux 仍由 egui 提供产品)
* test(contracts): 新增 tauri-linux-free 契约并修受影响的既有契约
- 新增 scripts/tauri-linux-free-contract.test.mjs:断言 src-tauri 无 Linux 专用 cfg、
manifest 无 Linux 目标依赖/wayland-scanner patch、vendor 补丁目录不存在、React 无 Linux 分支、
lib.rs 的 compile_error! 防线仍在(反证:注入探针文件后契约确实失败)
- macos-capsule-spaces-contract:函数体边界改为「下一项平台属性」,不再假定紧跟 Linux 实现
- windows-ui-config:外壳断言改查 shellRadius 恒 0;删除已退役的 Linux 标题栏断言
* fix(linux-egui): 胶囊边距按实测面板高度计算,不再依赖 exclusive_zone(-1)
KDE 下胶囊会压在底部面板上:原先靠 exclusive_zone(-1) 让合成器把表面挪出其他
独占区,但并非所有合成器都兑现该请求。现在改为自己量:
- bind wl_output,读 CURRENT mode 得到整屏高度
- 两个一次性 layer surface 探针(anchor=bottom+left+right,size=0)分别请求
exclusive_zone(-1) 与 (0),拿到合成器给出的可用高度,探针随后销毁
- 纯函数 capsule_bottom_margin/PanelInsetProbe 决策:合成器不避让面板时
margin = panel + gap;已避让时 margin = gap;测不到时退化为 gap(原行为)
- 胶囊改用 exclusive_zone(0),边距自己扛;测量值打到 stderr 供实机核对
- 新增 5 个单测覆盖决策树
* Revert "fix(linux-egui): 胶囊边距按实测面板高度计算,不再依赖 exclusive_zone(-1)"
This reverts commit 73c59cfd3750e70dcc07933b5224b79b6574d838.
* fix(fcitx5): 选区探针自行读 PRIMARY,修「旧选区被当成当前选区」
现象:在新选区里选中了没有 text mime 的东西(图片/文件/密码)时,划词助手
拿到的还是**上一次**的选区文本。
根因(已用源码核实):fcitx5 clipboard addon 把 PRIMARY 缓存在全局 primary_,
其 waylandclipboard.cpp 的 DataOffer::receiveRealData() 在 offer 既没有
text/plain;charset=utf-8 也没有 text/plain 时直接 return,回调不触发、setPrimary()
不被调用 —— 缓存里留着上一次的文本。而 IClipboard 公共接口
(clipboard_public.h)只有 primary()/setPrimary(),没有任何"选区变了"或"还有效吗"
的通知,插件无从判断缓存是否过期。
改动:
- 新增 primary_selection.{h,cpp}:用 ext-data-control(KWin 6 提供,
协议规定绑定 device 时补发一次 primary_selection,故一次 roundtrip 即拿到当前
状态)读当前 PRIMARY 的 mime 列表与文本;区分 NoSelection / NoText /
ReadFailed / Unsupported,并沿用 fcitx5 的密码管理器 hint 与 1s→150ms 超时策略。
- ③ 来源优先级改为「合成器此刻的 PRIMARY」优先,应用 surrounding 只在探针给不出
结论时兜底(它是客户端本地缓存,且协议限定 ≤4000 字节)。
- ② NoText 判失效,既不回退 clipboard 缓存也不回退 surrounding。
- ④(部分):更新插件文件头 GetSelectionText 的说明;GetSelectionText 与
CaptureSelectionTarget 共用同一探针,探针不可用才退回缓存。
- 构建:内置 wayland-protocols 1.49 的 ext-data-control-v1.xml(见 protocols/README.md),
由 wayland-scanner 生成胶水;project() 需声明 C 语言,否则生成的 .c 会被
CMake 当作头文件跳过(链接期 undefined reference)。
- 契约:input_target_contract 通过 setPrimaryReader 注入探针结果,新增 ①②③ 用例。
* fix(fcitx5): 探针不可用时才回退 PRIMARY 缓存,并加契约双向覆盖
任务书 ② 要求“新选区没有 text mime 就判失效、不回退旧值”,但严格照做会让
**探针不可用**的场景(X11 会话、合成器没有 ext-data-control)失去唯一来源:
X11/XIM 类应用既不导出 PRIMARY、也没有 surrounding text,于是划词彻底拿不到
选区 —— 相对改动前是功能回退。
改为分级边界:
- 探针**给出结论**(NoText / NoSelection)→ 仍严格判失效,绝不回退旧值(② 的本意);
- 探针**不可用**(Unsupported)且应用 surrounding 也为空 → 才退回 clipboard
addon 缓存,这是最后一级兜底,也是改动前的行为。
为此给缓存读取加了注入缝 selectionCacheReader_(默认实现仍是
cachedPrimarySelection()),契约用例据此同时覆盖两条边界:
- Unsupported + surrounding 空 → 命中 "clipboard-cache-fallback"(返回缓存文本);
- NoSelection / NoText → 捕获为空、getSelectionText() 也为空,缓存里有值也不读。
* refactor(linux-egui): 市场/风格弹窗遮罩统一为「单 Area」,历史确认框加固
「点阴影后阴影上移、设置没法用」的根因是遮罩与卡片处在不同容器:egui 会在指针按下时
把被按到的 Area 抬到同层最上(egui-0.33.3 area.rs:549 的 move_to_top),独立遮罩一旦
被抬起就盖住卡片。设置弹窗此前已改成「遮罩 + 点击拦截 + 卡片同属一个 Area」,这次把
同样的结构铺到其余两处遮罩:
- marketplace.rs:删掉 painter 遮罩 + 独立 backdrop-input Area + Tooltip 卡片的三段式,
合并为单个 openless-marketplace-detail-modal(Foreground)。卡片高度由内容决定(描述
行数可变),所以把上一帧实测高度记在 memory 里居中(首帧用估算值 + request_repaint
收敛),并把本帧**实际画出**的卡片矩形写进 openless-marketplace-detail-card-rect
供测试查询。
- style.rs:同款三段式合并为单个 openless-style-editor-modal(Foreground);卡片尺寸本
来就固定,矩形直接算出并写进 openless-style-editor-card-rect。
- history.rs:confirm_overlay 本来就是单 Area,但 Area 只 sense hover —— 点遮罩会漏到
下方页面(确认框开着时列表行仍会被选中)。改成 Sense::click() 吃掉点击(Tauri 这里用
原生 window.confirm(),点外面不关闭,所以只拦截不关闭),并写
openless-history-confirm-card-rect。
- 前两处顺带从 Order::Tooltip 挪回 Foreground:跨 Order 是 Tooltip > Foreground,弹窗
占 Tooltip 会盖住同层弹出的下拉/菜单。
- layout.rs 的 resize 条带仍是 Order::Tooltip(必须在遮罩之上),未改动。
测试(ui/frontend/mod.rs):新增 assert_overlay_keeps_the_card_on_top 结构性断言与三处
弹窗各一条用例——卡片必须落在弹窗自己的 LayerId 上、在自己的遮罩区里居中、遮罩点上的
输入层也是弹窗自己;点一下遮罩后卡片既不移位、也不会被抬起的遮罩盖住。反向验证:把遮罩
临时挪到另一个容器(不同 Order/不同 Area)后,用例立即失败于
「the card must live in the modal's own layer」,还原后通过。
* fix(linux-egui): 自己窗口里的热键本地兜底,弹窗崩溃重启加预算,QA 热键按 Tauri 切面板
插件只在 fcitx5 的 InputContextKeyEvent 上匹配热键,而我们的窗口从不注册
text-input(winit 默认 ime_allowed=false),所以窗口有焦点时插件一个信号都
不发:主窗口里热键全死,选区助手弹出并开始录音后按语音热键也停不下来。
现在每个窗口进程(主窗口 + 各弹窗)自己做本地匹配,复用设置页录制器的
captured_binding 与注册给插件的 (keysym, states),并与插件信号共用一张去重表
(同一物理按键只处理一次)。
弹窗崩溃(Exited{crashed:true})以前会无条件立刻重开,必崩的面板就变成
无限弹出;改为 60s 窗口内最多重开 N 次,超预算停手并记日志。
QA 热键以前走 Core 的 toggle_recording(切换录音),与 Tauri 的「切换面板显隐」
不一致;现在按 Tauri 语义切面板。
* fix(linux-egui): openless --help/--version 只打印并退出,不再启动应用
* fix(linux-egui): 润色结果沿用选区助手面板的外壳
润色内容并进选区助手面板后,egui 侧仍自己一套排版:没有头尾分隔线、
没有选区助手的 14px 内容边距,看着就像已经下线的独立预览窗口。现在
CardHeader / CardContent / CardFooter 的留白、字号、分隔线全部与选区
助手面板一致(头部还可拖动窗口)。
新增回归测试 the_polish_result_reuses_the_selection_ask_chrome:断言
润色模式画出的头部分隔线与选区助手面板的位置、宽度一致。
* docs: Linux egui 依赖升级计划(依赖批次 / Rust 1.98.1 / Vulkan-wgpu 工作量评估)
把升级调研结论固化进仓库:当前图形栈与全部依赖的可升/已最新清单、
四批执行计划(Rust 工具链、semver patch、10 个 breaking、egui 0.36+glow 0.17)、
Vulkan(wgpu) 的文件级工作量(siri_gl.rs 1090 行 / popup_layer.rs 919 行 / 4 个
GLSL→WGSL)与'双后端不划算、先 profiling'的结论、以及 egui 升级必查的回归清单。
状态:暂缓执行,后续照单执行。
* fix(linux-egui): 修「alt+X 弹出空面板」与「录音热键误弹选区助手」
两个宿主侧状态/时序 bug:
1) show_selection_popup() 先 ensure_popup 再 early-return:没有选区或没
有会话时会把弹窗进程拉起来却一个内容帧都不发 → 凭空出现一个**空的
选区助手面板**(用户报的 alt+X 弹出错误弹窗),并留下
polish_result_visible 脏标志。现在先校验负载再拉弹窗,缺负载时记 warn
并清标志。
2) qa_visible 残留:面板 ✕、弹窗进程正常退出、崩溃重启预算用尽这三条
路径都没清 qa_visible,于是下一次「录音热键」被 apply_local_hotkey_edges
当成「向选区助手提问」,把面板又弹出来(用户报的第二个症状)。现在
统一走 forget_qa_panel_visibility(),并要求 qa_state 存在才算面板在
提问态。
* alt+X 不再弹空润色面板:快照要带选区且结果非空,否则只在语音窗报错
- SelectionStateChanged 以前只看 phase:起手 Capturing 就 ensure_popup +
送一帧空 PolishPreview,于是按 alt+X 会先冒出一个空的润色结果面板
(用户报的「弹窗不对」),没有选区时更是空转一圈再消失。
- 现在要求 phase=Preview 且 preview_text 非空、并且 source_text(捕获到的
选区)非空,才把润色结果帧送进选区助手面板并置 polish_result_visible。
- 没有选区时改为弹语音输入窗(胶囊)报错后退出:文案用 Tauri 的
capsule.selectionPolish.noSelection('未选中内容'),终态 2 秒后自动收起,
不再静默无反馈。
- 诊断日志:有快照但结果不可用时打 debug,无选区时打 warn,便于下次真机定位。
* fix(core): 缓冲转写后台挂接改走注入的 TaskSpawner
CI 的 linux job 卡在 Enforce Linux dependency boundaries:
crates/openless-core/src/dictation_engine.rs:1154 在生产代码里直接
tokio::spawn,绕过 TaskSpawner(check-core-runtime-seam.ps1 明令禁止)。
该行由上游 a8ebcf6ff(#1082 录音优先/稳定模式)引入。
- BufferedTranscriptionInner 增加 task_spawner 字段;
BufferedTranscriptionSession 增 with_task_spawner 构造器,
new/new_with_limit 保持原签名并默认 TokioTaskSpawner(与
BackendDependencies::default 同源)。
- buffered_transcription_session 接收 task_spawner。
- DictationEngine 增加可覆写的 buffered_task_spawner(),默认返回
TokioTaskSpawner;start_transcription_with_progress 用它喂给缓冲区。
- attach_in_background 不再直接 tokio::spawn,行为不变。
* chore(linux-egui): 清掉全部编译警告(死代码与残留状态)
bin 与 test 构建现在都是 0 warning:
- UiResult 里 ProviderMutation / MarketplaceFlow / MarketplaceAuthPoll /
UpdateProgress / UpdateInstalled 五个变体从未被构造(新前端没有市场登录
与「安装更新」入口),删掉变体及其处理分支。
- OverviewSummary 的 last_7 / last_30 恒无读者(前端用 activity_daily 的
尾段自己切窗口):连 ActivityAggregate、aggregate_window 一起删,
测试改为只断言 daily/heatmap 的窗口化。
- 旧 settings 表单草稿字段(qa_input / new_channel_name / vocabulary_* /
correction_* / vocab_preset_* / history_search / marketplace_flow /
style_hotkey_*)在 OpenLessEguiApp 上已无读者,删字段与两处构造初始化
(弹窗进程自己那份 qa_input 保留)。
- ProvidersState::Failed / ProviderEditorState::Failed 的 String 负载、
ProviderEditorState::Loading 的 kind/channel_id 无人读,改为无负载变体。
- 删掉被新 UI 取代的 shortcut_editor / optional_shortcut_editor;
set_style_pack_hotkey 只有测试用,移进 mod tests。
- animated / painted 的初值恒被覆盖,改为延迟初始化。
- 顺手修掉 --help 那个测试上重复的 #[test](重复注册了同一个用例),
以及 popup.rs 测试模块里没用的 use super::*。
* [pi] Work in progress
* [pi] Work in progress
* test(linux-egui): 跟上 egui 0.36 的测试侧 API,并消费 FullOutput 的纹理增量
d055873a 把 egui/eframe 升到 0.36.2 时只改了生产代码,测试目标编译不过
(13 个 E0308/E0599/E0560),另有一批测试在 drop FullOutput 时 panic。
- 0.36 删掉了 CentralPanel::show(&Context, ..) 与 Context::run:测试改用
ctx.run_ui(RawInput, |ui| ..),即取 root Ui 再交给被测函数。
- RawInput 不再有 modifiers 字段:改在 begin_pass 后用
ctx.input_mut(|i| i.modifiers = ..) 注入(仅测试需要)。
- epaint 0.36 起 FullOutput 的 TexturesDelta 未处理就 drop 会 panic
("Dropped TexturesDelta with N unapplied deltas")。无头测试没有渲染器,
新增 run_pass / end_pass 两个测试助手统一 clear,替换 50 处出口。
剩余 6 个 GPU 回调断言仍失败:d055873a 把 siri_gl 的 glow 着色器路径从渲染
路径上摘掉(见该文件第 7 行的 allow(dead_code) 注释),paint() 现在只画 CPU
形状且恒返回 true,warm_up() 是空函数,但测试还在断言 Shape::Callback。
* test(linux-egui): 光效测试跟上「着色器路径已摘除」的现状
d055873a 把 siri_gl 的 glow 着色器从渲染路径上摘掉(paint 改为直接用 CPU
形状画光效并恒返回 true,warm_up 成空函数),但 6 个测试还在断言旧的
「GPU 回调 + CPU 回退所有权」语义。按现状改写断言,等 wgpu 光效落地后再恢复:
- paint_draws_every_mode_on_the_cpu_and_claims_ownership(原
paint_queues_a_gpu_callback):三种模式都画 CPU 形状且返回 true,帧里没有
Shape::Callback。
- warm_up_queues_nothing_while_the_shader_path_is_off:warm_up 不排队、不改
状态,也不影响 paint 的所有权结论。
- a_rejected_driver_still_gets_the_cpu_glow:GPU_FAILED 时 CPU 光效照画并
拿走中心,回调数 0。
- capsule_keeps_the_centre_glow_off_the_gpu_path(原
capsule_queues_the_gpu_glow_per_state):录音/思考的中心光效仍在(CPU
波形折线 / 流体圆点,新增 count_centre_glow 计数),回调数恒为 0,终态
无光效;继续锁死「没有外圈红扫光」。
- recording_capsule_keeps_its_centre_visual:中心视觉的判据补上 CPU 波形。
- capsule_paints_state_specific_content:文字口径按样式分——classic 中心画
「思考中」文字,siri 中心是光效不再叠文字(与 GPU 就绪后的终态一致)。
测试:--lib 192 passed、bin 93 passed、5 契约 PASS、i18n --check PASS。
* [pi] 搞完了,包给你:
* fix(linux-egui): refine titlebar and hotkey capture
* fix(linux-egui): synchronize provider channel editing
* fix(linux-egui): align shortcut handling and settings UI
* fix(linux-egui): refine Siri capsule and popup layout
* fix(linux-egui): keep capsule within desktop work area
* fix(linux-egui): keep capsule native on Wayland
* fix(linux-egui): keep Wayland capsule from stealing focus
* fix(linux-egui): align settings controls in fixed columns
* fix(linux-egui): refine Siri capsule and settings identity
* fix(linux-egui): 设置行固定标签列,模型块照 Tauri 重写
两处用户报的问题 + 三个测试构建遗留错误。
1) 录音与输入等设置页的开关/下拉又贴回标签了。17c8a620 把 row_desc 从
ui.horizontal 换成 allocate_ui_with_layout,但没撑住标签列宽度——egui 只按
内容实际宽度推进光标,于是标签列塌缩成文字宽度,控件紧贴标签(实测同一页
12 个开关的 x 是 235/167/124/138/181/100…)。现在显式 set_min_width,恢复
Tauri 的 minmax(0,200px) + 16px 间距硬列宽。新增几何回归测试
setting_rows_put_every_control_on_one_column 把「所有控件同一列」钉住。
2) provider 编辑器的「模型」块照 Tauri ProvidersSection 重写:
- 模型字段(ChannelModelField):descriptor 带 staticModels 时是预设下拉,
末尾附「自定义模型…」逃生口切手输;无预设时是手输框;值为空且有
defaultModel 时给「填入默认」。
- 可用模型(ProviderTools):当前 endpoint 命中带文档页的预设时按钮是
「查看支持的模型」(打开文档),否则「拉取模型」;拉取为空时明说
「鉴权成功,但没有返回可用模型。」;拉到后是带搜索框的下拉,**选中即写入
模型字段并落地凭据**(Tauri applyModel),不再要求用户再按一次保存。
- orcarouter 这类没有固定清单的服务商,打开编辑器时自动拉一次目录。
仍然替换掉自造的 providers.model_list / btn.list_models 文案,改用 Tauri
catalog 的 settings.channels.modelTitle/modelHint/availableModels 与
settings.providers.*(i18n 同步新增 16 个 key)。
3) 修 ChatGPT 那两个提交留下的测试构建错误(以前根本没编译过,所以从没跑):
popup.rs 里 env 迭代拿到的是 &OsStr,不能和 &str 直接比;fcitx5_contract /
host_contract 还在用 TranslationPressed 的旧 unit 形态;capsule 的 Wayland
断言与 popup_command 实际行为不符(只设 OPENLESS_CAPSULE_PATH,不显式设
WAYLAND_DISPLAY),改成断言「不删 WAYLAND_DISPLAY + 带上 layer 提示」。
测试:lib 192 passed、bin 95 passed、5 契约 PASS、i18n --check PASS、fmt 干净。
* fix(linux-egui): 对齐上游 Beta.2 的窗口层次、侧栏与历史详情
用户拿官方 Tauri Beta.2 实况(视频 2026-09-25)逐项对照,一次修完:
窗口/侧栏底色(左右搞反了)
- 侧栏改 n--ol-sidebar-bg (#f0f0f1),内容区底板从前是 CANVAS (#fafafa) 改回
纯白(Tauri 的 ol-console-main 是 --ol-surface);两个层次之前正好反着。
- 自绘标题栏不再是纯白,改浅灰 #f4f4f5 + 一条底部分隔线(对照视频里那条
标题栏带的灰底);标题栏高度 38 → 36(对齐 Tauri LINUX_TITLEBAR_HEIGHT)。
- 新增几何回归测试 shell_paints_a_gray_sidebar_over_white_content_and_a_
tinted_titlebar,直接钉住「侧栏灰 / 内容白 / 标题栏更灰」。
BETA 徽章与版本号
- 徽章照 Tauri FloatingShell:透明底 + 0.5px 蓝描边(--ol-pill-blue-border)+
圆角 5 + 10px 半粗蓝字(之前是蓝淡底、无描边、圆角 7)。
- 版本号另起一行:侧栏只有 188px,同行会把版本号拦腰折断
(「版本 v2.0.0-」/「Beta.2+…」),视频里上游也是换行显示的。
设置/内容区留白与滚动
- 页面顶部留白 0 → 56px、底部 8 → 24px,对齐 Tauri 的页面容器
(padding: 56px 28px 24px)——「今日概览」「历史记录」这些标题下移。
- 滚动条改预留 8px 槽位(Tauri 是 scrollbar-gutter: stable),不再悬浮压住
右边那列文字。
概览页
- 「概览」导航图标改成上游的 lucide ChartNoAxesColumn(只有三根柱,没有
坐标轴);之前多画了一条 L 形轴。
- 大标题 / 分区标题改用 Medium 字重(Tauri 是 600),字号不变。
- 窄屏判定改成按**窗口宽度**比 720(Tauri useMobileLayout 的阀值),不再是内容区
比 760:主窗口缩到最小(960)时内容区只有 748,之前会错把「星期几张图」和
「最近识别」上下堆叠。窗口太矮时整页滚动,不再把年度活动热力图裁掉。
历史页(按用户要求 + 上游 History.tsx)
- 详情头右侧的三个平铺按钮(删除/重新转录/导出)收进「…」菜单:右对齐、向下
展开,含 播放 / 导出录音 / 重新转录 / 删除(上游还有「重新润色」,egui 后端
还没这个能力)。菜单默认收起,点外面或 Esc 关闭。
- 流水线明细只留「识别」一行(用来语音识别的模型);润色/插入两步不再展示。
- 结果区默认只显示润色结果(胶囊是「润色 · 风格名」),原文靠「查看原文 /
隐藏原文」开关展开,不再默认摆两栏重复内容;润色失败时回退显示原文。
- 新增 i18n key:history.show_raw / history.hide_raw / shell.beta_tag /
history.play_recording(前两个同时补进 Tauri 的 8 个语言文件,与上游一致)。
省略号「···」变「…」
- 根因是字体:本机 fc-match sans-serif 命中的是 Noto Sans CJK SC,CJK 面的
U+2026 是**居中**的全角省略号;把拉丁面(sans-serif:lang=en)排到链首,
与 macOS 的 system-ui(拉丁优先、CJK 回退)一致。
测试:lib 192 passed、bin 96 passed(新增 1 个配色几何测试、改 2 个过时断言)、
5 契约全 PASS、i18n --check PASS、fmt 干净。
* fix(linux-egui): 纯图标按钮的图标居中
历史详情右上角的「…」菜单按钮传的是空 label,原来会按「图标 + 间距」的一半
偏移 3px,看着不居中。
* feat(linux-egui): 对齐历史、词典、风格与市场的第一批交互
* fix(ci): 用 basename 判定 lib.rs,兼容 Windows 路径分隔符
`tauri-linux-free-contract.test.mjs` 之前用 `file.endsWith('/lib.rs')`
判断「Linux 守卫文件」,在 Windows runner 上路径分隔符是 `\`,于是
`openless-linux-egui/src/lib.rs` 的 `cfg(target_os = "linux")` 守卫块被判成
违规,job 直接失败。改用 `path.basename`,并把 i18n 目录的跳过条件换成能同时
匹配 `/` 与 `\` 的正则。
本地 `node scripts/tauri-linux-free-contract.test.mjs` 通过(122 个 Rust 文件、
214 个 React 文件);远端 Windows CI 需在推送后确认。
* feat(core): 速记采用独立历史语义(永久录音、同一 id 更新、免于普通清理)
对齐上游 Beta.2 的 QuickNote:速记不是「history 页改个名」,它有自己的
持久化语义。
- `HistorySource::QuickNote` + `DictationOutputTarget::QuickNote`:录音开始时
先落一条草稿(`error_code = "recording"`),完成时用**同一个历史 id** 覆盖它,
取消时把草稿标记为 `cancelled`,而不是留下半条记录或整条消失。
- 速记不插入文本(`insert_text = false`)且音频永久归档。
- 历史清理策略区分两类记录:普通记录仍受 `retention_days` 与 `max_entries`
限制,速记只受 200 条硬上限约束;`clear()` 也只清普通记录,不再连速记一起删。
- `dictation_output_target()` 让宿主能查询当前会话的输出目标(按钮/快捷键
切换录音时不必猜)。
- 偏好里新增可选的速记快捷键,并接入既有的冲突消解顺序(听写 > 翻译 > 选区
助手 > 速记 > …)。
`cargo test --locked -p openless-core` 通过。
* feat(linux-egui): 速记页、快捷键、风格包图标与翻译页缩放修复
对齐上游 Beta.2 的界面与交互,并修掉用户报告的几处偏差。
速记(QuickNote)
- 新增侧栏页面:复用历史列表/详情,只筛 `HistorySource::QuickNote`;按钮与
快捷键共用同一条开关逻辑(未在速记中则开始永久录音,开始中则结束)。
- 速记页顶部是上游同款可收起的快捷键卡片(录制 / 停用 / × 收起),收起状态
持久化到 `linux-ui-state.json` 的 `quick_note_shortcut_hidden`(该写入改为
「合并单键」而不是覆盖整个文件,避免把语言偏好清掉)。
- 设置页「快捷键与选区」新增速记行;宿主的本地热键、fcitx5 插件(新增
`QuickNoteEvent` / `SetQuickNoteHotkeyRaw`)与去重都接上同一条通路;插件没
这个新方法时不报错(旧插件兼容)。
风格包图标
- 新增 `style_icon.rs`:本地把用户选的 SVG 栅格化成 128px PNG 再交给 Core。
先做 XML 结构校验(根元素、禁用 `script`/`foreignObject`/`image`/`animate*`、
禁止外部 `href`/`url(...)`/`@import`/`on*`),再限 256 KB 输入 / 64 KiB 输出。
- 卡片上的图标按钮(含铅笔角标、自定义时的 × 复位)走真实的
`set_style_pack_icon`;纹理解码按 `(pack id, icon path)` 缓存,不再每帧重解码。
翻译页
- 卡片头部的标题/说明与状态药丸在同一 `horizontal` 里:窄列下文字不换行会把
药丸顶出卡片,egui 的 `columns` 随即把整页宽度记为「内容宽度」,下一帧按更
大的宽度再排一次——这正是用户看到的缩放卡顿与「使用方法」被顶出可视区的
来源。给文字块加上按药丸宽度算出的 `set_max_width`,让说明换行。
- `ComboBox::width` 是最小文本宽度,最终按钮还要加图标与内边距,原来的
`available - 4` 会溢出卡片;改为预留 28px。
- 新增回归测试:大→中→小三种窗口下页面都不得超出视口,且最小窗口下内容必须
高于视口(说明「使用方法」靠滚动可达)。
纠正规则页
- 按上游合成单张卡片:「只看自动收集」改为真正的复选框(此前只是个不可点的
徽章,筛选根本用不了),与「一键清空」同行;规则为空时在同一张卡片内给出
占位说明。
选区助手
- 「保存历史」卡片的图标改为上游同款 40×40 圆角底 + 居中图标,标题/说明按
12/13px 与 5px 间距居中排布,开关尺寸对齐 40×24。
词典
- 筛选标签的选中胶囊只用一个动画值插值(原来在同一帧对同一个 id 写三次不同
目标,胶囊会抖)。
风格/市场
- 卡片角标配色与「衍生自」判定对齐上游(作者与来源作者相同时也算衍生);
市场卡片去掉未打开详情就出现的安装按钮,详情弹窗按 560px 固定宽度居中。
i18n:`src/i18n/*.ts` 与 `linux-egui/src/i18n.rs` 同步(速记导航、快捷键卡片、
图标提示等)。`node scripts/sync-egui-i18n.mjs --check` 无漂移。
* chore(release): 同步上游 Beta.2 的两条发版约定
按上游 Beta.2 的修法同步(上游 commit
|