Skip to content

Phone layout, Apple Health from iPhone (status, steps), and a data/ folder - #21

Merged
SeanathanVT merged 9 commits into
developmentfrom
feature/ui-mobile
Oct 8, 2026
Merged

SeanathanVT merged 9 commits into
developmentfrom
feature/ui-mobile

Conversation

@SeanathanVT

@SeanathanVT SeanathanVT commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Phone layout (3.19). base.html had no viewport meta, so phones rendered a shrunken desktop page. Under 480px: the speed reading fills the screen, presets and Pause/End are full-width rows docked at the bottom, every tap target is at least 44px, the header controls fold into a ⋮ menu, and Recent Sessions shows as cards. Desktop layout is pixel-identical to before at 1280px.
  • Screen wake lock (3.12). Native navigator.wakeLock on the Active screen. Browsers only allow it on localhost/HTTPS, so it keeps the computer's screen on but not a phone on LAN HTTP.
  • Home-screen app. static/manifest.json (display: standalone), so Add to Home Screen opens full-screen.
  • Apple Health on iPhone/iPad. A phone can't scan its own screen, so on iOS/iPadOS both QR codes become buttons: Log to Apple Health runs the Shortcut directly, and Install Shortcut opens Apple's Get Shortcut page. Computers keep the QR codes (macOS has no Health app).
  • Prompt auto-clears. The log link is now a shortcuts://x-callback-url/run-shortcut URL whose x-success opens /health_logged/<id>. That marks the session logged, and every open page updates within about a second over SSE, including the desktop that showed the QR. This works with the already-installed Shortcut.
  • Steps in Apple Health. WalkingDad now sends the session's step count, and the updated Shortcut logs it as a Steps sample next to the workout, so treadmill steps count toward the daily total. The install link and the exported apple_health/log-walkingdad-workout.shortcut point to the new version.
  • Per-session status, retroactive log, delete.
    • With export on, each Recent Sessions row shows a filled (logged) or outline (not logged) heart, with a tooltip on hover, tap or focus.
    • Edit reveals a per-row Log (QR on desktop, direct link on iOS) and a Delete button (completed sessions only).
  • data/ folder. Settings, the database, crash-recovery state and the JSON-migration backups move out of the code folder into data/ (backups in data/backups/).
  • Tool configs. pytest.ini, ruff.toml and .coveragerc are merged into pyproject.toml.

Upgrade notes

  • Files move on first start. The first start after upgrading moves config.json, the database (with its -wal/-shm, as one group), session_state.json and any session_history.json.* backups into data/. Nothing is overwritten; a blocked move is skipped and logged. Until then, config.py still reads a root config.json.
  • Settings file location. To edit settings by hand, use data/config.json from now on. config.json.example stays in the root.
  • Reinstall the Shortcut to log steps. Use Install Shortcut (or the setup QR) in Settings. The old Shortcut keeps working and ignores the new steps field. The first run asks for permission to write Steps to Health.
  • Health status values. sessions.health_logged now holds three values: 0 pending, 1 logged, 2 dismissed. Before this, Dismiss also wrote 1, so existing 1 rows show as logged even if some were only dismissed. No schema change or migration.
  • Callback needs the network. The phone has to reach WalkingDad for auto-clear: host must be 0.0.0.0 (the default), and when the desktop uses localhost the callback URL uses its LAN IP instead. If the phone can't reach it, the workout is still logged and only the prompt stays.

Notable implementation details

  • /health_logged/<id> is a GET, because x-success opens as a Safari navigation. It only acts on completed sessions and returns 404 otherwise. The CSRF guard skips GETs, so the most another website could do is mark one session logged.
  • Dismiss acts on the prompt's own session, and never undoes a log. It sends that session's id, so a session ending on another device while the prompt is open can't be dismissed in its place. A Dismiss from a page that hasn't yet seen the log (or the Settings off/on path) leaves a logged session logged.
  • Steps are a separate Health sample. The Shortcuts Log Health Sample action has no End Date and Log Workout can't attach samples, so the steps are stamped at the session start and aren't listed under the workout's Related Samples. Daily totals are correct.
  • iOS detection. It runs before first paint (is-ios class; iPadOS is detected as a touch-capable "Macintosh"), and .ios-only/.not-ios only ever hide elements.
  • Pressed states. Buttons previously faded to transparent while held: Bootstrap's --bs-btn-active-bg was never set. They now show a darker pressed color and shrink slightly, on desktop too.
  • Logged heart color. It uses --text-color rather than the theme accent or Apple pink. Accents fall under 3:1 contrast on 9 of the 15 dark themes, and the pink read as the app's danger red.

Testing

  • Tests: pytest passes (379) and ruff check . is clean. New tests cover:
    • the viewport and manifest
    • the x-callback URL (including steps in the data sent to the Shortcut) and the LAN-IP swap
    • /health_logged validation
    • Dismiss targeting its own session, and leaving a logged session logged
    • per-row Log URLs, and /delete_session (with the samples cascade and the in-progress refusal)
    • the data/ relocation (all-or-nothing database group, never overwriting)
    • the legacy config fallback
    • migration backups landing in data/backups/
  • Headless browser (Playwright, iPhone and desktop user agents):
    • no horizontal overflow and no tap targets under 44px at 375/390px
    • the desktop is pixel-identical when export is off
    • the iOS/desktop swaps, live updates over SSE (prompt, row heart, QR modal), Edit/Log/Delete, and the heart tooltips
  • Upgrade simulation: run on a repo copy with the real config.json, walkingdad.db and migration backups. All 32 sessions were preserved, the settings save went to data/config.json, and the root was left clean.
  • Shared Shortcut: downloaded from the new iCloud link and checked that the Steps sample's value is the steps field.
  • On a real iPhone (iOS 27.0.1):
    • phone layout and the ⋮ menu
    • Add to Home Screen
    • Log from the phone, plus auto-clear when scanning the desktop QR
    • Install Shortcut
    • retroactive Log and Delete
    • theme check of the hearts
    • steps: a test session logged its workout and a 68-step Steps sample, from one run

Not in this PR

  • Wake lock on phones: it needs HTTPS (see 3.12).
  • Android install prompt: it needs square 192/512 icons and HTTPS.
  • Indoor Walk label and steps attached to the workout: Shortcuts' Log Workout has no indoor option and can't attach samples. Both need a native HealthKit app, which can be built free with a personal Apple ID but has to be reinstalled from Xcode every 7 days.
  • Tablet breakpoint (3.17) and swipe gestures (3.7): both now build on the phone layout.

🤖 Generated with Claude Code

SeanathanVT and others added 9 commits October 7, 2026 10:53
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]>
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]>
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]>
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]>
- 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]>
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]>
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]>
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]>
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]>
@SeanathanVT SeanathanVT changed the title Phone layout, Apple Health on iOS, per-session health status, and a data/ folder Phone layout, Apple Health from iPhone (status, steps), and a data/ folder Oct 8, 2026
@SeanathanVT
SeanathanVT merged commit 68b0908 into development Oct 8, 2026
2 checks passed
@SeanathanVT
SeanathanVT deleted the feature/ui-mobile branch October 8, 2026 15:12
@SeanathanVT SeanathanVT mentioned this pull request Oct 8, 2026
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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant