UI/UX Part 12 — Nearby, QR/NFC Pairing & Device Linking UX Architecture
Reusable P2P Communication Platform
Status: UI/UX architecture specification
UI Series: Part 12
Desktop UI: Dioxus
Android UI: Kotlin + Jetpack Compose
Core runtime: Rust
Primary purpose: define the complete nearby-discovery, QR, NFC, SAS, contact-pairing, own-device linking, offline bootstrap, permission, trust, replay/expiry, mismatch, accessibility, privacy, and Rust presentation architecture across desktop and Android.
1. Purpose
Nearby/bootstrap flows are where an initially unknown physical device becomes one of several very different things:
a discovered nearby candidate
a contact
a cryptographically verified contact
one of my authorized devices
a temporary pairing session
These states must never be blurred.
The governing principle is:
Proximity proves only that something is nearby. Authentication and authorization must still be explicit.
2. Architectural Position
Platform Discovery
BLE / Wi-Fi / NFC / Camera
│
▼
Rust Proximity Abstraction
│
├── observations
├── rotating discovery identifiers
├── authenticated bootstrap
├── invite validation
├── SAS transcript
└── device authorization
│
▼
Bootstrap Presentation Service
│
┌──────┴──────┐
│ │
Dioxus Compose
Desktop Android
3. Critical Trust Distinctions
Nearby
≠
Known contact
≠
Accepted contact
≠
Verified contact
≠
Authorized own device
The UI must preserve these distinctions in labels, colors, actions, confirmation language, and navigation.
4. Main Entry Points
Recommended:
Contacts → Add Contact
Contacts → Nearby
Contacts → Scan QR
Devices → Link New Device
Devices → Show Link QR
Optional Android shortcut:
Tap NFC
5. Do Not Use One Generic "Pair" Button
Bad:
Pair device
for both contacts and account-device authorization.
Use explicit language:
Add Contact
Verify Contact
Link My Device
6. Nearby Discovery Concepts
Rust exposes nearby candidates as observations.
#![allow(unused)] fn main() { pub struct NearbyCandidateView { pub candidate_id: NearbyCandidateId, pub display_hint: Option<String>, pub proximity: ProximityHint, pub state: NearbyCandidateState, pub method: NearbyMethodSummary, } }
7. Candidate Identity
NearbyCandidateId is temporary.
It is not:
AccountId
DeviceId
until authenticated.
8. Nearby Candidate States
#![allow(unused)] fn main() { pub enum NearbyCandidateState { Discovered, Connecting, Authenticating, AwaitingConfirmation, Authenticated, Failed, Expired, } }
9. Nearby Methods
User-facing:
Nearby
Normal UI should not care whether candidate came from:
BLE
Wi-Fi Aware
Wi-Fi Direct
LAN
Bluetooth Classic
10. Diagnostics
Advanced diagnostics may expose underlying method.
11. Proximity Hint
Possible user-facing classes:
#![allow(unused)] fn main() { pub enum ProximityHint { VeryNear, Near, Nearby, Unknown, } }
Avoid displaying false precision such as:
1.7 meters
unless hardware truly supports reliable distance.
12. RSSI
Raw RSSI belongs only in diagnostics.
13. Nearby Discovery Screen — Android
Recommended:
TopAppBar
Nearby
Discovery status
Searching nearby…
LazyColumn
Candidate
Candidate
Candidate
Actions
Scan QR
Enter Code
14. Nearby Discovery Screen — Desktop
Recommended:
Nearby Devices
Search status
Candidate list
Alternative:
Show My QR
Scan QR
Enter Code
15. Discovery Start
User explicitly enters Nearby or taps:
Find Nearby
Then Rust/platform starts bounded discovery.
16. No Permanent Discovery
Do not continuously advertise/scan indefinitely unless user enables a deliberate feature.
17. Discovery Timeout
Example:
30–120 seconds
then:
No nearby devices found
Try again
Tune by platform/radio behavior.
18. Discovery Privacy
Use rotating/ephemeral identifiers.
UI must not expose stable account ID over unauthenticated nearby advertising.
19. Unknown Candidate
Show:
Nearby device
Unverified
not a trusted profile.
20. Display Hint
A peer may advertise a human-friendly hint only if privacy policy permits.
Treat it as untrusted until authentication.
21. Selecting Nearby Candidate
Flow:
Tap/click candidate
→ connect
→ authenticate bootstrap
→ compare/confirm identity
→ choose resulting relationship
22. Candidate Connection
Show:
Connecting…
23. Authentication
Normal user-facing text:
Verifying device…
Do not expose transcript internals.
24. Confirmation
Once authenticated, show:
identity preview
name
avatar
verification method
SAS/QR confirmation if required
25. Nearby Failure
Semantic reasons:
Device moved out of range
Connection failed
Session expired
Identity could not be verified
Unsupported version
26. Retry
Creates a new session where required.
Never reuse expired nonce/session blindly.
27. QR Bootstrap Types
Use distinct payload types:
#![allow(unused)] fn main() { pub enum BootstrapQrKind { ContactInvite, ContactVerification, DeviceLink, GroupInvite, } }
28. QR Visual Differentiation
Screen title must state:
My Contact QR
Verify Identity
Link New Device
Group Invite
Do not rely on QR appearance alone.
29. Contact Invite QR
Purpose:
share contact identity/invite
May allow recipient to:
add/accept contact
but does not automatically imply completed verification unless protocol flow explicitly authenticates it.
30. Contact Verification QR
Purpose:
verify an already-known contact
Both devices participate in verification transcript.
31. Device Link QR
Purpose:
authorize another device under my account
This is the highest-risk QR flow.
32. Device-Link Warning
Before showing/accepting:
A linked device can access your account and may receive your messages.
Only link a device you control.
33. Group Invite QR
Routes to Part 09.
Not interpreted as contact/device bootstrap.
34. QR Parser
Rust owns payload parsing and cryptographic validation.
UI never decides QR type from loose text heuristics.
35. Unknown QR
Show:
This code is not supported
Never open arbitrary URL automatically from scanner unless explicit product behavior.
36. Malformed QR
Show safe error.
No crash.
37. Expired QR
This code has expired
Ask for a new code
38. Revoked QR
This code is no longer valid
39. Already Used QR
For single-use invite:
This code has already been used
40. Self QR
Scanning own contact identity:
This is your identity
Do not create self-contact.
41. Contact QR Screen
Recommended contents:
QR
display name
short fingerprint
expiry if applicable
share/copy code
42. Device Link QR Screen
Recommended contents:
Link another device
QR
expires in…
Cancel
Avoid contact-style avatar/social language.
43. QR Expiry Countdown
Can show minute-level countdown.
Do not run expensive per-row timers.
44. Screenshot Sharing
Assume a QR can be photographed/copied.
Security cannot depend on physical display secrecy alone.
45. NFC Bootstrap
Android-specific high-quality shortcut.
Flow:
Tap phones
→ Android NFC
→ Kotlin obtains payload
→ Rust validates
→ confirmation
46. NFC Is Transport, Not Trust
NFC proximity alone does not authorize account/device.
47. NFC Failure
Offer fallback:
Scan QR instead
48. NFC Unsupported
Hide or disable NFC action.
49. SAS Verification
Short Authentication String allows users to compare:
numbers
words
symbols
50. Recommended SAS UX
Show a short readable code:
482 119
or word sequence.
Buttons:
Codes match
Codes do not match
51. Do Not Use "Confirm" Alone
Explicit wording reduces mistakes.
52. SAS Accessibility
Code should be:
large
high contrast
screen-reader friendly
grouped
53. SAS Canonical Reading
Screen reader can announce groups distinctly.
Example:
482, 119
54. SAS Mismatch
Strong result:
Codes do not match
Do not continue
Actions:
Cancel
Try again
Block
depending context.
55. Mismatch Never Marks Verified
Hard rule.
56. Interrupted Verification
Remains:
Unverified
57. Verification Success
Only after Rust confirms transcript:
Identity verified
58. Verification Method History
Contact security details can record:
Verified by QR
Verified by code comparison
with timestamp if useful.
59. Contact Pairing Flow
Recommended:
Add Contact
↓
QR / Nearby / Code
↓
Authenticated Identity Preview
↓
Accept Contact
↓
Optional/Integrated Verification
↓
Contact Ready
60. Acceptance vs Verification
UI can show:
Contact added
Identity not yet verified
if verification not completed.
61. Verify Later
Contact profile can expose:
Verify Identity
62. Manual Code Entry
Fallback for:
no camera
desktop
accessibility
remote sharing
63. Manual Code Input
Use grouped fields or one pasteable field.
64. Paste Support
Allow copy/paste invite code.
65. Code Validation
Rust authoritative.
66. Typo UX
Show:
Invalid code
without leaking detailed parsing internals.
67. Own-Device Linking Flow
Recommended:
Existing trusted device:
Devices
→ Link New Device
→ Show QR/code
New device:
Set up / Link Existing Account
→ Scan QR/code
→ Authenticate
→ Existing device approves
→ Optional SAS confirmation
→ New Device Authorized
68. Device-Link Roles
Existing device:
authorizer
New device:
candidate
69. New Device Identity
Normally gets fresh:
DeviceId
device keys
It does not clone old device identity.
70. Device Link Snapshot
#![allow(unused)] fn main() { pub struct DeviceLinkSessionView { pub session: DeviceLinkSessionId, pub state: DeviceLinkState, pub candidate_name: Option<String>, pub expires_at: Timestamp, pub sas: Option<SasView>, } }
71. Device Link States
#![allow(unused)] fn main() { pub enum DeviceLinkState { Created, Scanned, Authenticating, AwaitingApproval, AwaitingSas, Authorizing, Complete, Failed, Expired, Cancelled, } }
72. Explicit Approval
Existing device must show:
Link "Pixel 10" to your account?
with consequences.
73. Candidate Device Name
Treat proposed name as untrusted until linked.
74. Approval Actions
Link Device
Cancel
75. High-Risk Confirmation
Could require:
biometric/device credential
on Android in high-security mode.
Desktop may request OS authentication where available.
76. Device Link Completion
Show:
Device linked successfully
Then route to:
Devices
77. New Device Sync
After authorization:
identity metadata
contacts
messages/history according to policy
settings
may sync.
UI should show separate:
Setting up your device…
not keep "linking" indefinitely.
78. Device Setup Progress
Possible semantic phases:
Authorizing
Syncing account
Syncing conversations
Rebuilding search
Ready
79. Search Rebuild
Part 11 derived local index.
Do not block device-ready state on full reindex unless required.
80. Device Link Cancellation
Either side may cancel.
Session becomes unusable.
81. Device Link Expiry
Short-lived.
New code required.
82. Replay Protection
If old code reused:
This link code is no longer valid
No details necessary.
83. Existing Device Lost During Link
New candidate does not become authorized without confirmed backend/security completion.
84. New Device Crashes During Link
Session may be resumed only if secure protocol permits.
Otherwise restart.
85. Duplicate Device Link
If same device already linked:
This device is already linked
86. Revoked Device Re-Link
Should require fresh authorization.
87. Offline Device Linking
Ideal local-first flow:
QR
+
local Wi-Fi/QUIC/Bluetooth
can authorize without Internet if protocol/security supports.
88. Offline Contact Verification
Likewise possible through:
QR/SAS/local transport
89. Offline UX
Do not display:
No Internet
as failure if local pairing can continue.
90. Connectivity Requirement
If a specific step needs Internet:
Internet is required for this step
only then.
91. Android Camera Flow
Compose:
Scan QR
→ request camera permission contextually
→ scanner
→ payload
→ Rust
92. Camera Permission Denied
Offer:
Enter code manually
Open Settings
93. Android Nearby Permissions
Kotlin handles version-specific permissions for:
Bluetooth
Nearby Wi-Fi
location-related legacy requirements
Rust sees normalized capability.
94. Permission Explanation
Use accurate reason:
Nearby access lets this app discover devices close to you.
95. Do Not Overclaim Location Use
If permission is technically required but precise location is not used/shared, say so accurately.
96. Android NFC
No permission flow should be invented if platform does not require one.
Use platform capability state.
97. Android Lifecycle
Discovery should react to:
foreground
background
screen changes
Stop expensive scanning when UI no longer needs it unless active link session requires continuation.
98. Process Death
Rust durable/session state determines whether pairing can resume.
Compose does not reconstruct trust from SavedStateHandle.
99. SavedStateHandle
May store:
current screen
session ID
manual code draft
but not trust completion.
100. Desktop QR Scanner
Options:
webcam
paste code
open QR image
101. Desktop v1 Recommendation
Support:
show own QR
paste/manual code
optional webcam scan
Webcam scanning should not block shipping.
102. Desktop Clipboard
Copy:
invite code
fingerprint
through explicit action.
103. Open QR Image
If supported:
file picker
→ image decoder
→ QR parser
with bounded image processing.
104. Desktop Nearby
Could rely on:
LAN
Bluetooth if available
behind same Rust proximity API.
105. No Hardware Assumption
UI only shows:
Nearby unavailable on this device
if no suitable adapter.
106. Discovery Capability Snapshot
#![allow(unused)] fn main() { pub struct ProximityCapabilityView { pub nearby_available: bool, pub qr_scan_available: bool, pub nfc_available: bool, pub manual_code_available: bool, } }
107. Capability-Driven UI
Hide unsupported permanent capabilities.
Disable temporarily unavailable capabilities with reason.
108. Nearby Candidate Expiry
If candidate disappears:
remove after grace period
or mark:
No longer nearby
briefly.
109. Candidate List Stability
Do not reorder constantly from RSSI fluctuations.
Sort by:
stable first-seen / semantic proximity class
with hysteresis.
110. Duplicate Discovery
Same physical candidate seen over BLE + LAN:
one candidate
after Rust correlation where safe.
111. Correlation Privacy
Do not correlate unrelated observations beyond security/privacy model.
112. Nearby Candidate Selection Race
If candidate expires after tap:
Device is no longer nearby
Try again
113. Multiple Simultaneous Candidates
Clearly show target identity at confirmation.
Avoid accidental approval of wrong nearby device.
114. User Confirmation Screen
Before trust-changing action show:
name
avatar
SAS/fingerprint
relationship being created
115. Device Link Confirmation
Must explicitly say:
This is one of my devices
116. Contact Confirmation
Must explicitly say:
Add this person as a contact
117. Verification Confirmation
Must explicitly say:
Verify this identity
118. Prevent Context Confusion
Use different:
screen titles
icons
button labels
help text
for contact vs own-device flows.
119. QR Sharing
Android:
system share sheet
Desktop:
copy code
copy/save QR image optional
120. QR Export Privacy
Invite payload may grant capability.
UI should warn if sharing broadly.
121. Contact Invite Warning
Example:
Anyone with this invite can send you a contact request until it expires.
Exact wording matches backend semantics.
122. Device Link QR Warning
Stronger:
Do not share this code. It can be used to link a device to your account until it expires.
123. Single-Use Link
Show:
Single use
if true.
124. Revoking Link Session
User can tap:
Cancel link code
125. Pairing Session List
Devices/security screen could show active:
Pending device link
if session persists.
126. No Hidden Pending Authorization
All active device-link sessions should be inspectable/cancellable.
127. Trust Transcript
Advanced security details can expose:
method
time
session
but not raw secrets.
128. Identity Fingerprint
Part 08.
Nearby/QR flow routes to same contact identity semantics after authentication.
129. QR Contact Acceptance from Unknown Sender
Do not auto-create trusted relationship until user confirms.
130. Group Invite QR
Part 09 receives validated payload.
131. Extension QR Types
Future plugins may register typed QR payloads only through safe extension registry.
132. Unknown Plugin QR
Show:
This code requires plugin X
only after safe type identification.
133. QR Bomb / Oversize Data
Rust parser enforces:
size
depth
field bounds
134. Image Decoder Safety
QR image import is bounded/sandboxed where possible.
135. NFC Payload Bounds
Same.
136. Nearby Protocol Bounds
Untrusted discovery frames are bounded.
UI never allocates based on advertised length.
137. Security Error Taxonomy
#![allow(unused)] fn main() { pub enum BootstrapFailureKind { Expired, Revoked, Replay, InvalidPayload, IdentityMismatch, SasMismatch, UnsupportedVersion, CandidateUnavailable, PermissionRequired, AuthorizationDenied, Internal, } }
138. User-Facing Mapping
Expired:
Code expired
Replay/revoked:
Code is no longer valid
Mismatch:
Identity could not be verified
Unsupported:
Update one of the devices and try again
139. Do Not Expose Security Oracle Details
Avoid telling attacker exactly which cryptographic field failed.
140. Version Mismatch
If safe:
This device uses an incompatible version
Update the app
141. Downgrade Protection
UI does not offer:
Continue insecurely
if Rust security policy rejects version.
142. Identity Changed During Pairing
Abort.
Show:
Identity changed during verification
Start again
143. Session Timeout
Show:
Session expired
with:
Create new code
144. Accessibility — QR
QR is never the only path.
Always offer:
Enter code manually
Compare code
where protocol supports.
145. Screen Reader
QR screen announces:
Contact invite QR code. Expires in 4 minutes. Manual code 83 14 22.
where safe.
146. Manual Code Grouping
Use large grouped digits/words.
147. Color Independence
Verified/mismatch/expired state uses:
icon
text
not color only.
148. Large Font
Codes wrap/resize without truncation.
149. RTL
UI layout mirrors.
Cryptographic/manual codes preserve canonical order.
150. Reduced Motion
Nearby scanning animation can become static progress/status.
151. Haptics Android
Useful sparingly for:
QR recognized
NFC detected
verification success
mismatch
152. Audio Cues
Optional accessibility setting for successful scan.
153. Desktop Keyboard
Nearby list:
Up/Down
Enter
QR/manual code dialog fully keyboard accessible.
154. Focus After Scan
Move focus to:
identity confirmation
not hidden scanner control.
155. TalkBack Scanner
Scanner screen needs:
instructions
manual-code alternative
close
Do not require visually aligning QR without alternative.
156. Pairing History
Contact/security details can show completed verification history.
Do not keep unnecessary failed-session logs indefinitely.
157. Failed Session Privacy
Bound retention.
158. Telemetry
Never log:
full invite code
SAS
QR payload
fingerprint
159. Safe Metrics
Possible:
method used
success/failure class
time to complete
permission denial rate
without identifiers/codes.
160. Crash Reports
Redact bootstrap payloads.
161. Nearby Diagnostics
Advanced support bundle may include:
adapter state
discovery method
failure reason code
without peer identity/secrets.
162. Contact Pairing Presentation API
#![allow(unused)] fn main() { pub trait ContactPairingPresentation { async fn create_invite( &self, policy: ContactInvitePolicy, ) -> Result<ContactInviteView, UiError>; async fn inspect_invite( &self, payload: BootstrapPayload, ) -> Result<ContactInvitePreview, UiError>; async fn accept_invite( &self, payload: BootstrapPayload, ) -> Result<ContactPairingResult, UiError>; async fn cancel_invite( &self, invite: ContactInviteId, ) -> Result<(), UiError>; } }
163. Verification Presentation API
#![allow(unused)] fn main() { pub trait PairVerificationPresentation { async fn begin( &self, account: AccountId, method: VerificationMethod, ) -> Result<VerificationSessionView, UiError>; async fn confirm_sas( &self, session: VerificationSessionId, matches: bool, ) -> Result<VerificationResultView, UiError>; async fn process_qr( &self, payload: BootstrapPayload, ) -> Result<VerificationResultView, UiError>; async fn cancel( &self, session: VerificationSessionId, ) -> Result<(), UiError>; } }
164. Nearby Presentation API
#![allow(unused)] fn main() { pub trait NearbyPresentation { async fn capabilities( &self, ) -> Result<ProximityCapabilityView, UiError>; async fn start_discovery( &self, ) -> Result<NearbyDiscoverySessionId, UiError>; async fn stop_discovery( &self, session: NearbyDiscoverySessionId, ) -> Result<(), UiError>; async fn connect( &self, candidate: NearbyCandidateId, ) -> Result<NearbyPairingSessionView, UiError>; } }
165. Device Link Presentation API
#![allow(unused)] fn main() { pub trait DeviceLinkPresentation { async fn create_invite( &self, ) -> Result<DeviceLinkInviteView, UiError>; async fn inspect_invite( &self, payload: BootstrapPayload, ) -> Result<DeviceLinkPreview, UiError>; async fn approve( &self, session: DeviceLinkSessionId, ) -> Result<(), UiError>; async fn deny( &self, session: DeviceLinkSessionId, ) -> Result<(), UiError>; async fn confirm_sas( &self, session: DeviceLinkSessionId, matches: bool, ) -> Result<(), UiError>; async fn cancel( &self, session: DeviceLinkSessionId, ) -> Result<(), UiError>; } }
166. Platform Scanner Interface
#![allow(unused)] fn main() { pub enum PlatformBootstrapInput { QrPayload(Vec<u8>), NfcPayload(Vec<u8>), ManualCode(String), } }
Platform UI obtains input.
Rust validates.
167. Do Not Give Camera/NFC to Domain Core
Domain receives payload, not Activity/camera objects.
168. Android ViewModel
Owns:
scanner visibility
permission effects
manual-code text
selected candidate
confirmation sheets
Rust owns:
candidate/auth/session/trust state
169. Dioxus Presenter
Owns:
nearby selection
manual-code field
QR window
webcam/paste flow
focus
170. Nearby Events
#![allow(unused)] fn main() { pub enum NearbyUiEvent { CandidateAdded(NearbyCandidateView), CandidateUpdated(NearbyCandidateView), CandidateRemoved(NearbyCandidateId), DiscoveryStateChanged(NearbyDiscoveryState), } }
171. Verification Events
#![allow(unused)] fn main() { pub enum VerificationUiEvent { SessionChanged(VerificationSessionView), VerificationCompleted(VerificationResultView), VerificationFailed(BootstrapFailureKind), } }
172. Device Link Events
#![allow(unused)] fn main() { pub enum DeviceLinkUiEvent { SessionChanged(DeviceLinkSessionView), LinkCompleted(DeviceSummaryView), LinkFailed(BootstrapFailureKind), } }
173. Event Locality
Only update current pairing flow/candidate.
Do not refresh whole contacts/devices screens unnecessarily.
174. Pairing Session Persistence
Sensitive session state remains Rust-owned.
UI can reconnect by session ID.
175. Expired Session Reattach
Rust returns:
Expired
UI creates fresh flow.
176. Parallel Pairing
Limit concurrent high-risk device-link sessions.
177. Contact Invites
Multiple may exist if product allows.
178. Own-Device Link Limit
Recommendation:
one or very few active link sessions
to reduce confusion.
179. Pairing Rate Limits
Backend security policy.
UI can show:
Try again later
180. Abuse Protection
Unknown nearby devices cannot force modal dialogs.
They only appear in nearby list.
181. Unsolicited NFC
Requires deliberate user interaction/session where possible.
182. Unsolicited QR
Scanner requires user opening scanner.
183. Contact Request After Scan
User remains in control.
No silent message permission.
184. Own Device Security
Device-link flow should be accessible from:
Devices
Security Center
not general contact add shortcut alone.
185. Device Link Audit
After link:
New device added
security event appears on existing devices.
186. Device Link Notification
Other trusted devices may be notified.
187. Unauthorized Attempt
If denied/failed:
Device was not linked
and security system may log event.
188. Revoke Newly Linked Device
Devices screen allows immediate revoke.
189. Device Naming
After link, user can set friendly local/account device name:
Pixel 10
Desktop
Laptop
190. Name Is Not Device Identity
DeviceId remains authority.
191. Device Type Icon
Android phone/desktop/tablet icons are informational only.
192. Contact Nearby Reverification
Existing contact discovered nearby can show:
Verify Alice nearby
after authenticated identity match.
193. Identity Mismatch Existing Contact
Strong warning.
Do not silently create second contact with same display name.
194. Duplicate Nearby Candidates
Rust should deduplicate authenticated same identity.
195. Contact Verification Success Navigation
Return to contact profile/conversation.
196. Device Link Success Navigation
Return to:
Devices
not Contacts.
197. Group Invite Success Navigation
Return to group.
198. Typed Result Routing
Bootstrap result:
#![allow(unused)] fn main() { pub enum BootstrapResult { ContactAdded(AccountId), ContactVerified(AccountId), DeviceLinked(DeviceId), GroupJoined(GroupId), } }
UI routes accordingly.
199. No Generic Success Screen
Use context-specific success message.
200. Empty Nearby State
No nearby devices found
Actions:
Try again
Scan QR
Enter Code
201. Nearby Unavailable
Nearby discovery isn't available on this device
Fallbacks remain.
202. Bluetooth Off
Android can show:
Turn on Bluetooth
via platform settings/action where needed.
203. Wi-Fi Off
Only request enabling if current method requires it.
204. Airplane Mode
Local Bluetooth/Wi-Fi behavior varies.
UI should use capability state, not assume total impossibility.
205. Camera Unavailable
Offer manual code.
206. NFC Off
Offer:
Turn on NFC
or QR.
207. Pairing Loading
Use semantic steps:
Connecting
Verifying
Waiting for confirmation
Authorizing
not indefinite spinner.
208. Cancellation
Every waiting screen exposes:
Cancel
209. Back Navigation Android
Back cancels/dismisses only current UI flow.
If cancelling security session is necessary:
ViewModel asks Rust cancel
210. Desktop Escape
Esc closes current modal after safe cancellation.
211. Background Android
If user leaves scanner:
stop camera
Pairing session may remain if already authenticated.
212. Foreground Return
Request fresh session snapshot.
213. Screen Rotation
Does not duplicate scan acceptance.
Use session IDs.
214. Duplicate Scanner Event
Same QR may be decoded many frames.
Deduplicate by payload/session.
215. Haptic/Visual Scan Confirmation
Once recognized:
freeze scanner
show processing
to avoid repeated scans.
216. Manual Entry Paste
If pasted code includes spaces/hyphens:
normalize safely
in parser.
217. Copy Code
Display expiration next to code so users understand lifetime.
218. QR Brightness
Android may optionally offer:
increase screen brightness
while showing QR.
Restore afterward.
219. QR Screen Sleep
Keep screen awake briefly while actively showing pairing QR if appropriate.
220. Privacy Against Shoulder Surfing
Device-link codes should expire quickly.
Contact QR may have less-sensitive semantics but still bounded.
221. Screenshot Testing
Required states:
nearby searching
candidate found
connecting
verification
SAS
match
mismatch
QR contact
QR device link
expired QR
permission denied
manual code
device approval
link complete
dark mode
large font
RTL
222. Android Test Matrix
Verify:
camera permission
Bluetooth/nearby permission
NFC
QR duplicate frames
rotation
background/foreground
process death
TalkBack
manual-code fallback
223. Desktop Test Matrix
Verify:
nearby list
keyboard selection
manual code
paste code
QR display
optional webcam scanner
focus
224. Security Test Matrix
Verify:
expired invite
replay
revoked invite
wrong SAS
identity mismatch
unsupported version
self scan
device already linked
denied authorization
225. Offline Tests
Verify QR/SAS/local pairing works without Internet when backend path supports it.
226. Multi-Device Test
Link new Android device from desktop.
Expected:
new unique DeviceId
security event
new device appears in Devices
227. Contact vs Device Confusion Test
Ensure a device-link QR can never render ordinary:
Add Contact
confirmation.
228. QR Type Confusion Test
Group/contact/device QR cannot be interpreted as another type.
229. Accessibility Test
Complete contact verification without camera using manual/SAS flow.
230. Performance
Nearby UI update rates must be bounded.
RSSI changes should not produce 60 recompositions/sec.
231. Discovery Event Coalescing
Coalesce candidate signal/proximity changes.
232. Scanner Performance
QR decoding should run off main UI thread where appropriate.
233. Battery
Discovery/camera/NFC sessions are short-lived and user-driven.
234. Initial Production Scope
Ship:
Nearby discovery abstraction
contact invite QR
contact verification QR/SAS
manual code
Android QR camera
Android NFC where practical
desktop QR display/manual/paste
own-device linking as separate flow
explicit device authorization
session expiry/replay handling
offline local pairing where supported
Defer:
continuous ambient discovery
automatic contact exchange
ultra-wideband precise distance
complex social proximity
background NFC workflows
235. Definition of Done
UI/UX Part 12 is complete when:
- nearby observation is clearly distinct from authenticated identity
- contact addition, contact verification, own-device linking, and group invites use separate typed flows
- QR payload type is authenticated/validated by Rust
- device-link QR is visually and semantically distinct from contact QR
- own-device authorization requires explicit approval
- SAS comparison has explicit Match/Mismatch actions
- mismatches, expiry, replay, revocation, and version errors cannot result in trust
- Android camera, nearby, NFC, lifecycle, and permission behavior are defined
- desktop manual/paste/QR and optional webcam flows are defined
- QR/manual/SAS accessibility fallbacks are available
- session and trust state remain Rust-owned across lifecycle/process changes
- offline pairing is treated as valid where local transports allow
- no raw Bluetooth/Wi-Fi transport detail leaks into normal UX
- nearby candidate lists are stable, bounded, and privacy-aware
- security-sensitive codes/payloads are excluded from telemetry/logs
- typed Rust presentation APIs for contact pairing, verification, nearby discovery, and device linking are defined
- contact/device/group result routing uses distinct stable IDs
- security, offline, lifecycle, accessibility, and type-confusion test matrices are specified
236. Final Architecture
PLATFORM INPUTS
┌──────────────┼──────────────┐
│ │ │
Nearby QR NFC
│ │ │
└──────────────┼──────────────┘
│
Rust Bootstrap Core
│
┌──────────────┼───────────────┐
│ │ │
Contact Pairing Verification Device Linking
│ │ │
└──────────────┼───────────────┘
│
Typed Result
┌──────────────┼───────────────┐
│ │ │
AccountId Verified DeviceId
AccountId
Platform UI:
Desktop Dioxus:
nearby list
QR display
manual/paste code
optional webcam scan
Android Compose:
nearby
QR camera
NFC
manual code
platform permissions
237. Final Principle
The proximity/bootstrap UX should make secure pairing feel simple without pretending that proximity itself creates trust.
The correct mental model is:
I can see this device nearby
→ I authenticate who it is
→ I explicitly choose what relationship to create
and, for own devices:
I scanned a device-link code
→ I verify the candidate
→ I explicitly authorize it into my account
not:
it is nearby, therefore it is trusted
This preserves the security architecture while giving Dioxus desktop and Android Compose clear, native, offline-capable pairing flows.