Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 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
bca2c92
feat(overlay): adopt blocking overlay for DPNS registration (Bucket A…
lklimek Jun 18, 2026
f4594d1
chore: merge docs/platform-wallet-migration-design (#860, incl. overl…
lklimek Jun 19, 2026
6c32f5d
fix(overlay): restore SEC-003 watchdog TODO comment dropped by the ba…
lklimek Jun 19, 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
1 change: 1 addition & 0 deletions docs/user-stories.md
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,7 @@ As a user, I want to register a human-readable username on DPNS so that others c

- Choose identity, enter desired name.
- Cost estimate displayed before confirmation.
- While registration runs, a full-window blocking overlay (UX-001) is shown so the same name cannot be submitted twice; it lowers automatically on success or error.

### DPN-002: View owned usernames [Implemented]
**Persona:** Alex, Priya
Expand Down
56 changes: 46 additions & 10 deletions src/ui/identities/register_dpns_name_screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ use crate::ui::components::top_panel::add_top_panel;
use crate::ui::components::wallet_unlock_popup::{
WalletUnlockPopup, WalletUnlockResult, try_open_wallet_no_password, wallet_needs_unlock,
};
use crate::ui::components::{BannerHandle, MessageBanner, OptionBannerExt, ResultBannerExt};
use crate::ui::components::{
MessageBanner, OptionOverlayExt, OverlayConfig, OverlayHandle, ResultBannerExt,
};
use crate::ui::helpers::{TransactionType, add_key_chooser_with_doc_type};
use crate::ui::theme::{DashColors, ResponseExt};
use crate::ui::{MessageType, ScreenLike};
Expand Down Expand Up @@ -61,7 +63,11 @@ pub struct RegisterDpnsNameScreen {
completed_fee_result: Option<FeeResult>,
// Source of navigation to this screen
pub source: RegisterDpnsNameSource,
refresh_banner: Option<BannerHandle>,
/// Bucket A overlay-adoption pattern: a button-less full-window block raised
/// when the bounded registration is dispatched and torn down on every
/// terminal result. It replaces the old progress banner and, by blocking the
/// whole window, closes the double-submit hole the banner left open.
op_overlay: Option<OverlayHandle>,
}

impl RegisterDpnsNameScreen {
Expand Down Expand Up @@ -124,7 +130,7 @@ impl RegisterDpnsNameScreen {
show_advanced_options: false,
completed_fee_result: None,
source,
refresh_banner: None,
op_overlay: None,
}
}

Expand Down Expand Up @@ -270,6 +276,37 @@ impl RegisterDpnsNameScreen {
)))
}

/// Dispatch the registration and raise the Bucket A blocking overlay.
///
/// The overlay is raised only when a real task is produced (an identity and a
/// signing key are selected), so a no-op click never strands a block. The
/// full-window block is the in-progress feedback that replaces the old banner
/// and prevents a second submit while the first is in flight.
fn begin_registration(&mut self, ctx: &Context) -> AppAction {
let action = self.register_dpns_name_clicked();
if matches!(action, AppAction::BackendTask(_)) {
self.register_dpns_name_status = RegisterDpnsNameStatus::WaitingForResult;
self.raise_progress_overlay(ctx);
}
action
}

fn raise_progress_overlay(&mut self, ctx: &Context) {
self.op_overlay.raise(
ctx,
"Registering your username on the network.",
OverlayConfig::default(),
);
}

/// Test seam: run the exact production overlay-raise the Register button uses,
/// so the Bucket A adoption (raise + guaranteed teardown) is exercisable in
/// kittests without funding an identity. Mirrors `force_input_for_test`.
#[doc(hidden)]
pub fn raise_progress_overlay_for_test(&mut self, ctx: &Context) {
self.raise_progress_overlay(ctx);
}

pub fn show_success(&mut self, ui: &mut Ui) -> AppAction {
let action = crate::ui::helpers::show_success_screen_with_info(
ui,
Expand Down Expand Up @@ -301,17 +338,20 @@ impl RegisterDpnsNameScreen {
impl ScreenLike for RegisterDpnsNameScreen {
fn display_message(&mut self, _message: &str, message_type: MessageType) {
// Banner display is handled globally by AppState; this is only for side-effects.
// SEC-001: tear down the blocking overlay on the error terminal path so a
// failed registration can never hard-lock the window.
if matches!(message_type, MessageType::Error | MessageType::Warning) {
self.refresh_banner.take_and_clear();
self.op_overlay.take_and_clear();
self.register_dpns_name_status = RegisterDpnsNameStatus::Error;
}
}

fn display_task_result(&mut self, backend_task_success_result: BackendTaskSuccessResult) {
// SEC-001: tear down the blocking overlay on the success terminal path.
if let BackendTaskSuccessResult::RegisteredDpnsName(fee_result) =
backend_task_success_result
{
self.refresh_banner.take_and_clear();
self.op_overlay.take_and_clear();
self.completed_fee_result = Some(fee_result);
self.register_dpns_name_status = RegisterDpnsNameStatus::Complete;
}
Expand Down Expand Up @@ -551,11 +591,7 @@ impl ScreenLike for RegisterDpnsNameScreen {
.disabled_tooltip(&hover_text)
.clicked()
{
self.register_dpns_name_status = RegisterDpnsNameStatus::WaitingForResult;
let handle = MessageBanner::set_global(ui.ctx(), "Registering DPNS name...", MessageType::Info);
handle.with_elapsed();
self.refresh_banner = Some(handle);
inner_action = self.register_dpns_name_clicked();
inner_action = self.begin_registration(ui.ctx());
}

ui.add_space(10.0);
Expand Down
2 changes: 1 addition & 1 deletion src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ pub mod contracts_documents;
pub mod dashpay;
pub mod dpns;
pub mod helpers;
pub(crate) mod identities;
pub mod identities;
pub mod network_chooser_screen;
pub mod state;
Comment on lines 86 to 90
pub mod theme;
Expand Down
1 change: 1 addition & 0 deletions tests/kittest/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod message_banner;
mod migration_banner;
mod network_chooser;
mod progress_overlay;
mod register_dpns_name_screen;
mod restore_single_key;
mod secret_prompt;
mod startup;
Expand Down
93 changes: 93 additions & 0 deletions tests/kittest/register_dpns_name_screen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//! Kittest coverage for the Bucket A overlay adoption on the DPNS registration
//! screen (`RegisterDpnsNameScreen`).
//!
//! Proves the canonical contract the remaining transaction screens will copy:
//! dispatching the registration raises the global blocking overlay, and every
//! terminal result — success (`display_task_result`) or error
//! (`display_message`) — tears it down (the SEC-001 no-hard-lock guarantee).
//!
//! The screen needs an `Arc<AppContext>`, so each test borrows one from a
//! throwaway `AppState` built in an isolated data dir. The overlay raise/teardown
//! runs against an independent `egui::Context` the AppState never renders, so the
//! app's own SPV block can't perturb the `has_global` assertions.

use crate::support::with_isolated_data_dir;
use dash_evo_tool::app::AppState;
use dash_evo_tool::backend_task::{BackendTaskSuccessResult, FeeResult};
use dash_evo_tool::ui::MessageType;
use dash_evo_tool::ui::ScreenLike;
use dash_evo_tool::ui::components::ProgressOverlay;
use dash_evo_tool::ui::identities::register_dpns_name_screen::{
RegisterDpnsNameScreen, RegisterDpnsNameSource,
};

/// Build a `RegisterDpnsNameScreen` over a fresh, isolated `AppContext`.
fn screen_with_context() -> RegisterDpnsNameScreen {
let app_state = AppState::new(egui::Context::default()).expect("AppState builds");
let app_context = app_state.current_app_context().clone();
RegisterDpnsNameScreen::new(&app_context, RegisterDpnsNameSource::Dpns)
}

/// Dispatching the registration raises the global blocking overlay.
#[test]
fn dpns_dispatch_raises_blocking_overlay() {
with_isolated_data_dir(|| {
let rt = tokio::runtime::Runtime::new().expect("tokio runtime");
let _guard = rt.enter();

let mut screen = screen_with_context();
let ctx = egui::Context::default();

assert!(!ProgressOverlay::has_global(&ctx));
screen.raise_progress_overlay_for_test(&ctx);
assert!(
ProgressOverlay::has_global(&ctx),
"dispatching the registration must raise the blocking overlay"
);
});
}

/// A successful registration result tears the overlay down (success terminal path).
#[test]
fn dpns_success_result_clears_overlay() {
with_isolated_data_dir(|| {
let rt = tokio::runtime::Runtime::new().expect("tokio runtime");
let _guard = rt.enter();

let mut screen = screen_with_context();
let ctx = egui::Context::default();

screen.raise_progress_overlay_for_test(&ctx);
assert!(ProgressOverlay::has_global(&ctx));

screen.display_task_result(BackendTaskSuccessResult::RegisteredDpnsName(
FeeResult::new(0, 0),
));
assert!(
!ProgressOverlay::has_global(&ctx),
"a successful result must tear down the blocking overlay"
);
});
}

/// An error message tears the overlay down (error terminal path) — SEC-001: a
/// failed registration can never leave the window hard-locked.
#[test]
fn dpns_error_message_clears_overlay() {
with_isolated_data_dir(|| {
let rt = tokio::runtime::Runtime::new().expect("tokio runtime");
let _guard = rt.enter();

let mut screen = screen_with_context();
let ctx = egui::Context::default();

screen.raise_progress_overlay_for_test(&ctx);
assert!(ProgressOverlay::has_global(&ctx));

screen.display_message("Registration failed. Try again.", MessageType::Error);
assert!(
!ProgressOverlay::has_global(&ctx),
"an error result must tear down the blocking overlay"
);
});
}
Loading