Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# AGENTS.md

## Docs
Keep all docs (ROADMAP.md, CHANGELOG.md `[Unreleased]`, README.md, docs/) in sync with the code and with any plans discussed, in the same change. Released CHANGELOG sections are immutable.

## Git
- Never commit. The maintainer signs commits, and agent commits come out unsigned. Hand over the commit message and list the files to add. Messages follow [Conventional Commits](https://www.conventionalcommits.org/): `type(scope): summary`, e.g. `fix(ui): show transition hint only during belt sequences (ROADMAP 3.18)`, ending with a `Co-Authored-By:` trailer naming the agent.
- Don't stage unless asked in that turn, and then `git add` specific paths only (never `docs/` plan files). The maintainer reviews `git diff` first.
- Run `git status`/`git log` before any claim about commit state; the maintainer commits between turns, so earlier snapshots go stale.
- Feature branches merge into `development`, not `main`. Open PRs with `--base development` and scope diffs/logs against `origin/development`.

## Platforms
The maintainer runs macOS (primary) and Fedora Linux, never Windows. Suggest manual checks on macOS first, Fedora when Linux behavior differs. Windows-only code (`start_app.bat`, `WSAE*` errnos, Windows socket semantics) can't be verified by the maintainer; flag it rather than asking for a test.

## Design target
The primary client is a laptop browser at a desk above the treadmill. Design for desktop width and mouse/keyboard first; keep the phone layout working, but it doesn't drive layout or feature decisions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ All notable changes to WalkingDad will be documented in this file.
- **Other websites can no longer control the treadmill** (ROADMAP 2.2). A page open in your browser could previously submit Start, speed, Clear History, Bluetooth reconnect, or Shutdown requests to WalkingDad behind your back. Requests that come from another website are now refused. Using WalkingDad from any device on your network works exactly as before.
- **Pause and speed buttons no longer error out when Bluetooth isn't running.** If the Bluetooth connection had already gone away, pressing Pause or a speed button showed a server error page. Pause now still pauses the session, and the speed buttons do nothing.
- **A corrupted crash-recovery file no longer stops the app from starting.** If `session_state.json` contained unreadable (non-UTF-8) bytes, startup crashed. Startup now ignores the file, the same as an unparseable one.
- **Speed buttons no longer flash a "Getting the belt moving…" message** (ROADMAP 3.18). Presets and the +/- buttons briefly showed it and pushed the buttons down, and Start, Pause, Resume and End flashed the wrong message ("Stopping belt…", "Pausing belt…"). The message now appears only while the belt is actually starting, pausing or stopping.
- **A quick double-tap can no longer send a button press twice.** Buttons could re-enable mid-request on a live stats update; they now stay disabled from the moment you press one until the next screen loads.

### Internal

Expand All @@ -34,6 +36,7 @@ All notable changes to WalkingDad will be documented in this file.
- **Importing `app.py` with `WALKINGDAD_NO_STARTUP=1` no longer installs the Ctrl+C/SIGTERM handlers or the `atexit` hook**, so pressing Ctrl+C during a test run interrupts pytest instead of killing the process two seconds later. Running the app normally is unchanged.
- **pytest, ruff, and coverage settings merged into `pyproject.toml`**, replacing `pytest.ini`, `ruff.toml`, and `.coveragerc`. `requirements-dev.txt` adds `coverage[toml]` so coverage reads it on Python 3.10.
- **`run.py`'s launcher logic moved into a `main()` function** so it can be tested. `python run.py` behaves the same.
- **`AGENTS.md` added for AI coding agents** (`CLAUDE.md` is a symlink to it): keep docs in sync with code, the git workflow (PRs target `development`, the maintainer commits), the desktop-first design target, and the supported platforms.

---

Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
17 changes: 6 additions & 11 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,17 +456,12 @@ The Active screen requests a native screen wake lock (`navigator.wakeLock`) and

---

### 3.18 Transition Hint Shows on Every Button Press
- **Status:** Planned
- **Priority:** Medium
- **Problem:** Clicking a speed preset or stepper on the active screen briefly shows "Getting the belt moving…" with a spinner, and the buttons jump down while it's visible. Every button has a variant of this, and the text is wrong for most of them:
- Pause and End flash "Getting the belt moving…"
- Start flashes "Stopping belt…"
- Resume flashes "Pausing belt…"
### ✅ 3.18 Transition Hint Shows on Every Button Press
**Status:** ✅ Complete
**Priority:** Medium
**Files Modified:** `templates/base.html`, `templates/active_session.html`, `templates/paused_session.html`, `templates/start_session.html`

Each page's `#transitioning-hint` text describes the transition *into* that page (active: after Start/Resume; paused: after Pause; start: after End), and the `belt_transitioning` flag in the SSE stats payload is meant to drive it. But `disableButtonsOnSubmit()` in `templates/base.html` also calls `setTransitioning(hintId, …, true)` on every form submit, so the hint appears for the request round-trip. The page then reloads with it hidden again, because speed changes never set `_belt_transitioning`. The hint toggles `display: none`, so showing it reflows the layout.
- **Solution:** On submit, only disable and dim the buttons, which is all the double-tap guard needs. Leave the hint to the server's `belt_transitioning` flag, so it only ever shows during a real start/pause/resume/end belt sequence, with text that matches. Optionally reserve the hint's space (toggle `visibility` instead of `display`) so the legitimate post-Start/Resume hint doesn't shift the layout either.
- **Implementation:** Drop the hint from `disableButtonsOnSubmit()` (`templates/base.html`; it can take just `buttons`, or call `setTransitioning(null, buttons, true)`, which `getElementById(null)` already tolerates). Update its three call sites in `active_session.html`, `paused_session.html` and `start_session.html`, plus the comments there that describe the behavior. No backend change. Verify by hand in the browser: presets and steppers show no hint or shift, and the correct hint still appears after Start, Pause, Resume and End while the belt sequence runs. Template JS has no automated tests (see 2.12).
Submitting a form only disables and dims the buttons now; each page's `#transitioning-hint` follows the SSE `belt_transitioning` flag alone, so it shows only during a real start/pause/resume/end belt sequence, with matching text. A `submitting` flag in `base.html` also keeps the buttons disabled through SSE ticks until the page navigates away (cleared on a back/forward-cache restore), closing a double-tap window. The hint still toggles `display`, so the legitimate post-Start/Resume hint shifts the layout briefly; reserving its space was skipped to avoid a permanent gap on the phone layout. See `CHANGELOG.md` `[Unreleased]`.

---

Expand Down Expand Up @@ -535,7 +530,7 @@ Added the missing viewport meta, so phones get a real phone layout instead of a

Verified against current Apple documentation before building (the original plan below, kept for history, assumed a network-fetch design that turned out to need correcting): no Apple Developer Program membership is needed (a personal Shortcut is never distributed), and critically, the Health app does not exist on macOS as of Tahoe 26. This can only run on the iPhone, not the Mac WalkingDad itself runs on.

Two QR codes, both self-contained (no fetch back to WalkingDad's server): a **setup QR** (a plain iCloud share link, `Share → Copy iCloud Link` in the Shortcuts app) installs the Shortcut once per phone, and a **per-session QR** (`shortcuts://run-shortcut?name=...&input=text&text=<session JSON>`) runs it with that session's data embedded directly in the URL. Hosting the `.shortcut` file on GitHub and using `shortcuts://import-shortcut?url=...` was tried first and abandoned. It reliably failed with "shortcut URL provided was invalid" across every URL encoding/ordering tried by hand, a known, documented unreliability of GitHub-hosted `.shortcut` files with that scheme, not an encoding bug on WalkingDad's end. An iCloud link is Apple's actual supported distribution path. The Shortcut itself (`Get Dictionary from Input` → `Log Workout`, Type Walking, Date/Duration/Calories/Distance bound via Magic Variable, then `Log Health Sample` for Steps) was hand-built in the Shortcuts app and shared from there, since there's no official API to generate a `.shortcut` file programmatically.
Two QR codes, both self-contained (no fetch back to WalkingDad's server): a **setup QR** (a plain iCloud share link, `Share → Copy iCloud Link` in the Shortcuts app) installs the Shortcut once per phone, and a **per-session QR** (`shortcuts://run-shortcut?name=...&input=text&text=<session JSON>`) runs it with that session's data embedded directly in the URL. Hosting the `.shortcut` file on GitHub and using `shortcuts://import-shortcut?url=...` was tried first and abandoned. It reliably failed with "shortcut URL provided was invalid" across every URL encoding/ordering tried by hand, a known, documented unreliability of GitHub-hosted `.shortcut` files with that scheme, not an encoding bug on WalkingDad's end. An iCloud link is Apple's actual supported distribution path. The Shortcut itself (`Get Dictionary from Input` → `Log Workout`, Type Walking, Date/Duration/Calories/Distance bound via Magic Variable, then `Log Health Sample` for Steps) was hand-built in the Shortcuts app and shared from there, since there's no official API to generate a `.shortcut` file programmatically. When rebuilding it, `Log Health Sample`'s Value row for Steps only appears after granting Shortcuts write access to Steps.

**Off by default.** An `apple_health_export_enabled` setting (Settings page, styled as a pill/slide toggle) gates the whole feature; when off, neither QR nor the start-screen prompt appears. Turning it on doesn't retroactively surface an old, unrelated session that happened to be most recent before the feature existed: the currently-most-recent session is pre-marked as handled on that specific off→on transition, unless it's from today, in which case it's left showing (plausibly the reason someone would enable the feature mid-session in the first place).

Expand Down
2 changes: 1 addition & 1 deletion templates/active_session.html
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ <h1 class="visually-hidden">Active Session</h1>
// buttons need to stay disabled/dim until belt_transitioning clears, not
// just during the on-submit window.
var actionButtons = document.querySelectorAll('button[type="submit"]');
disableButtonsOnSubmit('transitioning-hint', actionButtons);
disableButtonsOnSubmit(actionButtons);

// Native only: needs a secure context (localhost/HTTPS), so a phone on LAN HTTP skips it.
if ('wakeLock' in navigator) {
Expand Down
26 changes: 21 additions & 5 deletions templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -1543,23 +1543,39 @@ <h2> -- Active/Paused/Start's own page headings are
// Toggles a "<verb> belt..." hint and disables/dims the buttons that
// shouldn't be double-tapped while a belt sequence is in flight. Shared
// by active/paused/start, which each pass their own hint id and button set.
// Once a form has submitted, buttons stay disabled regardless of `on`, so
// an SSE tick landing mid-request can't re-enable them.
var submitting = false;
function setTransitioning(hintId, buttons, on) {
var hint = document.getElementById(hintId);
if (hint) hint.style.display = on ? '' : 'none';
var disable = on || submitting;
buttons.forEach(function(b) {
b.disabled = on;
b.style.opacity = on ? '0.5' : '';
b.disabled = disable;
b.style.opacity = disable ? '0.5' : '';
});
}

// Wires every form on the page to disable/dim `buttons` the instant any
// of them is submitted -- the SSE-driven belt_transitioning update that
// setTransitioning() above also responds to can lag behind by a full
// network round-trip, leaving a window for a double-tap otherwise.
function disableButtonsOnSubmit(hintId, buttons) {
// network round-trip, leaving a window for a double-tap otherwise. The
// hint is left to belt_transitioning alone: most submits (presets,
// steppers) start no belt sequence, and each page's hint text only fits
// the sequence that led to that page.
// `submitting` never clears in normal use since every submit navigates
// away; a back/forward-cache restore brings the page back with it set.
function disableButtonsOnSubmit(buttons) {
window.addEventListener('pageshow', function(e) {
if (e.persisted) {
submitting = false;
setTransitioning(null, buttons, false);
}
});
document.querySelectorAll('form').forEach(function(form) {
form.addEventListener('submit', function() {
setTransitioning(hintId, buttons, true);
submitting = true;
setTransitioning(null, buttons, true);
});
});
}
Expand Down
2 changes: 1 addition & 1 deletion templates/paused_session.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ <h1 class="visually-hidden">Session Paused</h1>

<script>
var pausedButtons = [document.getElementById('btn-resume'), document.getElementById('btn-end')];
disableButtonsOnSubmit('transitioning-hint', pausedButtons);
disableButtonsOnSubmit(pausedButtons);

startStatsStream(function(data) {
if (data.session_active === false) {
Expand Down
2 changes: 1 addition & 1 deletion templates/start_session.html
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ <h2 class="content-title" style="font-size:1.5rem;">Recent Sessions</h2>
// can't fire twice -- matches active/paused_session.html. Only Start
// itself reacts to the belt_transitioning SSE signal below, since
// Restore/Discard aren't an in-flight belt sequence the same way.
disableButtonsOnSubmit('transitioning-hint', document.querySelectorAll('button[type="submit"]'));
disableButtonsOnSubmit(document.querySelectorAll('button[type="submit"]'));

startStatsStream(function(data) {
setTransitioning('transitioning-hint', startButtons, data.belt_transitioning === true);
Expand Down
Loading