No description
  • TypeScript 77.2%
  • JavaScript 13.3%
  • CSS 5.8%
  • HTML 2%
  • Python 1.4%
  • Other 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Viscid 641aeee72f Start-menu app: npm run install-shortcut; npm start works on every platform
scripts/install-shortcut.mjs writes a BikeBeater Start-menu entry (WSL /
Windows; a .desktop entry on Linux) whose one click runs `npm start` inside
the repo — the repo stays the app, nothing to rebuild after a change. The
shortcut's icon and working directory are Windows-local (Explorer won't
draw an icon from \\wsl.localhost). Icon: crank on blue, chosen from three
(assets/icon.svg → png + ico via scripts/make-icon.mjs, outputs committed).

npm start now launches Electron from node_modules on macOS, Linux and
native Windows, and uses scripts/app.sh (the Windows Electron) only under
WSL. Under WSL it also: adds the Windows system dirs to PATH when a login
shell dropped them (the shortcut's console had no powershell.exe — app.sh
died silently), tracks the overlay's Windows PID after launch, and closes
it with taskkill on Ctrl+C — killing the interop stub alone left the
window open. Verified through the real shortcut: launch, Ctrl+C closes
both halves, closing the overlay ends npm start.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CHfdg8t6xCeWiHhAqb9iXX
2026-08-25 20:51:06 -04:00
assets Start-menu app: npm run install-shortcut; npm start works on every platform 2026-08-25 20:51:06 -04:00
docs Rider profile lives in the DB; Profile tab locked behind Edit → Save 2026-08-25 20:03:24 -04:00
electron Start-menu app: npm run install-shortcut; npm start works on every platform 2026-08-25 20:51:06 -04:00
relay Relay: strap drops were failed pairing, not radio contention 2026-08-18 10:47:54 -04:00
scripts Start-menu app: npm run install-shortcut; npm start works on every platform 2026-08-25 20:51:06 -04:00
server Rider profile lives in the DB; Profile tab locked behind Edit → Save 2026-08-25 20:03:24 -04:00
src Rider profile lives in the DB; Profile tab locked behind Edit → Save 2026-08-25 20:03:24 -04:00
.gitignore Weekly riding time is a setting (riding h/week, 2-6, default 4) 2026-08-19 12:13:14 -04:00
index.html Rider profile lives in the DB; Profile tab locked behind Edit → Save 2026-08-25 20:03:24 -04:00
package-lock.json BikeBeater: cadence-coaching app for the Rogue Echo Bike 2026-08-16 19:21:33 -04:00
package.json Start-menu app: npm run install-shortcut; npm start works on every platform 2026-08-25 20:51:06 -04:00
README.md Start-menu app: npm run install-shortcut; npm start works on every platform 2026-08-25 20:51:06 -04:00
spike.html BikeBeater: cadence-coaching app for the Rogue Echo Bike 2026-08-16 19:21:33 -04:00
tsconfig.json BikeBeater: cadence-coaching app for the Rogue Echo Bike 2026-08-16 19:21:33 -04:00
vite.config.ts BikeBeater: cadence-coaching app for the Rogue Echo Bike 2026-08-16 19:21:33 -04:00

BikeBeater

Ride your Rogue Echo Bike (V3 connected console) to a program by ear: the app plays a metronome — one beep per pedal revolution — at the tempo that produces the target console RPM, and distinct audio cues when your actual cadence drifts outside tolerance. The console broadcasts live cadence/power over Bluetooth FTMS; the app compares what you're doing to what the program asks and nudges you back.

A one-time rhythm calibration (Sound card → Calibrate rhythm) maps beep tempo to console RPM: the console reports flywheel RPM, which is belt-geared faster than the cranks by an unpublished ratio, so the mapping must be measured, not assumed. You pedal one revolution per beep through three tempos (~90 s); the median transmitted cadence per tempo over the beat rate gives the gear ratio, cross-checked for consistency and snapped to a simple mapping when it is one (src/engine/calibrate.ts). Until calibrated, the app assumes 1:1 and the metronome will feel wrong — calibrate first.

Run

npm install
npm start          # server + desktop overlay, one terminal; close the overlay to stop both

npm start starts the Vite dev server (with the SQLite API) if nothing is on :5173, waits for it, then launches the overlay; a server already running is reused. For the pieces separately: npm run dev (server) and npm run app (overlay).

npm run install-shortcut puts BikeBeater in the Start menu (Windows / WSL — pin it to the taskbar from there) or in the app launcher (Linux); one click runs npm start. The repo is the app: a code change is live on the next launch, nothing to rebuild or reinstall.

Platforms: on macOS, Linux and native Windows the overlay is Electron from node_modules. Under WSL the overlay must run on the Windows side (a transparent window doesn't survive WSLg), so install the Windows Electron build into WSL — npm_config_platform=win32 npm install — and npm start launches it through scripts/app.sh while the server stays in WSL.

Or open http://localhost:5173 in Chrome or Edge on the Windows side (Web Bluetooth doesn't exist in WSL, and localhost forwarding makes the WSL dev server reachable from Windows). Press the console's Bluetooth/connect state if it needs one, click Connect Echo bike, pick a program, Start ride.

  • ?demo=1 — desk demo: fake bike + short program, straight to the ride screen (click the sound chip once; browsers block audio until a gesture).

Hardware spike (do this first on the real bike)

http://localhost:5173/spike.html — connect, pedal a few minutes including sprints and a full stop, Export JSON. The export answers the open hardware questions (real notify rate/jitter, flag layout, 0-RPM behavior, reconnect quirks) and becomes test fixtures in src/fixtures/.

Facts the design leans on

  • The Echo's cadence is genuinely measured (reed switch on the flywheel) but the number is flywheel RPM, not crank RPM — fixed belt gearing links them by a constant, unpublished ratio (confirmed on-bike: a 1-beep-per- transmitted-RPM metronome is audibly out of sync with the pedals). Watts are derived from RPM via a fan curve — console-relative, not calibrated power. Program targets and drift detection stay in console units; only the metronome tempo is divided by the calibrated ratio.
  • Because watts are a function of RPM, programs target cadence; expected watts are shown as an estimate once the app has passively learned enough (rpm, watts) pairs (src/engine/powermodel.ts, localStorage).
  • FTMS Indoor Bike Data arrives ~1 Hz as a scalar — beeps set the tempo but cannot phase-lock to crank position.
  • Drift alerting is a state machine (src/engine/drift.ts): EMA smoothing, outer/inner hysteresis bands, dwell times, re-alert cooldown, grace period after interval changes, and separate "stopped" and "stale" states.

Tests

npm test

Covers FTMS frame parsing, the drift state machine (alert/clear/grace/ stopped/stale/overshoot timing), and program stepping. Recorded rides (Download recording / spike export) can be replayed through the detector to tune thresholds without re-riding.