Adds 16 project skills (plus 3 runnable diagnostic scripts) that encode
the project's architecture contracts, backend capability matrix, storage
and migration protocols, build/run/debug runbooks, failure archaeology,
Lightning domain reference, and research discipline, so contributors and
AI coding sessions can operate at maintainer standard without rederiving
project knowledge.
All facts were verified against master c5fd094fb and adversarially
reviewed (factual/doctrine/usability passes) before inclusion. Also
updates .gitignore to share .claude/skills/ while keeping all other
.claude/ files (e.g. settings.local.json) local.
28 KiB
name, description
| name | description |
|---|---|
| zeus-failure-archaeology | Chronicle of Zeus's settled battles — every major investigation, revert, dead end, and multi-attempt saga as symptom → root cause → evidence (verified commit hashes/PRs) → status. Load BEFORE re-attempting anything that smells familiar, and whenever you hit these symptoms - stale wallet data appearing on a new iOS device / iCloud keychain leftovers; Fabric or New Architecture crashes (WalletHeader, keypad); infinite loading on app boot; settings corrupted or lost after a migration; "3600 hours" invoice expiry; Tor TLS certificate errors; navigation stack buildup or broken back-navigation; LDK Node crash on wallet deletion; embedded LND crash on restart (SQLite vs Bolt DB); Hermes regex stack overflow on QR scan; Cashu mint data shared between wallets; a merged fix that later vanished (revert). Also load before trusting any merged PR as final, before proposing a React Native or react-navigation upgrade, or when a commit hash/PR number from history needs context. |
Zeus Failure Archaeology
The chronicle of every costly investigation in this repo: what broke, why, what fixed it (or didn't), and the receipts. Purpose: no one re-fights a settled battle. Before you attempt a fix that feels obvious, search this file — someone may have shipped it and reverted it already.
All hashes below were verified with read-only git against master snapshot c5fd094fb ("Version bump: v13.1.3-alpha", 2026-07-02; no v13.1.3 tag exists yet — nearest tag is v13.1.2). Dates are commit author dates (git log --date=short).
When to use / When NOT to use
Use when:
- A bug matches a symptom in the index below — read the entry before touching code.
- You are about to propose a fix in storage/keychain, React Native upgrades, Tor TLS, navigation, LDK Node lifecycle, or embedded LND DB config — all have prior failed attempts.
- You need to know whether a merged fix is still live (this repo reverts fast — see MP-1).
- You need verified hashes/PR numbers for a historical claim.
Use a sibling instead when you need:
- The rules derived from these incidents (gating, review, migration sign-off) → zeus-change-control
- How storage/keychain/migrations work today and how to change them safely → zeus-storage-and-migrations
- Live debugging of a new symptom (triage tables, experiments) → zeus-debugging-playbook
- The currently-open node-lifecycle race campaign → zeus-node-lifecycle-campaign
- Design rationale and invariants (why the architecture is shaped this way) → zeus-architecture-contract
- Analysis technique (how to prove a race or migration is safe) → zeus-proof-and-analysis-toolkit
How to verify any claim in this chronicle
Jargon: a first-parent history walk follows only the main line of merge commits, skipping the internal commits of merged branches.
# Confirm a hash exists and read its subject/date:
git log -1 --format='%h %ad %s' --date=short <hash>
# Confirm it is on the master lineage (run from the v13.1.3-alpha snapshot or newer):
git merge-base --is-ancestor <hash> HEAD && echo on-master
# Read what it changed:
git show --stat <hash>
Two traps when citing history:
git log --allcontains grafted duplicates — the same change can appear under two hashes (verified example: revert of PR #3307 exists as both7d8678457on the master lineage and duplicate1cbf4a2c8on a side ref). Always cite the hash that passesgit merge-base --is-ancestor <hash> <master-snapshot>.- In development clones, the local
masterbranch pointer can be stale or divergent from upstream. Verify ancestry against a known-good snapshot (c5fd094fb= the v13.1.3-alpha version-bump commit) ororigin/master, never against a bare localmaster.
Entry format
Each entry: ID · dates · Symptom · Root cause · Evidence (hashes/PRs) · Status · Lesson (one imperative line). Status values: settled (fixed, closed), policy (resolution is a standing rule, not just a patch), open (still live or candidate).
FA-1 — The iCloud keychain saga (costliest failure; 3 attempts over ~3 months)
Dates: 2025-11-10 → 2026-01-30. Status: policy.
Background terms: the keychain is the OS secure credential store (accessed via react-native-keychain); on iOS, entries flagged kSecAttrSynchronizable are synced by iCloud Keychain to all of a user's devices. Zeus keeps every wallet secret (seeds, macaroons) in keychain entries, so unwanted sync means wallet seeds silently propagate across devices, and unwanted deletion means funds-losing data loss. That tension is why this took three attempts.
Timeline (all hashes verified on master lineage):
| Date | Event | Evidence |
|---|---|---|
| 2025-11-10 | Attempt 1 merged: "fix: exclude our storage from iCloud backup" | PR #3307, merge 5f533bb12 |
| 2025-11-10 | Reverted the same day via GitHub auto-revert branch revert-3307-... |
PR #3354, merge d1dcb7ba7, revert commit 7d8678457 (no explanation in commit body) |
| 2025-12-01 | Attempt 2 (the "proper fix") merged: re-introduces keychainCloudSyncMigration (first added in Attempt 1 as c6794cf85, reverted with it) — rewrites keychain entries in place with cloudSync: false (same key names; no zeus: prefix yet) |
PR #3371 merge e808fd71a; PR #3405 merge ed161541a ("Change migration Key"); refactor PR #3404 4c3d6dc6d |
| 2026-01-06 | Fallout fix: "fix: lost data after updating to v0.12.0" | 2ae645824 |
| 2026-01-14 | Attempt 3: iCloudCleanupMigration added — the Dec fix only created local copies and never deleted the old kSecAttrSynchronizable entries, so a user setting up a new iPhone inherited stale wallet data from iCloud; cleanup purges via Keychain.resetInternetCredentials({ ..., cloudSync: true }); commit body says "Properly closed #2915" |
46f2bac00 |
| 2026-01-13 | Restructured: separate iCloudCleanupMigration function removed; cloud-synced data deleted before writing local, inside keychainCloudSyncMigration. Same day, zeus:-prefixed key namespacing added: "migration: move to new prefixed keys, added safety steps" |
bddf52507 + 318c721e7 (+ dedup refactor f79ae68ff) |
| 2026-01-30 | Final reversal of the deletion idea: "migration: disable key deletion for now" — deleteFromOldKeychain becomes a deliberate no-op |
c6323fff4; also iOS timing mitigation 35aaf8897 (500ms delays around keychain reads/writes) |
Current state (verified in source, 2026-07-06): utils/MigrationUtils.ts → migrateKey() does read → write → verify → delete, but deleteFromOldKeychain() is a no-op whose comment reads "DISABLED: To prevent potential data loss during migration. Old keychain entries will remain but are harmless (orphaned data)." The old un-prefixed (and possibly iCloud-synced) entries are retained deliberately; the Keychain Recovery tool (utils/KeychainRecoveryUtils.ts, surfaced under views/Tools/) depends on those orphans existing. All new writes go through storage/index.ts with the zeus: prefix and cloudSync: false (both setItem and removeItem).
Root cause (of the whole saga): deleting keychain data is irreversible and iCloud sync timing is unobservable, so every "clean up old entries" attempt risked destroying the only copy of a seed. The team converged on: never delete, namespace instead.
Lesson: never delete keychain data in a migration — namespace new data (zeus: prefix), leave orphans, and treat any keychain deletion as a maintainer-gated change (see zeus-change-control).
FA-2 — React Native upgrade / New Architecture saga (~19-month freeze, then Fabric fallout)
Dates: 2024-06-13 → 2026-06-12 (fallout); ongoing platform risk. Status: settled (0.85.3 current), policy for Animated/layout patterns.
Background terms: React Native (RN) is the cross-platform mobile framework; its New Architecture replaces the old bridge with Fabric (new renderer) and TurboModules; Hermes is RN's JavaScript engine. Layout/animation code that was tolerated by the old renderer can hard-crash under Fabric.
Timeline:
| Date | Event | Evidence |
|---|---|---|
| 2024-06-13 | RN 0.74.2 lands | 1c97cdd4b, merge e89187c68 (PR #2178) |
| 2025 | Two upgrade attempts die as wip branches: deps-react-native-0.76.9 (tip 563f8fe1f, 2025-10-07, subject "wip") and deps-react-native-0.78.2 (tip e34e356a6, 2025-10-26, "wip"); also react-native-0.76.2 (tip a5264a298, 2025-03-03, "revert react-native-tor (for now)") |
local branches; see MP-2 caveat |
| 2026-01-04 | The jump: "deps: React Native upgrade: 0.74.2 -> 0.83.1" — ~19 months after 0.74.2 — with New Architecture ON (android/gradle.properties newArchEnabled=true, verified) |
d13ebc2cd, merged 5c678f54b (PR #3501, 2026-01-09) |
| 2026-02-18 | RN 0.84.0 | bb09f99c1 |
| 2026-03-28 | Fabric fallout 1: "fix WalletHeader animation crashes on New Architecture" | f08bfc7c5, merge 6baf1051c (PR #3910) |
| 2026-05-12 | RN 0.85.3 (current — package.json "react-native": "0.85.3", verified) |
5613ec2b7 |
| 2026-06-12 | Fabric fallout 2: "fix(keypad): prevent Fabric crash by restructuring baseline layout" | 2c55e4f89, merge fe15a65ef (PR #4163) |
Root cause pattern: the codebase accumulated 19 months of Animated/layout patterns validated only against the legacy renderer; Fabric enforces stricter invariants, so crashes surfaced screen-by-screen for months after the jump (WalletHeader at +3 months, keypad at +5 months).
Lesson: treat any pre-2026 Animated/layout pattern as Fabric-suspect, and never let the RN version freeze again — incremental upgrades are cheaper than a 9-minor-version jump. (An rn-v0.86.0 branch exists locally as candidate follow-up work — open, unmerged.)
FA-3 — Settings-migration corruption cluster (stringify, missing await, expiry re-run)
Dates: 2026-05-30 → 2026-06-22. Status: settled + policy.
Background terms: MobX is the state-management library; Zeus stores keep settings as MobX observables (proxied objects). A MOD_KEY migration is Zeus's one-shot pattern: check a flag key in legacy EncryptedStorage → mutate settings → settingsStore.setSettings(...) → set the flag (details in zeus-storage-and-migrations).
Three related failures:
- Stringify corruption. Symptom: settings corrupted after a migration ran. Root cause: a migration passed a
JSON.stringify'd string tosetSettings, which expects a real object — corrupting the MobX observable state. Fix:7ed901a10"fix(migrations): pass object to setSettings to avoid MobX observable corruption (#4150)" (2026-06-09; #4150 is the issue — merged via PR #4155, merge45e9d2095, 2026-06-22). - Un-awaited setSettings. Root cause: MOD_KEY blocks called
setSettingswithoutawait, so the flag could be set before the write landed (or interleave with other writes). Fix:d8e648b58"fix(migrations): await settingsStore.setSettings in all MOD_KEY blocks" (2026-05-30). - Invoice-expiry repair had to run twice. Symptom: invoice screen displayed "3600 hours" while invoices (BOLT11 = the standard Lightning payment-request format) actually expired in one hour. First repair
26d4215ea(2026-05-30) fixed users whose settings had anexpirySecondsfield. It missed pre-Feb-2024 installs, which never storedexpirySeconds/timePeriodat all — so a second passf7b2c30a8(merge ofa7860aa96, 2026-06-08, subject references #4149) derives a canonical seconds value and re-runs under a new flag keyinvoices-expiry-display-fix-v2(verified inutils/MigrationUtils.tsmigrateInvoiceExpiryDisplay).
Lesson: in migrations, always await settingsStore.setSettings(realObject) — never a stringified blob — and enumerate every historical settings shape (including fields that may have never existed) before declaring a repair complete.
FA-4 — UNSAFE-lifecycle refactor → infinite loading (3-week regression)
Dates: 2025-10-24 → 2025-11-12. Status: settled.
Background: React class components have deprecated UNSAFE_componentWillMount-style lifecycle methods; Zeus's views are class components, and its boot/connect logic re-runs on screen focus (see zeus-architecture-contract).
- Symptom: app stuck on infinite loading at boot.
- Root cause:
fdad118ed"refactor: replacing UNSAFE React lifecycle methods" (2025-10-24) changed when boot logic ran. - Fix:
93227029e(2025-11-12) — subject literally cites the offender: "fix: infinite loading regression fdad118ed4a72cd3824863638f668215c01c3b85".
Lesson: lifecycle-method refactors in boot/connect paths are behavior changes, not cleanups — test the cold-start path on both platforms before merging (this repo's naming convention of citing the offending hash in the fix subject is worth copying).
FA-5 — Tor TLS near-miss and the nitro-tor migration
Dates: 2026-05-26 → 2026-06-20. Status: settled + policy.
Background terms: Tor routes traffic through an anonymity network; a .onion address is a Tor hidden service (its TLS certs can't be CA-validated normally); a TLS cert bypass disables certificate verification. Bypassing certs for clearnet hosts reached over Tor exposes users to exit-node man-in-the-middle attacks.
- 2026-05-27: Tor stack replaced — merge
c512ea687(PR #3971) migrates from the retiredZeusLN/react-native-torfork toreact-native-nitro-tor(package.json pins0.6.0, verified). - 2026-05-26: hardening:
d4c8f12e1"fix(TorUtils): treat HTTP >= 300 responses as errors". - 2026-06-19: the near-miss —
9de1742f6"fix(tor): trust invalid certs on LND/CLN REST over Tor" trusted invalid certs broadly on the Tor path. - 2026-06-20: scoped one day later by
8792003ee"fix(tor): scope TLS bypass to .onion HTTPS endpoints", merge5a7422a4b(PR #4186). Standing rule since: cert bypass ONLY for HTTPS .onion; clearnet-over-Tor keeps strict TLS.
Lesson: never widen a TLS bypass beyond .onion HTTPS — any Tor networking change must state exactly which hosts lose cert verification.
FA-6 — react-navigation upgrade regressions → the popTo rule
Dates: 2024-05-05 → 2024-05-21 (recurring class since). Status: policy.
Background: react-navigation manages the screen stack; navigation.navigate(X) pushes a new instance if X isn't the immediate target, while popTo(X) unwinds the stack back to the existing X. Using navigate for "go back" silently builds duplicate screens.
- Symptom: broken back-navigation and node-picture selection after "upgrade react navigation"
3bae746ad(2024-05-05; bumped@react-navigation/bottom-tabs5.11→6.5 among others). - Fix: PR #2192 merge
7abc7a971(2024-05-21), key commita54b3c91b"use popTo instead of navigate to navigate back" — a sweep across 23 views. - The class recurs:
7830d772c(2026-03-30) "fix: use popTo when returning from MintDiscovery to prevent navigation stack buildup";bc135dda7(2026-03-11) moved navigation types toNativeStackNavigationProp(from@react-navigation/native-stack— the repo standard). Current deps:@react-navigation/native7.1.28,@react-navigation/native-stack7.12.0 (verified).
Lesson: for back-navigation always use popTo/popToTop, never navigate, and type screens with NativeStackNavigationProp.
FA-7 — LDK Node crash on wallet deletion (Tokio thread)
Dates: 2026-03-10. Status: settled (rule lives on).
Background terms: LDK Node is a Rust Lightning node embedded via FFI (foreign function interface — calling Rust from JS/native); Tokio is Rust's async runtime; dropping (freeing) a Tokio-owning object from inside a Tokio worker thread aborts the process.
- Symptom: native crash when deleting an LDK Node wallet, and on delete-then-create sequences.
- Root cause: the Node reference was released on a Tokio thread, and stop wasn't awaited before proceeding.
- Fix (same day, two commits):
d416052cd"prevent LDK Node crash on wallet deletion by releasing Node ref off Tokio thread" +0e2b89164"block on LDK Node stop before resolving to prevent crash on delete-then-create" (both 2026-03-10). Related later hardening (both 2026-05-17):b54e8f138"enhancement: LDK: wait for node readiness before fetching startup data" (addedwaitForLdkNodeReady) and8fc9698a0"fix(ldk-node): tolerate buildNode race during wallet startup" (added thewalletJustCreatedskip flag). - Lifecycle races in this area remain a live campaign → zeus-node-lifecycle-campaign.
Lesson: release LDK Node references off the Tokio thread and block on stop before resolving any delete/recreate flow.
FA-8 — Embedded LND SQLite/Bolt DB saga (4 course corrections)
Dates: 2026-01-27 → 2026-04-23. Status: settled.
Background terms: embedded LND is the LND Lightning node compiled with gomobile and run inside the app; LND stores channel/wallet state in either Bolt DB (bbolt, the legacy key-value store) or SQLite. Which backend a wallet was created with matters — you can't silently switch.
| Date | Event | Evidence |
|---|---|---|
| 2026-01-27 | SQLite enabled for newly created/restored embedded wallets | 251aa1b92 |
| 2026-03-27 | Restricted: SQLite iOS-only | ef4a42112 |
| 2026-03-28 | Re-enabled on Android (next day) | 3c6f4175e |
| 2026-04-20 | Retreat: default back to Bolt DB + explicit DB selector in Wallet Config, [SQLite] suffix labels |
d475ef2b4 (subject references #4002) |
| 2026-04-23 | Final crash fix: "prevent embedded LND crash on app restart with Bolt default" — restart crashed for wallets created during the SQLite-default window once the default flipped back | 3e04e6bf2, merge dc2415df4 (PR #4013) |
Root cause pattern: the DB backend default was treated as a config toggle, but it's a per-wallet persistent property (isSqlite per node) — flipping the default desynced existing wallets from the engine they were created with.
Lesson: a storage-engine default is per-wallet state, not a global flag — changing it requires tracking what each existing wallet was created with.
FA-9 — Hermes regex stack overflow on merchant QR
Dates: 2026-06-01. Status: settled.
- Symptom: app crash (Hermes engine stack overflow) when scanning/pasting long inputs that hit the merchant-QR regex matcher.
- Root cause: Hermes's regex engine recurses per character class; unbounded input length → stack overflow.
- Fix:
5c451839d"fix: length-cap merchant-QR matching to avoid Hermes regex stack overflow" —MERCHANT_QR_MAX_LEN = 500guard, verified atutils/handleAnything.ts(constant + two early-return checks). The detector-chain ordering in that file is separately load-bearing → see zeus-lightning-reference (§ handleAnything universal input router).
Lesson: length-cap every input before running non-trivial regexes under Hermes.
FA-10 — NWC transaction-list fix: merged and reverted the same day
Dates: 2025-12-15. Status: settled (as a revert); the underlying bug's final resolution is not chronicled here — check current NWC code before assuming it's fixed.
Background: NWC (Nostr Wallet Connect, NIP-47) lets Nostr clients drive the wallet; Zeus runs an NWC wallet service.
- PR #3432 "fix: lightning node transaction list bug in Nostr clients" merged
285da755a(2025-12-15). - Reverted the same day via auto-revert branch: PR #3444, merge
28bc50f5e, revert commit1c8400515— commit body empty, no recorded reason.
Lesson: NWC store/service-shape changes get reverted fast when release testing flags them — re-read MP-1 before building on any recent NWC fix.
FA-11 — Cashu node-dir collision: all LDK wallets shared one mint DB
Dates: 2026-03-20 → 2026-04-08. Status: settled.
Background: Cashu is a Chaumian ecash protocol; Zeus embeds the CDK (Cashu Dev Kit, Rust). Per-wallet Cashu data is namespaced by a node directory string.
- Symptom: multiple LDK Node wallets on one device saw each other's mint data/balances.
- Root cause: namespacing used the LND directory getter, which returns the literal fallback
'lnd'for every LDK Node wallet — so all LDK wallets collided on one namespace. - Fix:
1242ce94a(2026-03-20) "fix: Cashu: isolate mint data per LDK node using getNodeDir()" —CashuStore.getNodeDir()returnsldkNodeDir || 'ldk'for ldk-node,lndDir || 'lnd'otherwise (verified instores/CashuStore.ts). Follow-up35fa31fee(2026-04-08) "use getNodeDir for cashu multimint keys". Recovery is a copy migration, not a move:MigrationUtils.migrateLegacyCashuKeysToNodeDircopies fourcashu-*key suffixes from the legacylnd-prefixed keys to node-dir keys, only when the node key is empty (verified inutils/MigrationUtils.ts).
Lesson: namespace per-wallet data with CashuStore.getNodeDir() (never the raw lndDir getter), and migrate by copying — leave legacy keys in place (same no-delete doctrine as FA-1).
FA-12 — Bimodal pathfinding: default diverges for new vs migrated users (upstream lnd bug)
Dates: standing config quirk. Status: policy (tracks upstream).
Background: bimodal pathfinding is an lnd payment-routing probability model (alternative to the default "apriori" estimator).
- Root cause: upstream bug lightningnetwork/lnd#9085 made bimodal mode unsafe for users who had it enabled; rather than change the fresh-install default, Zeus force-disabled it only for existing users.
- Evidence (verified in source):
stores/SettingsStore.tsdefaultbimodalPathfinding: true(fresh installs);utils/MigrationUtils.tsMOD_KEY7 ='bimodal-bug-9085'setsbimodalPathfinding = falsefor migrated users, with the lnd issue URL in a comment. - Consequence: new and upgraded installs behave differently by design. Same divergence pattern exists for
showMillisatoshiAmounts(see zeus-config-and-flags for the full new-vs-migrated list).
Lesson: when an upstream bug forces a behavior change, decide explicitly whether fresh installs and migrated users should diverge — and record the upstream issue number in the MOD_KEY name.
MP-1 — Meta-pattern: the rapid revert
Status: policy (maintainer-confirmed: regressions found in release testing get reverted, never forward-fixed under pressure).
Merged ≠ final in this repo. Reverts arrive via GitHub auto-revert branches named revert-<PR#>-<branch>, and the revert commits carry no explanation in the body — the reasoning lives in out-of-repo release testing.
Verified revert pairs:
| Original PR (merge, date) | Revert PR (merge, date) | Gap |
|---|---|---|
#3307 iCloud exclusion (5f533bb12, 2025-11-10) |
#3354 (d1dcb7ba7, revert 7d8678457, 2025-11-10) |
same day |
#3432 NWC txlist (285da755a, 2025-12-15) |
#3444 (28bc50f5e, revert 1c8400515, 2025-12-15) |
same day |
#1568 sat rounding (4c0b2ad48, 2023-08-04) |
#1587 (3d4377935, revert cd7b30479, 2023-08-05) |
1 day |
#1679 app-lock timeout (3c1634710, 2023-10-10) |
#1773 (4dd71b8da, revert c0dc4e1f8, 2023-10-16) |
6 days |
Before trusting any merged fix (especially in storage/keychain/NWC territory), run:
git log --oneline --grep='revert-<PR#>' # revert merge, if any
git log --oneline --grep='Revert' --since=<merge-date> | head
Lesson: always check for a revert-<PR#> branch/merge before citing, extending, or reintroducing a merged change.
MP-2 — The stalled-branch graveyard
Status: open (each branch is unfinished work, not abandoned-by-decision unless noted).
Caveat: these are local branches in the primary development clone (hundreds exist); a fresh fork won't have them. Verify with git branch --list <name> (or git branch -r against the ZeusLN remote). Counts below are commits not reachable from c5fd094fb.
| Branch | Tip (verified) | Unmerged | What it is |
|---|---|---|---|
android-16kb-page-size |
fa2212457, 2025-10-10, "config: Android: 16 KB page size" |
1 | Android 16KB-page-size compliance; note android/check_elf_alignment.sh already exists on master but is invoked by nothing |
bip-321 |
3c1210922, 2025-08-20, "feat: BIP-321" |
1 | BIP-321 (unified bitcoin payment URIs) support |
cdk-init-remote-nodes |
62437d4c8, 2026-05-01 |
4 | Cashu (CDK) redemption for remote-node backends |
cashu-backup |
34228aac7, 2025-04-04 |
73 | Large stalled effort (ecash backup / ZEUS Pay-adjacent) — oldest and biggest |
zeus-rgs |
10cc63b7b, 2026-05-12 |
0 | Fully contained in master as of c5fd094fb — NOT stalled, safe to ignore |
deps-react-native-0.76.9 / deps-react-native-0.78.2 / react-native-0.76.2 |
see FA-2 | 1–3 | Dead RN upgrade attempts, superseded by the 0.83.1 jump |
rn-v0.86.0 |
203cb9147, 2026-06-26 |
6 | Candidate next RN bump — open |
backup-* and *-backup-* branches (backup-before-rebase, backup-pre-squash, ldk-node-backup-0326-*) are pre-rebase safety copies of work that already landed — not stalled work. Don't mine them for "lost" features.
Lesson: before starting 16KB-page-size, BIP-321, remote-node Cashu, or cashu-backup work, diff the graveyard branch first — someone already broke ground.
Lessons index (one line each)
- FA-1: Never delete keychain data in a migration — namespace and retain orphans.
- FA-2: Treat pre-2026 Animated/layout code as Fabric-suspect; never freeze the RN version for months.
- FA-3:
await settingsStore.setSettings(realObject)— never stringified — and enumerate every historical settings shape. - FA-4: Lifecycle refactors in boot paths are behavior changes; test cold start on both platforms.
- FA-5: TLS cert bypass only for HTTPS
.onion; treat HTTP ≥ 300 over Tor as errors. - FA-6: Use
popTofor back-navigation andNativeStackNavigationPropfor types. - FA-7: Release LDK Node refs off the Tokio thread; block on stop before delete/recreate.
- FA-8: A storage-engine default is per-wallet state, not a global flag.
- FA-9: Length-cap inputs before regexing under Hermes.
- FA-10/MP-1: Check for
revert-<PR#>before trusting any merged fix. - FA-11: Namespace per-wallet data via
getNodeDir(); migrate by copy, never move. - FA-12: Record upstream issue numbers in MOD_KEY names; decide fresh-vs-migrated divergence explicitly.
- MP-2: Diff the graveyard branch before restarting stalled work.
Provenance and maintenance
Facts verified 2026-07-06 against master snapshot c5fd094fb (git describe --tags = v13.1.2-3-gc5fd094fb; package.json version 13.1.3-alpha). Every hash above passed git log -1 <hash> and git merge-base --is-ancestor <hash> c5fd094fb. Dates are author dates from git log --date=short. Not independently verifiable from the repo: GitHub PR bodies (revert reasons confirmed absent only in commit bodies) and the maintainer's revert-first policy statement (2026-07-06, authoritative).
Re-verification one-liners for volatile facts:
| Fact | Command |
|---|---|
| Any cited hash exists + subject/date | git log -1 --format='%h %ad %s' --date=short <hash> |
| Hash is on master lineage | git merge-base --is-ancestor <hash> HEAD && echo ok |
| RN version (0.85.3 as of 2026-07) | grep '"react-native"' package.json |
| New Architecture still ON | grep newArchEnabled android/gradle.properties |
| nitro-tor version (0.6.0) | grep react-native-nitro-tor package.json |
| Keychain deletion still disabled | grep -n 'Skipping delete' utils/MigrationUtils.ts |
| Orphan-retention comment intact | grep -n 'DISABLED' utils/MigrationUtils.ts |
zeus:-prefix + cloudSync: false writes |
grep -n 'cloudSync' storage/index.ts |
| Merchant-QR length cap (500) | grep -n MERCHANT_QR_MAX_LEN utils/handleAnything.ts |
| Bimodal default (true) + migration off-switch | grep -n bimodalPathfinding stores/SettingsStore.ts utils/MigrationUtils.ts |
| Cashu node-dir logic | grep -n -A6 'getNodeDir = ' stores/CashuStore.ts |
Expiry-repair MOD key (...-fix-v2) |
grep -n 'invoices-expiry-display-fix' utils/MigrationUtils.ts |
| New reverts since this was written | git log --oneline --grep='revert-' --since=2026-07-06 |
| Graveyard branch still unmerged | git log --oneline HEAD..<branch> | wc -l |
| react-navigation major (7.x) | grep '@react-navigation/native' package.json |
If any command's output disagrees with an entry, the repo wins — update the entry and re-stamp this section.