TRIP and baiqing
aee0d82a59
feat(startup): add "start minimized" toggle (Windows background autostart) ( #357 )
...
回应用户「Windows 端开机启动时不弹软件界面、自动后台启动」需求。
新 pref start_minimized: bool,默认 false 不破坏现有行为。打开后所有
启动路径都不弹主窗口(包括开机自启 + 手动启动),App 仅在菜单栏 / 托盘
运行;用户从托盘进入主窗口。Windows 开机自启场景用得多——本来想要
后台 + 托盘,不想被主窗口打扰。
设计选择:不区分"自启动 vs 手动启动"——开关一开则所有启动统一静默。
理由:避免 argv parsing + 重新注册 autostart 带来的复杂性(tauri-plugin-
autostart 的 args 是 init() 时静态设的、动态切需要重注册)。开关明确
打开 = 接受所有启动都从托盘进。
实现:
- types.rs: UserPreferences 加 start_minimized 字段(含 UserPreferencesWire +
Default + Deserialize 全套兼容)
- lib.rs setup hook: 读 coordinator.prefs().get().start_minimized;为 true
时跳过 main.show()。OPENLESS_SHOW_MAIN_ON_START=1 仍保留老的强制 show
路径(dev 用),优先级高于 prefs
- 前端 types.ts / ipc.ts mockSettings / stylePrefs.test.ts: 加默认 false
- Settings.tsx Recording 区在 AutostartRow 下加 Toggle
- i18n 5 locale (zh-CN / zh-TW / en / ja / ko) 加 startMinimizedLabel +
startMinimizedDesc 两个 key
顺手修预存 bug(跟 PR #355 副 commit 同源——本 branch 没含那个修复):
HotkeyBinding default keys=None,避免旧 prefs 反序列化时 trigger / keys
不一致。types::tests 8/8 pass。
不在本 PR:Windows IME 切换还原偶发不还原 bug——需要 Windows 日志
+ 复现条件才能定位,盲改可能引入新 bug。已请求用户提供
%LOCALAPPDATA%\OpenLess\Logs\openless.log + 系统版本 + IME 信息。
cargo test --lib: 135/135 pass。npm run build: tsc + vite 干净。
Co-authored-by: baiqing <lbx12309@icloud.com >
2026-05-08 11:49:13 +08:00
TRIP and baiqing
375456f834
fix(icons): pad bundle icon to Apple HIG ~10% margin ( #356 )
...
* feat(history+polish): time-based retention + conversation-aware polish
回应用户「历史记录功能 + 对话上下文 + 提示词调优」需求。
新增 2 个用户偏好(types.rs + UserPreferencesWire + Default + Deserialize 全套兼容):
- history_retention_days: u32(默认 7;0=不按时间清理仅受 200 上限)
- polish_context_window_minutes: u32(默认 5;0=关闭走原有单轮 polish)
后端:
- persistence.rs:
- HistoryStore::append_with_retention(session, retention_days):写入新条目时
顺手把超过 N 天的会话裁掉,不需要后台轮询
- HistoryStore::recent_within_minutes(minutes):返回最近 N 分钟内的会话(newest-
first,take_while 利用 newest-first 顺序提前终止)
- 老 append() 保留,等价 append_with_retention(s, 0)
- polish.rs:
- polish() 多一个 prior_turns 参数;不为空时走 chat_completion_with_polish_history
构造 system + (user/assistant)... + 当前 user 多轮 chat completion
- 把 chat_completion 的 send/parse 抽成 send_chat_request 复用
- prompts::polish_context_instruction():明确告诉 LLM 历史 turns 仅作语义上下文,
不要复读已经被插入到用户文档里的 assistant 输出
- coordinator.rs:
- 主流水线在 Polishing 阶段拉 recent_within_minutes(prefs.window) 作为 prior_turns,
过滤掉失败的 session(error_code/empty final_text)避免喂噪声
- 翻译路径 / Raw 模式 / repolish 主动跳过上下文(语义不合适)
- 3 个 history.append 改成 append_with_retention(session, prefs.history_retention_days)
- commands.rs / coordinator.rs:repolish / polish.rs::tests / commands.rs::testProvider:
原 polish() 调用全部加 &[] 空 prior_turns 参数
前端:
- types.ts:UserPreferences 加 historyRetentionDays / polishContextWindowMinutes
- ipc.ts mockSettings + stylePrefs.test.ts mockPrefs:补默认值
- Settings.tsx Recording 区加 2 个 number input(裸 input + clamp,retention 0-365 天,
context window 0-60 分钟)
- i18n 5 locale(zh-CN / zh-TW / en / ja / ko)补 4 个 key:
historyRetentionLabel / Desc + polishContextWindowLabel / Desc
性能注意:retention 在 append 时 O(n) 一次扫描裁切;recent_within_minutes 使用
take_while 利用 newest-first 顺序提前终止,平均 O(window 内条目数) 不是 O(总历史)。
cargo check + commands::tests (30/30) + polish::tests (10/10) + npm run build 全 pass。
预先存在的 types::tests::legacy_hotkey_trigger 失败跟本 PR 无关(macOS 平台特有)。
不包含:连接 / 厂商 / 提示词排布优化(用户也提了)——那些需要先 measure 再动,
不能盲改,留给独立 PR。
* fix(types): HotkeyBinding default keys = None (legacy trigger / keys mismatch)
跑测试时发现 types::tests::legacy_hotkey_trigger_still_produces_effective_key_codes
fail,原因跟本 PR 无关,但用户要求中途发现 bug 顺手修。
Bug:HotkeyBinding 用 `#[serde(default)]` 结构级 default。反序列化时整个 struct
先按 Default 填充,JSON 字段覆盖。如果 Default 里 keys 预填具体 codes(macOS
Default ["AltRight"] / Windows ["ControlRight"]),那么旧 prefs 写
`{"trigger":"rightControl","mode":"toggle"}`(无 keys)反序列化后会变成:
trigger = RightControl ← JSON 覆盖
keys = Some([HotkeyKey { code: "AltRight" }]) ← Default 残留,跟 trigger 不一致
effective_codes() 在 keys 是 Some 时直接信任 keys、不再 fallback 到
legacy_trigger_code(trigger),于是用户选的 trigger 跟实际生效的 keys 对不上。
修法:Default::keys = None。这样 keys 缺省时 effective_codes() 走 legacy_trigger_code
路径,跟 trigger 自动同步;JSON 显式写 keys 时仍可以覆盖。
types::tests 8/8 pass,全 lib 测试 135/135 pass。
* test(polish): lock down "no echo prior context" with 4 explicit unit tests
用户的核心顾虑:上下文感知 polish 不能让 LLM 把上文吐出来。PR #355 设计了
两层防御(chat structure + system prompt 显式指令),但**没写过 explicit 测试**
盯死这条不变量——回归就听天由命。这次把它锁住。
把 chat_completion_with_polish_history 里 inline 的 messages 构造抽成纯函数
build_polish_history_messages(system_prompt, prior_turns, user_prompt) → Vec<Value>,
方便可测,行为零变化。
新增 4 个 polish::tests:
1. build_polish_history_messages_empty_prior_falls_back_to_two_messages
prior 空时只剩 [system, user],跟单轮 chat_completion 等价
2. build_polish_history_messages_orders_prior_oldest_to_newest_then_current
入参 prior_turns 是 newest-first(match HistoryStore::recent_within_minutes
返回顺序),chat 需要 oldest-first 时间序——验证 reverse 没漏。
顺序错了 LLM 会看到「未来→过去→当前」错乱时间轴,对话就废了。
3. build_polish_history_messages_keeps_polished_text_at_assistant_role
关键不变量:历史 polish 必须挂 role=assistant,**不能**误挂 user。
typo 把它放进 user role 会让 LLM 当成新输入再润色 → 输出复读上文,
直接违反"不复读"目标。这个 test 把这条锁死,重构时一改就 fail。
4. polish_context_instruction_explicitly_forbids_repeating_prior_assistant_output
第二层防御:仅靠 chat structure 不够(一些模型在长上下文里仍可能 echo prior turns)。
验证 system prompt 末尾追加的指令含「不要 / 复读 / assistant-or-已经整理 /
当前最新」四组关键词,文案可以改但语义不能丢。
cargo test --lib:139/139 pass(135 之前 + 4 新增),完全无回归。
* fix(icons): pad bundle icon to Apple HIG ~10% transparent margin (#354 )
issue #354:在 macOS 26 以下系统,Dock 里的 OpenLess 图标比其他 app 大约 25%。
根因:原 icon.png 内容 100% 填满 1024×1024 画布,没留 Apple HIG 推荐的
~10% 透明 padding。macOS 26 自带 Liquid Glass squircle 容器会自动套圆角 +
裁切,掩盖了这个问题;但 macOS 11-15 把 icon PNG 原样渲染到 Dock,
所以视觉尺寸明显比标准 app 大一圈。
修法:
- 用 PIL 把 1024×1024 master 缩到 820×820 居中、四周各 102px 透明 padding
(内容占比 ≈ 80%,符合 Apple HIG 约定)
- 重新生成 Tauri bundle 的 5 个 PNG:icon.png / 32×32 / 64×64 / 128×128 / 128×128@2x
- 用 iconutil 重新打包 icon.icns(含 16/32/128/256/512 + @2x 共 10 张)
- 不动 frontend public/AppIcon.png——那个是 React UI 内部用的小图(14-56px +
CSS borderRadius),padding 反而会让它在固定容器里显得偏小
校验:python PIL 读新 icon.png,bbox 显示画布 512×512、内容 416×416 居中、
四周 padding 各 48px,内容占比 81.2%——接近 80% 推荐值。
不影响 macOS 26 渲染:Liquid Glass 看到的视觉内容仍是同一张图,
只是周围多了透明像素,Apple 自动 squircle 会无视它。
---------
Co-authored-by: baiqing <lbx12309@icloud.com >
2026-05-08 11:43:33 +08:00
TRIP and baiqing
b86c8d7966
feat(history+polish): time-based retention + conversation-aware polish ( #355 )
...
* feat(history+polish): time-based retention + conversation-aware polish
回应用户「历史记录功能 + 对话上下文 + 提示词调优」需求。
新增 2 个用户偏好(types.rs + UserPreferencesWire + Default + Deserialize 全套兼容):
- history_retention_days: u32(默认 7;0=不按时间清理仅受 200 上限)
- polish_context_window_minutes: u32(默认 5;0=关闭走原有单轮 polish)
后端:
- persistence.rs:
- HistoryStore::append_with_retention(session, retention_days):写入新条目时
顺手把超过 N 天的会话裁掉,不需要后台轮询
- HistoryStore::recent_within_minutes(minutes):返回最近 N 分钟内的会话(newest-
first,take_while 利用 newest-first 顺序提前终止)
- 老 append() 保留,等价 append_with_retention(s, 0)
- polish.rs:
- polish() 多一个 prior_turns 参数;不为空时走 chat_completion_with_polish_history
构造 system + (user/assistant)... + 当前 user 多轮 chat completion
- 把 chat_completion 的 send/parse 抽成 send_chat_request 复用
- prompts::polish_context_instruction():明确告诉 LLM 历史 turns 仅作语义上下文,
不要复读已经被插入到用户文档里的 assistant 输出
- coordinator.rs:
- 主流水线在 Polishing 阶段拉 recent_within_minutes(prefs.window) 作为 prior_turns,
过滤掉失败的 session(error_code/empty final_text)避免喂噪声
- 翻译路径 / Raw 模式 / repolish 主动跳过上下文(语义不合适)
- 3 个 history.append 改成 append_with_retention(session, prefs.history_retention_days)
- commands.rs / coordinator.rs:repolish / polish.rs::tests / commands.rs::testProvider:
原 polish() 调用全部加 &[] 空 prior_turns 参数
前端:
- types.ts:UserPreferences 加 historyRetentionDays / polishContextWindowMinutes
- ipc.ts mockSettings + stylePrefs.test.ts mockPrefs:补默认值
- Settings.tsx Recording 区加 2 个 number input(裸 input + clamp,retention 0-365 天,
context window 0-60 分钟)
- i18n 5 locale(zh-CN / zh-TW / en / ja / ko)补 4 个 key:
historyRetentionLabel / Desc + polishContextWindowLabel / Desc
性能注意:retention 在 append 时 O(n) 一次扫描裁切;recent_within_minutes 使用
take_while 利用 newest-first 顺序提前终止,平均 O(window 内条目数) 不是 O(总历史)。
cargo check + commands::tests (30/30) + polish::tests (10/10) + npm run build 全 pass。
预先存在的 types::tests::legacy_hotkey_trigger 失败跟本 PR 无关(macOS 平台特有)。
不包含:连接 / 厂商 / 提示词排布优化(用户也提了)——那些需要先 measure 再动,
不能盲改,留给独立 PR。
* fix(types): HotkeyBinding default keys = None (legacy trigger / keys mismatch)
跑测试时发现 types::tests::legacy_hotkey_trigger_still_produces_effective_key_codes
fail,原因跟本 PR 无关,但用户要求中途发现 bug 顺手修。
Bug:HotkeyBinding 用 `#[serde(default)]` 结构级 default。反序列化时整个 struct
先按 Default 填充,JSON 字段覆盖。如果 Default 里 keys 预填具体 codes(macOS
Default ["AltRight"] / Windows ["ControlRight"]),那么旧 prefs 写
`{"trigger":"rightControl","mode":"toggle"}`(无 keys)反序列化后会变成:
trigger = RightControl ← JSON 覆盖
keys = Some([HotkeyKey { code: "AltRight" }]) ← Default 残留,跟 trigger 不一致
effective_codes() 在 keys 是 Some 时直接信任 keys、不再 fallback 到
legacy_trigger_code(trigger),于是用户选的 trigger 跟实际生效的 keys 对不上。
修法:Default::keys = None。这样 keys 缺省时 effective_codes() 走 legacy_trigger_code
路径,跟 trigger 自动同步;JSON 显式写 keys 时仍可以覆盖。
types::tests 8/8 pass,全 lib 测试 135/135 pass。
* test(polish): lock down "no echo prior context" with 4 explicit unit tests
用户的核心顾虑:上下文感知 polish 不能让 LLM 把上文吐出来。PR #355 设计了
两层防御(chat structure + system prompt 显式指令),但**没写过 explicit 测试**
盯死这条不变量——回归就听天由命。这次把它锁住。
把 chat_completion_with_polish_history 里 inline 的 messages 构造抽成纯函数
build_polish_history_messages(system_prompt, prior_turns, user_prompt) → Vec<Value>,
方便可测,行为零变化。
新增 4 个 polish::tests:
1. build_polish_history_messages_empty_prior_falls_back_to_two_messages
prior 空时只剩 [system, user],跟单轮 chat_completion 等价
2. build_polish_history_messages_orders_prior_oldest_to_newest_then_current
入参 prior_turns 是 newest-first(match HistoryStore::recent_within_minutes
返回顺序),chat 需要 oldest-first 时间序——验证 reverse 没漏。
顺序错了 LLM 会看到「未来→过去→当前」错乱时间轴,对话就废了。
3. build_polish_history_messages_keeps_polished_text_at_assistant_role
关键不变量:历史 polish 必须挂 role=assistant,**不能**误挂 user。
typo 把它放进 user role 会让 LLM 当成新输入再润色 → 输出复读上文,
直接违反"不复读"目标。这个 test 把这条锁死,重构时一改就 fail。
4. polish_context_instruction_explicitly_forbids_repeating_prior_assistant_output
第二层防御:仅靠 chat structure 不够(一些模型在长上下文里仍可能 echo prior turns)。
验证 system prompt 末尾追加的指令含「不要 / 复读 / assistant-or-已经整理 /
当前最新」四组关键词,文案可以改但语义不能丢。
cargo test --lib:139/139 pass(135 之前 + 4 新增),完全无回归。
---------
Co-authored-by: baiqing <lbx12309@icloud.com >
2026-05-08 11:42:47 +08:00
millionart
aa5add1188
[codex] fix Foundry Windows App Runtime prompt ( #340 )
2026-05-08 11:42:07 +08:00
Chris233
4aa3f6c58d
Merge pull request #352 from H-Chris233/fix/issue-297-session-uuid
...
修复 session_id 回绕导致的迟到事件误匹配
2026-05-08 11:08:56 +08:00
H-Chris233
e95519a6b4
Prevent stale recorder events from matching wrapped session ids
...
The dictation coordinator used a wrapping u64 counter to guard delayed recorder and startup events. Replacing that guard with UUID-backed session ids removes the theoretical wraparound collision while keeping the existing stale-event comparison model.
Constraint: Issue #297 requests the UUID-based fix rather than checked arithmetic.\nRejected: checked_add with warning | still preserves a numeric boundary condition instead of eliminating the collision class.\nConfidence: high\nScope-risk: narrow\nTested: cargo check --manifest-path openless-all/app/src-tauri/Cargo.toml\nTested: cargo test --manifest-path openless-all/app/src-tauri/Cargo.toml coordinator::tests:: --lib\nNot-tested: Full cargo test --lib still has unrelated existing failure in types::tests::legacy_hotkey_trigger_still_produces_effective_key_codes.\nRelated: #297
2026-05-08 10:39:33 +08:00
Chris233
6c5eee6b78
Merge pull request #351 from H-Chris233/fix/issue-316-hotkey-settings-error
...
修复热键设置加载失败后永久 loading
2026-05-08 10:31:37 +08:00
H-Chris233
98f25bd9bb
Keep hotkey settings load failures recoverable
...
Hotkey settings are a shared context for multiple pages, so one rejected IPC call must not leave the provider in a permanent loading state. The refresh path now records a localized recoverable error, keeps any successfully loaded half of the state, and always releases loading so consumers can render retry UI instead of staying blank.
Constraint: Issue #316 requires local load failures to stay visible and non-fatal.
Rejected: Keep Promise.all with one catch | it would still discard partial success from the other IPC call.
Confidence: high
Scope-risk: narrow
Tested: git diff --check; npm run build; cargo check --manifest-path src-tauri/Cargo.toml
Not-tested: Manual Tauri UI session with a deliberately corrupted preferences.json.
Related: https://github.com/appergb/openless/issues/316
2026-05-08 10:05:17 +08:00
Chris233
29c9160b9f
Merge pull request #346 from H-Chris233/fix/issue-300-linux-ci-gate
...
为 PR CI 增加 Linux cargo check 门禁
2026-05-08 09:48:47 +08:00
TRIP and baiqing
10440c7f72
chore: bump version to 1.2.24-3 (Beta-3) ( #349 )
...
Co-authored-by: baiqing <lbx12309@icloud.com >
2026-05-08 09:47:30 +08:00
TRIP and baiqing
e358c20b20
fix(updater): use GitHub atom feed for Beta lookup, dodge API rate limit ( #350 )
...
用户报「获取 Beta 版本信息失败」根因:fetch_latest_beta_release 调
api.github.com/repos/.../releases,未认证 60 req/h/IP。多次切 Beta toggle
+ 同 IP 共享配额(公司/家庭网络后),轻易撞 403 rate limit exceeded。
现场实测 IP 已耗尽:x-ratelimit-remaining: 0。
修法:换成 GitHub releases.atom(公开 RSS feed,CDN cache,无 API rate limit)。
具体改动:
- fetch_latest_beta_release 端点从 api.github.com 改成 github.com/.../releases.atom
- 提取一个纯函数 parse_latest_beta_from_atom 做字符串解析(不引 XML 库——
feed 格式稳定,找 /releases/tag/ 锚点抓 tag 即可),方便单测
- atom feed 不显式标 prerelease,但项目约定 tag 后缀 `-beta-tauri` 就是 Beta,
过滤后缀已足够
- timeout 从 10s 提到 15s,给跨境网络更多余地
- 加 2 个 unit test:(1) 混合 stable+beta 时返回 Beta entry;(2) 全 stable
时返回 None。两个 test 都已 pass
不改前端:JS 端调用签名 / 错误展示路径不变,下次 Beta-3 release 出来后用户
装上即生效。
Co-authored-by: baiqing <lbx12309@icloud.com >
2026-05-08 09:47:18 +08:00
TRIP and baiqing
8b55469d8e
feat(ui): drop sidebar version chip; show BETA tag only on beta builds ( #348 )
...
用户反馈:左上角 OpenLess logo 旁边的 v1.2.24-2 chip 在窄侧栏下被截断换行
(视觉上读作 "v1.2.24-/2"),太丑且没有信息价值——版本号在底部 footer
和「设置 → 关于」里都已经展示,不需要这第三处。
同时 BETA 标签当前是无条件渲染的,正式版用户也会看到,违反"Beta 不
泄漏到正式版"原则。
改动:
- appVersion.ts:新增 IS_BETA_BUILD 常量。约定:semver prerelease 段
(含 `-`)= Beta(如 1.2.24-1 / 1.2.24-2);不含 `-` = 正式版
(如 1.2.23 / 1.2.24)。
- FloatingShell.tsx:
- 删左上 brand 行里的 version chip <span>(保留 logo + "OpenLess" 文字)
- BETA 区域用 {IS_BETA_BUILD && (...)} 包条件渲染。正式版 build 直接
不渲染整块,避免普通用户误以为正式版是 Beta
footer 里的 "版本 v1.2.24-2 检查" 和 设置 → 关于的 AboutUpdateControl
保留不动——那两处是用户主动想看的版本信息,本次只删左上的冗余 chip。
npm run build 干净通过。
Co-authored-by: baiqing <lbx12309@icloud.com >
2026-05-08 09:39:14 +08:00
TRIP and baiqing
b7685cadda
docs(release): make macOS xattr workaround discoverable ( #347 )
...
发现的真因:v1.2.23 Stable 和 v1.2.24-1 Beta 的 macOS DMG 都是 ad-hoc 签名 + 未经 Apple 公证(仓库没配 APPLE_CERTIFICATE 等 secrets)。macOS 14+ Gatekeeper 默认拒绝运行未公证的 app,弹"OpenLess 已损坏"。
build-mac.sh:40-41 注释已经写明这是已知限制:
"用户免手工 xattr 的根本方案是 Developer ID 签名 + Apple notarization"
之前 README 只在 brew install 段写了 xattr -cr 指令,DMG 直装路径没写。
用户首次装 Beta 不知道要做这一步,看到"已损坏"以为 release 坏了。
本 PR:
- README.md / README.zh.md 的「下载与安装」段,DMG 直装路径加上 xattr -cr 指令 + 一句解释为什么需要(ad-hoc 签名、未公证)
- release-tauri.yml 的 Create release step 加 body 模板,所有未来 release 自动 prepend 一段:macOS xattr 指令 + Beta/Stable 渠道说明。用 append_body: true 让它放在 generated notes 之前
不在本 PR:配 Apple Developer 证书 + secrets——那是根治方案,需要 $99/年 Apple Developer 账号 + 用户决定要不要走那条路。
Co-authored-by: baiqing <lbx12309@icloud.com >
2026-05-08 09:27:16 +08:00
H-Chris233
c8fa63e30e
Catch Linux cfg drift before release
...
CI only checked macOS and Windows before merge, so Linux-only Rust paths could fail cargo check for the first time during release packaging. The matrix now includes an Ubuntu lane that runs the same frontend build and backend cargo check, with only the Linux libraries needed for checking Tauri dependencies and no bundle step.
Constraint: Issue #300 asks for PR-time Linux validation while skipping Tauri bundle work.\nRejected: Add Linux release bundle to CI | too slow and duplicates release-tauri.yml.\nConfidence: high\nScope-risk: narrow\nTested: python ci.yml textual assertions; git diff --check; cd openless-all/app && npm run build; cd openless-all/app && cargo check --manifest-path src-tauri/Cargo.toml\nNot-tested: Full GitHub Actions ubuntu-latest runner execution before PR.\nRelated: https://github.com/appergb/openless/issues/300
2026-05-08 09:16:46 +08:00
Chris233
66e0efa7de
Merge pull request #343 from H-Chris233/fix/issue-310-history-load-errors
...
修复 History IPC 失败时无反馈和卡 loading
2026-05-08 09:12:05 +08:00
TRIP and baiqing
2a99d6bf6e
chore: bump version to 1.2.24-2 (Beta-2) ( #345 )
...
Co-authored-by: baiqing <lbx12309@icloud.com >
2026-05-08 09:08:23 +08:00
TRIP and baiqing
58a0ad9e62
fix(release): use tagged URL in beta manifest, not releases/latest ( #344 )
...
修复 v1.2.24-1-beta-tauri release verification 时发现的 latent bug:
Beta manifest 里 url 字段指向 https://github.com/.../releases/latest/download/ <asset>,
但 releases/latest 永远是最新非-prerelease(即 Stable),Beta 用户按这个 url
auto-update 会拉到 Stable 包:
- macOS Beta: 文件名 OpenLess_aarch64.app.tar.gz 跟 Stable 重名 → 下载 Stable 包,
装上的是 1.2.23 但 manifest 声称 1.2.24-1,version drift
- Windows/Linux Beta: 文件名带版本号 OpenLess_1.2.24-1_x64-setup.exe / .AppImage,
releases/latest 路径下没这个文件 → 直接 404
Stable 渠道沿用 releases/latest 是对的(latest = Stable)。
修法:write-updater-manifest.mjs 多读一个 OPENLESS_RELEASE_TAG env,beta 时
url 拼成 releases/download/<tag>/<asset>(指定具体 tag,不被 prerelease 折叠
影响);stable 不变。脚本对 beta 缺 tag 做 fail-fast。
release-tauri.yml 在 manifest step 多注入 OPENLESS_RELEASE_TAG: ${{ github.ref_name }}。
为什么不重发 v1.2.24-1:当前走 Path A 手动下载式 opt-in,前端调
fetch_latest_beta_release 拿 GitHub release 页 html_url,用户跳浏览器手动选包,
不消费 manifest.url。所以 v1.2.24-1 已发布的 manifest 里 url 错也是 dormant。
下一轮 Beta tag(v1.2.24-2-beta-tauri 或 stable cut 后再做 Beta)会用修复后
的脚本产出正确 URL。
本地 smoke test:
- channel=beta + 缺 OPENLESS_RELEASE_TAG → fail-fast 抛 "is required"
- channel=beta + 有 tag → env 检查通过,进入 artifact 解析
- channel=stable + 不传 tag → 维持原行为
- python yaml.safe_load() 解析 release-tauri.yml 通过
Co-authored-by: baiqing <lbx12309@icloud.com >
2026-05-08 09:04:14 +08:00
H-Chris233
f79a8dc089
Avoid restoring stale History snapshots
...
The History mutation handlers already wait for backend success before changing visible list state, so failure recovery does not need to write back the request-time snapshot. Removing that restoration avoids overwriting a newer refresh result, and delete success now updates from the latest list state instead of the closure captured before the IPC call.
Constraint: PR #343 review identified stale snapshot writes during overlapping refresh and mutation requests.\nRejected: Add action-wide locking | more UI state than needed once failed mutations stop writing stale snapshots.\nConfidence: high\nScope-risk: narrow\nTested: cd openless-all/app && git diff --check && npm run build\nNot-tested: Manual overlapping refresh/delete in the Tauri app.
2026-05-08 08:50:47 +08:00
H-Chris233
1676557a75
Keep History usable after IPC failures
...
History actions previously assumed list, clear, and delete IPC calls would resolve. A rejected load could leave the page stuck in loading, while failed mutations gave no user-visible feedback. The page now clears loading in finally, shows retryable inline load errors, and reports clear/delete failures while restoring the prior local selection and items.
Constraint: Issue #310 asks for inline card feedback without changing Rust handlers or introducing a toast system.\nRejected: New shared error component | broader UI surface than needed for this single-page failure path.\nConfidence: high\nScope-risk: narrow\nTested: cd openless-all/app && git diff --check && npm run build\nNot-tested: Live Tauri IPC failure injection.\nRelated: https://github.com/appergb/openless/issues/310
2026-05-08 08:06:29 +08:00
Chris233
13937d21fd
Merge pull request #342 from H-Chris233/fix/issue-311-overview-load-errors
...
修复 Overview IPC 加载失败时误显示空数据
2026-05-08 08:02:27 +08:00
H-Chris233
d32da5c36c
Expose Overview IPC load failures inline
...
Overview previously treated rejected history and credential IPC calls like empty data, which made backend read failures indistinguishable from a clean first run. The page now keeps separate error flags, shows provider status as unreadable when credential status cannot load, and replaces history-derived metrics/recent rows with retryable inline failure states.
Constraint: Issue #311 asks for per-card inline feedback without changing backend handler signatures or adding global toast UI.\nRejected: Global error boundary or toast | broader UI behavior than needed for the Overview failure path.\nConfidence: high\nScope-risk: narrow\nTested: cd openless-all/app && git diff --check && npm run build\nNot-tested: Live Tauri IPC failure injection.\nRelated: https://github.com/appergb/openless/issues/311
2026-05-08 07:25:38 +08:00
Chris233
c4a7ebe62c
Merge pull request #338 from H-Chris233/fix/issue-315-selectionask-save-feedback
...
修复划词追问保存失败无反馈
2026-05-08 07:16:52 +08:00
H-Chris233
b0f481c78e
Block shortcut recording during Selection Ask saves
...
The Selection Ask page now disables its card toggles while a save is in flight, but the nested shortcut recorder still accepted new recordings. That left a small overlap window where a second QA hotkey save could race the current setQaHotkey/savePrefs pair.\n\nThis keeps the recorder default behavior unchanged for other pages by adding an optional disabled prop and only wiring it from Selection Ask during its saving state.\n\nConstraint: Review feedback on PR #338 requested preventing overlapping QA shortcut saves without broad settings-queue refactoring.\nRejected: Global serialization inside ShortcutRecorder | unnecessary for pages that already own their own persistence state.\nConfidence: high\nScope-risk: narrow\nTested: cd openless-all/app && npm run build\nTested: git diff --check\nNot-tested: Manual browser interaction against a slow Tauri IPC response.\nRelated: https://github.com/appergb/openless/issues/315\nRelated : https://github.com/appergb/openless/pull/338
2026-05-08 06:47:36 +08:00
Chris233
50cd1ae583
Merge pull request #341 from Cooper-X-Oak/docs/backfill-cooper-developer-entry-beta
...
docs: add Cooper's circular avatar
2026-05-08 06:44:40 +08:00
cooper
e331784b3b
docs: add Cooper's circular avatar image
...
- Add assets/people/cooper.png (circular avatar)
- Replace GitHub API link with local image file
- Match TRIP and Chris233's avatar style
2026-05-08 00:34:48 +08:00
cooper
620e817939
docs: make Cooper avatar circular
...
Add border-radius: 50% to Cooper's avatar to match TRIP and Chris233's circular style.
2026-05-08 00:32:29 +08:00
cooper
dd96d64672
docs: add website URLs back to developer section
...
- Add website URLs below each developer's name
- Increase cell width from 110 to 140 to accommodate URLs
- Keep circular avatars and table layout
2026-05-08 00:27:31 +08:00
cooper
712e9564f2
docs: refine developer section
2026-05-08 00:02:23 +08:00
H-Chris233
b027d8b210
Expose Selection Ask save failures before state drifts
...
Selection Ask reused the same QA hotkey and history persistence paths as other settings pages, but the page did not surface pending, success, or failure states. A rejected hotkey registration or settings write could therefore leave users believing the feature or history option was enabled when the backend had not accepted the change.\n\nThis keeps the existing persistence contract and mirrors the Translation page status treatment: register QA hotkeys before saving prefs, stop on registration errors, refresh after write failures, and add localized feedback for all supported UI languages.\n\nConstraint: Issue #315 requests minimal UI feedback and rollback behavior without refactoring the shared settings queue.\nRejected: Refactor HotkeySettingsContext persistence semantics | broader cross-page behavior change than needed for this bug.\nConfidence: high\nScope-risk: narrow\nTested: cd openless-all/app && npm run build\nTested: git diff --check\nNot-tested: Manual Tauri hotkey conflict flow on macOS/Windows.\nRelated: https://github.com/appergb/openless/issues/315
2026-05-07 23:32:41 +08:00
Chris233
fc24d37fdd
Merge pull request #331 from H-Chris233/fix/issue-313-style-save-feedback
...
修复 Style 页保存失败后的状态回滚
2026-05-07 22:57:22 +08:00
TRIP and baiqing
4962a661a3
chore: bump version to 1.2.24-1 ( #337 )
...
将 Beta 版本号从 1.2.24-beta.1 改为 1.2.24-1。
原因:Tauri MSI bundler 对 prerelease 段的限制比 npm/Cargo semver 严格,
要求 prerelease identifier 必须是「numeric-only and ≤ 65535」。
1.2.24-beta.1(含字母 + 多段)会让 windows MSI 构建直接报:
failed to bundle project: optional pre-release identifier in app version
must be numeric-only and cannot be greater than 65535 for msi target
实测后果:上次 v1.2.24-beta.1-beta-tauri tag 触发的 CI run 25501471895,
macOS arm64 / macOS x64 / Linux 都成功,只有 Windows MSI 卡在 candle 之前。
修复:
- 1.2.24-1 是合法的 npm/Cargo semver prerelease 写法( id="1" 纯数字)
- Tauri MSI 接受这种格式
- semver 排序仍然 1.2.23 < 1.2.24-1 < 1.2.24,符合渠道隔离要求
- 后续 Beta 迭代版本号: 1.2.24-2 / 1.2.24-3 / ...
- Beta 身份不靠版本字面,靠 tag 后缀 `-beta-tauri`(release-tauri.yml 检测)
5 处版本号同步:package.json、package-lock.json (root + nested)、
tauri.conf.json、Cargo.toml、Cargo.lock。CI 用的同一段 verify 通过。
合入后由维护者删旧 tag/release(已删)+ 推 v1.2.24-1-beta-tauri 触发新 CI。
Co-authored-by: baiqing <lbx12309@icloud.com >
2026-05-07 22:40:18 +08:00
TRIP and baiqing
2246d8f660
fix(release): merge duplicate jobs.build.env block in release-tauri.yml ( #335 )
...
PR #330 在 jobs.build 顶层多加了一个 env: 块来定义 OPENLESS_RELEASE_CHANNEL,
但下面 strategy/runs-on 之后已经有一个 env: 块(TAURI_SIGNING_PRIVATE_KEY 等)。
YAML map 不允许重复 key——GitHub Actions 解析直接 fail,整条 release-tauri.yml
工作流变成 "workflow file issue" 启动失败。
修复:把 OPENLESS_RELEASE_CHANNEL 并到下面那个唯一的 env: 块里,保留完整注释。
校验:
- python -c "yaml.safe_load(...)" 解析成功
- jobs.build.env 现在有 3 个 key: OPENLESS_RELEASE_CHANNEL, TAURI_SIGNING_PRIVATE_KEY,
TAURI_SIGNING_PRIVATE_KEY_PASSWORD
Co-authored-by: baiqing <lbx12309@icloud.com >
2026-05-07 22:16:27 +08:00
TRIP and baiqing
f4442e52d9
chore: bump version to 1.2.24-beta.1 ( #334 )
...
Co-authored-by: baiqing <lbx12309@icloud.com >
2026-05-07 22:04:50 +08:00
H-Chris233
f67e906af1
Prevent silent Style preference save failures
...
Style page writes previously updated local UI state before awaiting IPC, so rejected handlers left the page showing values that were never persisted. The change keeps the optimistic update, but centralizes rollback to the previous preferences and surfaces the backend error inline near the failing control.
Constraint: Issue #313 asks for minimal frontend-only handling; backend handlers remain unchanged.\nRejected: Global toast framework | outside the issue scope and heavier than an inline page error.\nConfidence: high\nScope-risk: narrow\nTested: cd openless-all/app && npx tsx src/lib/stylePrefs.test.ts\nTested: cd openless-all/app && npm run build\nTested: git diff --check\nRelated: https://github.com/appergb/openless/issues/313
2026-05-07 22:01:40 +08:00
TRIP and baiqing
fd79a051bd
feat(updater): manual-download Beta opt-in in Settings → About ( #332 )
...
* feat(updater): manual-download Beta opt-in in Settings → About
接入 PR-B-2:客户端「加入 Beta 渠道」开关 + 文档完善。
为什么不走 auto-update 切 endpoint:
tauri-plugin-updater 2.10 的 Builder 不暴露 endpoints() 运行时 API,
endpoints 只能从 tauri.conf.json 编译期读,不能跑时切。继续用 plugin
自带 check 路径意味着 Beta 包会被推到所有用户,违背「Beta 不溢出正式
版」原则。fork plugin 或自实现 update flow ~500 行高风险,本轮采用
最稳路径:plugin 永远只读正式版 manifest(旧文件名),Beta 改成手动
下载——Settings 里展示最新 prerelease 的下载入口,用户点了跳浏览器。
Rust 端:
- types.rs:UpdateChannel enum (Stable | Beta) + UserPreferences::update_channel
字段;UserPreferencesWire / Default / Deserialize 全套兼容(旧 prefs 反序列化
默认 Stable,迁移无痛)。
- commands.rs:三个新命令——
- get_update_channel:读 prefs
- set_update_channel:写 prefs,复用 persist_settings + emit prefs:changed
- fetch_latest_beta_release:reqwest 调 GitHub Releases API,过滤
prerelease=true 且 tag 以 -beta-tauri 结尾的最新一条
- lib.rs:三个命令注册到 invoke_handler
前端:
- ipc.ts:UpdateChannel + LatestBetaRelease 类型 + 三个 invoke 包装
- SettingsModal.tsx:在 AboutMini 末尾加 BetaChannelControl 组件——
Toggle 切换渠道;切到 beta 时拉一次最新 prerelease 信息,展示
「最新 Beta:v1.x.x-beta-tauri」+「前往下载」按钮(openExternal 跳
GitHub release 页面)+ 重新查询按钮。切回 stable 立即清空状态。
- Settings.tsx:把 Toggle 组件 export,让 SettingsModal 复用同一开关样式。
- i18n 五个 locale(zh-CN / zh-TW / en / ja / ko)的 settings.about 都补
齐 betaChannel* 9 个 key(zh-CN 是 source of truth)。
文档:
- README.md / README.zh.md:把 PR-A 的「opt-in,尚未接入」占位换成
manual-download 路径的说明 + tag 约定。
- CLAUDE.md:把「Channel distribution (in progress)」整段重写为已接入
状态,含 tag 约定 / wiring 位置 / 新加的 release verification checklist
(5 条,发版后一定要走一遍,含 stable/beta 双向 cross-check 与 endpoint
采样验证)。
cargo check + npm run build 本地都过。本 PR base = chore/branching-workflow
(PR-A),等 PR-A merge 后会自动指向 beta;与 PR-B-1(CI 端)解耦,可独立
review / merge。
* docs(readme): refresh Status / Architecture / Maintainer release checklist
把 README 的产品/发版描述更新到当前真实状态,配合 PR-A / PR-B 系列工作流改造:
Status (v1.2) 章节:
- 把云端 ASR / 本地 ASR / 润色 provider 三类拆开列;本地 ASR 提及内置
Qwen3-ASR + Windows Foundry Local Whisper 两条线
- 加翻译热键、QA 浮窗、Beta 渠道 opt-in、Homebrew Cask 分发渠道四个要点
- 把「中英双语」改成多语言 UI(实际已支持 zh-CN / zh-TW / en / ja / ko)
Architecture 顶句:
- 由「Auto-updates ride on Tauri updater plugin」改为双渠道描述:Stable
(v<v>-tauri) 自动推送,Beta (v<v>-beta-tauri) 手动下载
Maintainer release checklist:
- 完全重写为双渠道流程:通用准备(5 处版本号 + 冒烟 + secrets)+
Beta 流程(在 beta 分支打 v<v>-beta-tauri tag)+ 正式版流程
(beta→main 后在 main 打 v<v>-tauri tag)
- 把 CI 的 Verify version sync 5-file 校验显式提一次,避免维护者
漏改 Cargo.lock / package-lock 撞 CI 红灯
- 末尾引用 CLAUDE.md 的 5 步 release verification checklist
Roadmap 章节保持原样(多个项目实际状态我无法在不引入回归的前提下
确认,避免误判)。
* docs: drop reference to non-existent bump-version.sh; describe manual bump
---------
Co-authored-by: baiqing <lbx12309@icloud.com >
2026-05-07 22:00:33 +08:00
TRIP and baiqing
ec9584ea0c
feat(release): split updater manifest by channel from tag suffix ( #330 )
...
让 release pipeline 根据 tag 名后缀区分 Beta / 正式版渠道,是 opt-in beta
机制的服务端基础。客户端读取的 endpoint 在 PR-B-2 接入。
约定:
- `v<v>-tauri` = 正式版渠道(stable)
- `v<v>-beta-tauri` = Beta 渠道(beta)
release-tauri.yml 改动:
- 在 jobs.build 顶层加 OPENLESS_RELEASE_CHANNEL job-env,由
endsWith(github.ref_name, '-beta-tauri') 决定;workflow_dispatch 与
非 tag 触发回退为 stable,不改变现有 dispatch 行为。
- Write updater manifest step 把 OPENLESS_RELEASE_CHANNEL 透传给脚本。
- Upload-artifact 的 manifest path 改为 `latest-{tgt}-{arch}*.json` glob,
让 stable 和 beta 两种文件名(或 mirror 变体)都能被收集,避免
if-no-files-found 在 beta release 时挂掉。
- Create release 的 prerelease 改为
`${{ env.OPENLESS_RELEASE_CHANNEL == 'beta' }}`:beta tag 自动标
GitHub prerelease(UI 折叠 + 默认不算 latest),stable 保持不变。
write-updater-manifest.mjs 改动:
- 读取 OPENLESS_RELEASE_CHANNEL(默认 stable);非 stable / beta 时
fail-fast,避免拼错 env 静默走错路径。
- beta 渠道时 manifest 文件名加 `-beta` 后缀:
latest-{tgt}-{arch}-beta.json
latest-{tgt}-{arch}-beta-mirror.json
正式版用户的 endpoint 永远指向不带后缀的旧文件名,不会被 beta release 覆盖。
向后兼容:
- 现有 v1.2.23-tauri 等 stable release 行为完全不变(文件名、prerelease=false 沿用旧逻辑)。
- workflow_dispatch 不打 tag 时也照常构建 + 不上传 release。
发版后 checklist(PR-B-2 文档段会写进 CLAUDE.md):
- 推 v<v>-beta-tauri tag → GitHub Release 应标 prerelease 且只含
latest-*-beta.json
- 推 v<v>-tauri tag → 正式 release 且只含 latest-*.json(不带后缀)
Co-authored-by: baiqing <lbx12309@icloud.com >
2026-05-07 22:00:08 +08:00
TRIP and baiqing
2fe5f2e0d4
docs: introduce Beta/Stable two-channel branching workflow ( #327 )
...
把分支模型从「dev/main」改造为「Beta/正式版」双渠道,渠道语义直接由分支名承载:
- `beta` — Beta 渠道(开发版),默认分支、集成缓冲区,所有 PR 一律打到这里。
Beta 包不推送给普通用户,只面向主动加入 Beta 渠道的用户。
- `main` — 正式版渠道(Stable),始终可发布。仅由维护者在双端冒烟测试通过后从
beta 合入;普通用户默认拿到的就是这条线。
文档与 CI 改动:
- README.md / README.zh.md:把「Contributing workflow / 贡献流程」章节
改写为 Beta/正式版双渠道,含分支流向 ASCII 图、6 条核心规则,
以及「Beta 不溢出正式版」的隔离要求。
- CLAUDE.md:把 `### Branch & contribution workflow` 改名为
`### Branch & release-channel workflow`,明确「分支名=渠道名」。
- ci.yml:触发分支从 [main, dev] 改为 [main, beta]。
仓库侧已同步:
- 远端 dev 分支已重命名为 beta(git push origin dev:beta + 删除旧 dev)
- GitHub 仓库默认分支已切到 beta
- release-tauri.yml 用 v*-tauri tag 触发,跟分支无关,发版机制不受影响
Beta 包的 opt-in 分发机制(per-channel updater endpoint + 设置页开关)
将在独立 PR 中接入。在它落地之前,所有 v*-tauri tag 都按正式版对待,
不要从 beta 分支直接打 tag。
Co-authored-by: baiqing <lbx12309@icloud.com >
2026-05-07 21:55:31 +08:00
Chris233
0b8c7b4529
Merge pull request #328 from H-Chris233/fix/issue-314-translation-save-feedback
...
修复翻译页保存失败无提示
2026-05-07 21:19:31 +08:00
H-Chris233
829fea3c14
Expose translation save failures before users trust stale settings
...
Translation settings were optimistically updated without awaiting or surfacing write failures, so users could see enabled language and shortcut states that were never persisted. This keeps the fix inside the Translation page: all three save paths now show saving/saved/failed feedback, catch failed writes, and refresh preferences after failed preference persistence to roll back local state.
Constraint: Follow the new beta-first PR workflow from #327 .
Rejected: Refactor HotkeySettingsContext queue behavior | issue explicitly excludes queue redesign.
Confidence: high
Scope-risk: narrow
Directive: Keep Translation page save feedback local unless SelectionAsk receives the same treatment in its own issue.
Tested: npm run build
Tested: git diff --check
Related: #314
2026-05-07 20:58:08 +08:00
TRIP and baiqing
8c0611ff5f
fix(homebrew): bump cask to 1.2.23 and drop broken Formula ( #326 )
...
- 把 Casks/openless.rb 从 1.2.22 升到 1.2.23 + 对应 SHA256,
避免新用户首装拿到旧版(依赖 release 后 update-cask.yml 自动维护,
但合并 PR322 时 cask 已落后一版,先手工补齐)。
- 删除 Formula/openless.rb:作者在 PR322 中已说明 Tauri DMG bundler
在 Homebrew 沙盒构建里 hdiutil/create-dmg 受限会失败;保留会让
误用 `brew install openless`(不带 --cask)的用户卡住。
Co-authored-by: baiqing <lbx12309@icloud.com >
2026-05-07 20:40:17 +08:00
kiruno
9543a5a86f
feat: Add Homebrew Cask install option ( #322 )
...
Adds Casks/openless.rb + update-cask.yml workflow + README install docs. Closes #254 #5 .
Note: Formula/openless.rb known to fail in Homebrew sandbox (per author); will be removed in follow-up. Cask version will be bumped to current 1.2.23 in same follow-up.
2026-05-07 20:32:35 +08:00
Hongbro886
32133b3438
关于模型设置-模型下载进度条的若干修复 ( #293 )
...
* fix: Various fixes for the model settings - model download progress bar
* fix(local-asr): log partial-size errors and seed redownload progress
2026-05-07 20:21:51 +08:00
Chris233
813d9ec2e9
Merge pull request #324 from H-Chris233/fix/issue-323-llm-providers
...
增加更多 LLM Provider 预设
2026-05-07 20:19:31 +08:00
Chris233
93dc4b38cd
Merge pull request #325 from Cooper-X-Oak/docs/backfill-cooper-developer-entry
...
docs: backfill developer entry
2026-05-07 20:19:06 +08:00
cooper
188efa7e68
docs: add Cooper to developer list
2026-05-07 19:59:38 +08:00
H-Chris233
336ac9a11c
Broaden low-cost LLM provider choices
...
Issue #323 asks for easier switching to more OpenAI-compatible LLM providers instead of forcing users through a single custom slot. The settings page already stores credentials per active LLM provider id, so the narrow fix is to add practical presets and make provider switching seed the model field only when it is empty.
Constraint: Keep provider storage in the existing system credential vault schema
Rejected: Build named custom profile management | larger UI and persistence surface than needed for the first issue slice
Confidence: medium
Scope-risk: narrow
Directive: New LLM preset ids rely on the generic active LLM credential routing; do not special-case them in backend code unless the provider stops being OpenAI-compatible
Tested: npm run build
Tested: git diff --check
Related: #323
2026-05-07 19:29:51 +08:00
baiqing
5491ef5d33
chore(release): 1.2.23
...
Hotfix for 1.2.22:
- fix(recorder): macOS 麦克风停录后未释放(cpal Stream::drop 不调
AudioOutputUnitStop,显式 pause 强制停 callback)
- fix(coord): 录音条等真实开始捕获 PCM 后再弹出,避免短录音漏首字
- fix(localasr): macOS 不再渲染 Windows 专属的 Foundry Local Whisper 卡片
- fix(hotkey): macOS 上 Alt/Meta 键标签显示 Option/Cmd 而非 Alt/Win
2026-05-07 16:57:18 +08:00
baiqing
b1a07a4c66
fix(hotkey): macOS 上 Alt/Meta 键标签显示 Option/Cmd 而非 Alt/Win
...
QA 浮窗(划词追问触发的浮窗)一直在显示 "右Alt" 而不是用户实际按的
"右 Option"。
根因:QaPanel.tsx 用 getHotkeyBindingLabel(prefs.hotkey) → 走
getHotkeyBindingCodes → 把 trigger="rightOption" 通过 legacyTriggerCode
映射成键盘事件 code "AltRight" → getHotkeyCodeLabel("AltRight")
**平台无关**地返回 "右Alt" / "Right Alt"。
而 SelectionAsk 页用 formatComboLabel(prefs.dictationHotkey)(primary
"RightOption" → formatPrimary 走 "rightoption" 分支 → "Right ⌥"),
所以 SelectionAsk 页面是对的,QA 浮窗就显示成了 "Alt"。
同样的平台错配在 MetaLeft / MetaRight:之前一律显示 "Win",macOS
应该是 "Cmd / ⌘";OSLeft / OSRight 同样修复。
修复 getHotkeyCodeLabel:按 currentPlatform().isMac 分流:
- AltLeft → macOS "左 ⌥" / "Left ⌥",其它 "左Alt" / "Left Alt"
- AltRight → macOS "右 ⌥" / "Right ⌥",其它 "右Alt" / "Right Alt"
- MetaLeft / OSLeft → macOS "左 ⌘" / "Left ⌘"
- MetaRight / OSRight → macOS "右 ⌘" / "Right ⌘"
- Linux/Windows 路径不变
⌥/⌘ 与 formatPrimary 的输出格式一致,全 app hotkey 标签平台一致。
2026-05-07 16:33:29 +08:00
baiqing
2edaf522cd
fix(recorder): 显式 pause cpal Stream 再 drop,释放 macOS 麦克风占用
...
用户报告:按热键结束录音后麦克风没有被释放(橙色 mic 指示灯不灭、
process_callback 持续打 cb# 日志直到进程退出)。
根因:cpal 0.15 的 Stream::drop 在 macOS coreaudio backend 上不会同步
调 AudioOutputUnitStop,只走 dispose。AudioUnit 的 render callback 仍被
系统调用,process_callback 以 ~5 ms / 帧的速率继续推到 consumer 和
level_handler;macOS 一直认为我们在用 mic,橙点不灭,其它 app 无法独占。
实测日志:
T=01.786 hotkey 按下 → end_session 启动
T=02.699 audio-mute released by dictation (rec.stop() 已返回 = 音频
线程已 join 完成 = drop(stream)
已执行)
T=02.720 cb#750 ← 仍在跑
T=03.110 cb#800
…
T=09.510 cb#2000 ← 直到用户杀进程才停
修复:在 drop(stream) 之前显式 stream.pause()。
StreamTrait::pause 在 cpal coreaudio backend 里直接调 AudioOutputUnitStop,
同步终止 callback;之后 drop 处理 dispose / 资源释放。pause 失败时仅打
warn,不阻塞 drop 路径。
同时在 drop 之后加一行 "cpal Stream dropped (mic released)" 日志,
让以后排查这类问题能直接看到 drop 是否真的执行。
2026-05-07 16:33:12 +08:00
baiqing
43f95a0888
fix(coord): 录音条等真实开始捕获 PCM 后再弹出,避免短录音漏首字
...
用户报告:说比较短的语音时,开头第一个字偶尔被吞。
根因:start_recorder_for_starting 在 Recorder::start 返回 Ok 后立即 emit
Recording capsule。但 Recorder::start Ok 仅表示 cpal Stream::play 完成,
audio 线程还没开始向 consumer 推 PCM —— macOS CoreAudio AudioUnit 启动到
第一帧 process_callback 中间有 50–200 ms 间隙(Windows 类似)。
用户看到录音条立刻开口,前几个字落在 cpal init 窗口里被吞。
修复:删掉 Recorder::start Ok 之后的立即 Recording emit。
Recording capsule 改由 level_handler 的第一次触发来 emit ——
recorder.rs::process_callback 的执行顺序是 consume_pcm_chunk → level_handler,
所以 level_handler 第一次执行 == PCM 已经真实流到 consumer,从这一刻起
用户说什么都被录到。capsule 自然晚 50–200 ms 出现,但出现 ==
mic 真的在录,匹配「麦先录、UI 再弹」的预期。
短按 / cancel 路径不动:
- stop_recorder_if_pending_start_stop 仍然在 cpal init 期间松键时把 capsule
切 Transcribing(recorder 已 stop,level_handler 不会再触发)。
- level_handler 内部 phase != {Starting, Listening} 时直接 return,
不会在 Cancelled / Error 状态上盖回 Recording。
2026-05-07 15:23:38 +08:00