From 3c06100a23ac9549e39b79dd8f4b05211fe789d3 Mon Sep 17 00:00:00 2001 From: lambda Date: Wed, 28 Oct 2026 14:39:43 +0300 Subject: [PATCH] fix: PR review changes --- src-tauri/src/workout.rs | 25 ++++++++++++++++++++++++- src/index.html | 2 -- src/main.js | 9 +++------ src/styles.css | 9 --------- 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src-tauri/src/workout.rs b/src-tauri/src/workout.rs index 2549362..1889a7d 100644 --- a/src-tauri/src/workout.rs +++ b/src-tauri/src/workout.rs @@ -99,6 +99,8 @@ struct RawStep { #[derive(Deserialize)] struct RawPower { + #[serde(default)] + units: Option, #[serde(default)] value: Option, #[serde(default)] @@ -160,6 +162,12 @@ fn convert_step(raw: RawStep) -> WorkoutStep { /// линейный ramp от свободного диапазона. Предполагаем `units == "%ftp"` /// (все скачанные воркауты используют %ftp); абсолютные ватты не поддерживаются. fn convert_power(p: RawPower, ramp: bool) -> Option { + // Отвергаем воркауты в абсолютных ваттах: перевод watts → %FTP требует FTP, + // которого нет на этапе парсинга. Принимаем только %ftp (units == None + // трактуем как %ftp — все скачанные воркауты используют %ftp). + if matches!(p.units.as_deref(), Some(u) if u != "%ftp") { + return None; + } if let Some(v) = p.value { return Some(PowerTarget::Fixed { percent: v }); } @@ -199,7 +207,11 @@ pub fn total_duration_secs(segments: &[Segment]) -> u64 { } /// Целевая мощность (%FTP) в момент `elapsed_secs` внутри шага. -/// Fixed — константа; Ramp — линейная интерполяция from→to; Range — середина. +/// Fixed — константа; Ramp — линейная интерполяция from→to. +/// +/// Range (свободный диапазон low..high) сводится к середине: в ERG-режиме +/// нужна конкретная цель, середина — осознанный дефолт. Альтернативы: не слать +/// ERG вовсе (свободный ход) или слать low как «пол». pub fn target_percent(power: &PowerTarget, elapsed_secs: f64, duration_secs: u32) -> f64 { match power { PowerTarget::Fixed { percent } => *percent, @@ -330,6 +342,17 @@ mod tests { assert_eq!(w.steps[0].cadence_rpm, Some(85)); } + #[test] + fn rejects_non_ftp_power_units() { + // units:"watts" нельзя перевести в %FTP без FTP на этапе парсинга — + // шаг должен терять цель мощности, а не трактовать ватты как проценты. + let json = r#"{"name":"W","workout_doc":{"steps":[ + {"text":"X","duration":60,"power":{"units":"watts","value":200}} + ]}}"#; + let w = parse_workout(json).unwrap(); + assert_eq!(w.steps[0].power, None); + } + #[test] fn flatten_expands_repeats() { let w = parse_workout(EVENT).unwrap(); diff --git a/src/index.html b/src/index.html index 8c28b29..9d72778 100644 --- a/src/index.html +++ b/src/index.html @@ -58,13 +58,11 @@ мощность — — -
каденс — — -
чсс diff --git a/src/main.js b/src/main.js index ecad020..79bb870 100644 --- a/src/main.js +++ b/src/main.js @@ -211,10 +211,8 @@ const workoutStopBtn = document.querySelector("#workout-stop"); // --- Отклонение от цели --- const powerMetric = document.querySelector("#power-metric"); const powerTarget = document.querySelector("#power-target"); -const powerWarn = document.querySelector("#power-warn"); const cadenceMetric = document.querySelector("#cadence-metric"); const cadenceTarget = document.querySelector("#cadence-target"); -const cadenceWarn = document.querySelector("#cadence-warn"); const POWER_TOL_PCT = 0.1; const POWER_TOL_ABS = 15; // Вт @@ -229,11 +227,10 @@ function powerTolerance(target) { return Math.max(POWER_TOL_ABS, Math.round(target * POWER_TOL_PCT)); } -function updateDeviation(metricEl, targetEl, warnEl, actual, target, tolerance, unit) { +function updateDeviation(metricEl, targetEl, actual, target, tolerance, unit) { targetEl.textContent = target != null ? `${target} ${unit}` : "—"; if (actual == null || target == null) { metricEl.classList.remove("metric--off"); - warnEl.textContent = ""; return; } const diff = actual - target; @@ -245,11 +242,11 @@ function updateDeviation(metricEl, targetEl, warnEl, actual, target, tolerance, } function updatePowerDeviation() { - updateDeviation(powerMetric, powerTarget, powerWarn, actualWatts, targetWatts, powerTolerance(targetWatts ?? 0), "W"); + updateDeviation(powerMetric, powerTarget, actualWatts, targetWatts, powerTolerance(targetWatts ?? 0), "W"); } function updateCadenceDeviation() { - updateDeviation(cadenceMetric, cadenceTarget, cadenceWarn, actualCadence, targetCadence, CADENCE_TOL_ABS, "rpm"); + updateDeviation(cadenceMetric, cadenceTarget, actualCadence, targetCadence, CADENCE_TOL_ABS, "rpm"); } function clearTargets() { diff --git a/src/styles.css b/src/styles.css index 36f1a3c..b65f82a 100644 --- a/src/styles.css +++ b/src/styles.css @@ -347,15 +347,6 @@ body { font-weight: var(--fw-semibold); } -/* предупреждение — резервируем строку, чтобы не прыгало */ -.metric__warn { - min-height: var(--text-base); - font-size: var(--text-xs); - font-weight: var(--fw-medium); - color: var(--c-danger); - line-height: 1.2; -} - /* --- График мощности/пульса --- */ .graph canvas {