Skip to content

fix(ui): clear startup transient error banners once network/migration is ready#916

Merged
lklimek merged 2 commits into
v1.0-devfrom
fix/startup-transient-error-banners
Jul 21, 2026
Merged

fix(ui): clear startup transient error banners once network/migration is ready#916
lklimek merged 2 commits into
v1.0-devfrom
fix/startup-transient-error-banners

Conversation

@Claudius-Maginificent

@Claudius-Maginificent Claudius-Maginificent commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

TL;DR: Keep the "storage still updating" banner visible until storage is genuinely free again, instead of letting it disappear about a frame too early.

User story

As a user who just finished the app's first-run migration, I want the "storage update is still running" message to stay up until storage is truly available, so a retried action that fails during that window isn't left unexplained.

Scenario

Base flow

The app finishes cold-start migration; the user immediately retries a wallet-touching action.

Actual behavior

Two error banners could persist indefinitely during app cold start ("storage update is still running" and a masternode-list-not-yet-synced error) — even minutes after the underlying condition fully resolved. The initial fix for that made the storage banner reactive, but it could still clear about one frame before storage was actually free: a background best-effort node-list refresh kept holding the lock past the point the migration state reported "ready." A retry in that narrow window failed again with the banner already gone, looking like an unexplained glitch.

Expected behavior

Both banners clear reactively once their condition is genuinely resolved, and the storage banner specifically stays up until storage is truly free — so a failed retry in that window has a visible, honest explanation instead of none.

Detailed discussion

What was done

  • Added a dedicated TaskError::MasternodeListNotReady variant with a clean, actionable message instead of falling through to the generic, opaque SdkError text.
  • ConnectionBanner/MigrationReconciler (app/reconcilers.rs) now adopt these banner handles and clear them reactively once their readiness condition genuinely holds, instead of leaving them disable_auto_dismiss()'d forever.
  • Review fix: storage_guard_resolved was computed from MigrationState alone, so it could flip true (and clear the banner) before the migration_run mutex the error actually reports on was released — a detached best-effort DAPI-node refresh keeps holding it past MigrationState going Ready. Gated the clear on both the terminal state AND a non-blocking migration_run.try_lock() probe.
  • Extracted the repeated adopt/reset/conditional-clear banner-handle pattern (shared by ConnectionBanner and MigrationReconciler) into a small TransientBanner newtype — same behavior, less duplication.
  • Hardened the SDK error-string sentinel test to pin the literal upstream string shape (instead of round-tripping the same constant through itself), and cross-referenced the producer/consumer sites of that string so the coupling between context_provider.rs and backend_task/error.rs is discoverable.
  • Addresses claudius:grumpy-review findings CODE-001 (blocking), CODE-003 and PROJ-001 (non-blocking). CODE-002 (a pre-existing FIFO-eviction edge case in the "stuck offline forever" scenario) is a known, accepted follow-up — not fixed here.

Testing

  • New/updated reconciler + error-mapping tests (banner-clears-when-ready, plus the new guard/lock-aware clearing) — all green via the narrowest relevant --lib scope.
  • cargo clippy --bin dash-evo-tool --all-features -- -D warnings clean, cargo fmt --all clean.
  • Independently re-verified by qa-engineer-marvin during grumpy-review consolidation.

Breaking changes

None.

Checklist

  • Tests added (TDD)
  • User-facing strings are plain, complete sentences
  • No secrets in diff

Prior work

Attribution

🤖 Co-authored by Claudius the Magnificent AI Agent

Transfer startup-only error banner handles to the connection and migration
reconcilers so readiness transitions clear them reactively.

<sub>🤖 Co-authored by [Claudius the Magnificent](https://github.com/lklimek/claudius) AI Agent</sub>

Co-Authored-By: Codex GPT-5 <noreply@openai.com>
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change classifies masternode-list startup failures, routes storage and quorum readiness errors to dedicated banner trackers, and clears those banners when migration or masternode readiness is restored.

Changes

Startup readiness handling

Layer / File(s) Summary
Quorum readiness error classification
src/context_provider.rs, src/backend_task/error.rs
A shared SDK diagnostic identifies masternode-list startup failures and maps them to TaskError::MasternodeListNotReady, with unit-test coverage.
Startup banner tracking and clearing
src/app.rs, src/app/reconcilers.rs
AppState tracks storage and quorum startup banners through the appropriate reconcilers, which clear them after readiness or migration resolution. Tests cover both clearing paths.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SpvProvider
  participant TaskError
  participant AppStateUI
  participant ConnectionBanner
  participant MigrationReconciler
  SpvProvider->>TaskError: emit masternode-list-not-ready diagnostic
  TaskError->>AppStateUI: classify as MasternodeListNotReady
  AppStateUI->>ConnectionBanner: track quorum startup banner
  AppStateUI->>MigrationReconciler: track storage startup banner
  ConnectionBanner->>ConnectionBanner: clear when masternodes become ready
  MigrationReconciler->>MigrationReconciler: clear when storage migration resolves
Loading

Suggested labels: claudius-review

Suggested reviewers: lklimek

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main fix: clearing startup error banners when network or migration readiness is reached.
✨ 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 fix/startup-transient-error-banners

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.

@lklimek
lklimek marked this pull request as ready for review July 21, 2026 10:11
@thepastaclaw

thepastaclaw commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

🕓 Ready for review — 39 ahead in queue (commit 841b70d)
Queue position: 40/40

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 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/app/reconcilers.rs`:
- Around line 517-525: The storage_guard_resolved check in the migration state
handling must exclude transient backend-not-ready failures from being considered
resolved. Update the logic around MigrationState::Failed and
error.is_backend_not_ready() so the storage_startup_error banner is retained
while automatic retry resets the migration state to Idle; continue clearing it
for genuinely resolved states and non-transient failures.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a87a1a98-7a1b-48d4-a361-5c27cdf963c0

📥 Commits

Reviewing files that changed from the base of the PR and between 0812761 and 9ac720a.

📒 Files selected for processing (4)
  • src/app.rs
  • src/app/reconcilers.rs
  • src/backend_task/error.rs
  • src/context_provider.rs

Comment thread src/app/reconcilers.rs Outdated
Comment on lines +517 to +525
let storage_guard_resolved = !matches!(
&state,
MigrationState::Idle
| MigrationState::Running { .. }
| MigrationState::AwaitingWalletPasswords { .. }
);
if storage_guard_resolved && let Some(handle) = self.storage_startup_error.take() {
handle.clear();
}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Prevent premature banner clearance during transient migration failures.

If the migration fails transiently due to a slow-wiring backend (error.is_backend_not_ready()), the frame state evaluates to MigrationState::Failed. This satisfies storage_guard_resolved, prematurely clearing the user's active startup error banner. Immediately after, the failure match-arm below resets the migration status back to Idle for a seamless background retry, but the banner is already lost.

To ensure the "storage not ready" warning remains visible to the user while the automatic retry happens, storage_guard_resolved should not treat transient failures as resolved.

🐛 Proposed fix
-        let storage_guard_resolved = !matches!(
-            &state,
-            MigrationState::Idle
-                | MigrationState::Running { .. }
-                | MigrationState::AwaitingWalletPasswords { .. }
-        );
+        let is_transient_failure = match &state {
+            MigrationState::Failed { error }
+            | MigrationState::FailedWithUnreadableIdentities { error, .. } => {
+                error.is_backend_not_ready()
+            }
+            _ => false,
+        };
+        let storage_guard_resolved = !is_transient_failure
+            && !matches!(
+                &state,
+                MigrationState::Idle
+                    | MigrationState::Running { .. }
+                    | MigrationState::AwaitingWalletPasswords { .. }
+            );
         if storage_guard_resolved && let Some(handle) = self.storage_startup_error.take() {
             handle.clear();
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let storage_guard_resolved = !matches!(
&state,
MigrationState::Idle
| MigrationState::Running { .. }
| MigrationState::AwaitingWalletPasswords { .. }
);
if storage_guard_resolved && let Some(handle) = self.storage_startup_error.take() {
handle.clear();
}
let is_transient_failure = match &state {
MigrationState::Failed { error }
| MigrationState::FailedWithUnreadableIdentities { error, .. } => {
error.is_backend_not_ready()
}
_ => false,
};
let storage_guard_resolved = !is_transient_failure
&& !matches!(
&state,
MigrationState::Idle
| MigrationState::Running { .. }
| MigrationState::AwaitingWalletPasswords { .. }
);
if storage_guard_resolved && let Some(handle) = self.storage_startup_error.take() {
handle.clear();
}
🤖 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/app/reconcilers.rs` around lines 517 - 525, The storage_guard_resolved
check in the migration state handling must exclude transient backend-not-ready
failures from being considered resolved. Update the logic around
MigrationState::Failed and error.is_backend_not_ready() so the
storage_startup_error banner is retained while automatic retry resets the
migration state to Idle; continue clearing it for genuinely resolved states and
non-transient failures.

… reconciler banner tracking

The reactive storage-error banner cleared as soon as MigrationState went
terminal, but the detached best-effort DAPI-node refresh can still hold
migration_run for a real busy window after that point — a retry in that
window failed silently with the banner already gone. Gate the clear on
both the terminal state AND a non-blocking migration_run.try_lock() probe.

Also extracts the repeated adopt/reset/conditional-clear banner-handle
pattern shared by ConnectionBanner and MigrationReconciler into a small
TransientBanner newtype, and hardens the SDK error-string sentinel test
to pin the literal upstream string shape instead of round-tripping the
same constant through itself.

Addresses grumpy-review findings CODE-001, CODE-003, PROJ-001.

Co-Authored-By: Codex Sol <noreply@openai.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@lklimek
lklimek merged commit fc27fe6 into v1.0-dev Jul 21, 2026
4 of 5 checks passed
@lklimek
lklimek deleted the fix/startup-transient-error-banners branch July 21, 2026 12:52
orchardpaytl pushed a commit to orchardpaytl/orchardpay that referenced this pull request Jul 21, 2026
Brings in: shutdown fix (dashpay#905), duplicate-DPNS-name error message
(dashpay#915), startup banner clearing (dashpay#916), nav pointer cursor + tooltips
and wallet-less masternode indication (dashpay#917), onboarding disconnected-
banner suppression (dashpay#907), masternode dialog/nav/passphrase fixes
(dashpay#913), DAPI auto-refresh during pre-1.0 migration (dashpay#908), "Add
Receiving Address" wiring + its test hardening (dashpay#914, dashpay#920), and a CI
timeout bump (dashpay#912). dashpay#906 (shielded re-enable) was already pulled in
individually last session, so its squashed commit merged as a no-op.

Conflicts (6 files) were rebrand-naming overlaps (dash_evo_tool::  vs
orchardpay::  imports) plus one real merge in left_panel.rs, where
OrchardPay's green-icon tint had to combine with upstream's new nav
tooltip. Also fixed 5 files upstream's auto-merged (non-conflicting)
additions left un-rebranded: a stray DASH_EVO_DATA_DIR_LOCK/env-var
name in a new app.rs test, and dash_evo_tool:: references in three
kittest test files.

Added tooltip strings for OrchardPay's own nav entries (OrchardPay,
DashPay) so the new every_nav_entry_has_a_tooltip test covers them —
upstream's version only knows its own nav items. Fixed the new
nav_label_hover_shows_pointer_cursor kittest test: OrchardPay's nav
rail carries two more always-visible entries than upstream's, pushing
"Settings" below the scrollable list's default-size visible viewport;
scroll it into view first, matching what a real user would do.

Verified: cargo check (both feature modes), cargo clippy --all-features
--all-targets -- -D warnings, cargo fmt --all, cargo test --all-features
--workspace (2066 lib + 257 kittest + doc tests, 0 failed), all clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.

3 participants