Repository navigation
Add full unit test suite, CI for GitHub Actions and GitLab, and two crash fixes (2.5, 2.7) - #19
Merged
Merged
Conversation
Cover routes, BLE sequences (fake controller), status-packet math, persistence, and run.py; 300 tests, ~98% line coverage, green on Python 3.10/3.12/3.13. CI runs ruff and pytest with coverage on GitHub Actions and GitLab CI. fix: /pause and speed routes no longer return 500 when the BLE loop is gone; a non-UTF-8 session_state.json no longer crashes startup. Install signal and atexit handlers only when startup is enabled, move run.py's launcher into main() for testability, and pin ruff with its lint fixes applied. Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Claude <[email protected]>
Add tests for non-list and bad-end_time legacy history, a failed .migrated rename, graceful shutdown with no session/controller/client, End while paused, connect with no BLE client, discard with nothing pending, /shutdown loop-stop edge cases, SSE double-remove on close, and a failed broadcaster tick. Each test fails if its target branch is broken. storage.py reaches 100%; line+branch coverage 97.3% -> 98.4%. Update ROADMAP 2.5's "Not covered" list to match the measured gaps. Co-Authored-By: Claude <[email protected]>
Merged
SeanathanVT
added a commit
that referenced
this pull request
Oct 8, 2026
* Add unreleased section to CHANGELOG. * Add full unit test suite, CI for GitHub Actions and GitLab, and two crash fixes (2.5, 2.7) (#19) * feat(tests): add full unit test suite and CI (ROADMAP 2.5, 2.7) Cover routes, BLE sequences (fake controller), status-packet math, persistence, and run.py; 300 tests, ~98% line coverage, green on Python 3.10/3.12/3.13. CI runs ruff and pytest with coverage on GitHub Actions and GitLab CI. fix: /pause and speed routes no longer return 500 when the BLE loop is gone; a non-UTF-8 session_state.json no longer crashes startup. Install signal and atexit handlers only when startup is enabled, move run.py's launcher into main() for testability, and pin ruff with its lint fixes applied. Co-Authored-By: Claude <[email protected]> * docs(roadmap): add 3.18 transition hint flashing on every button press Co-Authored-By: Claude <[email protected]> * test: cover migration edge cases, shutdown paths, and SSE failures Add tests for non-list and bad-end_time legacy history, a failed .migrated rename, graceful shutdown with no session/controller/client, End while paused, connect with no BLE client, discard with nothing pending, /shutdown loop-stop edge cases, SSE double-remove on close, and a failed broadcaster tick. Each test fails if its target branch is broken. storage.py reaches 100%; line+branch coverage 97.3% -> 98.4%. Update ROADMAP 2.5's "Not covered" list to match the measured gaps. Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Claude <[email protected]> * fix(security): block cross-site state changes (ROADMAP 2.2) (#20) * fix(security): block cross-site POST requests (ROADMAP 2.2) Reject any non-GET request whose Origin isn't the app's own origin, or whose Sec-Fetch-Site is cross-site or same-site, so a website open in a browser on the network can't start the belt, change speed, clear history, or shut the app down. No token or sign-in: the app is LAN-only and LAN devices are trusted by design. Same-origin forms and fetches, requests without either header (curl, run.py's /shutdown), and all GETs are unaffected. Co-Authored-By: Claude <[email protected]> * fix(security): make /reconnect POST-only and harmonize docs (ROADMAP 2.2) /reconnect changed state on GET, so the cross-site guard skipped it and any web page could trigger a Bluetooth reconnect. Make it POST-only and turn the connecting screen's Connect / Try Again links into form buttons, so every state-changing route goes through the guard. Also harmonize docs and comments with the code: - shutdown comments: /stats_stream frames instead of /stats polling - remove "previously / before this change" narration from comments - README: startup connects in up to 3 attempts, database_path isn't on the Settings page, add apple_health_export_enabled row, note the LAN trust model on the host row - ROADMAP 2.2: files and known limits; 3.4: point at _build_stats_payload() - CHANGELOG: /reconnect POST-only, reconnect in the blocked actions Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Claude <[email protected]> * docs(roadmap): add 3.19 phone layout Co-Authored-By: Claude <[email protected]> * Phone layout, Apple Health from iPhone (status, steps), and a data/ folder (#21) * feat(ui): phone layout and screen wake lock (ROADMAP 3.19, 3.12) Add viewport meta so phones get a real layout instead of a scaled-down desktop page. Under 480px: full-height hero, bottom-docked presets and Pause/End, 44px+ targets, header controls behind a menu button, history as cards. Add a web app manifest for full-screen home-screen launch, pressed states for buttons (Bootstrap's default went transparent), and a native screen wake lock on the active screen. Co-Authored-By: Claude <[email protected]> * feat(health): on-device Log/Install buttons and auto-confirm on iOS A phone can't scan its own screen, so on iPhone/iPad the per-session and setup QR codes become buttons. The log link is now an x-callback-url whose x-success opens /health_logged/<id>, marking the session logged and clearing the prompt on every open page via SSE. Dismiss is stored separately (health_logged: 0 pending, 1 logged, 2 dismissed). Loopback hosts are swapped for the LAN IP so the phone can reach the callback. Co-Authored-By: Claude <[email protected]> * feat(health): per-session Apple Health status, retroactive log, delete Recent Sessions shows a filled/outline heart per session when Apple Health export is on. An Edit toggle reveals per-row Log (QR on desktop, direct Shortcut link on iOS) and Delete (POST /delete_session/<id>, completed sessions only). SSE health_status_changed now carries {id, status} so other pages update the row, banner, and open QR modal. Co-Authored-By: Claude <[email protected]> * chore: move runtime files into data/, tool configs into pyproject.toml Settings, the session database, crash-recovery state, and JSON-migration backups now live in data/ (backups in data/backups/) instead of next to the code. Files left in the app folder by older versions are moved on first start, never overwriting an existing file. config.py reads the legacy root config.json until it moves, and migrate_json() takes a backup_dir. pytest, ruff, and coverage settings merge into pyproject.toml, replacing pytest.ini, ruff.toml, and .coveragerc; requirements-dev.txt adds coverage[toml] so coverage reads it on Python 3.10. Co-Authored-By: Claude <[email protected]> * fix: review follow-ups for Apple Health status and data/ relocation - Close the Log QR modal only on a "logged" status change; a lingering "dismissed" change closed a later Log for that same session. - Dismiss sends the banner's session id instead of marking whichever session is newest at click time. - /health_logged and dismiss only act on completed sessions; unknown, in-progress, or failed writes return 404 without broadcasting. - Move the database with its -wal/-shm as one group so a stale file in data/ can't separate them. Co-Authored-By: Claude <[email protected]> * feat(ui): heart icon tooltips, right-aligned history numbers Bootstrap tooltips on the Recent Sessions heart icons, shown on hover, tap, and keyboard focus, and updated live when a session is logged elsewhere. Duration through Avg Speed are now right-aligned like Avg Speed already was, so spare width spreads evenly instead of collecting before the last column. Co-Authored-By: Claude <[email protected]> * style(ui): monochrome heart for logged sessions The logged heart uses the theme's text color instead of Apple Health pink, which clashed with themed palettes, fell just under 3:1 contrast on some light rows, and read as the app's danger red. Theme accents were too faint on most dark-mode rows. Fill vs outline carries the state. Co-Authored-By: Claude <[email protected]> * feat(health): send step count to the Apple Health Shortcut The Shortcut logs it as a Steps sample alongside the workout. Points the install link and the exported backup at the updated Shortcut. Co-Authored-By: Claude <[email protected]> * fix(health): don't let Dismiss undo a logged session A page that hasn't seen the log yet (or the Settings off/on path) could downgrade a logged session to dismissed, bringing back its Log button and inviting a duplicate Health workout. Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Claude <[email protected]> * docs(roadmap): add 5.4 native HealthKit companion app Records the Shortcuts limits found while adding steps (no indoor flag, no End Date, no attaching samples) and that HealthKit works on a free Apple ID with 7-day reinstalls. Co-Authored-By: Claude <[email protected]> * Fix transition hint flashing on every button press (ROADMAP 3.18); add AGENTS.md (#22) * fix(ui): show transition hint only during belt sequences (ROADMAP 3.18) Submitting any form called setTransitioning() with the page's hint id, so presets and steppers flashed "Getting the belt moving…" and shifted the layout, and Start/Pause/Resume/End flashed the wrong text. disableButtonsOnSubmit() now only disables and dims the buttons, and the hint follows the SSE belt_transitioning flag alone. Also stop SSE ticks from re-enabling the buttons while a submit is in flight: a page-level submitting flag keeps them disabled until the page navigates away, and is cleared on a back/forward-cache restore. Co-Authored-By: Claude <[email protected]> * docs: mark ROADMAP 3.18 complete and add CHANGELOG entries Co-Authored-By: Claude <[email protected]> * docs: add AGENTS.md (CLAUDE.md symlink) and Shortcut rebuild note AGENTS.md holds repo rules for coding agents: keep docs in sync with code, git workflow (maintainer commits, PRs target development), desktop-first design target, and supported platforms. CLAUDE.md links to it so Claude Code reads the same file. ROADMAP: note that Log Health Sample's Steps Value row only appears after granting Shortcuts write access to Steps. Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Claude <[email protected]> * docs(changelog): release 1.9.0 Map every completed ROADMAP item to its CHANGELOG version. Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Claude <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements ROADMAP 2.5 (Unit Tests) and 2.7 (Continuous Integration). Writing the tests turned up two
app.pybugs, which this PR also fixes, and a front-end bug that is logged as ROADMAP 3.18 for a follow-up.ruffandpytestwith a coverage report.Fixes
/pause, the steppers and the presets calledasyncio.run_coroutine_threadsafewithout the try/except that/startand/resumealready had. With no loop running they returned a server error, and/pausehad already changed session state by then. They now log the error and redirect.session_state.jsoncrashed startup._load_session_state()caughtJSONDecodeErrorbut notUnicodeDecodeError. It now catchesValueError, which covers both.Tests
tests/conftest.pyadds two shared fixtures:app_state: resets everyapp.pyglobal, points the session-state file,config.jsonand the database attmp_path, and records BLE coroutines instead of scheduling them.walking: a session in progress with a controllable clock, moved here fromtest_active_time.py.test_app_helpers.pyconfig.jsonwrites, DB helpers, and "log and continue" when storage failstest_status_packet.pytest_routes.py/stats_streamand/shutdowntest_ble_async.pytest_run.pyrun.py's browser launch, HTTP shutdown call, andmain()'s Waitress subprocess and Ctrl+C handlingtest_port_check.pyThe tests never touch the real
config.json,walkingdad.dborsession_state.json.Verification:
-W error::RuntimeWarning.CI
.github/workflows/ci.yml:ubuntu-latest, Python 3.10 (the supported floor) and 3.13. Runs on pull requests and on pushes tomain/development..gitlab-ci.yml:python:3.12. Uses merge-request pipelines when an MR is open and branch pipelines otherwise, and shows the coverage percentage plus a Cobertura report in the MR.pip install -r requirements.txt -r requirements-dev.txt, thenruff check ., thenpython -m pytest --cov=.. Coverage is reported but never fails the build.Supporting changes
SIGINT/SIGTERMhandlers and theatexithook are now installed only when startup is enabled (WALKINGDAD_NO_STARTUPnot set). Before, importingapp.pyin tests installed them, and Ctrl+C killed pytest two seconds later.run.py: the launcher code moved intomain()so it can be tested;python run.pyworks the same.decrease_speed/increase_speednow reuse_set_preset_speed, so the guard and try/except live in one place.ruff==0.16.10is pinned inrequirements-dev.txt, because its default rule set changes between releases.ruff.tomlignores four rules that only flag deliberate project style: the root logger, catch-all excepts around BLE I/O, naive local datetimes, and oneValueErrorraised to share anexcept.app.py: sorted imports, unusedglobaldeclarations removed, one nestedifcombined,IOErrorreplaced byOSError, and dict literals. None change behavior.pytest.ini(pythonpath = ., so barepytestworks),.coveragerc(leaves tests out of coverage), and.gitignoreadditions for coverage output.Docs
disableButtonsOnSubmit()shows each page's transition hint on every form submit, so for example clicking a speed preset flashes "Getting the belt moving…" and shifts the layout. It predates this branch; the fix is deferred to its own branch.ruffand CI.[Unreleased]has Fixed and Internal entries. Released sections are unchanged.Test plan
ruff check .is cleanpython -m pytestpasses on Python 3.10, 3.12 and 3.13TOTALlineactionlintpasses on the GitHub workflow🤖 Generated with Claude Code