OverviewFilesCommitsIssuesDocsReleasesPeers

favela-android commits / c114b75

tests must not fight production, and the README must not lie
Two things found while getting the app onto a handset, both of which had already cost time: The suite booted a REAL favela-web on its default port — the port the deployed favela-web listens on — so on any machine actually running favela it died with EADDRINUSE partway through, after minutes of real Corestore work, reading as a broken test rather than a collision. Assigned port, overridable. The README claimed minSdk 31 (build.gradle says 29, and I repeated the wrong number from here before reading the source), that the APK "has never been launched" (it runs), and that this is "not favela-web in a WebView" (it now is, deliberately). A status table that cannot be trusted is worse than none — every claim in it now traces to something measured today. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
c114b75da7facb3aa815717b73b858e2410ce35b
by plankatron <fleeky@gmail.com> · 2026-08-11T21:22:11.000Z

Changed files (2) +36 -15

README.md +27 -15
……@@ -4,34 +4,44 @@ A **full favela node on a phone** — React Native over a [bare-kit](https://git
44worklet. The phone holds its own DID key, authors and signs its own atproto repo, replicates over
55Hyperswarm and indexes its own appview.
66
7This is **not** favela-web in a WebView, and not a client of a home server. Those are different,
8easier products. See `spec/architecture.md` and `favela-web/spec/mobile-node.md`.
7It **is** favela-web in a WebView — but not a client of a home server, which is the distinction that
8matters. The phone runs the server. favela-web is embedded in the worklet, serving its pages to
9loopback from an hpp that holds the DID key on this device; nothing renders from a remote host.
10
11⚠ This reverses the original design, which rendered a hand-written React Native UI over favela-web's
12data. That was rebuilt here on 2026-08-11 because the native surface looked nothing like favela and
13could only be brought closer by rebuilding every screen a second time, by hand, to a lower standard.
14Rendering the real pages means every screen favela-web gains arrives on the phone for free — the
15reason to embed a whole appview in an app in the first place. See `spec/architecture.md` and
16`favela-web/spec/mobile-node.md`; the WebView decision post-dates both.
917
1018## Status — honest
1119
12The APK **builds and is signed. It has never been launched.** Everything below is measured; nothing
13here claims the app runs on a device yet.
20The APK builds, is signed, and **runs on hardware** (2026-08-11: boots, serves, indexes, signs in).
21Everything below is measured.
1422
1523| | |
1624|---|---|
1725| worklet bundle | 19 MB — hpp + appview + swarm, packed by `bare-pack --preset android` |
18| APK | 136 MB, arm64-v8a, minSdk 31, signed |
26| APK | 145 MB, arm64-v8a, minSdk 29, signed |
27| on-device boot | ~40 s cold: corestore → swarm → appview → favela-web answering `/status` 200 |
1928| pairing | 17/17 over a real testnet |
2029| IPC contract | 14 assertions against the real worklet, not a mock |
2130| storage in a worklet thread | 8/8 on Linux, incl. lock release on exit (the Doze-restart case) |
2231| storage on device | `rocksdb dlopen` + `Corestore.ready` on API 34, app-private |
2332
24Untested: bare-kit's own worklet lifecycle on hardware, foreground-service survival, and
25hyperdht hole-punching on cellular/CGNAT.
33Untested: the WebView surface itself under real use, foreground-service survival, and hyperdht
34hole-punching on cellular/CGNAT.
2635
2736## Architecture in one paragraph
2837
29`lib/node.js` is the worklet entry — a headless full node speaking line-delimited JSON over
30bare-kit's IPC pipe. It binds no HTTP port: React Native does not need one, so objects cross the
31boundary instead of HTML. **The worklet owns all state** — no CAR parsing, no MST, no signing on the
32RN side, so the signing key never leaves the thread that owns it. The appview was always
33render-agnostic (`getTimeline`/`_hydrate` return plain objects), which is why this is a re-render of
34favela-web rather than a rewrite.
38`lib/node.js` is the worklet entry — a full node that also brings up **favela-web in-process**, bound
39to `127.0.0.1:4041`, and signs in to it with a sub-key it delegates to itself. React Native renders
40that server in a WebView; the remaining IPC verbs over bare-kit's pipe are lifecycle and pairing, not
41content. **The worklet owns all state** — no CAR parsing, no MST, no signing on the RN side, so the
42signing key never leaves the thread that owns it, and writes reach hpp over its local control socket
43rather than any network. Because the pages are favela-web's own, this is favela on a phone rather
44than a second implementation of it.
3545
3646## Two bundlers, two artifacts
3747
……@@ -80,8 +90,10 @@ The APK is published as a release artifact and served with
8090https://7rnx.net/git/releases/favela-android/<version>/app-release.apk
8191```
8292
83Requires **Android 12+** (bare-kit's AAR declares minSdk 31). Sideloading needs "install unknown
84apps" for your browser.
93Requires **Android 10+** — `android/build.gradle` sets `minSdkVersion = 29`, matching what
94react-native-bare-kit 0.14.5 declares. (Upstream bare-kit has since moved to 0.15.0 / minSdk 31; the
950.14.5 pin is a deliberate divergence to keep the API-30 handset, not a wait for a fix.) Sideloading
96needs "install unknown apps" for your browser.
8597
8698⛔ The signing key is durable and kept. Android refuses to upgrade an APK signed with a different
8799key, and the only way out is uninstall — which deletes app-private storage, i.e. the node's identity,
test/ipc.test.mjs +9 -0
……@@ -8,6 +8,7 @@
88// NO Metro. The rendering surface is the only part that genuinely needs a phone.
99//
1010// Run: bare test/ipc.test.mjs
11import process from 'bare-process'
1112import fsm from 'bare-fs'
1213import os from 'bare-os'
1314import pathm from 'bare-path'
……@@ -78,6 +79,14 @@ const logs = []
7879client.on('log', (m) => logs.push(m.msg || JSON.stringify(m)))
7980
8081console.log('\n# the worklet boots and announces itself')
82// ⛔ NEVER THE DEFAULT PORT. lib/node.js boots a REAL favela-web, and its default (4041) is the port the
83// deployed favela-web listens on — so on any machine actually running favela (i.e. every machine anyone
84// develops this on) the suite died with EADDRINUSE partway through, after minutes of real Corestore work.
85// It reads as a broken test and is a port collision with production.
86//
87// Assigned, not random: a random port makes a rerun a different test, and a collision a coin-flip that
88// reproduces once in a hundred runs. `||=` so CI or a developer can still override.
89process.env.FAVELA_PORT = process.env.FAVELA_PORT || '4147'
8190await import('../lib/node.js')
8291await client.whenReady({ timeout: 120_000 })
8392assert(client.ready === true, 'client saw the ready signal')