docs: add code quality cleanup roadmap (fixes #699)#778
Conversation
* fix(insertion): macOS 让「恢复剪贴板」开关真正生效,修复被回退的 Open-Less#525 Open-Less#525 的 macOS 剪贴板恢复(commit 4b4d219)被 Android 移植 commit 1ea467a 连同 cfg 门控一起回退:整套恢复机制被重新 #[cfg] 排除出 macOS,macOS insert() 退回「只写剪贴板 + Cmd+V」,于是设置里的「恢复剪贴板」开关在 macOS 上完全无效 ——无论开关开还是关,剪贴板都被留成转写文字。 - 把共享的剪贴板恢复机制(ClipboardRestorePlan / schedule_clipboard_restore / restore_clipboard_after_delay / should_restore_clipboard 等)的 cfg 从 not(any(macos,android,ios)) 放宽为 not(any(android,ios)),重新纳入 macOS (移动端仍排除;Windows/Linux 不变)。 - macOS insert() 改为:保存原剪贴板 → 写转写文字 → Cmd+V → 仅当「恢复剪贴板」 开关开启且粘贴成功时按 CLIPBOARD_RESTORE_DELAY 延迟恢复;粘贴失败则保留转写 文字供手动粘贴、不恢复。开 = 恢复,关 = 保持现状,开关说了算。 - 删除随之失效的 macos_insert_status_after_paste;恢复相关单测在 macOS 也运行。 Windows/Linux 专用项(insert_with_clipboard_restore / paste_keys / simulate_paste(shortcut) / 其 insertion_success_status 变体)的 macOS 排除保持不变。 * fix(less-computer): 护栏 sudo/dd/chmod/chown 等设为不可批准,修批准注入畸形规则 (Open-Less#679) 旧审批流对所有命中 pattern 一律 format!("Bash({p}:*)") 生成 allow 规则: - 对带空格/参数的子串("sudo " → "Bash(sudo :*)","dd if=" → "Bash(dd if=:*)", "chmod -r 777 /"、"chown -r")生成的串既不等于 deny "Bash(sudo:*)" 等(故 deny 没被移除、批准静默失效),又把畸形规则注入了 allowed_tools。 改为:新增 guard::deny_rule_for_pattern(pattern),把「可批准」命令(rm -rf / git push --force / git reset --hard / git clean 等)映射到其在 default_deny_rules 里的 **精确** deny 规则;提权/毁盘/系统级命令(sudo / dd / mkfs / chmod / chown / shutdown / reboot / 管道执行 / fork 炸弹)返回 None = 不可批准,即使被批准也保持拦截 (fail-closed),不再注入畸形 allow。审批流用 filter_map 取精确规则,移除的 deny 与 加入的 allow 严格一致。 附单测:每个可批准 pattern 的映射规则确实存在于 default_deny_rules;危险系统命令一律 不可批准。 * fix(ui): unify macOS shell top inset with sidebar/background surface The 28px top inset that clears the macOS overlay traffic lights sat on the content wrapper with no background, so it exposed the lighter WindowChrome glass (--ol-window-bg) as a full-width band above the sidebar and panel — reading as an extra top row that the Windows build (no inset) doesn't have. Paint that wrapper with the same --ol-app-shell-bg the sidebar/background already use, so the inset blends into one continuous surface with the panel card floating on top. One property; token carries its own light + dark values; no layout, token, or other UI changes. * fix(capsule): 胶囊跟随鼠标光标所在屏,修复多屏/多 Space 只在第一块屏显示 多屏 + 多桌面下胶囊只出现在第一块屏、其他屏闪一下就消失。根因:定位 (position_capsule_bottom_center) 用 AX caret 选屏,多数 App 取不到 caret 矩形就 跌回胶囊自己的 current_monitor(第一块屏);而去重缓存 capsule_layout_snapshot 也 用 current_monitor,与定位看的不是同一块屏 → 胶囊一落第一块屏即被判“无变化”跳过 重定位,被锁死。两条路都没跟踪鼠标。 - 新增 mouse_cursor_point():CoreGraphics CGEventGetLocation 取光标,坐标系与 AX 一致、零权限、永远可用 - focused_input_target_monitor → capsule_target_monitor,选屏改“光标优先”(caret 仅兜底),选屏循环抽成 monitor_for_anchor_point - 定位与去重快照统一走 capsule_target_monitor,彻底消除不一致 - 保留 CAN_JOIN_ALL_SPACES(Spaces 契约强制且本就正确:帧落在光标屏→只那块屏显示) - 扩展 macos-capsule-spaces 契约测试,grep 守护“光标优先 + 两路共用同一函数” Windows 路径未动(本就跟随输入 App 屏且快照一致)。 验证:cargo check + lib 纯函数单测 + check:macos-capsule-spaces 全过。 * feat(less-computer): 边缘发光改用 EdgeGlow 式四层模糊堆叠 + iridescent 配色 把 Less Computer 全屏彩虹边缘发光从原来「两条都带模糊的软光带」改成 EdgeGlow(github.com/vector4wang/EdgeGlow,MIT)的实现方式,让彩虹「实线」 更清晰、发光更有层次。 - 4 层模糊堆叠:最内一条 0.5px 锐利彩虹实线(对应 EdgeGlow blur=0 那层, 定义边缘),外面叠 3 层逐渐变宽、变虚的光晕(blur 3/8/15px),得到 霓虹灯管式发光。原生用 CAShapeLayer + 高斯模糊,这里用 CSS「边框遮罩环 + filter:blur」等价复刻。 - 配色移植 EdgeGlow 默认的 iridescent(Apple Intelligence 高饱和霓虹: 紫→蓝→青→绿→金→橙→红→粉),按 12 段均匀铺满 360°。 - 流光整圈 8s/圈通过 --lcg-angle 旋转(只动渐变角度不动元素形状,GPU 友好), 光晕层各带不同周期呼吸;实线用 normal 保色准,光晕用 screen 叠加增亮。 保留 issue Open-Less#470 的隐藏即卸载发光层(零 GPU)逻辑、pointer-events:none、 prefers-reduced-motion 降级与 --lcg-radius 圆角变量。纯视觉、仅 macOS、单文件。 * fix(vault): Windows 凭据读取瞬时失败重试,修概率性「火山引擎未配置」 Windows Credential Manager(CredReadW)在登录后/并发下读 manifest+各 chunk 条目 时可能瞬时失败。任一条目读失败会让整组凭据看起来为空 → load_keyring_credentials 返回 Err → load_credentials 回退到空默认值 → 概览页显示「火山引擎未配置」,但下 一次听写重读成功,所以是「概率性」且「实际可正常使用」。凭据 chunk 越多,单次 load 读取次数越多,命中瞬时失败的概率越高。 修法:仅在 Windows 上对非 NoEntry 的读错误做几次短退避重试(4 次 / 60·n ms)。 NoEntry 是确定的「未存储」,立即返回不付重试延迟。macOS/Linux 保持原单次行为 ——它们的读错误是 ACL 拒绝,重试无益,且未缓存的错误路径下次调用本就会重读, 加 sleep 只会拖慢 macOS 首次 Keychain 授权流程。纯后端、cfg 隔离,零 PC-macOS 影响。 * fix(asr): 火山引擎连接增加超时与重试,修弱网下无法语音输入 connect_async 此前既无超时也无重试:弱网下 TLS/WebSocket 握手可能挂到 OS 级 TCP 超时(几十秒),期间用户卡在「Starting」;而协调器全局超时只覆盖 await_final_result,不覆盖 open_session,所以握手挂起完全不受约束。单次网络 抖动(连接被重置/瞬时 DNS 失败)也会直接让整次听写失败。 修法:每次握手包 5s 超时,最多 3 次尝试、250·n ms 退避。AuthRejected(凭据被拒) 短路不重试——重试不会变好只会拖慢报错。把请求构造抽成 build_connect_request (connect_async 消费 request 且 http::Request 非 Clone,需每次重建)。 桌面与安卓共享此路径,改动对两端等效;最坏耗时从「无界(~75s)」降到有界(~15s) 并新增抖动可恢复,对 PC 端只增不减。 * feat(coding-agent): OpenCode CLI 适配器,语音 Agent 可选 OpenCode 后端 (issue Open-Less#579) 把 OpenCode 当作与 Claude Code 同类的 coding agent CLI 接入,照既有 coding_agent 适配模式(不另造架构),复用同一套 CodingAgentRequest / CodingAgentEvent / CodingAgentError / 审批护栏链路。 后端: - coding_agent/opencode.rs(新):detect_opencode(opencode --version)、 build_opencode_args(opencode run --format json --model --dir [--dangerously-skip-permissions],prompt 作为 argv)、parse_opencode_json_line (NDJSON:text/tool_use/error;text 块累计,EOF 合成 Completed)、 run_opencode_agent(取消/超时 kill,护栏经 OPENCODE_CONFIG_CONTENT 注入)。 - coding_agent/args.rs:CodingAgentProvider 枚举(claude-code-cli/opencode-cli, 未知回落 Claude)+ from_pref/default_exe。 - coding_agent/guard.rs:build_opencode_guard_config —— 把高风险 bash 前缀翻译成 OpenCode permission.bash deny glob + webfetch deny,审批放行的前缀显式 allow。 - coordinator/dictation_voice_agent: run_less_computer_once 按 coding_agent_provider 分派 Claude / OpenCode 运行器;两路都 fail-closed(护栏生成失败即中止,不裸跑)。 审批拦截探测/重跑放行链路 provider 无关,自动复用。 - commands: coding_agent_detect_opencode 命令(已注册 lib.rs)。 前端: - CodingAgentSection: OpenCode 选项从「即将支持」改为可用 + 选中后探测安装状态并提示。 - ipc: codingAgentDetectOpencode + OpenCodeDetection。 - i18n: opencodeReady / opencodeMissing(5 语言,替换 providerOpenCodeSoon)。 验证:cargo check + cargo test(492 通过)+ npm run build 均通过。 Refs Open-Less#579 (Open-Less#579 还含「语音润色模型配置」:prefs 字段已存在但全链路未实现/未接 UI, 转写直接进 agent 未经语音润色模型整理 —— 故用 Refs 不用 Closes。) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(coding-agent): OpenCode 防参数注入 + 可配置 Agent 可执行文件路径 (Open-Less#638) 注入风险(@H-Chris233 评审):`opencode run` 的 prompt 作为最后位置参数,但之前缺 end-of-options 分隔。以 `-` / `--` 开头的 prompt(语音转写或被 prompt 注入诱导)会被 OpenCode 当作 flag 解析,可绕过护栏(混入 --dangerously-skip-permissions、--dir 改写 工作目录等)。修复:build_opencode_args 末尾追加 `--`,运行器把 prompt 接在其后;新增单测。 可配置路径(@pr-agent 指出之前 hardcode "opencode"):新增 prefs.coding_agent_exe, 语音 Agent 路径据此取 exe(留空回退默认 claude / opencode),检测命令亦用配置路径; 设置「高级 → Less Computer」新增「可执行文件路径」输入;补 5 语言 i18n。 * fix(updater): 安装失败可见化 + 手动下载兜底 + 错误写日志 (Open-Less#706) 下载完成后若 install() 抛错,旧代码把 status 置成 'error',而 UpdateDialog 不渲染 error 态 → 弹框消失、错误只 console.error,用户表现为「下载后无反应、重启仍旧版」(Open-Less#706)。 - 新增独立状态 installError:仅下载/安装失败用它;'error' 仍表示「检查失败」,行为不变(CheckUpdateButton 按钮内轻提示、后台自动检查都不弹框,零回归)。 - 失败弹框留在原地显示真实错误 + 「手动下载」按钮直达 GitHub Releases 兜底。 - 新增 Rust IPC log_client_error,把前端更新错误写进 openless.log(webview console 不入文件日志),便于用户「导出日志」后定位真因。 - 5 个 i18n 文件补 updateDialog.installError / manualDownload。 注意:这是兜底(失败可见 + 可诊断),不解决 ad-hoc/quarantine 机器自动更新真正成功的问题——那需要 Apple Developer ID 签名 + 公证(本次暂缓)。 * 手动补:dictation/ipc 拆分后的 OpenCode + updater 改动迁移 * fix: AutoUpdate 兼容性修复(补回 CheckUpdateOptions + Android 参数类型) * fix(audio-cue): 重建/唤醒退化的 AudioContext,修「用久了提示音消失」 长时间使用后「开始录音」提示音与设置页「试听」双双静音,且重启才恢复。 根因在 audioCue.ts 的 AudioContext 单例 sharedCtx 从不检查/恢复运行期状态: - WKWebView/WebView2 在频繁录音抢占音频会话后,ctx 可能转入 WebKit 非标准的 `interrupted` 态。旧代码只判断 `suspended`,interrupted 被当作可直接排期 → 排到非 running 的 ctx 上,无声且不尝试 resume → 永久静默(macOS 最可能根因)。 - ctx 也可能被系统 `closed`。closed 无法复活(resume 必拒、createOscillator 必抛、 被 catch 静默吞掉),而 getContext 从不重建 → 永久静默。 - 胶囊窗口与设置窗口各持一份 ctx,都会这样退化 → 两处提示音同时失效。 修复: - 新增纯函数 audioContextActionForState(state):closed→recreate / running→ready / 其余(suspended/interrupted/未知非运行态)→resume,单一真相、可单测。 - getContext 在 ctx 为 closed 时丢弃重建(并清空挂在旧 ctx 上的 activeVoices)。 - playRecordStartCue / primeAudioCue 改用该分类器,把 interrupted 一并按 resume 唤醒。 公共 API 签名不变,仅 AudioCue.tsx / RecordingInputSection.tsx 复用,行为向后兼容。 tsc --noEmit 通过;audioCue.test.ts 新增 5 条状态分类断言,全部通过。 * fix(macos): onboarding Accessibility authorization page stuck (Open-Less#703 follow-up) Root cause: permissions::request_accessibility() was called in setup(), which runs BEFORE the AppKit event loop is live. On some macOS versions, AXIsProcessTrustedWithOptions depends on the run loop for XPC to tccd. When the event loop isn't ready, TCC may not show the dialog but still consumes the one-time prompt. Later calls from the onboarding page's 'grant accessibility' button silently return Denied, trapping users on the onboarding page. Recovery requires System Settings grant + app restart, but users had no clear restart action — only a text hint. Changes: - lib.rs: Remove setup() TCC prompt. The prompt is now exclusively triggered by user action on the onboarding page, aligning with Apple HIG (explain why → then prompt). - Onboarding.tsx: * onGrantAccessibility now checks the TCC result: skip openSystemSettings when already granted (previously always opened) * New tccPromptShown state distinguishes 'never attempted' vs 'already denied' for correct button labels * Denied state now shows a prominent 'Restart OpenLess' button calling restartApp() - PermissionsSection.tsx: Same fix for settings page accessibility row — check TCC result, guide to System Settings when denied, show 'Restart App' button - i18n: Add onboarding.actionRestart + settings.permissions.restart for zh-CN / zh-TW / en / ja / ko Closes: macos-onboarding-accessibility-stuck * chore(release): publish 1.3.13 * fix(audio-cue): recover when resume() is rejected or wakes non-running Long sessions park the shared AudioContext in 'suspended' / WebKit 'interrupted' after the system audio session is repeatedly preempted by ASR and recording. The earlier fix only recreated a 'closed' context: a rejected resume() was swallowed and a resume() that resolved without reaching 'running' scheduled voices on a frozen currentTime, both of which leave the start cue permanently silent until the app restarts. Treat both "won't wake" states as recoverable — discard the dead context and retry once on a fresh one, bounded to avoid infinite recreation. The decision is extracted into the pure, unit-tested cueActionAfterResume. * chore(release): integrate 1.3.14 * fix(ipc): 去除 coding-agent.ts/index.ts 中 OpenCodeDetection 和 codingAgentDetectOpencode 的重复声明 integrate/cloud-sync-20260621 合并后 IPC barrel 出现重复 export(beta 已含 PR Open-Less#638 的 OpenCode 适配器,integrate 分支又加了一份)。合并去重,npm build 通过。 * style(less-computer): 气泡/审批/发送按钮改用 accent 主题 token 把 LessComputerPanel 里三处硬编码渐变(#3d6bff→#7a55f2)替换为 --ol-accent-solid-bg / --ol-accent-solid-ink,跟随浅/深主题。 * feat(capsule/asr/ui): 胶囊即时显示与预测展开、Apple 语音修复、弹窗全局遮罩 胶囊入场(延迟出现 / 无入场动画 / 亮起→卡→展开): - 按下热键即「乐观显示」胶囊并播入场动画,不等麦克风就绪;level_handler 首帧 PCM 到达后从预备态翻正式态、光条点亮(新增 warming 标志贯穿 emit_capsule payload) - 修入场动画被吞:入场帧把发给前端的事件挪到 window.show 之后再 emit - 预测式展开:光条按历史平均加载时长(EMA + localStorage 跨会话学习)从按下就平滑 展开,就绪早则加速补满、未就绪则慢爬至 90%,消除「亮起→卡→突然展开」 Apple 语音(说中文识别成英文 / 偶发不可用): - initWithLocale 跟随用户工作语言主语言,修 init 无参落系统首选语言(英文)导致 中文被英文引擎误识别 - on-device 优先(requiresOnDeviceRecognition):离线可用 + 隐私 + 不受网络波动/限流 - isAvailable 就绪轮询等待,修刚 init 时瞬时不可用就报错的竞态 弹窗遮罩:Modal 用 createPortal 到 body,修被 WindowChrome/FloatingShell 常驻 will-change: transform 祖先困住 fixed 遮罩(登录弹窗浮在亮着的设置页上、无全局压暗) 服务页 Apple 语音文案优化(zh-CN/zh-TW) --------- Co-authored-by: 吕柏青 <lubaiqing9@gmail.com> Co-authored-by: sim <sim@local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ess#763) @tauri-apps/plugin-shell (npm) 与 tauri-plugin-shell (crate) 的声明下限 ——npm `^2.0.1`、crate `"2"`——都落在 GHSA-c9pr-q8gx-3mgp 影响范围 (`< 2.2.1`,CRITICAL:`open` 端点作用域校验绕过,可致 RCE)。本项目 src/lib/ipc/utils.ts 正是通过该 `open` 端点打开外链,属受影响用法。 两个 lockfile 实际已解析到 2.3.5,但清单声明的下限仍是漏洞版本,供应链 扫描(SkillSpector SC4)因此读取到 2.0.1 报警。此处仅把两侧声明下限抬到 最新安全版 2.3.5,使清单与锁定版本一致、扫描不再命中;解析版本不变,无 运行时行为变化。 Closes Open-Less#668 Co-authored-by: sim <sim@local>
…pen-Less#760) Open volcengine streaming ASR's `enable_speaker_info` flag so each returned utterance carries a speaker label. Then in the result assembly, group utterances by speaker, pick the one with the longest total speaking time as the "primary speaker", and only concatenate their text — dropping utterances from other speakers (e.g. a colleague interjecting while you dictate). This is a heuristic quick-win for the "only transcribe my voice" request; it does not yet do enrolled voiceprint verification. If no speaker labels are present, behavior is unchanged (all utterances concatenated). Co-authored-by: Shicongcoding <Shicongcoding@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@owtffssent.com>
- 新增「从工具到基础设施 / From tool to infrastructure」专章:把 OpenLess 定位为 消灭「想法 → 干净文字」之间每一次协调的基础设施——语气沉降成风格包、专有名词 沉降成词典热词、文字落点沉降成插入协议、模型与凭据沉降进系统保管库,开屏授权 一次即为默认协议。 - 改写「更新亮点(增强)」与「当前状态(能力)」两节开场,从功能罗列改为「你授权 一次之后就不必再操心的基础设施层」;其余条目与版本号(v1.3.6)保留不动。 - 中英双 README 同步(README.md + README.zh.md),纯文档改动,不涉及代码/版本门禁。 Co-authored-by: sim <sim@local> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Open-Less#757) Long sessions park the shared AudioContext in 'suspended' / WebKit 'interrupted' after the system audio session is repeatedly preempted by ASR and recording. The earlier fix only recreated a 'closed' context: a rejected resume() was swallowed and a resume() that resolved without reaching 'running' scheduled voices on a frozen currentTime, both of which leave the start cue permanently silent until the app restarts. Treat both "won't wake" states as recoverable — discard the dead context and retry once on a fresh one, bounded to avoid infinite recreation. The decision is extracted into the pure, unit-tested cueActionAfterResume. Co-authored-by: sim <sim@local>
…down (Open-Less#764) OpenLessIme.dll is a system-wide TSF keyboard TIP, so CTF loads it into every process that has text input (explorer.exe, the taskbar, ...) and runs it on that process's UI thread. The per-process pipe server blocked on a synchronous ConnectNamedPipe, and Deactivate() -> Stop() joined that worker from the host UI thread, relying on a racy CancelSynchronousIo + self-connect WakePipe() to unblock it. When both lost the race the worker re-entered ConnectNamedPipe forever and join() froze the host UI thread indefinitely -> explorer.exe AppHangB1 (taskbar unresponsive, Explorer restarts). WER reports for the reported hangs show OpenLessIme.dll loaded in explorer.exe, and disabling the TSF profile stops the hang. Make every pipe wait deterministically cancelable: open the pipe with FILE_FLAG_OVERLAPPED and wait on {io_event, stop_event} via WaitForMultipleObjects. Stop() now just signals stop_event and joins, so it returns at once, never blocks the host UI thread, and leaks no worker thread. Connect/read/write go through WaitForClient / RunOverlapped, which CancelIoEx + drain the overlapped op before the stack OVERLAPPED / buffer go out of scope. Removes the racy WakePipe / CancelSynchronousIo / pipe_handle_ / pipe_mutex_. Pipe name, JSON wire protocol and the Rust client (windows_ime_ipc.rs) are unchanged. Fixes Open-Less#665 Fixes Open-Less#707 Co-authored-by: appergb <lubaiqing9@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(deps): patch tauri-plugin-shell CVE (GHSA-c9pr-q8gx-3mgp) @tauri-apps/plugin-shell (npm) 与 tauri-plugin-shell (crate) 的声明下限 ——npm `^2.0.1`、crate `"2"`——都落在 GHSA-c9pr-q8gx-3mgp 影响范围 (`< 2.2.1`,CRITICAL:`open` 端点作用域校验绕过,可致 RCE)。本项目 src/lib/ipc/utils.ts 正是通过该 `open` 端点打开外链,属受影响用法。 两个 lockfile 实际已解析到 2.3.5,但清单声明的下限仍是漏洞版本,供应链 扫描(SkillSpector SC4)因此读取到 2.0.1 报警。此处仅把两侧声明下限抬到 最新安全版 2.3.5,使清单与锁定版本一致、扫描不再命中;解析版本不变,无 运行时行为变化。 Closes Open-Less#668 * fix(audit): 1.3.14 审计修复一批(Apple Speech 取消/IME 保存锁死/侧向键卡死/火山429 等) - CRIT Apple Speech 取消/超时不终止 SFSpeechRecognitionTask(SendableTask+cancel_flag+[task cancel]+100ms 轮询) - HIGH windows_ime 未安装分支反致设置保存事务锁死(抽纯函数 keyboard_list_pref_short_circuit) - HIGH 侧向键 Released 卡死自愈 + macOS flags 类位竞态 + Linux 显式拒绝 - HIGH coordinator/hotkey_loops unused_assignments clippy 回归 - MED 火山 429 限流不重试(RateLimited)/ Heatmap clamp / 授权失败反馈 / SendInput 分类单一真相 / SendInputOptions 收敛 - 清理 删死码 ?? '重启' / side_aware 死字段 / 孤儿 MarketplaceModal cargo test 552-0 / npm build 绿。windows cfg 部分需 CI 四平台验证。 * feat(chat): 统一聊天面板组件库——官方 shadcn base 组件按 CLI 规则内嵌 @shadcn/react 官方 primitive + base-rhea registry 原文内嵌 components/chat/ui/ (message-scroller/marker/bubble/message/card/empty/input-group/button/input/ textarea/spinner,仅按 CLI 规则改 import 路径;button 保留 3 行 React18 inert 归一)。Tailwind v4 作用域隔离:utilities source(none) + @source 只扫聊天文件、 无层导入压过 global.css;官方主题 shadcn/tailwind.css + tw-animate-css。 胶囊同款思考动画(orbFeed 单 GL 源镜像多头像)、GitHub 头像(CSP img-src 放行)、 出现/消失生命周期 hook。平台门控:非 darwin 构建 DCE 掉 Less Computer chunk。 删除旧自制 Marker/MessageScroller 组件。 * feat(panels): QA/LC 浮窗重写为官方 message-scroller-demo 同款骨架 Card + Empty + InputGroup + MessageScroller 官方组合;QA=Message 带头像形态 (右 GitHub / 左旋转 orb),LC=Marker 工具行形态(无头像)。 语音状态只在输入区:录音红光/转译黑光绕输入组跑圈,问题落定即停; 思考头像行门控在用户消息落定之后(修「AI 头像先出现、动画抽搐」)。 LC 事件空轮自愈 + mount 后 less_computer_sync 重放(webview 冷加载丢 user 事件导致「后端在跑前端空白/用户的话不显示」)。纯白背景、官方 24px 圆角、出现/消失动画(chat-panel:shown/closing)。 * fix(backend): 聊天面板后端配套——命令注册/非激活面板/事件缓冲/生命周期 - 恢复桌面 invoke_handler 的 qa_submit_text / qa_toggle_recording(此前删 qa_window_pin 时误删相邻项,真机报 Command qa_submit_text not found) - QA/LC 窗口转非激活 NSPanel(胶囊同管线:NonactivatingPanelMask + CanJoinAllSpaces|FullScreenAuxiliary),chat_panel_focus_keyboard 改主线程 makeKeyAndOrderFront:打字不激活 app、主窗口不再被带出、frontmost 保持 用户原 app(划词选区在多轮期间仍可读) - Less Computer 事件按全局单调 seq 写入环形缓冲(fresh user 清空、cap 2048 丢最旧),新命令 less_computer_sync 供前端 mount 重放(仅 less-computer 窗口可调);附 2 个单测 - show/hide 生命周期:show 发 chat-panel:shown 并作废挂起 hide;hide 先发 chat-panel:closing、240ms 后真正隐藏(AtomicU64 世代防快速关-开竞态) - QA/LC 窗口统一固定 420×540;删除 qa_window_pin / less_computer_window_resize 整链(无消费方) --------- Co-authored-by: sim <sim@local>
三个缺陷叠加导致「阿里云百炼实时 ASR」的连接检查完全不可用: 1. 「验证」必然失败:只发 1 个 100ms 静音块就 finish-task,DashScope 对 <200ms 音频必返回 task-failed: EmptyAudio(2026-07 实测边界:100ms 拒、 200ms 起收)——有效凭据也永远显示「操作失败」。改为发送 500ms 静音。 2. 「拉取模型」假成功:直接返回硬编码列表、不发任何网络请求,Key/endpoint 全错也显示成功。现在先执行与「验证」相同的 WebSocket 连通性检查 (百炼无模型列表接口,列表本身仍为静态)。 3. 接口地址误填 https://(百炼控制台兼容模式/专属域名地址)时,WS 握手报的 "URL scheme not supported" 被前端兜底成笼统「操作失败」。现在验证入口 校验协议头必须为 ws:// / wss://,返回专用错误码 bailianEndpointSchemeInvalid,前端映射为含默认 wss 地址的明确提示 (en / zh-CN / zh-TW / ja / ko)。 测试:cargo test 555 passed;npm run build (tsc + vite) 通过; DashScope 线上协议行为已用独立脚本实测确认。
Co-authored-by: Chris233 <h-chris233@outlook.com>
Document the cleanup items from 1-app audit: - Open-Less#27: Remove blanket allow(dead_code) and clean incrementally - Open-Less#24: Extract shared request builder in polish.rs - Open-Less#32: Rename misleading mobile_stubs/selection.rs Includes execution order, risk assessment, and verification steps.
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
|
Request changes. 整体改动量较大,CI 已通过,但还有几个需要修正的问题:
修复以上问题后再继续 review。 |
User description
Summary
Document the code quality cleanup items from the 1-app audit (2026-06-17).
Changes
docs/CODE_QUALITY_CLEANUP.mdwith detailed roadmapItems Documented
Next Steps
Closes #699
/claim #699
PR Type
Enhancement, Bug fix, Tests, Documentation
Description
主窗 UI 大改:聊天面板统一尺寸、macOS 非激活 NSPanel
Apple Speech ASR 改进:locale 支持、on-device、取消等待、流式简繁
活动热力图与 Less Computer 缓冲同步
按键事件配对修复(macOS 侧修饰键)及 Linux 拒绝
持久化:样式偏好保留、活动计数写入
Diagram Walkthrough
File Walkthrough
17 files
聊天面板窗口重构(尺寸、NSPanel、退场动画)Apple Speech ASR 改进(locale、on-device、取消等待)Less Computer 缓冲、乐观胶囊、流式简繁转换连接重试、限流分类、声纹过滤添加 ActivityStore 与文本提交命令添加 set_preserving_current_style_preferences每日活动计数存储语音光效舞台组件活动热力图组件聊天头像组件聊天输入组样式更新聊天气泡样式更新语音光效动画逻辑工具提示组件消息滚动组件聊天按钮组件聊天卡片组件2 files
按键事件配对修复、Linux 拒绝持久化时保留样式偏好1 files
代码质量清理路线图文档80 files