Implement workout queue, performance management chart, and WASM suppo…#174
Conversation
…rt (#167) * feat: auto-pause workout on sensor dropout (#148) - Add sensor_dropout_enabled (bool, default true) and sensor_dropout_timeout_s (int, default 5s, range 2–30s) to Account with QSettings persistence - WorkoutDialog: 1-Hz watchdog timer checks last power timestamp; on dropout pauses workout and shows overlay message; auto-resumes after 3s countdown when signal is restored - Manual resume during dropout pause clears dropout state cleanly - DialogConfig Trainer tab: new 'Sensor Dropout' group with enable toggle and timeout spinner - WorkoutPlot: removeAlertMessage() method to dismiss alert overlay Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: proactive BLE sensor battery-level warnings (#156) - BtleHub: subscribe to Battery Service (0x180F / characteristic 0x2A19) on every connected device; emit signal_battery(sensorType, percentage) on connect (initial read) and on notifications; parseBatteryLevel() clamps value to 0-100 - btle_uuids.h: add BTLE_UUID_BATTERY_LEVEL = 0x2A19 - simulateNotification(): dispatch battery UUID to parseBatteryLevel() - Account: battery_warning_threshold (int, default 20, range 5-50) + saveBatteryWarningThreshold() - WorkoutDialog: batteryStatusReceived(sensorType, percentage) rewired to configurable-threshold check; suppresses repeated warnings within 5% of last-warned level; QHash<QString,int> m_warnedBatteryLevels tracks per-sensor session state - MainWindow: connect btleHub::signal_battery to WorkoutDialog slot - DialogConfig Trainer tab: Battery Warning group with threshold spinner - Unit tests: 5 new battery parsing tests covering threshold boundary, clamping, empty-packet rejection Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: per-interval transition summary overlay (#154) - DataWorkout: add getAvgIntervalPower/Hr/Cad + getNbPointsIntervalPower getters so WorkoutDialog can read stats before the reset in changeInterval() - IntervalSummaryUtil (src/model/intervalsummaryutil.h): new header-only helper with classifyPowerAdherence() — Met/NearMiss/Missed based on ±5% and ±10% thresholds; no Qt dependency for easy unit testing - model.pri: register intervalsummaryutil.h - Account: interval_summary_enabled (bool, default true) and interval_summary_duration_s (int, default 5, range 2–15) settings with QSettings load + saveIntervalSummarySettings() - WorkoutDialog::changeIntervalsDataWorkout(): capture interval stats before changeInterval() resets them; skip if studio mode, workoutOver, or interval < 10 s; call showIntervalSummaryOverlay() after - WorkoutDialog::showIntervalSummaryOverlay(): HTML overlay via the existing setDisplayIntervalMessage() FaderLabel; colour-coded power indicator (green/amber/red); shows avg W, % FTP, HR, cadence, duration - DialogConfig Trainer tab: 'Interval Summary Overlay' group with enabled checkbox and duration spinbox; wired to initTrainerTab/saveTrainerTab - Unit tests: 8 new tests for classifyPowerAdherence() covering exact target, ±5% boundaries, ±7% NearMiss, ±15% Missed, zero-target Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(#151): Add dark mode / system-adaptive theme support - Add AppTheme header-only class with Light/Dark/System modes - lightStylesheet() falls back to existing z_stylesheet-based QSS - darkStylesheet() provides comprehensive dark palette - resolveMode() uses QStyleHints::colorScheme() on Qt 6.5+ with palette-brightness fallback for older versions - apply() re-uses the original z_stylesheet content for light mode via the 'lightStylesheet' QApplication property - Add app_theme (int, default 2=System) to Account with saveAppTheme() and QSettings persistence under the 'account' group - Apply theme after login in main.cpp based on account preference - Add Theme combo box (Light/Dark/System) to DialogConfig Language tab - initLanguageTab() loads app_theme from Account - saveLanguageTab() persists and live-applies the selected theme - Add MainWindow::slotSystemThemeChanged() connected to QStyleHints::colorSchemeChanged (Qt 6.5+) for real-time OS tracking when System mode is active Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(#155): Add WASM PWA support with manifest and service worker - Add docs/app/manifest.json with PWA metadata: - name, short_name, description, start_url, scope - display: standalone, theme_color: #e05a00, background_color: #0d0d0d - Icons reference existing 512x512 main_icon.png - Add docs/app/service-worker.js: - Pre-caches shell assets (HTML, JS, manifest, icons) at install time - Cache-first strategy for same-origin GET requests - Stale-while-revalidate for HTML/JS files - Runtime caches WASM, JS, HTML, images on first fetch - Graceful fallback: missing precache entries logged but don't break install - Old caches deleted on activate - Update docs/app/index.html: - Link manifest.json via <link rel='manifest'> - Add theme-color, mobile-web-app-capable, Apple PWA meta tags - Register service-worker.js on window load - Add Playwright PWA tests: - manifest.json returns 200 - manifest has required fields (name, short_name, start_url, display, icons) - service-worker.js returns 200 - index.html links to manifest - index.html has theme-color meta tag Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(#148): Remove duplicate onBleConnectionError slot declaration in workoutdialog.h The checkSensorDropout() slot was inserted immediately before a second copy of onBleConnectionError(), causing 'class member cannot be redeclared' errors on all platforms. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(#151): Add missing QColor/QPalette includes in apptheme.h for MSVC MSVC does not implicitly pull in QColor/QPalette through other Qt headers. Adding explicit includes fixes the Windows build failure. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: resolve merge conflict markers left in account.cpp Conflict markers from feat/issue-154-interval-overlay merge were accidentally left in account.cpp, causing build failures on all platforms (CI #514). Resolves by keeping all three save methods: - saveSensorDropoutSettings() - saveBatteryWarningThreshold() - saveIntervalSummarySettings() Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: remove duplicate saveBatteryWarningThreshold declaration in account.h Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add missing signal_battery to BtleHubWasm with sensor type inference (#161) * Add signal_battery to BtleHubWasm to fix WASM build BtleHubWasm was missing the signal_battery(QString, int) signal that BtleHub declares, causing a compile error in mainwindow.cpp on the WASM target. Added the signal declaration, parseBatteryLevel() implementation, and wired Battery Level (0x2A19) in onBleNotification(). Agent-Logs-Url: https://github.com/MaximumTrainer/MaximumTrainer_Redux/sessions/c470d196-48d3-4e70-b8a9-0fb6f63adc4e Co-authored-by: MaximumTrainer <1376575+MaximumTrainer@users.noreply.github.com> * Implement determineSensorType() in BtleHubWasm for battery signal Track which measurement UUIDs have been received (HR, Power, FTMS, CSC, Moxy) and use that to infer a human-readable sensor type string for signal_battery, mirroring native BtleHub::determineSensorType(). Agent-Logs-Url: https://github.com/MaximumTrainer/MaximumTrainer_Redux/sessions/728cac51-9482-4475-883f-ae19a2e2fb7e Co-authored-by: MaximumTrainer <1376575+MaximumTrainer@users.noreply.github.com> * Fix Playwright wasm_btle_api.spec.js timeouts Increase waitForCanvas timeouts (30s→45s for canvas, 15s→45s for BLE setup) and set the test suite timeout to 120s to accommodate slow WASM loads on CI. Add diagnostic logging when BLE scan is not initiated. Agent-Logs-Url: https://github.com/MaximumTrainer/MaximumTrainer_Redux/sessions/3d6d36ce-46b7-442c-87c4-9daf91c62486 Co-authored-by: MaximumTrainer <1376575+MaximumTrainer@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: MaximumTrainer <1376575+MaximumTrainer@users.noreply.github.com> * Feat/issue 152 workout queue (#162) * feat: workout queue / back-to-back playlist (#152) - Add WorkoutQueue model: persistent QStringList of file paths + names, saved to QSettings under workoutQueue/ group; survives app restart - Add QueuePanelWidget: dock-able sidebar with QListWidget, Move Up/Down, Remove, Clear; connected to WorkoutQueue::queueChanged signal - Add WorkoutCountdownDialog: 60 s auto-start countdown before advancing to the next queued workout; Start Now / Cancel Queue buttons - Wire 'Add to Queue' context menu item in Main_WorkoutPage; emits addWorkoutToQueue(Workout) signal - MainWindow: creates dock widget (right side, hidden by default); shows dock on first item added; displays status message with queue count - After each workout (sim + BTLE paths), check queue and show countdown; on accept, parse next workout XML and call executeWorkout() recursively - Add on_actionToggleQueue_triggered() slot for future menu wiring Closes #152 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: fix saveBatteryWarningThreshold declaration placement in account.h - Move declaration to canonical public methods section (line 25) - Remove duplicate misplaced declaration adjacent to battery_warning_threshold field Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: resolve merge conflict markers in account.cpp - keep all three save methods Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: correct string literal syntax and add signal_battery to BtleHubWasm - mainwindow.cpp: fix empty string literal '' around %1 arg (was breaking C++ parse) - btle_hub_wasm.h: add signal_battery(QString, int) to match BtleHub signal contract Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: move addWorkoutToQueue from signals to private slots The function was in the signals: block but had a manual implementation in mainwindow.cpp, causing a duplicate symbol linker error on all platforms. Slots have user implementations; signals do not. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Dan Woollard <danwo@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(#146): Performance Management Chart (CTL/ATL/TSB) * feat: add Performance Management Chart (CTL/ATL/TSB) (#146) - PmcCalculator: computes CTL (42d EMA), ATL (7d EMA) and TSB per day from WorkoutHistorySummary history - PmcDialog: QwtPlot with CTL (blue), ATL (red) and TSB (green dashed) curves; Julian-day x-axis with DateScaleDraw; today's values readout - HistoryWidget: 'Performance Chart' button opens PmcDialog - WorkoutHistoryModel: adds const history() getter Closes #146 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: add INCLUDEPATH for src/fitness and signal_battery to BtleHubWasm - fitness.pri: INCLUDEPATH += \$\C:\Users\danwo\CodeProjects\MaximumTrainer_Redux so pmccalculator.h resolves from src/ui/ - btle_hub_wasm.h: add signal_battery(QString, int) signal to match BtleHub Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: remove qwt_plot_legend_item.h (unavailable) and use label legend qwt_plot_legend_item.h is not available in the QWT versions used across platforms. Replace QwtLegend + qwt_legend.h with a simple HTML-label colour legend below the plot. This is portable across all QWT builds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Dan Woollard <danwo@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(#145): Mean Maximal Power (MMP) / Critical Power Curve Analysis * feat: add MMP/Critical Power Curve analysis (#145) - MmpCalculator: reads per-second power from FIT Record messages, fills timestamp gaps with 0 for accurate window calculations, computes rolling-window MMP for 14 standard durations (1s to 2h) - CriticalPowerModel: linearized CP + W' regression using durations in 2-30 min range (requires >=3 data points for stability) - CriticalPowerDialog: QwtPlot with log-scale X axis, human-readable duration labels, MMP curve (blue) + CP model curve (red dashed), CP and W' readouts in stats panel - HistoryWidget: 'Critical Power Curve' button opens dialog with loaded activity history - WorkoutHistoryModel: added history() const getter Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: add INCLUDEPATH to fitness.pri and signal_battery to BtleHubWasm fitness.pri lacked INCLUDEPATH += \$\C:\Users\danwo\CodeProjects\MaximumTrainer_Redux so mmpcalculator.h was not found by UI files that include it by bare name. Also adds the missing signal_battery signal to BtleHubWasm to match BtleHub's interface. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Dan Woollard <danwo@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(#40): Implement missing WASM functionality (#166) * feat: add Performance Management Chart (CTL/ATL/TSB) (#146) - PmcCalculator: computes CTL (42d EMA), ATL (7d EMA) and TSB per day from WorkoutHistorySummary history - PmcDialog: QwtPlot with CTL (blue), ATL (red) and TSB (green dashed) curves; Julian-day x-axis with DateScaleDraw; today's values readout - HistoryWidget: 'Performance Chart' button opens PmcDialog - WorkoutHistoryModel: adds const history() getter Closes #146 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: WASM localStorage credential store + fix signal_battery + INCLUDEPATH - credential_store_wasm.cpp: rewrite with Emscripten localStorage for token persistence - btle_hub_wasm.h: add missing signal_battery(QString, int) to match BtleHub contract - fitness.pri: add INCLUDEPATH += \$\C:\Users\danwo\CodeProjects\MaximumTrainer_Redux so pmccalculator.h is resolvable Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: remove qwt_plot_legend_item.h (unavailable) and use label legend qwt_plot_legend_item.h and qwt_legend.h are not available in all QWT builds (specifically the custom WASM QWT). Replace QwtLegend with an HTML QLabel colour legend below the plot for cross-platform compatibility. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Dan Woollard <danwo@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Dan Woollard <danwo@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
@copilot resolve the merge conflicts in this pull request |
…refactor Co-authored-by: MaximumTrainer <1376575+MaximumTrainer@users.noreply.github.com>
Resolved all merge conflicts with
|
There was a problem hiding this comment.
Copilot wasn't able to review any files in this pull request.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
WorkoutQueue::dequeueName() documentation says it returns the first workout name without removing it, but the implementation removes both the name and the file path (takeFirst). Either update the docstring to match the destructive behavior, or change the method to a non-destructive peek (and add a separate dequeue method if needed).
| /// Consume (remove and return) the name of the first workout in the queue. | |
| /// Also removes the corresponding first file path to keep the queue entries aligned. |
There was a problem hiding this comment.
QListWidget is set to InternalMove drag/drop, but there is no code to propagate user drag reordering back into WorkoutQueue. This can leave the UI showing a different order than the persisted queue, and subsequent Remove/Move actions will operate on the wrong underlying item. Either disable drag/drop here, or handle the model update on drop (e.g., by listening for rowsMoved / dropEvent and calling queue reorder APIs).
There was a problem hiding this comment.
WorkoutCountdownDialog provides a "Cancel Queue" action, but when the dialog is rejected the queue is not cleared (and dialogResult() is ignored). This means users can click "Cancel Queue" yet the queue will still auto-advance after the next workout completes. Handle the rejected path by clearing/persisting the queue (and consider using dialogResult() to distinguish StartNow vs Cancelled explicitly).
There was a problem hiding this comment.
testBattery_aboveThreshold_noSignal() currently expects one signal emission and the inline comment says BtleHub emits for any reading. The test name implies the opposite (no signal). Rename the test (or adjust expectation) so failures are meaningful and the suite is self-documenting.
There was a problem hiding this comment.
Now that m_batteryService is tracked as a member, make sure it’s cleaned up/reset consistently with the other service pointers when reconnecting (e.g., in connectToDevice()’s "Clean up any previously-created service objects" block). Otherwise repeated connect cycles can leak service objects and leave stale pointers around.
There was a problem hiding this comment.
The test name/comment don’t match the actual data being asserted: this is labeled "Nominal battery level (85%)" but the payload is 15%. Either update the comment/test name or change the injected value so the test intent is unambiguous.
There was a problem hiding this comment.
This plot hard-codes a white canvas background, which will clash with the new Dark/System themes and makes the chart hard to read in dark mode. Prefer using the application palette (or a theme-derived color) for the canvas/background and grid colors so it adapts with AppTheme::apply().
There was a problem hiding this comment.
The Close button’s rejected() signal is wired to QDialog::accept(). This is inconsistent with other dialogs (e.g., CriticalPowerDialog uses reject()) and can produce an unexpected Accepted result for callers that check exec()’s return value. Wire Close to reject() (or close()) instead.
There was a problem hiding this comment.
SW_VERSION/CACHE_NAME are constant ("v1"), so a new deployment can keep serving stale cached assets—especially the .wasm, which is cached on first fetch and never revalidated unless the cache name changes. Consider injecting a build/version hash into SW_VERSION (or using a hashed cache name) so updates reliably bust old caches.
There was a problem hiding this comment.
The manifest only declares a 512×512 icon. Many PWA install flows expect a 192×192 icon as well (and some platforms use it for home-screen tiles). Consider adding a 192×192 icon entry (or generating one from the existing asset) to improve installability and align with typical PWA requirements.
|
@copilot apply changes based on the comments in this thread |
…ancel queue, PMC close Agent-Logs-Url: https://github.com/MaximumTrainer/MaximumTrainer_Redux/sessions/f87d05b0-6a24-488f-b27a-5a3923d86885 Co-authored-by: MaximumTrainer <1376575+MaximumTrainer@users.noreply.github.com>
Applied all review feedback in commit
The items already resolved in the merge commit ( |
…rt (#167)
The checkSensorDropout() slot was inserted immediately before a second copy of onBleConnectionError(), causing 'class member cannot be redeclared' errors on all platforms.
MSVC does not implicitly pull in QColor/QPalette through other Qt headers. Adding explicit includes fixes the Windows build failure.
Conflict markers from feat/issue-154-interval-overlay merge were accidentally left in account.cpp, causing build failures on all platforms (CI #514). Resolves by keeping all three save methods:
fix: remove duplicate saveBatteryWarningThreshold declaration in account.h
Add missing signal_battery to BtleHubWasm with sensor type inference (Add missing signal_battery to BtleHubWasm with sensor type inference #161)
Add signal_battery to BtleHubWasm to fix WASM build
BtleHubWasm was missing the signal_battery(QString, int) signal that BtleHub declares, causing a compile error in mainwindow.cpp on the WASM target. Added the signal declaration, parseBatteryLevel() implementation, and wired Battery Level (0x2A19) in onBleNotification().
Agent-Logs-Url: https://github.com/MaximumTrainer/MaximumTrainer_Redux/sessions/c470d196-48d3-4e70-b8a9-0fb6f63adc4e
Track which measurement UUIDs have been received (HR, Power, FTMS, CSC, Moxy) and use that to infer a human-readable sensor type string for signal_battery, mirroring native BtleHub::determineSensorType().
Agent-Logs-Url: https://github.com/MaximumTrainer/MaximumTrainer_Redux/sessions/728cac51-9482-4475-883f-ae19a2e2fb7e
Increase waitForCanvas timeouts (30s→45s for canvas, 15s→45s for BLE setup) and set the test suite timeout to 120s to accommodate slow WASM loads on CI. Add diagnostic logging when BLE scan is not initiated.
Agent-Logs-Url: https://github.com/MaximumTrainer/MaximumTrainer_Redux/sessions/3d6d36ce-46b7-442c-87c4-9daf91c62486
Feat/issue 152 workout queue (Feat/issue 152 workout queue #162)
feat: workout queue / back-to-back playlist (feat: Workout queue / back-to-back playlist #152)
Closes #152
fix: resolve merge conflict markers in account.cpp - keep all three save methods
fix: correct string literal syntax and add signal_battery to BtleHubWasm
The function was in the signals: block but had a manual implementation in mainwindow.cpp, causing a duplicate symbol linker error on all platforms. Slots have user implementations; signals do not.
feat(feat: Performance Management Chart (CTL / ATL / TSB) #146): Performance Management Chart (CTL/ATL/TSB)
feat: add Performance Management Chart (CTL/ATL/TSB) (feat: Performance Management Chart (CTL / ATL / TSB) #146)
Closes #146
qwt_plot_legend_item.h is not available in the QWT versions used across platforms. Replace QwtLegend + qwt_legend.h with a simple HTML-label colour legend below the plot. This is portable across all QWT builds.
feat(feat: Mean Maximal Power (MMP) / Critical Power Curve Analysis #145): Mean Maximal Power (MMP) / Critical Power Curve Analysis
feat: add MMP/Critical Power Curve analysis (feat: Mean Maximal Power (MMP) / Critical Power Curve Analysis #145)
fitness.pri lacked INCLUDEPATH += $\C:\Users\danwo\CodeProjects\MaximumTrainer_Redux so mmpcalculator.h was not found by UI files that include it by bare name. Also adds the missing signal_battery signal to BtleHubWasm to match BtleHub's interface.
feat(implement missing wasm functionality #40): Implement missing WASM functionality (feat(#40): Implement missing WASM functionality #166)
feat: add Performance Management Chart (CTL/ATL/TSB) (feat: Performance Management Chart (CTL / ATL / TSB) #146)
Closes #146
qwt_plot_legend_item.h and qwt_legend.h are not available in all QWT builds (specifically the custom WASM QWT). Replace QwtLegend with an HTML QLabel colour legend below the plot for cross-platform compatibility.