TRIP and baiqing
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
baiqing
5d050d6121
refactor(insertion): 注释改写为 位置+用途+机制 结构
...
按新约定重写:删除 issue 号、Swift 原版对照、历史 trade-off 故事;
保留每段「这一段在哪里 / 做什么 / 怎么做」的最简说明。
作为后续核心代码注释统一清理的 calibration 样本。
2026-05-17 13:46:00 +08:00
baiqing
468b8bcbcb
fix(persistence): atomic_write 用唯一 tmp 文件名修 macOS 并发测试 race
...
并行 cargo test 时多个 Coordinator::new() 同时进入 StylePackStore/PreferencesStore
写流程:固定 tmp 文件 `preferences.tmp` 被互相覆盖,先 rename 的赢,后到的 rename 找
不到源文件 → ENOENT,macOS CI 偶发 `style pack store init: rename failed` panic。
改用 `<filename>.tmp-<uuid>` 让每个写者独占自己的 tmp;rename 失败时主动清理孤儿 tmp。
验证:本机 cargo test --lib(默认并行)连续 5 轮 270/0/0。
2026-05-17 13:45:46 +08:00
TRIP and baiqing
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
TRIP and baiqing
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
TRIP and baiqing
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
TRIP and baiqing
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
TRIP and baiqing
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
Chris233
990824cb69
Keep dictation feedback visible without stealing workspaces ( #456 )
...
Route the capsule show path through platform no-activate behavior so macOS can use orderFrontRegardless during dictation while preserving the existing fallback path when native handles are unavailable.
Constraint: issue #452 requires AeroSpace users not to be pulled back to the OpenLess workspace during dictation startup.
Rejected: removing macOS fallback entirely | product decision is to preserve visible recording feedback if no-activate cannot access ns_window.
Confidence: medium
Scope-risk: narrow
Directive: macOS fallback to window.show is intentional but may still switch workspace; validate on real macOS with AeroSpace before claiming full issue acceptance.
Tested: npm run build; cargo test --manifest-path openless-all/app/src-tauri/Cargo.toml --lib -- --test-threads=1; git diff --check
Not-tested: macOS real-device AeroSpace workspace behavior
2026-05-17 09:45:10 +08:00
Chris233
05e31caaf9
Merge pull request #454 from Drswith/codex/fix-settings-sidebar-453
...
fix(ui): 提升设置入口可发现性
2026-05-17 09:04:07 +08:00
Chris233
e5c32acb80
Merge pull request #459 from Open-Less/chore/remove-internal-docs
...
chore(repo): 移除内部 AI 协作 / 规划 / 审计文档,仅本地保留
2026-05-17 08:25:14 +08:00
baiqing
d6d1fee77e
chore(repo): 移除内部 AI 协作 / 规划 / 审计文档,仅本地保留
...
公共仓库不再发布 CLAUDE.md / AGENTS.md / 内部 audit + roadmap +
github-tracking + superpowers + windows-*-tracking 等共 45 个文件。
- 全部用 `git rm --cached`,本地仍保留以供日常协作;
- `.gitignore` 加规则防止再次被 `git add` 拉回;
- 用户 docs(volcengine-setup / tauri-csp / references / 图片素材)
以及 ISSUE_TEMPLATE / workflows / pull_request_template 仍保留发布。
密钥层面审计同步完成:未发现 API key / token / 私钥泄露,
tauri pubkey 与 GitHub OAuth client_id 均为设计上公开值。
2026-05-16 22:07:30 +08:00
drswith
a46eb71c04
fix(ui): improve settings entry discoverability
2026-05-16 19:23:53 +08:00
baiqing
e6749b1d71
chore(release): bump version 1.3.2-3 → 1.3.3 for Beta channel
...
为 v1.3.3-beta-tauri 发布周期 bump 版本,5 个版本字段同步:
- package.json
- package-lock.json (root + packages[""])
- src-tauri/tauri.conf.json
- src-tauri/Cargo.toml
- src-tauri/Cargo.lock (cargo update -p openless)
含本周期 Beta 主要改动:
- Pro 风格"清晰结构"提示词 v1.7(回 v1.3.0 设计哲学)+ Light 模式语音纠错
- 「我的发布」改为弹框形态(搜索/登录/关闭三段顶部布局,已下架 5 分钟自动消失,Modal 进入动画)
- Marketplace URL 硬编码 apic.openless.top(Settings 不再输入)
- 风格市场按钮移到 Style 页刷新左
- list cache (sessionStorage, 5min) 重开秒呈现 + optimistic UI (like/upload)
- GitHub OAuth Device Flow Phase 1(client_id Ov23liyv3nEucG7oMHNE,登录后自动填 marketplaceDevLogin)
- 上传 picker 两步式确认 + z-index 修复
2026-05-16 17:26:35 +08:00
baiqing
ec60f22761
fix(marketplace): 上传 picker 两步式确认 + z-index 修复(被「我的发布」覆盖)
...
UX 问题:用户在「我的发布」点 上传 → 选 pack 卡片 → 直接上传(无确认);
+ 弹框被「我的发布」(zIndex 50) DOM 顺序后渲染挡住。
修法:
1. Modal helper 接受可选 zIndex prop(默认 50);上传 picker 用 zIndex={60} 叠在 mypacks 之上。
2. 新 selectedUploadPackId state:点 pack 卡片切换选中(不立刻上传),蓝边 + 蓝实心圆 ✓。
3. 底部加「确定上传」按钮,未选中时 disabled;取消按钮重置选中状态。
4. openUploadPicker:更新流程下预选「建议更新」的同名本地包(多数情况下一键确认)。
5. onUpload / cancel 退出时 setSelectedUploadPackId(null) 清状态。
2026-05-16 17:18:56 +08:00
baiqing
86d89760d8
chore(oauth): 填入 GitHub App client_id Ov23liyv3nEucG7oMHNE
...
OAuth App client_id 非敏感信息,可直接 bake 进开源代码;secret 不进库。
GitHub App 的 Device Flow 行为:scope 参数被忽略,权限由 GitHub App 端配置;
其它流程跟 OAuth App 一致。
2026-05-16 17:09:00 +08:00
baiqing
54b5ec751c
feat: marketplace 按钮移到 refresh 左 + list cache 秒开 + GitHub OAuth Device Flow Phase 1
...
Style.tsx:
- 「风格市场」按钮从 PageHeader.titleRight 移到 .right 槽,放在「刷新」按钮左边(按用户需求)。
- 删除 titleRight 那块独立的 pill 按钮,与其他动作按钮统一为 Btn variant=ghost icon=cloud。
ipc.ts:
- 加 sessionStorage 缓存:readMarketplaceListCache / writeMarketplaceListCache,5 分钟 TTL。
- 加 GitHub OAuth Device Flow IPC wrappers:githubDeviceFlowStart / githubDeviceFlowPoll + 类型定义。
commands.rs:
- 新增 github_device_flow_start:调 GitHub /login/device/code,返回 user_code/device_code/verification_uri。
- 新增 github_device_flow_poll:轮询 /login/oauth/access_token,拿到 token 后调 /user,返回 login。
- GITHUB_OAUTH_CLIENT_ID 常量:留空 + 详细错误指引(去 https://github.com/settings/applications/new 注册);
支持环境变量 GITHUB_OAUTH_CLIENT_ID 覆盖(dev 方便)。
lib.rs:
- 注册 github_device_flow_start / github_device_flow_poll 到 invoke_handler。
Marketplace.tsx:
- items state 用 readMarketplaceListCache() 做 lazy init —— 重开「风格市场」秒看到上次默认视图。
- refresh() 拿到默认视图(popular + 空 query)的列表后写缓存。
- 加载 UI 改成 (loading && items.length === 0) —— 有缓存时不显示「加载中」遮挡数据。
- OAuth 状态机:idle → starting → pending (展示 user_code) → success / error。
- 「我的发布」弹框里的 @login chip 点击 → 触发 GitHub OAuth Device Flow;浏览器自动拉起 verification_uri;
轮询直到用户授权完成;拿到 login 后自动 updatePrefs(marketplaceDevLogin)。
- 新 OAuth 弹框 UI:大字号 user_code + 一键复制 + 打开浏览器 + 取消 + waiting pulse。
Phase 2 留给后续:marketplace backend 验证 GitHub JWT 防止 X-Dev-User 伪造。
2026-05-16 17:01:54 +08:00
baiqing
8df0a3e140
feat(marketplace): 硬编码云端 URL + 我的发布弹框新布局 + 已下架自动消失 + Modal 进入动画
...
commands.rs:
- 新增 MARKETPLACE_BASE_URL = "https://apic.openless.top " 常量,marketplace_url_from_prefs 忽略 prefs 强返该常量。
- 避免用户在 Settings 改错地址导致连不上;GitHub OAuth 上线后再开放白名单 / 多 endpoint。
Settings.tsx:
- 删除 marketplaceBaseUrl 输入行 + onMarketplaceBaseUrlChange callback;保留 marketplaceDevLogin 输入(OAuth 上线前的过渡)。
Marketplace.tsx:
- 「我的发布」弹框顶部改一行:[🔍 搜索...] (flex 1) + [A @login] chip + [×] 关闭。
- 第二行:计数信息(左)+ 刷新 + 上传(右)。
- 已下架包 5 分钟后自动从「我的发布」隐藏:visibleMyPacks 加 withdrawn cutoff filter;弹框打开期间 setInterval(30s) tick nowTick。
- @login chip 点击触发 toast 提示 GitHub Device Flow 开发中(下轮接入);保留 marketplaceDevLogin 作过渡身份。
- Modal 组件加进入动画:backdrop fade-in (.14s) + card scale-up (.18s, spring easing)。
- 清理:删未用的 accountTitle 变量。
ipc.ts:
- mockSettings.marketplaceBaseUrl 默认值改成 "https://apic.openless.top "(与后端硬编码一致)。
2026-05-16 16:47:41 +08:00
baiqing
b5db6482fe
feat: 我的发布 modal + Pro v1.7 默认提示词对齐 + optimistic UI
...
Marketplace.tsx:
- 「我的发布」从 sort mode 改为弹框形态(与上传/详情共用 Modal 组件),叠在风格市场页面之上。
- 弹框结构:标题 + 账户信息(avatar/login/已发布数/审核中数)+ 搜索(左上,独立 myPacksQuery state)+ 刷新 + 上传 + 单列包卡片。
- 页面右上角 上传 按钮删除,避免与弹框内 上传 重复;保留「我的发布」入口 + 刷新。
- onLike: optimistic UI —— 点击立即切红心 + 调 likeCount,服务端回来后用真实值校准,失败 rollback。解决用户点了感觉没生效的问题。
- onUpload: optimistic UI —— 拿到 200 立即把新包推到 myPacks 最前(状态用服务端返回的 'pending'),后续 1.5s/5s polling 校准真实数据。解决等 polling 才显示的「上传特别慢」感知。
types.rs:
- PolishMode::Structured 整段回归 v1.7 设计哲学:砍掉 v1.5/v1.6 的失败定义堆叠 + 判定流程 + 事项切分算法等元规则;保留简洁规则块;用高密度演示性示例让模型学结构化模式。
- PolishMode::Light 新增「# 语音纠错 + 主动推断」段(按置信度分级处理 ASR 误识别 → Token/Secret Key/Claude/Gemini 等技术词主动修正)。
polish.rs:
- 同步更新 Structured prompt 单元测试 assert,防回归到 v1.4/v1.5/v1.6 关键词。
2026-05-16 16:23:52 +08:00
baiqing
68b9af3086
feat(polish): Pro 风格"清晰结构"提示词迁移 + 配套前后端改动
...
types.rs:
- PolishMode::Structured 整段重写为 Pro 风格短版(强制结构化触发 / 场景优先级 / 输出格式 / 术语纠错 / 反 AI 自述)。
- builtin StylePack Structured 的 description / example / tags 对齐 Pro。
- Phase B 强化:任务首段加"多事项罗列";强制结构化触发新增"3+ 完整句";新增示例 3 产品 UI 反馈。
前后端配套调整(一起进迁移):
- backend: commands.rs / lib.rs / polish.rs / persistence.rs。
- frontend: ipc.ts / types.ts / pages/Marketplace.tsx("我的发布"等)/ pages/Style.tsx。
Pro pack 镜像:~/Downloads/openless-pro-packs/OpenLess-清晰结构-PRO.zip 升级到 1.4.0(旧版 .bak-<timestamp> 自动备份)。
ipc.ts mockFullStylePrompts 保持简化 dev 预览,未跟随生产 prompt(与 Tauri 生产路径解耦)。
cargo check ✓(src-tauri workspace)。
2026-05-16 14:06:22 +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
baiqing
461a4f3fa7
fix(prompt): keep '5) 自动纠错' prefix so guard test still matches
...
CI 测 polish::tests::common_rules_include_auto_correction_and_natural_organization
hard-codes 'prompt.contains("5) 自动纠错")' 作为 guardrail;上一 commit 把前缀
改为「ASR 主动纠错」导致 4 个 mode 都失败。前缀回到「自动纠错」,新增说明
夹在括号里:'5) 自动纠错(ASR 主动纠错,按置信度分级处理)'。
2026-05-15 22:31:33 +08:00
baiqing
2b6f478018
chore(stable): disable marketplace entry + strengthen ASR correction rules
...
Marketplace entry → 灰色 pill + 点击 toast「暂时未开放」
- 风格市场云端服务尚未上线,先在 UI 层禁用入口
- 真正功能(Marketplace 组件 / IPC / backend client / MarketplaceModal)全部保留
- 后续云端就绪时改回 onClick={() => setMarketplaceOpen(true)} 即可恢复
- 配色从蓝色 pill 改为灰色,明确视觉禁用语义
ASR 主动纠错升级(吸收「完全重写」社区 prompt 的优点)
- types.rs::COMMON_RULES 规则 5 重写:
- 三级置信度策略(高 → 直接换;中 → 最优候选;低 → 保留)
- 中文音译 → 英文技术词还原(脱肯/西克瑞特/埃克塞斯 Token...)
- 技术字段大小写规范化(API/App ID/Access Key/Secret Key/OAuth/JWT/UUID 等 12+ 字段)
- 大小写敏感场景例外(代码变量 / Bash / 路径 / URL 段保留)
- 新增规则 6:禁止输出修改说明 / 原文对比 / 编造字段,所有模式无例外
- 4 个 builtin pack(Raw/Light/Structured/Formal)共享 COMMON_RULES 自动获益
2026-05-15 22:18:58 +08:00
Chris233
1ab231df65
Merge pull request #446 from H-Chris233/fix/issue-440-streaming-default-migration
...
Keep streaming insertion enabled for migrated prefs
2026-05-15 19:30:48 +08:00
baiqing
3cedb72a1d
feat(marketplace): derivative version + like toggle + my-pack management + UX polish
...
客户端配套 backend marketplace 更新:
衍生版本链路:
- StylePack 新增 originPackId / originAuthorLogin(仅 set_origin 通道写入,
普通 save 路径不会清掉)
- marketplace_install 拉 detail → 安装后写 origin 绑定
- marketplace_upload 把 originPackId 塞到 multipart;本地原创首发后回写自己
的远端 id 当 origin,让同设备后续走 supersede
- 卡片 / 详情 / 本地列表都加「衍生自 @<author>」绿色 pill;过滤掉自己 != 衍生
点赞 + 我发布的:
- 新增 marketplace_my_likes IPC(GET /me/likes)→ likedIds Set
- 心形 ♥/♡ + 红色已赞 + spring 缩放动画
- 排序栏第 3/4 项「我赞过的」/「我发布的」客户端 filter
发布流程:
- 编辑器 dirty 时自动 save 再 upload,不再阻塞
- 内置 pack 双层禁发(按钮 disabled + handler early return)
- 上传 picker 过滤 builtin
- 失败 toast 6s 自动消失
- Style 页 toast 改右下角,避开「导入 ZIP」按钮
风格市场弹窗:
- 整合到 Style 页面 modal(左侧 nav 去掉 marketplace tab)
- 右上 32×32 圆角 X 按钮;左上「@<login>」登录身份 pill
- 内容区右下角小 toast,从右滑入 / 停留 / 向右滑出
- 详情页右下「🗑 撤回发布」(仅作者可见,confirm 后 DELETE /packs/:id)
2026-05-15 19:14:46 +08:00
H-Chris233
b0c469dbd4
Keep streaming insertion enabled for migrated prefs
...
Older builds could persist the old default streamingInsert=false into preferences.json, which is indistinguishable from a user opt-out if the value is treated as an ordinary bool. Add a one-time migration marker, persist normalized legacy prefs on load, and keep later user opt-outs durable after migration.
Constraint: issue #440 requests streaming input/output defaults on for fresh installs and updates, plus release notes for the behavior change.
Rejected: treating any stored false as manual opt-out | old default writes would keep update users off forever.
Rejected: overriding every false forever | would erase a user's post-migration manual opt-out.
Confidence: high
Scope-risk: moderate
Directive: Keep UserPreferencesWire serde defaults and migration markers aligned with persisted UserPreferences fields.
Tested: cargo test --manifest-path src-tauri/Cargo.toml --lib streaming_insert -- --nocapture
Tested: cargo test --manifest-path src-tauri/Cargo.toml --lib legacy_streaming_insert_false_is_migrated_and_marker_is_persisted -- --nocapture
Tested: npm run build
Tested: cargo test --manifest-path openless-all/app/src-tauri/Cargo.toml --lib
Tested: git diff --check
Not-tested: cargo fmt --check fails on pre-existing unrelated formatting drift in volcengine.rs and commands.rs.
2026-05-15 18:59:45 +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
baiqing
9e119e2a2f
fix(marketplace): stale detail response guard (pr_agent #444 round 3)
...
跟 round 2 同类 race condition:openDetail 用户快速点两张卡片时,先点 A 的
detail response 可能晚于 B 到达 → 覆盖 B 的 detail / Install / Like 作用错对象。
修:复用 useRef seq token 模式,detailSeqRef 单调递增,response 比较 seq 丢
弃 stale。逻辑跟 refresh() 完全对称。
2026-05-15 14:52:22 +08:00
baiqing
4af7fec82f
fix(marketplace): UUID-v4 whitelist on pack_id + stale-response guard (pr_agent #444 round 2)
...
pr_agent 提的两个真问题:
1. Security: arbitrary file write —— marketplace_install 用 backend-controlled
pack_id 拼临时文件路径。若 backend 被攻陷返回路径遍历 id,可写客户端任意位置。
修:4 个 marketplace_* IPC 全部加 is_valid_session_id() UUID-v4 白名单
校验(跟 read_audio_recording 同套 boundary 校验)。
2. Race condition: 搜索 refresh 旧请求晚到覆盖新结果。用户连续输入时体验差。
修:useRef 单调递增 seq token,response 到了 check seq 是否最新,stale 直接丢。
cargo test 263 全过;tsc 0 error。
2026-05-15 14:48:05 +08:00
baiqing
242f7ef8cd
feat(marketplace): one-click publish + Settings marketplace config (goal 2.e)
...
Goal 2.e 一键发版到云端:
- Style.tsx 详情页加「发布到风格市场」按钮(在导出 ZIP 旁),
调既有 marketplace_upload IPC。disabled 直到用户在 Settings 配 GitHub login。
- Settings.tsx 加「风格市场」折叠区:marketplaceBaseUrl + marketplaceDevLogin
两个 input。base URL 空 = localhost:8090 dev 默认;生产填 https://api .<domain>。
- 5 语言 i18n(zh-CN/en/ja/ko/zh-TW)补全 marketplace 相关 settings 文案
cargo test 263 全过;tsc 0 error。
2026-05-15 14:25:51 +08:00
baiqing
c9c126730c
feat(marketplace): Phase A — 客户端 Marketplace tab (Goal 1)
...
围绕 user goal 1 (a-e) 实现:
- (a) 后端验证:5 个 marketplace_* IPC,HTTP 通过 reqwest 调 backend
- (b) 上传与拉取无问题:marketplace_install / marketplace_upload 复用本地 ZIP IPC
- (c) 单独弹窗:详情 + 上传选包器走 Modal 半透明背景中央卡片
- (d) 搜索框:顶部 input 300ms 防抖 + server-side ?q= 过滤
- (e) 按排名推荐:默认 sort=popular(按 like_count DESC)
后端 IPC(commands.rs):
- marketplace_list / detail / install / upload / like
- HTTP base URL 走 prefs.marketplace_base_url(默认 http://127.0.0.1:8090)
- dev-mode auth 走 prefs.marketplace_dev_login → X-Dev-User header
- install 路径:下载 ZIP 到 temp → 调既有 import_from_zip → 删 temp
- upload 路径:export 本地 pack → multipart POST → 后端入审核队列
UserPreferences 加两个字段(5 处 wiring:struct/Wire/Default×2/Deserialize):
- marketplace_base_url (空 = localhost:8090 默认)
- marketplace_dev_login (空 = 上传按钮 disabled)
前端:
- types.ts 加 MarketplaceListItem / MarketplaceDetail
- ipc.ts 加 5 个 wrapper + mock fixture(vite dev 模式仍可演示 UI)
- pages/Marketplace.tsx (~350 行) UI:搜索 / 排序 toggle / 卡片 grid / 详情弹窗 / 上传选包器
- FloatingShell.tsx + state/useAppState.ts 加 marketplace AppTab
- 5 语言 i18n 补全 nav.marketplace + marketplace.* 子树(en/zh-CN/zh-TW 完整,ja/ko nav 本地化 + 子树 fallback en)
cargo test 263 全过;tsc 0 error。
2026-05-15 14:02:55 +08:00
baiqing
efa94afc27
chore(release): bump version to 1.3.2-3 (Beta)
2026-05-15 12:35:02 +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
baiqing
55c0c64671
fix(llm): don't retry on timeout to avoid duplicate billing (pr_agent #443 round 2)
...
pr_agent #443 review 指出 Duplicate Request 风险:
> Retrying after send() fails can submit the same LLM request twice if the
> first attempt already reached the provider but the connection dropped
> before a response was returned. For non-idempotent completion calls, that
> can mean duplicate billing and two completions for one user action.
事实分析 reqwest::Error 各 variant:
- is_connect() → TCP 握手没建立,server 不可能收到 → 安全 retry
- is_request() → HTTP 请求层错误(构造问题),server 没收到完整请求 → 安全 retry
- is_timeout() → client 设置的 timeout 到了,server 可能已经收到并在处理
(非幂等 completion 调用)→ 不安全 retry,会重复 billing
修法:从 retry 触发条件移除 `|| e.is_timeout()`。timeout 直接返回
LLMError::Timeout,不再重试。
user 实际看到的失败模式是 "error sending request"(reqwest::is_connect),不在
被移除范围内,覆盖率不变。
注:pr_agent 同轮提的 "Ticket compliance ❌ Not compliant 442" 是 false positive
—— pr_agent 把 PR description 里提到的 #442 当成本 PR 的 ticket id;#442 是另一
个独立 PR(A-D 默认值 / 提示词重构),已 merge。
cargo test 263 全过。
2026-05-15 11:43:39 +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
baiqing
eb5430dc4a
fix(llm): retry transient network errors once before failing polish
...
用户反馈日志显示 streaming polish 偶发失败:
[coord] streaming polish FAILED: network error:
error sending request for url (https://api.deepseek.com/v1/chat/completions )
诊断:失败发生在 reqwest `request.send().await` 阶段(不是 HTTP 4xx/5xx),
属于 connect / request / timeout 三类 transient 错误。同一 session 内大量
polish 调用成功,证明网络没断、是间歇性抖动。
修法:抽 `send_with_transient_retry` helper,首次失败 + 错误是 transient
(is_connect / is_request / is_timeout)→ sleep 500ms 重试一次。retry 第二次
失败按原 LLMError::Timeout / Network 返回。HTTP 4xx/5xx 走 response.status()
分支不受影响。
适用范围:3 处 reqwest send 都用 helper:
- chat_completion_messages_streaming (SSE 流式,line 720)
- chat_completion_messages_streaming 的非流式兄弟 (line 591)
- send_chat_request 一次性 chat 调用 (line 517)
retry 安全前提:传入 RequestBuilder body 必须是内存型(json/form),用
`try_clone()` 复制;3 处都满足。对流式路径 retry 安全是因为失败发生在 SSE
开始前 → on_delta 必然未被调用 → 不会重复输出。
注:Codex OAuth 路径(line 1085)使用独立 client,结构不同,本 PR 不动;
后续如有相同抖动反馈再扩展。
cargo test 263 全过。
2026-05-15 11:37:34 +08:00
baiqing
0db0ce4e5e
feat: streaming-on by default, structured default mode, agent-style hotword block in middle
...
围绕 user 反馈(用户主动测试后给的迭代清单)做 4 项调整:
A. streaming_insert 默认 false → true
- 流式落字感知延迟低,所有 fallback case 都已经接好
- CJK IME / Codex / Gemini provider 自动回落到一次性路径,对存量用户无感
- 影响 mock + test fixture 一并更新
B. default_mode: PolishMode::Light → PolishMode::Structured
- 新装用户开箱默认走清晰结构 mode,对多事项口述场景更合适
C. 热词与纠错模块从「prompt 末尾」移到「ROLE_BLOCK 之后」
- 新增 types.rs::HOTWORDS_PLACEHOLDER = "{{HOTWORDS}}"
- default_style_system_prompt_for_mode 拼接顺序:
ROLE_BLOCK + {{HOTWORDS}} + task_and_example + COMMON_RULES + OUTPUT_BLOCK
- polish.rs::build_hotword_block 统一生成 agent-style 错别字纠正 + 热词列表文本
(明确告诉模型「转写来自 ASR 可能含错别字」,比旧的「热词:...」措辞更强)
- polish.rs::compose_system_prompt 找占位符替换;用户自定义 prompt 无占位符 + 热词
非空 → 末尾追加(兼容历史);空热词 → 原 prompt 不变(保留旧测试断言)
- preview 跟 system prompt 用同一段文本,消除「设置看一段,发给 LLM 是另一段」不一致
D. Style Pack 新建预填示例 prompt
- Style.tsx 新增 NEW_PACK_PROMPT_TEMPLATE 含 # 角色 / {{HOTWORDS}} / # 任务 /
# 通用规则 / # 输出 4 段结构,用户照着改即可
- 让用户看到 placeholder 怎么用,决定是否保留/移动/删除
cargo test 263 全过;tsc 0 error。
测试反馈:本地构建 1.3.2-2 装到 /Applications 跑通 A-D 全部功能,无回归。
2026-05-15 11:26:49 +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
baiqing
1234dcd9c0
fix(asr): shorten 403 message to '凭据被拒(403)'
...
原文案带 Settings 引导 + SAUC 资源说明,capsule 上显得拥挤。简化到 5 个字 + 状态码,原因留在错误类型 doc comment 里。
2026-05-15 09:28:07 +08:00
Chris233
b792caabf9
Merge pull request #437 from H-Chris233/issue-420-wayland-p1
...
Prevent Wayland dictation text loss
2026-05-15 09:23:06 +08:00
baiqing
5c9ebe1930
fix(asr): surface Volcengine 403 as 'credentials rejected' instead of raw HTTP error
...
用户反馈"豆包云端语音连接不了",日志显示 9 次相同的:
[coord] open ASR session failed: connection failed: HTTP error: 403 Forbidden
原因不是网络不通(网络通到 Volcengine 才会有 HTTP 状态码),而是凭据被拒:
App ID / Access Token 错、或账号没开通 SAUC bigmodel 资源。当前 capsule
显示原始 `HTTP error: 403 Forbidden`,用户看不懂、客服需要解释一遍。
修法:在 volcengine.rs 加 classify_connect_error 函数,握手收到 401/403 时
转成新的 VolcengineASRError::AuthRejected(status) variant;它的 thiserror
display 直接是中文「凭据被拒(HTTP 403):请检查 Settings → 凭据 → Volcengine
的 App ID 和 Access Token,或确认账号已开通 SAUC bigmodel 资源」。
coordinator 沿用原有错误透传路径,capsule 文案自动从英文变中文具体引导。
其它握手错误(DNS / TLS / connection refused)仍走 ConnectionFailed,文案不变。
cargo test 258 全过。
2026-05-15 09:20:43 +08:00
baiqing
5695d8c467
chore(release): bump version to 1.3.2-2 (Beta)
2026-05-15 08:42:08 +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
H-Chris233
c4cfad3632
Align Wayland trigger guidance
...
Wayland now uses CLI-triggered dictation and copy-only output safety, so user-visible Linux hints should not imply that Wayland streaming insertion or global hotkeys are still attempted inside OpenLess.
Constraint: Wayland trigger support is currently delivered through desktop shortcuts invoking openless CLI flags.
Rejected: Leaving old best-effort Wayland wording in Settings | It conflicts with Phase 1 copy-only behavior and the CLI trigger path.
Confidence: high
Scope-risk: narrow
Directive: Keep future portal/libei language in research docs, not current-product Settings hints.
Tested: npm run build; cargo check --manifest-path openless-all/app/src-tauri/Cargo.toml; git diff --check
Not-tested: Live Wayland Settings rendering on GNOME/KDE/Hyprland/sway.
2026-05-15 07:44:44 +08:00
H-Chris233
5fd991ae1f
Prevent Wayland dictation text loss
...
Wayland cannot prove synthetic typing or paste delivery, so Linux Wayland now bypasses streaming success semantics and keeps completed dictation text in the clipboard with explicit fallback status. The Phase 1 plan is recorded at repo root to keep follow-up work aligned with the current CLI trigger decision.
Constraint: Wayland security model does not provide X11-style global key or cross-app text injection guarantees.
Rejected: Treating enigo or simulated paste success as inserted on Wayland | It can report success while the target app receives no text.
Confidence: high
Scope-risk: narrow
Directive: Keep Wayland automatic input as an explicitly probed future enhancement; do not silently restore fake insertion success.
Tested: cargo fmt --manifest-path openless-all/app/src-tauri/Cargo.toml; cargo test --manifest-path openless-all/app/src-tauri/Cargo.toml --lib coordinator::dictation; cargo test --manifest-path openless-all/app/src-tauri/Cargo.toml --lib focus_restore_failure_uses_specific_error_code; git diff --check; cargo check --manifest-path openless-all/app/src-tauri/Cargo.toml
Not-tested: Real Wayland compositor clipboard behavior and Windows TSF runtime behavior on native hosts.
2026-05-15 07:41:08 +08:00
baiqing
3125de88fb
fix: normalize TOCTOU NotFound in read_audio_recording (round 3)
...
pr_agent round 3 提的 Missing-file handling 兜底:
read_audio_recording 在 exists() 检查后到 tokio::fs::read 之间,文件可能因
prune(条数 cap / retention 清理 / 用户手动删)而消失。原实现把 NotFound 错
格式化成 'read wav failed: No such file...' 这种 OS 原文,前端字符串匹配
'recording not found' 接不住,UI 显示一次 generic error 而不是隐藏按钮。
修法:read 失败时显式判 ErrorKind::NotFound → 返回跟 exists() 失败同样的
'recording not found' 字符串。前端单条 catch 就稳,不依赖本地化 OS 错误。
注:pr_agent 同轮提的 "Build break"(arch.lock() 返回 Result)是 false positive
—— recorder.rs 用 parking_lot::Mutex 而非 std::sync::Mutex,.lock() 直接返回
MutexGuard 无 Result。本地 cargo test 258 全过、3 平台 CI build 都绿可证。
cargo test 258 全过。
2026-05-14 21:31:47 +08:00
baiqing
67d1277361
fix: address pr_agent Stale closure + Missing file check (round 2)
...
pr_agent 二轮 review 又提了 2 个 issue,本 commit 解决:
Stale closure (AutoUpdateGate.tsx)
- 原 useEffect 依赖 [enabled],tick 闭包捕获渲染时的 u;u.status 变化时 tick
读不到新值,极端时序下 60min interval 触发的 tick 可能在用户已经手动打开
UpdateDialog 的情况下错过 busy 检查,并发触发第二次 checkForUpdates。
- 修:useRef(u) + 每次渲染同步 uRef.current = u;tick 读 uRef.current 始终拿
最新 useAutoUpdate 返回值,避免 stale 状态判断。
Missing file check (History.tsx)
- hasAudioRecording 只代表「录音时 wav 写盘成功」,但 audioRecordingMaxEntries
/ historyRetentionDays 之后 prune 可能已删 wav。前端无条件渲染按钮 → click
→ 后端返回 'recording not found'。属于 UX 不优雅(功能有 error UI 兜住)。
- 修:父组件加 audioMissingIds: Set<string>;AudioRecordingPlayer 加 onMissing
回调,遇 'not found' 错误时调用父让 id 加入 set;导出按钮 catch 同样错误
也 mark missing;按钮组渲染条件改为 hasAudioRecording && !audioMissingIds.
has(id),一次点击后永久隐藏,不再让用户撞同样的 error。
cargo test 258 / tsc 0 error。
2026-05-14 21:15:18 +08:00
baiqing
13839a2fd4
fix: address pr_agent Wrong Flag + Missing Audio review
...
pr_agent review #436 提了两个跟 has_audio_recording 字段相关的 issue,本 commit
全部修复:
Wrong Flag (dictation.rs:1551 polish 收尾路径)
- 原实现:has_audio_recording: Some(prefs.record_audio_for_debug)
以「开关是否打开」当真值。但 Recorder::start 内部 WavArchiver::create 可能失败
(recordings/ 创建不出 / 权限不足 / 磁盘满),开关 on 但 wav 没真写盘时,前端会
渲染播放按钮、点击得到 'recording not found'。
- 修法:Recorder::start 返回值加第三个 bool = archive_active。Inner 加
audio_archive_active: AtomicBool 字段,begin_session 时写入。history 写入路径
读这个字段而不是 prefs,反映真实写盘状态。
Missing Audio (dictation.rs:1233 empty-transcript 失败路径)
- 原实现:has_audio_recording: None,即使开关 on 也强行 None。但磁盘上 wav 仍存在
(recorder 已走完整段录音)。讽刺的是:ASR 没识别到文字恰好是用户最想听原始录音
诊断的场景。
- 修法:empty-transcript 路径同样用 inner.audio_archive_active.load(...)。
与 polish 路径一致。
副作用最小:
- SessionState 不动(不污染 10 个构造点)
- 改 Inner 加一个 AtomicBool(2 个构造点已补)
- Recorder::start 返回元组扩第 3 项;3 个调用点(dictation/QA/preview)都已更新
cargo test 258 全过。
2026-05-14 20:56:05 +08:00
baiqing
366f6c38eb
feat: polish hotword overrides, audio archive, auto-update check
...
围绕"ZIP 误识别 + AI 日报反馈"一波集中改动,共 7 项:
polish 提示词
- COMMON_RULES 规则 2 加热词例外(热词列表正确写法优先于"原样保留")
- 规则 5 自动纠错补英文短词同音示例(ZIP/VIP)
- 规则 2 显式举例带次版本号产品名(GPT-5.6 / Claude 4.7 / iOS 26.1)不省略小数
- Structured mode 主题数上限放宽给"多条独立新闻/日报"场景
- Structured mode 要求主题标题保留关键实体名
- 热词块尾部加「优先于规则 2」强约束
History 详情面板
- 修复复制按钮没 await 没反馈的 bug(1.5s 显示"已复制")
- 加 AudioRecordingPlayer(按需加载 wav → Blob → 原生 audio controls)
- 加导出录音按钮(anchor.download 触发浏览器下载)
Settings 新增
- 自动检查更新开关(默认 on,启动延 4s + 60min interval)
- 历史条数上限输入框(5–200,留空 = 200)
- 保留原始录音开关 + 录音条数上限输入框
录音归档(debug 开关版)
- recorder.rs WavArchiver(Drop 时回填 RIFF/data header)
- DictationSession.has_audio_recording 字段;history.id 与 SessionId 对齐
- persistence.rs 加 recordings_root / recording_path_for_session
- prune_recordings 双重 cap(days + count),仅扫 .wav,metadata 失败按过期处理
IPC 安全 + 性能
- read_audio_recording 改 async + tokio::fs::read 防阻塞主循环
- session_id 白名单 UUID-v4 字面校验 + 2 个单测覆盖路径越界
后续待优化(review 标记 H2 / M1 / M3):
- prune 在录音前调用,cap 边界少裁 1 条(最多多 1 个文件,不爆盘)
- WavArchiver Drop unwinding 时 header 长度兜底(QuickTime 可能拒播)
- has_audio_recording 跟磁盘脱钩(前端 catch 'recording not found' 已兜住)
cargo test 258 全过;tsc 0 error;5 语言 i18n 完整。
2026-05-14 20:27:05 +08:00