`RiderConfig` lived in `RideInputs` and nowhere else, and nothing in the
UI ever called `set_rider_config`. So every ride was ridden as the
struct's own default — a 105 kg rider on an 8 kg bike — with no way to
say otherwise short of editing the source. Mass is not a preference: it
sets the speed a given power produces, the ETA that follows from it, the
calorie estimate, and how a 6% ramp feels. FR-7.4 is a Must, and a
command the UI never calls does not satisfy it.
- settings.rs persists rider config, safety limits and display
preferences to app_data_dir()/settings.json, written atomically and
read back before the first tick, so no snapshot is ever computed
against the default. Advisory like known.rs: an unreadable file costs
the rider their setup, never their ride.
- A stored file is refused *whole* if it fails the same checks the
commands apply. It may predate a tightened bound or have been edited
by hand, and a zero mass reaching the engine divides by itself on the
next tick.
- The commands validate with instructions rather than codes — "CdA must
be between 0.1 and 1.5 m² — a road position is about 0.32" — because
this is now a form a rider fills in, not a struct only I ever touched.
- Preferences (FTP, maximum heart rate, units) are Tauri-side, not in
`RiderConfig`. None of it reaches the physics, and crates/core is the
frozen contract the engine and the FIT writer share. Zero is a real
answer for both references and means "no zones", not "unset and
guessed at".
- SettingsScreen commits on field-exit and reseats every input from what
Rust returned, so a rejected value can never sit on screen looking
accepted. Weight, FTP and units are on top; the eight settings with a
defensible default are folded away.
- Reachable on `,` from any screen, returning to whichever screen opened
it. Setup swallows the ride controls while it is up — a stray arrow
key while reading the form must not trim the gradient of a ride
happening behind it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>