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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PAhbNszs5JKdtnD4uBi3xp
This commit is contained in:
Krzysztof Rudnicki 2026-10-17 14:11:15 +02:00
parent 8450644a56
commit 3478ee1d2a
2 changed files with 17 additions and 7 deletions

View File

@ -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()

View File

@ -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"
)