Skip to content

feat: disable refresh button in dpns screen when already refreshing - #83

Merged
ogabrielides merged 1 commit into
v0.4-devfrom
feat/disable-refresh-when-already-refreshing
Nov 22, 2024
Merged

feat: disable refresh button in dpns screen when already refreshing#83
ogabrielides merged 1 commit into
v0.4-devfrom
feat/disable-refresh-when-already-refreshing

Conversation

@pauldelucia

Copy link
Copy Markdown
Member

No description provided.

@coderabbitai

coderabbitai Bot commented Nov 22, 2024

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


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?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@ogabrielides
ogabrielides merged commit 5c2628d into v0.4-dev Nov 22, 2024
@pauldelucia
pauldelucia deleted the feat/disable-refresh-when-already-refreshing branch November 22, 2024 13:23
lklimek added a commit that referenced this pull request Jul 1, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants