Commit 46406f8
Fix/lxmf v0.2.73 integration (#98)
* build(babel): babel.config.js + explicit class-static-block plugin (#86)
Adds babel.config.js enabling @babel/plugin-transform-class-static-block (required by three.js in the mesh-map renderer) and declares the plugin as a direct devDependency so resolution is guaranteed on fresh installs. Fixes the silent iOS bundling failure (Metro HTTP 500) that left dev-clients running stale embedded JS.
* fix(qr-scan): gate CameraView on permission + surface unrecognized QR
Two related iOS bugs observed on a personal-team dev build:
1. CameraView mounted before permission resolved → black preview that
never recovers until app restart. On iOS the component caches its
permission state at mount; flipping null→granted at runtime doesn't
re-init the preview. Gate the mount on `permission.granted === true`
so the conditional flip causes a fresh mount with permission in place.
2. PeersDrawer's QR onResult silently dropped any QR that wasn't a
plain LXMF hash — group QRs, Solana addresses, malformed/empty
payloads all closed the modal with no feedback, leaving users
thinking "scanner is broken." Surface explicit alerts:
- lxmf-group → "looks like a channel QR, use Join channel"
- solana → "wallet address, use Send screen"
- unknown → show first 64 chars of raw payload so user can debug
Also adds a __DEV__ console.log of the parsed result so we can adb
logcat-trace exactly what came out of the scanner.
* build(deps): add expo-audio as expo-camera 17 peer dep
expo-camera@17 split audio recording into a separate package (expo-audio).
iOS native side hard-links the ExpoAudio module even when the JS-level use
is barcode-scanning only, so the dev-client build fails with "Cannot find
native module expoAudio" without the peer dep installed.
`npx expo install expo-audio` added 1.1.1 to deps and registered the
config plugin in app.json. No source code changes — this is pure native
bridge availability.
* refactor(scanner): present QR scanner as a route, not a nested modal
QRScannerModal was a free-floating <Modal>. Rendered inside the join-channel sheet (itself a <Modal>) it stacked two native iOS windows — black camera preview, and corrupted touch handling on dismiss (frozen screen, camera never released). A single modal (Send, peers drawer) worked; only nesting broke.
- app/scan.tsx route + scan() promise API (src/services/qrScan), usable identically from any screen or modal; one camera/permission lifecycle; re-entrancy-guarded so overlapping calls can't stack routes.
- Convert the join-channel sheet to app/join-channel.tsx (transparentModal owning its slide, like receive) so the scanner composes over it natively — no nested-modal conflict.
- Update RecipientPicker, PeersDrawer, and the join flow to await scan(). Delete QRScannerModal and JoinGroupModal.
* test(tier0): align solanaPayUri label/message expectations with shipped lowercase default
* fix(lxmf): surface silent identity-persist failures and fix peer-history undershoot
L3: a secureSet rejection when persisting the LXMF identity was swallowed in a
console.warn. A failed write can spawn a fresh identity on next cold start —
losing the mesh address and message continuity — with no user-visible signal.
Route it to the existing LxmfErrorBanner via a new identityError state, cleared
on a later successful persist.
MSG-2: getPeerMessages fetched a fixed most-recent window (limit*2) then filtered
to the peer, silently returning too few messages for a peer whose history sits
deeper than that window. Grow the window until limit matches are found or a short
page proves the store is drained.
* feat(dev): guard app/dev/* routes behind __DEV__
Expo Router auto-registers every file in app/ as a deep-linkable route, so a
production binary could open anonmesh://dev/* developer screens. Add a dev-group
layout that redirects to the app shell unless __DEV__, keeping dev tools out of
release builds.
* fix(lxmf): gate dev local-PC hub behind __DEV__, not just an env var
MY_PC (a developer's local Reticulum hub) was derived solely from
EXPO_PUBLIC_LOCAL_LXMF_HOST and unshifted into the node's interface list in every
build. EXPO_PUBLIC_* values inline into the production bundle when present in the
build env, so a leaked var could wire a dev machine as a hub in a release binary.
Gate on __DEV__ per project conventions (use __DEV__, not EXPO_PUBLIC_*, for dev-only code).
* feat(dev): tutorial reset + dev menu for rehearsing first-run flows
Adds resetTutorial() (clears the tutorial-completed flag) and an app/dev index
screen that lists dev screens and exposes reset + replay actions, so onboarding
and the tutorial can be rehearsed repeatedly without reinstalling. Lives under
the __DEV__-guarded dev route group, so it never ships to production.
* fix(scripts): use a portable shebang for lxmf_send.py
The shebang hardcoded a teammate's pipx venv path (/home/m4gicred1/...), so the
LXMF send helper only ran on one machine. Use /usr/bin/env python3.
* fix(beacon): label beacon economic stats as a preview, not live earnings
The active BeaconRegistry card presented hardcoded figures — 0.000312 SOL
'earned', 24 co-signs, and derived rep/jitoSOL/yield — as if real, but the
co-sign and staking economy is future work (see the in-file note, and the stake
modal already reads 'not yet active'). Add a PREVIEW pill and a disclaimer so the
illustrative figures no longer masquerade as real earnings; the reachable-peer
count stays real. Exact copy/placement pending on-device visual confirmation.
* test(lxmf): extract + unit-test the peer-history window algorithm (MSG-2)
Pulls the adaptive-window logic out of getPeerMessages into a pure
collectPeerMessages() and adds tier0 assertions — including the deep-peer case
the old fixed limit*2 window silently dropped (old: 0 found, new: all 5).
getPeerMessages now delegates to the tested helper.
* fix(lxmf): stop getGroupMembers triggering setState during render
getGroupMembers runs inside a MessagesScreen useMemo (during render) and called
lxmf.getStatus(), which refreshes LxmfProvider state -> 'Cannot update a component
while rendering a different component' warning. Read the cached lxmf.status
instead (identical for our own addressHex). Pre-existing bug, surfaced by on-device
testing of the messages thread.
* perf(lxmf): single bounded fetch in getPeerMessages (revert escalating loop)
The adaptive-window loop re-fetched and re-scanned the whole message store on
every conversation open (400->800->1600->... until drained), making threads take
seconds to open on a busy mesh. Revert to a single most-recent-window fetch
(limit*2). The deep-peer undershoot it tried to address needs a native per-peer
query (catalogued), not a JS loop. Test updated to the bounded contract.
* refactor(ui): drop dead Expo-template theme island, add shared state primitives
Remove the unused starter-template stack (themed-text/view, parallax scroll
view, hello-wave, external-link, collapsible, icon-symbol, constants/theme,
use-theme-color, use-color-scheme) — it was a second, competing color/font
source with zero real importers.
Add EmptyState / ErrorState / LoadingState to components/ui as the canonical,
token-driven blocks for the app's empty / error / loading moments.
* refactor(onboarding): route hardcoded styling through design tokens
Tokenize colors, type sizes, radii and exact-match spacing on the onboarding
and tutorial surface. Brand/shader/gradient colors and bespoke loading
choreography left intact. No behavior change.
* refactor(messages): tokenize styling + use shared EmptyState
Route hex/type/radii/spacing through tokens across the messages tab and
component set; replace the group-members empty block with the shared
EmptyState primitive (copy/behavior preserved). Chat-bubble accent colors and
camera/lightbox chrome kept intentional. No behavior change.
* refactor(wallet): tokenize wallet + home-dashboard styling
Route colors/type/radii/spacing through design tokens across the wallet tab,
panels, tx detail, and home cards. Per-asset brand colors (SOL/USDC/etc.),
tx-direction red/green, and on-primary button ink kept intentional. Wallet,
balance, and transaction logic untouched.
* refactor(nodes): tokenize peers/mesh styling
Tokenize hex/type/radii/spacing on the Peers tab. Mesh-map visualization
palette (per-handle node colors, signal-link tints) and sub-token canvas
labels kept intentional. Cosign/transaction logic untouched.
* refactor(settings): tokenize settings + contacts/receive styling
Route colors/type/radii/spacing through tokens across settings, the security
modals, contacts and receive; fullscreen modal scrims now use colors.overlay;
contacts empty block uses the shared EmptyState primitive (copy preserved).
QR-rendering colors kept literal for scan contrast. Auth/biometric/key logic
untouched.
* refactor(send): tokenize send-flow styling
Tokenize hex/type/radii/spacing across the send recipient/amount/review/result
components. Oversized amount-display numerals and derived error-alpha borders
kept intentional. Transaction-build, validation and navigation logic untouched.
* refactor(ui): tokenize shared chrome + primitive nits
Tab bar, root error banner, and a few shared primitives: swap only
already-token-equal values (zero visual change) plus normal tokenization of
the non-reused chrome. Off-scale primitive dimensions and #000 shadows left
deliberately to avoid app-wide ripple.
* feat(observability): Sentry crash-only telemetry (ErrorBoundary, PII scrubber, DSN kill-switch)
src/observability/{sentry,scrub,ErrorBoundary} + scrub tests; ErrorBoundary wired in _layout; metro/app.json/deps for Sentry. Crash-only (no product analytics), DSN gated via env kill-switch, PII scrubbed.
* feat(crash-safety): add ErrorBoundary + global async error handler
Any render/lifecycle throw in the provider tree now shows a recovery
screen ("Something went wrong / Try again") instead of white-screening
the app. Hard-coded dark colors so the fallback renders even if
ThemeProvider itself threw. Global ErrorUtils handler logs unhandled
async throws without swallowing them; preserves the previous handler in
__DEV__ so the dev overlay still works.
* fix(wallet): remove hardcoded pending co-sign array
The three placeholder PendingCosign entries (0.25 / 1.05 / 0.005 SOL)
were rendered as if they were live requests. Replaced with an empty
array so the component falls through to its existing honest empty state
("Multisig co-signs not yet live") until the real data source is wired.
* fix(beacon): zero fake stats, fix PREVIEW pill, disable stake chip (QA-55)
- cosigns/earned: drop useState(24)/useState(0.000312); both are 0 until
the co-sign feed is live. Shown as '—' under the PREVIEW label.
- jitoAmt/yieldAmt/repScore: removed JITO_RATE, JITO_APY, STAKE_NUM
constants; all three now display '—' (preview placeholder).
- PREVIEW pill: was only shown when beacon is active; now always visible
so inactive-state users know it's a preview feature too.
- Stake chip: was a Pressable opening a staking modal; disabled to a
greyed-out "Soon" view since no real stake transaction exists.
- "Register as Beacon" description: removed false "Stake 0.5 SOL —
fully returned on exit" claim; fee list now lists relay (live) vs
staking/earnings (coming soon) honestly.
- QA-55: reachableCount no longer double-counts beacon-peers (beacons
are already merged into peers by LxmfContext.mergeBeacon).
* fix(ux): network-aware explorer URL, drop devnet copy in contacts
contacts.tsx:216 — "trusted devnet address" → "recipient address":
network-neutral, doesn't hint at test-network framing to users.
explorer.ts — renamed buildDevnetExplorerTxUrl → buildExplorerTxUrl;
function now reads EXPO_PUBLIC_SOLANA_RPC to derive the cluster param
so explorer links are correct when pointed at mainnet/testnet. Old
name kept as a deprecated re-export so callers compile without change.
* fix(mesh): throw typed error on malformed beacon RPC response
A truncated or non-JSON beacon relay body made JSON.parse in
MeshRpcAdapter.rpc throw a raw SyntaxError that bubbled up as an app
crash. Wrap the parse and throw a typed MeshResponseParseError so
confirmation polling and balance fetches treat it as a normal transport
failure and recover instead of hard-crashing the send flow.
* fix(send): bound every direct-RPC await with withTimeout
The fee-estimate path already wrapped its RPC calls in withTimeout, but
the signing, submit, confirmation-poll, and balance/activity reads did
not. On a degraded RPC that accepts the socket but never answers, those
bare awaits parked forever: the confirm loop could not re-check its
deadline mid-await and the user got a permanent spinner with no recovery.
Wrap all of them (sendRawTransaction, getLatestBlockhash on the submit
path, getSignatureStatus in the poll, getParsedTokenAccountsByOwner,
getSignaturesForAddress, getParsedTransactions) in withTimeout at the
existing DIRECT_RPC_TIMEOUT_MS. Each now rejects on a hang so callers
surface an error (or, in the poll, retry next cycle) instead of freezing.
Exported DIRECT_RPC_TIMEOUT_MS so walletData shares the one constant.
* fix(wallet): drop stale balance fetch on adapter swap; bound getBalance
A mesh<->online adapter switch (or wallet change) re-runs refetch while a
previous Promise.allSettled is still in flight. The slower stale fetch
could resolve last and overwrite fresh balances/activity with results
from the now-dead transport. Stamp a monotonic fetch id at the start of
each refetch and only let the latest one commit to state (including the
loading flags), so a superseded fetch is discarded.
Also wrap the one remaining bare RPC call here (getBalance) in withTimeout
so a degraded RPC cannot park the whole refresh; matches the bound now
applied to every other RPC site.
* fix(wallet): never delete a wallet on a transient keychain read error (QA-20)
secureGet() swallows all SecureStore errors and returns null, so a real
keychain read failure (transient lock, access-group mismatch after a
signing-cert change) was indistinguishable from "key never stored".
isFullyIntact() collapsed that to a bare false, and hasLocalWallet() /
createLocal() then DELETED the wallet — wiping a real, funded wallet over
a momentary glitch.
Add secureGetStrict() which throws on a read failure and returns null only
for verified absence. isFullyIntact() now reads strictly and returns a
discriminated { intact | absent | error } result. hasLocalWallet() and
createLocal() only delete/recreate on a verified 'absent'; an 'error' is
treated as "wallet present, integrity unknown" and surfaced to the unlock
path so the user can retry instead of losing keys. readAndDecrypt() reuses
secureGetStrict() in place of its duplicated inline try/catch.
* fix(send): repair review failure-recovery, cancel, and address save
Four coupled fixes in the confirm flow:
- Cancel during the confirm poll now aborts the in-flight controller.
Previously it only reset local UI state, so the poll kept running and a
late status read could still navigate to Success after the user backed
out. Also guards the confirmed branch against the abort-vs-resolve race.
- The failed branch now resets isConfirming/txPhase/slider before pushing
the failure screen. FailureCard's Try again does router.back() onto this
still-mounted Review; without the reset it landed on a frozen loader with
no slider, making retry unreachable.
- The cancelled branch resets state too (was a no-op), so an aborted poll
leaves a clean Review instead of a stuck loader.
- saveAddressBookRecipient moved into the confirmed branch only. Saving on
submit let a failed tx or poisoned address leak into the address book and
resurface as a suggestion.
- Loader sublabel is network-aware: only claims 'devnet' in online mode
(the hard-wired devnet RPC); neutral 'Confirming' over mesh, whose
relay cluster the screen cannot assert.
* fix(wallet): zero transient seed/AES buffers after wallet create (QA-54)
create() left the raw 32-byte seed and AES key sitting in the JS heap after
they were persisted. Both are fully consumed by then (seed by
Keypair.fromSeed, AES key by the encrypt + base64 persist) and never read
again, so wipe them with fill(0) to shrink the window where raw key material
is recoverable from memory. keypair.secretKey is left intact — the returned
wallet still needs it to sign.
* fix(messages): harden inbound req-pay/req-addr against malformed peer payloads
A peer fully controls the JSON body of req-pay/req-addr/share-addr. The
request bubbles render note/asset/amount straight as React children, so a
non-string value (e.g. an object) crashes the renderer and white-screens
the app (no error boundary above MessagesScreen).
Type-guard at the parse boundary: validate asset against a known ticker
allow-list, coerce note to a clamped string (drop non-strings), require a
finite non-negative amount, and require a bounded string address. Mirror
the guard inside RequestMoneyBubble/RequestAddressBubble as a render-time
safety net so no future caller can reach <Text> with an unsafe value.
* fix(wallet): wipe exported private key from clipboard (QA-23)
Copying the recovery key left the raw base58 private key on the system
clipboard indefinitely; the 1.4s timer only reset the "copied" badge. The
clipboard persists and on some OSes syncs cross-device, so the key leaked
well past the export flow.
Wipe it on three edges, mirroring the channel-key auto-clear already used in
ChannelShareSheet/CreateGroupModal: a 60s auto-clear timer reset on each copy,
an immediate wipe on dismiss, and a best-effort wipe on unmount. The pending
timer is cleared on unmount so no setState/clipboard write fires after teardown.
The copy UX is unchanged.
* fix(identity): always authenticate before keypair rotation (QA-19)
confirmRotate() wrapped the re-auth in `if (biometricEnabled)`, so with the
biometric toggle off the IRREVERSIBLE rotation (wipes the ed25519 keypair +
LXMF address) ran with zero authentication from any unlocked session.
Authentication is now always required. When biometrics are off or unenrolled
we force the OS passcode prompt (disableDeviceFallback: false), matching how
onboarding's wallet-create authenticates; the toggle now only chooses
biometric-only vs passcode-allowed, never whether to auth at all. A failed or
cancelled auth aborts the rotation.
* fix(messages): tell the truth in the no-peers empty state
NoPeersScreen showed an animated 'SCANNING FOR PEERS / will appear
automatically' sonar regardless of actual state — even offline or with
Bluetooth denied, when nothing is being scanned.
Branch on the real state (isRunning && bleActive): genuinely scanning
keeps the sonar plus a reassuring 'no one nearby yet'; offline/BLE-off
gets a static icon, a 'You're offline' heading, and an actionable
'Enable Bluetooth to find people nearby' instead of a false promise.
Park and hide the rings entirely when not scanning so they don't read
as a passive radar.
* fix(messages): stop silently dropping pay-request / share-address actions
The action-grid send was fire-and-forget: no active-peer check, no
node-up guard, the send() promise unawaited, and no delivery state — so
tapping 'request 2 SOL' with the node down showed the bubble as if it
had sent.
Mirror sendMsg: guard on a selected peer and a running node (surfacing a
system line otherwise), await the send, and track the returned seq so a
queued or failed request shows up in the queue banner like any message.
* fix(messages): validate deep-link and notification dest hashes as hex
paramDestHash (deep link) and the pending-conversation hash (notification
tap) were routed on without shape-checking. A malformed value is a
mis-route / notification-suppression vector — it can falsely equal, or
never equal, the active peer's hash and either open a junk thread or
swallow a real alert.
Require a well-formed 16-byte LXMF hash (32 lowercase hex chars, the
native canonical form) before routing. Consume the pending hash even when
invalid so junk can't linger and re-fire on the next focus.
* fix(crash-safety): always chain to previous global error handler
Gating the previous-handler call on __DEV__ swallowed fatal errors in a
release build — React Native's native fatal path (crash dialog + process
teardown) never ran, leaving the app limping with no crash signal. Always
chain to the previous handler so production crashes are not silenced.
* fix(messages): harden ShareAddressBubble, tighten amount parse, guard grid send
- ShareAddressBubble rendered peer-controlled asset/address verbatim; coerce
them like the other request bubbles so a malformed share-addr can't crash
the renderer (the QA-18 boundary covered the other two bubbles, not this one).
- safeAmount accepted '' (Number->0) and '0x10' (->16); require plain decimal
so a peer can't render a misleading or empty amount.
- a grid-action send() throw could surface as an unhandled rejection; treat it
as a failed send instead.
* fix(send): never blind-retry on submit timeout (double-send guard)
submitSignedTransaction wrapped sendRawTransaction in withTimeout, but
that call only broadcasts — it does not confirm. On a slow RPC it could
time out at 10s after the RPC had already accepted and forwarded the tx
to the cluster. The TimeoutError rethrew as a generic submission error,
ReviewCard's catch surfaced an inline Try again, and a retry re-signed
with a fresh blockhash — landing a SECOND transfer. The existing
double-send guard only covered the confirm path, not submit.
A submit TimeoutError means the broadcast outcome is UNKNOWN, not failed.
The signature is deterministic from the already-signed tx, so return it
(base58, exactly what sendRawTransaction returns on success) and let the
caller's confirmation poll establish the real outcome: confirmed -> success,
or the confirm budget elapses -> timeout failure screen with Try again
hidden and Explorer primary. A genuine RPC rejection (responded with an
error -> tx not accepted) still rethrows and stays inline-retryable. A
pre-sign getLatestBlockhash timeout never broadcast, so it is unaffected.
* fix(wallet): make stale-fetch guard survive the refetch cooldown
The fetchId bump sat after the COOLDOWN_MS early-return, so an adapter
swap within the 30s window could re-enter refetch, bail at the cooldown
gate without bumping the generation, and leave an in-flight fetch on the
now-stale adapter holding the latest id — it then passed isCurrent() and
committed dead-transport data, defeating the QA-52 guard.
Snapshot the generation before the cooldown gate and invalidate it on
adapter/publicKey change via a dedicated effect, so a fetch started on a
previous transport can never win isCurrent(), even when the new adapter
is still inside its cooldown and no fresh fetch starts.
* fix(lxmf): treat shrinking event log as reset, not all-new (QA-25)
A transport reset clears the LXMF event log. With the old guard, a shrunk
log fell through to the head-moved branch and returned the entire array as
new events, flooding duplicate notifications. Return [] on shrink so the
smaller log is a fresh baseline.
* fix(lxmf): clear activeConversationRef on resetIdentity (QA-26)
activeConversationRef held the previous identity's peer hash across a
rotate. The first incoming message on the new identity matched the stale
ref, so useMessageNotifications classified it as the active thread and
suppressed its notification. Null the ref in resetIdentity.
* fix(lxmf): pause UI poll loops in background + clear debounce timers on unmount (QA-07, QA-15)
QA-07: the 1 Hz BLE peer-count poll and the 10 s peer-prune poll ran
forever, even backgrounded, draining battery with no UI to update. Gate
both on an AppState foreground flag (same pattern as useMessageNotifications)
so they pause on background and resume on foreground. The native foreground
service is untouched — only the JS UI polls pause.
QA-15: announceTimerRef and storageTimerRef are tracked on refs that outlive
the event effect scheduling them, so the effect's own cleanup never cleared
them — on provider unmount they could fire setIsAnnouncing / prefSetJson
after teardown. Clear both in an unmount cleanup.
* fix(wallet): allow device passcode fallback when creating local wallet
PIN-only devices (no enrolled biometric) previously hit a silent failure:
LocalWallet.create() ran authenticateAsync with disableDeviceFallback:true,
so with no biometric there was no way to satisfy the prompt and creation
threw 'Authentication cancelled'. Allow the device passcode as a fallback so
these users can create a wallet. This is now the single auth prompt for the
create flow.
* fix(notifications): defer permission prompt until after onboarding
The notification permission was requested at cold mount from the always-on
NotificationBridge, so a brand-new user got an OS permission dialog before
they had created a wallet or understood the app. Gate the request on having
a connected wallet (onboarding complete) and notifications enabled, and fire
it at most once per session.
* fix(settings): confirm before disconnect + label dead cellular toggle
Disconnect previously wiped the session in one tap with no warning. It does
not erase the on-device key (you can sign back in with biometrics/passcode),
but the wallet is unrecoverable if the device is lost or the app is
reinstalled without a recovery-key backup. Add an explicit confirmation that
states this honestly and offers 'Export key first' (opens the existing
recovery-key modal). MWA gets a simpler confirm since its keys live in the
Seed Vault.
Also wrap the 'cellular fallback' toggle in the existing PreviewedActions
shield: it was a live-looking switch wired to nothing (local state only),
which reads as broken. Now labelled 'not yet active' like the other unbuilt
controls; removed the dead meshOnCell state.
* fix(onboarding): single auth prompt, recovery-key backup on create, no panel flash
Three cold-start trust fixes on the onboarding screen:
- Single biometric prompt: handleCreate prompted with authenticateAsync and
then LocalWallet.create() prompted again — a double prompt, and on PIN-only
devices the second (biometric-only) prompt failed silently and the button
looked dead. Drop the redundant prompt; LocalWallet.create() owns auth.
- Backup at creation: after a fresh local wallet is created, offer the
recovery-key export (reusing ExportWalletModal) with one honest line that
the key is device-local, before the user reaches the app. Non-blocking —
'Later' continues straight through. Skipped for MWA (Seed Vault).
- Panel flash (QA-13): returning users whose wallet auto-restores saw the
'Join the Mesh' panel flash on every launch before the redirect. Gate the
panel until wallet hydration settles (initialize() always cycles isLoading
true->false on mount, so the hidden window is exactly the old flash window).
* fix(android): inject android.os.Build import in content-capture plugin
The injected CONTENT_CAPTURE_BLOCK uses Build.VERSION, but the plugin only
imported android.view.View — a clean `expo prebuild` produced MainActivity.kt
with an unresolved 'Build' reference and failed the Kotlin compile. Inject the
Build import too, guarded so a template that already has it isn't doubled.
* refactor(send): extract withTimeout to a dep-free util (fix tier0 validator)
walletData.ts imported withTimeout/DIRECT_RPC_TIMEOUT_MS from sendTransaction.ts
as a VALUE import via the @/ alias. The raw-node tier0 services validator loads
walletData.ts and cannot resolve @/, and that import also dragged sendTransaction's
heavy @/ graph (polyfills, storage, ...) into the validator -> ERR_MODULE_NOT_FOUND.
Move the timeout primitive to src/utils/withTimeout.ts (dependency-free); walletData
imports it via a relative .ts path (same style as ./errors.ts). sendTransaction
re-exports for the existing call sites. tsc + tier0 services both green.
* style(send): hoist withTimeout import to the top import block
Clears the import/first lint warning from the prior extract commit; the
timeout primitive import now sits with the other imports and is re-exported
below for ReviewCard / useWalletBalance.
* fix(nodes): surface denied Bluetooth permission with a recovery path
enableBle() silently returned when BLE permission was denied, so peer
discovery died forever with no explanation — and the empty state kept
lying "Starting mesh…". Capture the permission status; when denied (or
never-ask-again) show a card explaining Bluetooth is needed to find nearby
devices, with a re-grant button (or Open Settings for never-ask-again), and
say "Bluetooth permission needed" instead of "Starting mesh…".
* fix(notifications): gate peer-count notif on isConnected too (complete SU-D)
useMessageNotifications now defers its permission request until onboarding, but
usePeerCountNotification still scheduled a notification gated only on `enabled`.
Scheduling triggers the iOS permission prompt, and the mesh autostarts + finds
peers ~1.5s in (wallet-independent) — so that prompt fired mid-onboarding,
partially defeating the deferred-permission choreography. Gate scheduling on
isConnected (identity created), matching useMessageNotifications.
* fix(nodes): stop the 'Starting mesh…' lie once the node is up
The Peers empty state said "Starting mesh…" forever, even after the node was
running with zero peers — the common solo case. Distinguish the states: only
"Starting mesh…" while !isRunning; once running with no peers, say "No peers
nearby yet". Mirrors the L4 Messages honest-empty-state pass for the Nodes tab.
* fix(wallet): flag a stale balance instead of showing it as live
The new withTimeout on the balance read (this batch) meant a refresh whose
balance RPC timed out silently kept the previous SOL figure with the spinner
gone and no error — a stale balance presented as current, which a user makes
send decisions on. Add a balanceStale signal: keep the last known value (don't
blank it to 0/null — a different lie) and show "Balance may be stale — showing
last known" next to it. Found by the batch's silent-failure review.
* fix(tabs): swipe back to Messages no longer boots to onboarding
The tab list used bare '/' for Messages, but '/' is a root route that
unconditionally Redirects to /onboarding — so swiping right from Wallet/Peers/
Settings back toward Messages kicked the user out of the app shell into
onboarding. Navigate via '/(tabs)' (the path the tutorial + notifications
already use to reach Messages) and map '/'+'/(tabs)' to the Messages index.
* fix(messages): don't stack the QR scanner over the Join-channel sheet
JoinGroupModal kept its own <Modal> visible while presenting QRScannerModal
(also a <Modal>), stacking two native modal windows — on iOS that's the
documented black-camera + frozen-screen failure, blocking the scan-a-channel-
invite join path. Gate the sheet's visible on !scanner so exactly one modal is
mounted at a time. (Full scanner-as-route migration still deferred.)
* refactor(ui): delete dead components + the unused light theme
Verified zero consumers (no direct or barrel imports; no useColorScheme):
- components/ui/{Icon,Card,GlassSurface}.tsx — never imported (the live icon is
primitives/Icon; surfaces use the useGlass() hook). The 'two Icons / two
Pills' collision the design brief feared is, for Icon, just dead code.
- theme lightColors — a full light palette with no reader; ThemeProvider is
hardcoded dark. Removed so AppColors/the type doesn't imply unsupported light
mode. Wiring light would be a feature, not a consistency fix.
No appearance change (nothing rendered these). tsc clean.
* feat(ui): add AppText + ScreenHeader foundation primitives
- AppText: a typography primitive over the shared textVariants scale, so text
size/family/line-height come from one source instead of inline fontFamily +
fontSize (68 files repeat that today). Additive — migrate forward, no bulk
rewrite.
- ScreenHeader: the canonical kicker+title masthead with an optional right slot,
to replace the 4+ hand-rolled headers that drifted on padding + title weight
(some used fontWeight:'600' rather than the SpaceGrotesk SemiBold family).
Built on AppText (dogfood).
Adoption of ScreenHeader across screens is the next, device-QA'd step.
* refactor(wallet): adopt shared ScreenHeader masthead
Replace hand-rolled kicker/title header with the canonical ScreenHeader.
Network/peers chips and QR button move into the right slot; local kicker
and screenTitle styles removed. Title text and right-slot behavior
unchanged.
* refactor(nodes): adopt shared ScreenHeader masthead
Replace hand-rolled peers header with ScreenHeader. Fixes the title
weight: it used fontWeight:'600' (synthetic) instead of the SpaceGrotesk
SemiBold family, now normalized via headingMd. Keeps the screen's wider
spacing[6] gutter.
* refactor(contacts): adopt shared ScreenHeader masthead
Replace hand-rolled address-book header with ScreenHeader (kicker
'LOCAL ONLY'). Close button moves into the right slot. Title now uses
the canonical headingMd (xl/SemiBold) instead of the larger 3xl/Bold
one-off; keeps the screen's wider spacing[6] gutter.
* refactor(messages): migrate ChannelShareSheet to AppBottomSheet primitive
Replace the bespoke Modal + Animated slide + overlay Pressable + grab-bar
with the shared AppBottomSheet, so the channel-share sheet inherits the
canonical gesture/animation/backdrop/SafeArea instead of a hand-rolled
reimplementation. Inner content, copy, QR, copy-rows, screen-capture
prevention, and 60s clipboard auto-clear are unchanged. Keep the glass
surface via backgroundColor; keep the header X (calls onClose, same as
the new drag-dismiss). copied-state reset moved to a visible=false effect
since the primitive owns the close animation.
* refactor(messages): migrate GroupMembersSheet to AppBottomSheet primitive
Replace the bespoke Modal + Animated slide + overlay Pressable + grab-bar
with the shared AppBottomSheet. Inner content (header, count row, empty
state, member ScrollView) and all logic are unchanged. Keep the glass
surface via backgroundColor; keep the header X (calls onClose). The
inner ScrollView keeps nestedScrollEnabled and lives within the
primitive's 90% maxHeight (was a tighter local 70% cap).
* feat(ui): add Toast primitive, replace copy-confirmation Alerts
Adds a themed, non-blocking, auto-dismissing Toast (token-driven via
useTheme, matching InAppNotificationBanner's style + spring/safe-area
pattern). Exposes module-level showToast() backed by a single <ToastHost/>
mounted once in AppShell as a sibling of the notification banner.
Replaces the three blocking Alert.alert("Copied", ...) native dialogs in
SuccessCard and FailureCard with showToast(); clipboard writes unchanged.
Native OS alerts for transient clipboard feedback are heavy and break the
dark/cyan theme; a toast is the consistent non-blocking pattern.
* refactor(nodes): migrate beacon opt-in modal to AppBottomSheet
Swap the hand-rolled become-a-beacon Modal (slide Animated + backdrop +
grab-bar) for the shared AppBottomSheet primitive. Inner content (header,
fee list, CTA) unchanged. Drops the bespoke sheetAnim/sheetY/overlayOp;
the stake modal keeps its own animation and is untouched.
* feat(ui): themed ConfirmSheet + useConfirm on AppBottomSheet
Adds a token-driven confirm/destructive dialog on the shared AppBottomSheet
primitive, replacing the OS-native Alert.alert as the app's confirm surface
(the only place the dark/cyan theme dropped to platform UI).
- module-level confirm({ title, message?, confirmLabel, destructive?,
cancelLabel? }): Promise<boolean>, mirroring Toast's host pattern
- single <ConfirmHost/> mounted once in AppShell next to ToastHost
- resolves true only on confirm; false on cancel/backdrop/drag-dismiss,
guarded to fire exactly once per request
- DepthButton actions; confirm styled danger/red tone when destructive
* refactor(ui): route confirm/destructive Alerts through ConfirmSheet
Replaces the multi-button confirm/destructive Alert.alert dialogs with the
themed confirm() so destructive flows stay on the dark/cyan surface. Each
preserves exact semantics — the destructive action fires iff the user
confirms; cancel and backdrop/drag-dismiss do nothing.
- contacts.tsx: Delete recipient
- RecipientPicker.tsx: Delete recipient (nested confirm under the long-press
menu's Delete action)
- PeersDrawer.tsx: Leave channel (row springs back to rest on either choice,
matching the old cancel/leave branches; onLeave only on confirm)
Left as-is: RecipientPicker's outer long-press menu (3-way Cancel/Manage/
Delete — doesn't map to confirm/cancel) and its single-button QR-not-
recognised info alert; both remain Alert.alert.
* test(tier0): align Solana Pay brand-casing assertions with canonical lowercase 'anonmesh'
Base f3b2107 test expected label=AnonMesh, but the app's canonical brand casing
is lowercase 'anonmesh' (solanaPayUri.ts emits it; 5 lowercase literals app-wide,
zero 'AnonMesh'). Stale test, not a code bug. Matches the identical fix on night2.
* feat(ui): ScreenHeader size variant + complete masthead adoption
Add size prop mapping to the type scale: 'sm' (default, tabs) = headingMd
20px SemiBold; 'lg' (focal/pushed) = displayMd 30px Bold. Adopt on receive
(size=lg renders identically to its prior hand-rolled 30px Bold title —
pure consolidation, drops 3 duplicate styles). Set contacts to size=lg to
restore the presence lost when it was first shrunk to 20px. Tabs stay sm.
All 4 kicker+title screens now share one masthead; size is semantic
(tabs vs focal) instead of ad-hoc fontSize/weight per screen.
* fix(ui): ConfirmSheet resolves its promise on overlap/unmount instead of hanging
Adversarial review found the listener overwrote the in-flight request without
resolving it, so a second confirm() (or host unmount) while one was open left
the first await hanging forever — violating the file's own resolve-exactly-once
contract. Hold the resolver in a ref; a new request or unmount now declines the
open one (false). Also removes a stale-closure: settle() no longer depends on
request state. Single-confirm dismiss paths (button/backdrop/drag) unchanged.
* fix(send): P1 double-send — treat all post-broadcast errors as unknown outcome
The submit double-send guard only special-cased TimeoutError. But web3.js
rejects with a plain Error (not TimeoutError) on a dropped socket or a 5xx from
a load-balanced RPC — which can happen AFTER the node already forwarded the
signed tx to the cluster. Those rethrew into ReviewCard's inline 'Try again',
which re-signs with a fresh blockhash and lands a SECOND transfer (recipient
paid twice). Found in adversarial re-review of the send path.
Now: any error whose broadcast outcome is unknown returns the deterministic
signature so confirmation polling adjudicates (no resubmit). The sole rethrow
is a node-side preflight rejection (SendTransactionError, skipPreflight=false)
— provably never broadcast — preserving clear 'insufficient funds' errors +
safe inline retry. Verified SendTransactionError is a runtime class (instanceof
is sound). tsc 0.
* feat(mesh-payment): offline Solana payments through beacon relay (#79)
- New executeMeshPayment() — fetch nonce via mesh RPC, derive ATAs, partially
sign execute_payment tx with payer key, zero key immediately, send partial tx
to beacon over LXMF. Beacon co-signs and submits to Solana.
- Fix duplicate rpcAdapter params in buildSplTransferTransaction /
estimateSplTransferFeeLamports / sendSplTransfer destructuring.
Key never transmitted over mesh; only the partial transaction is relayed.
* feat(mesh-rpc): migrate MeshRpcAdapter to beaconRpcWait link protocol (#80)
* feat(mesh-rpc): migrate MeshRpcAdapter to beaconRpcWait link protocol
Replaces beaconBroadcastRpc (fire-and-forget LXMF broadcast) with
beaconRpcWait (Reticulum Link + JSON-RPC 2.0 with zlib compression).
- MeshRpcAdapter: swap BeaconBroadcastRpcFn → BeaconRpcWaitFn; rpc()
now passes destHashHex and propagates isError as a thrown Error
- LxmfContext: expose beaconRpcWait from useLxmf() in LxmfCtxValue
- NetworkModeContext: destructure and pass beaconRpcWait to adapter
Native module handles correlation, retransmit, and link management.
No JS-side message routing needed.
* fix(send): remove duplicate rpcAdapter param in buildSplTransferTransaction
* feat(mesh-rpc): beacon broadcast RPC + sendTransaction fix (#81)
* feat(mesh-rpc): migrate MeshRpcAdapter to beaconRpcWait link protocol
Replaces beaconBroadcastRpc (fire-and-forget LXMF broadcast) with
beaconRpcWait (Reticulum Link + JSON-RPC 2.0 with zlib compression).
- MeshRpcAdapter: swap BeaconBroadcastRpcFn → BeaconRpcWaitFn; rpc()
now passes destHashHex and propagates isError as a thrown Error
- LxmfContext: expose beaconRpcWait from useLxmf() in LxmfCtxValue
- NetworkModeContext: destructure and pass beaconRpcWait to adapter
Native module handles correlation, retransmit, and link management.
No JS-side message routing needed.
* fix(send): remove duplicate rpcAdapter param in buildSplTransferTransaction
* fix(mesh-rpc): use beaconBroadcastRpc for RPC queries, keep beaconRpcWait for targeted ops
* feat(messages): tab filtering — Contacts / Groups / All Peers
- New useConversationSummaries hook: derives last message preview,
timestamp, and unread count per peer from native DB. Re-derives on
messageReceived events via sliceNewEvents. markRead clears unread count
in-memory when conversation is opened.
- PeersDrawer: three memoized tabs replacing the flat peer list. Contacts
shows only DMs with message history sorted by recency. Groups shows
joined group channels. All Peers shows full discovery list. Each tab
has a distinct empty state. All three lists wrapped in useMemo to
prevent re-computation on unrelated renders.
- MessagesScreen: wires hook, passes summaries/activeTab/onTabChange to
drawer, calls markRead on peer pick. Default tab is Contacts.
* Feature/messages tab filtering (#88)
* feat(mesh-payment): offline Solana payments through beacon relay (#79)
- New executeMeshPayment() — fetch nonce via mesh RPC, derive ATAs, partially
sign execute_payment tx with payer key, zero key immediately, send partial tx
to beacon over LXMF. Beacon co-signs and submits to Solana.
- Fix duplicate rpcAdapter params in buildSplTransferTransaction /
estimateSplTransferFeeLamports / sendSplTransfer destructuring.
Key never transmitted over mesh; only the partial transaction is relayed.
* feat(mesh-rpc): migrate MeshRpcAdapter to beaconRpcWait link protocol (#80)
* feat(mesh-rpc): migrate MeshRpcAdapter to beaconRpcWait link protocol
Replaces beaconBroadcastRpc (fire-and-forget LXMF broadcast) with
beaconRpcWait (Reticulum Link + JSON-RPC 2.0 with zlib compression).
- MeshRpcAdapter: swap BeaconBroadcastRpcFn → BeaconRpcWaitFn; rpc()
now passes destHashHex and propagates isError as a thrown Error
- LxmfContext: expose beaconRpcWait from useLxmf() in LxmfCtxValue
- NetworkModeContext: destructure and pass beaconRpcWait to adapter
Native module handles correlation, retransmit, and link management.
No JS-side message routing needed.
* fix(send): remove duplicate rpcAdapter param in buildSplTransferTransaction
* feat(mesh-rpc): beacon broadcast RPC + sendTransaction fix (#81)
* feat(mesh-rpc): migrate MeshRpcAdapter to beaconRpcWait link protocol
Replaces beaconBroadcastRpc (fire-and-forget LXMF broadcast) with
beaconRpcWait (Reticulum Link + JSON-RPC 2.0 with zlib compression).
- MeshRpcAdapter: swap BeaconBroadcastRpcFn → BeaconRpcWaitFn; rpc()
now passes destHashHex and propagates isError as a thrown Error
- LxmfContext: expose beaconRpcWait from useLxmf() in LxmfCtxValue
- NetworkModeContext: destructure and pass beaconRpcWait to adapter
Native module handles correlation, retransmit, and link management.
No JS-side message routing needed.
* fix(send): remove duplicate rpcAdapter param in buildSplTransferTransaction
* fix(mesh-rpc): use beaconBroadcastRpc for RPC queries, keep beaconRpcWait for targeted ops
* feat(messages): tab filtering — Contacts / Groups / All Peers
- New useConversationSummaries hook: derives last message preview,
timestamp, and unread count per peer from native DB. Re-derives on
messageReceived events via sliceNewEvents. markRead clears unread count
in-memory when conversation is opened.
- PeersDrawer: three memoized tabs replacing the flat peer list. Contacts
shows only DMs with message history sorted by recency. Groups shows
joined group channels. All Peers shows full discovery list. Each tab
has a distinct empty state. All three lists wrapped in useMemo to
prevent re-computation on unrelated renders.
- MessagesScreen: wires hook, passes summaries/activeTab/onTabChange to
drawer, calls markRead on peer pick. Default tab is Contacts.
---------
Co-authored-by: Excelsior <33706074+epicexcelsior@users.noreply.github.com>
* feat(contacts): open peer conversation → pin to Contacts tab (#89)
Opening any peer conversation calls touchPeer(destHash), which
persists the hash to AsyncStorage (CONTACTED_PEERS). On next derive,
buildSummaries adds a stub ConvSummary for touched peers with no
message history yet, so they appear in the Contacts tab immediately
and survive app restarts.
* fix(observability): scrub raw key bytes + align brand-casing test
PII scrubber matched only strings — a Solana secretKey (Uint8Array(64)) or seed
(randomBytes(32)) under a generic key (data/args/frame-local) shipped to Sentry
unredacted. Redact any binary buffer + long all-numeric array (key serialized as
number[]); 2 new tests cover it (10 total, all pass). Also align the stale
Solana-Pay brand-casing assertions to canonical lowercase 'anonmesh' (inherited
from f3b2107; same fix as night2/design) so the tier0 gate passes.
* chore(deps): bump react-native-lxmf to 0.2.73
* fix(lxmf): correct v0.2.73 integration, message corruption, dead code
Message corruption:
- Add single canonical decodeBody util (strict base64 guard + fatal UTF-8
decode, raw fallback). Route MessagesScreen and useConversationSummaries
through it, replacing two divergent decoders.
- Delete utils/lxmfDecode.ts (third decoder, zero imports) and the
looksReadable heuristic.
Wiring:
- Drive rnodeConnected from onRNodeConnected/onRNodeDisconnected events
(seed once on BLE active) instead of polling connectedRNodeCount() every 1s.
- Enable propagation relay: setPropagationNode(true) before start(), and
syncPropagation() on app foreground to pull store-and-forward messages.
- Surface getConnectedRNodes/unpairNusRNode/syncPropagation on context; UNPAIR
now disconnects the native RNode.
- Fix broken sliceNewEvents call in useConversationSummaries (was passing refs
instead of values, never updated — re-derived every render).
- Fix NetworkModeContext passing undefined beaconRpcWait to MeshRpcAdapter;
use the destructured beaconBroadcastRpc.
TCP path:
- Dev-only one-shot logger of announceReceived/messageReceived key sets to
confirm the real (untyped) native payload shape over TCP.
- A received message defaults a new peer to 'reticulum' (not 'ble') since the
message proves reachability, not transport.
- Reconcile sends stuck queued/stale over TCP against the native DB acked flag,
since a messageDelivered proof may never arrive multi-hop.
* fix(lxmf): tag peer transport from the event interface field, not hops
TCP-only and RNode peers were shown as BLE. Two heuristics caused it:
- resolveVia treated any hops===0 peer as BLE when the BLE radio was on
- a re-tag effect stamped every hops===0 peer as BLE whenever blePeerCount>0
Announce events carry no usable hops field, so all peers defaulted to hops=0
and got flagged BLE the moment one real BLE peer connected.
The native announce/message event reports the interface the packet arrived on.
Read it (probe common key names, substring-map the value to ble/rnode/reticulum)
and make it the authoritative source for a peer's transport:
- resolveVia now returns eventVia(e) ?? existing ?? 'reticulum' (no hops→ble guess)
- applyMessageReceived tags/updates via from the interface field
- applyLogAnnounce (parsed log line, no interface) preserves the existing tag
- deleted the blanket BLE re-tag effect
- dev logger now prints the matched interface field + mapped via to confirm shape
* fix(lxmf): guard phantom setPropagationNode/syncPropagation native calls
The 0.2.73 type defs declare setPropagationNode and syncPropagation, but
neither the iOS nor Android native module registers them — calling the hook
wrapper hits an undefined native function and surfaces "syncPropagation is not
a function" in the UI error banner.
Feature-detect with nativeHasFn() before calling: skip the propagation-node
enable on start() and the foreground syncPropagation() when the native build
lacks them. They auto-activate once a native build implements them.
getConnectedRNodes/unpairNusRNode are registered on both platforms, so they
stay unguarded.
* fix(messages): close composer/tab-bar gap, show placeholder for undecryptable bodies
Gap: MessagesScreen added a View of height insets.bottom below the Composer,
but the screen sits above the custom tab bar which already consumes
insets.bottom. Double-counted, so the composer floated ~insets.bottom above the
tab bar. Removed the spacer (and the now-unused kbShown state + Keyboard
listener + import).
Body: a received body that is strict base64 but not valid UTF-8 is
binary/ciphertext the native layer couldn't decrypt. decodeBody was dumping the
raw base64 as the message text (the "doesn't make sense" garbage). It now
returns a clear placeholder, "🔒 Encrypted message (couldn't decrypt)", for
that case. Real text (base64 of UTF-8) and plain non-base64 bodies are
unchanged.
* chore(lxmf): bump to 0.2.75, remove dead code
0.2.75 registers setPropagationNode/syncPropagation on both iOS and Android,
so the nativeHasFn guards now pass and propagation actually runs.
Dead-code cleanup (lint now 0 warnings across the messaging path):
- remove unused PROGRAM_ID_HEX const
- remove unused ExecutePaymentAccounts/ExecutePaymentParams type mirrors
(BeaconExecutePaymentAccounts/Params are the ones in use)
- remove unused MediaMsg import (MessagesScreen)
- remove unused Icon import (SettingsScreen)
- document the intentional per-member useMemo deps with an eslint-disable
* fix(messages): capture active peer once per incoming-event batch
The incoming-message effect read activePeerHexRef.current per iteration. A tap
that runs pickPeer (which mutates the ref synchronously) mid-batch could
misroute the remaining same-batch messages to the wrong thread. Snapshot the
active peer once at the top of the batch so all messages in a batch route
consistently.
(Investigation of "only one received message shows" points the dominant cause
at the native layer — the LXMF wire timestamp is discarded and all burst
messages get SystemTime::now() seconds, plus the 200-cap event buffer's
by-reference slicing. Fix prompt for the package is in the plan file.)
* feat(lxmf): track events by monotonic id, fixing dropped/duplicated bursts (0.2.76)
0.2.76 stamps every LxmfEvent with a strictly-increasing native id and raises
the hook buffer cap to 1000. Replace the head-by-reference sliceNewEvents (not
eviction-safe — a burst overflowing the buffer could drop or duplicate unread
messages) with an id watermark.
- new eventsAfter(events, lastSeenId): returns events with id > lastSeenId in
arrival order (oldest-first), O(new) and eviction-safe; delete sliceNewEvents.
- migrate all four consumers to a lastSeenId ref: MessagesScreen, LxmfContext,
useConversationSummaries, useMessageNotifications. Mount semantics preserved
(watermark starts at 0 → process backlog once); notifications advance the
watermark even when disabled to avoid replay.
- oldest-first ordering also fixes the prior reverse-append of burst messages.
Bumps @magicred-1/react-native-lxmf to 0.2.76 (native rebuild required).
* fix(lxmf): don't drop id-less events — restore peer announcements
Regression from the id-watermark migration: eventsAfter `continue`-skipped any
event without a numeric `id`, and the shipped 0.2.76 native build does not stamp
`id` on announceReceived (the type is `id?: number`). So every peer announce was
silently discarded before reaching the peer map — the peer list stopped
populating. (Beacons kept showing because they flow via lxmf.beacons/mergeBeacon,
not eventsAfter — the diagnostic tell.)
- eventsAfter now includes id-less events instead of skipping them; `id` only
bounds the already-seen tail. Safe: the message/summary/notification consumers
type-filter to messageReceived (id-bearing), and the announce/peer consumer is
idempotent (upsert by hash), so re-surfacing an id-less announce can't dupe.
- add highestEventId() to advance the watermark by the max id in the buffer
(at(-1) could be an id-less event → undefined → watermark never advanced).
- seed lastSeenId at -1 (was 0) so a genuine id=0 first event isn't treated seen.
Native follow-ups (not app-fixable, tracked in the prompt): 0.2.76 should stamp
id on announces, decode msgpack announce app_data to a clean name, and map the
rnodeConnected/rnodeDisconnected events in the Android bridge.
* merge: staging (route scanner #85) into night2/integration
Resolve scanner overlap in favor of #85's route-based scanner:
- drop JoinGroupModal.tsx (superseded by the /join-channel route)
- MessagesScreen: join via router.push('/join-channel'); keep night2's
isRunning/bleActive honest-empty-state props
* chore(lxmf): neutralize dev-gate comment wording
* fix(onboarding): drop 'Confidential' overclaim from welcome subtitle
Confidential payments aren't in the send path (Arcium = beacon privacy, not
payment confidentiality). Reframe to the honest, shipped capability set:
encrypted mesh messaging + off-grid payments. Clears the honesty-check gate.
* fix(onboarding): drop 'Confidential' overclaim from welcome subtitle
Confidential payments aren't in the send path (Arcium = beacon privacy, not
payment confidentiality). Reframe to the honest, shipped capability set:
encrypted mesh messaging + off-grid payments. Clears the honesty-check gate.
* fix(onboarding): drop 'Confidential' overclaim from welcome subtitle
Confidential payments aren't in the send path (Arcium = beacon privacy, not
payment confidentiality). Reframe to the honest, shipped capability set:
encrypted mesh messaging + off-grid payments. Clears the honesty-check gate.
* chore(app): bump version to 1.0.3
* chore(deps): sync lockfile to react-native-lxmf 0.2.78
* chore(app): set ios buildNumber + android versionCode to 3
* chore: remove @sentry/react-native (broke CI native upload)
The Sentry Gradle step (sentry.gradle source-map/symbol upload via sentry-cli)
failed the CI release build (no auth token). Remove the Expo config plugin and
the dependency so prebuild no longer wires sentry.gradle. sentry.ts becomes a
no-op shim preserving its API (Sentry.wrap/withScope/captureException,
initObservability) so app/_layout.tsx and ErrorBoundary.tsx are unchanged and
still render the error fallback. The global errorHandler (console.error + native
crash path) remains the crash signal.
* fix(metro): swap getSentryExpoConfig for getDefaultConfig
@sentry/react-native was already removed from package.json and app.json
on this branch, but metro.config.js still required its metro wrapper,
breaking expo start and any metro bundling step.
* fix(network): use beaconBroadcastRpc in MeshRpcAdapter
---------
Co-authored-by: Hunter F <33706074+epicexcelsior@users.noreply.github.com>
Co-authored-by: epicexcelsior <epicexcelsior@gmail.com>1 parent 34c3508 commit 46406f8
3 files changed
Lines changed: 38 additions & 63 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
108 | 108 | | |
109 | 109 | | |
110 | 110 | | |
111 | | - | |
112 | | - | |
113 | | - | |
114 | | - | |
115 | | - | |
116 | | - | |
117 | | - | |
118 | | - | |
119 | 111 | | |
120 | 112 | | |
121 | 113 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
2 | | - | |
3 | | - | |
4 | | - | |
| 1 | + | |
| 2 | + | |
| 3 | + | |
5 | 4 | | |
6 | 5 | | |
7 | | - | |
| 6 | + | |
8 | 7 | | |
9 | 8 | | |
10 | 9 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | | - | |
| 2 | + | |
3 | 3 | | |
4 | | - | |
5 | | - | |
6 | | - | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
7 | 10 | | |
8 | | - | |
9 | | - | |
10 | | - | |
| 11 | + | |
| 12 | + | |
11 | 13 | | |
12 | | - | |
13 | | - | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
| 16 | + | |
16 | 17 | | |
17 | | - | |
18 | | - | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
29 | | - | |
30 | | - | |
31 | | - | |
32 | | - | |
33 | | - | |
34 | | - | |
35 | | - | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
40 | | - | |
41 | | - | |
| 18 | + | |
42 | 19 | | |
43 | | - | |
| 20 | + | |
| 21 | + | |
44 | 22 | | |
45 | | - | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | | - | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
58 | 26 | | |
59 | 27 | | |
60 | | - | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
0 commit comments