Skip to content

refactor: wallet + network hygiene#47

Merged
epicexcelsior merged 5 commits into
anonmesh:v3from
epicexcelsior:epic/wallet-network-refactor
May 15, 2026
Merged

refactor: wallet + network hygiene#47
epicexcelsior merged 5 commits into
anonmesh:v3from
epicexcelsior:epic/wallet-network-refactor

Conversation

@epicexcelsior
Copy link
Copy Markdown
Collaborator

@epicexcelsior epicexcelsior commented May 15, 2026

Five tight refactors. No behavior change. +236 / -190 LOC.

  1. Drop dead Send/Swap/Yield/WalletTabs from components/wallet/index.ts barrel. Files kept with // FUTURE header so the design isn't lost. Zero consumers outside the barrel (grep). Stops accidental wallet-bento re-mounts of "MPC 3/3" / "JITO_RATE" theatre.
  2. Extract solanaConnection singleton to src/infrastructure/network/connection.ts. sendTransaction.ts re-exports it so existing callers don't churn. useNetworkMode + useWalletBalance import direct.
  3. Consolidate sliceNewEvents to src/utils/sliceNewEvents.ts. Three near-identical copies in LxmfContext, MessagesScreen, useMessageNotifications — the screen + notifications versions silently dropped events when the prev anchor fell off the capped buffer. Util now uses LxmfContext's safer semantics.
  4. Consolidate relTime to src/utils/relTime.ts. Two copies in WalletScreen + PendingCosigns; PendingCosigns was missing the d ago branch. Util uses WalletScreen's complete form.
  5. Promote useNetworkMode to context. Five call sites each instantiated their own MeshRpcAdapter (stateful pending-request map) and NetInfo subscription — only one was wired to receive rpcResponse events. Race risk under simultaneous mounts (AUDIT T23). New context/NetworkModeContext.tsx carries the logic; src/hooks/useNetworkMode.ts becomes a 4-line re-export shim so the 5 call sites don't move. Provider mounted in app/_layout.tsx: LxmfProviderNetworkModeProviderWalletProviderWalletBalanceProvider.

Closes ROADMAP §§ 0.A.8, 2.6 · ARCH P2 #1/#3/#4/#20 · AUDIT T23.

Local artifacts in worktree: PLAN.md, WORK_LOG.md.

Test plan

  • `npx tsc --noEmit` clean on every commit
  • `npm run lint` clean
  • `node ./scripts/validate-tier0-config.mjs` clean
  • Pre-existing v3 `tier0:services` brand-validator drift reproduces on `upstream/v3` HEAD; not introduced
  • Grep confirms zero consumers of dead barrel exports
  • Device: cold launch, no provider-throw red screen
  • Device: wallet balance loads, pull-to-refresh works
  • Device: send tiny devnet tx end-to-end
  • Device: airplane ON → tile state degrades, airplane OFF → recovers (single MeshRpcAdapter, no race)
  • Device: receive a DM → in-app notification fires (consolidated sliceNewEvents)
  • Device: activity rows render "5m ago" / "just now" (consolidated relTime)
  • Device: wallet bento — no dead Swap / Yield / Send-MPC panels mounted

Notes

  • Spec drift logged in WORK_LOG.md: RecentActivity is at components/home/ not components/wallet/; relTime duplication is in PendingCosigns not RecentActivity.
  • Provider order in _layout.tsx is load-bearing — NetworkModeProvider must stay inside LxmfProvider (depends on it) and outside WalletProvider (consumer).
  • Cluster 1 PR C of 4. PR A: chore: meta + foundation + CI #46. PR B + D to follow after device smoke.

These four files (WalletTabs, SendPanel, SwapPanel, YieldPanel) are roadmap-preview surfaces with no live behavior — grep confirms zero consumers outside the barrel. The barrel re-export keeps them one casual import away from showing up in the wallet bento with theatre copy (MPC 3/3, JITO_RATE, dead Deposit/Withdraw).

Removes the four exports from components/wallet/index.ts and adds a FUTURE header comment to each panel pointing back to AUDIT A6 / ROADMAP § 0.A.8. Files stay so the design isn't lost — re-export when each is wired and wrapped in <PreviewBadge>.
Connection construction lived inside sendTransaction.ts, which made the singleton's existence a side-effect of importing the send service. Three hooks already reached past it. Pulls the RPC_URL resolution + new Connection into src/infrastructure/network/connection.ts; sendTransaction.ts now imports and re-exports it so existing callers don't churn. useNetworkMode + useWalletBalance read from the new module directly. ARCH P2 #1.
The 'detect newly-prepended LXMF events given prev head + prev count' helper had drifted into three copies — LxmfContext (most defensive: handles old anchor falling off the end), MessagesScreen, useMessageNotifications. The notification + screen copies returned [] when the previous head disappeared from the (capped) buffer, silently dropping a window of events on restart.

Pulls the LxmfContext semantics into src/utils/sliceNewEvents.ts and points all three callers at it. ARCH P2 anonmesh#4.
WalletScreen + PendingCosigns each carried their own 'X ago' helper. PendingCosigns was missing the days branch so cosign requests over 24h stale would say '36h ago' instead of '1d ago'. Hoists the WalletScreen version (with the days branch) into src/utils/relTime.ts.

Spec said WalletScreen + RecentActivity, but RecentActivity actually uses Intl.DateTimeFormat — the duplication is in PendingCosigns. Logged the drift in WORK_LOG.
Five active call sites each got their own NetInfo subscription + a fresh MeshRpcAdapter — adapter has a stateful pending-request map and the rpcResponse routing useEffect ran in only one of them, so simultaneous mounts could mute responses for the others (T23).

Lifts the entire hook logic into context/NetworkModeContext.tsx with a Provider mounted in app/_layout.tsx inside LxmfProvider (depends on it) and outside WalletBalanceProvider (consumer). src/hooks/useNetworkMode.ts is now a 4-line re-export shim so existing imports stay valid.

ROADMAP § 2.6 / ARCH P2 #3.
@epicexcelsior epicexcelsior marked this pull request as ready for review May 15, 2026 08:48
@epicexcelsior epicexcelsior merged commit ca6a473 into anonmesh:v3 May 15, 2026
@epicexcelsior epicexcelsior deleted the epic/wallet-network-refactor branch May 15, 2026 09:13
epicexcelsior added a commit to epicexcelsior/anon0mesh that referenced this pull request May 16, 2026
…Actions

These three wallet panels are not exported from `components/wallet/index.ts`
today (per PR anonmesh#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.
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 to epicexcelsior/anon0mesh that referenced this pull request May 16, 2026
…ated formatter

The inline `relativeTime` helper in RecentActivity.tsx was an exact byte-equivalent
duplicate of `src/utils/relTime.ts` (added in PR anonmesh#47). Drop the local copy and
import the canonical formatter — same output, one fewer formatter surface to
drift.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
epicexcelsior added a commit that referenced this pull request May 16, 2026
…mponents (arch P2 #2) (#57)

* chore(wallet): use src/utils/relTime in RecentActivity; delete duplicated formatter

The inline `relativeTime` helper in RecentActivity.tsx was an exact byte-equivalent
duplicate of `src/utils/relTime.ts` (added in PR #47). Drop the local copy and
import the canonical formatter — same output, one fewer formatter surface to
drift.

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

* refactor(wallet): replace WalletScreen inline tiles with canonical components (arch P2 #2)

Architecture audit Rank #2 — eliminate the divergent "shadow component" copies of
BalanceTile / ActionTiles / ActivityTile in WalletScreen.tsx by mounting the
canonical components/home/{BalanceCard,ActionRow,RecentActivity} equivalents.
Net WalletScreen reduction: 458 → 154 LOC.

BalanceCard:
  - Add explicit eye-toggle + refresh icon buttons (preserves a11y from inline
    BalanceTile). Long-press hide-balance kept as accelerator. Icons positioned
    absolute top-right; rendered after the hero Pressable so taps register on
    the icons, not the outer expand-toggle.
  - Wire `refetch` from useWalletBalance.

ActionRow:
  - Consume `useNetworkMode`. When mode === "isolated", Send tile renders
    disabled with a "No route" badge — port of the same render-time gate from
    inline ActionTiles (ROADMAP § 2.4 / 02-UX P0 #6).
  - Tighten disabled-state accessibilityLabel to include the actual reason
    ("Send — no route" / "Swap — soon") rather than a blanket "coming soon".

WalletScreen:
  - Outer ScrollView + RefreshControl now wraps the whole content column so
    pull-to-refresh works anywhere on the page (previously only on the inner
    activity scroll).
  - Delete inline BalanceTile (62 LOC), ActionTiles (47 LOC), ActivityTile
    (113 LOC), the local `relTime`/`fmtSol`/`fmtAmount`/`TOKEN_COLOR` helpers,
    and ~60 LOC of dead tile styles.
  - Add accessibilityRole/Label to the receive-QR header button.

Visual fidelity:
  - BalanceCard uses a collapsible token-list (tap chevron) instead of the
    always-visible horizontal SPL strip from the inline tile. This is a UX
    upgrade (less clutter at rest) and matches the canonical design system.
  - Action tiles now use circular icon wells (PressSurface "card" variant)
    instead of the previous rectangular tiles. Same four actions, same routes,
    same isolated-mode gating, slightly more breathing room.
  - RecentActivity rows now show a status Pill (Settled/Failed) and explicit
    "Sent to <addr>" / "Received from <addr>" labels — a strict superset of
    the inline activity row info.
  - Receive modal, header chips, pending-cosigns preview, refresh haptic are
    all preserved verbatim.

Out of scope (pending separate PRs): receive Modal → AppBottomSheet migration,
multisig fixture removal.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
…mponents (arch P2 #2) (#57)

* chore(wallet): use src/utils/relTime in RecentActivity; delete duplicated formatter

The inline `relativeTime` helper in RecentActivity.tsx was an exact byte-equivalent
duplicate of `src/utils/relTime.ts` (added in PR #47). Drop the local copy and
import the canonical formatter — same output, one fewer formatter surface to
drift.


* refactor(wallet): replace WalletScreen inline tiles with canonical components (arch P2 #2)

Architecture audit Rank #2 — eliminate the divergent "shadow component" copies of
BalanceTile / ActionTiles / ActivityTile in WalletScreen.tsx by mounting the
canonical components/home/{BalanceCard,ActionRow,RecentActivity} equivalents.
Net WalletScreen reduction: 458 → 154 LOC.

BalanceCard:
  - Add explicit eye-toggle + refresh icon buttons (preserves a11y from inline
    BalanceTile). Long-press hide-balance kept as accelerator. Icons positioned
    absolute top-right; rendered after the hero Pressable so taps register on
    the icons, not the outer expand-toggle.
  - Wire `refetch` from useWalletBalance.

ActionRow:
  - Consume `useNetworkMode`. When mode === "isolated", Send tile renders
    disabled with a "No route" badge — port of the same render-time gate from
    inline ActionTiles (ROADMAP § 2.4 / 02-UX P0 #6).
  - Tighten disabled-state accessibilityLabel to include the actual reason
    ("Send — no route" / "Swap — soon") rather than a blanket "coming soon".

WalletScreen:
  - Outer ScrollView + RefreshControl now wraps the whole content column so
    pull-to-refresh works anywhere on the page (previously only on the inner
    activity scroll).
  - Delete inline BalanceTile (62 LOC), ActionTiles (47 LOC), ActivityTile
    (113 LOC), the local `relTime`/`fmtSol`/`fmtAmount`/`TOKEN_COLOR` helpers,
    and ~60 LOC of dead tile styles.
  - Add accessibilityRole/Label to the receive-QR header button.

Visual fidelity:
  - BalanceCard uses a collapsible token-list (tap chevron) instead of the
    always-visible horizontal SPL strip from the inline tile. This is a UX
    upgrade (less clutter at rest) and matches the canonical design system.
  - Action tiles now use circular icon wells (PressSurface "card" variant)
    instead of the previous rectangular tiles. Same four actions, same routes,
    same isolated-mode gating, slightly more breathing room.
  - RecentActivity rows now show a status Pill (Settled/Failed) and explicit
    "Sent to <addr>" / "Received from <addr>" labels — a strict superset of
    the inline activity row info.
  - Receive modal, header chips, pending-cosigns preview, refresh haptic are
    all preserved verbatim.

Out of scope (pending separate PRs): receive Modal → AppBottomSheet migration,
multisig fixture removal.


---------
Magicred-1 pushed a commit that referenced this pull request May 16, 2026
* refactor(wallet): move dead Send/Swap/Yield panels out of barrel

These four files (WalletTabs, SendPanel, SwapPanel, YieldPanel) are roadmap-preview surfaces with no live behavior — grep confirms zero consumers outside the barrel. The barrel re-export keeps them one casual import away from showing up in the wallet bento with theatre copy (MPC 3/3, JITO_RATE, dead Deposit/Withdraw).

Removes the four exports from components/wallet/index.ts and adds a FUTURE header comment to each panel pointing back to AUDIT A6 / ROADMAP § 0.A.8. Files stay so the design isn't lost — re-export when each is wired and wrapped in <PreviewBadge>.

* refactor(network): extract solanaConnection singleton

Connection construction lived inside sendTransaction.ts, which made the singleton's existence a side-effect of importing the send service. Three hooks already reached past it. Pulls the RPC_URL resolution + new Connection into src/infrastructure/network/connection.ts; sendTransaction.ts now imports and re-exports it so existing callers don't churn. useNetworkMode + useWalletBalance read from the new module directly. ARCH P2 #1.

* refactor: consolidate sliceNewEvents to single util

The 'detect newly-prepended LXMF events given prev head + prev count' helper had drifted into three copies — LxmfContext (most defensive: handles old anchor falling off the end), MessagesScreen, useMessageNotifications. The notification + screen copies returned [] when the previous head disappeared from the (capped) buffer, silently dropping a window of events on restart.

Pulls the LxmfContext semantics into src/utils/sliceNewEvents.ts and points all three callers at it. ARCH P2 #4.

* refactor: consolidate relTime formatter

WalletScreen + PendingCosigns each carried their own 'X ago' helper. PendingCosigns was missing the days branch so cosign requests over 24h stale would say '36h ago' instead of '1d ago'. Hoists the WalletScreen version (with the days branch) into src/utils/relTime.ts.

Spec said WalletScreen + RecentActivity, but RecentActivity actually uses Intl.DateTimeFormat — the duplication is in PendingCosigns. Logged the drift in WORK_LOG.

* refactor(network): promote useNetworkMode to context

Five active call sites each got their own NetInfo subscription + a fresh MeshRpcAdapter — adapter has a stateful pending-request map and the rpcResponse routing useEffect ran in only one of them, so simultaneous mounts could mute responses for the others (T23).

Lifts the entire hook logic into context/NetworkModeContext.tsx with a Provider mounted in app/_layout.tsx inside LxmfProvider (depends on it) and outside WalletBalanceProvider (consumer). src/hooks/useNetworkMode.ts is now a 4-line re-export shim so existing imports stay valid.

ROADMAP § 2.6 / ARCH P2 #3.
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