Skip to content

fix(ui): prevent stacking banners during batch wallet identity search#735

Merged
lklimek merged 5 commits into
dashpay:v1.0-devfrom
thepastaclaw:fix/stacking-progress-banners
Mar 20, 2026
Merged

fix(ui): prevent stacking banners during batch wallet identity search#735
lklimek merged 5 commits into
dashpay:v1.0-devfrom
thepastaclaw:fix/stacking-progress-banners

Conversation

@thepastaclaw
Copy link
Copy Markdown
Collaborator

@thepastaclaw thepastaclaw commented Mar 12, 2026

Fixes #713

Problem

When searching for wallet identities, each progress update (Searching index X of Y...) was sent as a BackendTaskSuccessResult::Message, which creates a new global banner via MessageBanner::set_global(). Since each message has different text, banners stacked up instead of replacing each other.

Fix

Added a BackendTaskSuccessResult::Progress(String) variant that:

  • Does NOT create global banners (bypasses MessageBanner::set_global())
  • Only updates the originating screen's existing banner handle in-place via display_task_result()

Changed the batch identity search to use Progress instead of Message for iteration updates.

Changes

  • src/backend_task/mod.rs: Added Progress(String) variant to BackendTaskSuccessResult
  • src/app.rs: Route Progress to screen without global banner
  • src/ui/identities/add_existing_identity_screen.rs: Handle Progress by updating existing banner handle in-place
  • src/backend_task/identity/load_identity_from_wallet.rs: Use Progress instead of Message for search iteration updates

4 files, +18/-2 lines.

Summary by CodeRabbit

  • New Features

    • Real-time, on-screen progress updates during identity loading; progress shows current/total and updates inline on the active screen.
  • Bug Fixes

    • Progress updates no longer reuse global success banners, improving local feedback.
    • Note: progress is screen-scoped and may appear on a different screen if you navigate while a task is running.

Add BackendTaskSuccessResult::Progress variant for progress updates
during long-running operations. Unlike Message, Progress does NOT
create global banners via MessageBanner::set_global — it only updates
the originating screen's existing banner handle in-place.

Root cause: load_user_identities_up_to_index() sent progress as
Message('Searching index X of Y...'), and app.rs's Message handler
calls MessageBanner::set_global() for every Message. Since each
progress text is unique, set_global() created a new banner each time,
causing the stacking visible in DET#713.

Fix: Change batch search progress to use the new Progress variant,
which bypasses global banner creation and only flows through
display_task_result() to update the screen's existing banner handle.

Closes dashpay#713
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 845de204-3cc4-42db-90e7-40490778c78a

📥 Commits

Reviewing files that changed from the base of the PR and between 9ae22ad and 994559b.

📒 Files selected for processing (4)
  • src/app.rs
  • src/backend_task/identity/load_identity_from_wallet.rs
  • src/backend_task/mod.rs
  • src/ui/identities/add_existing_identity_screen.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/backend_task/identity/load_identity_from_wallet.rs
  • src/app.rs

📝 Walkthrough

Walkthrough

Adds a structured BackendTaskSuccessResult::Progress { message, current, total } variant; backend tasks emit progress updates, the app loop routes them to the currently visible screen's display_task_result, and the AddExistingIdentity screen updates or creates an in-place refresh banner. Progress may appear on the wrong screen if navigation occurs before delivery.

Changes

Cohort / File(s) Summary
Backend Task Progress Definition
src/backend_task/mod.rs
Add BackendTaskSuccessResult::Progress { message: String, current: u32, total: u32 }; adjust doc comment on Message(String) to remove progress usage wording.
App Loop Progress Routing
src/app.rs
Handle BackendTaskSuccessResult::Progress { .. } inside TaskResult::Success by forwarding progress results to self.visible_screen_mut().display_task_result(...) (screen-scoped routing).
Identity Loading Backend Task
src/backend_task/identity/load_identity_from_wallet.rs
Per-iteration status now emitted as Progress { message, current: identity_index+1, total: max_identity_index+1 } with updated message text. Loop behavior and error handling unchanged.
Identity Screen UI Updates
src/ui/identities/add_existing_identity_screen.rs
display_task_result now matches Progress { message: msg, .. } and updates an existing refresh_banner via handle.set_message(&msg) or creates/replaces it with replace_with_elapsed(...) when absent.

Sequence Diagram

sequenceDiagram
    participant Backend as Backend Task
    participant AppLoop as App Loop
    participant UIScreen as Visible Screen
    participant Banner as Refresh Banner

    Backend->>AppLoop: emit BackendTaskSuccessResult::Progress{message,current,total}
    AppLoop->>UIScreen: visible_screen_mut().display_task_result(Progress)
    UIScreen->>Banner: update existing banner (set_message) or create banner (replace_with_elapsed)
    Banner->>UIScreen: banner rendered with new progress message
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibble dots and count them slow,
Each index hops, one, two, then go.
A banner whispers where I tread,
Progress blooms in softled thread.
binky-bink 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely describes the main fix: preventing banner stacking during batch wallet identity search, which aligns with the primary change.
Linked Issues check ✅ Passed The PR successfully addresses issue #713 by implementing structured progress updates via a new Progress variant to replace global banner Message calls, preventing banner stacking.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing issue #713: new Progress variant, routing logic, banner handling, and progress emission during identity search. No unrelated changes detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@lklimek lklimek marked this pull request as ready for review March 15, 2026 21:07
@lklimek lklimek requested a review from Copilot March 15, 2026 21:07
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Prevents UI banner stacking during batch wallet identity search by introducing a non-global “progress update” task result that updates the current screen’s banner in place.

Changes:

  • Added BackendTaskSuccessResult::Progress(String) for non-global progress updates.
  • Routed Progress results to screens without creating global banners.
  • Switched wallet identity search iteration updates from Message to Progress.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
src/backend_task/mod.rs Adds Progress(String) success variant and documents intended behavior.
src/app.rs Handles Progress by forwarding to the visible screen without creating a global banner.
src/ui/identities/add_existing_identity_screen.rs Updates existing banner handle in-place when receiving Progress.
src/backend_task/identity/load_identity_from_wallet.rs Emits Progress during batch search iterations to avoid banner stacking.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/backend_task/mod.rs Outdated
Comment on lines +109 to +110
/// Unlike `Message`, these do NOT create global banners — they only update
/// the originating screen's existing banner handle via `display_task_result`.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The latest commit updated the doc comment to say "By convention, the app routes Progress results to the visible screen's display_task_result without creating a global banner" — which reflects the actual enforcement mechanism.

Comment thread src/ui/identities/add_existing_identity_screen.rs Outdated
Comment thread src/backend_task/mod.rs
Comment thread src/backend_task/mod.rs Outdated
/// Progress updates during long-running operations (e.g. batch identity search).
/// Unlike `Message`, these do NOT create global banners — they only update
/// the originating screen's existing banner handle via `display_task_result`.
Progress(String),
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.

claw please use structured progress type, with current / total. In future, we might get a dedicated progress component , it will be useful.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done — switched to a struct variant with current: u32 and total: u32 fields alongside the message: String. The call site passes 1-based values so a future progress bar component can just do current / total without offset math.

Progress {
    /// Human-readable progress message
    message: String,
    /// Current step (1-based)
    current: u32,
    /// Total steps
    total: u32,
}

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: 1

🧹 Nitpick comments (1)
src/backend_task/identity/load_identity_from_wallet.rs (1)

284-287: Make the progress banner text a complete, localizable sentence.

"Searching index {} of {}..." reads like an implementation detail and relies on positional placeholders. A full sentence with named placeholders will translate more cleanly and is easier to understand in the banner.

✏️ Suggested tweak
-                    BackendTaskSuccessResult::Progress(format!(
-                        "Searching index {} of {}...",
-                        identity_index, max_identity_index
-                    )),
+                    BackendTaskSuccessResult::Progress(format!(
+                        "Searching wallet identity index {current} of {last}.",
+                        current = identity_index,
+                        last = max_identity_index,
+                    )),

As per coding guidelines, "All user-facing strings (labels, messages, tooltips, errors) must be simple, complete sentences. Avoid concatenating fragments, positional assumptions, or grammar that breaks in other languages. Each string should be extractable as a single translation unit with named placeholders for dynamic values."

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

In `@src/backend_task/identity/load_identity_from_wallet.rs` around lines 284 -
287, The progress banner currently uses a fragment with positional placeholders
in the BackendTaskSuccessResult::Progress format! call; change it to a complete,
localizable sentence and use named placeholders instead (e.g., "Searching
identity {current} of {total}."), replacing the format! invocation that
references identity_index and max_identity_index so translators get a single
extractable string and the placeholders are explicit (refer to the
BackendTaskSuccessResult::Progress call around the format! that builds the
banner).
🤖 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/app.rs`:
- Around line 1115-1119: BackendTaskSuccessResult::Progress is being routed via
visible_screen_mut() (call sites: BackendTaskSuccessResult::Progress branch,
visible_screen_mut(), display_task_result()), which means progress updates
follow whatever screen is currently visible instead of the screen that started
the task (e.g., AddExistingIdentityScreen) and so refresh_banner stops updating
when the user navigates away. Fix this by adding task-to-screen affinity: when
starting the task, capture an identifier or direct banner handle from the
originating screen (e.g., store originating_screen_id or banner_handle on the
task context created by AddExistingIdentityScreen), then in the
BackendTaskSuccessResult::Progress handling path route the unboxed_message to
that stored identifier/handle instead of calling visible_screen_mut() so updates
go to the original screen's banner (or, if the original screen no longer exists,
gracefully drop the update). Ensure references use the existing symbols:
BackendTaskSuccessResult::Progress, display_task_result(), refresh_banner,
AddExistingIdentityScreen, visible_screen_mut() to locate and update the logic.

---

Nitpick comments:
In `@src/backend_task/identity/load_identity_from_wallet.rs`:
- Around line 284-287: The progress banner currently uses a fragment with
positional placeholders in the BackendTaskSuccessResult::Progress format! call;
change it to a complete, localizable sentence and use named placeholders instead
(e.g., "Searching identity {current} of {total}."), replacing the format!
invocation that references identity_index and max_identity_index so translators
get a single extractable string and the placeholders are explicit (refer to the
BackendTaskSuccessResult::Progress call around the format! that builds the
banner).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1a3d7339-7269-4d5f-9832-d66389cb0819

📥 Commits

Reviewing files that changed from the base of the PR and between 91abb2c and be0570d.

📒 Files selected for processing (4)
  • src/app.rs
  • src/backend_task/identity/load_identity_from_wallet.rs
  • src/backend_task/mod.rs
  • src/ui/identities/add_existing_identity_screen.rs

Comment thread src/app.rs Outdated
- Reword Progress doc comment to clarify behavior is a routing convention
  enforced by app.rs, not inherent to the enum variant (Copilot review)
- Add fallback banner creation in Progress handler when refresh_banner
  is None, ensuring progress is always visible (Copilot review)
- Use named placeholders in progress format string for localizability
  (CodeRabbit nitpick)
- Add TODO about visible_screen_mut() task-to-screen affinity limitation
  (CodeRabbit major: architectural change deferred)
@thepastaclaw
Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 19, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@lklimek lklimek requested a review from Copilot March 20, 2026 09:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Prevents global banner stacking during batch wallet identity search by introducing a dedicated progress result type intended to update an existing in-screen banner in-place.

Changes:

  • Added BackendTaskSuccessResult::Progress(String) for non-global progress updates.
  • Routed Progress results directly to the current screen without creating global banners.
  • Updated wallet identity search to emit Progress updates during iteration.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/backend_task/mod.rs Adds Progress(String) success result variant + docs describing routing conventions.
src/app.rs Routes Progress results to the visible screen without invoking global banner creation.
src/ui/identities/add_existing_identity_screen.rs Handles Progress by updating the existing banner handle; adds fallback banner creation when missing.
src/backend_task/identity/load_identity_from_wallet.rs Emits Progress updates during batch identity search iterations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/ui/identities/add_existing_identity_screen.rs Outdated
Comment on lines +284 to +285
current = identity_index,
last = max_identity_index,
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

The progress message labels {last} but passes max_identity_index (a max index, not a count), which reads oddly when indices are 0-based (e.g., “index 0 of 10”). Consider changing the format parameters to be unambiguous (e.g., max_index = max_identity_index) or switching to 1-based “current/total” reporting (current = identity_index + 1, total = max_identity_index + 1) for clearer user-facing progress.

Suggested change
current = identity_index,
last = max_identity_index,
current = identity_index + 1,
last = max_identity_index + 1,

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good point — switched to 1-based indexing. Now shows "Searching wallet identity index 1 of 10" instead of "0 of 9".

Comment thread src/app.rs Outdated
…otal

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@thepastaclaw thepastaclaw force-pushed the fix/stacking-progress-banners branch from 9ae22ad to 994559b Compare March 20, 2026 10:18
Copy link
Copy Markdown
Collaborator Author

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

Clean, well-scoped fix. The Progress variant is properly routed in app.rs, the screen handler updates banners in-place, and the structured fields enable future progress bar rendering. No blocking issues — just a theoretical overflow edge case and a stale comment from the pre-Progress era.

Reviewed commit: 994559b

🟡 2 suggestions · ⚪ 1 nitpick

Review agents: claude-general, codex-general, claude-rust-quality, codex-rust-quality Specialist: rust-quality (selected by LLM based on PR content)

total = max_identity_index + 1,
),
current: identity_index + 1,
total: max_identity_index + 1,
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

suggestion (convergent: codex-general + codex-rust-quality, confidence: 0.80)

Progress counters can overflow at u32::MAX boundary

IdentityIndex is u32 and comes from user text input parsed at add_existing_identity_screen.rs:700. The expressions identity_index + 1 and max_identity_index + 1 overflow if the user enters 4294967295 — panic in debug, wrapping in release. In practice this is nearly impossible (default is 5), but saturating arithmetic is a trivial fix for correctness.

Suggested change
total: max_identity_index + 1,
message: format!(
"Searching wallet identity index {current} of {total}.",
current = identity_index.saturating_add(1),
total = max_identity_index.saturating_add(1),
),
current: identity_index.saturating_add(1),
total: max_identity_index.saturating_add(1),

}
_ => {}
}
}
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

nitpick (convergent: codex-general + codex-rust-quality, confidence: 0.70)

No regression test covers the non-stacking progress banner path

The core fix — repeated Progress updates updating a banner in-place instead of stacking — has no automated test. This is understandable given the egui test harness limitations, but worth noting for future kittest coverage.

@thepastaclaw
Copy link
Copy Markdown
Collaborator Author

suggestion (convergent: claude-general + claude-rust-quality, confidence: 0.85)

Stale comment and effectively dead else branch in Message handler (add_existing_identity_screen.rs, approx. lines 1044-1049)

The else branch in the BackendTaskSuccessResult::Message handler was the old progress-update handler before Progress existed. The comment says "This is a progress update" but progress now arrives via the Progress variant. The only Message values this screen's own tasks produce start with "Successfully loaded" (load_identity_from_wallet.rs:263, :340), so the else branch only fires if an unrelated background task's Message reaches this screen while it's visible — a routing edge case, not progress.

Consider removing the branch or updating the comment to reflect its actual role as a fallback for unexpected Message variants.

@lklimek lklimek merged commit 2b9bda7 into dashpay:v1.0-dev Mar 20, 2026
4 checks passed
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.

Load identity: load from wallet displays messy messages

3 participants