feat(style): per-app auto polish-mode override#367
feat(style): per-app auto polish-mode override#367katanumahotori wants to merge 2 commits intoOpen-Less:betafrom
Conversation
When dictating into different apps, users typically want different
polish styles (formal for email, raw for code, casual for chat). Today
the only mechanism is to manually cycle Ctrl+Shift+S each time.
coordinator.rs already captures the foreground app name
("front_app captured: ..."), so we just need to wire that into mode
selection.
Adds:
- AppModeOverride { app_pattern: String, mode: PolishMode } and
UserPreferences.app_mode_overrides: Vec<...>. Order is preserved so
the user can prioritize.
- pick_mode_for_app(app, &overrides) - case-insensitive substring
match, first hit wins, empty patterns are skipped (so an in-progress
edit doesn't apply globally). 9 unit tests.
- coordinator.rs: just before kicking off polish, override the
effective mode if any rule matches the foreground app, otherwise
fall back to default_mode. A "[coord] app override matched" log line
is emitted for diagnosis.
- set_app_mode_overrides IPC command does dangling-id validation for
Custom(...) references.
- Style page: a new "Per-app override" section under Custom styles
with add / edit / delete rows; each row picks among built-ins + the
user's custom modes. Saves are debounced 500ms.
Empty overrides Vec means existing behavior is unchanged.
Note: this PR's types.rs / commands.rs / coordinator.rs / Style.tsx
overlap heavily with the previous PR (custom polish styles). The
overlap is intentional - both features sit on the same UserPreferences
schema and share the Style page. If Open-Less#366 lands first, this PR will
rebase to a much smaller diff (just the AppModeOverride struct +
pick_mode_for_app + the override section UI).
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
…Mode::Custom Without these the PolishMode::Custom variant added to types.rs in this PR makes the existing match arms in polish.rs / coordinator.rs non-exhaustive and the crate fails to compile. Same scope-leakage caveat from the previous commit applies: these will be the canonical implementations once Open-Less#366 / Open-Less#367 land.
Without this the PolishMode::Custom variant added in this PR's types.rs makes the existing match arms in polish.rs non-exhaustive. Same scope-leakage caveat as the rest of this PR.
PR Reviewer Guide 🔍(Review updated until commit 8abeaff)Here are some key observations to aid the review process:
|
|
@claude 它具体的实现方式是怎样的?实现是否在全平台可以正常通过? |
|
Claude finished @appergb's task in 1m 40s —— View job 分析 PR #367 实现方式及跨平台兼容性
具体实现方式整个功能由四层串联实现,以下是每层的核心逻辑: 1. 数据模型(
|
| 平台 | capture_frontmost_app() 实现 |
返回内容 | 规则匹配对象 |
|---|---|---|---|
| macOS | NSWorkspace.frontmostApplication via objc2 |
"Slack (com.tinyspeck.slackmacgap)" |
App 名称 + Bundle ID |
| Windows | GetForegroundWindow + GetWindowTextW |
"Inbox - Gmail — Google Chrome" |
窗口标题(随 tab/文档变化) |
| Linux | 直接返回 None |
无 | 永远不触发任何 override |
macOS ✅ 正常工作
NSWorkspace.frontmostApplication 返回稳定的 app 名称 + Bundle ID,如 "Google Chrome (com.google.Chrome)"。用户写 "chrome" 就能可靠命中 Chrome,与当前打开的 tab 无关。
Windows ⚠️ 行为与文档描述不符
GetWindowTextW 返回的是当前窗口标题,例如:
- Chrome 打开 Gmail →
"Inbox - Gmail — Google Chrome" - VS Code 编辑 foo.ts →
"foo.ts - Visual Studio Code" - 空白新 tab →
"New Tab - Google Chrome"
这与 PR 描述的"进程名 substring 匹配"存在偏差:写 "chrome" 在大多数 Chrome 标题里能命中,但写 "visual studio code" 只在标题末尾固定时有效。PR 中没有文档说明这一区别,用户可能对"为什么有时不生效"感到困惑。
建议:Windows 下应优先使用 GetWindowThreadProcessId + QueryFullProcessImageNameW 获取进程路径/名称,或至少在 UI 里说明 Windows 匹配的是窗口标题。
Linux ❌ 功能完全静默禁用
capture_frontmost_app() 在 non-macOS/non-Windows 平台直接返回 None(coordinator.rs:4739-4741)。所有 override 规则永远不匹配。PR 描述和 UI 均未提及此限制。
已知 Bug(与 PR Reviewer 指出问题一致)
删除自定义 mode 后 override 悬空(commands.rs:1260-1279):
delete_custom_mode 只清理了 default_mode 和 enabled_modes,但没有清理 app_mode_overrides 里引用该 id 的规则。下次 dictation 时:
pick_mode_for_app命中悬空规则,返回Custom("deleted-id")prefs.custom_modes.iter().find(|m| m.id == "deleted-id")返回Noneprompt_override_owned = None,LLM 收到空 system prompt,polish 静默退化
set_app_mode_overrides 会校验 Custom id 存在性(防止新建悬空),但删除路径没有对称地清理,逻辑不一致。
总结
| 方面 | 状态 |
|---|---|
| macOS 功能完整性 | ✅ 核心逻辑正确,测试覆盖充分 |
| Windows 基本可用 | |
| Linux | ❌ 功能静默禁用,无任何提示 |
| 删除 Custom mode 后悬空 override | ❌ 导致空 system prompt,静默降级 |
| IPC 命令一致性 | set_app_mode_overrides 做了 id 校验,delete_custom_mode 没有对称清理 |
|
Persistent review updated to latest commit 8abeaff |
User description
Why
When dictating into different apps, users typically want different polish styles (formal for email, raw for code, casual for chat). Today the only mechanism is to manually cycle Ctrl+Shift+S each time.
coordinator.rsalready captures the foreground app name ('front_app captured: ...'), so we just need to wire that into mode selection.What
AppModeOverride { app_pattern: String, mode: PolishMode }andUserPreferences.app_mode_overrides: Vec<...>. Order is preserved so the user can prioritize.pick_mode_for_app(app, &overrides)- case-insensitive substring match, first hit wins, empty patterns are skipped (so an in-progress edit doesn't apply globally). 9 unit tests.coordinator.rs: just before kicking off polish, override the effective mode if any rule matches the foreground app, otherwise fall back todefault_mode. A[coord] app override matchedlog line is emitted for diagnosis.set_app_mode_overridesIPC command does dangling-id validation forCustom(...)references.Empty overrides
Vecmeans existing behavior is unchanged.Note
types.rs/commands.rs/coordinator.rs/Style.tsxoverlap heavily with #366 (custom polish styles). The overlap is intentional - both features sit on the sameUserPreferencesschema and share the Style page. If #366 lands first, this PR will rebase to a much smaller diff (just theAppModeOverridestruct +pick_mode_for_app+ the override section UI).Depends on #366 (custom modes) for
PolishMode::Customand the custom-mode picker dropdown in the override row.PR Type
Enhancement, Documentation, Tests
Description
Add custom style CRUD and lookup
Resolve per-app overrides before polishing
Thread prompt overrides through LLM pipeline
Expand i18n, IPC, and tests
Diagram Walkthrough
File Walkthrough
6 files
Add custom modes and override typesAllow prompt overrides in system promptsApply app overrides during session polishingAdd CRUD commands and validationBuild custom styles and override UIMirror custom mode and override types2 files
Register new Tauri commandsExpose new IPC helpers to frontend5 files
Add Japanese labels for new settingsAdd Korean labels for new settingsAdd Traditional Chinese labelsAdd Simplified Chinese labelsAdd English labels for new settings1 files
Update mock preferences for new fields