# Geohash Presence Specification ## Overview The Geohash Presence feature provides a mechanism to track online participants in geohash-based location channels. It uses a dedicated ephemeral Nostr event kind to broadcast "heartbeats," ensuring accurate and privacy-preserving online counts. ## Nostr Protocol ### Event Kind A new ephemeral event kind is defined for presence heartbeats: - **Kind:** `20001` (`GEOHASH_PRESENCE`) - **Type:** Ephemeral (not stored by relays long-term) ### Event Structure The presence event mimics the structure of a geohash chat message (Kind 20000) but without content or nickname metadata, to minimize overhead and focus purely on "liveness". ```json { "kind": 20001, "created_at": , "tags": [ ["g", ""] ], "content": "", "pubkey": "", "id": "", "sig": "" } ``` * **`content`**: Must be empty string. * **`tags`**: Must include `["g", ""]`. Should NOT include `["n", ""]`. * **`pubkey`**: The ephemeral identity derived specifically for this geohash (same as used for chat messages). ## Client Behavior ### 1. Broadcasting Presence Clients MUST broadcast a Kind 20001 presence event globally when the app is open, regardless of which screen the user is viewing. * **Global Heartbeat:** * **Trigger:** Application start / initialization, or whenever location (available geohashes) changes. * **Frequency:** Randomized loop interval between **40s and 80s** (average 60s). * **Scope:** Sent to *all* geohash channels corresponding to the device's *current physical location*. * **Privacy Restriction:** Presence MUST ONLY be broadcast to low-precision geohash levels to protect user privacy. Specifically: * **Allowed:** `REGION` (precision 2), `PROVINCE` (precision 4), `CITY` (precision 5). * **Denied:** `NEIGHBORHOOD` (precision 6), `BLOCK` (precision 7), `BUILDING` (precision 8+). * **Decorrelation:** Individual broadcasts within a heartbeat loop must be separated by random delays (e.g., 2-5 seconds) to prevent temporal correlation of public keys across different geohash levels. The main loop delay is adjusted to maintain the target average cadence. ### 2. Subscribing to Presence Clients must update their Nostr filters to listen for both chat and presence events on geohash channels. * **Filter:** * `kinds`: `[20000, 20001]` * `#g`: `[""]` ### 3. Participant Counting The "online participants" count shown in the UI aggregates unique public keys from both presence heartbeats and active chat messages. * **Logic:** * Maintain a map of `pubkey -> last_seen_timestamp` for each geohash. * Update `last_seen_timestamp` upon receiving a valid **Kind 20001 (Presence)** OR **Kind 20000 (Chat)** event. * A participant is considered "online" if their `last_seen_timestamp` is within the last **5 minutes**. ### 4. UI Presentation The presentation of the participant count depends on the geohash precision level and data availability. * **Standard Display:** For channels where presence is broadcast (Region, Province, City) OR any channel where at least one participant has been detected, show the exact count: `[N people]`. * **High-Precision Uncertainty:** For high-precision channels (Neighborhood, Block, Building) where: * Presence broadcasting is disabled (privacy restriction). * **AND** the detected participant count is `0`. * **Display:** `[? people]` * **Reasoning:** Since clients don't announce themselves in these channels, a count of "0" is misleading (people could be lurking). ### 5. Implementation Details (Android Reference) * **`NostrKind.GEOHASH_PRESENCE`**: Added constant `20001`. * **`NostrProtocol.createGeohashPresenceEvent`**: Helper to generate the event. * **`GeohashViewModel`**: * `startGlobalPresenceHeartbeat()`: Coroutine that `collectLatest` on `LocationChannelManager.availableChannels`. * Implements randomized loop logic (40-80s) and per-broadcast random delays (2-5s). * Filters channels by `precision <= 5` before broadcasting. * **`GeohashMessageHandler`**: * Refactored `onEvent` to update participant counts for both Kind 20000 and 20001. * **`LocationChannelsSheet`**: * Implements the `[? people]` display logic for high-precision, zero-count channels. ## Benefits * **Accuracy:** Counts reflect both active listeners (via heartbeats) and active speakers (via messages). * **Privacy:** High-precision location presence is NOT broadcast. Temporal correlation between different levels is obfuscated via random delays. * **Consistency:** "Online" status is maintained globally while the app is open. * **Transparency:** The UI correctly reflects uncertainty (`?`) when privacy rules prevent accurate passive counting.