Repository navigation
Description
Summary
On mobile, agents (and other users) can show as "offline" in the app even while genuinely online and visible from desktop, for extended stretches after any relay reconnect. This is a confirmed asymmetry between the mobile and desktop presence implementations, not a hypothesis.
Root cause
PresenceCacheNotifier (mobile/lib/features/profile/presence_cache_provider.dart) is subscribe-only:
if (sessionState.status == SessionStatus.connected) {
_subscribePresenceUpdates();
}
return {};_subscribePresenceUpdates() opens a live kind:20001 subscription with limit: 0 — no snapshot of current state, only future events. track() (line 40-46) is explicitly a no-op for the actual fetch:
/// Currently a no-op for the actual fetch — we rely on live kind:20001
/// events. The tracked set is still used to filter incoming events so the
/// cache doesn't grow unbounded.
void track(List<String> pubkeys) { ... }
// TODO(presence): once the relay supports a `presence:true` filter
// extension, issue a one-shot fetch here for the latest known state per
// pubkey. Until then, presence is "online whenever they publish".Every time sessionState transitions and build() re-fires — which happens on every relay reconnect — the presence map resets to {} and stays empty until a tracked pubkey happens to publish a fresh presence event during that session. iOS backgrounds/suspends the WebSocket aggressively (app backgrounding, network changes, lock screen), so mobile reconnects far more often than desktop ever does, and each reconnect blanks presence until something happens to re-publish.
Desktop has a real backstop for exactly this gap. desktop/src/features/presence/lib/presence.ts:40-50:
// get_presence omits offline/unknown pubkeys, so a live online event often
// targets a pubkey absent from the lookup — merge it in rather than dropping it.
export function mergePresenceUpdate(...)Desktop calls a get_presence snapshot query and merges it with the live kind:20001 stream, so a reconnect doesn't wipe known state. Mobile has no equivalent call anywhere in presence_cache_provider.dart — this was apparently deferred pending a relay-side presence:true filter extension per the TODO, not an oversight introduced by a recent regression.
Suggested fix
Short term: add a one-shot presence snapshot fetch (mirroring desktop's get_presence call) in _subscribePresenceUpdates(), issued once per successful (re)connect, merged into state the same way desktop merges live updates into its query cache. Long term: swap to the relay-side presence:true filter extension mentioned in the TODO once it exists, to avoid a separate REST/query round trip.
Repro
Force a mobile relay reconnect (background the app for >30s, then foreground it, or toggle airplane mode briefly) while an agent that isn't actively posting is a member of a visible channel/DM. The agent will show offline on mobile until it next publishes, while desktop (open at the same time, same relay) shows it online throughout.
Activity
KostaGorod commentedon Sep 3, 2026
Cross-linking a related report with additional detail: #7295 covers the cold-open variant of this (presence cache starts empty on app launch — agents trickle online one-by-one over ~30-60s due to staggered heartbeats) and lays out a fix matrix including a relay-side WS REQ synthesis option that would fix both mobile and desktop in one place.
fix(mobile): hydrate and refresh relay presence snapshots (#7382)
marcelogarciadoo commentedon Oct 3, 2026
Supporting the request for an authoritative presence snapshot on mobile connection/reconnection: an agent that can respond should not appear offline solely because the phone has not seen its next heartbeat. Please keep current presence hydration and live heartbeat updates consistent.
A separate integration caveat matters: an external adapter that does not publish kind:20001 heartbeats will also appear offline. That is not evidence of the mobile cache defect, and fixing mobile hydration alone cannot repair a missing publisher. Please document the required external-agent presence lifecycle and distinguish unknown presence from a confirmed offline state where possible.
marcelogarciadoo commentedon Oct 4, 2026
Suggested regression coverage for this issue and #7295:
These are proposed generic test cases, not a claim that a client fix has shipped.
fix(mobile): hydrate and refresh relay presence snapshots (block#7382)