Commit Graph
100 Commits
Author SHA1 Message Date
TRIP 1b6f0dffe1 Merge pull request #605 from Ltdaaa/view
feat: introduce a theme-aware frosted-glass visual system for OpenLess frontend
2026-06-07 12:45:11 +08:00
TRIP 5ca3a80cd6 Merge pull request #581 from aeoform/chore/linux-plugin-check-and-ux-cleanup
chore(linux): plugin 路径检查增强 + 去除启动加载界面 + 修正快捷键描述
2026-06-05 08:02:14 +08:00
TRIP 30cf33e2b1 Merge pull request #589 from Open-Less/docs/readme-real-example
docs(readme): 实际效果示例换成客户回邮件用例(中英双语)
2026-06-04 19:33:36 +08:00
TRIP 2067367d83 Merge pull request #588 from ig505gi/fix-beta-reset
fix(macos): Beta 更新重启前重置 TCC 权限记录
2026-06-04 19:29:46 +08:00
TRIP 61f5416c73 Merge pull request #587 from Open-Less/docs/discord-community-link
docs(readme): 顶部加入 Discord 社区入口
2026-06-04 19:00:47 +08:00
TRIP 7cc8175d98 Merge pull request #586 from Open-Less/ci/auto-update-homebrew-cask
ci(release): 正式发版自动更新 Homebrew cask(+ 补 cask 到 1.3.5)
2026-06-04 13:08:27 +08:00
TRIP 13a0a03ab3 Merge pull request #584 from Open-Less/fix/audio-cue-missing
fix(audioCue): 修复快速录音时录音提示音偶尔丢失
2026-06-04 13:08:19 +08:00
TRIP 85d6ed851f Merge pull request #583 from Open-Less/fix/polish-context-isolation
fix(polish): 对话感知上下文——按风格包隔离 + 翻译会话参与润色(不串味)
2026-06-04 13:08:09 +08:00
TRIP 4bc94ecc5e Merge pull request #560 from Open-Less/feat/audio-cue-on-record
feat(capsule): 录音开始播放合成提示音 + 录音与输入设置开关
2026-05-31 14:01:35 +08:00
TRIP 1b8217eddb Merge pull request #521 from Open-Less/feat/settings-revamp
feat(settings): 设置界面重构 — 分节化 / 精简 / Beta 渠道 / 麦克风 / 市场登录
2026-05-22 23:33:46 +08:00
TRIP 32d7c22a8c Merge pull request #519 from Open-Less/feat/frosted-glass-overlays
feat(ui): 划词追问框与语音胶囊磨砂玻璃质感
2026-05-21 23:54:01 +08:00
TRIP 370eaa858e Merge pull request #506 from Open-Less/pr2/ui-i18n-refresh
refactor(ui): 精简多语言文案 + 设置/风格/市场界面与图标重构
2026-05-20 16:31:31 +08:00
TRIP e8f8ac0f59 Merge pull request #505 from Open-Less/fix/saved-toast-position
fix(ui): 统一 SavedToast 弹框位置与进出方向
2026-05-20 16:29:09 +08:00
TRIPandbaiqing a1d9549db2 fix(startup): autostart catch 块缺 return 导致复活主窗口 (#468) (#488)
* fix(startup): autostart catch 块缺 return 导致复活主窗口 (#468)

PR #474 引入的"前端兜底"useEffect 在 webview ready 后读
prefs.startMinimized,true 则跳过 currentWindow.show()。但 catch 块只
console.warn 没有 return,控制流继续走到 show()——等于"prefs 读失败 = 强 show"。
autostart 早期 IPC 抖动时 getSettings() 偶发失败,旧逻辑让用户明明开了
静默启动仍把主窗口弹出来。

修复: catch 块加 return,安全侧默认 = 当作 startMinimized=true 保持隐藏。
宁可让用户从 tray 手动唤起,也不要在 autostart 抖动时强 show 一个白色
/ 透明主窗口。

LSUIElement 不在本 PR 范围: 那是全局 macOS UX 决策,会影响 Finder
双击 / Spotlight / Launchpad 启动场景,应单独 PR + 单独评审。
正确做法是 Rust 端动态切 activationPolicy 而非 plist 静态写死。

Refs #468

* docs(startup): clarify catch rationale + improve warn diagnostics

Both local review and PR-Agent bot flagged the "fallback when settings
read fails" semantic reversal. Re-analysis confirms the change is
correct, but the rationale needs to be explicit in the code:

- Rust `get_settings` signature is `pub fn -> UserPreferences` (not
  Result), so this catch is unreachable from a normal first-launch
  scenario (returns defaults). Only Tauri IPC infrastructure failures
  (e.g. __TAURI_INTERNALS__ not ready during autostart early-mount)
  reach this branch.
- Tray is registered by Rust setup() before webview ready, so it remains
  the universal fallback entry point — no "user locked out" risk.
- Improved console.warn to include err.message for diagnostics, since
  raw err objects serialize as [object Object] in Tauri devtools.

No behavior change; comment + log improvement only.

Refs review of #488

---------

Co-authored-by: baiqing <lbx12309@icloud.com>
2026-05-18 19:00:28 +08:00
TRIPandbaiqing 6e55e3059b fix(windows): capsule/qa 透明窗口 Acrylic 兜底 (#487)
* fix(windows): capsule/qa 透明窗口 acrylic 兜底 + CSS 可读性兜底

根因: tauri.conf.json 三个窗口都 transparent: true,但 lib.rs 只对 main
窗口调用 apply_mica。capsule / qa 在 Windows 上是裸 transparent,
Webview2 的 backdrop-filter 只能模糊页面内部 DOM 不能模糊 OS 桌面,
导致用户看到的胶囊 / QA 浮窗几乎完全透出桌面壁纸。

macOS 上 NSVisualEffectView (HudWindow vibrancy) 直接由系统合成器
混合桌面,所以 macOS 不需要额外处理。

修复:
1. lib.rs 在 setup 时对 capsule / qa webview 调用 apply_acrylic(
   color=(30,32,38,140)) — 选 Acrylic 而非 Mica 因为 Acrylic 支持
   Win10,更适合小浮窗。失败仅 log::warn 不 panic。
2. global.css 末尾追加 @supports not (backdrop-filter) 和
   @media (prefers-reduced-transparency: reduce) 兜底,给 html/body
   设实色背景。组件层 inline style 无法被覆盖,这只是二级防护。

Note: Capsule.tsx 和 FloatingShell.tsx 的 inline alpha 保留不动;
Acrylic 兜底通常足够。如果 Win10 实测仍偏透(Mica 不支持 Win10),
后续 PR 可考虑给 main 也加 Acrylic 作为 Mica fallback。

* style(windows): drop global html/body fallback rules from acrylic PR

Local code review flagged that `html, body { background: ... }` applies
to ALL windows including `main`, which would clobber the macOS
NSVisualEffectView vibrancy in `prefers-reduced-transparency: reduce`
mode.

Re-analysis confirms both rules are speculative:
- @supports never fires on modern Webview2 / WKWebView / Chromium
  (all support backdrop-filter)
- prefers-reduced-transparency triggers OS-level vibrancy neutralization
  on macOS already, but the CSS rule would still wrongly land on main

Acrylic in lib.rs is the canonical fix for the user-reported P0
(capsule/qa 透明遮罩看不清). The CSS layer is dropped to keep the PR
scoped to a single concern and avoid the cross-window collateral.

Refs review of #487

---------

Co-authored-by: baiqing <lbx12309@icloud.com>
2026-05-18 18:57:43 +08:00
TRIPandbaiqing f30a787227 fix(windows): hotkey toggle 二次按键失效 - 串行化 Pressed/Released edge (#489)
根因: hotkey_bridge_loop 和 combo_hotkey_bridge_loop 把每条 Pressed/Released
边沿独立 async_runtime::spawn 成两个 task,被 tokio work-stealing 调度器
并行执行。Windows WH_KEYBOARD_LL 边沿间隔微秒级,Released task 经常先
swap(true→false),导致下一次 Pressed 看到 was_held=true → toggle 被静默吞掉。

macOS 上 CGEventTap 边沿间隔几十毫秒,race window 极窄,肉眼不可见,
所以仅 Windows 必现。ESC 走 HotkeyEvent::Cancelled 不经过 latch,
是用户唯一能逃出 stuck-latch 的路径。

修复: 用 tauri::async_runtime::block_on 顺序 await 两个 handler,
bridge 线程的 mpsc::Receiver::recv() FIFO 保证 swap 顺序正确。
bridge 在独立 OS 线程而非 tokio worker,block_on 安全。

代价: Hold 模式短按 stop 多 ≤500ms 延迟,被 request_stop_during_starting
覆盖,可接受。qa / translation bridge 不走 handle_*_edge 不受影响。

附带清理 coordinator.rs unreachable_code warning。

Refs #468

Co-authored-by: baiqing <lbx12309@icloud.com>
2026-05-18 18:54:53 +08:00
TRIPandbaiqing b4e806e3ca fix(windows): 静默启动 #468 最后一公里 - 前端兜底 (#474)
* fix(windows): 修复 #466 #468 #469 IME 切回 / 静默启动 / QA 焦点 + #470 capsule 诊断 (#471)

集中修复 4 个 Windows-only bug:

- #469 IME 切不回 — restore_profile 对称补 legacy TSF ChangeCurrentLanguage + ActivateLanguageProfile,回归点是 c3b59329 升级激活路径时漏改恢复路径。现代 ActivateProfile 失败降为 warn(legacy 已完成视觉切回)。
- #468 Win11 静默启动失效 — single-instance 回调读 start_minimized,true 时跳过 show_main_window;托盘 / CLI intent 路径不受影响。
- #466 QA 弹窗 X/ESC 关不掉 — Win 上从 SW_SHOWNOACTIVATE 切到 Tauri window.show + set_focus,让 webview 拿键盘焦点。配套 focus-dance:begin_qa_session 抓选区前临时把焦点还给用户原 app,抓完再 refocus QA;多轮场景下用 GetWindowThreadProcessId 区分本进程窗口,刷新 qa_focus_target,避免 stale HWND。
- #470 录音胶囊不显示 — 加 per-process 一次性诊断 log(CapsuleState 用 capsule_state_log_name 显式短名,不走 Debug 防字段扩展时泄露),下次能直接定位 prefs / state / Win32 哪一环失败。

pr_agent 五轮迭代到 No major issues + No security concerns;Cloud 评审建议的 begin_qa_session 双锁 TOCTOU 已合并为单 scope。三平台 build + pr_agent CI 全绿。

#470 没标 closes,是 refs—需要真机日志才能定位真因,先 ship 诊断手段。

* chore(release): bump version to 1.3.4-3 (Beta) — Windows #466 #468 #469 修复

* fix(settings-ui): 权限页徽章不换行 + 本地 ASR 区块 Win 灰显

权限页 (Settings → 快捷键 → 权限):
- Pill 组件加 whiteSpace:nowrap + flexShrink:0 → "已安装"徽章在 Win
  窄宽下不再被挤成 3 行(每个汉字一行)。
- 行内 message span 加 whiteSpace:nowrap + overflow:hidden +
  textOverflow:ellipsis + minWidth:0 → 长文本("Windows 低层键盘 hook
  已安装")超宽时省略号收尾,徽章保持完整。
- zh-CN 描述大段精简:descAcc / descNoAcc / micDesc / accDesc /
  hotkeyDesc / networkDesc / windowsImeDesc / windowsIme.* 都改短,
  减少 Win 上拥挤感(用户反馈 zh 文案过长)。

高级页 (Settings → 高级 → 本地 ASR):
- Windows 上把"本地 ASR 模型(实验性)"标题区 + 警告小字 + Qwen3 行
  整组 opacity:0.45 灰显 —— Qwen3 在 Win 是 stub 不支持,那条"实验性"
  主线在 Win 没意义;用户视觉关注点应落到下方独立的 Foundry 行(Foundry
  保持正常颜色)。Toggle 的 disabled 行为已经在原 onToggle 条件里,本次
  纯加视觉灰显。

* fix(windows): 前端 App.tsx 在 show 主窗口前读 startMinimized 兜底

#471 在 Rust 端 setup() / single-instance callback 都加了 start_minimized
suppression,本机日志确认 Rust 路径完全正确("[main] start_minimized=true
→ 跳过初始 show")。但用户在 Win11 1.3.4-3 上仍然「重启进桌面那一刻主
窗口就出来了」。

定位到最后一条遗漏路径在前端:App.tsx 里 mount 时一条 useEffect 无条件
通过 IPC 调 `getCurrentWindow().show()`,把 Rust 端已经 suppress 的窗口
又拉出来 —— 这条路径 Rust log 完全看不见(走的是 plugin-window 的
showWindow IPC,不经过我们的 show_main_window helper)。

那条 useEffect 当初是给 issue #163 引入的:Windows 权限探测可能死锁让
首屏卡灰,所以前端兜底 force show 一次。

修法:show 前先读 prefs.startMinimized,true 就 return。读 prefs 失败
(极少数情况)保持原 show 行为,不让 #163 的兜底语义丢失。

closes #468 (the actual one this time, Rust + JS 双端都收敛了)

---------

Co-authored-by: baiqing <lbx12309@icloud.com>
2026-05-18 15:29:33 +08:00
TRIP ceda87f64f fix(settings-ui): 权限页徽章不换行 + 本地 ASR 区块 Win 灰显 (#473)
Windows 真机反馈的两条 UI 问题,纯 frontend:

- 权限页徽章 3 行 wrap — Pill 加 whiteSpace:nowrap + flexShrink:0;message span 加 ellipsis 五件套;zh-CN 描述大段精简
- 本地 ASR 区块 Win 灰显不完整 — Win 上标题区 + 警告 + Qwen3 行整组 opacity:0.45,Foundry 保持正常色

cherry-pick from main (be24bb7),PR base=beta 保持双线同步。pr_agent 一轮 clean、Claude LGTM、三平台 CI 全绿。
2026-05-18 13:35:20 +08:00
TRIP c4bbec9cef Merge pull request #451 from aeoform/beta
feat(linux): fcitx5 plugin for Wayland input + deb packaging
2026-05-18 09:07:13 +08:00
TRIPandbaiqing fe56eb3038 fix(ci): detach HEAD before claude-code-action on fork PR (#464)
PR #451 (来自 fork aeoform/openless, head ref 也叫 beta) 触发 @claude 时,
claude-code-action 执行 `git fetch origin pull/451/head:beta` 把 PR head
拉到本地 beta 分支,但此时 worktree 正 checkout 在 refs/heads/beta,
git 拒绝 fetch 到已 checkout 的分支:

    fatal: refusing to fetch into branch 'refs/heads/beta' checked out at ...
    Action failed with error: Command failed: git fetch origin --depth=20 pull/451/head:beta

修复: 在 actions/checkout 后立即 git checkout --detach,释放
refs/heads/<default-branch> 这个本地分支名,让 action 自己 fetch 不撞名.
仅在 fork head ref == base ref name 的 corner case 才会触发问题,
detach 对其他场景无副作用.

失败 run: https://github.com/Open-Less/openless/actions/runs/25978532215

Co-authored-by: baiqing <lbx12309@icloud.com>
2026-05-17 14:09:58 +08:00
TRIPandbaiqing 859fd5370f feat(updater): Beta 渠道接通 auto-update(Builder.endpoints runtime 切换) (#465)
* feat(updater): Beta 渠道接通 auto-update(Builder.endpoints runtime 切换)

CLAUDE.md 之前的论断「tauri-plugin-updater 2.10 Builder 不暴露 endpoints()」在
2.10.1 已不成立 —— Builder 现在有 .endpoints(Vec<Url>) -> Result<Self>,build()
里 self.endpoints.unwrap_or_else(|| self.config.endpoints.clone()) 走 runtime 覆盖。

实现 channel-aware 自动更新(不 fork plugin、不写自定义下载/签名校验):

Rust(~100 行新增)
- commands.rs::app_check_update_with_channel:
  Stable → webview.updater_builder().build()?.check()(默认 endpoints 从 tauri.conf)
  Beta   → fetch_latest_beta_release() → 拼镜像 + 直连两份 -beta manifest URL
         → builder.endpoints(urls)?.build()?.check()
- AppUpdateMetadata 字段形状对齐 plugin 的 UpdateMetadata(rid + currentVersion
  + version + body + rawJson),前端 new Update(metadata) 即可复用 plugin 的
  download / install / close。
- lib.rs 注册新 command。

前端(~30 行改动)
- AutoUpdate.tsx::checkForUpdates 从 import('@tauri-apps/plugin-updater').check
  改为 invoke('app_check_update_with_channel');其余 download / install / close
  全走 plugin 原生 JS API(new Update(metadata) 后链式调用)。
- SettingsModal::BetaChannelControl 删掉「打开 GitHub 下载」按钮 ——
  auto-update 接管了,手动下载路径冗余;保留「最新 Beta 版本号 + 刷新」做信息透明。
- i18n 5 个 locale 改 betaChannelDesc 文案,把「需要手动下载安装」改成
  「自动接收最新 Beta 版本」。betaChannelDownloadBtn 等 i18n key 暂留(无用但无害)。

物理隔离仍生效:Beta tag 的 manifest 文件名带 -beta 后缀,跟 Stable 的
latest-{tgt}-{arch}.json 在 GitHub Release assets 里是分开两份;即使代码逻辑
错把 Beta URL 给 Stable 用户也是 HTTP 404,不会拿到错档。

验证:cargo test --lib serial 270 / 0 / 0;tsc clean;vite build clean。

* feat(settings): BetaChannelControl 加版本比对 + 「立即更新」按钮

之前 BetaChannelControl 在 toggle 打开后,只机械显示「最新 Beta: vX-beta-tauri」,
不管本机版本是不是已经等于那个 tag。用户报告:「显示有最新 Beta,但应该比对版本号」。

修复:
- parseVersionFromBetaTag("v1.3.4-1-beta-tauri") → "1.3.4-1"
- semverGreater(remote, local) 做 SemVer 风格比对(支持 X.Y.Z 和 X.Y.Z-N,覆盖
  本仓库实际版本格式;prerelease 按 SemVer 规则 lower than no-prerelease)
- 本机 >= 最新 → 「最新 Beta: vX」 + 蓝色「已是最新」chip
- 本机 < 最新 → 「最新 Beta: vX」 + 「立即更新」按钮(蓝色高亮)
- 「立即更新」点击 → useAutoUpdate.checkForUpdates → 走和全局「检查更新」按钮
  完全相同的 download/install 流程,UpdateDialog 直接挂在 BetaChannelControl 内部
- 用户的第二个 feature 诉求:「在 Beta 渠道旁边一键检查 + 自动更新」 = 这个按钮

i18n 5 个 locale 加 4 个 key:betaChannelUpToDate / betaChannelUpdateNow /
betaChannelUpdateNowTitle / betaChannelChecking。

* chore(release): bump 1.3.4-1 → 1.3.4-2 for Beta channel

`-2` 后缀让 appVersion.ts 的 IS_BETA_BUILD 仍判为 Beta,UI BETA 标签照常显示。
配本 PR 的 Beta auto-update + BetaChannelControl「立即更新」按钮,1.3.4-1 用户
切到 Beta 渠道后点检查会发现 1.3.4-2 并走 auto-update 流程一键升上来。

发布 tag:v1.3.4-2-beta-tauri。

---------

Co-authored-by: baiqing <lbx12309@icloud.com>
2026-05-17 11:15:06 +08:00
TRIPandbaiqing 061558a0fa fix(shell): sidebar 底部翻回两行(版本在上、设置在下) (#463)
PR #462 把底部折成一行,结果在窄 sidebar 下「设置」被挤成两行
竖字、版本 chip 糊在旁边,可读性大幅退化。改回两行但反过来:
- 上行:[BETA] 版本 v1.3.4-1(容下 BETA 标 + 完整版本号)
- 下行:⚙ 设置(真正落到最底,符合最初诉求)

设置按钮恢复 display:flex + label flex:1 跟上面 nav 按钮统一风格。

Co-authored-by: baiqing <lbx12309@icloud.com>
2026-05-17 10:27:41 +08:00
TRIPandbaiqing 0ecb3bd5a4 chore: bump 1.3.4-1 + sidebar 底部行折成单行 (#462)
* feat(shell): sidebar 底部行折成单行(设置 + 版本)

把 sidebar 底部「⚙ 设置」和「版本 v1.3.x」从上下两行折成一行:
- 设置按钮 display 由 flex 改成 inline-flex,去掉 label 的 flex:1
  (不再撑满 sidebar 宽度)
- 版本 chip(含 BETA 标)用 marginLeft: auto 推到行尾

外观更紧凑、纵向更省、视觉重心从两行散开收到一行。

* chore(release): bump version 1.3.3 → 1.3.4-1 for Beta channel

`-1` 后缀让 appVersion.ts 的 IS_BETA_BUILD 自动判为 Beta,UI 上的
BETA 标签会自动恢复显示。bump-version.sh 不收带 dash 的 prerelease
格式,所以这次手动同步 5 个文件(package.json / package-lock.json
root + nested / tauri.conf.json / Cargo.toml / Cargo.lock)。

发布 tag 仍走 v<v>-beta-tauri:v1.3.4-1-beta-tauri。

---------

Co-authored-by: baiqing <lbx12309@icloud.com>
2026-05-17 10:18:21 +08:00
TRIPandbaiqing f47c2e4a29 docs(windows): 记录独占全屏游戏下 capsule overlay / hotkey 的 OS 级限制 (#457) (#458)
- coordinator.rs: 在 show_capsule_window_no_activate (Windows 路径) 上方加注释,
  说明 SetWindowPos(HWND_TOPMOST) 在独占全屏 DirectX/OpenGL 应用之上失效,以及
  UIPI 阻挡 hotkey 的同源现象,避免后续维护者再次尝试用户空间 workaround。
- USAGE.md: 给最终用户加 FAQ —— Minecraft 等全屏游戏下应切换到 borderless
  windowed fullscreen,对齐管理员权限,预先打开游戏聊天框。

此次仅文档与注释改动,不修改运行时行为。修复独占全屏 overlay 需要 DirectX
overlay 注入(Discord / MSI Afterburner 路线),工程量与风险都不在 surgical
修复范围。

Co-authored-by: baiqing <lbx12309@icloud.com>
2026-05-17 09:59:30 +08:00
TRIP 98dd4c7e47 chore(ci): 允许 Open-Less 组织成员触发 @claude (#461)
After repo transfer to Open-Less org, author_association == 'OWNER' no longer matches any human user on org repos. Expand the gate to [OWNER, MEMBER] so any org member can trigger @claude. No other changes.
2026-05-17 09:54:26 +08:00
TRIPandbaiqing ecf2b0f6c2 feat: style pack i18n + 差量缓存 + 内置三包升级到 v2 (#460)
* docs(windows): 记录独占全屏游戏下 capsule overlay / hotkey 的 OS 级限制 (#457)

- coordinator.rs: 在 show_capsule_window_no_activate (Windows 路径) 上方加注释,
  说明 SetWindowPos(HWND_TOPMOST) 在独占全屏 DirectX/OpenGL 应用之上失效,以及
  UIPI 阻挡 hotkey 的同源现象,避免后续维护者再次尝试用户空间 workaround。
- USAGE.md: 给最终用户加 FAQ —— Minecraft 等全屏游戏下应切换到 borderless
  windowed fullscreen,对齐管理员权限,预先打开游戏聊天框。

此次仅文档与注释改动,不修改运行时行为。修复独占全屏 overlay 需要 DirectX
overlay 注入(Discord / MSI Afterburner 路线),工程量与风险都不在 surgical
修复范围。

* feat: style pack i18n + 差量缓存 + 内置三包升级到 v2

i18n(5 个 locale 全覆盖)
- 新增 style.pack.* 命名空间(~70 keys)覆盖 Style 页面所有 UI 文案。
- 扩展 marketplace.* 命名空间(~50 keys,含 myPacks/upload/oauth/state/modal/derivativeBadge/sortLiked/likedEmpty 等)。
- ja.ts / ko.ts 原本完全缺失 marketplace.* —— JP/KR 用户进风格市场会 fallback 看见中文,本次补齐。
- Style.tsx 删除 100+ 行 `isEnglish ? '...' : '...'` 三元 copy 对象,全走 t()。
- Marketplace.tsx + MarketplaceModal.tsx 把硬编码 '我赞过的'/'我的发布'/'确定上传'/'已下架'/'撤回发布'
  /'审核中'/'已上架'/'未通过'/'未登录'/'衍生自' 等 30+ 处全走 t(),含 statusLabel(state, t) 重构。

差量缓存(src/lib/ipc.ts + Marketplace.tsx)
- list cache 从 sessionStorage 升到 localStorage,跨重启秒呈现,TTL 24h。
- 新增 detail cache:按 (id, version, updatedAt) 三元组缓存 marketplace_detail;
  三元组等价于内容版本签名,version/updatedAt 任一变化即失效重拉。命中 = 0 请求。
- 自动驱逐:写入 list cache 时根据当前三元组集合 prune 不在列表里的 detail 条目
  —— 满足「云端哈希被移除时本机也移除」的语义。
- 安全审计:
  - 包 ID 必须匹配 UUID v4 正则(防 key 注入)。
  - detail.id 必须等于请求 packId(防服务端返回错位内容)。
  - 单条 prompt 长度上限 200KB(防 OOM)。
  - detail 条目上限 64,LRU 淘汰(防 localStorage 配额耗尽)。
- openDetail 先查本机三元组缓存,命中即渲染 + 跳过网络;未命中再走 IPC + 校验回写。

内置 style pack 升级到 v2.0(types.rs + polish.rs tests)
- 「轻度润色」「清晰结构」「正式表达」三个 builtin pack 整体替换为社区维护的 PRO v2 prompt
  (含独立角色 + {{HOTWORDS}} + 七/八节中文序号骨架 + 高密度示例 + ASR 纠错词表)。
- default_style_system_prompt_for_mode 对这三个 mode 早返回各自的 BUILTIN_PROMPT,
  跳过标准 ROLE_BLOCK + COMMON_RULES + OUTPUT_BLOCK wrapper,避免与 v2 内同名段落重复。
- StylePack literal 同步:name 保留中文短名(去掉 PRO 后缀),version → 2.0.0,
  description/examples/tags 同步 manifest。
- 老用户磁盘已有的 builtin pack 不会被自动覆盖(persistence migration 只填空字段,
  保护用户对内置包的编辑);要应用 v2 需在「风格」页面点一次「重置」;新装用户直接拿 v2。
- polish.rs 的 4 处依赖旧 heading 的断言更新为 v2 实际锚点;
  common_rules_include_auto_correction_and_natural_organization 拆为
  Raw(走 wrapper)+ Light/Structured/Formal(走 v2 PRO 自带 ASR 纠错段)两组。

后端服务(apic.openless.top)这次没动。

* feat: 英文翻译注入 EN 专用指引 + 修「我的发布」加载/计数/驱逐

英文翻译加专用补丁
- translate_system_prompt 检测 target_language == English(含 '美式英文' / '英文' / 'english'
  / 'British English' 等别名)时,在基础 prompt 末尾拼一段 EN 专用指引:
    · ASR 纠错优先策略(高/中/低置信度分级)
    · 中→英技术词规范化表(令牌→Token / 密钥→Secret Key / 应用ID→App ID /
      鉴权失败→authentication failure / 调用接口→call the API …)
    · 模型名 / 产品名还原(克劳德→Claude、双子座→Gemini、卡布奇诺→Cappuccino …)
    · 自然英文要求(idiomatic、avoid Chinglish、code/URL/版本号原样保留)
- 其它语言行为不变。
- 来源:社区贡献的「重写为英文」prompt,精简后避免与 base 重复。
- 新增 translate_prompt_appends_en_extras_only_for_english 单测覆盖 happy path + 别名 + 非英文。

修「我的发布」三个 bug
1. 数据拉取态:新增 myPacksLoading / myPacksError 状态机。
   原行为:拉取中显示「你还没有发布过风格包」,失败只弹 toast;用户分不清"empty"和"loading"。
   现行为:四态优先级 loading → error → empty → list;loading 显示「正在拉取,请稍后…」;
   error 单独成块带「重试」按钮(inline 比 toast 更稳定可达)。
2. 计数错位:visibleMyPacks 立即过滤 withdrawn / superseded(移除原 5 分钟可见窗口)。
   原 bug:发布 1 个删 1 个后,刚下架的那个还在 5 分钟窗口内 → 计数显示 2,跟实际不符。
   下架的反馈走 actionMsg toast 即可,不需要在列表里继续占位。
3. 计数源切换:'已发布 N 个'里的 N 改用 visibleMyPacks.length(active 包),
   跟列表里实际看到的卡片数对得上。pending 计数同样基于 visible。
4. 顺手清理:刷新按钮在 myPacksLoading=true 时 disabled,避免并发请求;
   删掉只为旧 5 分钟窗口存在的 nowTick / setInterval 状态与 effect。

i18n(5 个 locale):marketplace.myPacks 新增 loadingTitle / loadingHint /
loadErrorTitle / loadErrorRetry 四个 key。

* refactor(translate): EN 目标整段切到专用 prompt(不再走 append)

上一版是「通用 base + 末尾追加 EN 专用补丁」,两段重复 / 注意力被稀释。这版改为:
- target_language 命中 English 时(含 '美式英文' / '英文' / 'english' / 'British English' 别名)
  → translate_system_prompt 直接 return 新增的 EN_TRANSLATE_SYSTEM_PROMPT 整段,不再拼通用 base。
- 其它语言行为不变(继续走原通用 # 任务(翻译输出) base)。

新 EN_TRANSLATE_SYSTEM_PROMPT 自包含、~60 行精简版(来源:社区「重写为英文」prompt):
  · 角色 + 输入是 ASR 不可靠 + 任务定义(先纠错再翻译,结果直插光标)
  · 工作流程 6 步(不可换顺序)
  · ASR 纠错按高/中/低置信度分级
  · 中→英技术术语规范化表(Token / Secret Key / App ID / Access Token / Endpoint /
    Webhook / authentication failure / call the API / quota / front-end / back-end /
    Claude / Gemini / Cappuccino / Coder / LongCat …)
  · 翻译要求:自然 idiomatic、不要 Chinglish、保持原语气、英语写法的日期数字
  · 原样保留:代码标识符 / 命令 / 路径 / env vars / 布尔值 / 完整版本号 /
    大写缩略语(API SDK JWT OAuth …)/ 人名地名品牌名
  · 边界 case + 5 条禁止 + 输出约束

替换 translate_prompt_appends_en_extras_only_for_english 单测为
translate_prompt_swaps_to_en_dedicated_when_target_is_english:断言
- 英文目标命中 EN 专用 prompt("# 任务(中文转写 → 英文翻译)"),不再含通用 base 标题
- 非英文目标仍走通用 base
- 别名 '美式英文' / '英文' / 'english' / 'British English' 都解析为英文

cargo test 269 / 0 / 0;tsc clean。

---------

Co-authored-by: baiqing <lbx12309@icloud.com>
2026-05-17 09:45:28 +08:00
TRIP e33fc0047b Merge pull request #450 from Open-Less/beta
release: 1.3.2 stable — marketplace UI gated, ASR correction upgraded
2026-05-15 23:03:04 +08:00
TRIP e8d0ccf303 Merge pull request #449 from Open-Less/chore/marketplace-disable-stable
chore(stable): disable marketplace entry + strengthen ASR correction rules
2026-05-15 22:44:48 +08:00
TRIP 74208322bb Merge pull request #444 from Open-Less/feat/marketplace-tab
feat: marketplace tab + 一键发布 + Settings 风格市场配置
2026-05-15 15:00:34 +08:00
TRIP ec93a4a87a Merge pull request #443 from Open-Less/fix/polish-network-retry
fix(llm): retry transient network errors once before failing polish
2026-05-15 12:33:30 +08:00
TRIP 1f5051d8e0 Merge pull request #442 from Open-Less/fix/streaming-default-prompt-restructure
feat: streaming-on by default, structured default, agent-style hotword block middle
2026-05-15 11:41:14 +08:00
TRIP e297bfdd6e Merge pull request #438 from Open-Less/fix/volcengine-403-msg
fix(asr): surface Volcengine 403 as 'credentials rejected'
2026-05-15 09:45:56 +08:00
TRIP a0c239a716 Merge pull request #436 from Open-Less/feat/voice-quality-audio-archive
feat: polish hotword overrides, audio archive, auto-update check
2026-05-15 08:40:35 +08:00
TRIP 0aa3ee64d3 Merge pull request #435 from Open-Less/feat/style-pack-ui-refresh
feat(style-pack): rework UI + 模板新建 + 清理 PR #429 遗留死代码
2026-05-14 15:01:38 +08:00
TRIP 1ab0807ada Merge pull request #434 from Open-Less/fix/qwen-asr-long-audio-loss
fix(local-asr): Qwen3-ASR 长语音末段丢内容 + 长录音超时
2026-05-13 23:08:48 +08:00
TRIP 61325e3e03 Merge pull request #433 from Open-Less/feat/wayland-callout-three-commands
feat(wayland): callout 补全 --toggle-qa / --cancel-dictation
2026-05-13 23:08:21 +08:00
TRIP 1f2b259909 Merge pull request #428 from Open-Less/fix/selectlite-zoom-offset
fix(SelectLite): zoom 补偿修位置偏移 + 同步二次定位消除闪动 + 三平台统一
2026-05-13 14:25:15 +08:00
TRIP b699384762 Merge pull request #427 from Open-Less/fix/issue-420-wayland-cli
fix(hotkey): Wayland 下用 CLI 触发录音,修复 #420
2026-05-13 12:45:58 +08:00
TRIP db86a14169 Merge pull request #426 from Open-Less/fix/v1.3.1-7
fix(SelectLite): 下拉位置错位 + 关闭动画 + 滚外部自动收缩 (v1.3.1-7)
2026-05-12 19:14:51 +08:00
TRIP 545c3312c3 Merge pull request #425 from Open-Less/fix/v1.3.1-6
fix(capsule): 入场出场动画加慢加曲线 + thinking 字号/颜色重做 (v1.3.1-6)
2026-05-12 18:44:04 +08:00
TRIP 784f947cf3 Merge pull request #423 from Open-Less/fix/v1.3.1-5
fix(v1.3.1-5): 胶囊动画 + Qwen3 灰显 + 5 SVG 重画 + 6 项 Windows 视觉修复 + polish 反降级
2026-05-12 17:45:27 +08:00
TRIP ec55d9e509 Merge pull request #422 from Open-Less/fix/v1.3.1-4-multi-tweaks
fix: v1.3.1-4 Windows 字号/顶栏色/玻璃 + Vocab 大圈 + polish 回滚
2026-05-12 16:59:24 +08:00
TRIP 8799830e3e Merge pull request #421 from Open-Less/fix/win-button-appearance
fix: Windows 主窗口 chrome 丢失 + Vocab 大圈 + polish 清晰结构回归 (v1.3.1-3 hotfix)
2026-05-12 15:34:25 +08:00
TRIP 993bb90dd3 Merge pull request #419 from Open-Less/fix/windows-ui-417-418
fix(ui): Windows 原生 chrome / 胶囊缩小 / 自定义下拉 (#417 #418)
2026-05-12 14:54:37 +08:00
TRIPandbaiqing 6baca19e05 refactor(settings): split Settings.tsx into per-section modules + simplify Advanced labels (#416)
Settings.tsx (2835 → ~2150 lines) split by logical responsibility: each tab
now lives in src/pages/settings/. Pure mechanical extraction — no behavior change.

- settings/shared.tsx     — SettingRow, Toggle, inputStyle, AsrPresetId
- settings/AdvancedSection.tsx
- settings/ShortcutsSection.tsx
- settings/PermissionsSection.tsx (incl. PermissionPill / HotkeyStatusPill / WindowsImeStatusPill)
- settings/LanguageSection.tsx
- settings/AboutUpdateControl.tsx
- Settings.tsx keeps RecordingSection + ProvidersSection (their helper tangle
  is large; follow-up PR), re-exports Toggle / AboutUpdateControl so
  SettingsModal's existing imports keep working.

Advanced settings labels: shortened verbose copy across zh-CN / zh-TW / en /
ja / ko. "启用流式输入" → "流式输入", "同步写入剪贴板" → "同步到剪贴板",
desc / hint blocks trimmed; the toggle already conveys enable/disable so the
verb prefix was redundant.

Verified: tsc clean, vite build green, cargo check green, cargo test --lib
222/222 passing.

Co-authored-by: baiqing <lbx12309@icloud.com>
2026-05-12 12:48:31 +08:00
TRIP 71f2792f09 Merge pull request #415 from H-Chris233/fix/issue-413-streaming-sse-edges
修复流式 SSE UTF-8 切分与部分输入丢失
2026-05-12 10:58:47 +08:00
TRIP d4162cf9cc Merge pull request #412 from Open-Less/pr/streaming-cross-platform
feat: cross-platform streaming polish output (Win/Linux/macOS)
2026-05-12 02:02:57 +08:00
TRIP af07795e25 Merge pull request #407 from Open-Less/pr/settings-vocab-collapsible
refactor(ui): Collapsible groups in Settings & Vocab
2026-05-12 01:25:41 +08:00
TRIP 1f11e2574b Merge pull request #408 from Open-Less/pr/capsule-thinking-shine
feat(capsule): animated thinking shine indicator
2026-05-12 00:32:33 +08:00
TRIP 6a1986e0c5 Merge pull request #409 from Open-Less/pr/hotkey-debounce-250ms
fix(hotkey): 250ms debounce on pressed-edge dispatch
2026-05-12 00:32:13 +08:00
TRIP 080fd9c155 Merge pull request #411 from Open-Less/pr/docs-and-empty-asr-zh
docs: refresh CLAUDE.md + localize empty-ASR capsule message
2026-05-12 00:31:55 +08:00
TRIP 051e058838 Merge pull request #410 from Open-Less/pr/polish-role-block-refine
polish: refine ROLE_BLOCK to preserve user intent
2026-05-12 00:31:13 +08:00
TRIP e7a0ad5971 Merge pull request #401 from BBossss/beta
Merging Codex OAuth LLM provider integration from external contributor.
2026-05-11 07:10:15 +08:00
TRIPandbaiqing 3125ff4d20 feat(settings): cross-platform parity for local ASR (#403)
* feat(settings): cross-platform parity for local ASR

- AdvancedSection: warning collapses to inline 11px amber badge in
  the title row's top-right; the enable-confirm popup is now a
  centered fixed modal with backdrop blur(8px) and click-outside-
  to-cancel (busy-guarded).
- Symmetric rows by platform — fixes the #400 bug where the Qwen3
  row was rendered on Windows even though all qwen_engine /
  local_provider / cache modules are #[cfg(target_os = "macos")],
  so Enable on Windows would always error:
    macOS:   Qwen3 row enabled / Foundry row hidden
    Windows: Foundry row enabled / Qwen3 row disabled with
             notSupportedHere desc
- Providers ASR Card: replaces the plain-text localAsrActiveNotice
  branch with an always-rendered dropdown that includes the
  platform-native local engine (macOS=Qwen3, Windows=Foundry) as a
  disabled option, plus a takeover hint underneath. Entire
  dropdown locks when a local engine is active, so it can only be
  released via Advanced (matches "启动之后供应商将被接管" UX).
- onLlmProviderChange / onAsrProviderChange now emit saving →
  saved through emitSaved. Data was already being persisted; the
  saved-toast was missing.
- LocalAsr embedded: hide Qwen3 model management (mirror /
  engine-status / model list) on Windows — dead UI without the
  Qwen3 backend. The shared error Card is kept unconditional
  since Foundry handlers also call setError.
- i18n × 5 (zh-CN/zh-TW/en/ja/ko): shorten localAsrWarningShort;
  rewrite qwen3Desc and foundryDesc to '启动之后,ASR 提供商将被接管';
  add notSupportedHere and localAsrTakeoverHint.

* fix(settings): clear saving toast on provider-switch failure

pr_agent #403 caught: emitSaved('saving') with no try/catch — if
setActiveLlmProvider / setActiveAsrProvider / any later credential
write throws, the right-corner toast stays stuck on saving forever.

Wrap both onLlmProviderChange and onAsrProviderChange bodies in
try/catch. On error, emit 'failed' (and re-throw) only if seq still
matches — stale calls superseded by a newer switch shouldn't
overwrite the newer call's own saving state.

* fix(settings): provider-card visual cleanup + log-export permission + pr_agent #403

Fixes piled up in user feedback during local verify of the previous
commit; bundled into one commit to keep #403 small.

- capabilities/default.json: add `dialog:default` permission. The
  About → Export error log button always failed because the Tauri 2
  capabilities did not allow `tauri-plugin-dialog`'s `save()` command;
  the JS rejected before export_error_log was ever invoked.

- ProvidersSection ASR Card (pr_agent #403 'Wrong active label'):
  when committedAsrProvider is a local engine, selectedValue now
  reflects the actually-active engine instead of being forced to
  platformLocalAsr. Cross-machine config sync (macOS profile with
  foundry-local-whisper / Windows profile with local-qwen3) used to
  make the dropdown lie about which backend is running. Anomalous
  local engines are added as disabled <option>s so the controlled
  <select> still finds its value and doesn't fall back to the first
  option.

- ProvidersSection: drop verbose desc on LLM and ASR provider rows.
  '选择后将自动填入 Base URL 默认值。' is ~16 Chinese chars and
  always wraps inside the 180px label column, leaving a stranded
  second line on the left — the dropdown itself already conveys the
  semantic, the desc was redundant.

- AdvancedSection: replace the [启用] / [已启用] Btn with the
  shared Toggle component for Qwen3 and Foundry rows. Toggle off
  switches back to volcengine. The Qwen3 row's Toggle truthfully
  reflects committed state on Windows (so a cross-platform anomaly
  shows ON even though it is non-interactive there). The standalone
  '禁用本地 ASR' escape row is now only rendered when no inline
  Toggle can disable the active engine — i.e., Linux / unsupported
  platform, or anomaly where the row for the foreign engine is
  hidden / non-interactive.

- PermissionsSection: every status pill (microphone, accessibility,
  hotkey, Windows IME, network) now right-aligns inside its row via
  `justifyContent: flex-end` + `width: 100%`. Pill text moved to the
  right of inline messages so the green '已授权' / 'Authorized'
  reads as a right-edge badge — closer to macOS System Settings
  convention.

* fix(settings): keep Windows Qwen3 toggle visually OFF (pr_agent #403)

pr_agent 'Stale Windows state' caught: previous commit had `on=
{isOnLocalQwen3}` which truthfully reflected committed state, so a
profile synced from macOS with activeAsrProvider=local-qwen3 would
render Toggle ON next to a desc that says '本平台暂不支持'. The
visual contradiction was the concern.

Switch to `on={isMac && isOnLocalQwen3}` — Windows always renders
the row OFF + non-interactive, matching the unsupported desc. The
cross-platform anomaly state surfaces via the standalone '禁用本地
ASR' escape row, which is rendered exactly when no inline Toggle
can disable the active engine.

Comment updated to match the new behavior and document the
tradeoff.

* fix(localasr): scope Qwen3 mgmt UI to macOS only (pr_agent #403)

pr_agent 'Linux regression' caught: previous wrap was `!IS_WINDOWS`,
so a hypothetical Linux render path would still expose the Qwen3
mirror / model-list / engine-status UI even though Qwen3's backend
is `#[cfg(target_os = "macos")]`.

Swap the two wrappers to `IS_MAC` so the management cards only
materialize on macOS. Today's AdvancedSection guards LocalAsr
embedded with `platformSupported = isMac || isWin`, so Linux already
short-circuits before LocalAsr renders — but defense in depth here
costs nothing and keeps the comment accurate.

---------

Co-authored-by: baiqing <lbx12309@icloud.com>
2026-05-11 07:01:00 +08:00
TRIPandbaiqing f3894c1f7b feat(llm): Google Gemini provider + fix LLM provider switch (#398)
* chore: ignore video-materials/ promo recordings

video-materials/ holds ~2.2 GB of promo recordings (promo /
promo-materials / promo-openless / promo-openless-v2 / SC). These
are local working assets that don't belong in the repo. Add to
.gitignore so future stash / commit operations can't accidentally
pull the binary blob in.

* feat(llm): Google Gemini provider with forced thinking-off + fix provider-switch credentials

Add Google Gemini as a native LLM provider hitting v1beta
:generateContent (single-shot) and :streamGenerateContent?alt=sse
(QA chat) with x-goog-api-key auth. Per-model thinkingConfig is
injected to keep thinking off for every supported ID — exact
matrix verified against ai.google.dev/gemini-api/docs/thinking:

  gemini-2.5-flash, gemini-2.5-flash-lite     thinkingBudget: 0
  gemini-2.5-pro                              field omitted (Google: cannot disable)
  gemini-3*-pro*                              thinkingLevel: "low"
  gemini-3*-flash*, unknown future IDs        thinkingLevel: "minimal"

Why native, not the OpenAI-compat shim: shim is beta and for 3.x
needs an extra_body workaround for thinking control; auth header
and contents shape differ enough that reusing
OpenAICompatibleLLMProvider would have been the worse compromise.

fix(settings): switching LLM preset previously only auto-filled
endpoint/model when those slots were empty. All providers share
ark.endpoint / ark.model_id (per-provider entries were never
introduced — see persistence.rs lookup_account), so for returning
users with non-empty slots, switching the dropdown updated active
provider but kept the OLD endpoint — polish silently kept calling
the previous provider. Now switching to any non-custom preset
overwrites endpoint/model to that preset's defaults. Trade-off:
user-customized endpoints within a preset are dropped on switch;
silent wrong-routing was a worse failure mode.

Backend:
- New module llm_gemini.rs (15 unit tests for thinking-config table,
  URL builder, response parser, SSE shape, history role mapping)
- polish.rs extracts compose_polish_prompts / compose_translate_prompts
  / compose_qa_system_prompt as pub(crate) helpers shared by both
  LLM paths so prompt assembly can't drift
- coordinator.rs's polish_text / translate_text / answer_chat_dispatch
  open with `if get_active_llm() == "gemini"` early-return; existing
  OpenAI-compat callsite stays untouched

Frontend:
- LLM_PRESETS gains gemini entry (baseUrl v1beta, model placeholder
  gemini-2.5-flash)
- ProviderTools hidden when committedLlmProvider === 'gemini'
  (its "Fetch models" button assumes OpenAI /models shape; native
  /v1beta/models response shape doesn't match)
- Static geminiModelHint lists the 7 available IDs so users pick
  correctly without auto-fetch
- 5 locales (zh-CN, zh-TW, en, ja, ko) gain presets.gemini and
  geminiModelHint

Test plan:
- cargo test --lib                          200/200 pass
- npm run build (tsc + vite)                green
- Manual: switch to Gemini in Settings,
  paste Google AI Studio key, dictate;
  verify polish reaches v1beta and respects
  the per-model thinking-off contract.

* docs: 2026-05-10 audit + beta logic review snapshots

Snapshot two audit artifacts produced 2026-05-10:

- docs/audit-2026-05-10-validated.md — 21 audit items run against
  the live source tree at openless-all/app/src-tauri/src/ with a
  CONFIRMED / FALSE_POSITIVE / PARTIAL verdict per item plus the
  exact file:line range that supports each verdict.

- docs/logic-review-2026-05-10.md — 5-path logic review of
  origin/beta @ 400097ad covering startup, press/release, end-of-
  session, capsule emit, and shutdown. Notable: at 400097ad the
  emit_capsule fix from PR #389 was NOT yet an ancestor, so the
  cpal audio-callback → AppKit/Win32 SIGTRAP risk was still live
  on that snapshot (subsequent merges have addressed it; this doc
  remains the dated record).

Both files are pure snapshots — no code changes here.

* fix(llm-gemini): buffer SSE bytes across chunks to handle multibyte UTF-8 split

PR #398 pr_agent flagged: reqwest::chunk() can split a multi-byte
UTF-8 codepoint (CJK / emoji) across two network chunks, but the
streaming code decoded each chunk independently with from_utf8 →
half a 你 in the tail of one chunk would surface as
"non-utf8 SSE chunk" and abort the whole stream. This breaks
Gemini QA streaming for any Chinese-language answer that doesn't
happen to align with packet boundaries.

Fix: buffer raw bytes (Vec<u8>) until we see the SSE delimiter
b"\n\n", and only then decode the complete event content as UTF-8.
The delimiter \n\n is two ASCII bytes (0x0A 0x0A) which can never
appear inside a multi-byte UTF-8 sequence, so byte-level splitting
at that boundary is always safe.

Extracted the parsing as drain_complete_sse_events(&mut Vec<u8>) so
the invariant is testable without a TcpListener. Three new unit
tests cover:
- normal split-on-delimiter + remainder buffering
- multibyte UTF-8 split across chunks (the actual regression)
- invalid-UTF-8 event content gracefully skipped (warn, don't crash)

The same byte-level pattern fix applies to polish.rs's
chat_completion_history_streaming — left untouched here to keep
this PR scoped to the Gemini diff pr_agent reviewed. polish.rs
should get the same fix in a follow-up.

* feat(llm-gemini): teach ProviderTools to fetch native Gemini /v1beta/models

Drop the verbose static geminiModelHint paragraph and restore the
normal ProviderTools "Fetch models" button when the active provider
is Gemini. Users now read live model IDs from Google instead of
staring at a wall of i18n text below their model field.

Backend fetch_provider_models now branches on the base URL:
- Google's generativelanguage.googleapis.com → x-goog-api-key auth +
  parse_gemini_model_ids() that walks `models[].name` and strips the
  `models/` prefix so the result is a plain `gemini-2.5-flash` style
  ID directly writable to ark.model_id.
- Anything else → unchanged Bearer auth + OpenAI `data[].id` shape.

Tests: parse_gemini_model_ids_strips_models_prefix_and_dedups +
is_gemini_base_url_matches_official_domain. cargo test --lib green
(32/32 in commands::tests subset).

i18n: settings.providers.geminiModelHint removed from all 5 locales.
The TS `typeof zhCN` constraint enforces the consistent removal.

* fix(llm-gemini): pr_agent #398 follow-ups — filter non-chat models + CRLF SSE delim

Two follow-up fixes for pr_agent advisories on PR #398:

1. parse_gemini_model_ids now filters by `supportedGenerationMethods`.
   Google's /v1beta/models lists every family — embeddings,
   TTS-image, predict-only — alongside generateContent-capable
   chat models. Without filtering, a "Fetch models" click would
   surface ids like `gemini-embedding-2`, and a user picking one
   would write it into ark.model_id and have polish silently fail
   on every dictation. Field is conservatively included when
   missing (some preview models don't expose it yet).

2. drain_complete_sse_events now accepts both `\n\n` and `\r\n\r\n`
   as event delimiters. Some HTTP/2 middleware and CDNs normalize
   line endings to CRLF; the previous LF-only implementation would
   never drain a complete event from those servers and report the
   stream as empty even when data was received. Tied positions
   can't happen by construction (\r\n\r\n contains no \n\n
   substring), so "earliest match wins" is unambiguous.

Tests:
- parse_gemini_model_ids_filters_out_non_generate_content_families
- drain_complete_sse_events_handles_crlf_delimiter
- drain_complete_sse_events_picks_earliest_delimiter_when_mixed

---------

Co-authored-by: baiqing <lbx12309@icloud.com>
2026-05-10 18:18:24 +08:00
TRIPandbaiqing ce21d99f05 feat(settings): hide local ASR behind new Advanced tab + confirm dialog (#400)
* feat(settings): hide local ASR behind new Advanced tab + system confirm dialog

Move local ASR options (local-qwen3 / foundry-local-whisper) out of
the main Providers → ASR dropdown into a new "Advanced" Settings tab,
so first-time users can't accidentally enable CPU inference and get
slower / less-accurate transcription than they expected. Enabling
local ASR now requires reading a strong yellow/amber warning and
confirming a system-level dialog (@tauri-apps/plugin-dialog ask()
with kind:'warning') — the in-page modal pattern wasn't loud enough.

Behavior changes:
- visibleAsrPresets now excludes local-qwen3 and foundry-local-whisper
  (the dropdown is cloud-only)
- useEffect lookup uses ASR_PRESETS (all) so committedAsrProvider can
  still resolve to a local provider when one is active
- When committedAsrProvider is local, the ASR Card replaces the
  dropdown with a plain notice ("Local ASR (X) is currently active —
  switch / disable in the Advanced tab"); existing
  LocalAsrProviderHint rendering for the Card body is preserved
- New AdvancedSection component:
  - Strong warning block (amber background + amber border + amber text,
    fontWeight 500, ⚠️ icon) — meets user's "yellow/red obvious" ask
  - Per-platform engine selector:
      macOS  : Qwen3-ASR only
      Windows: Qwen3-ASR + Foundry Local Whisper
      Linux  : "Local ASR model integration is not supported on this platform"
  - Enable buttons trigger Tauri ask() confirm dialog before switching
  - When already on local: shows a "Disable local ASR" button
    (switches to volcengine, no confirm needed) plus the existing
    LocalAsrProviderHint for model status / download navigation
- SettingsSectionId gains 'advanced'; SECTION_ORDER appends it after
  language so it sits last in the sidebar (matches macOS conventions
  for Advanced/Experimental panes)

i18n: 5 locales (zh-CN, zh-TW, en, ja, ko) gain
- settings.sections.advanced
- settings.providers.localAsrActiveNotice
- settings.advanced.{localAsrTitle, localAsrDesc, localAsrWarningStrong,
  qwen3Desc, foundryDesc, enable, alreadyActive, disableLocalLabel,
  disableLocalDesc, disable, platformNotSupported,
  confirmEnableLocalTitle, confirmEnableLocalBody}

Test plan:
- npm run build (tsc + vite) — green; the typeof zhCN constraint
  catches any locale that drifted out of shape
- Manual: switch Settings tab to "Advanced", verify warning is
  amber-on-amber and clearly visible, click "Enable" on Qwen3-ASR,
  verify a system dialog (not a React modal) appears with kind:warning
  iconography, cancel — verify nothing changed; confirm — verify the
  Providers tab now shows "Local ASR is currently active" instead of
  the dropdown
- Manual on macOS: confirm Foundry option does NOT appear
- Manual on Linux (if available): confirm "platform not supported"
  message renders

* feat(advanced): inline LocalAsr model UI + page-top confirm popup + concise warning

User feedback: the standalone "Models" page (LocalAsr.tsx as a main
nav tab) was redundant once Advanced settings exists, the inline
amber warning was a wall of text, and clicking Enable triggered an
OS-native dialog that didn't feel like part of the app. Refactor
the whole local-ASR surface around what users actually need:

- "Models" main-nav tab removed (FloatingShell + NAVIGATE_LOCAL_ASR_EVENT
  listener gone). Model management is now exclusively reachable via
  Settings → Advanced.
- LocalAsr.tsx gains an `embedded` prop. When embedded, the outer
  page padding/height, PageHeader, and standalone warning Card are
  skipped — the host (AdvancedSection) supplies the chrome.
- AdvancedSection no longer calls @tauri-apps/plugin-dialog ask().
  Enabling local ASR sets pendingTarget which renders a single
  amber-bordered Card at the very top of the section with
  Confirm / Cancel buttons. One box, in-app, dismissable. Disable
  is direct (no confirm needed — going back to cloud is safe).
- Persistent inline warning shrunk to one line. The longer wording
  (including the new "may drop words on under-spec hardware" risk
  the user asked for) lives in the popup body so it's only seen
  when actually consequential.
- ProvidersSection ASR Card body returns null when active is
  local — the dropdown row's localAsrActiveNotice already points
  to Advanced, no need to duplicate manage UI here.

i18n (5 locales): localAsrWarningStrong replaced by
localAsrWarningShort; confirmEnableLocalBody / Title shortened;
new `confirm` key for the popup CTA.

LocalAsrProviderHint and NAVIGATE_LOCAL_ASR_EVENT in Settings.tsx
are now unused but left in place to keep the diff focused; will
clean up in a follow-up commit if pr_agent flags them.

Test plan:
- npm run build (tsc + vite) — green
- Manual on macOS: Settings → Advanced shows one short amber line +
  Enable Qwen3-ASR button + LocalAsr model UI inline. Click Enable
  → page-top popup appears (no OS dialog), with Confirm / Cancel.
  Cancel → no change. Confirm → switches; Disable button appears,
  still single-click no confirm.
- Manual: Providers → ASR with active=local-qwen3 shows only the
  "Local ASR is currently active — switch in Advanced" notice,
  no manage UI.

* fix(advanced): pr_agent #400 follow-up — keep disable accessible on unsupported platform

If a user enables local-qwen3 (or foundry-local-whisper) on a machine
that supports it, then their preferences sync to a Linux/unsupported
machine, the previous Advanced UI rendered only the
"platformNotSupported" message and hid the disable button — they had
no in-app path to switch back to cloud and dictation would fail
silently on every press.

Move the disable SettingRow out of the platformSupported branch.
It's gated only on isOnAnyLocal now, so:
- Supported platform + on local → enable Qwen3/Foundry rows + disable row
- Supported platform + on cloud → enable rows only
- Unsupported platform + on local → "not supported" notice + disable row
- Unsupported platform + on cloud → "not supported" notice only

This is the pr_agent advisory from PR #400 review @ d0a425a1.

---------

Co-authored-by: baiqing <lbx12309@icloud.com>
2026-05-10 18:06:28 +08:00
TRIP 225859e18d Merge pull request #395 from appergb/chore/bump-1.2.24-7
chore: bump version to 1.2.24-7 (Beta-7)
2026-05-10 10:53:02 +08:00
TRIP 0f7961963b Merge pull request #394 from appergb/fix/keychain-cache
fix(vault): cache CredsRoot in process to stop repeated Keychain prompts
2026-05-10 10:12:27 +08:00
TRIP 618fc7f0b1 Merge pull request #393 from appergb/fix/post-audit-logic-review
fix(coordinator): post-audit logic-review hotfixes
2026-05-10 09:11:20 +08:00
TRIP 892f3c4bfe Merge pull request #389 from appergb/fix/audit-emit-capsule-mainthread
fix(coordinator): marshal capsule show/hide to main thread [高]
2026-05-10 08:41:01 +08:00
TRIP 400097adbf Merge pull request #391 from appergb/fix/audit-async-hygiene
fix(coordinator): wrap audio-mute shellouts in spawn_blocking [中]
2026-05-10 08:22:53 +08:00
TRIP ff5e8f8ee1 Merge pull request #377 from appergb/fix/issue-360-paste-shortcut
fix(insertion): make Windows/Linux paste shortcut configurable (#360)
2026-05-10 08:10:15 +08:00
TRIPandbaiqing fa6038f92d feat(ui): consolidate footer/nav, sliding indicator, hover cues, top-right saved toast (#381)
整体 UI 整理 + 跨页面"已保存"提示统一到右上角小 pill。

## Footer 简化
- 删 账号 icon(= openSettings('providers') 跟齿轮重复入口)
- 删 帮助 popover(version + 2 条外链跟 Settings → 关于 重复)
- 删 检查更新 按钮(Settings.about 已有同等控件)
- BETA 角标从 sidebar 移到右下角紧贴版本号
- 设置 icon 内嵌 12px 不再贴右边角

## Sidebar / Settings 三处 nav 统一
- 滑动指示器:用一个 absolute pill,currentTab 切换时 top/height 平滑过渡
  到目标 button(cubic-bezier(0.16,1,0.3,1) 360ms,无 overshoot 的 Apple
  ease-out-quint),不是各按钮瞬切背景。
- Hover 三段视觉层次:
    基础态 ink-3 中灰文字 + 透明底
    Hover  ink 全黑文字 + 浅灰底(图标通过 currentColor 同步加深)
    选中  ink 全黑文字 + 白色滑动 pill 底
  用 .ol-nav-btn class + :hover 实现,inline color 让位给 CSS(CSS 不能盖
  inline style 是关键)。

## "已保存" toast 改右上角小 pill
- 新增 components/SavedToast.tsx + lib/savedEvent.ts(emitSaved 事件总线)
- Translation / SelectionAsk 内联 banner 改为锚到 ol-thinscroll 控制台卡
  右上角的 absolute pill;scroll wrapper 加 position:relative
- Settings → ASR 配置:CredentialField API key 内联"已保存"挪到 SettingsModal
  右上角统一 pill(offsetStyle: right:54 避开关闭按钮);只保留持续性的
  readError 在原位标识字段不可用

## Translation / SelectionAsk 简化
- 删跟 Settings.shortcuts 重复的快捷键卡(用户改键统一在 Settings 完成)
- SelectionAsk "历史保存"从全宽 Card 改成左对齐的小模块(标签 + toggle 紧贴)
- PageHeader 砍 kicker+desc → 单行 title
- 使用方法卡去 inset 提示块(保留 5 步 ol)

## Overview 收头
- PageHeader 单行 title(去 kicker / desc / 右侧"按 X 开始"pill)

Co-authored-by: baiqing <lbx12309@icloud.com>
2026-05-09 16:29:52 +08:00
TRIPandbaiqing 14bfa5f749 fix(commands): wrap tray refresh in run_on_main_thread to avoid macOS UI freeze (#380)
set_settings 和 set_default_polish_mode 是同步 Tauri commands,跑在 IPC handler
线程。它们直接调 refresh_tray_microphone_menu,里面 tray.set_menu(...) 改 macOS
NSStatusItem 必须在主线程上做。从 IPC 线程直调会触发 macOS dispatch queue
死锁,导致用户改任何偏好开关后整个 UI 永久卡死、所有按键无响应。

修复:把两处 tray 刷新都包到 app.run_on_main_thread(move || ...) dispatch 到主线程,
IPC 线程立即返回不阻塞。这跟 lib.rs:558 里 start_tray_microphone_watcher 已经
在用的 pattern 完全一致(issue #169 stop_qa_hotkey_listener 同样用法)。

防御性扫描结论:整个项目里 tray.set_menu() 只在 refresh_tray_microphone_menu
(lib.rs:518)一处出现,所有 5 个调用点已确认安全:
  - lib.rs:207 tray hover event:tray 事件本身就在主线程
  - lib.rs:559 麦克风设备 watcher:已有 run_on_main_thread
  - lib.rs:605 tray 菜单事件:本身在主线程
  - commands.rs:160 set_settings:本提交修复
  - commands.rs:944 set_default_polish_mode:本提交修复

其它 sync IPC commands(set_dictation/qa/translation/switch_style/open_app_hotkey)
通过 coord.refresh_*_hotkey() 间接刷新,那些函数内部已包了 run_on_main_thread;
不碰 AppKit 的 commands 安全。

Co-authored-by: baiqing <lbx12309@icloud.com>
2026-05-09 16:29:45 +08:00
TRIPandbaiqing b44cb998af fix(installer): unregister TSF IME before replacing OpenLessIme.dll on upgrade (#321) (#369)
Auto-update was failing on Windows because OpenLessIme.dll is a TSF input-method
DLL: once registered, Windows maps it into every text-input client process
(browsers, IDEs, chat apps...). The installer's PREINSTALL hook tried to
overwrite the file directly while it was still mapped, hitting a sharing
violation and aborting the update.

NSIS hook (openless-ime-hooks.nsh):
- PREINSTALL now calls OPENLESS_IME_UNREGISTER_X86/X64 first to release the
  COM/TSF registration. On a fresh install this is a harmless no-op (existing
  unregister macros log the exit code without aborting).
- DLL bytes are written to OpenLessIme.dll.new and atomically renamed into
  place. If the old DLL is still locked by a TSF client process, Delete fails
  and we fall back to Rename /REBOOTOK + SetRebootFlag — Windows queues the
  replacement via PendingFileRenameOperations and the new bytes activate after
  the next reboot. Same TSF registration path is preserved, so re-registration
  in POSTINSTALL stays valid across the reboot.

WiX (openless-ime.wxs):
- UnregisterOpenLessImeX86/X64 now also fire Before InstallFiles when a
  previous version is installed (Installed AND NOT REMOVE). Original condition
  (REMOVE="ALL", Before RemoveFiles) only covered the dedicated uninstall
  sequence; auto-update / minor-upgrade skipped it entirely and ran straight
  into the same sharing violation as NSIS.

Bumps to 1.2.24-5 (Beta-5).

Co-authored-by: baiqing <lbx12309@icloud.com>
2026-05-08 19:58:05 +08:00
TRIPandbaiqing 29d3a79eb6 chore: bump version to 1.2.24-4 (Beta-4) (#358)
Co-authored-by: baiqing <lbx12309@icloud.com>
2026-05-08 11:57:36 +08:00
TRIPandbaiqing 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
TRIPandbaiqing 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
TRIPandbaiqing 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
TRIPandbaiqing 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
TRIPandbaiqing 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
TRIPandbaiqing 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
TRIPandbaiqing 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
TRIPandbaiqing 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
TRIPandbaiqing 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
TRIPandbaiqing 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
TRIPandbaiqing 64ab42c5a3 fix(release): backport YAML duplicate env fix into main (#336)
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:32:23 +08:00
TRIPandbaiqing 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
TRIPandbaiqing 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
TRIP 2db179029a release-sync: workflow rollout + soaked beta features → main (#333)
release-sync: workflow rollout + soaked beta features → main
2026-05-07 22:02:29 +08:00
TRIPandbaiqing 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
TRIPandbaiqing 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
TRIPandbaiqing 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
TRIPandbaiqing 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
TRIPandbaiqing 27d42e7303 fix(polish): #305 工作日报场景"清晰结构"无效 + 内置 AI 工具词 (#318)
## 修 #305 polish.rs Structured prompt
- 明确"已结构化 ≠ 不用改":原文是否已有标点 / 编号 / 换行不是判断依据;
  照抄原结构 = 失败
- 重组阈值降到 ≥3 条;即使原文已经是 "1. 2. 3." 也要按主题重新归类成
  双层 (a)(b) 子项,不能扁平照抄
- user_prompt 去掉"它不是问题,也不是任务"这条强 framing —— 旧措辞会
  让 LLM 把书面化输入误判为"已整理好"直接 passthrough(issue #305 截图
  根因)
- 新增 # 示例 3:工作日报场景(已半结构化输入仍要重组)作为回归示例

## 扩充 vocab-presets.json programmer 预设
追加 11 个 AI 工具名:Claude / Codex / Copilot / Cursor / Windsurf /
Anthropic / OpenAI / GPT / ChatGPT / Gemini / DeepSeek。启用 programmer
预设后会同时注入 ASR 热词 bias + polish 提示词的 # 热词 段,覆盖
"cloud → Claude"这类同音误识别。

## 测试
- polish.rs 新增 2 个 #305 回归测试:
  - structured_prompt_forces_regrouping_even_for_already_structured_input
  - user_prompt_no_longer_says_input_is_not_a_task
- cargo test --lib polish: 10/10 passed
- npx tsc --noEmit: clean

Closes #305

Co-authored-by: baiqing <lbx12309@icloud.com>
2026-05-07 12:36:00 +08:00
TRIPandbaiqing 32f2df31ee Add Claude Code GitHub Workflow (#308)
* "Claude PR Assistant workflow"

* "Claude Code Review workflow"

* ci(claude): 收紧 review 触发:只 OWNER @claude 才跑,默认 Sonnet 4.6

- 删除 claude-code-review.yml:原本 PR opened/sync 自动触发会无差别烧 OAuth 额度
- claude.yml if 加 author_association == 'OWNER' 门禁:只有仓库 owner 评论 @claude 才触发
- 加 Pick model step:默认 claude-sonnet-4-6;评论包含 --opus / claude-opus 时切 claude-opus-4-7
- 用 env 注入 body / title 而不是直接 inline expression,避免外部内容引号注入

---------

Co-authored-by: baiqing <lbx12309@icloud.com>
2026-05-07 10:46:53 +08:00
TRIP 2c9dca531d Merge pull request #307 from appergb/fix/post-pr236-i18n-selectionask-ui
fix(i18n,ui): 修 PR #236 合并后 ja/ko 缺键 + SelectionAsk 启用开关右置
2026-05-07 09:33:17 +08:00
TRIP 98da5970bf Merge pull request #236 from LeslieLeung/feature/key-binding
feat: add configurable shortcut bindings
2026-05-07 09:24:33 +08:00
TRIP b9adb6cf67 Merge pull request #294 from appergb/chore/ci-quality-gate-and-version-bump
chore(devex): CI 加 mac 矩阵 + version-triplet 校验 + bump-version.sh
2026-05-06 21:24:10 +08:00
TRIP 095e3127bb Merge pull request #277 from H-Chris233/fix/issue-230-credential-storage-model
对齐凭据存储策略与威胁模型
2026-05-06 20:27:13 +08:00
TRIP c0118ee481 Merge pull request #290 from appergb/chore/skip-macos-13-runner
chore(ci): 暂时搁置 macos-13 (Intel mac) 构建 — GH runner pool 卡死
2026-05-06 20:27:08 +08:00
TRIP 08b5b882f1 Merge pull request #291 from appergb/fix/startup-splash-transparent
fix(startup): splash 透明背景 + 卡片化,避免长启动时左半白屏
2026-05-06 20:27:03 +08:00
TRIP ab65432c1c Merge pull request #289 from appergb/fix/recording-no-swallow-first-words
fix(recording): 录音条出现时 mic 已 capture,不再吞开头字
2026-05-06 19:21:13 +08:00
TRIP 96ca96609f Merge pull request #287 from appergb/fix/windows-ime-static-crt
fix(windows-ime): 静态链接 CRT 防 host 进程 MSVCP140 劫持(修 QQ 切微软拼音崩溃)
2026-05-06 19:21:09 +08:00
TRIP eaf1dbed08 Merge pull request #286 from appergb/feat/settings-about-merge-export-log
feat(settings): 合并内外两个'关于' + 一键导出错误日志
2026-05-06 14:23:57 +08:00
TRIP f29a3ff196 Merge pull request #284 from appergb/feat/i18n-ja-ko-beta-full
feat(i18n): 完整翻译 ja/ko + 5 locale 加 Beta 标识
2026-05-06 14:00:50 +08:00
TRIP 83f1c807b3 Merge pull request #283 from appergb/feat/capsule-audio-easing
feat(capsule): AudioBars 用 cubic-bezier 缓动 + 加长 transition
2026-05-06 13:45:08 +08:00
TRIP b1d65fa05c Merge pull request #282 from appergb/fix/ci-checkout-submodules
fix(ci): checkout 拉 submodules — 修 mac 端 qwen-asr build fail
2026-05-06 13:09:47 +08:00
TRIP 284b75d3d2 Merge pull request #281 from appergb/fix/tauri-version-pin
fix(tauri): 锁 Rust crate ~2.10 修 npm/Rust 跨 minor mismatch
2026-05-06 12:01:57 +08:00
TRIP 6b38e5ef1d Merge pull request #280 from appergb/chore/release-prep-dedupe-nsis
chore(release-prep): 清理 #268/#279 双 NSIS 方案残留
2026-05-06 11:25:56 +08:00