Remember the rider, not only the hardware
`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>
This commit is contained in:
@@ -15,6 +15,7 @@ pub mod devices;
|
||||
pub mod events;
|
||||
pub mod heart_rate;
|
||||
pub mod known;
|
||||
pub mod settings;
|
||||
pub mod profile_view;
|
||||
pub mod recording;
|
||||
pub mod samples;
|
||||
@@ -98,6 +99,8 @@ pub fn run() {
|
||||
commands::set_rider_config,
|
||||
commands::safety_limits,
|
||||
commands::set_safety_limits,
|
||||
commands::preferences,
|
||||
commands::set_preferences,
|
||||
// profiles
|
||||
commands::load_profile_from_path,
|
||||
commands::load_profile_from_text,
|
||||
@@ -130,6 +133,20 @@ pub fn run() {
|
||||
Ok(path) => handle.state::<AppState>().lock().devices.attach_store(path),
|
||||
Err(e) => tracing::warn!(error = %e, "remembered devices unavailable"),
|
||||
}
|
||||
// FR-7.4: the rider's mass, bike and drag, before the first tick
|
||||
// reads them. Restored ahead of the ride loop starting so no
|
||||
// snapshot is ever computed against the 105 kg default the struct
|
||||
// falls back to.
|
||||
match settings::store_path(&handle) {
|
||||
Ok(path) => {
|
||||
let state = handle.state::<AppState>();
|
||||
let mut inner = state.lock();
|
||||
let inner = &mut *inner;
|
||||
let (rider, limits) = (&mut inner.inputs.rider, &mut inner.inputs.limits);
|
||||
inner.settings.attach(path, rider, limits);
|
||||
}
|
||||
Err(e) => tracing::warn!(error = %e, "rider settings unavailable"),
|
||||
}
|
||||
// NFR-7: scanning starts immediately, not on a user click.
|
||||
handle.state::<AppState>().lock().devices.start_scan();
|
||||
state::spawn_ride_loop(handle.clone());
|
||||
|
||||
Reference in New Issue
Block a user