screen-locker/screen_locker/tests
Krzysztof kuhy Rudnicki 82f3462896 Replay today's earned shutdown credit in the daily base reset
The i3bar showed 21:00 on 2026-09-13 with a counted workout on disk. The
football was logged at 00:02 and credited against the previous day's
config (already at the 23 ceiling, so it bought nothing); the 09:15 base
reset then wrote 21/21 and only re-layered the weekly bank, never asking
what today's log had already earned. Live credits are read-add-write, so
every workout credited between local midnight and the first locker tick
of the day lost its hours to the reset that followed.

- _shutdown_base.reset_to_base_if_new_day takes the log and writes
  min(23, base + hours today's entries earned) in one write, using the
  same credit rule as the weekly count. Idempotent: still once per day.
- _workout_credit names FIRST_WORKOUT_BONUS_HOURS=2 /
  EXTRA_WORKOUT_BONUS_HOURS=1 and derives earned_shutdown_bonus_hours()
  from them; _shutdown and the +1h path use the constants instead of
  literals so the replay and the live paths cannot drift.
- _weekly_check.credit_key: a synced copy whose sync_record_id names an
  entry already filed on the same day shares that entry's slot. This
  morning's rekey (967e3cd) let the sync pull the football back in as
  manual:2026-09-13T14:30, which counted as a second credit (weekly 4/5
  instead of 3/5) and would have made the replay ask for 24.
- annotate_duplicate_credits.py passes the day's id index so the ledger
  records the manual duplicate the same way it records phone/PC pairs.

Verified through the real path: cleared last_reset_date, started
workout-locker.service -> "--restore 23 23 5", "Daily base reset:
Mon-Wed=23, Thu-Sun=23 (base 21/21 + 2h already earned today)",
shutdown_countdown.sh prints "23:00"; a second start changed nothing.
leetcode-guard grants no shutdown time anywhere (grepped screen-locker,
digital-wellbeing, i3 config) — the 23:00 comes from the workout alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7vgacLhb4WJjxuVj2QLEP
2026-09-13 20:00:05 +02:00
..
fixtures Stop the phone counting deleted and empty manual workouts 2026-08-27 20:13:12 +02:00
shell Cap --restore at 23:00 so it is a reward, not an off switch 2026-08-28 19:59:43 +02:00
__init__.py Add comprehensive tests for stockfish_analysis (100% coverage) 2025-12-02 22:58:06 +01:00
_credential_fixtures.py Detect a Firebase credential the server rejects, not just one that fails to build 2026-08-27 21:32:39 +02:00
_gatelock_fixtures.py Migrate screen_locker to gatelock v0.2.0 per-output locking 2026-07-25 16:26:39 +02:00
_isolated_state.py Replace scheduled skips with the shared free-day pool 2026-09-07 15:42:41 +02:00
_locker_factories.py Split screen_lock.py under the 250-line cap 2026-08-15 19:01:18 +02:00
_opt_in_fixtures.py Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
_real_state_fixtures.py Replace scheduled skips with the shared free-day pool 2026-09-07 15:42:41 +02:00
_status_view_helpers.py Support multiple workouts per day; fix the dead sync; ban silent failures 2026-07-17 09:22:31 +02:00
_tk_mocks.py Finish the Python side: scripts, conftest, and the last test modules 2026-08-15 20:57:29 +02:00
_unlock_helpers.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
_workout_sync_fixtures.py Detect a Firebase credential the server rejects, not just one that fails to build 2026-08-27 21:32:39 +02:00
conftest.py Replace scheduled skips with the shared free-day pool 2026-09-07 15:42:41 +02:00
test_adb_and_phone_part2.py Split 13 test modules under the cap; document their test methods 2026-08-15 19:16:18 +02:00
test_adb_and_phone.py Split _shutdown, _phone_verification and _status_data 2026-08-15 20:11:28 +02:00
test_annotate_duplicate_credits.py Record which log entries are two recordings of one workout 2026-08-29 22:31:32 +02:00
test_armed_state.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_cli.py Cover the headless dispatch and the degraded-source annotation 2026-08-27 20:55:05 +02:00
test_compliance_state_part2.py Replace scheduled skips with the shared free-day pool 2026-09-07 15:42:41 +02:00
test_compliance_state_part3.py Split 13 test modules under the cap; document their test methods 2026-08-15 19:16:18 +02:00
test_compliance_state_part4.py Split 10 more test modules under the 250-line cap 2026-08-15 19:22:00 +02:00
test_compliance_state.py Replace scheduled skips with the shared free-day pool 2026-09-07 15:42:41 +02:00
test_credential_recovery_part2.py Detect a Firebase credential the server rejects, not just one that fails to build 2026-08-27 21:32:39 +02:00
test_credential_recovery.py Detect a Firebase credential the server rejects, not just one that fails to build 2026-08-27 21:32:39 +02:00
test_decision_log_modes.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_decision_log.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_decision_reasons.py Say every reason a lock decision had, not just the first one 2026-08-23 07:02:41 +02:00
test_decision_trail.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_duration_threshold_fixture.py Add sync-status card and duration-threshold fixture 2026-08-15 18:41:31 +02:00
test_early_bird_part2.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_early_bird_part3.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_early_bird.py Split 10 more test modules under the 250-line cap 2026-08-15 19:22:00 +02:00
test_extra_benefits_part2.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_extra_benefits.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_focus_and_scroll.py Take gatelock 0.8.1: nested LockConfig 2026-09-12 19:52:38 +02:00
test_heat_skip.py Add manual (unverified) workout logging with evidence form 2026-07-05 20:08:52 +02:00
test_home_isolation.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_init_and_log_part2.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_init_and_log_part3.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_init_and_log.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_instance.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_local_day_key.py Key every workout day on the LOCAL calendar day, not UTC 2026-09-13 09:50:06 +02:00
test_log_io.py Support multiple workouts per day; fix the dead sync; ban silent failures 2026-07-17 09:22:31 +02:00
test_log_mixin_chokepoint.py Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
test_manual_budget_merge_fixture.py Stop the phone counting deleted and empty manual workouts 2026-08-27 20:13:12 +02:00
test_manual_cli.py Stub the HMAC signature so the manual-CLI test stops measuring the host 2026-08-27 21:43:16 +02:00
test_manual_push_part2.py Detect a Firebase credential the server rejects, not just one that fails to build 2026-08-27 21:32:39 +02:00
test_manual_push.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_manual_sync_part2.py Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
test_manual_sync_part3.py Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
test_manual_sync.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_manual_workout_dialog.py Make the lock overlay's sport selector usable, and focus its form fields 2026-07-26 15:08:08 +02:00
test_manual_workout_features_part2.py Finish the Python side: scripts, conftest, and the last test modules 2026-08-15 20:57:29 +02:00
test_manual_workout_features_part3.py Split the last 8 test modules; scope pylint's test-inherent checks 2026-08-15 19:40:32 +02:00
test_manual_workout_features_part4.py Finish the Python side: scripts, conftest, and the last test modules 2026-08-15 20:57:29 +02:00
test_manual_workout_features.py Split the last 8 test modules; scope pylint's test-inherent checks 2026-08-15 19:40:32 +02:00
test_manual_workout_part2.py Split 12 more test modules under the 250-line cap 2026-08-15 19:34:59 +02:00
test_manual_workout_status_view.py Take gatelock 0.8.1: nested LockConfig 2026-09-12 19:52:38 +02:00
test_manual_workout.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_mcp.py Replace scheduled skips with the shared free-day pool 2026-09-07 15:42:41 +02:00
test_no_popup_widgets.py Make the lock overlay's sport selector usable, and focus its form fields 2026-07-26 15:08:08 +02:00
test_phone_check_unlock_part2.py Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
test_phone_check_unlock_part3.py Add auto-fill RunnerUp scan, carrot bonuses, and --status interface 2026-06-28 08:08:35 +02:00
test_phone_verification_part2.py Split _shutdown, _phone_verification and _status_data 2026-08-15 20:11:28 +02:00
test_phone_verification_part3.py Split 10 more test modules under the 250-line cap 2026-08-15 19:22:00 +02:00
test_phone_verification_part4.py Split _shutdown, _phone_verification and _status_data 2026-08-15 20:11:28 +02:00
test_phone_verification_part5.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_phone_verification_part6.py Split _shutdown, _phone_verification and _status_data 2026-08-15 20:11:28 +02:00
test_phone_verification_part7.py Split _shutdown, _phone_verification and _status_data 2026-08-15 20:11:28 +02:00
test_phone_verification_part8.py Split 10 more test modules under the 250-line cap 2026-08-15 19:22:00 +02:00
test_queue_state.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_runnerup_verification_part1.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part2.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part3.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part4.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part5.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part6.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part7.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part8.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part9.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part10.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part11.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part12.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part13.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part14.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_runnerup_verification_part15.py Finish the RunnerUp sport-allowlist removal; isolate the instance lock in tests 2026-08-21 13:17:26 +02:00
test_scheduled_skip.py Replace scheduled skips with the shared free-day pool 2026-09-07 15:42:41 +02:00
test_screen_lock_coverage_part1.py Split the last 8 test modules; scope pylint's test-inherent checks 2026-08-15 19:40:32 +02:00
test_screen_lock_coverage_part2.py Finish the Python side: scripts, conftest, and the last test modules 2026-08-15 20:57:29 +02:00
test_screen_lock_coverage_part3.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_screen_lock_coverage_part4.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_screen_lock_coverage_part5.py Credit StrongLifts sessions from the headless sync, as pc_workout_verified 2026-08-22 11:26:20 +02:00
test_session_sync.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_shutdown_base_replay.py Replay today's earned shutdown credit in the daily base reset 2026-09-13 20:00:05 +02:00
test_shutdown_base.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_shutdown_part2.py Split 13 test modules under the cap; document their test methods 2026-08-15 19:16:18 +02:00
test_shutdown_part3.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_shutdown_part4.py Split _shutdown, _phone_verification and _status_data 2026-08-15 20:11:28 +02:00
test_shutdown_part5.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_shutdown_part6.py Split 10 more test modules under the 250-line cap 2026-08-15 19:22:00 +02:00
test_sick_features_part2.py refactor: enforce 400-line file limit with pre-commit hook and split outliers 2026-05-28 21:19:18 +02:00
test_sick_features_part3.py Split 13 test modules under the cap; document their test methods 2026-08-15 19:16:18 +02:00
test_sick_features.py Split 13 test modules under the cap; document their test methods 2026-08-15 19:16:18 +02:00
test_sick_tracker_part2.py Split 12 more test modules under the 250-line cap 2026-08-15 19:34:59 +02:00
test_sick_tracker.py Split 12 more test modules under the 250-line cap 2026-08-15 19:34:59 +02:00
test_source_health.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_status_data_part2.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_status_data_part3.py Replace scheduled skips with the shared free-day pool 2026-09-07 15:42:41 +02:00
test_status_data_part4.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_status_data.py Replace scheduled skips with the shared free-day pool 2026-09-07 15:42:41 +02:00
test_status_part2.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_status_part3.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_status_view_part2.py Take gatelock 0.8.1: nested LockConfig 2026-09-12 19:52:38 +02:00
test_status_view_part3.py Make Check Phone week-aware; add RunnerUp GPS tolerance; fix CLI weekly count 2026-07-18 06:55:55 +02:00
test_status_view_part4.py Split 13 test modules under the cap; document their test methods 2026-08-15 19:16:18 +02:00
test_status_view_part5.py Split 10 more test modules under the 250-line cap 2026-08-15 19:22:00 +02:00
test_status_view_part6.py Split 12 more test modules under the 250-line cap 2026-08-15 19:34:59 +02:00
test_status_view_part7.py Serve the lock decision, its history and its health at 127.0.0.1:8770 2026-08-29 22:50:19 +02:00
test_status_view.py Take gatelock 0.8.1: nested LockConfig 2026-09-12 19:52:38 +02:00
test_status.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_surface_fanout.py Take gatelock 0.8.1: nested LockConfig 2026-09-12 19:52:38 +02:00
test_sync_degradation_part2.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_sync_degradation.py Replace scheduled skips with the shared free-day pool 2026-09-07 15:42:41 +02:00
test_sync_fixtures.py Split _workout_sync.py and _sick_tracker.py 2026-08-15 20:30:42 +02:00
test_sync_retry.py Retry sync past the boot/resume network race, and stop blaming the token 2026-07-20 08:46:37 +02:00
test_sync_status.py Show which sync backend the PC uses in the status window 2026-08-09 21:20:02 +02:00
test_systemd_units.py Re-arm the locker, and make a disarmed enforcer impossible to miss 2026-08-17 16:47:25 +02:00
test_temperature.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_time_check.py chore: set up as standalone repo 2026-05-28 07:43:06 +02:00
test_ui_and_timers_part2.py chore: set up as standalone repo 2026-05-28 07:43:06 +02:00
test_ui_and_timers.py Take gatelock 0.8.1: nested LockConfig 2026-09-12 19:52:38 +02:00
test_ui_flows_part2.py Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
test_verify_workout_part2.py Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
test_verify_workout.py Take gatelock 0.8.1: nested LockConfig 2026-09-12 19:52:38 +02:00
test_vt_switching.py Migrate to the shared gatelock backend 2026-06-21 20:11:16 +02:00
test_wake_shutdown.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_wake_skip.py Add auto-fill RunnerUp scan, carrot bonuses, and --status interface 2026-06-28 08:08:35 +02:00
test_wake_state.py fix: apply pre-commit formatting fixes to unblock CI 2026-05-28 20:59:35 +02:00
test_web_payload_part2.py Serve the lock decision, its history and its health at 127.0.0.1:8770 2026-08-29 22:50:19 +02:00
test_web_payload.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_web_server_part2.py Serve the lock decision, its history and its health at 127.0.0.1:8770 2026-08-29 22:50:19 +02:00
test_web_server.py Serve the lock decision, its history and its health at 127.0.0.1:8770 2026-08-29 22:50:19 +02:00
test_weekly_check_dedup_part2.py Count one StrongLifts session once, not once per ingestion path 2026-08-29 22:23:17 +02:00
test_weekly_check_dedup.py Count one StrongLifts session once, not once per ingestion path 2026-08-29 22:23:17 +02:00
test_weekly_check_part2.py Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
test_weekly_check.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_weekly_logic_part2.py Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
test_weekly_logic_part3.py Close the four gaps that let a decided lock never reach the screen 2026-08-30 15:24:45 +02:00
test_weekly_logic_part4.py Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
test_weekly_logic_part5.py Grade against 3.14 too, not just run on it 2026-08-29 11:42:47 +02:00
test_weekly_logic.py Split 12 more test modules under the 250-line cap 2026-08-15 19:34:59 +02:00
test_workout_app.py Supervise the workout app as the lock surface, on a deadline that holds 2026-08-22 11:52:02 +02:00
test_workout_handoff.py Lend the lock screen's grab to the workout app, without unlocking it 2026-08-22 12:08:40 +02:00
test_workout_ready.py Supervise the workout app as the lock surface, on a deadline that holds 2026-08-22 11:52:02 +02:00
test_workout_session.py Put the demo's escape hatch inside the app, the only place it can be received 2026-08-22 12:41:07 +02:00
test_workout_sync_part2.py Detect a Firebase credential the server rejects, not just one that fails to build 2026-08-27 21:32:39 +02:00
test_workout_sync_part3.py Detect a Firebase credential the server rejects, not just one that fails to build 2026-08-27 21:32:39 +02:00
test_workout_sync_part4.py Split 13 test modules under the cap; document their test methods 2026-08-15 19:16:18 +02:00
test_workout_sync_part5.py Split _workout_sync.py and _sick_tracker.py 2026-08-15 20:30:42 +02:00
test_workout_sync_part6.py Apply sync tombstones across the device union, not per device log 2026-08-21 13:38:52 +02:00
test_workout_sync.py Cover _tombstoned_ids 2026-08-21 13:43:34 +02:00