2025-11-30 21:20:17 +01:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""Screen locker with workout verification for Arch Linux / i3wm.
|
|
|
|
|
|
|
|
|
|
Requires user to log their workout to unlock the screen.
|
|
|
|
|
"""
|
|
|
|
|
|
2026-02-14 18:42:20 +01:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2025-11-30 21:20:17 +01:00
|
|
|
import logging
|
2025-11-30 23:03:03 +01:00
|
|
|
from pathlib import Path
|
2025-11-30 21:20:17 +01:00
|
|
|
import sys
|
|
|
|
|
import tkinter as tk
|
2026-02-14 18:42:20 +01:00
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
|
|
2026-06-21 20:11:16 +02:00
|
|
|
from gatelock import GateRoot, LockConfig, LockWindow
|
|
|
|
|
|
2026-05-28 07:43:06 +02:00
|
|
|
from screen_locker import _sick_tracker
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
from screen_locker._auto_upgrade import AutoUpgradeMixin
|
2026-05-28 07:43:06 +02:00
|
|
|
from screen_locker._constants import (
|
2026-05-01 19:07:34 +02:00
|
|
|
EARLY_BIRD_END_HOUR,
|
|
|
|
|
EARLY_BIRD_END_MINUTE,
|
|
|
|
|
EARLY_BIRD_START_HOUR,
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
EXTRA_BENEFITS_FILE,
|
2026-06-29 11:23:19 +02:00
|
|
|
HEAT_SKIP_CITY,
|
|
|
|
|
HEAT_SKIP_TEMP_THRESHOLD,
|
2026-04-09 21:44:13 +02:00
|
|
|
HMAC_KEY_FILE,
|
|
|
|
|
MAX_CLOCK_SKEW_SECONDS,
|
|
|
|
|
MIN_WORKOUT_DURATION_MINUTES,
|
2026-03-16 22:46:48 +01:00
|
|
|
PHONE_PENALTY_DELAY_DEMO,
|
|
|
|
|
PHONE_PENALTY_DELAY_PRODUCTION,
|
2026-05-22 16:00:15 +02:00
|
|
|
SCHEDULED_SKIPS_FILE,
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
SHUTDOWN_BASE_FILE,
|
|
|
|
|
SICK_DAY_STATE_FILE,
|
2026-03-16 22:46:48 +01:00
|
|
|
SICK_LOCKOUT_SECONDS,
|
2026-02-23 22:50:42 +01:00
|
|
|
)
|
2026-05-28 07:43:06 +02:00
|
|
|
from screen_locker._early_bird import EarlyBirdMixin
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
from screen_locker._extra_benefits import (
|
|
|
|
|
current_streak,
|
|
|
|
|
process_week_transition,
|
2026-07-03 15:27:08 +02:00
|
|
|
weekly_shutdown_bonus_hours,
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
)
|
2026-06-29 11:23:19 +02:00
|
|
|
from screen_locker._heat_skip import HeatSkipMixin
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
from screen_locker._log_mixin import LogMixin
|
2026-05-28 07:43:06 +02:00
|
|
|
from screen_locker._phone_verification import PhoneVerificationMixin
|
2026-06-25 17:50:24 +02:00
|
|
|
from screen_locker._runnerup_verification import RunnerUpVerificationMixin
|
2026-05-28 07:43:06 +02:00
|
|
|
from screen_locker._shutdown import ShutdownMixin
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
from screen_locker._shutdown_base import reset_to_base_if_new_day
|
2026-05-28 07:43:06 +02:00
|
|
|
from screen_locker._sick_dialog import SickDialogMixin
|
2026-06-29 11:23:19 +02:00
|
|
|
from screen_locker._temperature import is_too_hot
|
2026-05-28 07:43:06 +02:00
|
|
|
from screen_locker._ui_flows import UIFlowsMixin
|
2026-05-28 21:19:18 +02:00
|
|
|
from screen_locker._ui_flows_relaxed import UIFlowsRelaxedMixin
|
|
|
|
|
from screen_locker._ui_widgets import UIWidgetsMixin
|
2026-05-28 07:43:06 +02:00
|
|
|
from screen_locker._weekly_check import (
|
2026-06-25 17:50:24 +02:00
|
|
|
COUNTED_WORKOUT_TYPES,
|
2026-05-28 07:04:18 +02:00
|
|
|
WEEKLY_WORKOUT_MINIMUM,
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
count_weekly_workouts,
|
2026-05-28 07:04:18 +02:00
|
|
|
has_weekly_minimum,
|
|
|
|
|
is_relaxed_day,
|
|
|
|
|
)
|
2026-05-28 07:43:06 +02:00
|
|
|
from screen_locker._window_setup import WindowSetupMixin
|
2026-04-09 21:44:13 +02:00
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
from collections.abc import Callable
|
2026-04-09 21:44:13 +02:00
|
|
|
from concurrent.futures import Future
|
2026-03-18 22:20:05 +01:00
|
|
|
|
|
|
|
|
__all__ = [
|
2026-05-01 19:07:34 +02:00
|
|
|
"EARLY_BIRD_END_HOUR",
|
|
|
|
|
"EARLY_BIRD_END_MINUTE",
|
|
|
|
|
"EARLY_BIRD_START_HOUR",
|
2026-04-09 21:44:13 +02:00
|
|
|
"HMAC_KEY_FILE",
|
|
|
|
|
"MAX_CLOCK_SKEW_SECONDS",
|
|
|
|
|
"MIN_WORKOUT_DURATION_MINUTES",
|
2026-03-18 22:20:05 +01:00
|
|
|
"PHONE_PENALTY_DELAY_DEMO",
|
|
|
|
|
"PHONE_PENALTY_DELAY_PRODUCTION",
|
2026-05-22 16:00:15 +02:00
|
|
|
"SCHEDULED_SKIPS_FILE",
|
2026-03-18 22:20:05 +01:00
|
|
|
"SICK_LOCKOUT_SECONDS",
|
2026-05-28 07:04:18 +02:00
|
|
|
"WEEKLY_WORKOUT_MINIMUM",
|
2026-03-18 22:20:05 +01:00
|
|
|
"ScreenLocker",
|
|
|
|
|
]
|
2025-11-30 21:20:17 +01:00
|
|
|
|
2026-03-16 22:46:48 +01:00
|
|
|
_logger = logging.getLogger(__name__)
|
2026-02-14 18:42:20 +01:00
|
|
|
|
2025-11-30 21:20:17 +01:00
|
|
|
|
chore: optimize pre-commit, remove tracked binaries, fix lint issues
- Move slow hooks (mypy, pylint, bandit, pytest, prettier) to pre-push stage
- Remove redundant autoflake (ruff covers F401/F841)
- Fix shellcheck OOM by batching files with xargs -n 40
- Remove tracked .o, .wav, .pyc binaries from git
- Move pomodoro wav files to ../testsAndMisc_binaries/ with symlinks
- Add *.o, *.so, *.a to .gitignore
- Refactor hltb._pick_best_hltb_entry to fix C901/PLR0911/SIM102
- Fix SC2034 warnings in gif_to_square.sh and upgrade.sh
- Add disk_cleanup_check.sh script
- Various test and code improvements across screen_locker,
steam_backlog_enforcer, word_frequency, moviepy_showcase
2026-04-10 18:44:51 +02:00
|
|
|
def _assert_not_under_pytest() -> None:
|
|
|
|
|
"""Raise if the screen locker is being created inside a pytest run.
|
|
|
|
|
|
|
|
|
|
Defence-in-depth: prevents a real fullscreen Tk window from locking
|
|
|
|
|
the user's screen when tests forget to mock ``tk.Tk``.
|
|
|
|
|
The check is cheap (one dict lookup) and only fires during testing.
|
|
|
|
|
"""
|
|
|
|
|
if "pytest" in sys.modules and getattr(tk, "__name__", "") == "tkinter":
|
|
|
|
|
msg = (
|
|
|
|
|
"SAFETY: ScreenLocker.__init__ called under pytest with "
|
|
|
|
|
"real tkinter — tk.Tk is not mocked"
|
|
|
|
|
)
|
|
|
|
|
raise RuntimeError(msg)
|
|
|
|
|
|
|
|
|
|
|
2026-03-16 22:46:48 +01:00
|
|
|
class ScreenLocker(
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
AutoUpgradeMixin,
|
2026-05-22 22:48:28 +02:00
|
|
|
EarlyBirdMixin,
|
2026-06-29 11:23:19 +02:00
|
|
|
HeatSkipMixin,
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
LogMixin,
|
2026-05-22 22:48:28 +02:00
|
|
|
WindowSetupMixin,
|
2026-03-16 22:46:48 +01:00
|
|
|
ShutdownMixin,
|
|
|
|
|
PhoneVerificationMixin,
|
2026-06-25 17:50:24 +02:00
|
|
|
RunnerUpVerificationMixin,
|
2026-05-14 19:52:15 +02:00
|
|
|
SickDialogMixin,
|
2026-03-16 22:46:48 +01:00
|
|
|
UIFlowsMixin,
|
2026-05-28 21:19:18 +02:00
|
|
|
UIFlowsRelaxedMixin,
|
|
|
|
|
UIWidgetsMixin,
|
2026-03-16 22:46:48 +01:00
|
|
|
):
|
2025-11-30 21:20:17 +01:00
|
|
|
"""Screen locker that requires workout logging to unlock."""
|
|
|
|
|
|
2026-03-29 22:50:24 +02:00
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
*,
|
|
|
|
|
demo_mode: bool = True,
|
|
|
|
|
verify_only: bool = False,
|
|
|
|
|
) -> None:
|
2025-11-30 21:20:17 +01:00
|
|
|
"""Initialize screen locker with optional demo mode."""
|
chore: optimize pre-commit, remove tracked binaries, fix lint issues
- Move slow hooks (mypy, pylint, bandit, pytest, prettier) to pre-push stage
- Remove redundant autoflake (ruff covers F401/F841)
- Fix shellcheck OOM by batching files with xargs -n 40
- Remove tracked .o, .wav, .pyc binaries from git
- Move pomodoro wav files to ../testsAndMisc_binaries/ with symlinks
- Add *.o, *.so, *.a to .gitignore
- Refactor hltb._pick_best_hltb_entry to fix C901/PLR0911/SIM102
- Fix SC2034 warnings in gif_to_square.sh and upgrade.sh
- Add disk_cleanup_check.sh script
- Various test and code improvements across screen_locker,
steam_backlog_enforcer, word_frequency, moviepy_showcase
2026-04-10 18:44:51 +02:00
|
|
|
_assert_not_under_pytest()
|
2025-11-30 23:03:03 +01:00
|
|
|
script_dir = Path(__file__).resolve().parent
|
|
|
|
|
self.log_file = script_dir / "workout_log.json"
|
2026-03-29 22:50:24 +02:00
|
|
|
self.verify_only = verify_only
|
2026-05-01 19:07:34 +02:00
|
|
|
self.workout_data: dict[str, str] = {}
|
2026-05-28 07:04:18 +02:00
|
|
|
self._relaxed_day_mode: bool = False
|
2026-05-01 19:07:34 +02:00
|
|
|
self._check_early_exits(verify_only=verify_only)
|
2026-06-21 20:11:16 +02:00
|
|
|
self.root = GateRoot()
|
|
|
|
|
self.root.on_callback_error = self.on_callback_error
|
2026-03-29 22:50:24 +02:00
|
|
|
title_suffix = (
|
|
|
|
|
" [VERIFY]" if verify_only else (" [DEMO MODE]" if demo_mode else "")
|
|
|
|
|
)
|
|
|
|
|
self.root.title("Workout Locker" + title_suffix)
|
2025-11-30 21:20:17 +01:00
|
|
|
self.demo_mode = demo_mode
|
2026-02-14 18:42:20 +01:00
|
|
|
self.lockout_time = 10 if demo_mode else 1800
|
2026-06-21 20:11:16 +02:00
|
|
|
self._lock: LockWindow | None = None
|
2026-03-29 22:50:24 +02:00
|
|
|
if verify_only:
|
|
|
|
|
self._setup_verify_window()
|
2026-05-28 07:04:18 +02:00
|
|
|
elif self._relaxed_day_mode:
|
|
|
|
|
self._setup_relaxed_day_window()
|
2026-03-29 22:50:24 +02:00
|
|
|
else:
|
2026-06-21 20:11:16 +02:00
|
|
|
config = LockConfig(
|
|
|
|
|
mode="hard",
|
|
|
|
|
grab="local" if demo_mode else "global",
|
|
|
|
|
disable_vt=not demo_mode,
|
|
|
|
|
)
|
|
|
|
|
self._lock = LockWindow(self.root, config, hooks=self)
|
|
|
|
|
self._lock.setup()
|
2026-03-29 22:50:24 +02:00
|
|
|
if demo_mode:
|
|
|
|
|
self._setup_demo_close_button()
|
2026-02-14 18:42:20 +01:00
|
|
|
self.container = tk.Frame(self.root, bg="#1a1a1a")
|
|
|
|
|
self.container.place(relx=0.5, rely=0.5, anchor="center")
|
2026-02-24 21:11:05 +01:00
|
|
|
self._phone_future: Future[tuple[str, str]] | None = None
|
2026-06-25 17:50:24 +02:00
|
|
|
self._runnerup_future: Future[tuple[str, str]] | None = None
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
self._runnerup_on_failure: Callable[[], None] | None = None
|
2026-03-29 22:50:24 +02:00
|
|
|
if verify_only:
|
|
|
|
|
self._start_verify_workout_check()
|
2026-05-28 07:04:18 +02:00
|
|
|
elif self._relaxed_day_mode:
|
|
|
|
|
self._start_relaxed_day_flow()
|
2026-03-29 22:50:24 +02:00
|
|
|
else:
|
|
|
|
|
self._start_phone_check()
|
2026-06-21 20:11:16 +02:00
|
|
|
# Always set on this branch; guard only for mypy (can't narrow
|
|
|
|
|
# across two separate if/elif/else statements).
|
|
|
|
|
if self._lock is not None: # pragma: no branch
|
|
|
|
|
self._lock.grab_input()
|
2025-11-30 21:20:17 +01:00
|
|
|
|
2026-05-28 07:04:18 +02:00
|
|
|
def _check_non_verify_exits(self) -> None:
|
|
|
|
|
"""Check all normal (non-verify) startup early-exit conditions."""
|
|
|
|
|
if self._is_scheduled_skip_today():
|
|
|
|
|
_logger.info("Today is a scheduled skip day. Skipping screen lock.")
|
|
|
|
|
sys.exit(0)
|
|
|
|
|
return
|
2026-07-03 15:27:08 +02:00
|
|
|
# Award streak / shutdown-bonus / EB-extension rewards from last week
|
|
|
|
|
# before the daily reset, so a Monday transition's bonus is recorded
|
|
|
|
|
# in time for _apply_weekly_shutdown_bonus below to see it.
|
|
|
|
|
for reward_msg in process_week_transition(self.log_file, EXTRA_BENEFITS_FILE):
|
|
|
|
|
_logger.info("Weekly reward: %s", reward_msg)
|
|
|
|
|
# Reset shutdown config to base (21:00) at the start of each new day,
|
|
|
|
|
# then layer this week's earned bonus back on top of the fresh base.
|
|
|
|
|
if reset_to_base_if_new_day(
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
SHUTDOWN_BASE_FILE, self, sick_day_state_file=SICK_DAY_STATE_FILE
|
2026-07-03 15:27:08 +02:00
|
|
|
):
|
|
|
|
|
self._apply_weekly_shutdown_bonus()
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
# Auto-fill any RunnerUp workouts from earlier in the current ISO week
|
|
|
|
|
# before any early-exit check, so gaps are closed regardless of today's
|
|
|
|
|
# logged state (early_bird, sick_day, etc.).
|
|
|
|
|
prev_count = count_weekly_workouts(self.log_file)
|
|
|
|
|
n_filled = self._scan_and_fill_week_runnerup(self.log_file)
|
|
|
|
|
if n_filled:
|
|
|
|
|
new_count = count_weekly_workouts(self.log_file)
|
|
|
|
|
_logger.info(
|
|
|
|
|
"Auto-filled %d RunnerUp workout(s) from TCX exports.", n_filled
|
|
|
|
|
)
|
|
|
|
|
# Award +1h for each newly auto-filled workout above the minimum.
|
|
|
|
|
bonus = max(0, new_count - max(WEEKLY_WORKOUT_MINIMUM, prev_count))
|
|
|
|
|
if bonus > 0 and self._adjust_shutdown_time_by(bonus):
|
|
|
|
|
_logger.info("Auto-fill extra bonus: +%dh shutdown time.", bonus)
|
2026-05-28 07:04:18 +02:00
|
|
|
if self._check_today_state_exits():
|
|
|
|
|
return
|
|
|
|
|
# Day-of-week routing: Tue/Wed/Thu relaxed (optional), Fri-Mon enforced.
|
|
|
|
|
if is_relaxed_day():
|
|
|
|
|
_logger.info("Relaxed day (Tue-Thu) - showing optional workout prompt.")
|
|
|
|
|
self._relaxed_day_mode = True
|
|
|
|
|
return
|
|
|
|
|
# Fri-Mon: skip lock when weekly minimum is already met.
|
|
|
|
|
if has_weekly_minimum(self.log_file):
|
2026-05-01 19:07:34 +02:00
|
|
|
_logger.info(
|
2026-05-28 07:04:18 +02:00
|
|
|
"Weekly minimum of %d workouts met. Skipping screen lock.",
|
|
|
|
|
WEEKLY_WORKOUT_MINIMUM,
|
2026-05-01 19:07:34 +02:00
|
|
|
)
|
|
|
|
|
sys.exit(0)
|
2026-05-28 07:04:18 +02:00
|
|
|
return
|
2026-07-03 15:27:08 +02:00
|
|
|
# Only remaining same-day skip: genuine extreme heat. Sick days go
|
|
|
|
|
# through the justification flow instead; there is no banked
|
|
|
|
|
# "skip a workout" credit — that mechanic works against the goal of
|
|
|
|
|
# maximizing weekly workouts, so it was removed in favor of a
|
|
|
|
|
# shutdown-time-only reward (see _apply_weekly_shutdown_bonus).
|
2026-06-29 11:23:19 +02:00
|
|
|
hot_temp = is_too_hot(HEAT_SKIP_CITY, HEAT_SKIP_TEMP_THRESHOLD)
|
|
|
|
|
if hot_temp is not None:
|
|
|
|
|
_logger.info(
|
|
|
|
|
"Temperature %.0f°C exceeds threshold — showing heat-skip dialog.",
|
|
|
|
|
hot_temp,
|
|
|
|
|
)
|
|
|
|
|
if self._show_heat_skip_dialog(hot_temp):
|
|
|
|
|
self._save_heat_skip_log(hot_temp)
|
|
|
|
|
_logger.info("User skipped workout due to heat (%.0f°C).", hot_temp)
|
|
|
|
|
sys.exit(0)
|
|
|
|
|
return
|
2026-07-03 15:27:08 +02:00
|
|
|
|
|
|
|
|
def _apply_weekly_shutdown_bonus(self) -> None:
|
|
|
|
|
"""Layer this week's earned shutdown bonus back on top of the fresh base."""
|
|
|
|
|
bonus = weekly_shutdown_bonus_hours(EXTRA_BENEFITS_FILE)
|
|
|
|
|
if bonus > 0 and self._adjust_shutdown_time_by(bonus):
|
|
|
|
|
_logger.info("Weekly bonus: +%dh shutdown time this week.", bonus)
|
2025-11-30 21:20:17 +01:00
|
|
|
|
2026-02-14 18:42:20 +01:00
|
|
|
def _try_adjust_shutdown_for_workout(self) -> bool:
|
|
|
|
|
"""Try to adjust shutdown time later for actual workouts."""
|
|
|
|
|
workout_type = self.workout_data.get("type", "")
|
2026-06-25 17:50:24 +02:00
|
|
|
if workout_type not in COUNTED_WORKOUT_TYPES:
|
2026-02-14 18:42:20 +01:00
|
|
|
return False
|
|
|
|
|
adjusted = self._adjust_shutdown_time_later()
|
|
|
|
|
if adjusted:
|
2026-06-25 17:50:24 +02:00
|
|
|
_logger.info("Shutdown time moved 2 hours later as workout reward")
|
2026-02-14 18:42:20 +01:00
|
|
|
return adjusted
|
2025-11-30 21:20:17 +01:00
|
|
|
|
2026-05-14 19:52:15 +02:00
|
|
|
def _clear_debt_on_verified_workout(self) -> int | None:
|
|
|
|
|
"""Decrement workout debt by one for a verified workout.
|
|
|
|
|
|
|
|
|
|
Returns the new debt count, or ``None`` when this wasn't a
|
|
|
|
|
phone-verified workout.
|
|
|
|
|
"""
|
2026-06-25 17:50:24 +02:00
|
|
|
if self.workout_data.get("type") not in ("phone_verified", "runnerup_verified"):
|
2026-05-14 19:52:15 +02:00
|
|
|
return None
|
|
|
|
|
history = _sick_tracker.load_history()
|
|
|
|
|
if history.debt <= 0:
|
|
|
|
|
return 0
|
|
|
|
|
new_debt = _sick_tracker.clear_one_debt(history)
|
|
|
|
|
_sick_tracker.save_history(history)
|
|
|
|
|
return new_debt
|
|
|
|
|
|
2025-11-30 21:20:17 +01:00
|
|
|
def unlock_screen(self) -> None:
|
|
|
|
|
"""Save workout log and display success message."""
|
2026-07-03 15:27:08 +02:00
|
|
|
# sick_day is already persisted to sick_history.json by
|
|
|
|
|
# _finalize_sick_day — workout_log.json is reserved for real outcomes.
|
|
|
|
|
if self.workout_data.get("type") != "sick_day":
|
|
|
|
|
self.save_workout_log()
|
2026-02-14 18:42:20 +01:00
|
|
|
shutdown_adjusted = self._try_adjust_shutdown_for_workout()
|
2026-05-14 19:52:15 +02:00
|
|
|
new_debt = self._clear_debt_on_verified_workout()
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
|
|
|
|
|
# Extra-workout bonus: +1h per workout above the weekly minimum.
|
|
|
|
|
extra_bonus_delta = 0
|
|
|
|
|
weekly_count = count_weekly_workouts(self.log_file)
|
|
|
|
|
if weekly_count > WEEKLY_WORKOUT_MINIMUM:
|
|
|
|
|
old_cfg = self._read_shutdown_config()
|
|
|
|
|
if old_cfg and self._adjust_shutdown_time_by(1):
|
|
|
|
|
new_cfg = self._read_shutdown_config()
|
|
|
|
|
if new_cfg:
|
|
|
|
|
extra_bonus_delta = new_cfg[1] - old_cfg[1]
|
|
|
|
|
|
2025-11-30 21:20:17 +01:00
|
|
|
self.clear_container()
|
2026-02-14 18:42:20 +01:00
|
|
|
self._label("Great job! 💪", font_size=48, color="#00ff00", pady=30)
|
2026-02-02 21:38:52 +01:00
|
|
|
if shutdown_adjusted:
|
2026-02-14 18:42:20 +01:00
|
|
|
self._text(
|
2026-06-25 17:50:24 +02:00
|
|
|
"Shutdown time +2h later! 🎁",
|
2026-02-14 18:42:20 +01:00
|
|
|
font_size=24,
|
|
|
|
|
color="#ffaa00",
|
2026-02-02 21:38:52 +01:00
|
|
|
)
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
if extra_bonus_delta > 0:
|
|
|
|
|
extra_n = weekly_count - WEEKLY_WORKOUT_MINIMUM
|
|
|
|
|
self._text(
|
|
|
|
|
f"Extra workout #{extra_n}! +{extra_bonus_delta}h tonight",
|
|
|
|
|
font_size=20,
|
|
|
|
|
color="#ffaa00",
|
|
|
|
|
)
|
2026-05-14 19:52:15 +02:00
|
|
|
if new_debt is not None:
|
|
|
|
|
self._text(
|
|
|
|
|
f"Workout debt: {new_debt}",
|
|
|
|
|
font_size=20,
|
|
|
|
|
color="#ffaa00" if new_debt > 0 else "#888888",
|
|
|
|
|
)
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
streak = current_streak(EXTRA_BENEFITS_FILE)
|
|
|
|
|
if streak >= 1:
|
|
|
|
|
self._text(
|
|
|
|
|
f"🔥 {streak}-week streak (5+ workouts each)",
|
|
|
|
|
font_size=14,
|
|
|
|
|
color="#888888",
|
|
|
|
|
)
|
2026-02-14 18:42:20 +01:00
|
|
|
self._text("Screen Unlocked!", font_size=36, pady=20)
|
2026-06-25 17:50:24 +02:00
|
|
|
if self.workout_data.get("type") in ("phone_verified", "runnerup_verified"):
|
2026-05-14 19:52:15 +02:00
|
|
|
self.root.after(
|
|
|
|
|
1500,
|
|
|
|
|
lambda: self._show_commitment_prompt(on_done=self.close),
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
self.root.after(1500, self.close)
|
2025-11-30 21:20:17 +01:00
|
|
|
|
|
|
|
|
def close(self) -> None:
|
|
|
|
|
"""Close the application and exit."""
|
2026-06-21 20:11:16 +02:00
|
|
|
if self._lock is not None:
|
|
|
|
|
self._lock.close()
|
|
|
|
|
else:
|
|
|
|
|
self.root.destroy()
|
2025-11-30 21:20:17 +01:00
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
|
|
def run(self) -> None:
|
|
|
|
|
"""Start the Tkinter main event loop."""
|
2026-06-21 20:11:16 +02:00
|
|
|
if self._lock is not None:
|
|
|
|
|
self._lock.run()
|
|
|
|
|
else:
|
|
|
|
|
self.root.mainloop()
|
2025-11-30 21:20:17 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
if "--status" in sys.argv:
|
|
|
|
|
from screen_locker._status import run_status
|
|
|
|
|
|
|
|
|
|
# Bypass __init__ (no UI) — only log_file and workout_data are needed.
|
|
|
|
|
_sl = object.__new__(ScreenLocker)
|
|
|
|
|
_sl.log_file = Path(__file__).resolve().parent / "workout_log.json"
|
|
|
|
|
_sl.workout_data = {}
|
|
|
|
|
run_status(_sl)
|
|
|
|
|
|
|
|
|
|
demo_mode = True
|
2026-03-29 22:50:24 +02:00
|
|
|
verify_only = "--verify-workout" in sys.argv
|
2025-11-30 21:20:17 +01:00
|
|
|
|
2026-03-29 22:50:24 +02:00
|
|
|
if "--production" in sys.argv:
|
2025-11-30 21:20:17 +01:00
|
|
|
demo_mode = False
|
|
|
|
|
|
2026-03-29 22:50:24 +02:00
|
|
|
locker = ScreenLocker(
|
|
|
|
|
demo_mode=demo_mode,
|
|
|
|
|
verify_only=verify_only,
|
|
|
|
|
)
|
2025-11-30 21:20:17 +01:00
|
|
|
locker.run()
|