fix: 12 reported bugs + audit drain, iOS 26 chat, light theme, bitchat overhaul#190
Open
Kelbie wants to merge 524 commits into
Open
fix: 12 reported bugs + audit drain, iOS 26 chat, light theme, bitchat overhaul#190Kelbie wants to merge 524 commits into
Kelbie wants to merge 524 commits into
Conversation
…elper ChatMessageBubble carried a local formatTimestamp expecting unix ms; UserMessagesScreen carried an identical function expecting unix seconds. Same display contract, drifted unit — a textbook ubiquitous-language miss that a single audit comment had been tracking as partial. Extract one formatChatTimestamp(unixMs) into shared/ui/composed/chat/ and delete both copies. UserMessagesScreen call sites now multiply Nostr created_at by 1000 at the boundary, making the seconds-to-ms conversion explicit instead of hiding it inside a formatter. Boy-scout (skill:improve-codebase-architecture): ChatMessageBubble.tsx — deletion test passed, the local helper had no leverage; sibling file is the obvious home and lookalikes drops two of three formatTimestamp rows. Refs: __audits__/20.json#F-006
Promote the private assertPubkeyHex helper in shared/lib/nostr/secureStorage.ts to an exported `isNostrPubkeyHex` type predicate, and migrate the two inline duplicates that previously accepted any 64-char string regardless of charset: - features/feed/components/HomeFeed.tsx:107 (getCategoryPubkeysFromSpec) — was `value.length !== 64`, which let UTF-8 mojibake or e.g. `01javascript:…` padded to 64 chars flow into Primal `mega_feed_directive` and NDK `#e` filters as a "pubkey". - features/settings/screens/SettingsKeyringScreen.tsx:286 (raw-hex private-key import path) — was inlining its own `length === 64 && /^[0-9a-fA-F]+$/` check. The predicate keeps the `secureStorage.assertPubkeyHex` throwing API as a one-line consumer so trust-boundary callers can choose between predicate narrowing and assertion. A colocated jest test pins the exact attack shape the audit named (a 64-char string with non-hex characters must be rejected). Refs: __audits__/26.json#F-005
… extractor Two file-local `extractNostrPubkey` copies had drifted: `shared/lib/getMintCatalog.ts` required canonical 64-char hex (the right trust gate, but it silently dropped any mint operator publishing `npub1…` per NUT-06 — followers/reputation pills vanished from the picker after b5ca041), while `features/mint/hooks/useMintProfiles.ts` accepted any non-empty string (impersonation surface still open on the search screen). Replace both with `shared/lib/nostr/extractMintNostrPubkey.ts`, which accepts canonical hex via the existing `isNostrPubkeyHex` predicate and `npub1…` decoded through `nip19.decode` with checksum validation; rejects everything else (LN addresses, URLs, malformed bech32, mojibake, wrong-prefix bech32). The shared helper closes the lookalikes 2-way collision and lets both call sites agree on one trust posture. Boy-scout (skill:improve-codebase-architecture): shared/lib/getMintCatalog.ts — drop the local `ContactEntry` interface and `NOSTR_HEX_PUBKEY_REGEX` constant, move the structural input type into the helper. Boy-scout (skill:improve-codebase-architecture): features/mint/hooks/useMintProfiles.ts — drop the local `MintContactList` / `MintInfoForProfile` types that existed only to feed the deleted extractor. Refs: __audits__/60.json#F-001, __audits__/60.json#F-002
Render-time and catch-block logging at payment+feed boundaries was
leaking bytes into log.txt and Sentry breadcrumbs:
- PaymentInfo.tsx:118 logged `preview: selectedValue.slice(0, 30)` on
every render, where selectedValue is a cashuB token, bolt11 invoice,
Lightning Address, or payment-request URI.
- QRCode.tsx:124 had the same `preview: address.slice(0, 30)` shape on
every address change (sister site, same pattern, not separately cited).
- HomeFeed.tsx:321 and :527 logged `{ error }` directly from the unknown
caught in loadFeed / loadMoreItems, which can carry stack/message/url
bytes from the WebSocket failure with no redaction.
The logger's SECRET_STRING_PATTERNS guard inside summarizeString only
fires when the call site hands the FULL value (and the string exceeds
maxStringLength=120). Manual `value.slice(0, 30)` bypasses the seam by
construction. Fix is to drop the preview field at every call site and
narrow unknown errors before logging.
PaymentInfo also migrates its three log.{info,debug} calls onto the
existing paymentLog child logger (already used by the popup neighbours
in shared/lib/popup/popups/payment.ts), per the audit's recommendation.
Refs: __audits__/46.json#F-003 [High], __audits__/26.json#F-017 [Low]
… mapping
decodeTextRecord previously logged a warn and decoded UTF-16 bytes as UTF-8
anyway — silent garbage out for any tag written with the UTF-16 encoding bit.
Now it dispatches to a small decodeUtf16 helper that honors FE FF / FF FE
BOMs and defaults to big-endian per the NFC Forum Text RTD. Buffer has no
native utf16be, so BE input is byte-swapped in place before utf16le decode.
buildTextNdef gains an optional { lang } override (default NDEF_TEXT_LANG
= 'en' in constants.ts) so the magic literal is named at the call site, and
validates the ASCII-bytes ≤63 limit imposed by the status byte's lang-length
field.
apdu.ts's catch block extracts the substring-match cascade into a named
mapTransceiveError helper. The brittleness is real and unfixable at the
consumer level — react-native-nfc-manager surfaces transceive failures as
plain strings on both platforms with no error code on the JS side — but it
now lives in one documented place, ready to swap if the library ever
exposes structured codes.
New test in __tests__/nfcNdefDecode.test.ts locks down UTF-8 round-trip
(short + normal records), UTF-16 BE-no-BOM / BE-BOM / LE-BOM decode paths,
and the explicit-lang round-trip.
Refs: __audits__/48.json#F-005, __audits__/48.json#F-007,
__audits__/48.json#F-008, __audits__/48.json#F-012
…n and SettingsRecoveryScreen SlideToDelete and SlideToRecover were near-identical: same THUMB_SIZE/TRACK_PADDING constants, same Pan-gesture/threshold/spring config, same track/thumb/textContainer styles. Both wrapped a redundant GestureHandlerRootView (the app root and drawer root each provide one already), and both shipped a StyleSheet.create block in files that otherwise use Uniwind className. Collapsed both onto a single SlideToConfirm in shared/ui/composed; static layout moved to Uniwind classes; redundant gesture-root wrappers and unused gesture/animation imports removed from both screens. Refs: __audits__/24.json#F-010, __audits__/24.json#F-015, __audits__/24.json#F-023
… ChatMessage.senderPubkey ChatMessage.senderPubkey carried three semantically distinct identities (BLE peer-id, per-geohash Nostr pubkey, '' for own echoes) and was treated as a pubkey by ChatMessageBubble's avatar seed. Renamed to senderId end-to-end (ChatMessage, ChatBubbleMessage, useMessageGrouping, useBitChat, GeohashChatScreen, WhitenoiseDMScreen) so the field name matches its transport-agnostic role; protocol-truth pubkeys on NostrMessageEvent / NostrPrivateMessageEvent stay as senderPubkey. Same slice tightens the bridge surface: addBLEPeerListener now takes a typed BLEPeerEvent (was `event: any`); BLEPeer / BLEMessageEvent / BLEDiagnostics / BLEPeerEvent live in src/types.ts (consolidated from BitChatModule.ts); deleted unused public exports (nativeEncodeGeohash, nativeDecodeGeohash, getNeighbors, getClosestRelays, getClosestRelaysForGeohash, decodeGeohash, stopBLE, getBLEDiagnostics, stopNostr, BitChatEventMap, Participant, RelayStatus) — zero non-iOS callers. Refs: __audits__/63.json#F-002, __audits__/63.json#F-004, __audits__/63.json#F-006, __audits__/63.json#F-007, __audits__/63.json#F-010
…ing shared geohash subs
Three lifecycle bugs in useBitChat clustered around cleanup doing too much:
1. Each per-transport effect cleared the React message buffer on every
teardown. Deps like `nickname` resolving from useBitchatNickname after
first render churned the effect and wiped history — for ble-dm, where
BLE has no replay path, that loss was permanent. Reset is now a single
identity-keyed effect (transport/dmPeerID/geohash); per-transport
cleanups only tear down the subscription.
2. The public-nostr cleanup called leaveGeohash() unconditionally. Native
side (BitChatNostrBridge) keeps one active geohash that fans out to
BOTH the public chat sub (`geo-{g}`) AND the gift-wrap DM sub
(`geo-dm-{g}`). Closing the public screen while a nostr-dm thread on
the same geohash was open silently tore down the DM subscription. The
nostr-dm cleanup already documented this contract; the public branch
now matches.
3. bitchatDM passed `geohash ?? 'mesh'` to GeohashChatScreen so a
missing geohash for transport='nostr-dm' would have subscribed to an
attacker-populatable 'mesh' pseudo-channel. The zod refine on the
route already forces nostr-dm to carry geohash, so the fallback was
dead defensive code — but the type lied about it. Widened
useBitChat/GeohashChatScreen geohash to string | undefined so the
hook's existing !geohash short-circuit handles missing input
honestly.
Refs: __audits__/13.json#F-003, __audits__/13.json#F-005, __audits__/49.json#F-003
…gial wallpaper provider Extract the per-unit wallpaper resolver into shared/lib/theme/resolveUnitWallpaper.ts (pure functions) plus shared/lib/theme/useUnitWallpaper.ts (Zustand hook). themeStore no longer imports wallpaperStore — the only sovran-app cycle in analyze-structure goes away. wallpaperStore keeps reading themeStore as a one-way arrow. ProfileWallpaperProvider was a Context wrapper around the same useThemeStore selector that nobody read (the hook bypassed the Context entirely). Deleting it removes one provider layer from AccountScopedProviders compose chain. Account.tsx and ThemeProvider import the hook directly from the new module; themeDraft consolidates onto the new getCatalogThemesForAlbum helper, dropping its inline duplicate. Adds __tests__/resolveUnitWallpaper.test.ts covering the four-step fallback chain (override → first-stored override → newest album → 'dark') plus the built-in colors album short-circuit. analyze-structure: Cycles 2→1, Architecture 40→55, Overall 41→45. Refs: __audits__/16.json#F-004, __audits__/41.json#F-004
…sum cache-buster Three intent-vs-behavior bugs in features/feed where the shape of the code implied dynamic behaviour but the runtime behaviour was dead, wasted, or wrong. - createPrimalRelayClient was assigning ws.onerror/onclose twice: once before the openPromise constructor, then again inside it. Wire all three handlers (onopen/onerror/onclose) exactly once inside the constructor, with the openSettled-guarded settle() clearing the open timeout. Same failure semantics in both pre-open and post-open paths. - engagementRevision summed entry.updatedAt timestamps as a cache-busting identifier; the sum could decrement when entries were removed and is not what the LegendList consumer wants from a "revision". Replace with a ref-counter that bumps on each useMemo recompute. (Substitutes the audit's in-store counter shape with an in-hook ref-counter to avoid a persist-shape change — same monotonic-identifier semantics.) - The actions = useRef(useNostrSocialStore.getState()) + refreshing useEffect was performing a per-render no-op: Zustand 5 store-method references are stable across renders, so re-snapshotting the store just to read .actions off it adds nothing over reading useNostrSocialStore.getState() inline. Delete the ref and inline the seven call sites. Boy-scout (skill:improve-codebase-architecture): drop the redundant `export` keyword on getVideoUrlsFromContent, prettifyUrl, normalizeRawPrimalEvent, and getFirstTagValue in nostr/shared.tsx — analyze-structure flagged them as "exported but only used internally", confirmed by zero external imports. Refs: __audits__/26.json#F-012, __audits__/26.json#F-014, __audits__/26.json#F-019
…d drop type re-export drift AnimatedImageOverlay.tsx was using runOnJS from react-native-reanimated while sibling provider.tsx already used scheduleOnRN from react-native-worklets — same package, two equivalent scheduling APIs side by side. Standardise on scheduleOnRN across all 19 worklet→JS call sites so the package has one canonical surface (Reanimated v4's recommended API moved to react-native-worklets). Also delete the duplicate type re-export block in provider.tsx that mirrored five types already exported from ./types and re-exported by the index barrel — three import paths collapsed to one. External consumers already routed through the barrel; internal siblings already import from ./types. Boy-scout (skill:improve-codebase-architecture): provider.tsx — reorder the five session/timeout refs above registerThumbnailLayout so the closure no longer references thumbnailLayoutsRef declared 65 lines later (temporal hazard for readers; useRef hoisting made it work). Net -8 lines across two files. No behaviour change; existing imageOverlayComputeExpandedSize.test.ts continues to pass. Refs: __audits__/58.json#F-003, __audits__/58.json#F-005, __audits__/58.json#F-008
…r brand/picker emojis locally AnimatedEmoji.tsx wrapped expo-image around a fonts.gstatic.com URL keyed by emoji codepoints, leaking the user's emoji selection pattern to Google for every unique emoji rendered (Noto animated WebP CDN). Two consumers — MarmotIcon (whitenoise brand glyph) and the emoji-picker popup's copy-confirmation icon. Both render fine through the OS emoji font (the existing `failed` fallback in AnimatedEmoji at lines 22-24 was already this exact path), so promote the fallback to the primary path and delete the primitive. Net -49 LOC, no network dependency, no behaviour change on offline cold-start. Refs: __audits__/17.json#F-016, __audits__/33.json#F-014, __audits__/52.json#F-013
Three audit findings shared one root cause: the streaming send had no
lifecycle handle. Fixed together so the hook owns one consistent flow.
- Hook-scoped AbortController aborts a prior in-flight stream when a new
send/retry starts, threads `signal` through sendMessage to fetch, and
is aborted from a cleanup useEffect on unmount. Backgrounding or
unmounting mid-stream now stops billing instead of writing tokens into
a stale tree.
- Balance refresh promise is tracked in a ref; the next streamIntoPlaceholder
awaits it before snapshotting `balanceBeforeMsats`. Retry-during-
balance-refresh used to capture the same pre-send balance as the first
call and double-count the cost diff.
- Stream finalize logic moved to features/ai/lib/finalize.ts:pickFinalizeMessage
with a regression test. Reasoning-only streams (DeepSeek-R1 truncated
after thinking; o-series with low-effort caps) now persist
`{content:'', reasoningContent}` so the bubble's `hasContent` check
renders the reasoning section without the apologetic
"(No response received)" body. Cost-stamp finalize re-uses the same
payload so the second write doesn't undo the first.
AbortError from any of the three (sendMessage, for-await loop,
checkBalance) is treated as a normal lifecycle event: the placeholder is
removed, the span tags `outcome: 'aborted'`, and no failure popup fires.
Refs: __audits__/34.json#F-003 (High), __audits__/34.json#F-004 (Medium),
__audits__/34.json#F-013 (Low)
Engine and bridge were positioned at shared/lib/popup/ root but consumed only by popups/* wrappers (15/15 of engine, 3/4 of bridge); external callers always go through the index.ts barrel. The split was purely organisational and obscured the public-vs-internal boundary of the popup module — analyze-structure flagged both files as MOVE candidates. Refs: __audits__/42.json#F-006
WhitenoiseSigner now copies the user's nsec into a buffer it owns and exposes dispose() that zeros the buffer + trips a guard so subsequent sign / nip44 calls throw. WhitenoiseProvider's existing useEffect cleanup calls disposeSigner on profile-switch and unmount so the previous account's key bytes don't sit in process memory until GC reclaims the slab. Defense-in-depth — Hermes does not zero freed memory, and nostr-tools' nip44.v2.utils.getConversationKey may cache derived secrets internally (UNVERIFIED upstream); zeroing the input is the minimal step we control. Regression test pins the disposal seam. useWhitenoiseDM no longer takes accountIndex as a parameter — reads it from useWhitenoise() context like the sibling hooks. The lazy-init useRef for WhitenoiseDmIndex is now a useMemo keyed on accountIndex, so a future provider re-mount with a different account creates a fresh index instead of reusing the previous account's. Closes a class of silent-corruption bugs across the provider/hook seam. upsertMessage uses sorted insertion (O(n) per upsert) instead of full sort. resolveInboxRelays() helper extracted in client/network.ts so the inbox watcher and DM-send path apply the same try/catch + fallback. Refs: __audits__/33.json#F-004, __audits__/33.json#F-008, __audits__/33.json#F-012, __audits__/33.json#F-018
Route paymentOptionsPopup / paymentFallbackPopup through PopupHost's standalone <BottomSheet> (FullWindowOverlay enabled) instead of the heroui Menu lane, which uses disableFullWindowOverlay and renders behind iOS route modals like the send-flow camera.
NPubCash receive was broken after 8b16e5b pointed the plugin at npub.cash, which doesn't serve the sync API. Centralize the host as npubx.cash (matching eNuts), key the sync cursor by pubkey so every profile gets its own durable position, and replace the mocked username claim + localhost punt with the real npubcash-sdk NPCClient.setUsername flow. The Select Mint screen now hides the add-mint and inspect chrome under NPC scope and disables (0.5 opacity + reason) mints that don't speak NUT-17 websockets, so the receiving mint can actually auto-redeem paid quotes.
iOS 26 uniformly dims any RN FlatList/VirtualizedList in the AI tab's wrapper tree (affects inverted AND non-inverted lists; not the same UIScrollEdgeEffect bug from facebook/react-native#54181 — we disabled all four edge effects via patch-package and the dim survived). LegendList sidesteps the dim entirely because it uses its own virtualization rather than RN's FlatList path. Rebuild AiChatScreen directly on it instead of through the shared <ChatScreen /> (GiftedChat). Other chat surfaces (BitChat, WhiteNoise, Nostr DM, geohash) live in (user-flow) modal stacks where the dim doesn't reproduce, so they keep using the shared ChatScreen and aren't touched. Per-file: - features/ai/screens/AiChatScreen.tsx: full rewrite onto LegendList v3 beta. Uses the canonical chat pattern (initialScrollAtEnd, alignItemsAtEnd, maintainScrollAtEnd, maintainVisibleContentPosition, recycleItems) — no manual scrollToEnd chasers. Composer + list both ride the keyboard via a single shared Reanimated translateY (driven from useReanimatedKeyboardAnimation height/progress), since RN's position:absolute children don't lift with KeyboardAvoidingView's paddingBottom. On mount, archive any in-progress conversation via routstrStore.createSession() so the surface always opens empty. - app/(drawer)/(tabs)/ai/_layout.tsx: scrollEdgeEffects:hidden on all four edges of the AI Stack.Screen. iOS 26 defaults this to 'automatic' which renders a UIKit gradient material (opaque-to-transparent) at scroll edges; on AI that showed up as a top fade on the chat content. - app/(drawer)/(tabs)/_layout.tsx: AI NativeTabs.Trigger gets disableAutomaticContentInsets + scrollEdgeEffects:hidden on the tab bar. Same iOS 26 edge-effect mechanism, just for the tab bar surface. - shared/ui/composed/chat/ChatScreen.tsx: iOS 26 added a new auto-management path that re-applies scrollIndicator vibrancy via automaticallyAdjustsScrollIndicatorInsets even when contentInsetAdjustmentBehavior is 'never'. Force-disable both indicator auto-adjust and pin static insets to zero so UIKit has no insets to drive material from.
Strip three pieces from 4c07290 that weren't strictly necessary for the AI tab fix: - app/(drawer)/(tabs)/ai/_layout.tsx — revert to `headerTransparent: true` and drop the `scrollEdgeEffects:hidden` Stack.Screen prop. The fade these were aimed at was never verified visually. - app/(drawer)/(tabs)/_layout.tsx — drop the AI NativeTabs.Trigger props (`disableAutomaticContentInsets`, `unstable_nativeProps.scrollEdgeEffects`). Pre-session work bundled in under the iOS-26 theme; not introduced or verified by the LegendList port itself. - shared/ui/composed/chat/ChatScreen.tsx — revert the scrollIndicator auto-adjust tweak. Affects only the GiftedChat-based ChatScreen which AI no longer consumes (other surfaces — BitChat / WhiteNoise / Nostr DM / geohash — are unchanged from main). Also tidy AiChatScreen.tsx after the revert: remove the now-unused `topInset` local + the stale `headerTransparent: false` / v2 `waitForInitialLayout` comments. No behavior change.
Adds a `PatternBackground` composed component that loads
`assets/icons/internal/pattern.svg` once, rewrites every `fill="#hex"` to
`fill="currentColor"`, strips the full-canvas backdrop path, and lets
react-native-svg's native `<pattern>` rasterize one tile that the GPU
repeats — so the source's 216 paths cost one parse per mount instead of
one parse per repeat. Each path now carries `fill-opacity="0.08"` so
overlapping shapes build up depth instead of flattening under a single
wrapper alpha.
Mounted on the AI tab behind the chat list and composer. Theme-driven by
default: `color` defaults to `useThemeColor('foreground')` so the
pattern inverts naturally between light and dark themes.
Drops the centered robot empty-state — the pattern carries the visual
weight on an empty session. Aligns `AiLayout` Stack `contentStyle` with
the surface token used by `SearchLayout` for Feed/Contacts so transition
bleed-through doesn't show a transparent fallback.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Make the "≈ <fiat>" pill read as the same family as the split-bill / swap / theme buttons in every capability state: neutral muted border on flat, a real BlurView capsule on iOS non-liquid (was a fake tinted flat), and matching glass on liquid. Green identity now lives in the chrome tint (0.28) instead of double-greening with green text — text uses foreground, mirroring how the action buttons render their icons. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a neutral grayscale Light palette, surfaces it in the built-in themes, and replaces hard-coded chrome with theme-aware tokens so the app renders correctly when `background` is light. - New `useColorScheme` hook (BT.601 luma of the `background` token). - Thread `colorScheme` into SwiftUI glass surfaces (BalancePill, CircleActionButton, PrimaryBalance EcashStatusPill) and `liquid-glass-text` via a `colorScheme` prop, since the app pins `userInterfaceStyle: dark` at the window level. - Default `View blur` tint to `systemThickMaterialLight` on light themes; `CircleActionButton.blur` switches to a light tint. - QRButton inverts foreground/background with the theme; QRCode and the keyring QR are pinned dark-on-white for scanner reliability. - CustomKeyboard, FeedScreen, ContactsScreen, MonthlyChart, and ScreenStates pull `foreground` / `muted` / `danger` / `success` instead of `opacity(foreground, ...)` or hex literals. - SearchLayout sets `headerTitleStyle` and `headerTintColor` explicitly so the native bar title doesn't render invisible. - BitcoinNearYou gates the iOS blend stack and adds a `screen`-blend light lift; Android stays plain. - AmountFormatter / AmountEntryView use `success` for receive amounts; FiatCurrencyPill swaps `green-500@0.28` -> `green-400@0.4`. - themeEngine light overlay shadow uses an inset bright edge + soft white glow + 4 %-black ring (dark drop-shadow read as muddy). - Extract `UnderlineTabs` from ContactsScreen and migrate ContactsScreen + ReceiveScreen to it; ReceiveScreen's P2PK row gains a `GradientCard` wrapper. - wallpaperSync drops albums whose `author.pubkey` doesn't match Sovran's published pubkey, with a debug log of dropped authors. - patches: react-native-gifted-chat switches its `AnimatedFlatList` from RNGH's FlatList to RN's FlatList so the inverted scroll transform lands on the native list. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tabs - QR card uses GradientCard on light theme so the code reads on frosted chrome instead of vanishing into a flat white surface - Bitcoin currency disc keeps a white "B" since the disc is always orange, preventing a near-black glyph on orange in light mode - ReceiveScreen tab row gets horizontal margin so it aligns with the surrounding content gutter Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Continuous-corner radius on the scene's left side, surface-colored backdrop so the rounded gaps blend with the drawer, and an inset hairline (separator-secondary) hosted on the overlay that traces the curve when the drawer opens. Drawer dim and menu scrim flip to white in light mode. Patches @react-navigation/drawer to forward overlayStyle from screenOptions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Map `--success` to blue in the design system so every `variant="success"` surface (StatusToast, PaymentStatusIcon, Badge, etc.) picks up blue automatically, and swap every direct `green-*` reader to `blue-*` so the old green accents disappear without losing the green scale itself. Reshape the fiat "≈" pill to a neutral white glass wash with adaptive text (#FFF on dark / system, #000 on light) and wire `tint(success)` on the SwiftUI Menu so the selected currency row highlights blue. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Swap the OfflineShell accent from `red-300` to `blue-300` so the offline banner + screen border match the new blue success/info palette. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e edge Pull the scene + overlay corner radius from `expo-screen-corner-radius` so the drawer hugs the physical display curve instead of a fixed token (falls back to `radius['2xl']` on Android <12 or non-rounded displays). Fire a single Light-impact haptic on every drawer state transition (gesture, hamburger, overlay tap all converge on the same status flip), and widen `swipeEdgeWidth` 40 → 128 so the open gesture is easier to catch from the screen edge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drive the composer + list lift via a Reanimated translate that reads keyboard height directly, instead of relying on KeyboardAvoidingView's padding to lift an absolutely-positioned composer. The KAV path was unreliable — composer ended up either pinned under the keyboard or floating well above it depending on whether RN resolves position:absolute against the border or padding box in the current context (full-screen vs user-flow modal stack). AiChatScreen also folds the SovranTabBar height into the lift formula on the non-NativeTabs path so the composer lands flush at the keyboard top instead of overshooting by the tab bar's height. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d blur
Replaces react-native-gifted-chat (inverted FlatList + internal KAV) with
LegendList for every DM-like chat surface (BitChat, Nostr DM, WhiteNoise,
geohash), matching AiChatScreen's architecture so the whole app uses one
list library.
The migration also resolves an iOS 26 chrome artifact where, on keyboard
dismissal, a backdrop material the height of the keyboard would persist
behind the chat content. Two changes were required:
- Static `StyleSheet.absoluteFillObject` backdrop sibling at the bottom
of ChatScreen's z-stack (mirrors AiChatScreen's `<PatternBackground />`).
Without a static solid sibling, iOS 26 perceives any animated child as
a translucent moving layer and captures a backdrop snapshot.
- Lift the list wrapper with `top: keyboardHeight.value` (Yoga layout
property) instead of `transform: [{ translateY: ... }]` (CALayer
transform). Transforms promote the wrapper to its own compositor layer,
which iOS 26 captures during keyboard transitions; layout offsets stay
on the same render layer and avoid the snapshot.
Composer uses react-native-keyboard-controller's `<KeyboardStickyView>`
instead of a hand-rolled Reanimated translate.
Drops the `react-native-gifted-chat` dependency. Also cleans up
ModalLayoutWrapper to only mount its debug-overlay Views when `debug` is
true — the empty leaves were blocking react-native-screens' scroll-view
finder from reaching any underlying scroll view.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tpatch@^1.0.0 The published nutpatch only exposes an OutputCreator HybridObject (targeting cashu-ts 4.x), not the lower-level primitives our local fork added — hashToCurve/blind/unblind/hashE/dleq, pbkdf2HmacSha512, nip44Ecdh, chacha20, hmacSha256, batchDeriveLegacy. Rather than maintain a vendored Nitro module with monocypher + secp256k1 to keep parity, fall back to JS for these paths and park nutpatch@1.0.0 as a dependency until we upgrade cashu-ts. Removed: - packages/nutpatch/ — vendored Nitro module - shared/lib/cashu/nativeCrypto.ts — Crypto HybridObject bridge - Native NIP-44 v2 path in shared/lib/nostr/nip17.ts (probeNative, native ECDH/ChaCha20/HMAC, hkdfExpandNative, nip44DecryptNative) → nostr-tools nip44.v2 fallback - Native PBKDF2 path in shared/lib/nostr/keyDerivation.ts → bip39 mnemonicToSeedSync; the in-memory rootSeed cache stays so PBKDF2 runs once per profile switch - __CASHU_NATIVE bridge + batchDeriveLegacy branch from cashu-ts patch Kept in cashu-ts patch: __CASHU_PERF telemetry (consumed by SettingsRecoveryScreen + log-doctor) and the JS-only _deriveBoth / _masterCache / _keysetCache BIP-32 derivation cache. Perf regressions on Hermes (acknowledged): - BIP-39 mnemonicToSeed: ms → ~3s cold (cached after first call) - NIP-44 encrypt/decrypt: sub-ms → 5–15ms ECDH + JS ChaCha20/HMAC - Cashu hashToCurve/blind/unblind/hashE: ~100× slower (noble-curves) - NUT-13 batch derive: 1 native call → N JS derivations (still keyset-cached) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reverts the success token (`--success` / `--success-foreground`) and a handful of consumer screens (rebalance plan, recovery, mint-add validator, transfer separator, Badge variant="success", SelectableCheck "success") back to green. The prior blue mapping shipped with the light-theme polish sweep made positive states indistinguishable from primary/link surfaces. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…flow Adds two fire-and-forget machine operations to coco-payment-ux: `resolveRecipientPubkey` (Lightning Address → Nostr hex pubkey via NIP-05) and `resolveRecipientProfile` (pubkey → kind-0 metadata). Both fan out from `send()` when a melt target is set, so amount-entry, mint-select, melt-preview, and payment-request screens render "Pay <name>" with an avatar instead of an opaque lud16 string. Default implementations ship with the package (nip05.ts, recipient.ts); the wallet wires the SWR-cache- backed profile resolver in CocoPaymentUX via `parseRawMetadata`. Receive-side history headers gain a `showRecipientAvatar` opt-out so the new avatar doesn't replace the mint-quote / receive-token icon (those are self-receive flows with no counterparty). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…very acks Native BLE bridge rewrite (BitChatBLEBridge.swift): drops the standalone geohash helper, folds nostr-bridge cleanup into the main module, and emits a per-message delivery-status stream (sending → sent → delivered → failed). `sendBLEPrivateMessage` now takes a caller-supplied messageId so the JS layer can correlate acks back to the optimistic bubble. JS side adds a persisted `bitchatDmMessages` store (thread state keyed by 16-hex peerID — peerIDs aren't pubkeys, so they live alongside the nostr DM cache rather than inside it) and a `useBitchatDmContacts` hook backed by a UserDefaults-mirrored DM-history map so peers we've messaged survive app kills. `useSplitBillOrchestrator` and the geohash/network screens are updated for the new `sendBLEPrivateMessage(peerID, content, nickname, messageId)` signature. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…eed override `ChatBubbleMessage.deliveryStatus` grows from sending/sent to also include `'delivered'` (double-check glyph after counterparty decrypt-ack) and `'failed'` (warning glyph — typically a BitChat handshake that never completed). Nostr-based DMs still only ever surface sending/sent; the extra states fire from the new BLE delivery-status stream. `DmChatHeader` gains a `seed` prop so the header avatar can match in-thread bubble avatars when the row's identity isn't a pubkey (e.g. BLE peerID DMs). `ChatMessageBubble` is re-exported from the package index so feature screens can import it without reaching into the file. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s anim - `PullToAiRefreshControl` — a drop-in `RefreshControl` that navigates to the AI tab on pull. Replaces an earlier `Gesture.Fling()` wrapper that lost to inner scroll views. Wired into the Wallet, Feed, and (later commit) Contacts top-level lists. - `CurrencySwapperPill` — sat↔fiat toggle for the amount-entry screen, built on `BalancePill` chrome (`ctaLabel` mode) so the liquid/blur/flat variants stay aligned with the wallet header pill. Adds a `ctaLabel` rendering path to `BalancePill` and surfaces `ctaIcon` color in the flat variant. - `MintSelector` accepts `height` / `contentHeight` overrides so the amount-entry pill can render at a non-default size with correct inner padding. - `SovranTabBar` icons now spring-scale on press (88% on press-in, snap back via spring on press-out) instead of a static `surface-secondary` fill. Mirrors the iOS 17 tab-bar feel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…emo mode ContactsScreen now merges three sources into the Recent / All tabs: NIP-17/NIP-04 nostr contacts, accepted Marmot DM counterparties, and persisted BitChat BLE-DM peers. BLE rows live in a separate namespace (`ble:<peerID>`) because peerIDs are 16-hex BLE identifiers, not Schnorr pubkeys, and route to the BLE DM screen instead of the nostr one. The ContactRow API adds a matching `bleIdentity` constructor. `mockDataStore` is extended with a small curated set of mock contacts (real npubs decoded to hex once, hand-authored threads, kind-0 metadata seeded into the SWR cache). `useRecentContacts` and `UserMessagesScreen` short-circuit on mock pubkeys when `mockMode` is on so the demo flow has real-looking conversations without ever publishing to the network. Also wires `PullToAiRefreshControl` into both list variants in ContactsScreen so the AI pull works there too. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The avatar fade (600 → 320ms) and the verified-badge spring (delay 300 → 80ms, snappier damping/stiffness) felt sluggish after the rest of the screen had landed. New timings make the badge feel attached to the avatar rather than a delayed afterthought. Adds an opt-in OK-badge override (`okBg`, `okIcon`, `okOutline`) so the verified-mint marker stays green even though the app-wide `success` token is now blue. The override draws a solid green-400 disc with a green-100 checkmark and a background-tinted ring, matching the seam between the badge and the avatar. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DM and AI chat surfaces migrated to LegendList in earlier commits (1c825ff, 4c07290). The gifted-chat patch hasn't applied to any runtime code since — patch-package emits a warning on install — so removing it cleans up the install log. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add deterministic NIP-05 resolver coverage, including the real odell@primal.net response shape, plus machine and screen-action tests for recipient pubkey/profile propagation. Security-impact: low Touches-keys: false
Replaces the two-button Equalize/Rebalance footer with a labeled CircleActionButton row (Split / Reset / Focus) above a single Next CTA, matching the wallet home secondary-action treatment. Adds mirrorBalances and concentrateOnPrimary helpers to the distribution store so all three actions write a deterministic 10,000bp split. Also tidies the per-mint item: Min on the left / Max on the right with icons sharing the label color, and wraps the help copy in <Card variant="info"> with clearer wording. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Queue the pull-to-AI navigation while the user is still dragging and commit it from the scroll release callback. Wire the release handlers through the wallet, feed, and contacts scroll hosts so the refresh threshold no longer switches tabs mid-gesture. Security-impact: none Touches-keys: false
Add a persisted developer setting for White Noise visibility and expose it beside the existing mock no-glass control. Use the setting to hide White Noise from contact filters, request/contact rows, the setup banner, and the profile send menu until the developer toggle is enabled. Security-impact: none Touches-keys: false
Native bitchat singletons previously shared one keychain/storage namespace across profiles, leaking DM history and transport identity. Thread the active profile scope through startBLE/startNostr and recreate the native services on scope change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
Started as a fix for 12 user-reported wallet bugs (
fix/twelve-reported-issues); grew into a sustained drain of the rolling__audits__/*.jsoncorpus and then a deeper re-platforming pass — chat surfaces migrated to LegendList with iOS 26 keyboard fixes, full light-theme support, Liquid Glass capability variants, adefineVariantsUI port, exhaustive lint/type hardening (10+ new bans),shared/styles/tokens.ts+__rules__/, ananalyze-structuretooling overhaul with CI structural-health, NPC (NPubCash) host + claim flow, secure-store boot-FaceID fix, broader deeplink ingress, drawer X-style header + scene corners, the popup-registry collapse, a full BitChat BLE bridge rewrite (DM message store + delivery acks + per-profile keychain/identity scoping), NIP-05 recipient identity enrichment threaded through the send/payment flow, and a batch of wallet-surface UX upgrades (pull-to-AI refresh, currency-swap pill, tab-bar press anim, mint balance-split footer rework, mint info reveal polish). 524 commits, ~70 audit findings shipped end-to-end.Why
The branch name is stale. Three pressures stacked onto the original 12-bug ticket:
__audits__/corpus had accumulated dozens of unaddressed Critical/High/Medium findings — silent attacker-mint trust, on-boot crashes on Android, secrets leaking into log/storage dumps, dead null-guards, TOCTOU in key derivation, re-render storms from over-broad zustand selectors. Each was small in isolation but the un-cited backlog was load-bearing context for every new audit cycle. This PR drains it.shared/styles/tokens.ts, thedefineVariantscapability port, and the lint bans would have re-introduced the same debt.Splitting now would be pure churn — the slices are independently reviewable as commits. Sliced one finding (or one tight cluster) per commit per
codereview/fix.md.What changed
Grouped by area. One commit per slice; one
chore(audits)annotation per slice for the audit work. Headings call out the broad themes — see the Exhaustive commit list at the bottom for everything.Security / safety fixes
bitchat: scope BLE keychain + Nostr per-geohash identity to the active profile (no more cross-profile DM/identity leak through the singleton native services).mint: bound the rebalance-trust window — attacker-controlled intermediary mints can no longer be silently auto-trusted mid-rebalance (__audits__/12.jsonF-001). Canonicalise the trust-and-add path; restore Nostr pills for npub-publishing mints; makeswapStatusStorethe source of truth for the rebalance toast; bound mint-discovery fanout and guard late setState.wallet: closeBitcoinNearYoulocation-leak privacy seam.settings: redact bearer instruments + geolocation from the storage-dump export; swap legacy clipboard; cross-platform key import (Alert.prompt was iOS-only); gate-mode popup leak; stop recovery from permanently trusting backend-supplied mints.whitenoise: zero nsec on dispose; dropAnimatedEmoji's Google CDN fetch; bound inbox cold-start fetch; silence setup listener.logger: redact secrets insummarizeString, freeze deduped entries, drop module-load side effects; share core state across child loggers; capaiLogspan.logs: drop payment-value previews; narrow raw error objects.payments: validate untrusted external strings at the trust boundary.bitchat: platform-guard the nativerequireso Android JS bundle stops crashing at boot.nostr: verify NIP-17 seal sig and rumor id at unwrap boundary; hardensecureStorageseam (BIP-39 read-side validation, sha256 mnemonic hash, persistent key index); validate BIP-39 checksum at every mnemonic-store seam; refuse to overwrite a corrupt mnemonic; self-heal corrupt SecureStore caches.seed: keep debug mnemonic out of bundles; read master seed for reinstall.auth: tear out non-functional passcode gate.keys: require iOS biometric auth on secure-store reads (then later relax for boot-cascade — seesecure-storecorrectness).api-client: strip URL queries from logs; https-only mint info; relocate WS URL.security: scheme-validate untrustedLinking.openURLinputs.Correctness fixes
bitchat: full BLE bridge rewrite — dedicated DM message store (bitchatDmMessages), delivery acks routed back through the JS bridge, retry state surfaced on the message bubble.nostr: dedupe key derivation + single-flight mnemonic reads (one FaceID prompt per boot wave instead of three); centralise 64-char hex pubkey validator; consolidate nip17 onto shared impl.secure-store: drop biometric requirement on boot reads to stop FaceID cascades.popup: clearlastPayloadRefon close; stack "Choose how to pay" above camera route modal; tighten popup-area hygiene.nfc: honor UTF-16 NDEF text records; isolate transceive error mapping; own session lifetime in a deep module; consolidate NDEF write through canonical helper.payments: keep swap-toast gate engaged on mid-flight dismissal + re-pop terminal state; wiremockFailMelt/mockFailSendthroughcoco-payment-ux; reuse payment guard for DM send-money; stabilise relay-flush re-fire in contact-discovery hooks.npc(NPubCash): switch hostnpubx.cash → npub.cash; mirror eNuts host coverage; durable since-cursor; real claim flow.chat/ai: scopeuseAiSendstream + balance to a hook controller; preserve message history across dep churn; unstamp peer pubkey from own DM messages; stable keys + accurate item-size + single filter pipeline; unify keyboard-lift math across AI tab and modal-stack chats; extend delivery vocabulary todelivered/failedwith explicit avatar-seed override.splash: restore splash → QR morph; stop pre-mount native-splash hide.bitchat: preserve message history across dep churn; stop yanking shared geohash subs.android: wireCapsuleButtontaps; drop deadLiquidButtonViewprops.cashu: serialize manager init/cleanup via stored promises; closeawaitRestoreReadyrace + re-init Coco on profile switch.wallet: restoreMintSelectorandroid resolution.deeplinks: broaden URI ingress to match Minibits / Macadamia coverage.contacts: stop stale derived state from polluting virtualised lists; show reputation + followers on search rows via schemas v2; gate the white-noise contact behind the developer toggle; surface BitChat BLE peers in contacts + seed mock contacts in demo mode.stores: dropDOMExceptionusage in apiClient (Hermes lacks it); mint collision-safe local ids and roll back failed bitchat sends.split-bill: reconcile payment state via coco events, not screen-scoped polling.btcmap: bound details cache; reset on wipe; raise cluster cache cap.Feature work (wallet UX / payments / mint)
wallet: pull-to-AI refresh, currency-swap pill, tab-bar press anim.nav: defer the AI-pull shortcut until release-ready.mint: rework balance-split footer with explicit Split / Reset / Focus actions; snappier mint-info reveal with custom green OK badge.send: resolve recipient identity (NIP-05 + Nostr profile) inside the payment flow so payment confirmation shows the recipient's display name + avatar, not just hex; covered with a newcoco-payment-uxtest.transactions: swipeable month pager.Theming & light mode
theme: full light-theme support across chrome and content; retint success + green consumers to blue (then retint a subset back to green for done-state confirmation); neutral fiat pill; light-mode polish for QR card, bitcoin disc, receive tabs; center status pill on wallpaper preview cards; download wallpaper before flippingthemeStoreon apply.tokens: introduceshared/styles/tokens.tsfor non-color design tokens (spacing, radius, alpha, duration, zIndex, iconSize, hitSlop, shadow, minTouchTarget); rename color-shaped variables to mirror semantic source; consolidate brand colors (BITCOIN_ACCENT); replace hardcoded brand hexes with theme tokens; drop module-scope and hardcoded colors that survive theme flips.offline: retint YOU ARE OFFLINE banner to blue.wallpaper: breakthemeStore↔wallpaperStorecycle and drop vestigial provider; delete deadapplyAlbum+ seeded-shuffle distribution path.Chat surfaces
ChatScreenwrapper to consolidate DM surfaces; collapse AI surface onto sharedChatScreen; collapse duplicateformatTimestampinto shared helper; extractuseChatSurfacePerfLogger; stabilise chat-surface render lifecycle.Drawer & navigation chrome
nav: validate deep-link route params with zod; close remaining route-boundary param-validation gaps; consolidate hex64 boundary schemas onto canonical primitive; consolidate drawer route file onto one anchor + segment match; consolidate token/quote route wrappers onto canonical shells; liftpaymentRequestheader overrides into the layout; drop redundantas anycasts on typed-route pathnames.Capability / Liquid Glass port (
defineVariants)defineVariantsplumbing.CapsuleButton,BalancePill,FiatCurrencyPill,CircleActionButtontodefineVariants.useCapabilities+useLiquidGlassModifiersat inline sites.style(ui): cohere wallet surfaces and fix Liquid Glass dispatch.Lint / type hardening
typescript-eslintrules across full repo.console.*in favour of the scoped logger.fetchJsonwrapper instead of rawfetch.formatDateoverDate#toLocale*String.openExternalUrlwrapper instead of rawLinking.openURL.react-nativeAnimated in favour of Reanimated v4.borderWidth: 0.5; migrate the 4 existing call sites.eslint-plugin-react-compilerat warn level.eslint-plugin-react-perfat warn level.no-restricted-importstap and clipboard bans.tsc noImplicitOverride.react-nativePressable/TouchableOpacityimports.Pressable / shared primitives
Pressableprimitive that auto-guardsonPress(single-flight, double-tap re-entry guard).Pressable/TouchableOpacityto shared primitives.Button+ButtonHandlerguards throughuseSingleFlight; extend to Nostr publish + non-button surfaces.TouchableOpacity's behaviour; rewireButtonto use it; drop haptics duplication.TouchableOpacity; narrow lint rule.Refactors / hygiene (zustand selectors, store cycles, dead code)
stores: version+migrate+zod-merge baseline for persisted stores (extended to remaining 17 stores); collapseclearAllDataintoclearPersistedStore; redact catch errors and route logs through scoped child loggers; collapse zustand persist boilerplate ontopersistConfighelper; close profile-scoped persist hygiene gaps; collapse double-scoped pubkey records in profile stores; organise persist migrations as append-only step chain; indexscanHistoryStoreby transactionId; narrow zustand selectors and subscriptions across the app; scope zustand subscriptions to primitive results.feed(6 slices): harden image-overlay relay-trust seam; extractuseThread; tighten NIP-10 thread parsing; unify worklet→JS API onscheduleOnRN; narrow image-overlay context; bound untrusted relay content at the parser/payment seam; drop dead handlers and no-op action refs; inlineFeedFiltersintoFeedScreen; splitnostr/shared.tsxkitchen-sink into 9 focused modules; consolidate home/user feed parsers into shared helper.popup: collapse 58 named popup wrappers behindstaticPopup/paramPopupregistry; collapse per-domain popup catalog into the barrel; colocate engine and bridge intopopups/subfolder; delete 30 unused popup helpers and slim barrel; consolidate popup engine and unify status-toast nav; collapse popup wrappers behind a single factory; consolidate popuponCloseseam onto typed close-reason; tint failed Menu rows + route reserved-proofs picker throughactionMenuPopup.cashu/coco-payment-ux: tighten presentation primitives; harden fail-safety at boundaries; drop type-laundering casts at coco seam; hoist Manager-internals seam out ofcoco-payment-ux; makeamountEntryconfig reactive; scope confirm-handler async state; tighten coco-event handler discipline; type history-entry seam indefaultOps; harden react/tracker entry points; consolidate fetch primitives + drop laundering casts; inject logger at seam, drop raw console; groupCocoPaymentUXProviderprops by concern; dropoperation:anycasts; dropas-anySDK fallbacks in split-bill + rebalance flows; collapse Manager private-field reach-ins behind one seam; delete deadCocoManagermethods and route reach-ins through one seam; bound external-server calls; cover the new recipient-identity enrichment with a dedicated test.logger: split god-module; close any-cast cleanup; drop module-load side effects; share core state across child loggers; cap aiLog span.ui/ shared primitives: extractStatusToastshell from payment and swap toasts; consolidate toast scaffolding behindToastSlab; consolidate section vocabulary; collapse stack primitives onto native flex gap; consolidate map subtree onto canonical primitives; forwardexpo-imageprops through the image primitive; subscribe to viewport viauseWindowDimensions; un-export reflexive Props/types in shared primitives; gateCardononPress, narrowButton/Spinnerany, plug View prop leak; drop redundantReact.memowrappers; wire accessibility labels throughButton+ListRow; close shared/ui a11y rollout punch list;SelectableCheckprimitive; align fiat pill chrome withCircleActionButtonfamily.whitenoise: collapse lifecycle escape hatches; inline serialization helpers into single consumer; consolidate provider seam; zero nsec on dispose.theme: migratefeatures/themeStyleSheet to uniwind className.map: splitMapScreenorchestrator into camera + markers hooks; tightenexpo-mapsseam typing; resolve offline + camera context abovecoco-payment-ux.mint: liftMintRebalancePlanScreenorchestration intouseMintRebalanceOrchestrator; canonicalise the trust-and-add path; typecashu-tsmelt seam; typemintInfoagainstcoco-core/cashu-tsseam; collapse duplicate audit-info helper into one canonical module; routefetchMintInfothroughfetchJson; SWR cache above coco's mint-info HTTP refresh; route audit-score derivation throughtransformAuditData.transactions: extractHistoryEntryTimelinestate machine + clear cast/dead-code/silent-fallback debt; drop account JSON-blob route param; add swipeable month pager toTransactionsScreen; stabilise derived collections in mintSelect + split-bill picker.chat/contacts: tightenContactsScreenlist-item types and drop typed-route casts; harden relay→UI trust boundary in contacts; liftuseNostrProfiletoshared/hooks; consolidate participant-status helpers; inlineSEARCH_FILTERS_HEIGHTinto filter components.nfc: own session lifetime in a deep module.camera: consolidate permission gateway + tighten lifecycle hygiene.routstr: collapse store interface and stop double-persisting active session view.splitBill: extractSlideToConfirmprimitive (also used inSettingsRecoveryScreen/DeleteScreen); thread BitChat profile scope through BLE send.crypto: drop the localnutpatchpackage, depend on publishednutpatch@^1.0.0.hygiene: drop the unusedopenaiSDK; drop the unusedreact-native-gifted-chatpatch; dedupe polyfill imports; retirefeatures/bitchatStyleSheet; trim unused exports + delete dead helpers (multiple slices via knip); delete unreachable feature surfaces; retire unreachable wallet-health feature; collapse single-account wallet pager.bitchat: full BLE bridge rewrite + dedicated DM message store + delivery acks; per-profile keychain + per-geohash identity scoping; rename overloadedChatMessage.senderPubkey; delete orphan parallel chat implementation.boot: move module-loadinitLogcalls below imports.providers: collapse migration gates ontoInitializationGate; drop dead splash UI from initialization provider; resolve offline + camera context abovecoco-payment-ux.receive: tighten screen seam — drop dead prop, inline shallow hook, type-driven state.screens: adoptScreenandBottomButtonsin whitenoise setup; hoist render-body logs into effects; use scoped loggers; resolve reported wallet issues.animation: migrate three feature screens from legacy RN Animated to Reanimated v4.payments: carryrecipientPubkeythrough chat send-money seam; resolve recipient identity (NIP-05 + profile) inside the send flow.nav: centralize route-boundary schema primitives.hooks: hide render-time ref mirrors behinduseLatestRef; narrow payment-status subscribers to event payloads; tighten payments-feature contact-discovery hooks.net: route raw fetches throughfetchJson+ abort + zod.storage: unify whitenoise namespaces under one envelope.e2e: drop legacy yaml test runner from log-doctor.Tooling & docs
analyze-structure): expand with depth, smell, history, and llm reports; add structural-health score; default to verbose with opt-out flags; add--focusfilter; split into metrics + extract modules; rewrite README; fold lookalikes into subcommand; track re-export edges and platform variants; consolidateanalyze-structure/lookalikes/log-doctorintocodereview/; correct re-export and aliased-import name extraction; exclude internal tooling andpackages/*/libfrom walks; extract WDA primitives (real cycle fix); break log-doctor static cycle viacreateRequire.structural-healthpostsanalyze-structure --llmsummary on PRs.__rules__/: introduce, relocate capability rules; add responsive-scaling rule; document native-module profile-scope pattern incaching.md; wireCLAUDE.md.AUDIT.md/TASK.mdwith concise audit and fix prompts; bind Matt Pocock skills to fixer phases and self-check; bias audit and fix prompts toward deletion over addition; land coco-payment-ux mission and guiding principles in fixer prompt.__audits__is now gitignored; annotations live on disk only.ignoreIssuesoverrides.ContactRowspec; remove legacy cursor and claude rule files; relocate capability rules; codereview docs cleanup.Settings / misc
settings: point Contact the Developer at@SovranBitcoin; scrub hygiene rot across settings screens.android: match iOS launcher icon (black S on white).buttons: let bottom button row size asflex | flex | fixed.wallet: add spacer between pending and confirmed transaction sections.mint-select: unblock Select Mint open and restyle inspect button.onboarding: tighten claim-username surface (abort, validate, redact).search: contacts search fixes.eas: pass--non-interactiveto dev and production build scripts.Audits
chore(audits): annotate completion statusper slice marks the corresponding__audits__/NN.jsonfinding(s) as complete/partial/stale.__audits__/is gitignored so the annotations live on disk only.Testing
yarn lintyarn type-checkyarn knipyarn prettybitchatnative-require crash) + launcher iconCashu / Lightning / Nostr specific
__audits__/12.json,__audits__/24.json); SWR mint-info cache; trust-and-add canonicalisation; mint-discovery fanout bound; balance-split footer rework__audits__/54.json,__audits__/55.json); contacts relay seam; image-overlay relay trust; NIP-17 seal sig verification; nip17 consolidation; secureStorage hardening; BIP-39 checksum at every seam; per-geohash NIP-17 identity derivation scoped to the active profile in the BitChat Nostr bridge; NIP-05 recipient resolution in the send flowexpo-secure-store—NostrKeysProvider+shared/lib/nostr/secureStorage.tssingle-flight; iOS biometric requirement re-tuned for bootcoco-payment-ux— extensive refactor (see above); recipient-identity enrichment + test; does not touch the upstreamcoco/checkoutcoco-payment-uxexternal-server bounding; mock failure wiringBreaking changes
None expected. Store public surfaces preserved; the
themeStore↔wallpaperStorecycle break is internal. Removed exports were dead (knip-flagged). Thenutpatchswitch from a local package to a published^1.0.0requiresyarn installafter pull; the API surface is unchanged.Rollout
Standard. No remote-config flag; no schema migration. Persist migrations are append-only step chain — existing users will run through them on first boot. The BitChat profile-scoped keychain takes effect on first profile-aware
startBLE/startNostrcall — pre-existing single-namespace keychain entries are left in place and ignored (no migration required; native services derive fresh per-profile keys).Linked issues
Refs the rolling
__audits__/*.jsoncorpus, specifically findings in:02, 12, 13, 14, 16, 17, 20, 23, 24, 25, 26, 27, 31, 32, 33, 34, 36, 37, 41, 42, 43, 44, 46, 47, 48, 49, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63and follow-on slices through ~70 total findings. Also addresses the original 12 user-reported wallet bugs.Reviewer notes
codereview/fix.md. Reviewing commit-by-commit is much more tractable than the squashed 524-commit diff.fix/twelve-reported-issuesis stale; the work expanded as the audit corpus grew and as the iOS 26 / theming / capability porting / BitChat rewrite needs surfaced.defineVariants), lint hardening, andshared/styles/tokens.tslandings are load-bearing for the light-theme work — review them in roughly that order.Self-review checklist
openai; one swapped — localnutpatch→ publishednutpatch@^1.0.0)coco/submodule changes__audits__/54.json+55.jsonand follow-on findingsconsole.log, no newany, no commented-out code introduced (now enforced by lint)Exhaustive commit list
Newest first. Audit-annotation commits (
chore(audits): annotate completion status) are interleaved between feature commits and are omitted below for brevity; the feature commit always precedes its annotation.BitChat / contacts (recent)
0704f2b6fix(bitchat): scope BLE and Nostr identity to active profile9cf8bf99fix(contacts): gate white noise behind developer toggle05dee3bafeat(contacts): surface BitChat BLE peers and seed mock contacts in demo moded05176farefactor(bitchat): rewrite BLE bridge + add DM message store and delivery acksdb4a46b7fix(bitchat): platform-guard native require so Android bootsa8a47da7fix(bitchat): preserve message history across dep churnc55c7081fix(bitchat): unstamp peer pubkey from own DM messages47786d07refactor(bitchat): dedupe message merge, drop dead diagnostic interval273d61e3refactor(bitchat): clean bitchat-module surface and rename overloaded ChatMessage.senderPubkey52d0d887refactor(bitchat): delete orphan parallel chat implementationbe6e781erefactor(hygiene): consolidate brand colors and retire features/bitchat StyleSheetWallet UX / mint UX (recent)
c6a31b44feat(mint): rework balance-split footer with Split/Reset/Focus actionsb0b86dd4fix(nav): defer AI pull shortcut until release478731c6feat(wallet): pull-to-AI refresh, currency-swapper pill, tab-bar press anim9284b06cfix(mint): snappier info reveal + custom green OK badgeSend / payments (recent)
adfca416feat(send): resolve recipient identity (NIP-05 + profile) in payment flow8ae15e4atest(coco-payment-ux): cover recipient identity enrichmentChat delivery polish (recent)
75c10939feat(chat): extend delivery vocabulary to delivered/failed + avatar seed overrideTheme follow-ups (recent)
9fef8e2afix(theme): retint success/done surfaces from blue back to greenHygiene (recent)
a1b9ecf1chore: drop unused react-native-gifted-chat patch0fd9e885refactor(crypto): drop local nutpatch package, depend on published nutpatch@^1.0.0iOS 26 / chat / LegendList
1c825ff0refactor(chat): migrate DM surfaces to LegendList, fix iOS 26 keyboard blur962151e9fix(chat): unify keyboard-lift math across AI tab and modal-stack chats8bf317e3fix(ai): drop chrome changes bundled into LegendList port4c07290efix(ai): port chat to LegendList to bypass iOS 26 FlatList dime9e0ce0ffeat(ai): tile pattern wallpaper behind chat surfacebeb85ee7fix(ai): adapt composer bottom inset to tab-bar pathb0ba7ba6feat(chat): migrate DM + AI surfaces to react-native-gifted-chatd1ce9809refactor(chat): collapse AI surface onto shared ChatScreen + Button compact size189f8f9brefactor(chat): extract ChatScreen wrapper to consolidate DM surfacesd129b8e6refactor(chat): unify DM surfaces on shared bubble + header3e95b82brefactor(chat): collapse duplicate formatTimestamp into shared chat helper533a0e56refactor(chat): extract useChatSurfacePerfLoggerd27c8310perf(chat): stabilise chat-surface render lifecycleLiquid Glass composer (chat)
cae747fcrefactor(chat): SwiftUI TextField inside the liquid-glass composer bubble18114416fix(chat): let taps in input padding reach the SwiftUI press animation1f5e23d5feat(chat): input is also a glass button + 2pt more breathing roomd6bb1ce9Revert "fix(chat): trust buttonStyle('glass') for animation"7325c970fix(chat): trust buttonStyle('glass') for animation; input is also a glass button9a81bcadfix(chat): single-ring glass buttons that still animatef038ae7ffix(chat): single glass circle per button (drop double-ring on press)1591197ffix(chat): drop tints on the composer glass materiala17160f2fix(chat): drop the press-glow on the composer glass buttons292e1ebffix(chat): stop the glass shapes blending into one persistent blob8b8f68a7fix(chat): bouncy scale-in for send button via SwiftUI-side animation83209681fix(chat): wire animation modifier so the glass-morph actually animatesfe08b2c2fix(chat): liquid-glass composer morph animation + content-sized inpute18b03a0feat(chat): liquid-glass composer for bitchat / nostr-DM / whitenoise21b422cdfeat(chat): unify sending → sent delivery indicator across all surfacesc803c496Revert "refactor(whitenoise): drop AnimatedEmoji's Google CDN fetch…"Drawer / navigation chrome
890d169efeat(drawer): device-radius scene corners, toggle haptics, wider swipe edgea491da73feat(drawer): rounded scene edge with theme-aware hairline outline906559f1feat(drawer): X-style header, flat surface, unified menu scrimd17107abfeat(ui): use mesh gradients for drawer surfacesf664a72dStyle tab bar with theme colors and rounded cornersLight theme & tokens
1cea90d5fix(offline): retint YOU ARE OFFLINE banner to blue63304562feat(theme): retint success + green consumers to blue, neutral fiat pill99eed5fefix(theme): light-mode polish for QR card, bitcoin disc, and receive tabs6a2ceb4cfeat(theme): light theme support across chrome and content67d685e1fix(theme): center status pill on wallpaper preview cardsf27222fafix(theme): download wallpaper before flipping themeStore on apply874dbf67refactor(theme): rename color-shaped variables to mirror semantic source873085ffrefactor(theme): introduce shared/styles/tokens.ts for non-color design tokens75802662fix(theme): kill module-scope and hardcoded colors that survive theme flips5bc55235refactor(brand-colors): consolidate raw '#F7931A' into BITCOIN_ACCENT token72725a99style(theme): drop hardcoded #3B82F6 in theme pickerb60a2aa0refactor(ui): replace hardcoded brand hexes with theme tokensf7e3b4d1refactor(theme): delete dead applyAlbum + seeded-shuffle distribution path5a1c68ebrefactor(theme): break themeStore↔wallpaperStore cycleea824d4arefactor(theme): migrate features/theme StyleSheet to uniwind className7a86e8fefix(wallet): align fiat pill chrome with CircleActionButton familyCapability / Liquid Glass port
aec70d8drefactor(ui): finish capability migration + document the rulesf08b3db5refactor(ui): adopt useCapabilities + useLiquidGlassModifiers at inline sitesff6fb4dcrefactor(ui): migrate CircleActionButton to defineVariants (selector form)025ef694refactor(ui): migrate BalancePill + FiatCurrencyPill to defineVariants851f163brefactor(ui): migrate CapsuleButton to defineVariants9e26d0f8refactor(ui): introduce capability port + defineVariants plumbing0a7a2d6fstyle(ui): cohere wallet surfaces and fix Liquid Glass dispatchNPC (NPubCash)
086fa37ffix(npc): mirror eNuts host + durable since-cursor + real claim flow8b16e5b6fix(npc): switch NPubCash host from npubx.cash to npub.cashSecure store / boot
25d8a814fix(secure-store): drop biometric requirement to stop FaceID cascades on boot0a85163cfix(nostr): dedupe key derivation and single-flight mnemonic reads634603c3security(keys): require ios biometric auth on secure store reads959fe8fdfix(nostr): harden secureStorage seam — read-side BIP-39, sha256 mnemonic hasha8b76314fix(nostr): refuse to overwrite a corrupt mnemonic in ensureMnemonicExistsee158241fix(nostr): validate BIP-39 checksum at every mnemonic-store seamd170376bfix(nostr): self-heal corrupt SecureStore caches and add input validationaea594derefactor(nostr): consolidate secure-storage seam onto canonical adaptere2be9ac4refactor(nostr): centralise 64-char hex pubkey validation6df46a86refactor(nostr): consolidate nip17 onto shared canonical impl112885f5fix(nostr): verify NIP-17 seal sig and rumor id at unwrap boundary1b8f8d9afix(seed): keep debug mnemonic out of bundles, read master seed for reinstall997a47bbrefactor(auth): tear out non-functional passcode gateb291b0b4refactor(camera): consolidate permission gateway + tighten lifecycle hygieneDeeplinks / URI ingress
5a676b4afix(deeplinks): broaden URI ingress to match Minibits/Macadamia coverage3c5002bbfix(security): scheme-validate untrusted Linking.openURL inputs7df64614refactor(nav): validate deep-link route params with zod0dddea5frefactor(nav): validate payment-flow deep-link params with zod17e87fd7refactor(nav): close remaining route-boundary param-validation gaps5b3c8fa6refactor(nav): consolidate hex64 boundary schemas onto canonical primitive70d209e6refactor(nav): centralize route-boundary schema primitivesLint / type hardening
ff9d4dd8chore(lint): clear 369 mechanical eslint suppressions (624 → 255)e85aedb8chore(lint): round out the no-restricted-imports tap and clipboard bansc71d3968chore(types): enable tsc noImplicitOverride04605aa8chore(lint): add eslint-plugin-react-perf at warn level2fc684d6chore(lint): add eslint-plugin-react-compiler at warn levelb1596039chore(lint): ban borderWidth: 0.5; migrate the 4 existing call sites634f6bfechore(lint): ban legacy react-native Animated in favour of Reanimated v488952c01chore(lint): require openExternalUrl wrapper instead of raw Linking.openURL739e9455chore(lint): require formatDate over Date#toLocale*String1465878achore(lint): require fetchJson wrapper instead of raw fetch434cffccchore(lint): ban raw console.* in favour of the scoped logger09890a8fchore(lint): ban hardcoded hex colors outside the canonical theme homes4aa74bdbchore(lint): land type-aware typescript-eslint rules across full repobb0a57aachore(ci): forbid raw react-native Pressable / TouchableOpacity importsPressable / shared primitives
3fd3c1ffrefactor(ui): delete shared TouchableOpacity, narrow lint rule2e36b076refactor(ui): migrate TouchableOpacity callers to shared Pressable2878c0f3refactor(ui): rewire Button to use shared Pressable, drop haptics duplication047fa09frefactor(ui): unify shared Pressable to absorb TouchableOpacity's behaviour8fd85284refactor(ui): migrate raw RN Pressable/TouchableOpacity to shared primitivesb1ba1d07refactor(ui): add shared Pressable primitive that auto-guards onPressea42808drefactor(ui): guard shared TouchableOpacity with useSingleFlight5f135f9crefactor(ui): route Button + ButtonHandler guards through useSingleFlight4249c89brefactor(ui): extend single-flight to nostr publish + non-button surfaces13f9fa9frefactor(ui): guard interactive primitives against double-tap re-entryPopup
8fb0ec61fix(popup): stack "Choose how to pay" above the camera route modalc87c7055fix(popup): tint failed Menu rows + route reserved-proofs picker through actionMenuPopup428494d2fix(popup): clear lastPayloadRef on close and tighten popup-area hygiene10648248refactor(popup): collapse 58 named popup wrappers behind staticPopup/paramPopup registry0423e38arefactor(popup): collapse per-domain popup catalog into the barrel6bcb5960refactor(popup): colocate engine and bridge into popups/ subfolderce3d02cerefactor(popups): delete 30 unused popup helpers and slim barrelca2ecf15refactor(ui): consolidate popup engine and unify status-toast navf0f53d44refactor(ui): collapse popup wrappers behind a single factory61fe91darefactor(ui): consolidate popup onClose seam onto typed close-reasonCashu / coco-payment-ux
cd71082dfix(cashu): serialize manager init/cleanup via stored promisesb6c9b535fix(coco): close awaitRestoreReady race + re-init Coco on profile switch27bec5d9fix(split-bill): reconcile payment state via coco events, not screen-scoped pollinge739f165fix(payments): wire mockFailMelt/mockFailSend through coco-payment-ux487524ddfix(payments): keep swap-toast gate engaged on mid-flight dismissal5cd2ad63refactor(mint): type cashu-ts melt seam, drop residual any in coco bootstrap4973b2dbrefactor(mint): type mintInfo against coco-core / cashu-ts seam5e762cdbdocs(coco-payment-ux): match grouped CocoPaymentUXProvider props772c2f4brefactor(coco-payment-ux): group CocoPaymentUXProvider props by concern7e064c9frefactor(providers): harden coco-payment-ux react/tracker entry points338e2bb9refactor(cashu): tighten coco-event handler discipline at payment seam23658223refactor(cashu): scope confirm-handler async state in coco-payment-uxd7d97184refactor(cashu): make amountEntry config reactive in coco-payment-ux58f1e641fix(cashu): tighten coco-payment-ux presentation primitivesd5dcbfaarefactor(cashu): tighten snapshot discipline in coco-payment-ux machineryb5ca041afix(cashu): harden coco-payment-ux fail-safety at boundaries1f578d8erefactor(cashu): drop type-laundering casts at coco-payment-ux seamb17f8dcdrefactor(cashu): hoist Manager-internals seam out of coco-payment-uxdb64864erefactor(cashu): type history-entry seam in coco-payment-ux defaultOpsc20e3e22refactor(hooks): narrow payment-status subscribers to event payloadsf5007112refactor(hooks): tighten payments-feature contact-discovery hooks31fde611fix(providers): resolve offline + camera context above coco-payment-ux27ea51ecrefactor(cashu): drop operation:any casts on coco event payloads7840fa4erefactor(cashu): drop as-any sdk fallbacks in split-bill + rebalance flowsd40a202drefactor(cashu): delete dead CocoManager methods and route reach-ins through one seamf27ea8e8refactor(lightning): bound external-server calls in coco-payment-ux8a8cc833refactor(cashu): collapse Manager private-field reach-ins behind one seam6f3b95dfrefactor(cashu): inject logger at coco-payment-ux seam, drop raw consolea9d17a77refactor(cashu): consolidate fetch primitives + drop laundering casts at coco seam2dd02c3crefactor(whitenoise): inline serialization helpers into single consumer5874a0d2perf(mint): add SWR cache above coco's mint-info HTTP refreshMint / payments / wallet
4c576b7dfix(wallet): add spacer between pending and confirmed transaction sectionsf487671bfix(mint-select): unblock Select Mint open and restyle inspect button86c0a4a7fix(mint): bound the rebalance trust window so attacker mints can't linger0d36402erefactor(mint): canonicalise the trust-and-add path504ea4aefix(mint): make swapStatusStore the source of truth for rebalance toast29cee9d7fix(mint): restore Nostr pills for npub-publishing mints; consolidate extractor7c721ea8fix(mint): bound mint-discovery fanout and guard late setState0db0a74arefactor(mint): collapse duplicate audit-info helper into one canonical module57a4a928refactor(mint): route fetchMintInfo through fetchJsonef79f4berefactor(mint): route audit-score derivation through transformAuditDatad7b9d604refactor(mint): lift MintRebalancePlanScreen orchestration into useMintRebalanceOrchestrator hookcdb5515dfix(wallet): restore MintSelector android resolution8e272397refactor(payments): validate untrusted external strings at trust boundarieseee1fa58feat(mint): unified swap status toast with per-leg progress532d31c1feat(payments): carry recipientPubkey through chat send-money seamcad2951aperf(payments): stabilise relay-flush re-fire in contact-discovery hooks1ba8b9a3fix(send): reuse payment guard for DM send money research69a595b5fix(screens): resolve reported wallet issuesb503a11cfix(settings): stop recovery from permanently trusting backend-supplied mintsStores / persist
a43f3c19refactor(stores): organise persist migrations as append-only step chainab19c27erefactor(stores): close profile-scoped persist hygiene gaps77d94677perf(stores): narrow zustand selectors and store subscriptions02c3e0e2refactor(stores): scope zustand subscriptions to primitive resultsb2f688c8refactor(stores): collapse double-scoped pubkey records in profile storesa7d9e98erefactor(stores): collapse zustand persist boilerplate onto persistConfig helperddca6c40refactor(stores): collapse scanHistoryStore — drop dead processed field, normalise dedupe2e6fd40brefactor(transactions): index scanHistoryStore by transactionId80b4f25drefactor(stores): drop dead clearAllData and zero-callsite store actionsb0a4e386refactor(stores): collapse clearAllData into clearPersistedStore520c57a1refactor(stores): extend version+migrate+zod-merge to remaining 17 stores95c14ea3refactor(stores): version+migrate+zod-merge baseline for persisted stores20662da9refactor(stores): redact catch errors and route logs through scoped child loggers0836024afix(stores): route remaining hand-rolled local ids through mintLocalId3f9a0557fix(stores): mint collision-safe local ids and roll back failed bitchat sends6050f846fix(contacts,feed): stop stale derived state from polluting virtualised lists1317d126fix(stores): drop DOMException usage in apiClient; hermes lacks it498457c1refactor(stores): thread AbortSignal and default timeout through apiClientf71fb859fix(api-client): strip URL queries from logs, https-only mint info, relocate WS URLFeed / contacts / nostr
a9548f89search fixes.cbcd07b3fix(contacts): show reputation + followers on search rows via schemas v27fc34d4erefactor(contacts): harden relay→UI trust boundary, narrow types, dedup helperscb8ca4afrefactor(contacts): tighten ContactsScreen list-item types and drop typed-route casts383ad950refactor(contacts,feed): inline SEARCH_FILTERS_HEIGHT into filter components62cbbe0crefactor(feed,user): lift useNostrProfile to shared/hooks35c84169refactor(feed): split nostr/shared.tsx kitchen-sink into 9 focused modules1ab29372refactor(feed): inline FeedFilters into FeedScreen47bba4b2refactor(feed): unify image-overlay worklet→JS API on scheduleOnRN1e5167bbrefactor(feed): drop dead handlers, no-op actions ref, and timestamp-sum cache-busterb6fa2133refactor(feed): bound untrusted relay content at the parser/payment seam0afa6165refactor(feed): narrow image-overlay context surface9c30a5b0refactor(feed): harden image-overlay relay-trust + drop dead branches in provider1d8625f5refactor(feed): extract useThread hook + tighten NIP-10 thread parsingc32e1623refactor(nostr): collapse home/user feed parsers into shared helper61441050refactor(nostr-cache): expose cache singletons instead of rebound methods4d36bf1erefactor(screens): retire routstr branch from user messages screenWhitenoise / bitchat
e9e8a9bbfix(whitenoise): zero nsec on dispose, consolidate provider seamdc03539frefactor(whitenoise): bound inbox cold-start fetch and silence setup listener38ab13f3refactor(whitenoise): drop AnimatedEmoji's Google CDN fetch and render brand/picker emojis locally515b558brefactor(nostr): collapse whitenoise lifecycle escape hatches69c0d5a8refactor(screens): adopt Screen and BottomButtons in whitenoise setupTransactions / receive / send
718d3a60feat(transactions): add swipeable month pager to TransactionsScreen34172866refactor(transactions): drop account JSON-blob route param14c6447frefactor(transactions): extract HistoryEntryTimeline state machinec861ec5dfix(transactions): stable keys, accurate item-size, single filter pipeline55069422perf(memo): stabilize derived collections in mintSelect + split-bill picker1a53f930refactor(receive): tighten screen seam — drop dead prop, inline shallow hooka146f11erefactor(send): rename Sovran provider, drop redundant hook re-exportsfdc0e26crefactor(settings): extract SlideToConfirm primitive from DeleteScreen and SettingsRecoveryScreenbbac6c25refactor(splitBill): consolidate participant status helpersLogger / logs
7409349crefactor(logger): split god-module + close any-cast cleanup + cap aiLog spanf576c41afix(logger): redact secrets in summarizeString and freeze deduped entriesaa4b2d6frefactor(logger): drop module-load side effects + production hot-path overhead5dec1cd6refactor(logger): share core state across child loggerse56fd460refactor(boot): move module-load initLog calls below imports62f657edrefactor: scope domain logs through the registered child loggers14b88f86refactor: route scoped loggers through the shared registryc8c9fb55refactor(screens): hoist render-body logs into effects; use scoped loggers03c27f75refactor(logging): drop render-body log calls from screen componentsdded751dfix(logs): drop payment-value previews and narrow raw error objects6aba6375refactor(ui): drop deprecated Screen alias for the logger Log componentSettings / onboarding / android
d50b14f5fix(settings): point Contact the Developer at @SovranBitcoin0ef81f77fix(android): match iOS launcher icon (black S on white)c87f80fafix(settings): redact bearer instruments + geolocation from storage dump62ed9d27fix(settings): cross-platform key import + gate-mode popup leakeb091ae7fix(settings): scrub hygiene rot across settings screens70e5e2dffix(onboarding): tighten claim-username surface (abort, validate, redact)3333c31abuild(eas): pass --non-interactive to dev and production build scriptsNFC
8c9f087afix(nfc): honor utf-16 ndef text records and isolate transceive error mapping91c9ce00refactor(nfc): own session lifetime in a deep moduleb4f7e1d1refactor(nfc): consolidate ndef write through canonical helperMap / btcmap / privacy
91ae8cd2fix(btcmap): bound details cache, reset on wipe, raise cluster cache cap4bf501a7fix(wallet): close BitcoinNearYou location-leak privacy seam858cd895refactor(map): split MapScreen orchestrator into camera + markers hooksc41295c7refactor(map): tighten expo-maps seam typingAI
d24d2c83fix(ai): scope useAiSend stream + balance to a hook controllerRoutstr
d1e3a0a8refactor(routstr): collapse store interface and stop double-persisting active session viewSplash / boot
35009555fix(splash): restore splash → QR morph and stop pre-mount native-splash hidef3b9553drefactor(providers): collapse migration gates onto InitializationGatecc00f3darefactor(providers): drop dead splash UI from initialization providerShared UI primitives
8868f0d5fix(buttons): let bottom button row size as flex | flex | fixed0633beb7fix(ui): align ScrollEdgeFade default blur intensity with BottomButtons386cda15fix(ai): use full blur background to match feed9f78e3e2fix(ui): isolate ButtonHandler loading spinner per-button2010ea37fix(ui,map,whitenoise): cancel async writes on effect cleanupc267d441fix(ui): wire accessibility labels through Button + ListRow primitives349a81f5feat(a11y): close shared/ui a11y rollout punch list493ea508refactor(ui): drop redundant React.memo wrappers276f64c6refactor(ui): gate Card on onPress, narrow Button/Spinner any, plug View prop leak0c47df6arefactor(ui): un-export reflexive Props/types in shared/ui primitivesc5479815refactor(ui): extract StatusToast shell from payment and swap toasts810b35b9refactor(ui): collapse toast scaffolding behind a ToastSlab primitive451b09b2refactor(ui): consolidate section vocabulary onto canonical shared seamb9848350refactor(ui): collapse stack primitives onto native flex gap20844aa7refactor(ui): consolidate map subtree onto canonical primitivesd6a47204refactor(ui): extract protocol-prefix display formatting from DetailsList primitive3c5731derefactor(ui): SelectableCheck primitive, fix split-bill picker re-render22c63ae2refactor(icons): add internal: namespace, share animated-status shapese33fbd5erefactor(date): consolidate to shared/lib/date.ts with locale-aware formattingccd09dbdrefactor(ui): forward expo-image props through the image primitivedee4cf6frefactor(ui): subscribe to viewport via useWindowDimensions578b782erefactor(ui): tighten ButtonHandler onPress to drop no-op close argNav / routing
2ccf8c06refactor(nav): lift paymentRequest header overrides into the layoutf9a8a256refactor(nav): consolidate token/quote route wrappers onto canonical shells14fe6448refactor(nav): consolidate drawer route file onto one anchor and segment matchf4ce838drefactor(nav): drop wrapperas anycast on NetworkSheet route.replace6fd5bc37refactor(nav): drop redundantas anycasts on typed-route pathnamesHooks / net / storage
c2932a64refactor(hooks): hide render-time ref mirrors behind useLatestRefa45d64cdrefactor(net): route raw fetches through fetchJson + abort + zod257ed529refactor(storage): unify whitenoise namespaces under one envelopeAnimation
bc2ef580refactor(animation): migrate three feature screens from legacy RN Animated to Reanimated v4Hygiene / dead code
d11743a2chore(hygiene): drop unused openai SDK + dedupe polyfill importsf1b74edcchore(hygiene): un-export knip-flagged dead values across receive/send/whitenoise/map/nav/stores/persist/primitivesdfde903dchore(hygiene): un-export knip-flagged dead exports across ai/theme/transactionsae0bdcc3chore(hygiene): delete dead exports flagged by knip0ec1c058refactor(hygiene): trim dead exports and dup re-exports947dcfb7refactor(hygiene): trim unused exports + delete dead helperf455c53drefactor(hygiene): drop unused barrel re-exports and finish btcMapStore dead statec1b5a04crefactor: delete unreachable feature surfaces13fa9a3brefactor: delete dead public surfaces flagged by knip5aaa7747refactor: retire unreachable wallet-health feature5f13140drefactor(wallet): collapse single-account pager and prune dead module surface3f940423refactor(redux): inline deprecated action-type stringsTooling — analyze-structure / codereview / rules
f5f24202docs(rules): add responsive-scaling rulece853837docs: relocate capability rules to rules/, wire CLAUDE.md6f4f423aci(structural-health): post analyze-structure --llm summary on PRsbad0ae17chore(knip): drop obsolete ignore entries and dead ignoreIssues overridesb14bdb57chore(codereview): exclude internal tooling from analyze-structure walk5cb7671echore(codereview): exclude packages/*/lib build output from walksb0672467refactor(codereview): split lookalikes into its own module + scripts shims933601b3refactor(codereview): extract WDA primitives into wda.ts (real cycle fix)e91bff75refactor(codereview): break log-doctor static cycle via createRequirea3ecb695fix(codereview): track re-export edges and platform variants in analyze-structuref3bfd3c6fix(codereview): correct re-export and aliased-import name extraction45af0172refactor(codereview): split analyze-structure into metrics + extract modules; rewrite README5a3dfa45refactor(codereview): drop scripts/ shims, fold lookalikes into analyze-structure subcommand868dab69refactor(codereview): consolidate analyze-structure / lookalikes / log-doctor into codereview/489e16e8Add --focus filter to analyze-structuredd7c0a18refactor: default analyze-structure to verbose with opt-out flagse292fb09feat(scripts): expand analyze-structure with depth, smell, history, and llm reportsd76077b4feat(scripts): add structural-health score to analyze-structured9d86119feat(scripts): replace AUDIT.md/TASK.md with concise audit and fix prompts4a5e9987fix(scripts): bind matt pocock skills to fixer phases and self-checkcbe07dc3fix(scripts): force-add gitignored audits in fixer audit-status commitd73d260afix(scripts): land coco-payment-ux mission and guiding principles in fixer prompt04082c61docs(scripts): bias audit and fix prompts toward deletion over addition8f099b6bExpand review dimensions from 10 to 147ad0ec72Require Matt Pocock process-skill loads (Pass 0)a02ab56fAdd structural cross-cites and boy-scout rule2a05d9bcchore(skills): add zod, zustand, typescript and prompt-engineering skills1d156ba3chore(skills): add matt-pocock skill set and auditor protocolc3a3b417docs(rules): remove legacy cursor and claude rule filesd4b31479docs: drop stale ContactRow spece8ab80d9chore(audits): stop tracking audits findings44425ceedocs(codereview): drop audit-status commit + force-add machineryd01c9a22chore(scripts): drop orphaned palette exploration scripts and JSON snapshotsd7f2307crefactor(e2e): drop legacy yaml test runner from log-doctorStructure / knip cleanup
f50119ecchore(structure): delete dead code surfaced by knip + analyze-structure315180c0chore(structure): kill duplicate export names, fix re-export classifier9a0f0f72chore(structure): drop unused exports + resolve duplicate namesAudit-status annotations
chore(audits): annotate completion statuscommits (one per shipped slice).__audits__/is gitignored; annotations live on disk only.Branch base
38797b50fix: address 12 user-reported bugs across mint, send/receive, swap, split-bill, navigation (the original 12-bug commit that named the branch)