feat: disable refresh button in dpns screen when already refreshing - #83
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
…ty switcher (#842) * docs(ux): add identity + dashpay redesign wireframe Collapse today's separate Identities and Dashpay nav entries into a unified Identities section with Home / Contacts / Activity / Settings tabs, a two-pill Wallet + Identity switcher, and an identity-first naming model where DashPay profile is an optional social-profile overlay. Adds wireframe.html (8 frames, persona + theme toggles), design-spec.md (IA, screen-by-screen, wording audit, tooltip catalog), and README.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(ux): address review findings for identity redesign wireframe Fixes Diziet and Adams audit findings: - Gate F4/F8 Advanced blocks so Alex never sees raw IDs (.adv class) - Wire aria-describedby on ~60 tooltip triggers (screen-reader access) - Add tooltips to identity-type, network, and connection badges - Add .alex-only CSS gate + class on insight banner (P3) - Drop protx: jargon prefix from Masternode ID chips in F4 and F8 - Adopt spec wallet-pill phrase "Funded by {wallet_name}" across all frames - Add topbar Refresh icon-button to F3/F4/F5/F6/F7/F8 - Fix F3/F4 switcher pills: tooltip wrappers + aria-haspopup parity - Add secondary actions row (Add funds / Send to wallet / Send to another identity) on F3 and F4 - Remove dead enabled-Review markup (FG4) - Wording: "No social profile yet", skip-link, funding subtitle, activity subtitle - Nits: dead white-space rule, redundant inline style, redundant font-weight load - Spec: Shadow alpha intentional deviation documented in §E and §F - Spec: §B.9 Add-funds wizard with all 4 funding methods and persona gating - Spec: §B.10 Create-identity wizard flow - Spec: §B.11 Load-existing-identity with all 3 load modes - Spec: §B.8 Voter-identity keys (Masternode/Evonode), Local nickname, Auto-accept-proof - Spec: §B.2/B.3 secondary actions row authoritative with entry-point rationale - Spec: §B.5 Add-by-username accepts raw Identity ID - Spec: §B.13 Pick-a-username with contested detection, fee preview, vote explanation - Spec: Onboarding checklist steps enumerated with per-step visibility rules - Spec: Identity pill dropdown ordering rule + inline search threshold in §A.3 - Spec: §G closed questions G6-G9 for deferred and decided items - README: design-decisions section for shadow alphas, secondary actions, local nickname - Catalog §D: entries #83-86 for secondary Home actions and topbar refresh Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(ux): add identity picker, collapse switcher to one row - Shorten wallet-pill copy from "Funded by Main Wallet" to "Main Wallet" - Collapse wallet + identity switcher into a single horizontal row - Add F3 Identity picker grid as default landing when ≥2 identities exist with per-identity cards (avatar/monogram, name, balance, type pill) and an "Add a new identity" card - Spec §A.4 default-landing rules (0/1/≥2 identities); §B.14 picker screen Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(ux): breadcrumb switcher, populated Contacts, reorder frames - Fold wallet + identity switcher into the breadcrumb itself; placeholders "(no wallet yet)", "(no identity yet)", "(choose an identity)" when empty - Remove the separate switcher row under the breadcrumb from all frames - Move App chrome zoom to the last frame (F8) and rename to "App chrome reference" - Consolidate F3 Identity Home: one canonical frame covering both social-profile-set and no-social-profile states via annotation - Replace Contacts gated-state frame with a populated Contacts page showing received requests (2), active contacts (5), and sent requests (2) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(identity-hub): Phase 1 planning artifacts Requirements, UX plan, test-case spec, and dev plan for the new Identities hub UI section (4-tab hub: Home/Contacts/Activity/Settings) derived from docs/ai-design/2026-04-22-identity-dashpay-redesign/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(identity-hub): add feature flag, enum variant, and module scaffold Adds the foundation for the unified Identities hub UI section (docs/ai-design/2026-04-23-identity-hub-impl/). This commit only introduces the compile-time scaffold; subsequent commits wire up the left-nav entry, AppState registration, and per-tab content. Changes: - New Cargo features `identity-hub` (default on) and `identity-hub-activity-feed` (default off, gates the unified activity timeline backend aggregator that does not exist yet). - New `RootScreenType::RootScreenIdentityHub` variant with stable on-disk encoding `27` and round-trip tests. - New `ScreenType::IdentityHub` and `Screen::IdentityHubScreen` variants; all `ScreenLike` dispatch arms plus `change_context` extended. Macro `set_ctx!` receives the new variant via the `skip` list since the explicit match arm in `change_context` already handles it. - New `src/ui/identity/` module with a `ScreenLike` implementation that dispatches by loaded-identity count (onboarding/home/picker) and renders a placeholder tab bar plus per-tab stubs. The module is unconditionally compiled so the enum dispatch stays exhaustive; the `identity-hub` feature only controls nav visibility, which lands in a follow-up commit. - Eight unit tests covering tab ordering, labels, accessible descriptions, default variant, `HubLanding` state transitions, and `RootScreenType` round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(identity-hub): wire left-nav entry and AppState registration Completes the coexistence wiring for the new Identities hub. Users now see three identity-related entries in the sidebar: - `Dashpay` (legacy, feature-gated via existing FeatureGate) - `Identities` (legacy identities screen, unchanged) - `Identity Hub` (new, only when the `identity-hub` feature is on) Feature gating at the Cargo level — not a runtime FeatureGate — because the new hub doesn't share a predicate with the other entries. The entry is inserted in the button array immediately after the legacy `Identities` entry so the three identity-related items cluster together. AppState::new() inserts an `IdentityHubScreen` into `main_screens` via an iterator chain that is empty when the feature is disabled, so the screen map never contains an unreachable entry. No existing screen is modified. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(identity-hub): add breadcrumb pill and identity pill components Introduces two reusable components for the Identities hub breadcrumb switcher (design-spec §A.3): - `BreadcrumbPill` — label + optional icon + chevron, three visual modes (Interactive / Subdued / Placeholder). Self-contained theming for light + dark mode. Builder methods for icon, tooltip, accessible name, and mode override. - `IdentityPill` — thin wrapper that resolves the identity label via the priority rule: Local nickname → DPNS username → shortened Identity ID (design-spec §G6). Label resolution is a pure function (`display_label`) so it is unit-testable without egui context. Both components follow `docs/COMPONENT_DESIGN_PATTERN.md`: private fields with builder methods, a `ComponentResponse`-implementing response struct, no direct egui state leakage. 16 unit tests added: mode toggling, label priority ordering (nickname/DPNS/id, empty, whitespace), raw-id shortening (head 5 + "…" + tail 3), and response round-trip. No existing code paths modified; the components are exposed via `src/ui/components/mod.rs` and can now be consumed by the hub (breadcrumb switcher composition in a follow-up). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(identity-hub): add kittest integration tests for hub scaffold Three integration tests register the new hub with AppState and verify: 1. `identity_hub_mounts_and_renders` — AppState with the hub selected as the active root screen renders ten frames without panicking on the default empty database (onboarding path). 2. `legacy_nav_entries_coexist_with_hub` — verifies the enum contains all three coexisting variants and the on-disk encoding for the new hub variant round-trips through `from_int` / `to_int`. 3. `identity_hub_screen_type_creates_hub_screen` — guards against a refactor that drops the hub case from the `create_screen` dispatch. These are the minimum acceptance tests for the current scaffold. The per-tab assertions (IT-HOME-01, IT-CONTACTS-01, IT-ACTIVITY-01, IT-SETTINGS-01 from the test-case spec) arrive alongside each tab's content in follow-up commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(identity-hub): update components catalog and user stories - `src/ui/components/README.md`: new section documenting `BreadcrumbPill` and `IdentityPill` with their label priority rule and modes. - `docs/user-stories.md`: new IDH section with six stories covering first-time setup, identity home, multi-identity switching, optional social profile, dev-mode bulk creation, and the gated unified activity timeline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(identity-hub): address CodeRabbit review findings on PR #842 Five Major findings + several Minor docs fixes. Majors: - `src/app.rs` — build the `main_screens` BTreeMap first, then resolve `selected_main_screen` by checking the built map. Falls back to `RootScreenIdentities` if the persisted value is not registered. This prevents `active_root_screen_mut()` from panicking when a user previously selected the hub and the `identity-hub` feature is later disabled. - `src/ui/components/breadcrumb_pill.rs` — use `.inner` (the Label's Response) instead of `.response` (the Frame's outer Response) to capture clicks. In egui, `Frame::show(...).response` only senses `Sense::hover` — child-widget click sensing is not inherited, so the previous code silently broke click detection on all interactive pills. The inner Label has `.sense(Sense::click())` applied; reading from it makes the click fire as intended. - `src/ui/components/identity_pill.rs` — refactor to the lazy-init component pattern. Previously the struct eagerly stored a `BreadcrumbPill`; now it stores the domain fields (local_nickname, dpns_handle, identity_id_base58) plus builder-set options (tooltip, accessible_name, mode) and constructs the inner `BreadcrumbPill` inside `show()`. Returns a new `IdentityPillResponse` implementing `ComponentResponse` instead of leaking `BreadcrumbPillResponse`. Also: `display_label` now falls back to a stable `Unknown identity` placeholder when given an empty id, so the pill is never invisible. - `src/ui/identity/hub_screen.rs::landing()` — stops swallowing load errors via `unwrap_or(0)`. On failure it surfaces a calm `MessageBanner` ("Could not load your identities from this device. Try refreshing or reopening the app.") with the error details attached via `BannerHandle::with_details`, and reuses the last-known- good landing so a real zero-identity account is still distinguishable from a broken one. - `src/ui/identity/hub_screen.rs::impl ScreenLike` — adds explicit `refresh`, `refresh_on_arrival`, `display_message`, `display_task_result`, and `display_task_error` implementations. `refresh` clears any stale load-error banner so the next `landing()` attempt can try again cleanly. The others are scaffold no-ops with comments explaining why. Minors: - `tests/kittest/identity_hub.rs`: guard against the correct legacy DashPay root variant (`RootScreenDashpay`), not the sub-screen `RootScreenDashPayProfile`. - `src/ui/identity/mod.rs`: feature-gate note now documents BOTH integration sites (`left_panel.rs` nav entry + `app.rs main_screens` registration) so future changes cannot accidentally produce unreachable variants. - `src/ui/components/breadcrumb_pill.rs`: `BreadcrumbPillResponse::new` promoted to `pub(crate)` so the `IdentityPill` wrapper and tests can fabricate responses without running egui. - `docs/ai-design/2026-04-23-identity-hub-impl/`: corrected feature names (`identity-hub` / `identity-hub-activity-feed`, not underscored) across ux-plan, test-case-spec, and dev-plan. Fixed dev-plan step 6 to point at `src/ui/mod.rs` (where `RootScreenType` lives) rather than `src/database/settings.rs`. - `docs/user-stories.md`: added Identities Hub TOC entry; downgraded IDH-002..005 from `[Implemented]` to `[Gap]` since only the scaffolds ship in this PR (follow-up work does the tab content). Tests: 485 lib + 75 integration + 3 kittest passing. `cargo clippy --all-features --all-targets -- -D warnings` clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(identity-hub): add tab bar and onboarding empty state Introduce the four-tab horizontal bar for the Identities hub and wire the onboarding empty state as the landing view when no identities are loaded. The new `IdentityHubTabBar` component follows the project component pattern (private fields, builder methods, `ComponentResponse`-based response) and reuses existing theme tokens only — `DashColors::DASH_BLUE` for the selected fill, `border_light` outline for unselected tabs, and `Typography::SCALE_SM` / `Shape::RADIUS_MD` / `Spacing::SM` for layout. Each tab surfaces `IdentityHubTab::accessible_description()` as its clickable tooltip for screen-reader parity. `IdentityHubScreen` now renders the new bar in place of the scaffold's inline `selectable_label` preview. Selection still lives on the screen, mirroring the existing controlled-component pattern. Onboarding copy was already in place per T3 scaffolding; this change keeps the strings verbatim from design-spec §B.1 and confirms the developer-mode footer is gated on `AppContext::is_developer_mode()`. Tests: - UT-TABS-01 — unit test asserts the bar's selection contract through its `ComponentResponse`, plus builder / default-state coverage. - IT-ONBOARD-01 — new kittest under `tests/kittest/identity_hub_onboarding.rs` mounts `AppState`, forces `RootScreenIdentityHub`, and asserts the heading + both CTAs render while the developer-mode footer stays hidden on the default persona. Refs: docs/ai-design/2026-04-23-identity-hub-impl/04-dev-plan.md (T5), docs/ai-design/2026-04-23-identity-hub-impl/03-test-case-spec.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(identity-hub): add identity picker grid Implements T7 from the identity-hub dev plan. - New IdentityPickerCard component: avatar/monogram, identity-type badge, heading (display_name -> DPNS -> shortened id), sub-line, tabular balance, wireframe "Opens Identity Home" hint. Full card is a single click target; response carries the identity id. - New IdentityPickerAddCard component: dashed border default, solid Dash-blue on hover, fixed design-spec strings, click reports add_requested. - Picker grid now renders a responsive flow of identity cards followed by the add card, auto-fitting columns based on available width (design-spec rule minmax(260px, 1fr)). Add card click routes to the existing AddNewIdentityScreen via AppAction::AddScreen - no new screen introduced. Unit tests: UT-PICKER-01/02/03 plus heading/sub-line edge cases, response round-trip, column-count monotonicity. 17 new tests, all passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(identity-hub): add Activity tab shell with filter chips T10 ships the Activity tab shell and its reusable row component: - `src/ui/components/activity_row.rs` — a 48 px compact row with `Payment`, `Funding`, and `PlatformOp` kinds and `Normal`, `Expanded`, and `Failed` statuses. `Failed` rows render a `Retry` small button and a danger-stroke border, matching design-spec §B.6. The component reports `ToggleExpand` or `Retry` actions via `ComponentResponse`. - `src/ui/identity/activity.rs` — filter-chip row (All / Payments / Funding / Platform) with `All` as the default reset, plus a gated empty state. When `identity-hub-activity-feed` is off (default) the tab points users to the legacy DashPay Payments screen; when on, it renders an aggregator placeholder — no new backend aggregator is introduced (additive-only rule). Tests: - UT-ACTIVITY-ROW-01 — covers Normal, Expanded, and Failed render paths with Retry-button presence assertion (plus 6 smaller unit tests over response semantics and builder composition). - IT-ACTIVITY-01 — kittest asserting the three required filter chips and the gated empty-state copy render on the default feature set. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(identity-hub): add Contacts tab with gated + populated states Add T9 of the identity-hub implementation plan: the Contacts tab renders either a centered social-profile gate card (when the active identity has no DashPay profile) or a three-section populated shell (received · active · sent) per design-spec §B.4 / §B.4.1. New flat components in `src/ui/components/`: - `social_profile_gate_card` — centered card with handle-aware body, `Add a display name` primary CTA, and a toggleable `Why?` panel. - `request_card` — received (amber strip + Accept/Decline) and sent (blue strip + Pending pill + Cancel request) variants. - `contact_row` — clickable list row with avatar monogram, display name, `@handle`, optional last-payment hint, and Send + overflow actions; response carries the contact id for click routing. The populated-state shell dispatches the existing `DashPayTask::LoadContacts` via `AppAction::BackendTask` — no new backend variants are added (explicitly scoped out for T9). Interactive accept / decline / cancel flows are deferred to T10 with inline TODO markers. Tests: - UT-GATE-01, UT-REQUEST-CARD-01, UT-CONTACT-ROW-01 (unit). - IT-CONTACTS-01 (kittest) — mounts `contacts::render_gated` directly and asserts the gate heading + primary button render while the populated section headings stay absent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(identity-hub): add Home tab with hero and onboarding checklist Implements T8 from the identity-hub dev plan: - `IdentityHeroCard` — gradient hero (DASH_BLUE → PLATFORM_PURPLE at 14 %, RADIUS_XL, Shadow::elevated()) with two variants: social-profile-set (96 px avatar + display name + @handle) and no-social- profile (type-glyph monogram + optional `Pick a username` prompt). Renders an identity-type badge pill and optional network pill. Follows `docs/COMPONENT_DESIGN_PATTERN.md` with private fields, builder methods, and a response struct implementing `ComponentResponse`. - `OnboardingChecklist` — three-step strip (Pick a username · Set a display name · Add your first contact) with check-mark / empty-circle bullets and a dismiss button. Response carries activation / dismissal intent. - Identity Home tab (`src/ui/identity/home.rs`) — full wiring: hero card, Send / Receive / Add contact quick actions (Add contact gated behind a social profile per §B.3), Add funds / Send to wallet / Send to another identity secondary ghost actions, inline `Set up your social profile` card in the no-profile variant, the onboarding checklist (hidden once dismissed or complete), a recent-activity preview (empty-state for now; wired to flip to the Activity tab), and an Advanced details expander listing raw Identity ID, revision, and key count. - `IdentityHubScreen` owns a small `HomeState` (dismiss flag, skip-social flag, advanced toggle) so tab switches don't wipe per-tab UX state. Dismissal is ephemeral in memory — no DB schema change. Tests (UT-HERO-01..02, UT-CHECKLIST-01..02): 18 unit tests across the two new components + 7 tests in the home module cover the state machine and credit-to-DASH formatter. Kittest IT-HOME-01 mounts the hub, asserts the Home outcome API surface, and pins the four-tab label order. Strings are copied verbatim from design-spec §B.2 / §B.3 / §C / §D. All design choices documented in module-level comments. No backend_task changes — tab dispatches reuse existing TransferScreen / TopUpIdentity / WithdrawalScreen / RegisterDpnsName screens until the dedicated Send sheet (§B.7) lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(identity-hub): add Settings tab Implements T11 of the identity-hub impl plan. Renders a two-column layout inside the central island: social profile (left) and username + aliases (right), with a full-width Advanced expander below that contains identity type + raw ID, keys summary, refresh action, and a danger-zone card. Backend integration is strictly additive — the save path dispatches the existing DashPayTask::UpdateProfile, the refresh path uses IdentityTask::RefreshIdentity, and the register-username CTA routes to the existing RegisterDpnsNameScreen. Controls without a matching backend task (Delete social profile, Add / Remove / Make-primary alias, Unload identity from this device) are rendered as non-interactive affordances with disabled_tooltips explaining that the action is coming, and marked with TODO(identity-hub) comments so the backend follow-up can search for them. Copy comes verbatim from the design spec (§B.8 and §D tooltip catalog). The hub screen now owns a SettingsTab and dispatches through its stateful render so edit drafts persist across frames. Tests: - 10 unit tests in src/ui/identity/settings.rs covering validation thresholds, dirty tracking, string helpers, and the identity-type badge. - One kittest in the same module asserts the three required section headings (Social profile / Username / Aliases / Advanced) render via a build_ui harness — this covers the IT-SETTINGS-01 label assertions without bootstrapping a full identity fixture. - IT-SETTINGS-01 in tests/kittest/identity_hub_settings.rs exercises the AppState-level mount path on the Settings tab and verifies the hub continues to render without panicking on a fresh database. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(identity-hub): debounce Contacts LoadContacts dispatch to once per tab entry The Contacts populated shell previously dispatched `DashPayTask::LoadContacts` on every paint — flooding the backend channel and hammering the SDK. Introduce `ContactsState` owned by the hub with a `load_requested` flag set on first dispatch; reset on tab switch or `refresh_on_arrival`. This keeps the dispatch additive (no new backend task variant) while making it safe to paint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(identity-hub): add SwitchIdentityHubTab AppAction and wire Contacts gate deep link Introduce a feature-gated `AppAction::SwitchIdentityHubTab` variant that lets in-hub deep links hop between sub-tabs through the normal action-dispatch channel, rather than coupling sibling tabs to each other. `AppState::update` resolves the currently-visible screen and, when it is the Identity Hub, forwards the tab switch via `IdentityHubScreen::select_tab`. Wire the Contacts tab's social-profile gate card to emit `SwitchIdentityHubTab(Settings)` when the user clicks the primary CTA — that is where display name and avatar editing lives. The T8 Home-tab "See all activity" link already hops tabs synchronously via `HomeOutcome`, so no additional deep-link plumbing is needed there. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(identity-hub): clarify Contacts accept/decline/cancel wiring plan T9 shipped the `RequestCard` component with accept/decline/cancel response flags, but the contacts tab currently renders only empty-state placeholder copy — there is no request data feeding the component yet. Document where the wiring belongs: `AcceptContactRequest` / `RejectContactRequest` backend variants exist; a `CancelContactRequest` variant does not and is explicitly deferred to a later wave per integration constraints. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(identity-hub): update components reference and user stories - Add Identity Hub components section to `src/ui/components/README.md` covering the nine new components shipped in Wave 1: `IdentityHubTabBar`, `IdentityHeroCard`, `OnboardingChecklist`, `IdentityPickerCard`, `IdentityPickerAddCard`, `SocialProfileGateCard`, `RequestCard`, `ContactRow`, `ActivityRow`. - Flip IDH-002 (Home at a glance) and IDH-004 (social-profile opt-in) from `[Gap]` to `[Implemented]`. IDH-003 and IDH-006 remain `[Gap]` because the breadcrumb switcher composition and the unified activity aggregator are deferred. IDH-005 (dev bulk creation) unchanged — still gap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(identity-hub): move hub-specific components to src/ui/identity/ Applies the revised placement rule: `src/ui/components/` now hosts only widgets with a plausible second consumer outside the Identities hub. Hub-specific widgets move next to the tab modules that consume them. Moved (git mv) into `src/ui/identity/`: - identity_hub_tab_bar - identity_hero_card - onboarding_checklist - identity_picker_card - identity_picker_add_card - identity_pill - social_profile_gate_card - request_card - contact_row - activity_row `breadcrumb_pill` stays in `components/` — it is a generic label + icon + chevron pill usable anywhere a breadcrumb exists. Import sites updated from `crate::ui::components::X` to the matching `super::X` / `crate::ui::identity::X` path. `components/README.md` rewritten to document the new placement rule; a new `identity/README.md` catalogs the hub-local widgets. No behavioural change — pure module relocation. Co-Authored-By: Claudius the Magnificent <noreply@anthropic.com> * fix(identity-hub): wire dead buttons on Home/Contacts/Activity The hub shipped dead-on-arrival in PR #842 Wave 2: every click on the Home tab produced no visible effect because `hub_screen.rs::ui` unconditionally returned `AppAction::None` from the central-island closure. The tab modules computed a real `AppAction` in their match arms but `ui.vertical_centered(|ui| { ... })` discarded the value; the closure then returned `AppAction::None` explicitly. Send, Receive, Add funds, Send to wallet, Send to another identity, Change photo, Save social profile, Register a username, Add a new key, Refresh identity — all produced no action. Also dead: the three Contacts header buttons (`Add by username`, `Scan QR`, `Show my QR`) had no click handling at all. The populated-state `Add by username` in the active-contacts section had the same gap. The Activity tab rendered the DashPay Payments hint as plain text rather than a link. Fixes: - `hub_screen.rs`: route the match's `AppAction` through `vertical_centered(...).inner` so AddScreen / BackendTask reach `AppState`. - `contacts.rs`: thread the three header clicks plus the populated- state `Add by username` through a new pure `contacts_button_kind` dispatcher. `Add by username` opens `DashPayAddContact`, `Scan QR` routes to the same screen (it owns the scan affordance today), and `Show my QR` opens `DashPayQRGenerator`. Gate card CTA continues to emit `AppAction::SwitchIdentityHubTab(Settings)`. - `activity.rs`: render the legacy-payments pointer as a clickable link that emits `AppAction::SetMainScreen(RootScreenDashPayPayments)` via a new `activity_button_kind` dispatcher. - `home.rs`: introduce `HomeButton` + `HomeButtonKind` + `home_button_kind(button)`. Every Home click site now dispatches through the pure resolver. The inline "Set up your social profile" CTA and the onboarding "Set a display name" step now route via a new `HomeOutcome::GoToSettings` — DashPay profile editing lives in §B.8 (Settings tab), not in the DPNS register-a-username flow. The old behaviour pushed RegisterDpnsNameScreen for these paths. Regression coverage: each tab module now owns a `#[test]` suite that enumerates every button variant and asserts the dispatcher returns a non-dead result. That is the ground-truth check we could add without a GUI harness — kittest 0.3 is query-only and cannot simulate clicks. The exhaustive-match pattern also makes adding a new button a compile error if the dispatcher arm is missing. Co-Authored-By: Claudius the Magnificent <noreply@anthropic.com> * fix(identity-hub): adapt hub to platform-wallet (#860) APIs after rebase Rebasing the unified Identities hub (#842) onto the platform-wallet backend rewrite (#860) replays cleanly, but the hub was written against the old egui 0.33 / local-DB architecture. This commit makes it build, lint, and test green on the new foundation — no hub feature dropped. Adaptations to #860's APIs: - egui 0.35 rename: every `Context::style()` becomes `global_style()` across the hub modules and `breadcrumb_pill` (21 sites). `Ui::style()` is unchanged. - `ScreenLike::ui` now takes `&mut egui::Ui` instead of `&Context`; the hub screen adopts the project idiom (`let ctx = ui.ctx().clone();`) and passes `ui` to `add_top_panel` / `add_left_panel` / `island_central_panel`. - The local SQLite DashPay-profile cache was removed; profiles now load asynchronously via `DashPayTask::LoadProfile` through the upstream `DashpayView`. New `ProfileCache` (src/ui/identity/profile_cache.rs) wraps that flow: tabs read it synchronously (empty until loaded), the hub queues a load on a miss after rendering and feeds the result back via `display_task_result`. Home, Contacts, and Settings read the cache instead of the removed `db.load_dashpay_profile`; Settings guards against clobbering in-progress edits when a late load lands. - Dropped the `ProofLogScreen` and `MasternodeListDiffScreen` registrations — both screens were removed in #860. - `ui::identities` stays `pub` (a #860 kittest reads it externally); the hub's `ui::identity` module is added alongside. - Restored the `contacts_state` hub field + initializer that lived only in a dropped merge-commit resolution, leaving its uses orphaned after lineariz. - Removed the now-unreachable `_` arm in `network_label` and the obsolete `network_db_key` test (dash-sdk `Network` is exhaustive; the DB key is gone). - The five AppState-mounting hub kittests now wrap their bodies in `support::with_isolated_data_dir`, matching #860's single-open wallet-storage test isolation, so they no longer race on a shared `det-app.sqlite`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(identity-hub): make the Identities hub island fill the panel width User report: on "Welcome to Identities." the bordered island does not reach the window edges — it sits pinned narrow with dead space outside its border. Root cause: `island_central_panel` draws the island as an egui `Frame` that shrink-wraps to its content width (screens that want a full-width island opt in via `ui.set_min_width(ui.available_width())`, e.g. the tokens screens). The hub never opted in, and `onboarding::render` centers a 640px-capped readable column, so the island Frame collapsed to the content width. Measured at a 1400px window: the island occupied 977px of 1314px available — a 337px gap outside the border. Fix (localized, idiomatic — zero impact on the ~65 other island screens): `onboarding::render` and the hub's Home/Picker branch now claim the full available width before centering, exactly like the existing full-width screens. The readable column stays centered and capped at 640px per design-spec §B.1. Adds a kittest that renders the real onboarding inside the real `island_central_panel` at a wide window and asserts the island content fills its panel (reproduces pre-fix at 977/1314, passes post-fix at ~1314/1314). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(identity-hub): app-scoped selected wallet/identity foundation Adds the app-scoped selection seam beneath the Identities hub (Wave 0 of IDH-003). No visible UX change; the linchpin ships unused and the picker write-side is in place for Wave 1 to route into. - New `model/selected_identity.rs`: `SelectedIdentity { identity_id }` blob at `det:selected_identity:v1` — a SEPARATE per-network blob from `SelectedWallet` (extending the wallet blob would break existing `:v1` decoding — R6). Plus the pure precedence/reconcile helpers (`keep_if_loaded`, `resolve_selected`) the context setters delegate to, unit-tested without egui/AppContext. - `AppContext`: `selected_identity_id` + `pending_identity_selection` fields; getters `selected_wallet_hash`/`selected_identity_id`/`resolve_selected_identity`; setters `set_selected_identity`/`set_selected_hd_wallet`/`set_selected_single_key_wallet`; `persist_selected_identity_kv`; private `restore_selected_identity_from_kv` (called in `ensure_wallet_backend`, keep-if-loaded). Setters compute BOTH pointers and write the mutexes directly — they never call each other (no reconciliation recursion — R5). Owning wallet is derived via the identity's signing-key path (`get_selected_wallet`), never `associated_wallets.keys().next()` (R1). - `WalletBackend`: `get_selected_identity`/`set_selected_identity` KV accessors. - `IdentitySelector`: opt-in `with_app_default` (seed empty buffer from the app-scoped id) + `syncing_global` (write-back on user change). Default — neither called — is byte-identical to before (R2: no accidental sync on the 9 no-sync sites). - `ui/state/hub_selection.rs`: `HubSelection` (picker override + search buffers) and the pure `effective_view` state machine. - `wallets_screen`: its private persist helpers now delegate to the centralized setters, removing the duplicated lock+persist and closing the single-key persist gap (R10). D7 reachability: `KeysScreen` (bare `Identity`) and `left_wallet_panel.rs` are unreachable (no navigation produces `ScreenType::Keys`; `add_left_wallet_panel` has no callers) — quarantined, untouched. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(identity-hub): wire the breadcrumb wallet/identity switcher Wave 1 of IDH-003: the hub becomes a working wallet + identity switcher and its tabs render the app-scoped selected identity. - `top_panel.rs`: extract the shared `render_top_island` scaffold (island, network accent, connection indicator, right-button grouping) and add `add_top_panel_with_breadcrumb`. `add_top_panel` keeps its exact signature and delegates — zero change for its 72 callsites (R2). The new entry point is not feature-gated because the hub screen is compiled unconditionally to keep the `Screen` enum exhaustive; `identity-hub` still gates nav visibility + registration. - `breadcrumb_pill.rs` / `identity_pill.rs`: surface the inner egui `Response` so the switcher can anchor `Popup`s to the pill (the PR #842 inner-vs-frame hazard — R3). `IdentityPill` gains an optional painted avatar. - `avatar.rs` (new): `paint_identity_monogram` extracted from the hero card and shared with the breadcrumb pill (hero refactored to call it). - `breadcrumb_switcher.rs` (new): `Identities` link › wallet pill › identity pill; wallet + identity `Popup` dropdowns (wallet list + "Set up another wallet"; identity list scoped via the stored `wallet_hash` filter — never `associated_wallets.keys().next()` (R1) — plus the "Identities without a wallet on this device" group via `wallet_index.is_none()`, inline search at ≥7, Create/Load footers, dev-mode bulk entry); per-state pill modes (§7) and verbatim tooltips (tt-2/tt-3/tt-4, §D — tt-3 ends "…to unlock switching."); returns a typed `BreadcrumbEffect`. - `hub_screen.rs`: hosts the switcher on all landings; an effective-view state machine (Onboarding/Picker/Home); applies `BreadcrumbEffect` (switch wallet / identity via the AppContext setters, resetting `contacts_state` / `profile_cache` on switch); wires `picker::render` and routes its selection (closing the picker write-bug end to end). - `home.rs` / `contacts.rs` / `settings.rs`: read `resolve_selected_identity()` instead of `first_loaded_identity` / `.first()`; `settings.ensure_selected` reads the app-scoped value, removing the flip-flop (D4). Tests: UT-SWITCH-MODE-01 (pill-mode resolver), UT-SWITCH-TT-01 (verbatim tooltips, guards the tt-3 wording), the no-wallet discriminator guard (R1), `HubSelection` effective-view + the avatar/short-hex/monogram helpers; kittest IT-SWITCH-03 (onboarding placeholder segments). The multi-wallet IT-SWITCH-01/02/04 need a seeded multi-identity DB fixture absent on this branch — deferred (the switching logic is unit-covered); noted in the report. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(identity-hub): land IT-SWITCH-04 + stale-selection reconcile (seeded multi-identity fixture) Closes the multi-identity kittest fixture gap Bilby deferred. A wallet-less basic-identity seeding helper (insert_local_qualified_identity) lets the hub kittests cover the end-to-end multi-identity path through the real AppState frame loop: - IT-SWITCH-04: two identities + no selection lands on the Picker (both listed by alias); setting the app-scoped selection (the picker-click effect) drives the Picker -> Home transition and resolve_selected_identity returns it. - stale-selection reconcile: a selected id absent from the loaded set is not treated as explicit (no phantom Home) and resolve falls back to a loaded id. IT-SWITCH-01/02 (wallet dropdown + wallet-scoped identity list) remain out of reach: they need a loaded HD Wallet fixture with matching wallet_hash/index, which the wallet-less helper does not provide. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QKxzpYs9FsGuSKwa8Nexud * fix(identity-hub): clear derived wallet on wallet-less identity selection Addresses Marvin's W0+W1 QA (QA-001 MUST, QA-002/003/005). QA-001 (MEDIUM, correctness): selecting a wallet-less (imported-by-id) identity left `selected_wallet_hash` pointing at a *different* identity's wallet, so the breadcrumb wallet pill and the active identity disagreed. Per the canonical model (identity primary, wallet derived) a wallet-less identity has no owning wallet: - `AppContext::set_selected_identity` now ALWAYS reconciles the derived wallet on selection — writing `owning_wallet_hash(id)`, which is `None` for a wallet-less identity (previously the `if let Some(hash)` short-circuit left the pointer stale). - The breadcrumb is now identity-primary: the identity pill reflects the *explicitly* chosen identity (or a lone auto-selected one), and a wallet-less active identity renders the wallet segment as an empty `(no wallet)` placeholder instead of falling back to another identity's wallet. The pill stays a placeholder in the ≥2-none-chosen picker state (§7) — it must not show the first-identity fallback (that also duplicated a picker-grid label, which regressed IT-SWITCH-04; fixed here). QA-002 (LOW): deleted the tautological `no_wallet_group_uses_wallet_index_ discriminator` test (asserted only `Some(3).is_some()`); replaced with `qa_002_no_wallet_group_filter_on_real_data`, which drives the real `wallet_index.is_none()` filter over a seeded wallet-less identity. QA-003 (LOW): added the R2 no-accidental-sync regression locks in `identity_selector` — a selector with neither `with_app_default` nor `syncing_global` never seeds from nor writes the app-scoped selection. QA-005 (LOW): the hub render path now loads the identity list once per frame in `hub_screen::ui` (shared by the view computation and the Picker arm) and the breadcrumb derives the active identity + no-wallet group from a single load; a `TODO(IDH-003 follow-up)` notes folding `landing()`'s remaining load in. New test: `qa_001_wallet_less_selection_clears_derived_wallet` (the wallet-less reconcile path — wallet pointer cleared, breadcrumb shows the selected identity). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(identity): app-scoped selection screen-migration plan (W2-W5) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(contracts): obey app-scoped selected identity in register/update/document screens (W2) Batch B1 of the W2-W5 migration plan. Each screen seeds its initial identity from `selected_identity_id()` (fallback: first loaded) and adds `.syncing_global()` to its `IdentitySelector` so a user pick propagates back to the app-scoped selection. - `RegisterDataContractScreen::new()`: seed from selected_identity_id - `UpdateDataContractScreen::new()`: seed from selected_identity_id - `DocumentActionScreen::new()`: seed via resolve_selected_identity() when None - All three IdentitySelectors: `.syncing_global(self.app_context.clone())` Tests: 3 kittests in tests/kittest/contract_screen.rs asserting each screen defaults to the app-scoped identity on construction (seeding direction). Write-back direction deferred (TODO: private-key fixture, TI-1). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(identity): default DPNS registration to the app-scoped identity (W2) Batch B2: `RegisterDpnsNameScreen::new()` seeds `selected_qualified_identity` from `selected_identity_id()` (fallback: first loaded) so the DPNS registration screen opens on the identity the user last operated as, not always the first DB row. The `IdentitySelector` in `render_identity_id_selection` now carries `.syncing_global()` so a user pick writes back to the app-scoped selection. Test: extended `tests/kittest/register_dpns_name_screen.rs` with `dpns_registration_defaults_to_app_scoped_identity`, which seeds two identities, sets the second as the global selection, constructs the screen, and asserts it opens on the second identity. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(dashpay): sync DashPay screens with the app-scoped selected identity (W3) Batch B3: all 7 DashPay screens now seed `selected_identity` from the app-scoped selection on construction and write user picker changes back via `syncing_global`. Screens migrated (seed in `new()` + `refresh()`; `syncing_global` on selector): - `AddContactScreen::new()` + `new_with_identity_id()` — seeded; selector syncs - `ContactsList::new()` + `refresh()` — seed prefers scoped id over first - `ContactRequests::new()` + `refresh()` — seed prefers scoped id over first - `PaymentHistory::new()` + `refresh()` — seed prefers scoped id over first - `ProfileScreen::new()` + `refresh()` — seed prefers scoped id over first - `QRCodeGeneratorScreen::new()` — seeded; selector syncs - `QRScannerScreen::new()` — previously `None`; now seeds from scoped id `selected_identity` made `pub` on each struct for test verification (consistent with `RegisterDataContractScreen` / `DocumentActionScreen` precedent). Tests (9 assertions in `tests/kittest/dashpay_screen.rs`): each screen opens on the second of two seeded identities when it is set as the app-scoped selection. Write-back canary deferred (TI-1 / private-key fixture gap; TODO added). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(tokens): default the token creator to the app-scoped identity (W4) Batch B4: `TokensScreen::new()` seeds `selected_identity` from the app-scoped identity on construction (falling back to the first loaded identity). Changes: - `mod.rs`: after the struct literal, look up the preferred id in the already- built `identities` BTreeMap and populate `selected_identity` + `identity_id_string`. - `token_creator.rs` (simple mode): added `.syncing_global()` to the `IdentitySelector` so a user pick writes back to the app-scoped selection. - `token_creator.rs` (advanced mode): snapshot before/after `add_identity_key_chooser` and call `set_selected_identity` on change (helper uses raw ComboBox, not IdentitySelector, so manual write-back is needed). - `selected_identity` made `pub` on `TokensScreen` for test verification. Test: `tests/kittest/tokens_screen.rs` — `token_creator_defaults_to_app_scoped_identity` seeds two identities, sets the second as the global selection, constructs the screen, and asserts it opens on the second identity. Write-back requires a private-key fixture (TI-1 gap; TODO). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(wallets,tools): seed wallet-scoped and tool screens from the app-scoped identity (W5) Batch B5: three READ-only screens now seed their identity selector from the app-scoped selection subject to their specific membership guards. No screen uses `syncing_global` (R1/R4 reasons documented inline). Changes: - `create_asset_lock_screen.rs`: Added `.with_app_default(&self.app_context)` to the "Identity to top up" selector. Guard: seeds only if the global id is in this wallet's identity list (IdentitySelector handles the check). No `new()` pre-fill change needed. - `grovestark_screen.rs`: Manual EdDSA-guarded seed in `new()` and `refresh_identities()`. The global identity is used iff it passes the EdDSA-key filter; otherwise falls back to first EdDSA identity or `None`. `selected_identity` made `pub` for test verification. - `send_screen.rs`: Manual wallet-membership-guarded seed at render-time: when the wallet-scoped identity list is built each frame, seeds `selected_identity` from the global id iff it is among this wallet's identities; otherwise `selected_identity` stays `None`. Tests: `tests/kittest/tools_screen.rs` — `grovestark_does_not_seed_non_eddsa_identity`: seeds two basic identities (no EdDSA keys), sets the second as the global selection, constructs the screen, and asserts `selected_identity == None` (R4 guard). Positive-seed and wallet-membership tests deferred (EdDSA-key fixture and WalletFixture gaps; TODOs in test file). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(identity): lock session-local and no-sync identity pickers (W5) Batch B6: regression-lock tests and the K3 comment on GroupActionsScreen. GroupActionsScreen: - Added one-line K3 comment to the IdentitySelector: session-local screen, no `with_app_default` or `syncing_global` by design. - `selected_identity` made `pub` for test verification. Tests: - `contract_screen::group_actions_does_not_seed_from_global_identity` (K3 lock): seeds two identities, sets the second as global, creates `GroupActionsScreen`, asserts `selected_identity == None` and the global selection is unchanged. This pins the session-local behaviour against future drift. - Updated `tokens_screen.rs` doc-comment to document the B6 N/A regression-lock reasoning: the 6 N/A token recipient/target/member selectors are covered by the `default_selector_has_no_sync_target` unit test in identity_selector.rs; a structural note captures this invariant here. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: apply nightly fmt across W2-W5 migration files Formatting-only commit: `cargo +nightly fmt --all` on all files touched during the B1-B6 migration batches. No functional changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(identity-selector): add QA-001 write-back and QA-003 inert-lock unit tests; fix QA-002 misleading docstring QA-001 (MED): add fixture-free `syncing_global_writes_selection_to_app_context` unit test inside `identity_selector::tests`. Calls `sync_to_global()` directly (private access in same module) with in-memory `QualifiedIdentity` structs and a bare `AppContext` (no Harness, no wallet-backend wiring needed — KV persistence gracefully skips). Proves the write-back path without private keys or DB insertion. QA-003 (LOW): add `with_app_default_inert_when_global_id_not_in_candidate_list` unit test. Verifies that when the app-scoped identity is absent from the selector's candidate list, `app_default_seed()` returns `None` — locks the wallet-membership guard that `CreateAssetLockScreen` relies on (R1). QA-002 (LOW): correct the misleading `contacts_list_defaults_to_app_scoped_identity` docstring in `dashpay_screen.rs`. It called itself a "write-back canary" but only tests seeding; updated to accurately describe seeding coverage and point to QA-001 for write-back coverage. QA-004 (LOW): update deferred TODO comments in `contract_screen.rs`, `tokens_screen.rs`, and `tools_screen.rs` to reference the new QA-001 unit test so readers know write-back is now covered at the component level. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(user-stories): mark IDH-003 multi-identity switching implemented The app-scoped selected identity now drives every operate-as screen (W2-W5), completing the multi-identity switching story. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(identity-selector): add QA-001 egui-kittest write-back keystone test Replace the unit-test-only QA-001 proof with a proper kittest that exercises the full rendering path: a genuine ComboBox change → `sync_to_global()` → `AppContext::set_selected_identity()`. `tests/kittest/identity_selector.rs` — `combo_change_writes_selection_to_app_context`: - Phase 1: initial render with buffer pre-seeded to Alice must NOT invoke `set_selected_identity` (seeding ≠ write-back). - Phase 2: `get_by_value("Alice").click()` opens the ComboBox popup, `get_by_label("Bob").click()` selects Bob; asserts `ctx.selected_identity_id() == Some(bob_id)` after `harness.run()`. Setup pattern: `build_eframe` + `run_steps(5)` to fully wire `ensure_wallet_backend` (and drain `restore_selected_identity_from_kv`) BEFORE seeding the identity, so the async initialization race does not overwrite the seed. Unit test `syncing_global_writes_selection_to_app_context` in `identity_selector.rs` is kept and re-scoped to the *mechanism* (`sync_to_global()` method). The new kittest covers the *rendering gate* (`combo_changed || text_response.changed()` at line 321). Also updates `identity_selector.rs` docstring to cross-reference the kittest. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(identity-hub): T28 network-switch refresh · T29 LoadContactRequests · T30 tooltip copy T28 (blocking): IdentityHubScreen.change_context now calls screen.refresh() after swapping app_context. Without this the contacts load-guard stayed set, so the Contacts tab would show stale "already loaded" data forever after a network switch. T29 (blocking): render_populated dispatches DashPayTask::LoadContactRequests alongside LoadContacts on first tab entry. Previously only LoadContacts was fired, so the Received and Sent sections could never hydrate from the backend. T30 (blocking): corrected Quick-action tooltip copy to match actual routes. The "Send" button routes to the identity Transfer screen (identity→identity credits, not wallet-Dash send), and "Receive" routes to TopUpIdentity (wallet→identity credits, not a QR-code/receive-address screen). Chose option (b) — update copy to match the current implementation — because option (a) requires adding a new QR- generator entry point that does not yet exist in the hub context. The "Send to another identity" secondary action already has an accurate tooltip; this aligns the primary row to the same standard. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(identity-hub): V1 hero compact sizing · V2 social-profile card placement · T09 avatar image rendering V1: Remove `ui.set_min_height(200.0)` from IdentityHeroCard so the hero card sizes to its actual content. The fixed 200 px floor caused a large empty gradient slab in the no-social-profile variant and made the card unnecessarily tall even in the profile-set variant. V2: Move the "Set up your social profile" inline card to be immediately below the hero (before the quick-actions row) rather than buried after the secondary-actions row. Together with V1 this produces the compact hero+prompt visual the wireframe shows for the no-profile state — no empty gap, no hidden prompt. T09: IdentityHeroCard.paint_avatar_or_monogram now actually renders avatar bytes when with_avatar_bytes() is called. Previously the field was stored but ignored; the render always fell through to the initials monogram, making avatar_uses_initials_fallback() lie when bytes were present. Implementation: - Decode PNG/JPEG bytes via the `image` crate (already a dependency). - Cache the TextureHandle in the egui context keyed by a FNV-1a hash of the bytes so decode runs exactly once per unique avatar. - Paint via egui::Image::corner_radius (48 px = perfect circle clip). - Overlay the same accent ring used by the initials monogram. - Fall back to initials on decode failure, so avatar_uses_initials_fallback() stays honest. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(identity-hub): V3 checklist visual rework + T10 full-row clickability V3 / T10: Rework the onboarding checklist to match wireframe §B.2: Heading: "Get set up" → "Finish setting up your identity" Dismiss: bare "×" symbol → labelled "Hide this for now" link so the intent is explicit (existing dismiss logic preserved, tooltip unchanged). Per-item subtext: each step now renders a short descriptive line below the title — e.g. "This is how you appear to contacts." for SetDisplayName — which the wireframe shows for both pending and done states. For done PickUsername the subtext reads "You are @{handle}." when the identity has a DPNS name (injected via the new with_handle() builder), falling back to "Your username is set." when the handle is not available yet. Inline action buttons: pending steps render an underlined link-style action button (e.g. "Set display name", "Add a contact") so the user can act from the checklist without hunting for the entry point. Full-row clickability (T10): the bullet circle and surrounding whitespace now participate in the click sense via egui UiBuilder::sense — not just the label text. The inline action button additionally emits its own click, and both produce ChecklistAction::Activated so the hub routes correctly. No existing tests broke; all 9 checklist unit tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(identity-hub): T08/T11/T12 implement ComponentResponse for three response types ContactRowResponse (T08): implements ComponentResponse<DomainType = String> - has_changed() → any of clicked/send_clicked/overflow_clicked is true - changed_value() → &self.contact_id (the echoed identifier) - is_valid() / error_message() → trivial (no validation) RequestCardResponse (T11): introduces typed RequestAction enum (Accepted, Declined, Cancelled) as the ComponentResponse::DomainType alongside the existing public booleans. All existing call sites that read response.accepted / .declined / .cancelled compile unchanged. The typed action is available via response.action() and via ComponentResponse changed_value() (populated by show() into a private action_cache field so the borrow can return a &Option<RequestAction>). Added action_derives_from_ booleans unit test. SocialProfileGateCardResponse (T12): introduces typed GateCardAction enum (PrimaryClicked, WhyToggled) via the same private action_cache field pattern. Existing call sites (contacts.rs response.primary_clicked) are unaffected. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(identity-hub): T17/T21 picker slot overwrite + deferred profile baseline T17 (picker.rs): `captured_selection` was unconditionally written to the caller's `selected_id_out` slot, including on frames with no click (`None`). This would silently clear any selection the caller had set. Fix: only update the slot when `captured_selection` is `Some` — i.e. when the user actually clicked a card this frame. T21 (settings.rs + hub_screen.rs): `SettingsTab` was mirroring `original_display_name/bio/avatar_url` at the moment the Save button was clicked. This meant a failed `UpdateProfile` backend task left the baseline wrong: the Save button immediately disabled itself even though no server round-trip succeeded. Fix: - Remove the premature mirror-on-click in settings.rs - Add `SettingsTab::on_profile_saved()` which moves the mirror to the moment of confirmed success - Add `SettingsTab::selected_identity()` accessor for identity matching - Wire `on_profile_saved()` in `IdentityHubScreen::display_task_result()` on `BackendTaskSuccessResult::DashPayProfileUpdated`, guarded by identity ID comparison to reject stale results from prior selections Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(identity-hub): apply nightly fmt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(identity-hub): QA-001/T21 commit submitted snapshot, not current edit fields Problem: on_profile_saved() was mirroring the current edit fields as the new baseline. If the user kept typing after clicking Save, the in-flight success would commit never-saved edits as if they had been saved (silent data loss). Reproduced as a failing test: left="Alicia Smith" right="Alicia". Fix: - Add `pending_save: Option<(String, String, String)>` to SettingsTab, set to a snapshot of (display_name, bio, avatar_url) at the moment Save is clicked. - on_profile_saved() now pops pending_save and commits THAT snapshot as the original_* baseline; if the user has kept typing, the edit fields are untouched so Save re-enables for the remaining edits. - pending_save is cleared on identity switch (ensure_selected) so a stale success from the old identity cannot corrupt the new identity's baseline. Tests: - IT-SETTINGS-02: submitted snapshot vs current edits distinction - IT-SETTINGS-03: pending_save cleared on identity switch Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * revert(identity-hub): QA-002/T29 remove premature LoadContactRequests dispatch The previous pass dispatched DashPayTask::LoadContactRequests alongside LoadContacts so the Received/Sent sections could hydrate. However: - BackendTaskSuccessResult::DashPayContactRequests is consumed ONLY by the old ui/dashpay/contact_requests.rs screen; the hub's display_task_result routes it nowhere. - The Received and Sent sections still render hardcoded empty-state labels. - Result: a real SDK round-trip fires on every Contacts tab entry with zero user-visible benefit. Revert the dispatch. Add a TODO(identity-hub/T29) comment listing the three wiring steps needed before re-adding it: (1) cache on ContactsState, (2) hub display_task_result handler, (3) real RequestCard rows. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(identity-hub): QA-003/004/006 avatar readiness, contact_id contract, gate-card dedup QA-004 / T08 — ContactRowResponse ComponentResponse contract - contact_id was echo-set unconditionally at show() start, breaking the ComponentResponse invariant: changed_value() must be Some only when has_changed() is true. - Fix: initialise response with contact_id = None; set it alongside the flag only when a click is actually detected (body/Send/overflow). - Add UT-CONTACT-ROW-04: no-click default has contact_id = None. QA-003 / T09 — avatar_uses_initials_fallback() stays honest on decode failure - was: `has_social_profile() && avatar_bytes.is_none()` — returned false (claims a real image) even when bytes were present but undecodable. - Add avatar_decode_ok: bool field; with_avatar_bytes() probes the bytes via image::load_from_memory (probe only, GPU upload still lazy). avatar_uses_initials_fallback() now also returns true when decode fails. - Add UT-HERO-03: valid 1×1 PNG → decode_ok true, fallback false. - Add UT-HERO-04: corrupt bytes → decode_ok false, fallback true. - Add QA-007 resource note in try_paint_avatar_image doc comment. QA-006 — V2/V3: suppress social-profile gate card when checklist visible - With both V2 and V3 applied, a no-profile Home shows the inline gate card (SetUpSocialProfile → Settings) AND the checklist's "Set a display name" step — the same action twice on one screen. - Fix: add checklist_covers_profile guard so the gate card is only shown when dismissed_checklist is true (i.e. the checklist is hidden). When the checklist is visible it handles the profile-setup affordance alone. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(identity-hub): apply nightly fmt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(identity-hub): QA-001 follow-ups — clear pending_save on error, T28 test Two residual LOWs from Marvin's second QA pass, both independent of T29. settings.rs — SettingsTab::clear_pending_save(): A failed UpdateProfile left pending_save dangling. A later DashPayProfileUpdated from any path (e.g. legacy ProfileScreen "Change photo") would commit the stale submitted snapshot as the new baseline. Adding clear_pending_save() and wiring it into hub_screen::display_task_error closes the window: on any task error, the stale snapshot is cleared so it can't corrupt a future success. hub_screen.rs — display_task_error: Calls settings_tab.clear_pending_save(). Clearing when pending_save is None is a no-op, so this is safe to call on every error regardless of which task failed. contacts.rs — t28_reset_clears_load_guard (test): Guards the T28 fix (change_context → refresh → contacts_state.reset() re-enables the load dispatch). Previously untested; now pinned to prevent silent regressions if reset() loses the load_requested = false line. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(dashpay): hydrate hub Contacts Received/Sent from contact requests (T29) Depends on the Identity Hub UI introduced in #842 (src/ui/identity/). ContactsState (contacts.rs): - ContactRequestEntry: { counterpart_id, request_id, relative_time } — a lightweight cache entry derived from a raw DashPayContactRequests document. counterpart_id: Base58 display label (sender for incoming, recipient for outgoing) until profile-name integration lands. relative_time: pre-formatted via format_relative_time from doc.created_at / updated_at. - incoming: Vec<ContactRequestEntry> — populated by record_requests(), cleared by reset() so a refresh or identity/network switch doesn't leave stale rows. - outgoing: Vec<ContactRequestEntry> — same lifecycle as incoming. - record_requests(incoming, outgoing): converts Vec<(Identifier, Document)>: incoming sender = doc.owner_id(); outgoing recipient = doc.properties()["toUserId"]; timestamp via format_relative_time. - reset() now clears incoming + outgoing alongside the load guard. render_populated (contacts.rs): - Snapshots state_guard.incoming / outgoing before closures to avoid re-borrow conflicts. - Received section: iterates entries, renders RequestCard::received per row with abbreviated counterpart_id; empty-state label when list is empty. - Sent section: same with RequestCard::sent. - Section headings carry " · N" count when N > 0. - Dispatches LoadContacts + LoadContactRequests together on first paint (guarded: fires once per tab-entry, reset by refresh/network-switch). - Deferred TODOs: Accept/Decline wiring (variants exist, button not wired); Cancel (DashPayTask::CancelContactRequest not yet present). hub_screen::display_task_result (hub_screen.rs): - Restructured body to use match &result; existing DashPayProfileUpdated arm preserved unchanged. - New DashPayContactRequests { incoming, outgoing } arm calls contacts_state.record_requests(...) to hydrate the caches. Helpers: - abbreviate_id(): first 8 chars + "…" for long Base58 IDs; identity for short IDs (≤ 10 chars). Tests: - t28_reset_clears_load_guard_and_caches: reset() clears guard + both caches. - abbreviate_id_shortens_long_ids: helper unit test. - section_headings_include_count_when_populated: heading format with count. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
No description provided.