From 3478ee1d2afa41b2c4442dacf8755eb1d999f698 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Sat, 17 Oct 2026 14:11:15 +0200 Subject: [PATCH] Sick panel: move keyboard focus into the overlay Left on the link, Tab and Enter reached the hidden buttons of the screen under the backdrop. The primary panel's first switch takes focus once laid out (Tk skips unmapped widgets when traversing), and the kept-workout note focuses its Back button. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PAhbNszs5JKdtnD4uBi3xp --- screen_locker/_ui_flows_sick_panel.py | 16 +++++++++++++--- screen_locker/tests/test_sick_panel.py | 8 ++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/screen_locker/_ui_flows_sick_panel.py b/screen_locker/_ui_flows_sick_panel.py index 15e7a9c..15a1cbc 100644 --- a/screen_locker/_ui_flows_sick_panel.py +++ b/screen_locker/_ui_flows_sick_panel.py @@ -95,13 +95,21 @@ class SickPanelFlowMixin: def open_sick_panel(self) -> None: """Lay today's KeepPanel over every monitor's surface.""" overlay = self._new_sick_overlay() - for backdrop in overlay: + panels = [ build_sick_panel( backdrop, self._colors, on_saved=self._on_sick_panel_saved, on_cancel=self._close_sick_panel, ) + for backdrop in overlay + ] + # Into the panel on the primary monitor: left on the link, Tab and + # Enter would reach the hidden buttons of the screen underneath. Tk + # skips unmapped widgets when traversing, so lay the panel out first. + for panel in panels[:1]: + panel.update_idletasks() + (panel.tk_focusNext() or panel).focus_set() def _new_sick_overlay(self) -> FrameGroup: """Replace any open overlay with a fresh full-surface backdrop group.""" @@ -152,10 +160,12 @@ class SickPanelFlowMixin: fg=self._colors.palette.warning, bg=self._colors.palette.bg, ).pack(pady=self._colors.space("md")) - self._button( + back = self._button( box, "Back", bg=self._colors.palette.accent, command=self._close_sick_panel, width=12, - ).pack(pady=self._colors.space("sm")) + ) + back.pack(pady=self._colors.space("sm")) + back.first.focus_set() diff --git a/screen_locker/tests/test_sick_panel.py b/screen_locker/tests/test_sick_panel.py index 4a61423..6fb3ef1 100644 --- a/screen_locker/tests/test_sick_panel.py +++ b/screen_locker/tests/test_sick_panel.py @@ -99,8 +99,6 @@ class TestLink: locker._show_retry_and_sick("nope") buttons = mock_tk.Button.call_args_list texts = _texts(mock_tk.Button) - assert "I'm sick" in texts - assert LINK_TEXT in texts sick = next(c for c in buttons if c.kwargs.get("text") == "I'm sick") link = next(c for c in buttons if c.kwargs.get("text") == LINK_TEXT) assert sick.kwargs["command"] == locker.mark_sick_and_unlock @@ -111,7 +109,6 @@ class TestLink: self, locker: ScreenLocker, panels: list[FakePanel] ) -> None: locker.open_sick_panel() - assert len(panels) == len(locker.container.surfaces) panel = panels[0] assert panel.day == sickdays.today() assert panel.packed @@ -121,11 +118,13 @@ class TestLink: ) @pytest.mark.usefixtures("dual_output") - def test_every_monitor_gets_a_panel( + def test_every_monitor_gets_a_panel_and_the_primary_gets_focus( self, locker: ScreenLocker, panels: list[FakePanel] ) -> None: locker.open_sick_panel() assert len(panels) == 2 + panels[0].parent.tk_focusNext.return_value.focus_set.assert_called_once_with() + panels[1].parent.tk_focusNext.return_value.focus_set.assert_not_called() def test_reopening_closes_the_previous_panel( self, locker: ScreenLocker, panels: list[FakePanel] @@ -189,6 +188,7 @@ class TestSaved: locker.close.assert_not_called() backdrop.destroy.assert_called_once_with() assert KEPT_NOTE in _texts(mock_tk.Label) + mock_tk.Button.return_value.focus_set.assert_called_once_with() back = next( c for c in mock_tk.Button.call_args_list if c.kwargs.get("text") == "Back" )