Skip to content

fix: honest mesh + wallet state#48

Merged
epicexcelsior merged 7 commits into
anonmesh:v3from
epicexcelsior:epic/honest-state-batch
May 15, 2026
Merged

fix: honest mesh + wallet state#48
epicexcelsior merged 7 commits into
anonmesh:v3from
epicexcelsior:epic/honest-state-batch

Conversation

@epicexcelsior
Copy link
Copy Markdown
Collaborator

@epicexcelsior epicexcelsior commented May 15, 2026

Five honesty fixes — every present-tense claim in the UI now reflects actual state.

  1. fix(messages): enc:false on insert, flip to true only on messageDeliveredscreens/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. resolveSeq extended: when state === 'delivered', reverse-lookup msgId via idToSeqRef and flip enc on the matching ChatMsg.

  2. fix(beacon): drop silent auto-activation on first internetcomponents/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.

  3. fix(nodes): replace 7-fake-peer fallback with honest empty statescreens/NodesScreen.tsx + components/nodes/constants.ts. When LXMF wasn't running, the radar filled from a hardcoded 7-item fixture (NODES export), painting @beacon_prime · 12ms etc. on a totally cold device. Fixture deleted; empty overlay reads "Starting mesh…" (native available) or "Mesh unavailable on this device".

  4. fix(settings): drop hardcoded 78% BATT pillscreens/SettingsScreen.tsx. PairedDevice has no battery field; pill rendered "78% BATT" on every paired card regardless. Removed. Comment in place for when the pairing API exposes real battery.

  5. fix(send): disable Send tile in isolated mode at render-timescreens/WalletScreen.tsx. Send tile fired router.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 in ReviewCard:216-223 kept as defense-in-depth for mid-flow mode changes.

Test plan

  • `npx tsc --noEmit` clean
  • `npm run lint` clean
  • `node ./scripts/validate-tier0-config.mjs` clean
  • Pre-existing v3 `tier0:services` brand-validator drift; not introduced
  • AI-fingerprint scan clean
  • Device: DM to known peer on Wi-Fi → bubble no lock at first → lock appears ~2s after delivery
  • Device: force-fail send (airplane mid-send) → bubble stays no-lock (no false enc on failed)
  • Device: cold launch w/ no internet + no LXMF native → Nodes shows "MESH UNAVAILABLE ON THIS DEVICE", no fake peers
  • Device: cold launch, native running, no peers yet → "STARTING MESH…"
  • Device: fresh launch w/ Wi-Fi → BeaconRegistry toggle OFF (was auto-flipping)
  • Device: tap Register-as-Beacon → toggle flips ON
  • Device: pair RNode → Settings paired card shows CONNECTED + LoRa pills only, no 78% BATT
  • Device: airplane + no mesh peers → Wallet Send tile dimmed + NO ROUTE pill, tap = no-op
  • Device: online mode → Send tile fully enabled

Notes

  • Some existing issues to group chat DMs. Fixes still relevant but need to test messaging transports separately.

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.
@epicexcelsior epicexcelsior marked this pull request as ready for review May 15, 2026 08:48
@epicexcelsior epicexcelsior requested a review from Copilot May 15, 2026 08:58
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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, so isolated is 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;
Comment thread mobile_app/screens/MessagesScreen.tsx Outdated
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';
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.
@epicexcelsior epicexcelsior merged commit f6ba506 into anonmesh:v3 May 15, 2026
1 check passed
@epicexcelsior epicexcelsior deleted the epic/honest-state-batch branch May 15, 2026 09:12
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.
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