Skip to content

Friends

The friend graph lives behind the Hub → Friends screen. The UX covers sending and accepting requests, searching for people, removing friends, and the connection points (suggestions, profile actions, online strip).

Entry points

From Action
Hub → Friends Direct route — /hub/friends. The primary surface.
Profile (someone else's) "Add friend" / "Friends" / "Pending" action button reflects current status.
Discover map → friend marker Profile-mini opens; "Add friend" if not yet connected.
Radar → stranger card "Add friend" inline action.
User search anywhere Tapping a result lands on profile; friend status is the call-to-action there.

Primary flow — find and add

  1. Open Friends. The screen opens on the Friends tab — a list of accepted friends with online indicators inline. A horizontal "Online now" strip pins to the top.
  2. Search. The top search input filters the friend list instantly. After 2 characters, an additional backend call returns matching users not in the friend list, grouped as "Others" below the local results.
  3. Tap a result. Friend → opens chat or profile. Stranger → opens profile with the "Add friend" action.
  4. Send a request. Tapping "Add friend" sends a request. The button flips to "Requested" and a toast confirms. The target user appears under their "Pending" tab.
  5. Recipient accepts. When the other user opens their Pending tab and taps ✓, both sides see the friendship as accepted. The acceptor's "Online now" strip updates within ~10 seconds.

Sub-flows

Sub-flow Behavior
Pending received The Friends screen has a Pending tab showing inbound requests. Each row has ✓ Accept and ✗ Reject. Accept makes the friendship; reject deletes the request.
Pending sent The Sent tab shows outgoing requests. Each row has a "Cancel" action that withdraws the request.
Cancel a request Tapping cancel removes the request from both sides. The recipient's Pending list updates on next refresh.
Unfriend From the friend's profile or the Friends list (long-press): "Unfriend" → confirmation modal. Removing is symmetric — both sides lose the friendship and the cached friend list re-fetches.
Block From a profile, overflow → "Block". (Block is a backend status today but the user-facing UX is minimal — see "Edge cases & gotchas".)
Suggestion carousel A horizontal strip on the Friends screen surfaces "People you may know" — sourced from mutual-friends and proximity signals. (Suggestion logic is heuristic; see backend.)
Online now strip A pinned horizontal row of avatars at the top of Friends showing currently-online friends. Tap an avatar to open their profile-mini.
Open chat from friend row Tapping the chat icon on a row opens or creates the 1:1 DM room.
Mutual-friends badge Profile shows a "N mutual friends" pill under the name when the viewer is not already a friend.

States

State What the user sees
Friends tab empty A muted illustration: "No friends yet — search above or invite someone."
Pending tab empty "No pending requests."
Sent tab empty "You haven't sent any requests."
Search loading A spinner inside the input; the previous results remain in place.
Search no results "No people match that search."
Request sent The action button flips from "Add friend" to "Requested" with a checkmark.
Acceptance succeeded A toast confirms. The new friend appears at the top of the Friends list and on the Online strip if they're online.
Request failed A toast with the error. The action button reverts.

Edge cases & gotchas

  • Sender and recipient see different things. The friendship row stores user_id_1 (sender) and user_id_2 (recipient). The recipient sees the request under "Pending"; the sender sees it under "Sent". Both can cancel.
  • Accept takes the other user's ID, not the request ID. This is an API quirk that the UI hides — tapping ✓ resolves the other user from the row context. The user does not see request IDs.
  • Removing a friend is symmetric. There's no asymmetric "remove from my list but keep theirs". Both sides lose access immediately.
  • Block is a soft surface today. The backend status blocked exists but the user-facing block flow (where blocked users can no longer message or see profile) is not fully wired. Treat block UX as TBD. (See product/domains/.)
  • Friend cache TTL. The friend list is cached server-side for 10 minutes. A just-accepted friend may not appear in another user's friend list immediately — the user is expected to refresh the screen if they're checking on a fresh acceptance.
  • Online strip is presence-driven. The "Online now" filter only shows friends whose is_location_shared setting allows it and whose presence TTL is fresh and whose show_activity_status preference is on. A friend may be online but invisible to the strip if they've disabled activity status.
  • Search is paginated implicitly. Friend results return up to 10; "Others" return up to 5. There is no "load more" on search — the user is expected to refine the query.
  • Profile of a stranger when not signed in. The same profile URL works without auth (degraded view: no friend actions, no mutual count, no chat affordance).
  • Self-search. The user's own row is filtered out of search results.
  • Backgrounded mid-accept. The accept request is fire-and-forget — backgrounding before the toast does not invalidate it.

See also