From cd419558fd11eea4171354113a3bd1821dc47711 Mon Sep 17 00:00:00 2001 From: kemal Date: Fri, 28 Aug 2026 02:03:51 +0300 Subject: [PATCH 1/4] Do not require claude on PATH when assistant uses a hosted provider --- dikte/cli.py | 2 +- tests/test_cli.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/dikte/cli.py b/dikte/cli.py index c1ef786..37bc87d 100644 --- a/dikte/cli.py +++ b/dikte/cli.py @@ -873,7 +873,7 @@ def cmd_doctor(opts): # Recording, the device list, and KDE's shortcut registry. wanted += ["pw-record", "pactl", "kwriteconfig6"] wanted += ["ffmpeg", - assistant.executable(assistant.provider(conf)) or "claude", + assistant.executable(assistant.provider(conf)), cleanup.executable(cleanup.provider(conf))] programs = {name: shutil.which(name) or "" for name in wanted if name} target = conf.transcribe_target() diff --git a/tests/test_cli.py b/tests/test_cli.py index 67c6f21..c319d4b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -577,6 +577,21 @@ class Doctor(DikteTest): self.run_doctor(as_json=False, cleanup_provider="codex", cleanup_codex_model="gpt-5.4")) + def test_agent_on_hosted_provider_does_not_ask_for_a_cli_program(self): + for provider in ("openrouter", "opencode"): + with self.subTest(provider=provider): + reply = self.run_doctor(assistant_provider=provider) + self.assertEqual(reply["agent"]["provider"], provider) + for cli_name in ("claude", "codex", "agy"): + self.assertNotIn(cli_name, reply["programs"]) + + def test_agent_on_a_cli_asks_for_the_program(self): + for provider, binary in (("claude", "claude"), ("codex", "codex"), ("agy", "agy")): + with self.subTest(provider=provider): + reply = self.run_doctor(assistant_provider=provider) + self.assertEqual(reply["agent"]["provider"], provider) + self.assertIn(binary, reply["programs"]) + class Devices(DikteTest): def test_a_machine_with_nothing_names_its_own_missing_program(self): From 21e28f621e74e4a71eac18cb6997cf0774c04d91 Mon Sep 17 00:00:00 2001 From: kemal Date: Fri, 28 Aug 2026 02:06:22 +0300 Subject: [PATCH 2/4] Allow wheel events on focused boxes when window is inactive --- dikte/settings_ui.py | 4 +++- tests/test_ui.py | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/dikte/settings_ui.py b/dikte/settings_ui.py index 56771f8..1bf182e 100644 --- a/dikte/settings_ui.py +++ b/dikte/settings_ui.py @@ -207,7 +207,9 @@ class WheelGuard(QObject): """ def eventFilter(self, box, event): - if event.type() == QEvent.Type.Wheel and not box.hasFocus(): + win = box.window() + focused = box.hasFocus() or (win is not None and win.focusWidget() is box) + if event.type() == QEvent.Type.Wheel and not focused: # Refused rather than swallowed. An unaccepted wheel event carries # on up the parents to the scroll area, so the page still moves. event.ignore() diff --git a/tests/test_ui.py b/tests/test_ui.py index 6273447..038137f 100644 --- a/tests/test_ui.py +++ b/tests/test_ui.py @@ -223,6 +223,17 @@ class Settings(DikteTest): QApplication.sendEvent(box, self.wheel()) self.assertNotEqual(box.currentIndex(), before) + def test_the_wheel_is_refused_when_another_widget_has_focus(self): + window = self.window(cfg.Config()) + box = window.ui_language + other = window.corner + other.setFocus() + before = box.currentIndex() + rolled = self.wheel() + QApplication.sendEvent(box, rolled) + self.assertEqual(box.currentIndex(), before) + self.assertFalse(rolled.isAccepted()) + def test_a_wrapped_label_keeps_the_room_its_lines_need(self): # The program path shares a row with a button, and a row is measured # before its width is known: the label has to claim the second line back From 6d7c591b7de093c67c895eb94edc398fec00cc5e Mon Sep 17 00:00:00 2001 From: kemal Date: Fri, 28 Aug 2026 02:14:40 +0300 Subject: [PATCH 3/4] Use generic agent label in desktop shortcut name --- dikte/hotkey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dikte/hotkey.py b/dikte/hotkey.py index 91e4c6b..cc793e8 100644 --- a/dikte/hotkey.py +++ b/dikte/hotkey.py @@ -57,7 +57,7 @@ SHORTCUTS = { "Dikte: pause/resume the recording", "pause_shortcut", ""), "cancel": Shortcut("cancel", CANCEL_DESKTOP_ID, "Dikte: discard the recording", "cancel_shortcut", ""), - "ask": Shortcut("ask", ASK_DESKTOP_ID, "Dikte: ask Claude Code", + "ask": Shortcut("ask", ASK_DESKTOP_ID, "Dikte: ask the agent", "assistant_shortcut", ""), "meeting": Shortcut("meeting", MEETING_DESKTOP_ID, "Dikte: start/end a meeting recording", From afa53934c21c3fc3f4376e6e585f841132b0b4f6 Mon Sep 17 00:00:00 2001 From: yusufipk Date: Wed, 9 Sep 2026 11:19:03 +0300 Subject: [PATCH 4/4] Test remembered wheel focus in inactive windows --- tests/test_ui.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/tests/test_ui.py b/tests/test_ui.py index 038137f..a51d2f3 100644 --- a/tests/test_ui.py +++ b/tests/test_ui.py @@ -14,7 +14,7 @@ from unittest import mock from PyQt6.QtCore import QPoint, QPointF, Qt from PyQt6.QtGui import QWheelEvent -from PyQt6.QtWidgets import QApplication, QMessageBox +from PyQt6.QtWidgets import QApplication, QComboBox, QMessageBox, QSpinBox, QWidget from dikte import audio from dikte import cleanup @@ -223,6 +223,40 @@ class Settings(DikteTest): QApplication.sendEvent(box, self.wheel()) self.assertNotEqual(box.currentIndex(), before) + def test_the_wheel_uses_remembered_focus_in_an_inactive_window(self): + # Keep the window hidden so no desktop activation policy can give it + # keyboard focus. Its remembered focus still selects the wheel target. + for widget_type in (QComboBox, QSpinBox): + with self.subTest(widget=widget_type.__name__): + window = QWidget() + self.addCleanup(window.deleteLater) + box = widget_type(window) + other = QComboBox(window) + if isinstance(box, QComboBox): + box.addItems(["first", "second", "third"]) + box.setCurrentIndex(1) + value = box.currentIndex + else: + box.setValue(5) + value = box.value + box.setFocusPolicy(Qt.FocusPolicy.StrongFocus) + guard = settings_ui.WheelGuard(window) + box.installEventFilter(guard) + box.setFocus() + self.assertFalse(window.isActiveWindow()) + self.assertFalse(box.hasFocus()) + self.assertIs(window.focusWidget(), box) + before = value() + QApplication.sendEvent(box, self.wheel()) + self.assertNotEqual(value(), before) + other.setFocus() + self.assertIs(window.focusWidget(), other) + before = value() + rolled = self.wheel() + QApplication.sendEvent(box, rolled) + self.assertEqual(value(), before) + self.assertFalse(rolled.isAccepted()) + def test_the_wheel_is_refused_when_another_widget_has_focus(self): window = self.window(cfg.Config()) box = window.ui_language