Skip to content

Copilot/add daily volume average weight selectors#143

Merged
gfauredev merged 12 commits into
mainfrom
copilot/add-daily-volume-average-weight-selectors
May 7, 2026
Merged

Copilot/add daily volume average weight selectors#143
gfauredev merged 12 commits into
mainfrom
copilot/add-daily-volume-average-weight-selectors

Conversation

@gfauredev
Copy link
Copy Markdown
Owner

@gfauredev gfauredev commented May 4, 2026

This Pull Request…

Engineering Principles

  • PR only contains changes strictly related to the requested feature or fix,
    scope is focused (no unrelated dependency updates or formatting)
  • This code totally respects README’s Engineering Principles

CI/CD Readiness

  • Branch follows Conventional Branch: feat/…, fix/…, refactor/…, …
  • Code is formatted with dx fmt; cargo fmt
  • All checks pass, nix flake checks succeeds without warnings
    • Code compiles, dx build with necessary platform flags succeeds
    • cargo clippy -- -D warnings -W clippy::all -W clippy::pedantic
      produces zero warnings
    • All unit tests pass without warnings
      cargo llvm-cov nextest --ignore-filename-regex '(src/components/|\.cargo/registry/|nix/store)'
    • End-to-end tests pass maestro test --headless maestro/web
      maestro test --headless maestro/android

Summary by CodeRabbit

  • New Features

    • Analytics Mode selector (Set, Session Avg, Session Total) and a dedicated Volume metric added to charts; charts expand to show the fifth metric when present.
    • Session cards gain an expandable detail view showing per-session exercise logs.
  • Enhancements

    • All-Time High values in exercise forms are clickable to auto-fill inputs.
  • Style

    • ATH values display a dotted underline and pointer cursor.
    • New styling for analytics mode selector layout.
  • Localization

    • Added translation keys for analytics modes and volume label.

Copilot AI and others added 4 commits April 27, 2026 10:55
…vg weight metrics

Agent-Logs-Url: https://github.com/gfauredev/LogOut/sessions/7cdbcc43-a72a-4b79-8557-de1d62900618

Co-authored-by: gfauredev <19304085+gfauredev@users.noreply.github.com>
…geDailyWeight→AverageSessionWeight

Agent-Logs-Url: https://github.com/gfauredev/LogOut/sessions/00d6bd52-a26e-4687-b142-85e220950f97

Co-authored-by: gfauredev <19304085+gfauredev@users.noreply.github.com>
…c match arms

Agent-Logs-Url: https://github.com/gfauredev/LogOut/sessions/00d6bd52-a26e-4687-b142-85e220950f97

Co-authored-by: gfauredev <19304085+gfauredev@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a Volume metric and AnalyticsMode, expands analytics availability from 4 → 5 metric slots, updates chart layout to optionally render a third chart region, implements mode-dependent aggregation (Set / SessionAverage / SessionTotal) including a computed Volume series, updates selector types, makes ATH UI cells clickable, and adds related translations and styles.

Changes

Analytics core, charting, selector, and models

Layer / File(s) Summary
Data Model
src/models/analytics.rs
Adds AnalyticsMode (Set, SessionAverage, SessionTotal). Adds Metric::Volume, maps it to index 4, makes extract_value return None for Volume, and maps Volume unit to "kg·reps".
Availability / Aggregation
src/components/analytics/mod.rs
Expands availability buckets to 5, introduces VOLUME_SLOT, computes available_by_metric for five metrics, implements AnalyticsMode-dependent point generation, and computes a dedicated Volume series (per-set, per-session average, per-session total) using weight×reps. Adds UI wiring for Volume exercise selection and colouring.
Chart Layout & Rendering
src/components/analytics/chart.rs
Extends ALL_METRICS to include Volume, grows axis data to 5 entries, conditionally enables a third chart region (has_chart3) and total height adjustments, updates y-coordinate routing to map metric indices into chart 1/2/3, and extends axis/tick rendering and cursor guideline drawing to cover the fifth metric/chart3.
Selector Prop Type
src/components/analytics/selector.rs
Prop available_by_metric changed from Memo<[Vec<(String, String)>; 4]>Memo<[Vec<(String, String)>; 5]>.
Exports / API
src/models/analytics.rs
Public enum AnalyticsMode added; Metric enum extended with Volume. No breaking API removals.

UX, forms, styles, translations

Layer / File(s) Summary
Session Form ATH wiring
src/components/session_exercise_form.rs
ATH cells for duration/weight/distance/reps are now rendered with class="ath" and onclick handlers that populate the corresponding inputs (using HG_PER_KG and M_PER_KM conversions where applicable). Imports updated for conversion constants.
Styles
assets/_component.scss, assets/_unique.scss
Added .exercise-edit .ath { cursor: pointer; text-decoration: underline dotted; } and a new fieldset.analytics-mode block for mode selector layout and styling.
Translations
assets/en.ftl, assets/es.ftl, assets/fr.ftl
Added keys for analytics mode UI (analytics-mode-label, analytics-mode-set, analytics-mode-session-avg, analytics-mode-session-total) and analytics-volume-exercise-label.
sequenceDiagram
    actor User
    participant SessionForm as Session Exercise Form
    participant MetricSelector as Metric Selector
    participant Analytics as Analytics Component
    participant Chart as Chart Renderer

    User->>SessionForm: Click ATH value (weight/distance/reps/duration)
    SessionForm->>SessionForm: Convert units (HG_PER_KG / M_PER_KM)
    SessionForm->>SessionForm: Populate input field

    User->>MetricSelector: Select metric / mode (e.g., Volume, SessionAverage)
    MetricSelector->>Analytics: Signal metric/mode change

    Analytics->>Analytics: Evaluate available_by_metric (5 slots)
    Analytics->>Analytics: Aggregate points per AnalyticsMode (Set / SessionAverage / SessionTotal)
    Analytics->>Analytics: Compute Volume = weight_kg * reps where applicable
    Analytics->>Chart: Send chart_data with 5-metric mapping

    Chart->>Chart: Route metric indices to chart 1/2/3, compute axis scales
    Chart->>Chart: Render series, axes, ticks, and cursor guides across charts
    Chart->>User: Display updated analytics with Volume and selected mode
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • gfauredev/LogOut#141: Modifies adapt_metric_unit in src/models/analytics.rs and touches metric/unit mapping logic related to this PR.
  • gfauredev/LogOut#142: Also edits src/components/session_exercise_form.rs and input handling, related to ATH/input interactions.

Poem

🐰 I clicked an ATH and hopped with glee,
Volume counted weight times reps for me,
Charts sprouted a third row in the sun,
Modes to average, sum, or per-set run,
Translations and styles — a tidy hop, done.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description consists only of an unchecked template with no actual implementation details, context, or explanations provided by the author about the changes made. Provide a detailed description explaining what feature was added (volume metric support), why it was needed, and summarise the key changes made across analytics components and related files.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title refers to adding volume average weight selectors in analytics, which directly aligns with the substantial changes to analytics components (chart.rs, mod.rs, selector.rs, models/analytics.rs) and related translation keys across multiple language files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch copilot/add-daily-volume-average-weight-selectors

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 4, 2026

📊 Coverage Report

Lines: 3757/5005 (75.06493506493507%)

⏱️ Tests: 255 tests in 0.614s

FilenameFunction CoverageLine CoverageRegion CoverageBranch Coverage
main.rs
  22.00% (11/50)
  58.94% (155/263)
  60.56% (218/360)
- (0/0)
models/analytics.rs
   0.00% (0/6)
   0.00% (0/38)
   0.00% (0/50)
- (0/0)
models/enums.rs
 100.00% (28/28)
 100.00% (147/147)
 100.00% (337/337)
- (0/0)
models/exercise.rs
  92.00% (46/50)
  92.10% (548/595)
  91.01% (749/823)
- (0/0)
models/log.rs
 100.00% (12/12)
 100.00% (118/118)
 100.00% (144/144)
- (0/0)
models/mod.rs
 100.00% (11/11)
 100.00% (67/67)
 100.00% (97/97)
- (0/0)
models/session.rs
  72.22% (13/18)
  84.36% (151/179)
  83.33% (210/252)
- (0/0)
models/units.rs
 100.00% (28/28)
 100.00% (167/167)
  98.88% (353/357)
- (0/0)
services/app_state.rs
   1.89% (1/53)
   2.52% (11/436)
   2.31% (15/650)
- (0/0)
services/exercise_db.rs
  88.81% (127/143)
  90.30% (1210/1340)
  89.61% (1924/2147)
- (0/0)
services/exercise_loader.rs
   0.00% (0/14)
   0.00% (0/100)
   0.00% (0/135)
- (0/0)
services/native_queue.rs
   0.00% (0/15)
   0.00% (0/145)
   0.00% (0/197)
- (0/0)
services/notifications.rs
   0.00% (0/3)
   0.00% (0/9)
   0.00% (0/10)
- (0/0)
services/service_worker.rs
 100.00% (2/2)
 100.00% (6/6)
 100.00% (6/6)
- (0/0)
services/storage.rs
  63.77% (88/138)
  80.97% (766/946)
  83.53% (1187/1421)
- (0/0)
services/wake_lock.rs
 100.00% (2/2)
 100.00% (5/5)
 100.00% (5/5)
- (0/0)
utils.rs
  91.36% (74/81)
  91.44% (406/444)
  91.80% (649/707)
- (0/0)
Totals
  67.74% (443/654)
  75.06% (3757/5005)
  76.57% (5894/7698)
- (0/0)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/analytics/chart.rs`:
- Around line 247-258: The loop over axis_data (for i in 0..7_usize) computes
axis side with is_right = i % 2 == 1 which causes chart 3 axes (indices 4 and 6)
to collide on the left; update the logic in that loop to detect the chart-3
collision case and handle it: if i == 6 and axis_data[4].is_some() then either
force is_right = true for index 6 (render SessionReps on the right) or compute a
slight inward offset for x_pos when both left-side axes for chart3 exist; modify
the x_pos calculation (and any downstream label/tick placement that uses x_pos)
so ticks/labels for index 6 are drawn without overlapping axis 4.

In `@src/components/session_exercise_form.rs`:
- Around line 269-276: The three span.ath elements (weight, distance, reps) and
the time.ath element need keyboard accessibility: update each span bearing class
"ath" (e.g., the span that calls weight_input.set(...), the ones that set
distance_input and reps_input, and the time.ath span) to include tabindex: 0,
role: "button", and add an onkeydown handler that triggers the same fill action
when Enter or Space is pressed (mirroring the existing onkeydown pattern already
used elsewhere in this file). Ensure the onkeydown logic invokes the same setter
(weight_input.set(...), distance_input.set(...), reps_input.set(...),
time_input.set(...)) as the onclick so keyboard users can activate the ATH
shortcut.
- Around line 194-206: The time element for bests.duration is styled with class
"ath" and given an onclick that only acts when time_input is Some, causing a
misleading interactive affordance in view-only/perform modes; change the
rendering so the "ath" class and the onclick handler are only applied when
time_input.is_some(), i.e. detect time_input (the Option bound in this
component) and conditionally add class: "ath" and the onclick closure that calls
time_input.set(format_time(dur)) only when time_input is Some (otherwise render
a plain time element showing format_time(dur)), updating the code around
bests.duration / time and the usage of format_time and time_input accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cdffac86-c7de-46e7-bc82-b3e5263f7d28

📥 Commits

Reviewing files that changed from the base of the PR and between 8fa5bae and 21f248a.

📒 Files selected for processing (9)
  • assets/_component.scss
  • assets/en.ftl
  • assets/es.ftl
  • assets/fr.ftl
  • src/components/analytics/chart.rs
  • src/components/analytics/mod.rs
  • src/components/analytics/selector.rs
  • src/components/session_exercise_form.rs
  • src/models/analytics.rs

Comment thread src/components/analytics/chart.rs Outdated
Comment on lines 194 to 206
if let Some(dur) = bests.duration {
time { "{format_time(dur)}" }
time {
class: "ath",
onclick: move |_| {
if let Some(mut ti) = time_input {
ti.set(format_time(dur));
}
},
"{format_time(dur)}"
}
} else {
time { "–" }
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Duration ATH styled as interactive but silently no-ops when time_input is None.

The time element unconditionally receives class: "ath" (pointer cursor + dotted underline from the stylesheet), but its onclick body is guarded by if let Some(mut ti) = time_input { … }. In perform mode and view-only mode, time_input is None, so clicking the visually interactive element does nothing — a misleading affordance.

🐛 Proposed fix — conditionally apply the `ath` class
-                        time {
-                            class: "ath",
-                            onclick: move |_| {
-                                if let Some(mut ti) = time_input {
-                                    ti.set(format_time(dur));
-                                }
-                            },
-                            "{format_time(dur)}"
-                        }
+                        time {
+                            class: if time_input.is_some() { "ath" } else { "" },
+                            onclick: move |_| {
+                                if let Some(mut ti) = time_input {
+                                    ti.set(format_time(dur));
+                                }
+                            },
+                            "{format_time(dur)}"
+                        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/session_exercise_form.rs` around lines 194 - 206, The time
element for bests.duration is styled with class "ath" and given an onclick that
only acts when time_input is Some, causing a misleading interactive affordance
in view-only/perform modes; change the rendering so the "ath" class and the
onclick handler are only applied when time_input.is_some(), i.e. detect
time_input (the Option bound in this component) and conditionally add class:
"ath" and the onclick closure that calls time_input.set(format_time(dur)) only
when time_input is Some (otherwise render a plain time element showing
format_time(dur)), updating the code around bests.duration / time and the usage
of format_time and time_input accordingly.

Comment on lines +269 to +276
span {
class: "ath",
onclick: move |_| {
weight_input
.set(format!("{:.1}", f64::from(best.0) / HG_PER_KG));
},
"{best}"
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

ATH click-to-fill elements are unreachable via keyboard.

All three span.ath elements (weight, distance, reps) — and the time.ath element at Line 195 — lack tabindex, role="button", and an onkeydown handler. Since onclick does not fire on Enter/Space for non-interactive HTML elements without tabindex, keyboard-only users cannot trigger the fill-from-ATH shortcut.

Focusing is necessary to catch keyboard events on non-interactive elements, so tabindex: 0 is the minimum required. The pattern for onkeydown already exists in the file (e.g. Lines 243–247).

♻️ Illustrative fix for the weight ATH span (apply the same pattern to distance, reps, and time.ath)
                    if let Some(best) = bests.weight_hg {
                        span {
                            class: "ath",
+                           tabindex: 0,
+                           role: "button",
                            onclick: move |_| {
                                weight_input
                                    .set(format!("{:.1}", f64::from(best.0) / HG_PER_KG));
                            },
+                           onkeydown: move |evt| {
+                               if evt.key() == Key::Enter {
+                                   weight_input
+                                       .set(format!("{:.1}", f64::from(best.0) / HG_PER_KG));
+                               }
+                           },
                            "{best}"
                        }

Also applies to: 342-349, 409-415

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/session_exercise_form.rs` around lines 269 - 276, The three
span.ath elements (weight, distance, reps) and the time.ath element need
keyboard accessibility: update each span bearing class "ath" (e.g., the span
that calls weight_input.set(...), the ones that set distance_input and
reps_input, and the time.ath span) to include tabindex: 0, role: "button", and
add an onkeydown handler that triggers the same fill action when Enter or Space
is pressed (mirroring the existing onkeydown pattern already used elsewhere in
this file). Ensure the onkeydown logic invokes the same setter
(weight_input.set(...), distance_input.set(...), reps_input.set(...),
time_input.set(...)) as the onclick so keyboard users can activate the ATH
shortcut.

…toggle (Set/SessionAverage/SessionTotal) and dedicated Volume chart

Agent-Logs-Url: https://github.com/gfauredev/LogOut/sessions/07d5a767-7b0e-4202-acdb-85424116769d

Co-authored-by: gfauredev <19304085+gfauredev@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/analytics/chart.rs`:
- Around line 82-86: chart3_top is always computed from chart2_bottom causing an
empty gap when has_chart3 is true but has_chart2 is false; change the logic to
position chart3 immediately after the last visible chart by computing chart3_top
from chart2_bottom when has_chart2 is true, otherwise from chart1_bottom (e.g.
chart3_top = if has_chart2 { chart2_bottom + x_gap } else { chart1_bottom +
x_gap }), then compute chart3_bottom = chart3_top + chart_height and set
total_height to chart3_bottom + chart2_bottom_margin when has_chart3.

In `@src/components/analytics/mod.rs`:
- Around line 65-90: The current mapping logic uses weight_hg.0 > 0 to decide
metric support, which misclassifies many exercises; replace those boolean checks
with the metric-driven predicate metric.extract_value(log).is_some() when
populating the maps array (the array initialized as maps and indexed per
Metric::to_index()), leaving Volume as the only special-case rule (keep Volume
populated when is_weighted && log.reps.is_some()); also update the corresponding
selectors/filters used by Set, SessionAverage, and SessionTotal to use the same
metric.extract_value(log).is_some() predicate so the selector and chart data
remain consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 02a027db-f23b-4618-b9e4-bd14c31fa5da

📥 Commits

Reviewing files that changed from the base of the PR and between 21f248a and dcd5c81.

📒 Files selected for processing (8)
  • assets/_unique.scss
  • assets/en.ftl
  • assets/es.ftl
  • assets/fr.ftl
  • src/components/analytics/chart.rs
  • src/components/analytics/mod.rs
  • src/components/analytics/selector.rs
  • src/models/analytics.rs

Comment thread src/components/analytics/chart.rs Outdated
Comment thread src/components/analytics/mod.rs Outdated
…e, simplify maps, add axis/mode docs

Agent-Logs-Url: https://github.com/gfauredev/LogOut/sessions/07d5a767-7b0e-4202-acdb-85424116769d

Co-authored-by: gfauredev <19304085+gfauredev@users.noreply.github.com>
Copilot AI and others added 2 commits May 6, 2026 14:58
…order in session detail

Agent-Logs-Url: https://github.com/gfauredev/LogOut/sessions/ce75a3ab-7b9c-4b14-bbf1-aa931d8aada5

Co-authored-by: gfauredev <19304085+gfauredev@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

📊 Coverage Report

Lines: 3774/5029 (75.04474050507059%)

⏱️ Tests: 255 tests in 0.560s

FilenameFunction CoverageLine CoverageRegion CoverageBranch Coverage
main.rs
  22.00% (11/50)
  58.94% (155/263)
  60.56% (218/360)
- (0/0)
models/analytics.rs
   0.00% (0/6)
   0.00% (0/36)
   0.00% (0/48)
- (0/0)
models/enums.rs
 100.00% (28/28)
 100.00% (147/147)
 100.00% (337/337)
- (0/0)
models/exercise.rs
  92.00% (46/50)
  92.10% (548/595)
  91.01% (749/823)
- (0/0)
models/log.rs
 100.00% (12/12)
 100.00% (118/118)
 100.00% (144/144)
- (0/0)
models/mod.rs
 100.00% (11/11)
 100.00% (67/67)
 100.00% (97/97)
- (0/0)
models/session.rs
  72.22% (13/18)
  84.78% (156/184)
  83.33% (210/252)
- (0/0)
models/units.rs
 100.00% (28/28)
 100.00% (167/167)
  98.88% (353/357)
- (0/0)
services/app_state.rs
   1.89% (1/53)
   2.47% (11/445)
   2.27% (15/662)
- (0/0)
services/exercise_db.rs
  88.81% (127/143)
  90.30% (1210/1340)
  89.61% (1924/2147)
- (0/0)
services/exercise_loader.rs
   0.00% (0/14)
   0.00% (0/100)
   0.00% (0/135)
- (0/0)
services/native_queue.rs
   0.00% (0/15)
   0.00% (0/145)
   0.00% (0/197)
- (0/0)
services/notifications.rs
   0.00% (0/3)
   0.00% (0/9)
   0.00% (0/10)
- (0/0)
services/service_worker.rs
 100.00% (2/2)
 100.00% (6/6)
 100.00% (6/6)
- (0/0)
services/storage.rs
  63.77% (88/138)
  81.21% (778/958)
  83.53% (1187/1421)
- (0/0)
services/wake_lock.rs
 100.00% (2/2)
 100.00% (5/5)
 100.00% (5/5)
- (0/0)
utils.rs
  91.36% (74/81)
  91.44% (406/444)
  91.80% (649/707)
- (0/0)
Totals
  67.74% (443/654)
  75.04% (3774/5029)
  76.47% (5894/7708)
- (0/0)

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

📊 Coverage Report

Lines: 3774/5029 (75.04474050507059%)

⏱️ Tests: 255 tests in 0.571s

FilenameFunction CoverageLine CoverageRegion CoverageBranch Coverage
main.rs
  22.00% (11/50)
  58.94% (155/263)
  60.56% (218/360)
- (0/0)
models/analytics.rs
   0.00% (0/6)
   0.00% (0/36)
   0.00% (0/48)
- (0/0)
models/enums.rs
 100.00% (28/28)
 100.00% (147/147)
 100.00% (337/337)
- (0/0)
models/exercise.rs
  92.00% (46/50)
  92.10% (548/595)
  91.01% (749/823)
- (0/0)
models/log.rs
 100.00% (12/12)
 100.00% (118/118)
 100.00% (144/144)
- (0/0)
models/mod.rs
 100.00% (11/11)
 100.00% (67/67)
 100.00% (97/97)
- (0/0)
models/session.rs
  72.22% (13/18)
  84.78% (156/184)
  83.33% (210/252)
- (0/0)
models/units.rs
 100.00% (28/28)
 100.00% (167/167)
  98.88% (353/357)
- (0/0)
services/app_state.rs
   1.89% (1/53)
   2.47% (11/445)
   2.27% (15/662)
- (0/0)
services/exercise_db.rs
  88.81% (127/143)
  90.30% (1210/1340)
  89.61% (1924/2147)
- (0/0)
services/exercise_loader.rs
   0.00% (0/14)
   0.00% (0/100)
   0.00% (0/135)
- (0/0)
services/native_queue.rs
   0.00% (0/15)
   0.00% (0/145)
   0.00% (0/197)
- (0/0)
services/notifications.rs
   0.00% (0/3)
   0.00% (0/9)
   0.00% (0/10)
- (0/0)
services/service_worker.rs
 100.00% (2/2)
 100.00% (6/6)
 100.00% (6/6)
- (0/0)
services/storage.rs
  63.77% (88/138)
  81.21% (778/958)
  83.53% (1187/1421)
- (0/0)
services/wake_lock.rs
 100.00% (2/2)
 100.00% (5/5)
 100.00% (5/5)
- (0/0)
utils.rs
  91.36% (74/81)
  91.44% (406/444)
  91.80% (649/707)
- (0/0)
Totals
  67.74% (443/654)
  75.04% (3774/5029)
  76.47% (5894/7708)
- (0/0)

Flake lock file updates:

• Updated input 'crane':
    'github:ipetkov/crane/7cf72d978629469c4bd4206b95c402514c1f6000?narHash=sha256-SPm9ck7jh3Un9nwPuMGbRU04UroFmOHjLP56T10MOeM%3D' (2026-04-10)
  → 'github:ipetkov/crane/d459c1350e96ce1a7e3859c513ef5e9869d67d6f?narHash=sha256-2uoQAqUk2H0ijQtGiWAyNeQYGYc6yfAcRRLlJAz4Gp8%3D' (2026-05-03)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/4c1018dae018162ec878d42fec712642d214fdfa?narHash=sha256-ar3rofg%2BawPB8QXDaFJhJ2jJhu%2BKqN/PRCXeyuXR76E%3D' (2026-04-09)
  → 'github:NixOS/nixpkgs/549bd84d6279f9852cae6225e372cc67fb91a4c1?narHash=sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9%2BhrDTkDU%3D' (2026-05-05)
• Updated input 'rust-overlay':
    'github:oxalica/rust-overlay/3c27f4c92a7d977556dd2c10bb564d9c61b375e9?narHash=sha256-/f/6/1WOfBJaGMfqV3VxWD9lpFRbPpF%2BCx4MO%2B0mGok%3D' (2026-04-13)
  → 'github:oxalica/rust-overlay/adf987c76af8d17b8256d23631bcf203f81e1a63?narHash=sha256-EZnAOkPgEeOO2rCRhwkTvesCq/E6dbsyxhMyaefgIWM%3D' (2026-05-06)
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

📊 Coverage Report

Lines: / (%)

⏱️ Tests: tests in s

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

📊 Coverage Report

Lines: 3774/5029 (75.04474050507059%)

⏱️ Tests: 255 tests in 0.584s

FilenameFunction CoverageLine CoverageRegion CoverageBranch Coverage
main.rs
  22.00% (11/50)
  58.94% (155/263)
  60.56% (218/360)
- (0/0)
models/analytics.rs
   0.00% (0/6)
   0.00% (0/36)
   0.00% (0/48)
- (0/0)
models/enums.rs
 100.00% (28/28)
 100.00% (147/147)
 100.00% (337/337)
- (0/0)
models/exercise.rs
  92.00% (46/50)
  92.10% (548/595)
  91.01% (749/823)
- (0/0)
models/log.rs
 100.00% (12/12)
 100.00% (118/118)
 100.00% (144/144)
- (0/0)
models/mod.rs
 100.00% (11/11)
 100.00% (67/67)
 100.00% (97/97)
- (0/0)
models/session.rs
  72.22% (13/18)
  84.78% (156/184)
  83.33% (210/252)
- (0/0)
models/units.rs
 100.00% (28/28)
 100.00% (167/167)
  98.88% (353/357)
- (0/0)
services/app_state.rs
   1.89% (1/53)
   2.47% (11/445)
   2.27% (15/662)
- (0/0)
services/exercise_db.rs
  88.81% (127/143)
  90.30% (1210/1340)
  89.61% (1924/2147)
- (0/0)
services/exercise_loader.rs
   0.00% (0/14)
   0.00% (0/100)
   0.00% (0/135)
- (0/0)
services/native_queue.rs
   0.00% (0/15)
   0.00% (0/145)
   0.00% (0/197)
- (0/0)
services/notifications.rs
   0.00% (0/3)
   0.00% (0/9)
   0.00% (0/10)
- (0/0)
services/service_worker.rs
 100.00% (2/2)
 100.00% (6/6)
 100.00% (6/6)
- (0/0)
services/storage.rs
  63.77% (88/138)
  81.21% (778/958)
  83.53% (1187/1421)
- (0/0)
services/wake_lock.rs
 100.00% (2/2)
 100.00% (5/5)
 100.00% (5/5)
- (0/0)
utils.rs
  91.36% (74/81)
  91.44% (406/444)
  91.80% (649/707)
- (0/0)
Totals
  67.74% (443/654)
  75.04% (3774/5029)
  76.47% (5894/7708)
- (0/0)

Agent-Logs-Url: https://github.com/gfauredev/LogOut/sessions/e35f3cc3-5bcf-47df-8b23-160682423ef9

Co-authored-by: gfauredev <19304085+gfauredev@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/components/analytics/chart.rs (1)

70-94: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Empty chart-1 region when only Volume is selected.

If no series in slots 0..7 has data but the dedicated Volume series does (metric_has_data = [F,F,F,F,T]), has_chart2 = false, has_chart3 = true, but chart 1 has no has_chart1 gate: the unconditional baseline (lines 220-227) and cursor guide (lines 418-428) still render, and chart3_top = chart1_bottom + x_gap places the Volume chart below an empty ~342px chart-1 area. Mirrors the chart-2-absent case the previous review fixed.

Suggest gating chart 1 the same way and collapsing higher charts upward when prior charts are empty:

💡 Proposed direction
+    let has_chart1 = metric_has_data[0] || metric_has_data[1];
     let has_chart2 = metric_has_data[2] || metric_has_data[3];
     let has_chart3 = metric_has_data[4];
@@
-    let chart1_top = top_pad;
-    let chart1_bottom = top_pad + chart_height;
-    let chart2_top = chart1_bottom + x_gap;
-    let chart2_bottom = chart2_top + chart_height;
-    let chart3_top = if has_chart2 {
-        chart2_bottom + x_gap
-    } else {
-        chart1_bottom + x_gap
-    };
-    let chart3_bottom = chart3_top + chart_height;
+    let chart1_top = top_pad;
+    let chart1_bottom = if has_chart1 { chart1_top + chart_height } else { chart1_top };
+    let chart2_top = if has_chart1 { chart1_bottom + x_gap } else { chart1_top };
+    let chart2_bottom = if has_chart2 { chart2_top + chart_height } else { chart2_top };
+    let chart3_top = if has_chart2 { chart2_bottom + x_gap } else { chart2_top };
+    let chart3_bottom = chart3_top + chart_height;

Then guard the chart-1 baseline (lines 220-227) and the chart-1 cursor guide (lines 418-428) with if has_chart1 { … }, and update interact_height / xlabel_y to use the topmost rendered chart.

Also applies to: 220-247, 418-454

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/analytics/chart.rs` around lines 70 - 94, Define a has_chart1
boolean (e.g., based on metric_has_data for slots used by chart1) and use it to
gate rendering and layout for the first chart: compute has_chart1 before
computing chart1_top/chart1_bottom and update
chart3_top/chart3_bottom/total_height logic to collapse subsequent charts upward
when has_chart1 or has_chart2 is false (replace the unconditional chart1 spacing
with conditional branches using has_chart1/has_chart2/has_chart3); wrap the
chart-1 baseline and chart-1 cursor guide rendering blocks (previously
unconditional around the baseline and cursor guide) with if has_chart1 { ... }
guards; and finally adjust interact_height and xlabel_y calculations to pick the
topmost rendered chart (use chart1_top if has_chart1, else chart2_top if
has_chart2, else chart3_top) so interaction region/x-labels align with the first
visible chart.
src/components/analytics/mod.rs (1)

100-179: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove or mark unreachable the Metric::Volume branches in log_matches and SessionTotal arms.

MetricSelector excludes Volume from the 8 regular metric slots — the dropdown offers only Weight, Reps, Distance, and Duration. Volume is handled exclusively through the dedicated volume_exercise slot. This means the Metric::Volume case in log_matches (line 121) and the Metric::Volume => 0.0 arm in the SessionTotal match (line 172) are unreachable dead code. Remove these branches or mark them with unreachable!() to clarify intent and improve code maintainability.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/analytics/mod.rs` around lines 100 - 179, The Metric::Volume
arms are dead code; update the log_matches closure and the
AnalyticsMode::SessionTotal match to either remove the Metric::Volume branches
or replace them with unreachable!() to indicate they cannot occur. Specifically,
edit the log_matches closure defined inside the chart_data mapping (the closure
named log_matches) to eliminate the Metric::Volume => false branch (or change it
to unreachable!()), and in the AnalyticsMode::SessionTotal match inside the same
mapping, replace the Metric::Volume => 0.0 arm with unreachable!() (or remove it
and ensure match remains exhaustive via a wildcard/unreachable), so intent is
explicit and dead branches are removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/home.rs`:
- Around line 383-405: Clicks inside the detail panel are bubbling to the outer
toggle and collapsing the view; wrap the detailed block emitted when
*show_detail.read() is true (the inner article/ul/li produced from
resolved_logs.iter().rev().enumerate()) in a container element that calls
evt.stop_propagation() on onclick (e.g., an inner div/span with onclick: |evt|
evt.stop_propagation()) so inner taps don't reach the outer article toggle, or
alternatively move the toggle logic off the article body into a dedicated
control/button; also replace the fragile key: "{idx}" with a stable identity
from the log (e.g., log.start_time or a composite like (log.exercise_id,
log.start_time)) so diffs track the correct items.
- Line 322: The article toggle is not accessible and the detail-related
computation runs every render and clicks bubble out; update the article element
(where onclick: move |_| show_detail.toggle()) to include role: "button",
tabindex: 0, aria_expanded: "{*show_detail.read()}", and an onkeydown handler
that checks Key::Enter and Key::Character(" ") to prevent_default and toggle
show_detail so keyboard and screen readers see and can activate it; move the
resolved_logs computation (the block computing resolved_logs used only by the
detail view) behind a guard that checks show_detail.read() so it only computes
when detail is visible; and in the detail container click handler(s) add
stop_propagation() to prevent clicks inside the detail from bubbling up and
collapsing the view.

---

Outside diff comments:
In `@src/components/analytics/chart.rs`:
- Around line 70-94: Define a has_chart1 boolean (e.g., based on metric_has_data
for slots used by chart1) and use it to gate rendering and layout for the first
chart: compute has_chart1 before computing chart1_top/chart1_bottom and update
chart3_top/chart3_bottom/total_height logic to collapse subsequent charts upward
when has_chart1 or has_chart2 is false (replace the unconditional chart1 spacing
with conditional branches using has_chart1/has_chart2/has_chart3); wrap the
chart-1 baseline and chart-1 cursor guide rendering blocks (previously
unconditional around the baseline and cursor guide) with if has_chart1 { ... }
guards; and finally adjust interact_height and xlabel_y calculations to pick the
topmost rendered chart (use chart1_top if has_chart1, else chart2_top if
has_chart2, else chart3_top) so interaction region/x-labels align with the first
visible chart.

In `@src/components/analytics/mod.rs`:
- Around line 100-179: The Metric::Volume arms are dead code; update the
log_matches closure and the AnalyticsMode::SessionTotal match to either remove
the Metric::Volume branches or replace them with unreachable!() to indicate they
cannot occur. Specifically, edit the log_matches closure defined inside the
chart_data mapping (the closure named log_matches) to eliminate the
Metric::Volume => false branch (or change it to unreachable!()), and in the
AnalyticsMode::SessionTotal match inside the same mapping, replace the
Metric::Volume => 0.0 arm with unreachable!() (or remove it and ensure match
remains exhaustive via a wildcard/unreachable), so intent is explicit and dead
branches are removed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8f76d6f5-1f6c-4892-8649-af2dc089260c

📥 Commits

Reviewing files that changed from the base of the PR and between dcd5c81 and 4b95358.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • flake.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • flake.nix
  • garnix.yaml
  • src/components/analytics/chart.rs
  • src/components/analytics/mod.rs
  • src/components/home.rs
  • src/components/more.rs
  • src/components/session_timers.rs
  • src/models/analytics.rs
  • src/models/session.rs
  • src/services/app_state.rs
  • src/services/exercise_db.rs
  • src/services/storage.rs

Comment thread src/components/home.rs
};
rsx! {
article {
article { onclick: move |_| show_detail.toggle(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Dioxus 0.7 KeyboardData event Key enum variants for Enter and Space

💡 Result:

In Dioxus 0.7, the KeyboardData accessor.key returns a dioxus::prelude::Key enum. The variants you want are: 1) Enter -> dioxus::prelude::Key::Enter [1] 2) Space -> dioxus::prelude::Key::Character(" ") (space as a character) rather than a dedicated Key::Space variant [1] Notes: - KeyboardData::key is “the value of the key pressed by the user”, considering modifier keys/layout, so the spacebar is represented as the character produced: " " [2]. - If you instead want the physical key (layout-independent), use KeyboardData::code which returns a dioxus::prelude::Code enum, where Space is Code::Space [3].

Citations:


🏁 Script executed:

wc -l src/components/home.rs

Repository: gfauredev/LogOut

Length of output: 87


🏁 Script executed:

sed -n '310,330p' src/components/home.rs

Repository: gfauredev/LogOut

Length of output: 784


🏁 Script executed:

sed -n '380,410p' src/components/home.rs

Repository: gfauredev/LogOut

Length of output: 1294


🏁 Script executed:

sed -n '300,325p' src/components/home.rs

Repository: gfauredev/LogOut

Length of output: 1039


Article element lacks keyboard and screen-reader accessibility.

The <article> at line 322 has onclick: move |_| show_detail.toggle() but no role, tabindex, aria_expanded, or onkeydown handler. This makes the toggle inaccessible to keyboard and assistive technology users—the element is not focusable and does not announce as interactive.

Add keyboard support by including:

  • role: "button"
  • tabindex: 0 (to enable keyboard focus)
  • aria_expanded: "{*show_detail.read()}" (to indicate state)
  • onkeydown: move |evt| { if matches!(evt.key(), Key::Enter | Key::Character(" ")) { evt.prevent_default(); show_detail.toggle(); } }

The web search confirms Dioxus 0.7 supports Key::Enter and Key::Character(" ") for these events.

Additionally, the resolved_logs computation (lines 303–320) executes on every render despite only being used when show_detail is true; gate it on the signal to avoid unnecessary allocations and signal reads. Similarly, clicking elements in the detail view (lines 383–405) bubbles up and collapses the detail; add stop_propagation() to the detail container to prevent this.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/home.rs` at line 322, The article toggle is not accessible and
the detail-related computation runs every render and clicks bubble out; update
the article element (where onclick: move |_| show_detail.toggle()) to include
role: "button", tabindex: 0, aria_expanded: "{*show_detail.read()}", and an
onkeydown handler that checks Key::Enter and Key::Character(" ") to
prevent_default and toggle show_detail so keyboard and screen readers see and
can activate it; move the resolved_logs computation (the block computing
resolved_logs used only by the detail view) behind a guard that checks
show_detail.read() so it only computes when detail is visible; and in the detail
container click handler(s) add stop_propagation() to prevent clicks inside the
detail from bubbling up and collapsing the view.

Comment thread src/components/home.rs
Comment on lines +383 to +405
if *show_detail.read() {
for (idx, (name, log)) in resolved_logs.iter().rev().enumerate() {
article { key: "{idx}",
header {
h4 { "{name}" }
}
ul {
if log.weight_hg.0 > 0 {
li { "{log.weight_hg}" }
}
if let Some(reps) = log.reps {
li { "{reps} reps" }
}
if let Some(d) = log.distance_m {
li { "{d}" }
}
if let Some(dur) = log.duration_seconds() {
li { "{crate::models::format_time(dur)}" }
}
}
}
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clicks inside the detail view bubble up and immediately collapse it.

The inner <article> / <ul> / <li> elements emitted under if *show_detail.read() have no stop_propagation, so any tap inside the detail (e.g. to read or select a metric) bubbles to the outer <article> onclick and toggles show_detail off. Either wrap the detail block in a span/div with onclick: |evt| evt.stop_propagation(), or move the toggle to a dedicated control so the body is not part of the click target.

Also note key: "{idx}" after .rev().enumerate() produces stable keys per render but is not tied to log identity; preferring log.start_time (or a composite of exercise_id and start_time) gives more meaningful diffs if the detail content ever changes between renders.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/home.rs` around lines 383 - 405, Clicks inside the detail
panel are bubbling to the outer toggle and collapsing the view; wrap the
detailed block emitted when *show_detail.read() is true (the inner article/ul/li
produced from resolved_logs.iter().rev().enumerate()) in a container element
that calls evt.stop_propagation() on onclick (e.g., an inner div/span with
onclick: |evt| evt.stop_propagation()) so inner taps don't reach the outer
article toggle, or alternatively move the toggle logic off the article body into
a dedicated control/button; also replace the fragile key: "{idx}" with a stable
identity from the log (e.g., log.start_time or a composite like
(log.exercise_id, log.start_time)) so diffs track the correct items.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

📊 Coverage Report

Lines: 3774/5029 (75.04474050507059%)

⏱️ Tests: 255 tests in 0.611s

FilenameFunction CoverageLine CoverageRegion CoverageBranch Coverage
main.rs
  22.00% (11/50)
  58.94% (155/263)
  60.56% (218/360)
- (0/0)
models/analytics.rs
   0.00% (0/6)
   0.00% (0/36)
   0.00% (0/48)
- (0/0)
models/enums.rs
 100.00% (28/28)
 100.00% (147/147)
 100.00% (337/337)
- (0/0)
models/exercise.rs
  92.00% (46/50)
  92.10% (548/595)
  91.01% (749/823)
- (0/0)
models/log.rs
 100.00% (12/12)
 100.00% (118/118)
 100.00% (144/144)
- (0/0)
models/mod.rs
 100.00% (11/11)
 100.00% (67/67)
 100.00% (97/97)
- (0/0)
models/session.rs
  72.22% (13/18)
  84.78% (156/184)
  83.33% (210/252)
- (0/0)
models/units.rs
 100.00% (28/28)
 100.00% (167/167)
  98.88% (353/357)
- (0/0)
services/app_state.rs
   1.89% (1/53)
   2.47% (11/445)
   2.27% (15/662)
- (0/0)
services/exercise_db.rs
  88.81% (127/143)
  90.30% (1210/1340)
  89.60% (1922/2145)
- (0/0)
services/exercise_loader.rs
   0.00% (0/14)
   0.00% (0/100)
   0.00% (0/135)
- (0/0)
services/native_queue.rs
   0.00% (0/15)
   0.00% (0/145)
   0.00% (0/197)
- (0/0)
services/notifications.rs
   0.00% (0/3)
   0.00% (0/9)
   0.00% (0/10)
- (0/0)
services/service_worker.rs
 100.00% (2/2)
 100.00% (6/6)
 100.00% (6/6)
- (0/0)
services/storage.rs
  63.77% (88/138)
  81.21% (778/958)
  83.53% (1187/1421)
- (0/0)
services/wake_lock.rs
 100.00% (2/2)
 100.00% (5/5)
 100.00% (5/5)
- (0/0)
utils.rs
  91.36% (74/81)
  91.44% (406/444)
  91.80% (649/707)
- (0/0)
Totals
  67.74% (443/654)
  75.04% (3774/5029)
  76.46% (5892/7706)
- (0/0)

@gfauredev gfauredev merged commit fda7349 into main May 7, 2026
8 checks passed
@gfauredev gfauredev deleted the copilot/add-daily-volume-average-weight-selectors branch May 7, 2026 08:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants