feat(ui): redesign wallet screen information architecture - #791
Conversation
…627) During SPV reconciliation, per_address_sum only contains addresses with current UTXOs. Addresses whose funds were fully spent never had their balance reset to zero, causing the address table to display stale non-zero balances even though UTXO count correctly showed 0. Now explicitly zeroes address_balances for any known address absent from the refreshed UTXO map before applying current sums. Closes #571 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…613) * fix: handle malformed YAML gracefully in load_testnet_nodes_from_yml Replace .expect() with match expression to avoid panicking when .testnet_nodes.yml contains malformed YAML. Instead, logs the error with tracing::error and returns None, allowing the application to continue without crashing. Closes #557 Co-authored-by: lklimek <lklimek@users.noreply.github.com> * fix: propagate YAML parse errors to UI and remove unwrap calls - Change load_testnet_nodes_from_yml to return Result<Option<TestnetNodes>, String> so parse errors display in the UI error banner instead of only logging - Use explicit type annotation on serde_yaml_ng::from_str::<TestnetNodes> - Replace .unwrap() with .and_then() in fill_random_hpmn/fill_random_masternode Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: lklimek <lklimek@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* chore: move doc/ contents into docs/ and update references Consolidate documentation under a single docs/ directory. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: CLAUDE.md should write manual test scenarios for PRs --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…tion (#603) * build(flatpak): use only-arches for dynamic protoc architecture selection Replace the fragile sed-based CI patching of the Flatpak manifest with Flatpak's native `only-arches` source selector. The protoc module now declares both x86_64 and aarch64 sources inline, and build-commands use a glob pattern (`protoc-*.zip`) so no per-arch fixup is needed. Changes: - flatpak manifest: add aarch64 protoc source with `only-arches`, use glob in unzip commands, remove stale CI-patching comment - CI workflow: remove `protoc-zip`/`protoc-sha256` matrix variables and the "Patch manifest for architecture" step https://claude.ai/code/session_015AD2pCWoJdV2VDydcqFHPG * fix(flatpak): use dest-filename for deterministic protoc extraction Use dest-filename to normalize both arch-specific protoc zips to a common name, avoiding glob expansion in build-commands. This ensures the unzip target is deterministic regardless of shell behavior in the Flatpak sandbox. https://claude.ai/code/session_015AD2pCWoJdV2VDydcqFHPG * build: update platform to b445b6f0 and remove rust-dashcore patches Update dashpay/platform dependency from d6f4eb9a to b445b6f0e0bd4863 (3.0.1 → 3.1.0-dev.1). Remove the [patch] section that pinned rust-dashcore crates to a separate rev, as the new platform commit resolves them correctly on its own. https://claude.ai/code/session_015AD2pCWoJdV2VDydcqFHPG --------- Co-authored-by: Claude <noreply@anthropic.com>
Resolve conflicts preserving shielded pool features from HEAD while adopting v1.0-dev improvements: refactored asset lock fee calculation, mine dialog hardening, network validation, and API signature updates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…xtract/all-merged
The [patch] section referenced ../platform local paths that don't exist in CI. Since dash-sdk already depends on the feat/zk branch, all transitive platform crates resolve correctly without patches. Also fixes a formatting issue in address_table.rs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The remove_utxos tests were failing because `register_test_address` inserts into `wallet_addresses` which has a foreign key constraint on `wallet(seed_hash)`. Added the missing `store_wallet` call so the parent row exists before inserting child address rows. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rged # Conflicts: # Cargo.lock # Cargo.toml # src/backend_task/core/mod.rs # src/backend_task/wallet/fetch_platform_address_balances.rs # src/context/wallet_lifecycle.rs # src/database/wallet.rs # src/model/wallet/mod.rs # src/ui/wallets/wallets_screen/mod.rs
The `pub mod shielded;` declaration was accidentally removed from `src/model/wallet/mod.rs` during the v1.0-dev merge, causing build failures since the shielded.rs file still exists. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Initial plan * fix(test): restore store_wallet calls lost in merge Co-authored-by: lklimek <842586+lklimek@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lklimek <842586+lklimek@users.noreply.github.com>
…rged Resolve conflicts in Cargo.toml (keep feat/zk branch), Cargo.lock (regenerate with pinned platform rev 4d7b9be5), and backend_task/mod.rs (combine TaskError wrapping with ShieldedTask). Fix post-merge integration issues: - SPV manager: remove stale .await on subscribe methods, add command_receiver channel for updated DashSpvClient::run() API - send_screen: update SendStatus::WaitingForResult to unit variant - network_chooser_screen: handle new SyncState::Initializing variant Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rged # Conflicts: # .github/workflows/clippy.yml # .github/workflows/tests.yml # CLAUDE.md # Cargo.lock # Cargo.toml # src/app.rs # src/backend_task/core/mod.rs # src/backend_task/error.rs # src/backend_task/mod.rs # src/context/mod.rs # src/database/initialization.rs # src/spv/manager.rs # src/ui/components/top_panel.rs # src/ui/components/wallet_unlock.rs # src/ui/contracts_documents/add_contracts_screen.rs # src/ui/contracts_documents/document_action_screen.rs # src/ui/contracts_documents/group_actions_screen.rs # src/ui/contracts_documents/register_contract_screen.rs # src/ui/contracts_documents/update_contract_screen.rs # src/ui/dashpay/contact_requests.rs # src/ui/dashpay/profile_screen.rs # src/ui/dashpay/profile_search.rs # src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs # src/ui/identities/identities_screen.rs # src/ui/identities/keys/key_info_screen.rs # src/ui/mod.rs # src/ui/tokens/destroy_frozen_funds_screen.rs # src/ui/tokens/direct_token_purchase_screen.rs # src/ui/tokens/freeze_tokens_screen.rs # src/ui/tokens/mint_tokens_screen.rs # src/ui/tokens/pause_tokens_screen.rs # src/ui/tokens/resume_tokens_screen.rs # src/ui/tokens/set_token_price_screen.rs # src/ui/tokens/tokens_screen/my_tokens.rs # src/ui/tokens/unfreeze_tokens_screen.rs # src/ui/tokens/update_token_config.rs # src/ui/tools/masternode_list_diff_screen.rs # src/ui/wallets/asset_lock_detail_screen.rs # src/ui/wallets/create_asset_lock_screen.rs # src/ui/wallets/single_key_send_screen.rs # src/ui/wallets/wallets_screen/mod.rs
Migrate from feat/zk to 3.1-dev branch of dashpay/platform, adapting to breaking API changes in dash-spv, key-wallet, and dpp: - FeeLevel removed; use FeeRate::normal() directly - DashSpvClientInterface/Command removed; use DashSpvClient directly - SyncState::Initializing removed; replaced with WaitForEvents - NetworkExt trait inlined into Network impl - OrchardProver now requires wrapper struct around ProvingKey - OrchardAddress::from_raw_bytes now returns Result - Builder functions gain fee/platform_version params - NullifierSyncConfig API uses NullifierSyncCheckpoint - WalletManager.create_unsigned_payment_transaction removed; use TransactionBuilder directly - Work around Send lifetime issues with spawn_blocking Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…kError Replace all Result<T, String> error patterns in the shielded pool module with typed TaskError variants, aligning with the codebase-wide typed error migration (PR #739). New TaskError variants: ShieldedNoUnspentNotes, ShieldedInsufficientBalance, PlatformAddressNotFound, ShieldedMerkleWitnessUnavailable, ShieldedTransitionBuildFailed, ShieldedBroadcastFailed, ShieldedInvalidRecipientAddress, ShieldedAssetLockTimeout, ShieldedSyncFailed, ShieldedTreeUpdateFailed, ShieldedNullifierSyncFailed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merge origin/zk branch which adapts to dashpay/platform v3.1-dev API: - OrchardProver trait wrapper (CachedProver) replaces raw ProvingKey refs - OrchardAddress::from_raw_bytes now returns Result - FeeLevel removed; use FeeRate::normal() directly - WalletManager.create_unsigned_payment_transaction removed; use TransactionBuilder - SyncState::Initializing removed; replaced with WaitForEvents - SpvClient ownership model changed (no longer Arc-wrapped) Resolved conflicts preserving typed TaskError variants from our branch while adopting origin/zk's v3.1-dev API adaptations. Fixed branch name typo (3.1-dev → v3.1-dev) in Cargo.toml. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
backport: merge v1.0-dev (including extracted non-ZK fixes) back into zk
Reserve width for Save and Auto Update buttons so the password input doesn't consume all available space, pushing buttons off-screen. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nto zk-fixes # Conflicts: # Cargo.lock # Cargo.toml # src/app.rs # src/backend_task/core/mod.rs # src/backend_task/error.rs # src/database/initialization.rs # src/spv/manager.rs # src/ui/wallets/wallets_screen/mod.rs
…how details Add CoreRpcConnectionFailed variant to TaskError that includes the configured address in the user-facing message. Connection-refused errors are now detected via is_rpc_connection_error() and enriched with host:port at every RPC call site where the URL is known (AppContext::rpc_error_with_url helper). Details panel is now shown for all RPC-related errors regardless of developer mode, so users can always see the technical information they need to diagnose connectivity issues. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…gtest The Network Chooser screen had three bugs related to RPC password handling: 1. Password input was initialized from Regtest config only, ignoring the current network selection. 2. Password UI was hidden for all networks except Regtest, even though Mainnet/Testnet/Devnet also use RPC mode. 3. Save logic was hardcoded to update Regtest config and triggered a SwitchNetwork(Regtest) action, which disconnected the active network's ZMQ listener unnecessarily. Now the password input shows for any network in RPC mode, reads/writes the correct network config, reinits the RPC client in-place without triggering a network switch, and reloads the stored password when the user switches network tabs. The "Auto Update" (dashmate) button remains Regtest-only since dashmate is only relevant for local networks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ummary The label field was constructed but never read outside the builder. is_key_only() had no callers. Remove both instead of suppressing dead_code warnings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract only the category data (clone of AccountCategory + index) before the match instead of cloning the entire enum. This avoids unnecessary allocation for the Shielded and System variants. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
invalidate_address_inputs() cleared the AddressInput widget but left the validated_address stale, which could reference an address from the previous network. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…CII precondition Move truncate_address from address_input.rs and shielded_tab.rs into src/model/address.rs as a parameterized public function. Both callers now delegate to the shared implementation with their own prefix/suffix lengths. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ueue_shielded_sync The spawn_blocking(block_on(...)) pattern is required because async methods on Arc<Self> produce non-'static futures due to a known Rust compiler limitation (rust-lang/rust#100013). Document this to prevent future removal attempts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (4)
src/model/address.rs (1)
103-105:⚠️ Potential issue | 🟠 MajorAdd
9to the Core-prefix whitelist.Dash testnet P2SH addresses can also start with
9. Those inputs currently go through the identity-first branch, so this disambiguation is still wrong for one valid Core prefix.Prefix fix
let core_prefix = matches!( trimmed.as_bytes().first(), - Some(b'X' | b'Y' | b'y' | b'8' | b'7') + Some(b'X' | b'Y' | b'y' | b'8' | b'9' | b'7') );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/model/address.rs` around lines 103 - 105, The core_prefix check in address parsing (the matches! on trimmed.as_bytes().first() used to set core_prefix) omits '9', causing valid Dash testnet P2SH addresses to be misclassified; update that matches! pattern to include b'9' alongside b'X'|b'Y'|b'y'|b'8'|b'7' so trimmed (and the core_prefix boolean) correctly recognizes '9' as a Core prefix in the logic that governs identity-first vs core address parsing.src/ui/wallets/wallets_screen/mod.rs (3)
1798-1818:⚠️ Potential issue | 🟡 MinorRefresh mode labels still don't match the dispatched work.
create_refresh_action_for_mode()always appends shielded sync, so “Core Only” and “Platform Only” still start extra work. The same globalself.refreshingflag then paints the Platform row as syncing even when only Core/shielded work is running, so the UI still overstates what is being refreshed.Also applies to: 2121-2159
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/wallets/wallets_screen/mod.rs` around lines 1798 - 1818, The UI shows platform syncing whenever the global self.refreshing is true because create_refresh_action_for_mode() always appends shielded/platform work; update create_refresh_action_for_mode() to only include shielded/platform steps when the selected RefreshMode requires them (do not unconditionally append shielded sync), and introduce per-target flags (e.g., self.refreshing_core, self.refreshing_platform, self.refreshing_shielded) or derive booleans from the dispatched Action to represent which subsystems are actually running; then change the platform row rendering (the addr_color/selecting spinner logic in wallets_screen mod.rs and the analogous code at lines ~2121-2159) to consult the platform-specific flag (e.g., self.refreshing_platform) instead of the global self.refreshing so the Platform row only shows syncing when platform work is dispatched.
1084-1137:⚠️ Potential issue | 🟡 MinorDev Tools are still always expanded in developer mode.
This block renders
Get Test Dash,Mine, and the refresh-mode control inline whenever developer mode is enabled. The PR objective/test plan calls for those controls to live behind an expandable Dev Tools affordance, so the progressive-disclosure behavior is still missing.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/wallets/wallets_screen/mod.rs` around lines 1084 - 1137, The Dev Tools controls are always shown when is_developer_mode() is true; make them hidden behind an expandable affordance by adding a toggled panel (e.g., a boolean field like self.dev_tools_expanded) or using egui's collapsing header and render the existing allocate_ui_with_layout block only when that expanded flag is true; keep the header/button (label "Dev Tools" or similar) visible when self.app_context.is_developer_mode() and toggle self.dev_tools_expanded on click, then move the Get Test Dash, Mine (open_mine_dialog()) and Refresh mode (self.refresh_mode.next()) UI code inside the expanded conditional so they are only shown when the Dev Tools panel is opened.
1719-1786:⚠️ Potential issue | 🟠 MajorUse
ConnectionStatusfor the SPV health row.This branch still reads
spv_manager().status()directly for SPV state, peers, and phase text. That can drift from the rest of the app’s health reporting and breaks the single-source-of-truth contract for connection status. As per coding guidelines, "For connection health (status, peer counts, errors, overall state), always read fromConnectionStatus, not directly fromSpvManageror other subsystems.ConnectionStatusis the single source of truth."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/wallets/wallets_screen/mod.rs` around lines 1719 - 1786, In the CoreBackendMode::Spv branch, don't call self.app_context.spv_manager().status() directly; instead read the SPV health (state, peer count, sync progress/phase, error) from the single source-of-truth ConnectionStatus (via self.app_context.connection_status()) and use those values to drive the UI labels and spinner logic; replace uses of snapshot, snapshot.status, snapshot.connected_peers, and snapshot.sync_progress with the corresponding properties on ConnectionStatus, still using spv_phase_summary to format any progress phase text, and keep the same UI branches (Idle/Stopped, Starting, Syncing, Running, Stopping, Error) and colors.
🧹 Nitpick comments (1)
src/model/address.rs (1)
353-396: Prefer fixed fixtures over randomness in these unit tests.Both tests depend on
Identifier::random(), and the second one can pass without ever exercising its assertion path. Fixed Base58 fixtures would make CI deterministic and cover these Core-prefix branches directly.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/model/address.rs` around lines 353 - 396, Replace the randomized identifier generation in the tests detect_identity_base58 and detect_identity_with_core_prefix_still_works_when_not_valid_core with fixed Base58 fixtures: prepare a small Vec<&str> of hard-coded Base58 identifier strings that cover (1) identifiers without Core prefixes (for detect_identity_base58) and (2) identifiers that start with Core prefix characters both valid and invalid as Core addresses (for detect_identity_with_core_prefix_still_works_when_not_valid_core); then iterate those fixtures instead of calling Identifier::random(), call AddressKind::detect(&id_str) and use id_str.parse::<Address<NetworkUnchecked>>() to classify the Core-parse-fail case and assert the expected AddressKind::Identity outcomes. Ensure you update the test names/comments to reflect deterministic fixtures and keep references to Identifier::random removed so CI is deterministic.
🤖 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/backend_task/shielded/sync.rs`:
- Around line 126-136: The current branch that detects a decrypted-value
mismatch for an existing dn.position only logs a warning and continues, leaving
stale data in existing_notes and allowing shielded_state.recalculate_balance()
to use wrong values; change this to treat divergence as a sync failure or
actively replace and persist the note. Specifically, in the block handling
existing_notes.get(&dn.position) where you compare let new_value =
dn.note.value().inner(), either return an error (e.g.,
Err(SyncError::NoteDivergence { position: dn.position, expected: existing_value,
found: new_value })) to abort sync, or update the persisted store and the
in-memory map: overwrite the DB entry for dn.position with dn.note, update
existing_notes.insert(dn.position, new_value), and ensure any in-memory
shielded_state structures that feed recalculate_balance() are updated before
calling shielded_state.recalculate_balance(). Ensure the chosen approach is
applied where existing_notes, dn.position, dn.note.value(), and
shielded_state.recalculate_balance() are referenced.
In `@src/model/address.rs`:
- Around line 243-245: The truncation check in the function that builds the
shortened address uses min_useful = prefix_len + suffix_len + 3 and only skips
truncation when addr.len() < min_useful, which still truncates when addr.len()
== min_useful and buys no space; change the condition to if addr.len() <=
min_useful (or otherwise use <= when comparing addr.len() against min_useful) so
that when the formatted result would be exactly the same length as the original
the function returns addr.to_string() unchanged (update the check around
min_useful/prefix_len/suffix_len in the same function).
- Around line 15-18: The current code in is_platform_address_string() (called by
AddressKind::detect()) slices s using s[..hrp.len()] which can panic on
arbitrary UTF-8 input; change to use boundary-checked access: call
s.get(..hrp.len()) and if it returns Some(prefix) perform
prefix.eq_ignore_ascii_case(hrp), and replace the raw index access
s.as_bytes()[hrp.len()] with s.as_bytes().get(hrp.len()) and compare the
Option<u8> to b'1' (or check Some(&b'1')). This ensures no slicing or byte
indexing panics for invalid UTF-8/short strings while preserving the same logic
in is_platform_address_string().
In `@src/ui/wallets/wallets_screen/mod.rs`:
- Around line 1145-1166: build_account_tabs currently appends every summary
whose category is "visible" which in normal mode produces multiple Bip44 or
Platform tabs; change the logic so when developer_mode is false you only add a
single tab per primary category (e.g., one AccountCategory::Bip44 (prefer index
0), one Platform/PlatformPayment, one Shielded) by tracking which primary
categories have already been added (a small HashSet or bools) before pushing
AccountTab::Category(summary.category, summary.index); keep existing behavior
when developer_mode is true; still ensure the Bip44 index 0 tab exists by
inserting AccountTab::Category(AccountCategory::Bip44, Some(0)) if not present.
- Around line 1826-1835: The code currently collapses a poisoned mutex into None
via .lock().ok()? which makes a lock failure indistinguishable from "no shielded
state"; update the shielded_states lock handling in the closure that computes
shielded_info (and the analogous block around lines 2100-2108) to explicitly
detect PoisonError and propagate or surface it instead of returning None:
replace .lock().ok()? with a match (or map_err/and_then) on
self.app_context.shielded_states.lock() so that on Err(poison) you either return
an Err variant or set a distinct error/status flag (and log the poison using
processLogger/app_context logger) so the UI shows an error state rather than
"never synced"; keep the remainder that reads state.get(&hash) unchanged.
---
Duplicate comments:
In `@src/model/address.rs`:
- Around line 103-105: The core_prefix check in address parsing (the matches! on
trimmed.as_bytes().first() used to set core_prefix) omits '9', causing valid
Dash testnet P2SH addresses to be misclassified; update that matches! pattern to
include b'9' alongside b'X'|b'Y'|b'y'|b'8'|b'7' so trimmed (and the core_prefix
boolean) correctly recognizes '9' as a Core prefix in the logic that governs
identity-first vs core address parsing.
In `@src/ui/wallets/wallets_screen/mod.rs`:
- Around line 1798-1818: The UI shows platform syncing whenever the global
self.refreshing is true because create_refresh_action_for_mode() always appends
shielded/platform work; update create_refresh_action_for_mode() to only include
shielded/platform steps when the selected RefreshMode requires them (do not
unconditionally append shielded sync), and introduce per-target flags (e.g.,
self.refreshing_core, self.refreshing_platform, self.refreshing_shielded) or
derive booleans from the dispatched Action to represent which subsystems are
actually running; then change the platform row rendering (the
addr_color/selecting spinner logic in wallets_screen mod.rs and the analogous
code at lines ~2121-2159) to consult the platform-specific flag (e.g.,
self.refreshing_platform) instead of the global self.refreshing so the Platform
row only shows syncing when platform work is dispatched.
- Around line 1084-1137: The Dev Tools controls are always shown when
is_developer_mode() is true; make them hidden behind an expandable affordance by
adding a toggled panel (e.g., a boolean field like self.dev_tools_expanded) or
using egui's collapsing header and render the existing allocate_ui_with_layout
block only when that expanded flag is true; keep the header/button (label "Dev
Tools" or similar) visible when self.app_context.is_developer_mode() and toggle
self.dev_tools_expanded on click, then move the Get Test Dash, Mine
(open_mine_dialog()) and Refresh mode (self.refresh_mode.next()) UI code inside
the expanded conditional so they are only shown when the Dev Tools panel is
opened.
- Around line 1719-1786: In the CoreBackendMode::Spv branch, don't call
self.app_context.spv_manager().status() directly; instead read the SPV health
(state, peer count, sync progress/phase, error) from the single source-of-truth
ConnectionStatus (via self.app_context.connection_status()) and use those values
to drive the UI labels and spinner logic; replace uses of snapshot,
snapshot.status, snapshot.connected_peers, and snapshot.sync_progress with the
corresponding properties on ConnectionStatus, still using spv_phase_summary to
format any progress phase text, and keep the same UI branches (Idle/Stopped,
Starting, Syncing, Running, Stopping, Error) and colors.
---
Nitpick comments:
In `@src/model/address.rs`:
- Around line 353-396: Replace the randomized identifier generation in the tests
detect_identity_base58 and
detect_identity_with_core_prefix_still_works_when_not_valid_core with fixed
Base58 fixtures: prepare a small Vec<&str> of hard-coded Base58 identifier
strings that cover (1) identifiers without Core prefixes (for
detect_identity_base58) and (2) identifiers that start with Core prefix
characters both valid and invalid as Core addresses (for
detect_identity_with_core_prefix_still_works_when_not_valid_core); then iterate
those fixtures instead of calling Identifier::random(), call
AddressKind::detect(&id_str) and use id_str.parse::<Address<NetworkUnchecked>>()
to classify the Core-parse-fail case and assert the expected
AddressKind::Identity outcomes. Ensure you update the test names/comments to
reflect deterministic fixtures and keep references to Identifier::random removed
so CI is deterministic.
🪄 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: 1032f4c4-c3f9-4314-b00e-98072f5ebf21
📒 Files selected for processing (8)
src/backend_task/shielded/sync.rssrc/context/wallet_lifecycle.rssrc/model/address.rssrc/ui/components/address_input.rssrc/ui/wallets/account_summary.rssrc/ui/wallets/shielded_tab.rssrc/ui/wallets/wallets_screen/mod.rstests/backend-e2e/tx_is_ours.rs
✅ Files skipped from review due to trivial changes (1)
- tests/backend-e2e/tx_is_ours.rs
🚧 Files skipped from review as they are similar to previous changes (4)
- src/context/wallet_lifecycle.rs
- src/ui/components/address_input.rs
- src/ui/wallets/account_summary.rs
- src/ui/wallets/shielded_tab.rs
bootstrap_wallet_addresses only ran when known_addresses was empty, but new_from_seed already derives one Core address. This meant platform payment addresses were never bootstrapped for new wallets — only populated later by network sync (which returns nothing for fresh wallets with no on-chain activity). Now checks for the presence of PlatformPayment addresses in watched_addresses and runs bootstrap if missing. Bootstrap functions are idempotent (add_address_if_not_exists). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Platform addresses were only populated from platform_address_info (network sync results). Fresh wallets with no on-chain activity had empty platform_address_info, so no platform addresses appeared in the Send screen autocomplete. Now derives platform addresses from watched_addresses (which contains all bootstrapped platform payment addresses), with balance looked up from platform_address_info (defaulting to 0). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the `selected_account` field that was mutated as a side-effect during rendering, causing the last-rendered collapsible section in the System tab to overwrite previous sections' state. Now each render call receives its `(AccountCategory, Option<u32>)` as a direct parameter. Additionally, `system_tab_sections` now builds per-(category, index) pair so multi-index accounts show correct per-index balance and address counts instead of aggregating across all indices. Fixes CODE-005, CODE-011, PROJ-004. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ZIP32 key derivation and DB reads in `initialize_shielded_wallet` ran synchronously on the UI thread during `handle_wallet_unlocked()`. Move the call to a `spawn_blocking` task tracked by `subtasks` so the UI remains responsive. The shielded tab already handles the "initializing" state gracefully via its `is_initialized` check. Fixes CODE-010. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract the SPV is_ours override into a named function and add four unit tests covering: outgoing already-ours, incoming not-ours (the main override case), zero-amount edge case, and outgoing not-ours. A comment explains why bloom filter false positive testing requires mocking the SPV layer and is out of scope for unit tests. Fixes PROJ-002. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add WAL-021 through WAL-024 covering the tab-based navigation, developer-mode System tab, collapsible transaction history, and collapsible balance breakdown sections introduced in PR #791. Fixes PROJ-007. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Core addresses now show "(change)" suffix for BIP44 change addresses (m/44'/5'/0'/1/x). New with_exclude_change(true) builder method filters them out — send inputs should not display change addresses since users don't share them with others. Change detection uses the existing DerivationPathHelpers::is_bip44_change trait method from the wallet model. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Change addresses are internal wallet addresses not meant to be shared. The Send screen's destination AddressInput now uses with_exclude_change(true) to filter them out of the autocomplete. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add right-aligned compact buttons below the address table for generating new addresses on Core (BIP44) and Platform tabs. Core addresses use the async BackendTask path for SPV compatibility; Platform addresses derive synchronously. The button is disabled during generation to prevent double-clicks. Shielded tab already has its own button; System tab has no button (addresses are derived automatically). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
System addresses (Identity Registration, CoinJoin, Provider keys, etc.) are internal wallet infrastructure not meant for user-facing send/receive operations. Filter them out by checking each address against watched_addresses path_reference → AccountCategory. Only BIP44 (Core) and PlatformPayment addresses are shown. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address type suffix (Core/Platform/Shielded/Identity) is now always shown in autocomplete entries, not just when multiple types are enabled. Users can type "platform", "core", "plat", etc. to filter entries by type — the filter matches against both short_label() and display_name(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Detect AddressInvalidNonceError from Platform and show an actionable message: "The transaction used an outdated sequence number. Please retry — the wallet will use the correct number automatically." Previously this fell through to the generic ShieldedBroadcastFailed message which gave no guidance. Upstream fix filed: dashpay/platform#3407 (SDK auto-retry on nonce mismatch). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/ui/components/address_input.rs (2)
219-223:⚠️ Potential issue | 🟡 MinorMake
with_exclude_change()order-independent.
with_wallets()eagerly fillsall_entries, so callingwith_exclude_change(true)later is a silent no-op. That makes the new flag easy to miss in builder chains and leaves change addresses visible in the very flow this option is meant to fix.♻️ Minimal fix
pub fn with_exclude_change(mut self, exclude: bool) -> Self { self.exclude_change = exclude; + if exclude { + self.all_entries + .retain(|e| e.address_kind != AddressKind::Core || !e.is_change); + } self }Also applies to: 261-264
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/components/address_input.rs` around lines 219 - 223, with_wallets currently populates all_entries eagerly which makes calling with_exclude_change afterwards a no-op; fix by making with_exclude_change order-independent: (1) have with_wallets consult the existing self.exclude_change flag when calling extract_wallet_entries and skip adding change addresses if true, and (2) have with_exclude_change set the exclude flag and also filter/remove any already-populated entries in self.all_entries (use the same change-detection logic as extract_wallet_entries) so toggling the flag after with_wallets updates existing state; reference methods: with_wallets, with_exclude_change, extract_wallet_entries, and the all_entries collection.
625-633:⚠️ Potential issue | 🟡 MinorNormalize case before the prefix check in
validate_shielded().
validate_shielded()uses a case-sensitivestarts_with(expected_prefix)check before parsing, so an all-uppercase shielded address on the right network gets rejected with "different network." The BIP-350 bech32m spec allows all-uppercase addresses. Follow the pattern invalidate_platform(): check for valid case (all-upper or all-lower, not mixed), normalize to lowercase, apply the prefix check to the canonical form, and pass the canonical string toOrchardAddress::from_bech32m_string().🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/components/address_input.rs` around lines 625 - 633, In validate_shielded(), update the prefix check to operate on a canonical lowercase form like validate_platform() does: first verify the address case is valid (either all-lower or all-upper, reject mixed-case), then normalize the input to lowercase, perform the starts_with(expected_prefix) check against that canonical lowercase string, and pass the canonical string to OrchardAddress::from_bech32m_string(); ensure error messages remain the same but are emitted after the canonicalization and prefix check.
♻️ Duplicate comments (1)
src/ui/components/address_input.rs (1)
383-398:⚠️ Potential issue | 🟠 MajorStill unresolved: default autocomplete scales with every derived address.
This still materializes
known_addressesintoall_entries, whilefiltered_entries()scans and sorts the whole list on each focused frame. Including zero-balance derived addresses by default makes opening the dropdown progressively slower on larger wallets.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/components/address_input.rs` around lines 383 - 398, The UI is materializing all derived addresses into all_entries and filtered_entries() rescans/sorts the full set each focus, causing slow autocomplete; modify the logic that builds the dropdown so it does not iterate/collect every entry from known_addresses but instead applies filters incrementally: skip system addresses (system_addresses), skip change addresses when self.exclude_change and derivation_path.is_bip44_change(self.network) is true, and skip zero-balance addresses by default unless with_balance_range(1..) is explicitly requested (use address_balances.get(address).copied().unwrap_or(0) to check balance); ensure filtered_entries() no longer sorts/scans the entire known_addresses on each focus but uses the already-filtered iterator or caches the filtered list to avoid per-frame full traversal.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/user-stories.md`:
- Around line 217-224: Update the WAL-024 user story text to reflect the
implemented UI: state that the "Balance breakdown" section (not the
address/asset-lock sections) is collapsible and is expanded by default only when
developer mode is enabled, while the "Address table" and "Asset locks" sections
each have their own collapsible headers and are expanded by default for users;
adjust the acceptance checklist and wording under the WAL-024 header so QA has
the correct criteria and examples for testing.
In `@src/context/wallet_lifecycle.rs`:
- Around line 161-166: register_wallet() currently only triggers shielded setup
via handle_wallet_unlocked() in the SPV branch, so newly-registered RPC wallets
skip shielded init; refactor by extracting shielded initialization into a
standalone call and ensure queue_shielded_init_and_sync(seed_hash) (or a new
helper like initialize_shielded_for_wallet(seed_hash)) is invoked for every
successful wallet registration path (both SPV and RPC) instead of being tied
only to handle_wallet_unlocked(); remove or consolidate any duplicate calls so
shielded init runs exactly once per registration.
- Around line 181-245: The unlock-time shielded init/sync
(queue_shielded_init_and_sync → run_shielded_sync →
sync_shielded_notes/check_nullifiers_task) can race with user-triggered
refreshes and remove state from the Mutex, causing TaskError::WalletNotFound;
serialize these by adding a per-seed lock (e.g., a shared
Arc<Mutex<HashMap<WalletSeedHash, Arc<Mutex<()>>>>) and acquire the
seed-specific mutex at the start of queue_shielded_init_and_sync (and likewise
when handling UI-triggered SyncNotes/CheckNullifiers) so both paths hold the
same per-seed guard while calling initialize_shielded_wallet,
sync_shielded_notes, and check_nullifiers_task; alternatively, dispatch the
unlock-time sync through the existing BackendTask queue used by UI refreshes
(use the same task type/queue) so both flows are serialized.
In `@src/ui/components/address_input.rs`:
- Around line 370-381: The AddressInput component currently unconditionally
builds system_addresses by filtering guard.watched_addresses via
AccountCategory::from_reference and hides non-Bip44/PlatformPayment addresses
everywhere; make this filtering opt-in by adding a private builder-config flag
(e.g., hide_system_addresses: bool) to AddressInput's private fields with a
builder method (e.g., .hide_system_addresses(bool)) and only apply the
AccountCategory-based filtering when that flag is true; ensure the component
follows the pattern by keeping fields private, exposing builder methods for
configuration, and keeping/adding a Response struct that implements
ComponentResponse so callers can opt into the send-autocomplete behavior (caller
screens should pass developer_mode or set the builder flag as needed).
In `@src/ui/wallets/wallets_screen/mod.rs`:
- Around line 1059-1067: The current shielded_balance_duffs collapses a poisoned
mutex into a zero balance; change its signature from fn
shielded_balance_duffs(&self, seed_hash: &WalletSeedHash) -> u64 to ->
Option<u64> and stop using lock().ok(). Instead call
self.app_context.shielded_states.lock() and match the Result: on Ok(states)
return states.get(seed_hash).map(|s| s.shielded_balance / CREDITS_PER_DUFF), and
on Err(poison_err) log the error (including poison_err) and return None so
callers can render an error state instead of silently showing 0; update callers
of shielded_balance_duffs to handle None (e.g., show error/placeholder or skip
badge).
- Around line 1172-1204: build_account_tabs currently backfills Dash Core
(AccountCategory::Bip44) but not Platform, causing wallets without a
PlatformPayment summary to lack the Platform tab and preventing
render_bottom_options from being invoked to create the first Platform address;
fix by adding the same backfill logic used for Dash Core to ensure an
AccountTab::Category(AccountCategory::Platform, Some(0)) is inserted (e.g.,
right after the Bip44 insertion) when no Platform summary exists, keep Shielded
and System insertion logic unchanged, and remove/avoid any early-return or
empty-tab short-circuit in the caller so render_bottom_options is still called
when only the backfilled Platform tab exists (refer to build_account_tabs,
AccountTab::Category, AccountCategory::Platform, and render_bottom_options to
locate the spots to change).
- Around line 161-162: The generating_core_address flag (field
generating_core_address in wallets_screen) must be reset when the Wallets screen
regains focus so a stale in-flight task doesn't keep the "+ New Receive Address"
button disabled; add a focus handler (or extend the existing on_focus /
set_visible / on_show logic for the Wallets screen) to set
self.generating_core_address = false when the screen becomes visible, and ensure
this ties into the main update() routing (display_task_result()) behavior so any
pending TaskResult routed elsewhere won't leave the flag set when returning to
the Wallets screen.
- Around line 2226-2238: The shielded_tab_view.tick() is still called when
switching from an HD wallet to a single-key wallet, draining pending_task for
the old HD seed; update the guard so we only call tick() when the shielded view
corresponds to the currently-selected account (not just when
selected_account_tab != AccountTab::Shielded). Concretely, before calling
self.shielded_tab_view.as_mut().map(|v| v.tick()), check that the
shielded_tab_view’s associated seed/seed_hash or account identifier matches the
currently selected account (or that the selected account is an HD/seed-backed
account); if it doesn’t match, return AppAction::None. Use existing identifiers
like shielded_tab_view, tick(), selected_account_tab and the shielded view’s
seed/seed_hash or account id to implement the extra guard.
---
Outside diff comments:
In `@src/ui/components/address_input.rs`:
- Around line 219-223: with_wallets currently populates all_entries eagerly
which makes calling with_exclude_change afterwards a no-op; fix by making
with_exclude_change order-independent: (1) have with_wallets consult the
existing self.exclude_change flag when calling extract_wallet_entries and skip
adding change addresses if true, and (2) have with_exclude_change set the
exclude flag and also filter/remove any already-populated entries in
self.all_entries (use the same change-detection logic as extract_wallet_entries)
so toggling the flag after with_wallets updates existing state; reference
methods: with_wallets, with_exclude_change, extract_wallet_entries, and the
all_entries collection.
- Around line 625-633: In validate_shielded(), update the prefix check to
operate on a canonical lowercase form like validate_platform() does: first
verify the address case is valid (either all-lower or all-upper, reject
mixed-case), then normalize the input to lowercase, perform the
starts_with(expected_prefix) check against that canonical lowercase string, and
pass the canonical string to OrchardAddress::from_bech32m_string(); ensure error
messages remain the same but are emitted after the canonicalization and prefix
check.
---
Duplicate comments:
In `@src/ui/components/address_input.rs`:
- Around line 383-398: The UI is materializing all derived addresses into
all_entries and filtered_entries() rescans/sorts the full set each focus,
causing slow autocomplete; modify the logic that builds the dropdown so it does
not iterate/collect every entry from known_addresses but instead applies filters
incrementally: skip system addresses (system_addresses), skip change addresses
when self.exclude_change and derivation_path.is_bip44_change(self.network) is
true, and skip zero-balance addresses by default unless with_balance_range(1..)
is explicitly requested (use address_balances.get(address).copied().unwrap_or(0)
to check balance); ensure filtered_entries() no longer sorts/scans the entire
known_addresses on each focus but uses the already-filtered iterator or caches
the filtered list to avoid per-frame full traversal.
🪄 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: f183cbe1-ff64-4118-b78f-a7e512d9f8a0
📒 Files selected for processing (6)
docs/user-stories.mdsrc/context/wallet_lifecycle.rssrc/ui/components/address_input.rssrc/ui/wallets/send_screen.rssrc/ui/wallets/wallets_screen/address_table.rssrc/ui/wallets/wallets_screen/mod.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- src/ui/wallets/send_screen.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/ui/components/address_input.rs (1)
370-381:⚠️ Potential issue | 🟠 MajorKeep system-address filtering opt-in in this shared component.
Line 378 is reusing the wallet screen’s default-mode visibility split, and Lines 390-397 then drop those addresses unconditionally. That means dev-only/tooling addresses never reach autocomplete anywhere
AddressInputloads wallets, becausedeveloper_modeis not consulted in this path. Please move this behind a caller-controlled builder flag, or let callers pass the allowed categories, instead of hard-codingis_system_category()here.As per coding guidelines,
src/ui/components/**/*.rs: "Provide private fields only, builder methods for configuration, Response struct with ComponentResponse trait."Also applies to: 390-397
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/components/address_input.rs` around lines 370 - 381, The AddressInput component is unconditionally filtering out system addresses by using AccountCategory::from_reference(info.path_reference).is_system_category() on guard.watched_addresses; make this behavior opt-in by adding a private configuration field on the AddressInput builder (e.g., allowed_categories or allow_system_addresses boolean, defaulting to current safe behavior) and a builder method to set it; then change the logic that builds system_addresses (the map/filter that uses AccountCategory::from_reference and the subsequent drop logic at the later block) to consult that new field—only exclude system-category addresses when the builder flag indicates they should be filtered, otherwise include them so callers can control visibility.
🧹 Nitpick comments (2)
src/backend_task/error.rs (1)
1019-1025: Add regression tests for the new nonce classification branch.The new
AddressInvalidNonceErrormapping has no dedicated test coverage. Please add tests for:
shielded_broadcast_error()→TaskError::ShieldedNonceMismatch, and- display text includes actionable retry guidance.
This keeps the new behavior from silently regressing.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/backend_task/error.rs` around lines 1019 - 1025, Add unit tests that cover the new nonce-classification branch: write tests that call shielded_broadcast_error() (or the public wrapper that produces the TaskError) with a simulated ConsensusError::StateError(StateError::AddressInvalidNonceError(...)) and assert it returns TaskError::ShieldedNonceMismatch (checking the variant). Also add an assertion that the Display (or to_string()) of TaskError::ShieldedNonceMismatch contains actionable retry guidance text (e.g., mentions "retry" or "increment nonce"); use the same constructors/types (ConsensusError::StateError, StateError::AddressInvalidNonceError, TaskError::ShieldedNonceMismatch) to build the inputs and verify both variant and user-facing message to prevent regressions.src/ui/components/address_input.rs (1)
400-405: Build the dropdown label with one localizable format.Line 400 appends
" (change)", and Line 885 then wraps the finished text again with"{} ({})". Composing user-facing labels from suffix fragments makes them hard to translate or reorder cleanly. Please funnel the final row text through a single formatter/helper instead.As per coding guidelines,
**/*.rs: "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 and no logic in the text itself."Also applies to: 885-887
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/components/address_input.rs` around lines 400 - 405, The code currently builds the dropdown label by concatenating fragments (change_suffix + truncated/full address) in multiple places (see change_suffix, display, truncate_address, and later wrapping with "{} ({})"); replace these fragment concatenations with a single helper/formatter (e.g., format_address_label) that takes named parameters like prefix, address, and is_change (or full_addresses flag) and returns one fully formatted, localizable string; update both the current display construction and the later wrapping (the code that uses "{} ({})") to call this helper so the entire user-facing label is produced by one localization call with named placeholders.
🤖 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/backend_task/error.rs`:
- Around line 833-841: The ShieldedNonceMismatch error claims automatic retry
but with_anchor_retry only retries on ShieldedAnchorMismatch; update
with_anchor_retry to also handle ShieldedNonceMismatch by invoking the
wallet/nonce refresh path (the same refresh used for AddressInvalidNonceError
handling) and performing a single retry of the broadcast attempt before
returning the error; ensure you reference ShieldedNonceMismatch in the match
arm, call whatever nonce-refresh/rehydration routine used elsewhere (or reuse
shielded_broadcast_error mapping logic) to update the cached nonce, then retry
the operation once and return the eventual result or the error if it still
fails.
---
Duplicate comments:
In `@src/ui/components/address_input.rs`:
- Around line 370-381: The AddressInput component is unconditionally filtering
out system addresses by using
AccountCategory::from_reference(info.path_reference).is_system_category() on
guard.watched_addresses; make this behavior opt-in by adding a private
configuration field on the AddressInput builder (e.g., allowed_categories or
allow_system_addresses boolean, defaulting to current safe behavior) and a
builder method to set it; then change the logic that builds system_addresses
(the map/filter that uses AccountCategory::from_reference and the subsequent
drop logic at the later block) to consult that new field—only exclude
system-category addresses when the builder flag indicates they should be
filtered, otherwise include them so callers can control visibility.
---
Nitpick comments:
In `@src/backend_task/error.rs`:
- Around line 1019-1025: Add unit tests that cover the new nonce-classification
branch: write tests that call shielded_broadcast_error() (or the public wrapper
that produces the TaskError) with a simulated
ConsensusError::StateError(StateError::AddressInvalidNonceError(...)) and assert
it returns TaskError::ShieldedNonceMismatch (checking the variant). Also add an
assertion that the Display (or to_string()) of TaskError::ShieldedNonceMismatch
contains actionable retry guidance text (e.g., mentions "retry" or "increment
nonce"); use the same constructors/types (ConsensusError::StateError,
StateError::AddressInvalidNonceError, TaskError::ShieldedNonceMismatch) to build
the inputs and verify both variant and user-facing message to prevent
regressions.
In `@src/ui/components/address_input.rs`:
- Around line 400-405: The code currently builds the dropdown label by
concatenating fragments (change_suffix + truncated/full address) in multiple
places (see change_suffix, display, truncate_address, and later wrapping with
"{} ({})"); replace these fragment concatenations with a single helper/formatter
(e.g., format_address_label) that takes named parameters like prefix, address,
and is_change (or full_addresses flag) and returns one fully formatted,
localizable string; update both the current display construction and the later
wrapping (the code that uses "{} ({})") to call this helper so the entire
user-facing label is produced by one localization call with named placeholders.
🪄 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: e90e7f38-07f5-47ab-9d01-86fac0cb8ff6
📒 Files selected for processing (2)
src/backend_task/error.rssrc/ui/components/address_input.rs
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 23 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…on (#797) (#800) Regtest and Testnet share the `tdash` bech32m prefix, but `PlatformAddress::from_bech32m_string()` always returns `Network::Testnet` for `tdash` addresses. The strict `!=` comparison in the fund-platform dialog rejected valid Regtest addresses with "Address network mismatch". - Make `networks_address_compatible()` `pub(crate)` in `model::wallet` so all modules can reuse the canonical check - Remove the duplicate private copy in `backend_task::core` and import from the single source - Replace `network != self.app_context.network` in `dialogs.rs` with `networks_address_compatible()` so Testnet/Devnet/Regtest addresses are accepted interchangeably Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(error): add actionable messages for shielded fee and pool-size errors Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(error): add actionable message for shielded anchor mismatch Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(shielded): auto-resync notes and retry on anchor mismatch When unshield_credits, shielded_transfer, or shielded_withdrawal fails with ShieldedAnchorMismatch (stale commitment tree witnesses), automatically sync notes once to update the tree and retry the operation. Only one resync attempt is made — if the retry also fails, the error propagates as-is. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(shielded): ensure shielded tables exist and log DB errors during init Three changes to fix empty shielded balance after app restart: 1. Defensive table creation in initialize(): after migrations complete, ensure shielded_notes and shielded_wallet_meta tables exist even if the DB version was already past v29/v30 from a prior build. Both methods use CREATE TABLE IF NOT EXISTS, so this is safe. 2. Log DB errors during shielded init: change silent if-let-Ok pattern to match/Err with tracing::warn, so missing-table errors are visible instead of silently producing empty note lists. 3. Safety net resync: when the commitment tree has been synced but no unspent notes were loaded from DB, clear the tree and reset last_synced_index to 0 so the auto-sync rediscovers all notes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): consolidate migrations v28-v32 into v33 Resolves version numbering collision between zk and v1.0-dev branches: the zk branch used v28 for shielded tables while v1.0-dev used v28 for contacts. After merging, users migrating from either branch could end up with missing tables depending on which version their DB was at. v33 runs all sub-migrations idempotently in one step, ensuring all tables exist regardless of prior migration history. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): consolidate migrations v28-v32 into v33 Resolves version numbering collision between zk and v1.0-dev branches: the zk branch used v28 for shielded tables while v1.0-dev used v28 for contacts. After merging, users migrating from either branch could end up with missing tables depending on which version their DB was at. v33 runs all sub-migrations idempotently in one step, ensuring all tables exist regardless of prior migration history. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: pin platform dependency to zk-fixes revision Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): address PR review — fresh schema, error propagation, rename coverage - propagate SQL error in add_nullifier_sync_timestamp_column instead of swallowing it with unwrap_or(false) - add shielded_notes and shielded_wallet_meta to rename_network_dash_to_mainnet Note: Fix 1 (last_nullifier_sync_timestamp in CREATE TABLE) was already present on this branch. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): address PR review — fresh schema, error propagation, rename coverage - propagate SQL error in add_nullifier_sync_timestamp_column instead of swallowing it with unwrap_or(false) - add shielded_notes and shielded_wallet_meta to rename_network_dash_to_mainnet Note: Fix 1 (last_nullifier_sync_timestamp in CREATE TABLE) was already present on this branch. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): add foreign key constraints to shielded tables shielded_notes and shielded_wallet_meta both had wallet_seed_hash columns with no FK constraint. Added FOREIGN KEY (wallet_seed_hash) REFERENCES wallet(seed_hash) ON DELETE CASCADE to both, matching the pattern used by all other per-wallet tables in the codebase. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): add foreign key constraints to shielded tables shielded_notes and shielded_wallet_meta both had wallet_seed_hash columns with no FK constraint. Added FOREIGN KEY (wallet_seed_hash) REFERENCES wallet(seed_hash) ON DELETE CASCADE to both, matching the pattern used by all other per-wallet tables in the codebase. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): remove duplicate shielded methods after v1.0-dev merge On zk-fixes, shielded DB methods live in shielded.rs. The v1.0-dev backport inlined them in initialization.rs (no shielded.rs on that branch). Merging v1.0-dev back caused E0592 duplicate definitions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(test): remove duplicate wallet store in register_test_address register_test_address called db.store_wallet redundantly — callers already store the wallet before calling it, causing UNIQUE constraint violations when tests run in parallel on CI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(shielded): address PR review — error propagation, retry helper, safety net - Propagate DB error from get_unspent_shielded_notes instead of swallowing - Extract with_anchor_retry() helper to deduplicate ~27 lines across shielded_transfer_task, unshield_credits_task, shielded_withdrawal_task - Fix safety net false positive: check all notes (spent + unspent) to distinguish "never had notes" from "all spent" - Propagate error from clear_commitment_tree_tables instead of discarding Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(error): address PR review — Amount formatting, Display completeness - Add Amount::dash_from_credits() constructor to model/amount.rs - Replace manual format_credits_as_dash() logic with Amount::dash_from_credits().to_string(), so credit amounts use Amount's Display (with DASH unit, trimmed trailing zeros) - Update ShieldedFeeExceedsBalance #[error] message: remove redundant "Dash" literal (Amount's Display already includes the "DASH" unit suffix) - Update format_credits_as_dash tests to match new output format ("1 DASH", "2.5 DASH", etc.) - Remove special-casing that showed with_details() for all RPC errors unconditionally: all required user-facing info is already in each variant's Display string; technical details are now shown only in developer mode (aligns with CLAUDE.md §error-messages rule 4) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(rpc): address PR review — context fallback, success banner, error source - Guard in-memory config update + reinit behind a check that the target network's AppContext actually exists; skip both when it doesn't so we don't accidentally overwrite mainnet config via the fallback path. - Show success banner only when reinit succeeds; show a warning when it fails so the user knows the connection may not reflect the new password. - Make CoreRpcConnectionFailed.source Option<dashcore_rpc::Error> so chain_lock_rpc_error can pass None instead of fabricating a fake ConnectionRefused error from a borrowed reference. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: simplify shielded helpers comment in initialization.rs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): address PR #789 review — doc comments, migration default - Split misplaced doc comment: `add_wallet_transaction_status_column` now has its own Migration 30 doc; `rename_network_dash_to_mainnet` gets its own Migration 29 doc. - Add inline comment explaining why the migration uses DEFAULT 2 (Confirmed) while fresh CREATE TABLE in wallet.rs uses DEFAULT 0. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): remove duplicate shielded methods after v1.0-dev merge On zk-fixes, shielded DB methods live in shielded.rs. The v1.0-dev backport inlined them in initialization.rs (no shielded.rs on that branch). Merging v1.0-dev back caused E0592 duplicate definitions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: simplify shielded helpers comment in initialization.rs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore(db): document migration DEFAULT 2 vs fresh DEFAULT 0 Existing transactions predate status tracking and are assumed confirmed (DEFAULT 2). Fresh installs use DEFAULT 0 (Unconfirmed). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(wallet): address review — zero-balance detection, status warning Add `spv_balance_known: bool` to `Wallet` to distinguish a synced zero-balance wallet from an unsynced one. `spv_confirmed_balance()` now returns `None` only before the first SPV report, and `Some(0)` after SPV confirms an empty wallet. Add `tracing::warn!` in `TransactionStatus::from_u8` when an unknown discriminant is encountered, avoiding silent data coercion in a financial context. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(error): address review — typed BIP32 source, migration error variant Replace WalletKeyDerivationFailed { detail: String } with a typed #[source] field (Box<dyn Error + Send + Sync>) so the error chain is preserved and Display/Debug separation is explicit. Update all callsites in wallet/mod.rs and identity/mod.rs accordingly. Replace the semantically wrong InvalidParameterName map_err in rename_network_dash_to_mainnet with a plain ? — execute() already returns rusqlite::Result so no conversion is needed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(db): add v33 consolidated migration regression tests Two test scenarios verify the v33 consolidated migration that merges sub-migrations v28-v32 (core_wallet_name, shielded tables, contacts, wallet transaction status, network rename): 1. Fresh install creates all v33 tables/columns directly via create_tables() 2. Upgrade from v27 runs the full migration path and produces identical schema Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ui): show warning when config save fails instead of success When config.save() fails, stop early with a warning banner instead of continuing to reinit and showing a misleading success message. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(error): consolidate format_credits_as_dash and remove jargon from user messages - fee_estimation::format_credits_as_dash now delegates to Amount::dash_from_credits() instead of doing its own f64 math with fixed 8 decimals; output is now trimmed (e.g. "1 DASH" instead of "1.00000000 DASH") - Remove private format_credits_as_dash from error.rs; import the pub version from fee_estimation instead - IdentityInsufficientBalance: show amounts in DASH rather than raw credit integers - ShieldedAnchorMismatch: replace "out of sync" with plain-language retry guidance - ShieldedFeeExceedsBalance: remove "shielded balance" / "shield more credits" jargon - ShieldedInsufficientPoolNotes: remove count interpolations and ZK pool jargon - Update tests throughout to match new message content and format Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(test): remove duplicate wallet store in register_test_address register_test_address called db.store_wallet redundantly — callers already store the wallet before calling it, causing UNIQUE constraint violations when tests run in parallel on CI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(core): log chain lock RPC errors that aren't auth/connection failures Silent swallowing of errors like "Unable to find any ChainLock" made it impossible to diagnose why the active network showed as Disconnected. Now warns via tracing when chain_lock_rpc_error returns None. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ui): surface chain lock RPC errors in Networks tab Previously, non-auth/non-connection RPC errors (e.g. "Unable to find any ChainLock") were silently swallowed — the UI showed "Disconnected" with no explanation. Now the error message is carried through the ChainLocks result, stored in ConnectionStatus.rpc_last_error, and displayed as "Error: ..." in both developer and non-developer RPC status labels on the Networks tab. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(ui): unified AddressInput component with autocomplete (#787) * feat(ui): add unified AddressInput component with autocomplete Introduce a reusable AddressInput component that handles text input with real-time address type detection, autocomplete from wallet data, balance display, type filtering, and network-aware validation. Supports Core, Platform, Shielded, and Identity address kinds. New files: - src/model/address.rs: AddressKind enum and ValidatedAddress enum - src/ui/components/address_input.rs: full component with 33 unit tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(ui): integrate AddressInput component into send and unshield screens Replace inline address parsing and validation with the unified AddressInput component across 3 proof-of-concept sites: - UnshieldCreditsScreen: full migration, removes local Destination enum and parse_destination(), gains autocomplete and type-restricted input - WalletSendScreen simple mode: full migration, removes AddressType enum, replaces detect_address_type/is_shielded_address with AddressKind-based detection, eliminates double-parsing in send handlers - WalletSendScreen advanced mode: minimal migration, updates type detection to use AddressKind, keeps CoreAddressInput/PlatformAddressInput structs unchanged Net reduction of ~47 lines per screen. All send flows preserved. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(address-input): fix misleading test and add missing coverage The existing `disabled_type_rejected_with_correct_error` test was testing empty input (which returns no error) rather than actually verifying type restriction. Fixed the test and added coverage for: - Selection-only mode rejection of manual input - Identity validation with valid/invalid identifiers - Truncate boundary at exactly 16/17 characters - Empty input in selection-only and restricted-type modes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): address QA findings for AddressInput component QA-001: Extract duplicated address detection logic from send_screen.rs into AddressKind::detect() on the model type. Both send_screen and address_input now delegate to the single canonical implementation. QA-002: Fix autocomplete "...and N more" count using unfiltered total. filtered_entries() now returns the pre-truncation match count so the overflow label shows the correct number of remaining matches. QA-003: Add minimum length check (>= 60 chars) to shielded address validation. Previously any string with the correct prefix was accepted. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): address bot review findings for AddressInput component - Validate shielded addresses via OrchardAddress::from_bech32m_string() instead of prefix+length check only - Use char-aware truncation in truncate_address() to prevent panics on multi-byte UTF-8 input (DPNS labels, emoji) - Reset AddressInput when source selection changes in send screen, and configure allowed destination kinds per source type - Store bech32m string in ValidatedAddress::Platform variant so to_address_string() returns canonical encoding instead of debug hex Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): address remaining review findings for AddressInput component - Fix manual entry not propagating validated address on blur (HIGH) - Fix selected_from_autocomplete causing repeated change signals - Remove protocol jargon from unshield screen messages - Reject mixed-case bech32m platform addresses per BIP-350 - Use per-instance ComboBox ID to prevent state collision - Clear cached_detection after autocomplete selection - Fix doc comments and reuse filtered_entries() computation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(ui): support multi-wallet autocomplete in AddressInput Replace with_wallet/set_wallet (single wallet) with with_wallets/set_wallets (slice of wallets). When multiple wallets are loaded, each autocomplete entry is prefixed with the wallet alias so the user can tell which wallet owns the address. send_screen.rs now passes all loaded wallets to AddressInput instead of only the selected one. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ui): restore missing "Show zero-balance addresses" checkbox The checkbox was accidentally dropped during a branch merge. Restored the horizontal layout with heading on the left and checkbox right-aligned, matching the v1.0-dev implementation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ui): improve AddressInput autocomplete UX - Fix click selection: keep popup rendered when text field loses focus so the click handler fires (was gated on has_focus only) - Show dropdown on focus with any input length (removed 3-char minimum) - Add type suffix (Core), (Platform), (Identity), (Shielded) to dropdown entries when multiple address types are enabled - Validate immediately on paste (text >3 chars) instead of requiring blur first, so "Fund Platform Address" button activates without needing to click away Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ui): make entire autocomplete row clickable in AddressInput Previously only the address label was clickable — clicking the balance text on the right side of the row did nothing. Now the click handler uses the horizontal row response, so the entire row triggers selection. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ui): fix autocomplete click handling and format balances with 4dp - Capture selectable_label click response instead of discarding it; combine with row-level click for full-row clickability - Format all DASH balances in dropdown with exactly 4 decimal places Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ui): use single interaction rect for full-row clickable autocomplete Replace selectable_label + horizontal layout with allocate_exact_size and manual painting — no child widgets steal clicks, the entire row (address, balance, dead space) is clickable with hover feedback. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(ui): add wallet address autocomplete to unshield screen Populate AddressInput with wallet addresses via .with_wallets() so users can select a destination from the dropdown instead of manually entering addresses. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(error): handle asset lock and shielded insufficient funds errors with user-friendly messages Add two new TaskError variants to replace raw SDK errors with actionable, jargon-free messages: - AssetLockOutPointInsufficientBalance: shown when an asset lock outpoint has been partially consumed and lacks credits for the operation. Extracts credits_left/credits_required and displays DASH amounts. - ShieldedAddressInsufficientFunds: shown when a shielded broadcast fails because the address balance is too low. Detected in shielded_broadcast_error() before falling through to ShieldedBroadcastFailed. Both follow the IdentityInsufficientBalance pattern with format_credits_as_dash. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): use AmountInput component for all amount inputs in wallet screens Replace raw text_edit_singleline + custom parse_amount_*() methods with the AmountInput component in 4 shielded screens (unshield, shield, shield-from-asset-lock, shielded-send). This fixes a bug where entering "1" would send 1 credit instead of 1 DASH because the raw input had ambiguous parsing. Also adds "Amount (DASH):" label to SendScreen's AmountInput and removes its redundant manual label. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add UI components reference and teach CLAUDE.md to use it Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(ui): redesign wallet screen information architecture Replace the old Balances|Shielded top-level tabs and account dropdown with a unified layout that better matches how users think about their wallet: - Balance section with collapsible breakdown (Core/Platform split) - Dev Tools expandable button replaces scattered dev-mode controls - Transaction History shown as collapsible section (visible in all modes) - Accounts & Addresses use tabs instead of a dropdown selector - Shielded view moved from top-level tab to an account tab - Asset Locks restricted to the Dash Core tab only - "Main Account" renamed to "Dash Core" throughout - Fee column added to transaction table (dev mode only) - "Get Test Dash" button opens testnet faucet in dev tools Tab visibility follows progressive disclosure: default mode shows only Dash Core, Platform, and Shielded tabs. Developer mode reveals all account types that have addresses. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): use AddressInput component in Mine dialog for core address selection Replace the manual ComboBox address selector in the Mine Blocks dialog with the unified AddressInput component, configured for Core-only addresses with selection-only mode from the current wallet. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): trigger shielded balance refresh after all shielding operations After Shield, Shield from Core, Send Private, Unshield, and Send Dash (with shielded source) complete successfully, automatically dispatch a SyncNotes task so the shielded tab shows updated balances without manual refresh. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: demote cookie auth fallback log to trace level Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ui): prevent transaction list showing wrong wallet data Add defensive checks in render_transactions_section to prevent cross-wallet transaction leakage: 1. Verify the selected wallet Arc matches the canonical one in app_context.wallets (guards against stale references). 2. Filter displayed transactions to only those with at least one output matching the wallet's known addresses (prevents showing transactions from other wallets that leaked in via a non-wallet-scoped RPC endpoint). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): auto-show zero-balance addresses when wallet is empty When all addresses have zero balance and there are fewer than 5 addresses total, bypass the zero-balance filter so new/empty wallets show their addresses instead of a blank list. The checkbox remains functional for wallets with balances. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): address PR review — shielded balance, dev tools layout 1. Include shielded balance in the collapsible balance breakdown 2. Add shielded balance to the total wallet balance everywhere 3. Right-align the Dev Tools button in the action row 4. Always show downward arrow on Dev Tools button 5. Dev Tools opens as a vertical dropdown popup instead of a horizontal inline row Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): rename transaction heading, add explorer link, merge zk-fixes - Rename "Transaction History" to "Dash Core Transaction History" - Add "View" button on transactions for Mainnet/Testnet (opens Insight explorer) - Merge zk-fixes (mine dialog, shielded refresh, wallet bug fixes, log level) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ui): trigger shielded sync on wallet refresh and wallet switch The wallet Refresh button now dispatches a SyncNotes task alongside the core wallet refresh when the shielded wallet has been initialized. Switching HD wallets also triggers a full refresh (core + shielded) on the next frame for unlocked wallets. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(log): add diagnostic logging for shielded transfer operations Add tracing::info! and tracing::debug! logs throughout the shielded transfer pipeline to enable post-mortem diagnosis when balances don't update after broadcast. Key additions: - bundle.rs: log amount, input notes, change before building each shielded operation (transfer, shield, unshield, withdrawal, shield-from-asset-lock); log broadcast success with note that balance updates after next block - sync.rs: warn when next_start_index is 0 (full rescan); log post-sync balance with unspent note count - context/shielded.rs: log note spend marking with before/after unspent counts in with_anchor_retry - shielded_send_screen.rs: log task result variant received and post-transfer sync completion Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): improve Dev Tools dropdown layout and refresh mode cycling Right-align popup content to match the Dev Tools button position. Replace ComboBox with a cycle-on-click button to avoid nested popup conflicts in egui, where the inner ComboBox dropdown would close the parent popup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): fix shielded balance conversion, add tab balances, reorder sync status - Fix shielded balance inflating total: divide credits by CREDITS_PER_DUFF before summing with duffs-denominated balances (was showing 5000 DASH instead of 5 DASH) - Show balances in account tab labels: Dash Core (0.9980) | Platform (0.1000) | Shielded (5.0000), with (empty) for zero-balance tabs - Move Sync Status section inside detail panel between balance breakdown and action buttons for better visual flow Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(ui): collapsible sections, move tx history to Dash Core tab, restore account types - Wrap Addresses, Transaction History, Asset Locks, and Shielded Notes in collapsible headers (default open, tx history default closed) - Move Dash Core Transaction History from top-level into the Dash Core tab, between Addresses and Asset Locks - In developer mode, show ALL account category tabs (Bip44, Platform, Bip32, CoinJoin, Identity Registration/System/Top-up/Invitation, Provider) even when no addresses exist for that type - Add TODO for shielded tab layout redesign Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): improve shielded transfer UX messaging for balance update delays After a shielded transfer, the sender's change notes and the recipient's new notes won't appear until the next block is mined and synced. Users see their balance temporarily drop to 0 and think the transfer failed. Add informational messages on all three shielded transfer screens (ShieldedSendScreen, UnshieldCreditsScreen, WalletSendScreen) explaining that balances will update after the next block is confirmed. For shielded-to-shielded transfers, also note that the recipient needs to sync after the next block. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(ui): consolidate dev-mode accounts into System tab, limit balance decimals Replace all individual dev-mode-only account type tabs (Identity Registration, Identity System, Identity Top-up, Identity Invitation, CoinJoin, Provider, Legacy BIP32) with a single "System" tab. Inside the System tab, each account type appears as a collapsible section (collapsed by default) showing address count and balance. Tab order: Dash Core | Platform | Shielded | System (dev-mode only, always last) Additional changes: - Limit tab balance display to max 4 decimal places (was 8) - Rename "Dev Tools" button to "Tools" - Simplify refresh mode button: single button with "Refresh mode: X" text, no separate label or arrow indicator - Remove "Accounts & Addresses" section heading - Style active tab with underline for visual distinction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(shielded): scope commitment tree per wallet for multi-wallet correctness Each wallet's ClientPersistentCommitmentTree now uses its own dedicated SQLite file under <data_dir>/shielded/<hex>.db instead of sharing the main database's global commitment_tree_* tables. This prevents wallets from stepping on each other's Merkle tree state, which caused invalid witnesses and silently rejected transactions. Changes: - SHLD-001: Per-wallet commitment tree DB files via ClientPersistentCommitmentTree::open() - SHLD-002: clear_commitment_tree_for_wallet() scoped to a single wallet - SHLD-007: Safety-net resync guard now logs wallet seed hash before clearing - Migration v34: drops orphaned global commitment_tree_* tables - clear_network_data() deletes per-wallet tree files for the target network Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(shielded): prevent permanent state leak on sync_notes failure in with_anchor_retry When sync_notes() failed after an anchor mismatch, the early `?` return skipped re-inserting the shielded state back into the HashMap, permanently orphaning it until app restart. Now the sync result is captured without early return, ensuring the state is always re-inserted regardless of success or failure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): apply password change for current session even when config save fails The in-memory config update and SDK reinit only ran on the save-success path, silently discarding the password change on save failure despite the banner promising session-level application. Now the in-memory update and reinit run unconditionally; only the banner text varies across the four (save x reinit) outcome combinations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(error): use dedicated TaskError variants for non-build wallet errors reload_utxos() and recalculate_affected_address_balances() errors were routed through shielded_build_error() which pattern-matches for build- specific patterns like "AnchorMismatch". These are wallet operations, not shielded builds. Added WalletUtxoReloadFailed and WalletBalanceRecalculationFailed variants with appropriate user-facing messages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(error): preserve error chain in CoreRpcConnectionFailed chain_lock_rpc_error() took the RPC error by reference and created CoreRpcConnectionFailed with source: None, dropping all diagnostic info. Added a detail: Option<String> field to the variant to carry the formatted error. Boxed the source field to keep the enum size under the clippy threshold. Updated all constructors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(ui): two-column wallet header layout, rename Tools to Advanced Split the wallet detail panel header into left (name, total balance, action buttons) and right (collapsible balance breakdown, sync status) columns. Renamed the "Tools" dropdown to "Advanced" and moved it into the action buttons row instead of right-aligning it separately. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(test): rename test_v33_migration_fresh_install to match DB version 34 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * revert(shielded): use shared main DB for commitment tree instead of per-wallet files Reverts the per-wallet SQLite file approach (63ce0e18). Multiple wallets share the same commitment_tree_* tables in the main database. This is accepted behavior until the SDK adds proper wallet-scoping support (dashpay/grovedb#653). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(shielded): fix stale clear_commitment_tree_for_wallet reference after merge Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(shielded): harden anchor retry, commitment tree clearing, and state guard SEC-002: Verify shielded balance after anchor retry sync before retrying the operation — prevents doomed retries with zero balance. SEC-003: Handle missing commitment tree tables in clear_commitment_tree_tables (grovedb creates them lazily, so DELETEs fail on fresh installs). Fix clear_network_data to log-and-continue when commitment tree clearing fails — these tables are optional and shouldn't block network data reset. Add SEC-001 INTENTIONAL annotation for panic safety in with_anchor_retry. Document shield_from_asset_lock_task: anchor retry is not applicable since it only reads the payment address and doesn't use the commitment tree. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(core): sanitize RPC errors and simplify CoreRpcConnectionFailed SEC-006: Store user-friendly message instead of raw RPC error string in network status when chain lock query fails with a non-auth, non-connection error. CODE-002: Remove redundant detail field from CoreRpcConnectionFailed -- format diagnostic info into the url field instead, keeping source for the error chain. Add SEC-005 INTENTIONAL annotation for RPC errors in status tooltip. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): config permissions, address detection, and per-frame caching SEC-007: Set config file permissions to 0600 on Unix after save (contains RPC credentials). CODE-003: Deduplicate Amount::dash_from_duffs by delegating to dash_from_credits. CODE-006: Remove unused network parameter from AddressKind::detect -- detection is format-based, network validation happens separately. CODE-004/CODE-005: Cache filtered transaction indices per wallet to avoid rebuilding HashSet and filtering on every frame. Invalidated on wallet switch and refresh. CODE-008: Reset AddressInput widgets on network switch so they pick up the new network for validation. Add invalidate_address_input methods to SendScreen, UnshieldCreditsScreen, and WalletsBalancesScreen. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(logging): correct log levels per coding best practices - debug! -> trace!: "state transition built, broadcasting" messages in bundle.rs (5 occurrences) — primary-path step-by-step progress - trace! -> debug!: cookie auth fallback in core/mod.rs and context/mod.rs (2 occurrences) — secondary/fallback execution path, not primary flow - info! -> debug!: anchor mismatch retry in context/shielded.rs — error handling branch, not a business event - info! -> debug!: "marked N note(s) spent" in context/shielded.rs — internal bookkeeping, not a user-visible business event - info! -> debug!: post-transfer sync complete in shielded_send_screen.rs — internal plumbing at screen level Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: minor ui fixes * feat(ui): replace Advanced dropdown with inline right-aligned buttons Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): use allocate_ui_with_layout to right-align dev buttons The right_to_left layout inside with_layout only got the remaining space after left buttons, centering the dev buttons instead of pushing them to the right edge. Using allocate_ui_with_layout with the full remaining width forces the right-to-left block to span the entire remaining area. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ui): eagerly initialize shielded balance on wallet screen open The shielded tab balance only recalculated when the user clicked the Shielded tab because ShieldedTabView was lazily initialized on tab selection. This meant the wallet list and tab header showed zero balance until the user manually visited the tab. Two-layer fix: - Backend: call initialize_shielded_wallet() in handle_wallet_unlocked() so persisted shielded balance is loaded into shielded_states at wallet load time, making it available to all UI screens immediately. - UI: eagerly create ShieldedTabView on wallet screen construction and wallet switch, and tick() it every frame so the init/sync chain (InitializeShieldedWallet -> SyncNotes -> CheckNullifiers) runs even when the Shielded tab is not the active tab. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove stale balance_breakdown_expanded field from cherry-pick Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ui): move action buttons to full-width row below header columns The button bar was inside the left column (55% width), so the right-aligned dev buttons ended up in the middle of the screen. Move render_action_buttons() below the two-column header so it spans the full available width. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(ui): change sync status to bullet-point layout Replace the wide horizontal Platform line (pipe-separated) with individual bullet-point items: Core, Addresses, Notes, Nullifiers each on their own line. Removes the Frame wrapper for a lighter appearance. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(shielded): prevent double balance from redundant init + sync chain When eager init (wallet_lifecycle) populates shielded_states and then ShieldedTabView.tick() dispatches another InitializeShieldedWallet, the idempotency check returns early but handle_result() chains SyncNotes which can re-append notes already loaded from DB. Fix: tick() now checks if shielded_states already contains the wallet's state. If so, it adopts the cached balance and marks itself initialized without dispatching a backend task, preventing the redundant sync chain. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(shielded): move initialization entirely to backend, remove UI init path The shielded wallet had two initialization paths: the backend (handle_wallet_unlocked -> initialize_shielded_wallet) and the UI (ShieldedTabView::tick dispatching InitializeShieldedWallet). This created redundant init attempts and blurred the responsibility boundary. Now: - handle_wallet_unlocked initializes the shielded state synchronously, then queues SyncNotes -> CheckNullifiers as a background task - ShieldedTabView reads its state from AppContext::shielded_states via refresh_from_backend_state(), never dispatching init itself - The "Initialize Shielded Wallet" button and WalletUnlockPopup are removed from ShieldedTabView (wallet unlock already calls handle_wallet_unlocked which handles everything) - Resync Notes (developer mode) still dispatches InitializeShieldedWallet as an explicit user action Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(shielded): deduplicate notes in sync to prevent double balance When the commitment tree resets (empty/corrupt) but persisted notes remain in the DB, initialize_shielded_wallet loads notes from DB while last_synced_index falls to 0. The subsequent sync rescans from position 0 and re-appends all decrypted notes — duplicating the ones already in memory from DB, doubling the balance. Fix: sync_notes() now checks if a note at the same position already exists in shielded_state.notes before appending, preventing duplicates regardless of the last_synced_index value. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(ui): add shielded diversified address table Replace the single-address display with prev/next buttons in the Shielded tab with a collapsible address table showing all generated diversified addresses. Each row shows the index, truncated bech32m address (click or Copy button to copy full address), and status (Default for index 0). The section is collapsed by default in normal mode and expanded in developer mode. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address review findings — duplicate controls, perf, tab visibility - Remove duplicate sync status/buttons block in shielded_tab.rs - Fix BIP44 accounts with index > 0 being invisible (regression) - Fix early return in render_account_tabs blocking Shielded tab for new wallets - Fix double-tick of ShieldedTabView per frame when tab is active - Fix system_tab_sections O(n*m) perf by precomputing address counts - Track queue_shielded_sync via subtasks for graceful shutdown - Use HashSet for O(1) duplicate note check in sync (was O(n^2)) - Fix balance_breakdown default_open to respect developer mode - Fix is_system_category to delegate to is_visible_in_default_mode - Fix doc comment for system_tab_sections sort order claim - Narrow #[allow(dead_code)] to specific field on AccountSummary Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ui): replace mutex unwrap() with graceful error handling in shielded tab Replace .lock().unwrap() calls on shielded_states with .lock().ok() patterns throughout ShieldedTabView to prevent UI panics on poisoned mutex. Shows fallback messages or skips updates gracefully. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(mcp): resolve async lifetime errors for Rust 2024 edition Use spawn_blocking + block_on in dispatch_task to avoid Send bound issues with platform SDK types (DataContract/Sdk references across await points). Same pattern already used by AppState::handle_backend_task. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: fix mcp dispatch * doc: remove obsolete manual teting docs * fix(ui): include zero-balance addresses in AddressInput wallet entries Pull Core addresses from known_addresses (all derived) instead of address_balances (only funded). Balance is looked up from address_balances, defaulting to 0. Callers use with_balance_range(1..) when they need funded-only addresses. Fixes Mine dialog showing empty dropdown for fresh/zero-balance wallets. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): sort AddressInput wallet entries alphabetically Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * revert: remove redundant sort in extract_wallet_entries filtered_entries() already sorts alphabetically by display_label. The sort added in extract_wallet_entries was redundant. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): clarify shielded network validation logic Replace double-negation with explicit mainnet-class comparison. Documents that testnet/devnet/local share the same HRP ("tdash1z") and cannot be distinguished at the address level. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): include send-all transactions in wallet history Check both outputs and inputs when filtering transactions. Build an OutPoint→Address lookup from the wallet's own transactions to resolve input addresses. Send-all transactions (no change output) were previously dropped because no output matched known_addresses. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): use is_ours flag for transaction filtering Simplify transaction filter to use the is_ours flag instead of address matching. Fix SPV path to set is_ours=true for all wallet transactions (upstream only sets it for sends). SPV history is per-wallet, so all entries are ours by definition. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(e2e): verify is_ours flag for SPV send and receive transactions Sends funds between two wallets via SPV and asserts that both the sender (negative net_amount) and receiver (positive net_amount) have is_ours=true. Validates the fix to the SPV reconcile path that overrides the upstream library's send-only is_ours logic. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(core): clear RPC error state on successful chain lock fetch When get_best_chain_lock() succeeds on the active network, explicitly clear any lingering RPC error in ConnectionStatus so the connection indicator recovers after a transient outage. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(model): move is_platform_address_string from UI helpers to model layer AddressKind::detect() in the model layer depended on crate::ui::helpers for platform address detection — wrong layering. Move the function to src/model/address.rs and keep a re-export in helpers for existing callers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): add actionable guidance to address validation error messages Each validation error now tells the user what to do, not just what went wrong: "check for typos", "check you are using the correct network", "use all lowercase". Messages that already had guidance are unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): invalidate address inputs on all screens during context switch TransferScreen, AddressBalanceScreen, and ShieldedSendScreen retained stale address text from the previous network after a context switch. Add invalidate_address_input() to each and call it from change_context(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): clear password field on network switch when no config exists When switching networks, if no config entry exists for the new network (or the password is empty), the password input field now clears instead of retaining the previous network's password. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): clear validated_destination in invalidate_address_input WalletSendScreen and UnshieldCreditsScreen cleared the AddressInput widget but left validated_destination with stale validation from the previous network. Now both are cleared together. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): consume ShieldedNotesSynced to update shielded send screen state After a shielded transfer, the post-transfer note sync result was only logged but never used to update the UI. Now ShieldedNotesSynced updates the balance display, clears the pending-update banner, and appends the remaining balance to the success message. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: apply cargo +nightly fmt formatting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(model): disambiguate Core vs Identity address detection by prefix Core addresses on Dash start with X/Y (mainnet) or y/8/7 (testnet). When the input starts with a known Core prefix, try Core first. Otherwise try Identity first, since Identity IDs use the same Base58 alphabet but don't have Core address prefixes. This fixes identity-only AddressInput mode rejecting valid Identity IDs that happened to parse as Core addresses, and eliminates the flaky test that had to skip Core-looking random identifiers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(core): show actual RPC error on Networks page instead of generic message The Networks page is a debugging tool — users need the real error text to diagnose Dash Core issues. Replace sanitized "RPC error — check Dash Core status" with the actual error from dashcore_rpc. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(db): resolve deadlock in clear_network_data clear_network_data() held self.conn.lock() for the transaction, then called clear_commitment_tree_tables() which tried to acquire the same mutex — classic non-reentrant mutex deadlock. UI froze on "Delete Data". Fix: scope the connection lock so it's released before the commitment tree clearing acquires it again. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): replace mutex unwrap() with graceful .ok() in shielded_sync_task The shielded_states mutex lock used .unwrap() which would panic on a poisoned mutex. Replace with .ok()? to return None gracefully. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): replace RwLock unwrap() with graceful error handling in shielded tab Replace wallets.read().unwrap() with .read().ok() and show an error label instead of panicking on a poisoned RwLock. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(spv): add debug log when overriding is_ours for receive transactions Log at debug level when SPV reports is_ours=false for a receive transaction (net_amount >= 0) to detect upstream API behavior changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(shielded): log warning on note value divergence during position dedup Use a HashMap<position, value> instead of HashSet<position> so we can detect when a re-synced note at an existing position has a different value — indicating potential data integrity issues. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * perf(model): avoid allocation in is_platform_address_string Replace to_lowercase() with eq_ignore_ascii_case() for the HRP prefix check. The HRP constants are ASCII-only so this is safe and avoids a heap allocation on every call. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(ui): remove unused label field and is_key_only from AccountSummary The label field was constructed but never read outside the builder. is_key_only() had no callers. Remove both instead of suppressing dead_code warnings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(ui): avoid cloning full AccountTab enum in tab content match Extract only the category data (clone of AccountCategory + index) before the match instead of cloning the entire enum. This avoids unnecessary allocation for the Shielded and System variants. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): clear validated_address on network switch in mine dialog invalidate_address_inputs() cleared the AddressInput widget but left the validated_address stale, which could reference an address from the previous network. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(model): move truncate_address to model layer and document ASCII precondition Move truncate_address from address_input.rs and shielded_tab.rs into src/model/address.rs as a parameterized public function. Both callers now delegate to the shared implementation with their own prefix/suffix lengths. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs(shielded): document why spawn_blocking trampoline is needed in queue_shielded_sync The spawn_blocking(block_on(...)) pattern is required because async methods on Arc<Self> produce non-'static futures due to a known Rust compiler limitation (rust-lang/rust#100013). Document this to prevent future removal attempts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: apply nightly rustfmt formatting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(wallet): bootstrap platform addresses on wallet creation bootstrap_wallet_addresses only ran when known_addresses was empty, but new_from_seed already derives one Core address. This meant platform payment addresses were never bootstrapped for new wallets — only populated later by network sync (which returns nothing for fresh wallets with no on-chain activity). Now checks for the presence of PlatformPayment addresses in watched_addresses and runs bootstrap if missing. Bootstrap functions are idempotent (add_address_if_not_exists). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): show bootstrapped platform addresses in AddressInput Platform addresses were only populated from platform_address_info (network sync results). Fresh wallets with no on-chain activity had empty platform_address_info, so no platform addresses appeared in the Send screen autocomplete. Now derives platform addresses from watched_addresses (which contains all bootstrapped platform payment addresses), with balance looked up from platform_address_info (defaulting to 0). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(ui): pass account filter directly to render_address_table Remove the `selected_account` field that was mutated as a side-effect during rendering, causing the last-rendered collapsible section in the System tab to overwrite previous sections' state. Now each render call receives its `(AccountCategory, Option<u32>)` as a direct parameter. Additionally, `system_tab_sections` now builds per-(category, index) pair so multi-index accounts show correct per-index balance and address counts instead of aggregating across all indices. Fixes CODE-005, CODE-011, PROJ-004. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * perf(shielded): move wallet initialization to background thread ZIP32 key derivation and DB reads in `initialize_shielded_wallet` ran synchronously on the UI thread during `handle_wallet_unlocked()`. Move the call to a `spawn_blocking` task tracked by `subtasks` so the UI remains responsive. The shielded tab already handles the "initializing" state gracefully via its `is_initialized` check. Fixes CODE-010. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(spv): add unit tests for is_ours override logic Extract the SPV is_ours override into a named function and add four unit tests covering: outgoing already-ours, incoming not-ours (the main override case), zero-amount edge case, and outgoing not-ours. A comment explains why bloom filter false positive testing requires mocking the SPV layer and is out of scope for unit tests. Fixes PROJ-002. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs(user-stories): add stories for wallet tab redesign Add WAL-021 through WAL-024 covering the tab-based navigation, developer-mode System tab, collapsible transaction history, and collapsible balance breakdown sections introduced in PR #791. Fixes PROJ-007. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: apply nightly rustfmt formatting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(ui): distinguish change addresses in AddressInput autocomplete Core addresses now show "(change)" suffix for BIP44 change addresses (m/44'/5'/0'/1/x). New with_exclude_change(true) builder method filters them out — send inputs should not display change addresses since users don't share them with others. Change detection uses the existing DerivationPathHelpers::is_bip44_change trait method from the wallet model. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): exclude change addresses from Send screen destination input Change addresses are internal wallet addresses not meant to be shared. The Send screen's destination AddressInput now uses with_exclude_change(true) to filter them out of the autocomplete. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(ui): add new address generation button to wallet tabs Add right-aligned compact buttons below the address table for generating new addresses on Core (BIP44) and Platform tabs. Core addresses use the async BackendTask path for SPV compatibility; Platform addresses derive synchronously. The button is disabled during generation to prevent double-clicks. Shielded tab already has its own button; System tab has no button (addresses are derived automatically). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): exclude system addresses from AddressInput autocomplete System addresses (Identity Registration, CoinJoin, Provider keys, etc.) are internal wallet infrastructure not meant for user-facing send/receive operations. Filter them out by checking each address against watched_addresses path_reference → AccountCategory. Only BIP44 (Core) and PlatformPayment addresses are shown. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(ui): always show address type label, enable type-based filtering Address type suffix (Core/Platform/Shielded/Identity) is now always shown in autocomplete entries, not just when multiple types are enabled. Users can type "platform", "core", "plat", etc. to filter entries by type — the filter matches against both short_label() and display_name(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(error): add user-friendly message for shielded nonce mismatch Detect AddressInvalidNonceError from Platform and show an actionable message: "The transaction used an outdated sequence number. Please retry — the wallet will use the correct number automatically." Previously this fell through to the generic ShieldedBroadcastFailed message which gave no guidance. Upstream fix filed: dashpay/platform#3407 (SDK auto-retry on nonce mismatch). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: fix stable rustfmt formatting for address_input filter Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): compare WalletSeedHash in shielded ScreenType equality Wildcard matches caused all shielded screens to be considered equal regardless of which wallet they belonged to, breaking multi-wallet navigation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): reset all transient state in ShieldedTabView::update_seed_hash Switching wallets left stale initializing/syncing flags, pending tasks, and address count from the previous wallet, causing UI glitches. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(error): replace string-based SPV payment errors with typed TaskError variants Add WalletInfoUnavailable, MissingBip44Account, and ChangeAddressDerivation variants to TaskError, replacing ad-hoc WalletPaymentFailed { detail: String } usage for these three recurring error patterns in SPV payment code. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(perf): skip Halo2 proving key warmup in test builds The ~30s background thread for proving key generation is unnecessary during tests and slows down the test suite. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): delegate is_platform_address and rewrite hints as full sentences is_platform_address now delegates to is_platform_address_string for consistent case-insensitive HRP matching. Address hint constants are rewritten as complete, i18n-extractable sentences. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): sanitize raw RPC error strings in connection status display Strip OS-level error details like "(os error 111)" and transport error wrappers before storing in ConnectionStatus, so users see clean messages instead of raw system error text. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(error): rewrite SPV error messages for everyday users - WalletInfoUnavailable: "wallet is still loading" instead of technical jargon - MissingBip44Account: "needs to be refreshed" instead of "BIP44 account" - ChangeAddressDerivation: "could not prepare transaction" instead of "derive change address" - sanitize_rpc_error: fix doc/impl mismatch, strip "RPC error:" prefix, remove redundant "RPC: " output prefix Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Quantum Explorer <quantum@dash.org> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: lklimek <lklimek@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Summary
Redesigns the wallet screen layout from a vertically-stacked ComboBox-driven architecture to a tab-based information architecture with progressive disclosure.
User Story
Imagine you are a Dash user opening the Wallets screen. Instead of a dropdown to switch between account types, you now see clear tabs — Dash Core, Platform, Shielded — letting you instantly navigate between your different balance types. Your transaction history is neatly collapsible, and if you're a developer, a Dev Tools button gives you quick access to test faucets, mining, and refresh controls without cluttering the main interface.
Changes
Files Changed
src/ui/wallets/account_summary.rs— renamed labels, addedtab_label(),is_visible_in_default_mode()src/ui/wallets/wallets_screen/mod.rs— restructured rendering flow, tab system, collapsible sectionsTest plan
🤖 Co-authored by Claudius the Magnificent AI Agent
Summary by CodeRabbit
New Features
Bug Fixes
Improvements
Tests
Documentation