Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
660a11a
docs(overlay): requirements + UX spec for blocking progress overlay
lklimek Jun 17, 2026
9f4efec
docs(overlay): test case specification
lklimek Jun 17, 2026
ce985e1
docs(overlay): development plan and architecture decisions
lklimek Jun 17, 2026
112c9f8
feat(overlay): generic button facility + Component trait conformance
lklimek Jun 17, 2026
a8964b2
docs(overlay): align D-5 and risk notes to the generic-button redesign
lklimek Jun 17, 2026
ba9a421
test(overlay): add ignored probe proving button-less keyboard-block g…
lklimek Jun 17, 2026
6af742a
fix(overlay): frame-start input claim (QA-001) + clear action queue o…
lklimek Jun 17, 2026
ddc4075
fix(overlay): QA-wave hardening — watchdog, keyed dispatch, secondary…
lklimek Jun 17, 2026
7840703
docs(overlay): sync requirements/dev-plan to the shipped design + add…
lklimek Jun 17, 2026
c51279e
test(overlay): close re-QA coverage residuals RQ-1/RQ-2/RQ-3
lklimek Jun 17, 2026
66d8ec0
docs(overlay): close QA residuals — README catalog entry, requirement…
lklimek Jun 17, 2026
22ff0cb
feat(overlay): hard-block the UI during startup/Connect SPV sync
lklimek Jun 17, 2026
f8851dc
refactor(overlay): align SPV-sync block to the approved spec
lklimek Jun 17, 2026
cc896ea
docs(overlay): reconcile SPV-sync block decision (F-SPV-1) + phase-st…
lklimek Jun 17, 2026
01364a9
fix(overlay): scope SPV block to user-initiated sync + de-jargon copy…
lklimek Jun 17, 2026
3f896e5
Merge branch 'docs/platform-wallet-migration-design' into feat/blocki…
lklimek Jun 17, 2026
b726871
fix(overlay): address review findings — deterministic elapsed test, S…
lklimek Jun 17, 2026
2186429
fix(overlay): close one-frame SPV block gap, fix slow-phase watchdog,…
lklimek Jun 18, 2026
20723d2
feat(overlay): keyboard-reachable escape for the SPV hard block (QA-0…
lklimek Jun 18, 2026
f50cbca
fix(overlay): activate keyboard escape at frame start (SEC-001, SEC-002)
lklimek Jun 18, 2026
4ad2950
docs(overlay): document hidden progress_token watchdog reset; pin cro…
lklimek Jun 18, 2026
d057027
Merge remote-tracking branch 'origin/docs/platform-wallet-migration-d…
lklimek Jun 18, 2026
1f45f51
fix(overlay): keep escape button mouse-clickable after a backdrop press
lklimek Jun 18, 2026
327b603
Merge origin/docs/platform-wallet-migration-design into feat/blocking…
lklimek Jun 18, 2026
8fefb7e
fix(overlay): arm the SPV-sync block on the post-onboarding auto-star…
lklimek Jun 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

1,133 changes: 1,133 additions & 0 deletions docs/ai-design/2026-06-17-blocking-progress-overlay/02-test-spec.md

Large diffs are not rendered by default.

557 changes: 557 additions & 0 deletions docs/ai-design/2026-06-17-blocking-progress-overlay/03-dev-plan.md

Large diffs are not rendered by default.

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions docs/user-stories.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ See [docs/personas/](personas/) for full persona descriptions.
- [Developer and Power Tools (DEV)](#developer-and-power-tools-dev)
- [Network and Settings (NET)](#network-and-settings-net)
- [Programmatic Access (MCP)](#programmatic-access-mcp)
- [User Experience (UX)](#user-experience-ux)

---

Expand Down Expand Up @@ -1121,3 +1122,27 @@ As an AI agent, I want MCP server access so that I can assist users with wallet
- Bearer token authentication for HTTP mode.
- Network verification guard prevents cross-network mistakes.
- Tools expose wallet, identity, and platform operations.

---

## User Experience (UX)

### UX-001: Blocking progress overlay for unsafe-to-interrupt operations [Implemented]
**Persona:** Alex, Priya, Jordan

As a user, while a long operation that is unsafe to interrupt is running (broadcasting a state transition, signing, key import, a multi-step registration, a network migration), I want to see a clear please-wait block over the whole window so that I understand the app is busy and cannot accidentally fire a conflicting second action.

- A full-window dimming overlay with an indeterminate spinner and an optional "Step N of M" counter and description appears while the operation runs, and lowers automatically when it finishes (success or error).
- All interaction beneath the block is suppressed: pointer clicks hit a sink, and keyboard/text input is claimed at frame start so nothing reaches a focused field beneath (FR-8 / QA-001). The block is never dismissable by Esc, Enter, Space, or Tab.
- The block yields to a passphrase prompt: when a secret prompt is shown above the overlay it keeps the keyboard (Enter/Esc/Tab) so the user can still authenticate or cancel (SEC-004).
- Honest escalation, never a fake exit: after 30 s a calm "This is taking longer than usual." line appears; after 120 s with no progress it escalates to "This is taking much longer than expected…" and logs a one-shot developer warning. For these unsafe-to-interrupt operations there is no background/dismiss button — the safety guarantee is that every blocked operation is bounded and always lowers the block through the normal path. _(Exception: the startup/Connect SPV-sync block of UX-002 is unbounded but read-only, so it ships an always-visible "Continue in the background" escape instead.)_

### UX-002: Blocking SPV-sync overlay with a "continue in the background" escape [Implemented]
**Persona:** Alex, Priya, Jordan

As a user, while the app connects to and syncs the Dash chain on startup or after I press Connect, I want a clear please-wait block so I know it is working — and because that sync can wait indefinitely for peers, I want an always-visible "Continue in the background" button so I am never trapped behind it.

- While that startup/Connect sync is getting connected, a full-window block appears with a plain please-wait sentence ("Connecting to the Dash network." / "Syncing with the Dash network.") and a friendly progress indicator ("Step N of 5") — no blockchain jargon, raw heights, or percentages.
- The block always offers a secondary "Continue in the background" button. Clicking it lowers the block; sync keeps running in the background (it is read-only and strands nothing), and the block is not re-raised for the rest of that sync episode.
- The block is scoped to *user-initiated* sync (startup auto-start / Connect): it lowers on its own when the chain becomes usable (Synced) or fails (Error), and an **ambient** reconnect or per-block catch-up afterward does not block a working user. Pressing Connect (or a fresh startup) blocks again.
- This is the overlay's first real adopter (PR #863). Unlike the unsafe-to-interrupt operations in UX-001, SPV sync is **unbounded but safe to background** — so its C2 "never trap the user" guarantee is met by the always-on escape, not by operation boundedness.
391 changes: 388 additions & 3 deletions src/app.rs

Large diffs are not rendered by default.

48 changes: 48 additions & 0 deletions src/context/connection_status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,15 @@ impl ConnectionStatus {
self.overall_state.load(Ordering::Relaxed).into()
}

/// Test seam: force the overall connection state directly. Bypasses
/// [`Self::refresh_state`] so a test that drives a consumer in isolation (not
/// the throttled frame loop) sees a stable state. Compiled only under the
/// `testing` feature.
#[cfg(feature = "testing")]
pub fn set_overall_state(&self, state: OverallConnectionState) {
self.overall_state.store(state as u8, Ordering::Relaxed);
}

/// Recompute the overall connection state from the individual subsystem
/// flags.
///
Expand Down Expand Up @@ -631,6 +640,30 @@ pub fn spv_phase_summary(progress: &SpvSyncProgress) -> String {
"syncing...".to_string()
}

/// Map the currently-active SPV sync phase to a 1-based step number for the
/// blocking overlay's "Step N of 5" counter — Headers=1, Masternodes=2,
/// Filter Headers=3, Filters=4, Blocks=5 — or `None` when no phase is actively
/// syncing yet. Mirrors the pipeline order of [`spv_phase_summary`].
pub fn spv_phase_step(progress: &SpvSyncProgress) -> Option<u32> {
let is_syncing = |state: SyncState| state == SyncState::Syncing;
if progress.headers().is_ok_and(|p| is_syncing(p.state())) {
Some(1)
} else if progress.masternodes().is_ok_and(|p| is_syncing(p.state())) {
Some(2)
} else if progress
.filter_headers()
.is_ok_and(|p| is_syncing(p.state()))
{
Some(3)
} else if progress.filters().is_ok_and(|p| is_syncing(p.state())) {
Some(4)
} else if progress.blocks().is_ok_and(|p| is_syncing(p.state())) {
Some(5)
} else {
None
}
}

fn pct(current: u32, target: u32) -> u32 {
if target == 0 {
0
Expand Down Expand Up @@ -677,6 +710,21 @@ mod tests {
assert!(status.spv_sync_progress().is_none());
}

/// F-SPV-2 — the blocking overlay's "Step N of 5" counter maps to the active
/// phase, and the summary reflects the live headers phase.
#[test]
fn spv_phase_step_and_summary_track_active_phase() {
let progress = syncing_progress();
assert_eq!(spv_phase_step(&progress), Some(1), "headers phase → step 1");
assert!(
spv_phase_summary(&progress).starts_with("Headers: 5000 / 10000"),
"summary reflects the live headers phase"
);

// No phase actively syncing → no step (the overlay shows the generic line).
assert_eq!(spv_phase_step(&SpvSyncProgress::default()), None);
}

#[test]
fn spv_status_snapshot_reflects_live_state() {
let status = ConnectionStatus::new();
Expand Down
1 change: 1 addition & 0 deletions src/ui/components/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Concise catalog of all reusable UI components. Consult before creating new UI el
| Component | File | Description |
|-----------|------|-------------|
| `MessageBanner` | `message_banner.rs` | Global error/warning/success/info banners. `set_global()`, `with_details()`, auto-dismiss. Extensions: `OptionBannerExt`, `OptionBannerShowExt`, `ResultBannerExt` |
| `ProgressOverlay` | `progress_overlay.rs` | Full-screen blocking progress overlay: spinner, optional step counter, generic buttons (`with_button(id, label)`), 120s watchdog. Global path: `show_global()` / `render_global()`, claims input each frame. Companions: `OverlayConfig`, `OverlayHandle`, `OptionOverlayExt`, `ProgressOverlayResponse` |

## Styled Components (`styled.rs`)

Expand Down
4 changes: 4 additions & 0 deletions src/ui/components/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub mod left_wallet_panel;
pub mod message_banner;
pub mod passphrase_modal;
pub mod password_input;
pub mod progress_overlay;
pub mod secret_prompt_host;
pub mod selection_dialog;
pub mod styled;
Expand All @@ -27,4 +28,7 @@ pub use message_banner::{
BannerHandle, BannerStatus, MessageBanner, MessageBannerResponse, OptionBannerExt,
OptionBannerShowExt, ResultBannerExt,
};
pub use progress_overlay::{
OptionOverlayExt, OverlayConfig, OverlayHandle, ProgressOverlay, ProgressOverlayResponse,
};
pub use secret_prompt_host::{ActivePrompt, EguiSecretPromptHost, QueuedPrompt};
5 changes: 5 additions & 0 deletions src/ui/components/passphrase_modal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ pub fn passphrase_modal(
let window_response = egui::Window::new(config.window_title)
.collapsible(false)
.resizable(false)
// Render on Order::Foreground so the prompt stays above the blocking
// progress overlay (also Foreground, but drawn earlier this frame) — the
// overlay must never cover a secret prompt it triggered (R-1, SEC-002).
// Created after the overlay and focus-raised, so it wins within Foreground.
.order(egui::Order::Foreground)
.anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
.open(&mut window_is_open)
.frame(egui::Frame {
Expand Down
Loading
Loading