OverviewFilesCommitsIssuesDocsReleasesPeers

favela-android commits / def5738

pairing must be reachable when the device already has an identity
This shell showed the pairing screen only when there was NO identity, which removed the one route to the ceremony for the case that needs it most: a handset that holds the DID and its signing key but is absent from the room's __members__, so every write is refused with "this room lists members and this device is not among them". Re-pairing from a roster-holding node is the fix for that state, and the app had no way to run it — a regression against the old UI, which reached pairing from settings. The affordance is small, bottom-left (favela-web owns bottom-right), and unconditional: this shell cannot observe admission — that lives in the worklet's StateBase — so gating it would mean a new IPC verb to answer a question the ceremony is harmless to run anyway. It goes away when pairing moves into favela-web and the web UI owns this screen like every other. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
def5738a58b9a184aac58101d343c74856e6a45e
by plankatron <fleeky@gmail.com> · 2026-08-11T23:49:23.000Z

Changed files (1) +25 -4

app/App.jsx +25 -4
……@@ -24,6 +24,15 @@ export default function App () {
2424 const [status, setStatus] = useState(null)
2525 const [error, setError] = useState(null)
2626 const [booting, setBooting] = useState('starting the node…')
27 // Pairing on demand, for a device that ALREADY has an identity.
28 //
29 // ⛔ HAVING AN IDENTITY IS NOT THE SAME AS BEING A MEMBER. The first cut of this shell showed the
30 // pairing screen only when there was no identity at all, which removed the one route to the ceremony
31 // for the case that actually needs it: a handset holding the DID and its signing key, but absent from
32 // the room's __members__, so every write is refused with "this room lists members and this device is
33 // not among them". Re-pairing from a roster-holding node is the FIX for that, and the app had no way
34 // to run it. The old UI reached pairing from settings for exactly this reason.
35 const [pairing, setPairing] = useState(false)
2736
2837 // Boot is a named operation because it happens TWICE: once at launch, and again after pairing. The
2938 // worklet reads `cfg.dids` only in bootMultiDevice (lib/node.js), so a node that has just been given
……@@ -59,12 +68,14 @@ export default function App () {
5968 // `signing` is false exactly when there is no identity config at all (lib/node.js only boots hpp when
6069 // one exists), which is the fresh-install case this screen is for.
6170 if (!status.favelaUrl) return <Center><Text style={s.err}>favela-web did not start — the UI has nothing to show</Text><Text style={s.dim}>{booting}</Text></Center>
62 if (!status.signing) return <Pair client={client} onPaired={paired} />
71 if (!status.signing || pairing) {
72 return <Pair client={client} onPaired={async (r) => { setPairing(false); await paired(r) }} />
73 }
6374
64 return <Web status={status} />
75 return <Web status={status} onPair={() => setPairing(true)} />
6576}
6677
67function Web ({ status }) {
78function Web ({ status, onPair }) {
6879 const ref = useRef(null)
6980 const [canBack, setCanBack] = useState(false)
7081 const [failed, setFailed] = useState(null)
……@@ -132,6 +143,12 @@ function Web ({ status }) {
132143 renderLoading={() => <Center><ActivityIndicator /></Center>}
133144 style={s.fill}
134145 />
146 {/* The pairing affordance. Deliberately small and always present rather than hidden behind a state
147 this shell cannot observe: the app has no way to know whether the room has admitted it (that
148 lives in the worklet's StateBase), so gating the button on "not admitted" would need a new IPC
149 verb to answer a question the ceremony is harmless to run regardless. Goes away when pairing
150 moves into favela-web (design §4) and the web UI owns this screen like every other. */}
151 {onPair ? <Pressable onPress={onPair} hitSlop={12} style={s.pair}><Text style={s.pairTxt}>pair</Text></Pressable> : null}
135152 </SafeAreaView>
136153 )
137154}
……@@ -140,5 +157,9 @@ const s = StyleSheet.create({
140157 fill: { flex: 1 },
141158 err: { color: '#b00020', padding: 16, textAlign: 'center' },
142159 dim: { color: '#666', padding: 8, textAlign: 'center' },
143 retry: { color: '#2563eb', padding: 12, fontSize: 16 }
160 retry: { color: '#2563eb', padding: 12, fontSize: 16 },
161 // Bottom-LEFT: compose and the feed's own controls live bottom-right/centre in favela-web, and an
162 // overlay that covers a button the page owns is worse than no overlay.
163 pair: { position: 'absolute', left: 10, bottom: 14, paddingHorizontal: 10, paddingVertical: 6, borderRadius: 14, backgroundColor: 'rgba(120,120,128,0.28)' },
164 pairTxt: { fontSize: 11, color: '#fff', opacity: 0.9 }
144165})