Skip to content

Changes before error encountered#142

Merged
gfauredev merged 4 commits into
mainfrom
copilot/add-maintain-increment-decrement-buttons
Apr 26, 2026
Merged

Changes before error encountered#142
gfauredev merged 4 commits into
mainfrom
copilot/add-maintain-increment-decrement-buttons

Conversation

@gfauredev
Copy link
Copy Markdown
Owner

@gfauredev gfauredev commented Apr 24, 2026

Agent-Logs-Url: https://github.com/gfauredev/LogOut/sessions/0fa7bfd2-30f6-431b-8cc3-ae8db43b741e

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

    • Toast confirmations for session and exercise-log deletions.
    • Press-and-hold controls for adjusting time, weight, distance and repetitions.
    • Empty cardio distance accepted for time-only tracking.
    • UI shows “–” for missing exercise metrics instead of “0”.
  • Improvements

    • Cancelled/unmounted components no longer trigger stale or duplicate scheduled notifications.

Agent-Logs-Url: https://github.com/gfauredev/LogOut/sessions/0fa7bfd2-30f6-431b-8cc3-ae8db43b741e

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

coderabbitai Bot commented Apr 24, 2026

📝 Walkthrough

Walkthrough

Adds two new localisation keys for deletion toasts; emits those toasts on session or exercise-log deletion; implements press‑and‑hold continuous increment/decrement and improved cardio distance/placeholder handling in the session exercise form; and adds an unmount/cancellation token to prevent stale timer notifications.

Changes

Cohort / File(s) Summary
Localization
assets/en.ftl, assets/es.ftl, assets/fr.ftl
Add two new translation keys: toast-session-deleted and toast-log-deleted.
Exercise log UI
src/components/completed_exercise_log.rs
Emit t!("toast-log-deleted") into the global ToastSignal queue when an exercise log is deleted.
Session deletion flow
src/services/app_state.rs
Append localized toast-session-deleted to the toast queue before calling the session deletion enqueue.
Session exercise form
src/components/session_exercise_form.rs
Replace one‑shot +/- handlers with press‑and‑hold pointer events using per-field generation tokens; treat empty cardio distance as valid (None/time‑only); clear input on near‑zero decrement; show for absent ATH/best values.
Session timers
src/components/session_timers.rs
Introduce use_cancel_token() returning an Arc<AtomicBool> set on unmount; pass cancel token into schedule_duration_notification and check before firing to avoid notifications from unmounted/stale components.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Comp as Component
  participant App as AppState / ToastSignal
  participant Storage

  Note over User,Comp: Delete exercise log or session
  User->>Comp: Click Delete
  Comp->>App: enqueue toast (`toast-log-deleted` / `toast-session-deleted`)
  App->>Storage: persist deletion (save_session / enqueue_delete_session)
  App->>App: ToastSignal broadcasts toast -> UI shows notification

  Note over Comp: Timers use cancel token to avoid stray notifications
  User->>Comp: Unmount component
  Comp->>Comp: set cancel token -> schedule_duration_notification returns early
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Click, hold, and watch the toast appear,
A nibble of change that hops in cheer,
Buttons hum steady, distance clears,
Timers stay quiet when unmounted nears,
I hop away delighted — coding carrots here! 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description contains only the template checklist with all items unchecked and no substantive explanation of what code changes were made or why. Complete the description by explaining the feature or fix implemented, listing the key changes (toast notifications, UI improvements to buttons, timer fixes), and documenting which checklist items have been verified.
Title check ❓ Inconclusive The title 'Changes before error encountered' is vague and does not clearly convey the primary changes in the PR (toast notifications and UI improvements). Provide a clear, specific title that describes the main changes, such as 'Add toast notifications for session and log deletion' or 'Implement press-and-hold increment/decrement buttons'.
✅ Passed checks (3 passed)
Check name Status Explanation
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-maintain-increment-decrement-buttons

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

📊 Coverage Report

Lines: 3757/5000 (75.14%)

⏱️ Tests: 255 tests in 0.615s

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/33)
   0.00% (0/45)
- (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.14% (3757/5000)
  76.62% (5894/7693)
- (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

Caution

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

⚠️ Outside diff range comments (1)
src/components/completed_exercise_log.rs (1)

85-93: ⚠️ Potential issue | 🟡 Minor

Misleading “Exercise log deleted” toast when the screen-lock guard rejects the write.

The toast is enqueued unconditionally at the top of the closure, but storage::save_session (in src/services/app_state.rs) short-circuits and pushes toast-screen-locked when screen_is_locked() is true and the session is not the currently active one. In that case the user sees both “🗑️ Exercise log deleted” and “🔒 Screen locked …”, falsely confirming a deletion that never happened.

Consider enqueuing the success toast only after the guards have passed (i.e. after save_session in a path that returns success), or moving the guard/confirmation inside this component before emitting the toast.

🔧 One possible adjustment
                         on_delete: move |()| {
-                            consume_context::<crate::ToastSignal>()
-                                .0
-                                .write()
-                                .push_back(t!("toast-log-deleted").to_string());
                             let mut current_session = session.read().clone();
                             current_session.exercise_logs.remove(idx);
                             storage::save_session(current_session);
+                            consume_context::<crate::ToastSignal>()
+                                .0
+                                .write()
+                                .push_back(t!("toast-log-deleted").to_string());
                         },

Note: save_session still returns () on a guard rejection, so ideally it should expose success/failure to the caller so the toast is only shown on an actual save. Happy to sketch that refactor if useful.

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

In `@src/components/completed_exercise_log.rs` around lines 85 - 93, The toast
"toast-log-deleted" is enqueued before attempting to persist changes, causing a
misleading success message when storage::save_session short-circuits (e.g.,
screen lock); move the
consume_context::<crate::ToastSignal>().0.write().push_back(...) call so it runs
only after storage::save_session(...) completes successfully (i.e., after you
mutate current_session.exercise_logs and call save_session), and if possible
refactor storage::save_session to return a Result or boolean success flag that
on_delete can check before enqueuing the success toast; update the on_delete
closure accordingly (referencing on_delete,
current_session.exercise_logs.remove, storage::save_session and
consume_context::<crate::ToastSignal>).
🤖 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/session_exercise_form.rs`:
- Around line 98-131: Extract the duplicated press-and-hold logic into two small
helpers (e.g. fn start_hold_repeat(mut gen_sig: Signal<u32>, mut tick: impl
FnMut() + 'static) and fn cancel_hold_repeat(mut gen_sig: Signal<u32>)) that
perform the immediate tick, bump the generation, spawn the delayed repeat loop
(HOLD_INITIAL_DELAY_MS / HOLD_REPEAT_INTERVAL_MS) and cancel by bumping the gen;
then replace each button's
onpointerdown/onpointerup/onpointerleave/onpointercancel blocks (references:
time_btn_gen, rep_btn_gen, sets_btn_gen, etc.) with calls to
start_hold_repeat(gen_sig, move || { /* original tick body using time_input /
rep_input / sets_input */ }) and cancel_hold_repeat(gen_sig) so each ± button
only contains its tick closure and uses the existing parse_duration_seconds /
format_time call sites.

In `@src/components/session_timers.rs`:
- Around line 15-34: Replace the custom CancelOnDrop type and its Clone/Drop
logic with Dioxus lifecycle primitives: in ExerciseElapsedTimer and
InlineExerciseTimer stop using Arc<AtomicBool> cancel tokens and the
CancelOnDrop owner/non-owner pattern and instead either (a) call use_drop to run
cleanup on unmount or (b) prefer dioxus::prelude::spawn (or use_future) inside
the existing use_effect to run the timeout so the task is automatically
cancelled on unmount/re-run; remove the CancelOnDrop struct and all
_cancel_guard/cloning usage, and if you retain a cancel-token approach extract
the duplicated cancel-token boilerplate from ExerciseElapsedTimer and
InlineExerciseTimer into a shared helper function called by both.
- Around line 85-94: The effect in use_effect that calls
schedule_duration_notification spawns wasm_bindgen_futures::spawn_local tasks
(guarded by cancel and duration_bell_rung) on every render, leaking pending
futures; modify the use_effect invocation that wraps
schedule_duration_notification to include a dependency array [exercise_start,
last_duration] (or replace with yew_hooks::use_future/use_future_with_deps) so
tasks only spawn when those values change, ensuring no accumulation of in-flight
spawn_local timers tied to re-renders.

---

Outside diff comments:
In `@src/components/completed_exercise_log.rs`:
- Around line 85-93: The toast "toast-log-deleted" is enqueued before attempting
to persist changes, causing a misleading success message when
storage::save_session short-circuits (e.g., screen lock); move the
consume_context::<crate::ToastSignal>().0.write().push_back(...) call so it runs
only after storage::save_session(...) completes successfully (i.e., after you
mutate current_session.exercise_logs and call save_session), and if possible
refactor storage::save_session to return a Result or boolean success flag that
on_delete can check before enqueuing the success toast; update the on_delete
closure accordingly (referencing on_delete,
current_session.exercise_logs.remove, storage::save_session and
consume_context::<crate::ToastSignal>).
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 25b1a3ae-8a9e-49df-8ea9-907965270493

📥 Commits

Reviewing files that changed from the base of the PR and between a9ce681 and 17cbdd6.

📒 Files selected for processing (7)
  • assets/en.ftl
  • assets/es.ftl
  • assets/fr.ftl
  • src/components/completed_exercise_log.rs
  • src/components/session_exercise_form.rs
  • src/components/session_timers.rs
  • src/services/app_state.rs

Comment thread src/components/session_exercise_form.rs
Comment thread src/components/session_timers.rs Outdated
Comment thread src/components/session_timers.rs
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 26, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 2 file(s) based on 3 unresolved review comments.

Files modified:

  • src/components/session_exercise_form.rs
  • src/components/session_timers.rs

Commit: 6fc1d245e9cd39c958107e51c627dc274a952af5

The changes have been pushed to the copilot/add-maintain-increment-decrement-buttons branch.

Time taken: 7m 40s

Fixed 2 file(s) based on 3 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
@github-actions
Copy link
Copy Markdown

📊 Coverage Report

Lines: 3757/5000 (75.14%)

⏱️ Tests: 255 tests in 0.600s

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/33)
   0.00% (0/45)
- (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.14% (3757/5000)
  76.62% (5894/7693)
- (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: 2

🤖 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/session_exercise_form.rs`:
- Around line 105-110: Remove the redundant mutability on the per-field
generation signal bindings: change the declarations of time_btn_gen,
weight_btn_gen, dist_btn_gen, and reps_btn_gen so they are created with
use_signal(|| 0u32) without the mut keyword; leave start_hold_repeat and
cancel_hold_repeat usage unchanged (these functions accept their own mut
parameters), so only remove mut from the local bindings in the
session_exercise_form component.

In `@src/components/session_timers.rs`:
- Around line 41-44: Update the stale intra-doc link that points to
`CancelOnDrop` in the doc comment describing the cancel token: replace the
reference with the new helper `use_cancel_token` (or an intra-doc link like
[`use_cancel_token`]) and, if helpful, mention it uses `use_drop`; modify the
comment block around the paragraph that currently mentions `CancelOnDrop` in
src/components/session_timers.rs so the link is correct and `rustdoc`/`cargo
clippy` no longer reports a broken_intra_doc_links warning.
🪄 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: 9de9b992-8934-42c6-ba70-67d715049cbb

📥 Commits

Reviewing files that changed from the base of the PR and between 17cbdd6 and 6fc1d24.

📒 Files selected for processing (2)
  • src/components/session_exercise_form.rs
  • src/components/session_timers.rs

Comment thread src/components/session_exercise_form.rs Outdated
Comment thread src/components/session_timers.rs Outdated
@github-actions
Copy link
Copy Markdown

📊 Coverage Report

Lines: 3757/5000 (75.14%)

⏱️ Tests: 255 tests in 0.593s

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/33)
   0.00% (0/45)
- (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.14% (3757/5000)
  76.62% (5894/7693)
- (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/session_exercise_form.rs`:
- Around line 9-38: Refactor the spawned repeat loop in start_hold_repeat to use
a while loop with the cancellation predicate instead of loop { if … break; … }
for clarity: inside the async move block replace the current loop/if-break
pattern with while *gen_sig.peek() == next_gen { tick();
crate::utils::sleep_ms(HOLD_REPEAT_INTERVAL_MS).await; } so the cancellation
condition is self-evident (keep the existing tick(), sleep, and use of next_gen
as-is).

In `@src/components/session_timers.rs`:
- Around line 158-171: Extract the duplicated cancel-token + scheduling effect
into a shared hook called e.g. use_duration_notification and replace the inline
effect in both ExerciseElapsedTimer and InlineExerciseTimer with a single call
to that hook; implement use_duration_notification to call use_cancel_token(),
then use_effect(use_reactive!(|(exercise_start, last_duration)| {
schedule_duration_notification(exercise_start, last_duration,
duration_bell_rung, cancel.clone()); })), and keep the same parameter types
(exercise_start: Option<u64>, last_duration: Option<u64>, duration_bell_rung:
Signal<bool>) so existing calls to schedule_duration_notification,
use_cancel_token, use_effect and use_reactive continue to work without other
changes.
- Around line 164-171: The effect spawns a gloo_timers task that isn’t canceled
on effect re-run, causing stale notifications; update the reactive closure
around use_effect/use_reactive! that calls schedule_duration_notification so
each re-run cancels the previous task: either (A) create a per-effect cancel
token (e.g., a Signal/Arc<AtomicBool> stored via a use_hook or Signal) and flip
it before spawning the new task and still flip on unmount (replacing the shared
cancel used by schedule_duration_notification), or (B, preferred) convert the
logic to use_resource(use_reactive!(...)) for schedule_duration_notification so
Dioxus auto-cancels in-flight tasks on dependency changes and unmount; apply the
same fix to InlineExerciseTimer and remove reliance on the global
use_cancel_token cancel flag for these effects.
🪄 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: 97cd8701-56c9-4a61-95be-aa1c4f68387e

📥 Commits

Reviewing files that changed from the base of the PR and between 6fc1d24 and 24fdc19.

📒 Files selected for processing (2)
  • src/components/session_exercise_form.rs
  • src/components/session_timers.rs

Comment on lines +9 to +38
/// Initial hold delay before continuous increment/decrement starts (ms).
const HOLD_INITIAL_DELAY_MS: u32 = 400;
/// Interval between repeated increments/decrements while holding (ms).
const HOLD_REPEAT_INTERVAL_MS: u32 = 100;

/// Start press-and-hold repeat behavior for increment/decrement buttons.
///
/// Performs an immediate tick, bumps the generation signal to cancel any
/// in-flight hold task, then spawns a delayed repeat loop.
fn start_hold_repeat(mut gen_sig: Signal<u32>, mut tick: impl FnMut() + 'static) {
tick();
let next_gen = gen_sig.peek().wrapping_add(1);
gen_sig.set(next_gen);
spawn(async move {
crate::utils::sleep_ms(HOLD_INITIAL_DELAY_MS).await;
loop {
if *gen_sig.peek() != next_gen {
break;
}
tick();
crate::utils::sleep_ms(HOLD_REPEAT_INTERVAL_MS).await;
}
});
}

/// Cancel press-and-hold repeat by bumping the generation signal.
fn cancel_hold_repeat(mut gen_sig: Signal<u32>) {
let next = gen_sig.peek().wrapping_add(1);
gen_sig.set(next);
}
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

Hold-to-repeat extraction looks good.

The two-helper pattern with a per-field generation signal is sound: the immediate tick() runs synchronously before the gen bump, so any in-flight task with the previous generation cleanly exits on its next peek check (no double-tick is possible under Dioxus' single-threaded async runtime). Cancellation via wrapping_add means at most one extra tick can fire after release (within the 100 ms interval), which is acceptable.

Optional nit: the loop { if … break; … } could be expressed more idiomatically as a while to make the cancellation predicate self-evident.

♻️ Optional refactor
     spawn(async move {
         crate::utils::sleep_ms(HOLD_INITIAL_DELAY_MS).await;
-        loop {
-            if *gen_sig.peek() != next_gen {
-                break;
-            }
+        while *gen_sig.peek() == next_gen {
             tick();
             crate::utils::sleep_ms(HOLD_REPEAT_INTERVAL_MS).await;
         }
     });
🤖 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 9 - 38, Refactor the
spawned repeat loop in start_hold_repeat to use a while loop with the
cancellation predicate instead of loop { if … break; … } for clarity: inside the
async move block replace the current loop/if-break pattern with while
*gen_sig.peek() == next_gen { tick();
crate::utils::sleep_ms(HOLD_REPEAT_INTERVAL_MS).await; } so the cancellation
condition is self-evident (keep the existing tick(), sleep, and use of next_gen
as-is).

Comment on lines +158 to +171
// Cancel token: set to `true` when this component unmounts so any pending
// `spawn_local` duration notification is discarded.
let cancel = use_cancel_token();
// Schedule a precise one-shot notification (WASM only; native uses tick).
use_effect(move || {
schedule_duration_notification(exercise_start, last_duration, duration_bell_rung);
});
// Only run when exercise_start or last_duration changes to avoid spawning
// duplicate tasks on re-renders.
use_effect(use_reactive!(|(exercise_start, last_duration)| {
schedule_duration_notification(
exercise_start,
last_duration,
duration_bell_rung,
cancel.clone(),
);
}));
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

Extract the duplicated cancel-token + scheduling effect into a shared hook.

Lines 158–171 (ExerciseElapsedTimer) and 223–237 (InlineExerciseTimer) are byte-for-byte identical. Per the coding guidelines (idiomatic Rust, DRY), pull this into a helper hook so future changes — e.g., the per-effect-cancel fix above — only need to be made in one place.

♻️ Proposed helper
/// Wires the one-shot duration notification with unmount cancellation,
/// rescheduling only when `exercise_start` or `last_duration` changes.
fn use_duration_notification(
    exercise_start: Option<u64>,
    last_duration: Option<u64>,
    duration_bell_rung: Signal<bool>,
) {
    let cancel = use_cancel_token();
    use_effect(use_reactive!(|(exercise_start, last_duration)| {
        schedule_duration_notification(
            exercise_start,
            last_duration,
            duration_bell_rung,
            cancel.clone(),
        );
    }));
}

Then both timer components collapse to a single call:

-    let cancel = use_cancel_token();
-    use_effect(use_reactive!(|(exercise_start, last_duration)| {
-        schedule_duration_notification(
-            exercise_start,
-            last_duration,
-            duration_bell_rung,
-            cancel.clone(),
-        );
-    }));
+    use_duration_notification(exercise_start, last_duration, duration_bell_rung);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/session_timers.rs` around lines 158 - 171, Extract the
duplicated cancel-token + scheduling effect into a shared hook called e.g.
use_duration_notification and replace the inline effect in both
ExerciseElapsedTimer and InlineExerciseTimer with a single call to that hook;
implement use_duration_notification to call use_cancel_token(), then
use_effect(use_reactive!(|(exercise_start, last_duration)| {
schedule_duration_notification(exercise_start, last_duration,
duration_bell_rung, cancel.clone()); })), and keep the same parameter types
(exercise_start: Option<u64>, last_duration: Option<u64>, duration_bell_rung:
Signal<bool>) so existing calls to schedule_duration_notification,
use_cancel_token, use_effect and use_reactive continue to work without other
changes.

Comment on lines +164 to +171
use_effect(use_reactive!(|(exercise_start, last_duration)| {
schedule_duration_notification(
exercise_start,
last_duration,
duration_bell_rung,
cancel.clone(),
);
}));
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

🧩 Analysis chain

🌐 Web query:

Dioxus 0.7 use_future use_reactive cancellation on dependency change and on unmount

💡 Result:

In Dioxus 0.7: use_future: - Cancellation on unmount: Yes, futures are automatically cancelled when the component unmounts (like all spawn-based tasks). - Cancellation on dependency change: No automatic behavior. use_future is non-reactive and runs once on mount. For dependency-driven restarts, manually call restart (which cancels the current future first) or use use_resource instead. - Manual control: UseFuture provides cancel, restart (cancels + new future), pause, resume. use_reactive: - This hook makes non-reactive data reactive by returning a closure that tracks dependencies when called in reactive contexts (e.g., inside use_memo or use_effect). - It does not directly handle async/cancellation. Use it to make props reactive, e.g., in combination with use_resource: use_resource(use_reactive!(|(dep,)| async { ... })) where dep changes trigger rerun + cancellation. Key quotes: - "Futures can be cancelled before they complete... automatically be cancelled when the component is unmounted." - use_future restart: "Cancel the current future and spawn a new one." - use_resource: "Previous futures are automatically cancelled when restarting" on dependency changes. For reactive async with auto-cancel on deps: prefer use_resource over use_future + deps.

Citations:


🏁 Script executed:

head -n 250 src/components/session_timers.rs | tail -n +150

Repository: gfauredev/LogOut

Length of output: 3940


🏁 Script executed:

rg -n "schedule_duration_notification" src/components/session_timers.rs | head -20

Repository: gfauredev/LogOut

Length of output: 186


🏁 Script executed:

sed -n '47,120p' src/components/session_timers.rs

Repository: gfauredev/LogOut

Length of output: 3030


Effect re-run leaks the previous spawn_local task and can fire a stale notification.

use_cancel_token only flips on component unmount (via use_drop), not on effect re-execution. When exercise_start or last_duration changes during the component's lifetime, use_effect re-runs and schedule_duration_notification spawns a new gloo_timers future, but the previously-spawned future is still pending against the old deadline. The shared cancel token remains false, so whichever task wakes up first will pass the cancel.load(...) check, set duration_bell_rung, and send a notification at the outdated time. The duration_bell_rung.peek() guard then suppresses the correct one. Same root cause applies to the effect in InlineExerciseTimer.

Two idiomatic fixes (per coding guidelines, prefer modern Dioxus patterns):

  1. Use a per-effect cancel token: store an Arc<AtomicBool> in a Signal/use_hook, flip the previous one before spawning the new one inside the reactive closure (in addition to flipping on unmount).
  2. Replace spawn_local with use_resource(use_reactive!(...)) so Dioxus automatically cancels the in-flight task on dependency change and on unmount, eliminating the cancel token entirely.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/session_timers.rs` around lines 164 - 171, The effect spawns a
gloo_timers task that isn’t canceled on effect re-run, causing stale
notifications; update the reactive closure around use_effect/use_reactive! that
calls schedule_duration_notification so each re-run cancels the previous task:
either (A) create a per-effect cancel token (e.g., a Signal/Arc<AtomicBool>
stored via a use_hook or Signal) and flip it before spawning the new task and
still flip on unmount (replacing the shared cancel used by
schedule_duration_notification), or (B, preferred) convert the logic to
use_resource(use_reactive!(...)) for schedule_duration_notification so Dioxus
auto-cancels in-flight tasks on dependency changes and unmount; apply the same
fix to InlineExerciseTimer and remove reliance on the global use_cancel_token
cancel flag for these effects.

@gfauredev gfauredev merged commit 8fa5bae into main Apr 26, 2026
7 checks passed
@gfauredev gfauredev deleted the copilot/add-maintain-increment-decrement-buttons branch April 26, 2026 16:13
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