Workout handoff clears gatelock's Claim.grab_held while the app has input
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (flutter, flutter-analyze, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (mypy, mypy, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (pylint, pylint, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (rest, , mypy,pylint,flutter-analyze) (push) Waiting to run
Tests / test (push) Waiting to run
Tests / lock-popup-safety (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (0) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (1) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (2) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (3) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (4) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (5) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (6) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (7) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (8) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (9) (push) Waiting to run
Tests / flutter-coverage (push) Blocked by required conditions
Tests / web-coverage (push) Waiting to run
repo-contract / repo contract (push) Waiting to run

Lending the X grab to the workout app stops recovery and releases it with
the claim still live, so gatelock 0.11 held_grabs (leisure-watch C2) read
"held" for the whole workout. release() now reports held=False before
grab_release; reacquire()'s grab_input() sets it true once the grab lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dsh4Znwh2KnC7q6vbGpX1N
This commit is contained in:
Krzysztof Rudnicki 2026-10-19 12:04:48 +02:00
parent 77d3866ee7
commit a79ccc523b
2 changed files with 11 additions and 3 deletions

View File

@ -42,6 +42,10 @@ def lock_grab_handoff(lock: LockWindow) -> GrabHandoff:
# Stop first: a watchdog tick between the release and the app's own
# grab would take the screen straight back off it.
lock._recovery.stop()
# Claim.grab_held false BEFORE the release (fail closed), so
# leisure-watch never reads "held" while the workout app has input.
# reacquire() -> grab_input() sets it true once the grab lands.
lock.report_grab_held(held=False)
with contextlib.suppress(tk.TclError):
lock.root.grab_release()
_logger.warning(

View File

@ -43,6 +43,10 @@ class _FakeLock:
self.root = _Root()
def report_grab_held(self, *, held: bool) -> None:
"""gatelock 0.11: what leisure-watch reads via ``held_grabs``."""
self.events.append(f"held={held}")
def grab_input(self) -> None:
self.events.append("grab_input")
self._recovery.running = True
@ -59,14 +63,14 @@ class TestLockGrabHandoff:
lock = _FakeLock()
lock_grab_handoff(lock).release()
assert lock._recovery.events == ["stop"]
assert lock.events == ["grab_release"]
assert lock.events == ["held=False", "grab_release"]
assert not lock._recovery.running
def test_reacquire_restores_the_grab_and_the_watchdog(self) -> None:
handoff = lock_grab_handoff(lock := _FakeLock())
handoff.release()
handoff.reacquire()
assert lock.events == ["grab_release", "grab_input"]
assert lock.events == ["held=False", "grab_release", "grab_input"]
assert lock._recovery.running
def test_never_closes_the_lock_window(self) -> None:
@ -81,7 +85,7 @@ class TestLockGrabHandoff:
handoff = lock_grab_handoff(lock := _FakeLock(release_raises=True))
handoff.release()
handoff.reacquire()
assert lock.events == ["grab_release", "grab_input"]
assert lock.events == ["held=False", "grab_release", "grab_input"]
class TestStartWorkout: