Skip to content

feat(network): NetworkBanner + a11y motion guards + empty-state copy#52

Merged
epicexcelsior merged 4 commits into
anonmesh:v3from
epicexcelsior:epic/devnet-banner
May 16, 2026
Merged

feat(network): NetworkBanner + a11y motion guards + empty-state copy#52
epicexcelsior merged 4 commits into
anonmesh:v3from
epicexcelsior:epic/devnet-banner

Conversation

@epicexcelsior
Copy link
Copy Markdown
Collaborator

Summary

  • Adds a slim DEVNET / TESTNET / CUSTOM-RPC banner pinned above every screen so Phantom-trained users see the cluster before they send. Mainnet stays chrome-free.
  • Wraps the top 6 looping animations (PulseDot / Skeleton / NetworkStatusBadge / PigeonLoader / onboarding LoadingOverlay / MessagesScreen sonar) in a useReducedMotion guard.
  • Rewrites 5 bland "No X" empty-state strings to teach the next action (receive no-wallet, home nearby-peers card, conversations drawer, channel roster).

Why

Roadmap B1 / Tier 0.5: above-the-fold devnet disclosure. Today the only signal that the app is on devnet is the explorer-link query string on the success screen, by which point a real-funds mistake is already on chain. Phantom users have no other cue.

Roadmap Tier 4.1: a11y motion audit. The audit flagged 15+ infinite Animated.loop / withRepeat(-1) cycles with zero reduce-motion guards. This PR knocks out the 6 most visible.

Roadmap Tier 4.2: empty-state copy audit. Bland labels reframed to point at the next action.

Changes

NetworkBanner

  • New components/ui/NetworkBanner.tsx + detectCluster() helper.
  • Source: process.env.EXPO_PUBLIC_SOLANA_RPC (matches src/infrastructure/network/connection.ts), falls back to devnet.
  • Copy: amber DEVNET / TESTNET . testnet funds only; neutral CUSTOM RPC . hostname; hidden on mainnet.
  • accessibilityRole="alert" + accessibilityLiveRegion="polite"; pinned at AppShell root with absolute positioning + zIndex 1000 + pointerEvents="box-none".
  • Single root mount in app/_layout.tsx covers wallet tab, receive (transparent-modal), and onboarding splash.

Reduce-motion guards (useReducedMotion from react-native-reanimated)

  • components/ui/PulseDot.tsx
  • components/ui/Skeleton.tsx
  • components/ui/PigeonLoader.tsx
  • components/wallet/NetworkStatusBadge.tsx
  • components/onboarding/LoadingOverlay.tsx (dots + caret)
  • screens/MessagesScreen.tsx (NoPeersScreen sonar)

Outstanding loops deferred for a follow-up: components/nodes/MeshMap.tsx, components/settings/RadarScan.tsx.

Copy

  • app/receive.tsx no-wallet panel
  • components/home/NearbyPeersCard.tsx (offline + no-peers labels)
  • components/messages/PeersDrawer.tsx (no matches + no conversations)
  • components/messages/GroupMembersSheet.tsx (empty channel roster)

What did not ship

  • Messages-first onboarding (PITCH section 6 pivot 1) was scoped out as too large a flow rewrite to ship in one tight PR. The current onboarding still leads with wallet creation; that work is tracked separately.

Test plan

  • tsc --noEmit clean
  • Smoke-test app boot: banner renders on Wallet, Messages, Nodes, Settings tabs
  • Smoke-test receive screen: banner visible above the scrim
  • Smoke-test onboarding splash: banner visible above ASCII hero
  • Set EXPO_PUBLIC_SOLANA_RPC=https://api.mainnet-beta.solana.com -> banner hides
  • Set EXPO_PUBLIC_SOLANA_RPC=https://some-custom-rpc.example.com -> "CUSTOM RPC . some-custom-rpc.example.com"
  • Enable iOS Reduce Motion -> PulseDot / skeletons / sonar / pigeon pulse all stop looping
  • Empty conversation list -> "Scan a contact QR..." appears

epicexcelsior added a commit to epicexcelsior/anon0mesh that referenced this pull request May 16, 2026
Two of the largest infinite Animated.loop / withRepeat(-1) sites that were
deferred from PR anonmesh#52. Adds `useReducedMotion()` guards using the same
pattern as PulseDot / Skeleton / NetworkStatusBadge / PigeonLoader.

- MeshMap: the "me" node pulse halo + ghost skeleton breathe both honor
  reduce-motion. Pinned to mid-frame opacity so the indicators still read
  but don't loop. No structural changes.
- RadarScan: rotation sweep pinned at 0deg + center dot held at resting
  scale under reduce-motion. Static ring guides still convey state.

Both visual-only — happy path unchanged.

Per ROADMAP Tier 4.1 (a11y motion audit).
epicexcelsior added a commit that referenced this pull request May 16, 2026
* chore(preview): wrap dead Yield/Swap/Send panel controls in PreviewedActions

These three wallet panels are not exported from `components/wallet/index.ts`
today (per PR #47) but the source still ships in the bundle and contains
live Pressables that look real. Wrap the dead CTAs in <PreviewedActions>
and add a <PreviewBadge label="…coming soon"> above each panel so that if
anything ever does render them, the affordance reads as preview-only.

- YieldPanel: deposit/withdraw vault buttons wrapped (the expand/collapse
  Pressable is purely local UI state, left alone).
- SwapPanel: SWAP + "new order" CTAs wrapped (flip-asset Pressable left).
- SendPanel: "send privately" + "new transfer" CTAs wrapped (asset picker
  and MAX shortcut left — local state only).

Per AUDIT A6 / ROADMAP § 0.A.8.

* feat(a11y): honor reduce-motion on MeshMap + RadarScan loops

Two of the largest infinite Animated.loop / withRepeat(-1) sites that were
deferred from PR #52. Adds `useReducedMotion()` guards using the same
pattern as PulseDot / Skeleton / NetworkStatusBadge / PigeonLoader.

- MeshMap: the "me" node pulse halo + ghost skeleton breathe both honor
  reduce-motion. Pinned to mid-frame opacity so the indicators still read
  but don't loop. No structural changes.
- RadarScan: rotation sweep pinned at 0deg + center dot held at resting
  scale under reduce-motion. Static ring guides still convey state.

Both visual-only — happy path unchanged.

Per ROADMAP Tier 4.1 (a11y motion audit).

* docs(release): pre-release smoke-test playbook (ROADMAP Tier 1.2)

Adds `docs/SMOKE_TEST.md` — the 5-minute manual gate that runs before
every tester APK release. Seven numbered steps with explicit pass/fail
criteria, each linked to an AUDIT / ROADMAP ID for traceability.

Steps:
1. Cold install + onboarding
2. Local wallet generation
3. DEVNET banner visible (Tier 0.5 / B1)
4. Send 0.001 SOL to self (PR #45 confirmation)
5. Nodes screen honesty (no fixture data)
6. Seed reveal biometric gate
7. Airplane-mode toggle

Adapted from § 10 of LOCAL_NOTES/REVIEW-2026-05-13/raw-tracks/
OFFGRID_FALLBACK_AUDIT.md ("Off-grid testing playbook") for the
team's actual release workflow. Includes a sign-off table.

* chore(diagnostics): warn on 3 silent-failure sites (TxDetailModal + MeshRpcAdapter)

Drops overlap w/ #53 (ReceivePanel + LxmfContext warns) — #53 owns those.
Per AUDIT § 3 silent failure inventory.
epicexcelsior and others added 4 commits May 15, 2026 19:24
Audit per Tier 4.1 -- the app had 7 components running infinite Animated.loop
or reanimated withRepeat(-1) cycles with no reduce-motion guard. iOS/Android
"Reduce Motion" users get vestibular discomfort from looping breathing /
pulse / shimmer effects. This pass wraps the five most visible:

- PulseDot           -- header presence dots (every screen with PulseDot)
- Skeleton           -- every list/card placeholder during loads
- NetworkStatusBadge -- wallet-tab mesh/isolated presence pulse
- PigeonLoader       -- send-flow breathing scale on the pigeon sprite
- LoadingOverlay     -- onboarding caret blink + 3-dot stagger

Pattern: `useReducedMotion()` from react-native-reanimated (re-uses the
same AccessibilityInfo subscription under the hood, works alongside the
RN Animated API the older components use). When reduced motion is on we
short-circuit before starting the loop and pin the relevant value to a
static state that still reads correctly with a screen reader.

Remaining loopers tracked for a follow-up: components/nodes/MeshMap.tsx
and components/settings/RadarScan.tsx (less above-the-fold, larger surface
area to retest, deferred to keep the scope tight).

Roadmap: Tier 4.1 ("a11y motion audit").
Tier 4.2 sweep -- bland "No X / X yet" labels replaced with copy that
points the user at the next step they can take.

- app/receive.tsx (no-wallet receive panel)
  "Connect wallet to receive" → "Set up your wallet to share an address"
  Subtitle now routes users to the home tab instead of restating the
  problem.

- components/home/NearbyPeersCard.tsx (wallet-tab home card)
  "No nearby peers yet" → "Tap to scan for nearby peers"
  "Mesh offline" → "Mesh offline -- tap to start"
  The card already navigates to the Nodes tab on press; the label now
  advertises that affordance instead of stating an absence.

- components/messages/PeersDrawer.tsx (conversation list empty state)
  "No conversations yet" → "Scan a contact QR to start your first conversation"
  "No matches" → "No matches -- paste an LXMF hash or scan a QR"

- components/messages/GroupMembersSheet.tsx (empty channel roster)
  Reframed from passive ("Members appear here once they send a message")
  to active ("Share the channel invite -- members appear here after their
  first message").

5 strings touched. Larger UX rewrite (replace inbox-only empty states
with full first-run guidance) tracked separately as messages-first
onboarding work.

Roadmap: Tier 4.2 ("empty-state copy audit").
Follow-on to the Tier 4.1 audit -- NoPeersScreen runs three offset
withRepeat sonar rings as the visual "scanning" affordance whenever a
user has zero peers. Under iOS/Android "Reduce Motion" we now short-
circuit before scheduling any of the three loops and pin all three ring
shared values to 0, so only the static center icon + "SCANNING FOR
PEERS" label render. Same information, no vestibular motion.

Roadmap: Tier 4.1 ("a11y motion audit").
@epicexcelsior epicexcelsior marked this pull request as ready for review May 16, 2026 03:26
@epicexcelsior epicexcelsior merged commit e7fcd21 into anonmesh:v3 May 16, 2026
1 check passed
@epicexcelsior epicexcelsior deleted the epic/devnet-banner branch May 16, 2026 05:08
epicexcelsior added a commit that referenced this pull request May 16, 2026
* chore(preview): wrap dead Yield/Swap/Send panel controls in PreviewedActions

These three wallet panels are not exported from `components/wallet/index.ts`
today (per PR #47) but the source still ships in the bundle and contains
live Pressables that look real. Wrap the dead CTAs in <PreviewedActions>
and add a <PreviewBadge label="…coming soon"> above each panel so that if
anything ever does render them, the affordance reads as preview-only.

- YieldPanel: deposit/withdraw vault buttons wrapped (the expand/collapse
  Pressable is purely local UI state, left alone).
- SwapPanel: SWAP + "new order" CTAs wrapped (flip-asset Pressable left).
- SendPanel: "send privately" + "new transfer" CTAs wrapped (asset picker
  and MAX shortcut left — local state only).

Per AUDIT A6 / ROADMAP § 0.A.8.

* feat(a11y): honor reduce-motion on MeshMap + RadarScan loops

Two of the largest infinite Animated.loop / withRepeat(-1) sites that were
deferred from PR #52. Adds `useReducedMotion()` guards using the same
pattern as PulseDot / Skeleton / NetworkStatusBadge / PigeonLoader.

- MeshMap: the "me" node pulse halo + ghost skeleton breathe both honor
  reduce-motion. Pinned to mid-frame opacity so the indicators still read
  but don't loop. No structural changes.
- RadarScan: rotation sweep pinned at 0deg + center dot held at resting
  scale under reduce-motion. Static ring guides still convey state.

Both visual-only — happy path unchanged.

Per ROADMAP Tier 4.1 (a11y motion audit).

* docs(release): pre-release smoke-test playbook (ROADMAP Tier 1.2)

Adds `docs/SMOKE_TEST.md` — the 5-minute manual gate that runs before
every tester APK release. Seven numbered steps with explicit pass/fail
criteria, each linked to an AUDIT / ROADMAP ID for traceability.

Steps:
1. Cold install + onboarding
2. Local wallet generation
3. DEVNET banner visible (Tier 0.5 / B1)
4. Send 0.001 SOL to self (PR #45 confirmation)
5. Nodes screen honesty (no fixture data)
6. Seed reveal biometric gate
7. Airplane-mode toggle

Adapted from § 10 of LOCAL_NOTES/REVIEW-2026-05-13/raw-tracks/
OFFGRID_FALLBACK_AUDIT.md ("Off-grid testing playbook") for the
team's actual release workflow. Includes a sign-off table.

* chore(diagnostics): warn on 3 silent-failure sites (TxDetailModal + MeshRpcAdapter)

Drops overlap w/ #53 (ReceivePanel + LxmfContext warns) — #53 owns those.
Per AUDIT § 3 silent failure inventory.
epicexcelsior added a commit that referenced this pull request May 16, 2026
…52)

* feat(a11y): honor reduce-motion in top 5 looping animations

Audit per Tier 4.1 -- the app had 7 components running infinite Animated.loop
or reanimated withRepeat(-1) cycles with no reduce-motion guard. iOS/Android
"Reduce Motion" users get vestibular discomfort from looping breathing /
pulse / shimmer effects. This pass wraps the five most visible:

- PulseDot           -- header presence dots (every screen with PulseDot)
- Skeleton           -- every list/card placeholder during loads
- NetworkStatusBadge -- wallet-tab mesh/isolated presence pulse
- PigeonLoader       -- send-flow breathing scale on the pigeon sprite
- LoadingOverlay     -- onboarding caret blink + 3-dot stagger

Pattern: `useReducedMotion()` from react-native-reanimated (re-uses the
same AccessibilityInfo subscription under the hood, works alongside the
RN Animated API the older components use). When reduced motion is on we
short-circuit before starting the loop and pin the relevant value to a
static state that still reads correctly with a screen reader.

Remaining loopers tracked for a follow-up: components/nodes/MeshMap.tsx
and components/settings/RadarScan.tsx (less above-the-fold, larger surface
area to retest, deferred to keep the scope tight).

Roadmap: Tier 4.1 ("a11y motion audit").

* copy(empty-states): teach the next action, not "No X"

Tier 4.2 sweep -- bland "No X / X yet" labels replaced with copy that
points the user at the next step they can take.

- app/receive.tsx (no-wallet receive panel)
  "Connect wallet to receive" → "Set up your wallet to share an address"
  Subtitle now routes users to the home tab instead of restating the
  problem.

- components/home/NearbyPeersCard.tsx (wallet-tab home card)
  "No nearby peers yet" → "Tap to scan for nearby peers"
  "Mesh offline" → "Mesh offline -- tap to start"
  The card already navigates to the Nodes tab on press; the label now
  advertises that affordance instead of stating an absence.

- components/messages/PeersDrawer.tsx (conversation list empty state)
  "No conversations yet" → "Scan a contact QR to start your first conversation"
  "No matches" → "No matches -- paste an LXMF hash or scan a QR"

- components/messages/GroupMembersSheet.tsx (empty channel roster)
  Reframed from passive ("Members appear here once they send a message")
  to active ("Share the channel invite -- members appear here after their
  first message").

5 strings touched. Larger UX rewrite (replace inbox-only empty states
with full first-run guidance) tracked separately as messages-first
onboarding work.

Roadmap: Tier 4.2 ("empty-state copy audit").

* feat(a11y): honor reduce-motion on MessagesScreen no-peers sonar

Follow-on to the Tier 4.1 audit -- NoPeersScreen runs three offset
withRepeat sonar rings as the visual "scanning" affordance whenever a
user has zero peers. Under iOS/Android "Reduce Motion" we now short-
circuit before scheduling any of the three loops and pin all three ring
shared values to 0, so only the static center icon + "SCANNING FOR
PEERS" label render. Same information, no vestibular motion.

Roadmap: Tier 4.1 ("a11y motion audit").

* fix(lint): escape apostrophe in receive empty-state copy

---------

Co-authored-by: epicexcelsior <intern@anonme.sh>
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.

1 participant