screen_locker pushed manual workouts as "pc" and the workout app as "phone". A role constant collides the moment a second machine takes the same role, and after a reinstall the new install inherits the previous one's CRDT identity. Both halves now mint a uuid once and persist it: the PC in ~/.local/share/screen_locker/.device_id, the app under SharedPreferences 'crdt.nodeId' -- the same key every sibling app uses. The old constant survives as the legacy id and is passed to sync_log / syncLog, so devices/<legacy>/ is skipped as this device's own rather than re-downloaded and re-merged as a peer's on every tick. It drops to None once that path is reclaimed. Note the *read* loop in _workout_sync deliberately still merges every device including this one: the PC needs its own manual workouts in the merged view, so "skip own" would be wrong there. Only the push path and the HLC node id move. The test fixture redirecting the new id file is added in the same change that introduces the constant -- without it the first test that syncs mints a uuid into the real state dir and this machine's live sync identity is decided by whichever test ran first. Verified: ~/.local/share/screen_locker/ held no .device_id after a full suite run. Verified against the live backend, not just mocks: ran the real push_pc_workouts, which minted 57582b3b and pushed 9 records. In the RTDB the new device's log is a strict superset of both old paths -- 0 of devices/pc/'s 10 records and 0 of devices/phone/'s 9 are missing from it. Python: 907 tests, 100% branch coverage. workout_app: 313 tests, analyzer clean. Pins bumped to crdt-sync-v0.6.0 / crdt_sync_dart-v0.7.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZH89WEqAjuJT8WZdqc7AP |
||
|---|---|---|
| .github/workflows | ||
| docs | ||
| screen_locker | ||
| scripts | ||
| stronglift_replacement | ||
| .gitignore | ||
| .mcp.json | ||
| .pre-commit-config.yaml | ||
| CLAUDE.md | ||
| DESIGN_AUDIT_TODO.md | ||
| early-bird-workout-check.timer | ||
| install_autostart.sh | ||
| install_systemd.sh | ||
| install.sh | ||
| pyproject.toml | ||
| README.md | ||
| remove_autostart.sh | ||
| remove_systemd.sh | ||
| requirements.txt | ||
| run.sh | ||
| workout-locker.service | ||
screen-locker
Tkinter/systemd screen locker with workout tracking, sick-day management, and wake-alarm integration. It enforces a workout cadence by locking the screen until a RunnerUp-verified (or explicitly justified) workout is logged.
MCP server (Claude Code integration)
screen-locker exposes a read-only MCP server (screen_locker._mcp) so
Claude Code and its subagents can query workout compliance and the
lock-decision state through typed tools — without shelling out to the
screen-locker-status CLI or opening the Tk window.
- Read-only tools:
get_status(full status snapshot),get_summary(one-line i3blocks summary +ok/warn/lockstate word),explain_lock(why the screen is / isn't locked right now),get_flags(the individual boolean lock-decision predicates for today). - There are no write/action tools, by design. Nothing here logs a workout or mutates state — workouts are logged only from RunnerUp-verified TCX data, never from a caller's claim. No tool exposes the sync token or any HMAC key.
The mcp SDK is an optional dependency (pip install -e '.[mcp]'), kept out of
the CLI/systemd system-python path. One-time setup of the dedicated venv that
Claude Code spawns:
./scripts/setup_mcp.sh
Registration lives in the checked-in .mcp.json (project scope).
Restart Claude Code in this repo and approve the project MCP-server prompt for it
to load. Verify with claude mcp list.