Skip to content

fix(swift-example-app): expose Picker options to UI automation#3903

Merged
QuantumExplorer merged 2 commits into
v3.1-devfrom
claude/cool-brattain-b8207a
Jun 15, 2026
Merged

fix(swift-example-app): expose Picker options to UI automation#3903
QuantumExplorer merged 2 commits into
v3.1-devfrom
claude/cool-brattain-b8207a

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Jun 15, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

Several SwiftExampleApp flows start with a SwiftUI Picker that renders as a menu popover (the Form default, or an explicit .pickerStyle(.menu) / MenuPickerStyle()). When opened, idb's accessibility tree shows only an element labeled "Dismiss context menu" — the actual option rows ("Platform Payment #0 — 0.0231 DASH", wallets, identities, etc.) are not exposed, so UI automation can neither query nor tap a specific option (a blind coordinate tap just dismisses the menu).

This blocked automated runs of the identity funding flows — ID-05 (top up), ID-06 (top up from Platform addresses), ID-08 (create from Platform addresses) — and the token-register / builder identity pickers. The features work fine manually; this is a testability/accessibility fix, not a product-bug fix.

QA tracking: #3897 (updated — the Picker-popover blocker is now marked RESOLVED).

What was done?

Added a shared helper, AccessiblePicker.swift, with two presentation modifiers (no behaviour change — selection bindings / onChange untouched):

  • .accessibleFormPicker(_:).pickerStyle(.navigationLink) for pickers inside a Form/List. Pushes a real List; per-row accessibilityIdentifiers surface as distinct, tappable rows.
  • .accessibleInlinePicker(_:).pickerStyle(.inline) for pickers not in a Form (where .navigationLink renders poorly). Rows are laid out in place; addressable by AXLabel.

Applied them — with stable per-row identifiers — to:

  • TopUpIdentityView — funding-source picker (topup.fundingSource.*)
  • CreateIdentityView — source-wallet, funding-source, denomination pickers (createIdentity.*)
  • RecipientPickerView — local-identity picker (recipient.identity.*)
  • TransitionInputView — identity / recipient-identity / contract / document-type / token / generic-select pickers (transition.*)

The Create funding-source row id includes the account type (…account.<accountType>.<accountIndex>) because that picker lists both Core and Platform-Payment accounts, so a bare #0 would collide (BIP44 #0 vs Platform Payment #0).

Pure SwiftUI / accessibility change in the example app — no FFI, no Rust, no business logic (per packages/swift-sdk/CLAUDE.md).

How Has This Been Tested?

Built packages/swift-sdk/build_ios.sh --target sim (clean) and drove the booted iOS simulator (iPhone 17, fully-provisioned QA wallet) with idb:

  • ID-05 — end-to-end. Opened the funding-source picker → idb ui describe-all listed topup.fundingSource.account.0 ("Platform Payment #0 …") instead of "Dismiss context menu"; selected it (amount pre-filled, submit enabled), set a small amount, tapped Top Up Balance. Success banner showed the new balance, and SwiftData ground truth confirmed the identity balance increased to match. No errors/panics in the app log.
  • ID-08CreateIdentityView source-wallet picker exposed all rows; selecting a wallet revealed the funding-source picker, which exposed unique rows and propagated selection (verified the id-collision fix; drove to submit-ready, did not broadcast the create).
  • RecipientPickerView — inline rows exposed; tapping one selected it.

Note: .inline pickers expose rows but SwiftUI does not surface their per-row ids (rows inherit the container id; address by AXLabel) — documented in the helper. Menu { Button } navigation menus (e.g. the Identities "+") were never affected — they already expose their children.

Breaking Changes

None.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any (n/a — no breaking changes)
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added SwiftUI picker accessibility helpers to standardize form and inline picker presentation.
  • Improvements
    • Updated identity, recipient identity, funding source, wallet, token, contract, and document-type pickers to include stable accessibility identifiers for the “none/choose” option and each selectable row.
    • Switched some pickers to the new accessibility-friendly wrapper for clearer assistive-technology navigation.
    • Added per-option identifiers to picker-driven inputs, including manual-entry where applicable.

SwiftUI `Picker`s rendered as menu popovers (the Form default, or an
explicit `.pickerStyle(.menu)` / `MenuPickerStyle()`) surface only a
single "Dismiss context menu" element to idb's accessibility tree — the
option rows are unreachable, so UI automation can neither query nor tap
a specific option. This blocked automated runs of the identity funding
flows (top-up, create-from-addresses) and the token-register / builder
identity pickers.

Add a shared AccessiblePicker helper with two presentation modifiers:
- `.accessibleFormPicker` (.pickerStyle(.navigationLink)) for pickers in
  a Form/List — pushes a real list whose rows expose per-row identifiers.
- `.accessibleInlinePicker` (.pickerStyle(.inline)) for non-Form pickers
  — lays the rows out in place (addressable by AXLabel).

Apply them, with stable per-row accessibilityIdentifiers, to the
funding-source, source-wallet, denomination, recipient-identity, and
transition (token-register / builder / contract / document-type / token)
pickers. The Create funding-source row id includes the account type so
BIP44 #0 and Platform Payment #0 no longer collide.

Pure SwiftUI/accessibility change in the example app — no FFI, no
business logic. Verified on the iOS simulator with idb: option rows now
appear in `describe-all` and tapping one updates the selection; drove a
top-up end-to-end (ID-05, identity balance increased).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a865ac94-15e1-4b4d-89d3-95c52775c3c6

📥 Commits

Reviewing files that changed from the base of the PR and between 26935e7 and 2ff2000.

📒 Files selected for processing (2)
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/Components/RecipientPickerView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TransitionInputView.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TransitionInputView.swift

📝 Walkthrough

Walkthrough

Introduces AccessiblePicker.swift with two View extension helpers (accessibleFormPicker and accessibleInlinePicker) that set picker style and container-level accessibilityIdentifier. Extends FundingAccountOption with accountType and accountIndex fields for stable identifiers, and applies these helpers with per-option accessibilityIdentifiers across CreateIdentityView, TopUpIdentityView, RecipientPickerView, and TransitionInputView.

Changes

Accessible Picker Infrastructure and View Updates

Layer / File(s) Summary
AccessiblePicker helper extensions
Views/Components/AccessiblePicker.swift
New file defining accessibleFormPicker(_:) and accessibleInlinePicker(_:) View extensions that apply navigationLink or inline picker style and set a container-level accessibilityIdentifier, documented for automation use.
FundingAccountOption model extension
Views/CreateIdentityView.swift, Views/TopUpIdentityView.swift
Adds accountType and accountIndex stored properties to FundingAccountOption; accountOptions(for:) populates accountIndex from PersistentAccount when building the option list.
CreateIdentityView picker accessibility
Views/CreateIdentityView.swift
Assigns deterministic accessibilityIdentifiers to Source Wallet, Funding Source, Shielded Balance, and Denomination picker options and wraps each picker with accessibleFormPicker.
TopUpIdentityView picker accessibility
Views/TopUpIdentityView.swift
Adds accessibilityIdentifiers to the Funding Source picker's none option and each account row (keyed by accountIndex), and applies accessibleFormPicker("topup.fundingSourcePicker").
RecipientPickerView picker accessibility
Views/Components/RecipientPickerView.swift
Adds an explicit nil "Choose…" option and per-identity accessibilityIdentifiers to the local identity picker; switches from .pickerStyle(.menu) to accessibleFormPicker("recipient.identityPicker").
TransitionInputView multi-picker accessibility
Views/TransitionInputView.swift
Replaces MenuPickerStyle with accessibleInlinePicker and adds accessibilityIdentifiers to placeholder and dynamic options across six picker types: select, token selector, contract, document type, identity, and recipient identity.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested reviewers

  • shumkov
  • llbartekll
  • ZocoLini

Poem

🐇 Hop hop, the pickers all have names now,
Each row tagged for the robots to see!
.accessibleInlinePicker leads the way,
accountIndex keeps identifiers stable and free.
No binding was harmed in this fluffy relay ~

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding UI automation accessibility to Picker components in SwiftExampleApp by exposing their options to the accessibility tree.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/cool-brattain-b8207a

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@thepastaclaw

thepastaclaw commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

✅ Review complete (commit 2ff2000)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TransitionInputView.swift`:
- Around line 124-135: The accessibility identifiers in the Picker blocks use
fixed hardcoded strings like "transition.selectPicker" and
"transition.select.none" that are not namespaced by the input key, causing
collisions when multiple TransitionInputView instances render together. Replace
the hardcoded accessibility identifier prefixes with a namespaced pattern that
includes the input key (such as input.name) in all Picker container
accessibleInlinePicker calls and in the tag accessibilityIdentifier values for
both the "Select..." option and the ForEach option items. Apply this namespacing
pattern consistently across all picker blocks in the file, including the ones
for token, contract, documentType, identity, and recipientIdentity selections,
so that each picker instance has unique, collision-free identifiers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e6afa0b1-32d4-4f85-baa7-8563d04c2393

📥 Commits

Reviewing files that changed from the base of the PR and between e2039e5 and 26935e7.

📒 Files selected for processing (5)
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/Components/AccessiblePicker.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/Components/RecipientPickerView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/CreateIdentityView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TopUpIdentityView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TransitionInputView.swift

@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

✅ DashSDKFFI.xcframework built for this PR.

SwiftPM (host the zip at a stable URL, then use):

.binaryTarget(
  name: "DashSDKFFI",
  url: "https://your.cdn.example/DashSDKFFI.xcframework.zip",
  checksum: "fae8cd0337158187cc5f2297cda29bc34cc6183122aea006669cf741307a2776"
)

Xcode manual integration:

  • Download 'DashSDKFFI.xcframework' artifact from the run link above.
  • Drag it into your app target (Frameworks, Libraries & Embedded Content) and set Embed & Sign.
  • If using the Swift wrapper package, point its binaryTarget to the xcframework location or add the package and place the xcframework at the expected path.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Pure SwiftUI accessibility/automation change in SwiftExampleApp; no FFI, Rust, or consensus surface touched. One in-scope suggestion from Codex stands: the shared RecipientPickerView is always Form-hosted at its call sites, so the inline-picker choice forfeits the per-row identifiers that the new helpers were designed to expose. Claude's expanding-inline-picker note is an acknowledged trade-off in the PR description and is dropped.

🟡 1 suggestion(s)

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/Components/RecipientPickerView.swift`:
- [SUGGESTION] packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/Components/RecipientPickerView.swift:136: Use the navigationLink helper for the local-identity picker so per-row identifiers actually surface
  Every current call site of `RecipientPickerView` embeds it in a `Form` `Section` (TransferCreditsView, TokenTransferActionView, TokenMintActionView, TokenFreezeActionView, TokenUnfreezeActionView, TokenDestroyFrozenFundsActionView). AccessiblePicker.swift documents that with `.accessibleInlinePicker` SwiftUI does not surface per-row `accessibilityIdentifier`s — every option inherits the container id — so the carefully-constructed `recipient.identity.<base58>` ids on lines 129 and 133 are effectively dead, and idb has to fall back to selecting by `displayName`. `PersistentIdentity.displayName` can collide (two identities with the same alias / DPNS name), making automation ambiguous. Since the picker is always Form-hosted, switching to `.accessibleFormPicker` preserves the stable per-row ids the helpers were designed to expose.

}
}
.pickerStyle(.menu)
.accessibleInlinePicker("recipient.identityPicker")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: Use the navigationLink helper for the local-identity picker so per-row identifiers actually surface

Every current call site of RecipientPickerView embeds it in a Form Section (TransferCreditsView, TokenTransferActionView, TokenMintActionView, TokenFreezeActionView, TokenUnfreezeActionView, TokenDestroyFrozenFundsActionView). AccessiblePicker.swift documents that with .accessibleInlinePicker SwiftUI does not surface per-row accessibilityIdentifiers — every option inherits the container id — so the carefully-constructed recipient.identity.<base58> ids on lines 129 and 133 are effectively dead, and idb has to fall back to selecting by displayName. PersistentIdentity.displayName can collide (two identities with the same alias / DPNS name), making automation ambiguous. Since the picker is always Form-hosted, switching to .accessibleFormPicker preserves the stable per-row ids the helpers were designed to expose.

Suggested change
.accessibleInlinePicker("recipient.identityPicker")
.accessibleFormPicker("recipient.identityPicker")

source: ['codex']

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved in 2ff2000Use the navigationLink helper for the local-identity picker so per-row identifiers actually surface no longer present.

Auto-resolved by the review system based on the latest commit diff. If you believe this was closed in error, reopen the thread.

Address automated review feedback on the accessible-picker change:

- TransitionInputView: namespace every picker's accessibility identifiers
  by the input's stable field key (`transition.\(input.name).…`). A single
  builder renders many `TransitionInputView` instances, so fixed strings
  like `transition.selectPicker` / `transition.select.none` collided across
  input fields and made automation queries ambiguous. (CodeRabbit.)

- RecipientPickerView: use `.accessibleFormPicker` (navigationLink) instead
  of `.accessibleInlinePicker`. All six call sites embed this view in a Form
  `Section`, so the inline style needlessly forfeited the per-row
  `recipient.identity.<base58>` identifiers (inline rendering drops them).
  navigationLink pushes a real list and surfaces them. (thepastaclaw/Codex.)

Verified on the iOS simulator with idb: the recipient picker now pushes a
list whose rows report unique `recipient.identity.<base58>` ids (no
duplicates). App builds clean for the simulator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@QuantumExplorer
QuantumExplorer merged commit 7e5d6f2 into v3.1-dev Jun 15, 2026
17 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/cool-brattain-b8207a branch June 15, 2026 18:17

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Pure SwiftUI accessibility fix scoped to RecipientPickerView and TransitionInputView. Carried-forward prior finding is FIXED at head 2ff2000 — RecipientPickerView.swift:141 now uses .accessibleFormPicker("recipient.identityPicker"). The codex finding about TransitionDetailView identifies a pre-existing inaccessible picker in a file not modified by this PR; tracked as out-of-scope rather than blocking. No in-scope issues remain.

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