fix: honest mesh + wallet state#48
Merged
epicexcelsior merged 7 commits intoMay 15, 2026
Merged
Conversation
Lock icon (enc:true) was set the moment a send was queued, so failed sends and still-in-flight sends both rendered as encrypted on the wire — a present-tense factual claim that isn't true. AUDIT T9 / ROADMAP § 0.3. Sets enc:false on insert; resolveSeq flips it to true only when the native module emits messageDelivered for the corresponding seq. Reverse-lookup msgId via idToSeqRef so the existing seq→msg map stays load-bearing.
…ternet BeaconRegistry flipped the user into beacon mode silently the first time the device saw internet — beacon mode relays others' traffic and co-signs Solana txs, so opting users in without a tap is a consent violation per AUDIT T10 / ROADMAP § 0.B.3. Drops the auto-activate useEffect. The Register-as-Beacon control (via setBeaconMode) stays — it's now the only way to enter beacon mode. autoActivatedRef + useEffect import follow it out.
When LXMF wasn't running yet, NodesScreen filled the radar from a 7-item fixture (constants NODES) — same renderer as live peers, so users saw '@beacon_prime · 12ms' on a totally cold device. That's a lie about current mesh state, not a roadmap signal. AUDIT T7 / ROADMAP § 0.4. Removes the NODES fixture and the fallback. The radar now shows real peers only; when empty, an overlay reads 'Starting mesh…' (native module available) or 'Mesh unavailable on this device' (no native).
PairedDevice doesn't carry battery telemetry — the pill rendered '78% BATT' on every paired-device card regardless of actual battery state. Removed for now; when the pairing API exposes a real battery field, swap this back to a live readout. AUDIT T11 / ROADMAP § 0.B.5.
Send tile fired `router.push('/send/recipient')` regardless of network state — user picked a recipient, typed an amount, slid to confirm, and only at the confirm step got "no Solana RPC route is available" (ReviewCard.tsx:216-223). Now disables the tile up front when mode === 'isolated' and overlays a NO ROUTE pill so the dead-end is visible from wallet home.
Keeps the confirm-time bail as defense-in-depth for the mid-flow mode-change race. ROADMAP § 2.4 / 02-UX P0 anonmesh#6.
17 tasks
Contributor
There was a problem hiding this comment.
Pull request overview
This PR makes several UI state claims reflect live app/network state instead of optimistic or hardcoded values.
Changes:
- Delays outbound message encryption indicators until delivery confirmation.
- Removes fake mesh peers, beacon auto-activation, and hardcoded hardware battery text.
- Disables the Wallet Send tile when no RPC route is available.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
mobile_app/screens/WalletScreen.tsx |
Adds isolated-mode Send tile disabling and “NO ROUTE” badge. |
mobile_app/screens/SettingsScreen.tsx |
Removes hardcoded paired-device battery pill. |
mobile_app/screens/NodesScreen.tsx |
Uses real peer data only and shows mesh empty-state copy. |
mobile_app/screens/MessagesScreen.tsx |
Initializes sent text bubbles as unlocked and flips lock on delivery events. |
mobile_app/components/nodes/constants.ts |
Removes the fake NODES fixture export. |
mobile_app/components/nodes/BeaconRegistry.tsx |
Removes automatic beacon activation on internet availability. |
Comments suppressed due to low confidence (1)
mobile_app/screens/WalletScreen.tsx:138
useNetworkMode()initializes its internet state as online and only corrects it after the async NetInfo event/fetch, soisolatedis false on the first render even for an offline/no-relay cold start. That leaves the Send tile briefly enabled and tappable, which undermines the render-time gate this change is adding; treat the initial network state as unresolved/offline for this affordance or gate on a resolved mode from the parent.
const { mode } = useNetworkMode();
const isolated = mode === 'isolated';
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+48
to
+49
| void hasInternet; | ||
| void setBeaconMode; |
| if (msgIdForSeq !== null) { | ||
| setMsgs(prev => prev.map(m => | ||
| m.id === msgIdForSeq && 'enc' in m ? { ...m, enc: true } : m, | ||
| )); |
Comment on lines
131
to
+138
| function ActionTiles() { | ||
| const { colors } = useTheme(); | ||
| const router = useRouter(); | ||
| // Gate the Send tile at render-time so isolated-mode users can't walk three | ||
| // screens deep before the confirm-step error tells them no RPC route exists. | ||
| // ROADMAP § 2.4 / 02-UX P0 #6. | ||
| const { mode } = useNetworkMode(); | ||
| const isolated = mode === 'isolated'; |
Collaborator
Author
There was a problem hiding this comment.
Real concern at this branch's diff base (upstream/v3 c6738d6 — pre-context useNetworkMode). After PR #47 merged to v3, useNetworkMode is now a useContext shim — the second call here is just a context lookup, not a fresh NetInfo subscription or MeshRpcAdapter instance. Resolved by the merge order A → C → B → D.
Without the auto-activate useEffect (removed in 78a801d), there was no remaining path to flip beacon mode on — the modal's CTA was pointerEvents:none + 'Preview — not yet active'. Replaces the disabled preview with a real Pressable that calls setBeaconMode(true) + dismisses the modal. Stake + biometric co-sign flow remain future work; the JS-side opt-in is the consent gate today.
resolveSeq only updated the currently-rendered msgs array, so if the user navigated away from a thread before native emitted messageDelivered, the sent bubble in threadsRef stayed at enc:false. Reopening the conversation showed no lock even though delivery had been confirmed. Now also walks threadsRef and rewrites any cached thread containing the seq's msgId.
Magicred-1
pushed a commit
that referenced
this pull request
May 16, 2026
* fix(messages): enc:false on insert; flip to true on messageDelivered
Lock icon (enc:true) was set the moment a send was queued, so failed sends and still-in-flight sends both rendered as encrypted on the wire — a present-tense factual claim that isn't true. AUDIT T9 / ROADMAP § 0.3.
Sets enc:false on insert; resolveSeq flips it to true only when the native module emits messageDelivered for the corresponding seq. Reverse-lookup msgId via idToSeqRef so the existing seq→msg map stays load-bearing.
* fix(beacon): require explicit opt-in instead of auto-activating on internet
BeaconRegistry flipped the user into beacon mode silently the first time the device saw internet — beacon mode relays others' traffic and co-signs Solana txs, so opting users in without a tap is a consent violation per AUDIT T10 / ROADMAP § 0.B.3.
Drops the auto-activate useEffect. The Register-as-Beacon control (via setBeaconMode) stays — it's now the only way to enter beacon mode. autoActivatedRef + useEffect import follow it out.
* fix(nodes): drop 7-fake-peer fallback for honest empty state
When LXMF wasn't running yet, NodesScreen filled the radar from a 7-item fixture (constants NODES) — same renderer as live peers, so users saw '@beacon_prime · 12ms' on a totally cold device. That's a lie about current mesh state, not a roadmap signal. AUDIT T7 / ROADMAP § 0.4.
Removes the NODES fixture and the fallback. The radar now shows real peers only; when empty, an overlay reads 'Starting mesh…' (native module available) or 'Mesh unavailable on this device' (no native).
* fix(settings): drop hardcoded 78% BATT pill from paired-hardware card
PairedDevice doesn't carry battery telemetry — the pill rendered '78% BATT' on every paired-device card regardless of actual battery state. Removed for now; when the pairing API exposes a real battery field, swap this back to a live readout. AUDIT T11 / ROADMAP § 0.B.5.
* fix(send): disable Send tile in isolated mode at render-time
Send tile fired `router.push('/send/recipient')` regardless of network state — user picked a recipient, typed an amount, slid to confirm, and only at the confirm step got "no Solana RPC route is available" (ReviewCard.tsx:216-223). Now disables the tile up front when mode === 'isolated' and overlays a NO ROUTE pill so the dead-end is visible from wallet home.
Keeps the confirm-time bail as defense-in-depth for the mid-flow mode-change race. ROADMAP § 2.4 / 02-UX P0 #6.
* fix(beacon): wire Register-as-Beacon CTA to setBeaconMode
Without the auto-activate useEffect (removed in 78a801d), there was no remaining path to flip beacon mode on — the modal's CTA was pointerEvents:none + 'Preview — not yet active'. Replaces the disabled preview with a real Pressable that calls setBeaconMode(true) + dismisses the modal. Stake + biometric co-sign flow remain future work; the JS-side opt-in is the consent gate today.
* fix(messages): also flip enc on cached non-active threads
resolveSeq only updated the currently-rendered msgs array, so if the user navigated away from a thread before native emitted messageDelivered, the sent bubble in threadsRef stayed at enc:false. Reopening the conversation showed no lock even though delivery had been confirmed. Now also walks threadsRef and rewrites any cached thread containing the seq's msgId.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Five honesty fixes — every present-tense claim in the UI now reflects actual state.
fix(messages):enc:falseon insert, flip totrueonly onmessageDelivered—screens/MessagesScreen.tsx. Lock icon was set the moment a send was queued, so failed sends + still-in-flight sends both painted as encrypted on the wire.resolveSeqextended: whenstate === 'delivered', reverse-lookup msgId viaidToSeqRefand flip enc on the matchingChatMsg.fix(beacon): drop silent auto-activation on first internet —components/nodes/BeaconRegistry.tsx. Beacon mode (relays others' traffic, co-signs Solana txs) was being flipped on without consent the first time the device saw internet. Removed the useEffect; user must tap Register-as-Beacon.fix(nodes): replace 7-fake-peer fallback with honest empty state —screens/NodesScreen.tsx+components/nodes/constants.ts. When LXMF wasn't running, the radar filled from a hardcoded 7-item fixture (NODESexport), painting@beacon_prime · 12msetc. on a totally cold device. Fixture deleted; empty overlay reads "Starting mesh…" (native available) or "Mesh unavailable on this device".fix(settings): drop hardcoded78% BATTpill —screens/SettingsScreen.tsx.PairedDevicehas no battery field; pill rendered "78% BATT" on every paired card regardless. Removed. Comment in place for when the pairing API exposes real battery.fix(send): disable Send tile in isolated mode at render-time —screens/WalletScreen.tsx. Send tile firedrouter.push('/send/recipient')regardless of network state; user walked 3 screens then hit "no Solana RPC route available" at confirm. Now dims the tile + overlays a NO ROUTE pill up front. Confirm-time bail inReviewCard:216-223kept as defense-in-depth for mid-flow mode changes.Test plan
Notes