The NamecoinNameService.init() singleton was the only consumer of
the companion object removed in the previous commit. The
namecoinNameService val itself was never referenced — namecoinResolver
is what's wired into Nip05Client.
- Change `items` state initialization to use `toList()` instead of `toMutableList()` to ensure a fresh immutable snapshot from the flow.
- Reformat chained modifiers in `DraggableItem` for better readability.
Add NamecoinLookupException sealed class to distinguish:
- NameNotFound: name queried successfully but doesn't exist on blockchain
- NameExpired: name exists but expired (>36000 blocks since last update)
- ServersUnreachable: all ElectrumX servers failed with connection errors
Previously, all three cases returned null from nameShowWithFallback,
making it impossible for the UI to show meaningful feedback.
Changes:
- ElectrumxClient.connectAndQuery: throws NameNotFound/NameExpired
instead of returning null for definitive blockchain answers
- ElectrumxClient.nameShow: lets NamecoinLookupException propagate
(only catches connection/IO errors as null)
- ElectrumxClient.nameShowWithFallback: short-circuits on NameNotFound/
NameExpired (no point trying other servers for definitive answers),
throws ServersUnreachable when all servers fail with connection errors
- SearchBarViewModel: new namecoinSearchState flow with typed states
(Idle/Loading/Resolved/NotFound/Expired/ServersUnreachable) for UI
to show loading spinners, resolved profiles, and specific error
messages. Derived namecoinResolvedUser from this flow.
Discovered while porting to notedeck (damus-io/notedeck#1314).
Fixes discovered while porting Namecoin NIP-05 to Primal Android
(PrimalHQ/primal-android-app#934):
1. Race condition: stale intermediate lookups overwrite final result
- Changed map to mapLatest in SearchBarViewModel so that previous
in-flight ElectrumX lookups are cancelled when the query changes
- Without this, typing 'd/testls' character by character causes
stale results from 'd/test', 'd/testl' etc. to arrive after
the correct 'd/testls' result and overwrite it with null
2. CancellationException swallowed in catch block
- mapLatest cancels previous coroutines via CancellationException
- The generic catch was catching it and emitting null, wiping the
valid result. Now re-throws CancellationException
3. Root lookup fails when names object has no underscore key
- extractFromDomainValue tried names[localPart] then names[underscore]
which is the same thing for root lookups
- Now falls back to first available entry for root lookups when
no underscore key exists
- Non-root lookups still fail correctly (no false matches)
4. Global Mutex serializes all lookups
- Single Mutex in ElectrumxClient blocked unrelated lookups to
different servers. A 25s timeout on one server stalled everything
- Now uses per-server mutexes via ConcurrentHashMap
5. customServers set but never read
- NamecoinNameService.setCustomServers() stored the list but the
resolver was constructed with the default serverListProvider
- Now wires customServers through to the resolver
6. resolveLive uses orphaned CoroutineScope
- Now accepts an optional external scope parameter so callers can
tie resolution to their own lifecycle
Adds 2 new tests: root fallback to first entry, non-root no-fallback.
Replace up/down arrow buttons with a drag handle icon that supports
long-press drag gestures for reordering reaction items in settings.
Items visually lift with elevation and scale during drag, and swap
positions when dragged past neighboring item thresholds.
https://claude.ai/code/session_01RYsPFgnoeJ3BmwjFPMvTuU