Skip to content

Profile & settings

The Hub is the user's home base for their own profile, their preferences, their security settings, and account-level actions. The same surface also covers viewing other people's profiles and the public share view.

Entry points

From Action
Nav dock Profile avatar / icon → /hub.
Friend row → tap Opens the friend's profile at /u/:handle (handle is username or UUID).
Discover marker → friend Profile-mini → "Open profile" → full profile.
Public share link tomoda.life/u/:handle, public profile view, no auth required (degraded).
Authenticated /u/:handle Same handler behind auth, adds friend status and a Message action.

Primary flow — view own profile

  1. Open the Hub. A vertical list of navigation rows: Profile, Edit profile, Appearance, Preferences, Friends, Security, Account.
  2. Profile. Tapping opens /hub/profile — the user's own profile card with avatar, name, username, bio, location, age, and tabs for Moments / Events / Friends.
  3. Edit profile. Tapping the edit affordance opens /hub/profile-edit — inline editable name, bio, gender, country, and avatar.
  4. Save. Each field saves on blur with an inline checkmark; no global save button. Avatar uploads via OS photo picker.

Primary flow — view someone else's profile

  1. Reach the profile. From a friend row, search result, marker, or share link.
  2. See the action bar. Varies by relationship:
    • Stranger → "Add friend" + "Message" (message availability depends on their preferences).
    • Pending sent → "Requested" (disabled until response).
    • Pending received → "Accept request" + "Reject".
    • Friend → "Message" + "Unfriend" (overflow).
    • Self → "Edit profile".
  3. Inspect. Avatar, name, location, age, mutual-friends pill, recent public moments, upcoming public events.
  4. Friends-only content. A non-friend sees only public moments and public events. The friends-only section is omitted, not redacted.

Sub-flows — settings

Sub-flow Behavior
Appearance Three theme options: System, Dark, Light. Tapping one applies the theme immediately and persists.
Preferences → Notifications enabled Master switch — currently a stored preference, not yet enforced (no notification service).
Preferences → Location sharing Toggle for is_location_shared. When off, other users cannot see this user's location.
Preferences → Show activity status Toggle for show_activity_status. When off, friends see this user as offline regardless of presence.
Preferences → Read receipts Toggle for read_receipts_enabled. Mutual — when off, neither side sees read receipts in chats.
Preferences → Language Picker between en-US, ja-JP, zh-CN, zh-TW. Switch applies immediately to the entire app.
Security → Change email Form to change email. Sends an OTP to the new address; entering the OTP confirms the change.
Security → Change password Form requiring the current password + new password.
Account → Logout Confirmation modal. Logout clears local tokens; redirects to /auth/login.
Account → Delete account Destructive confirmation. Delete soft-deactivates the user and triggers async cleanup (media purged, friends notified, sessions invalidated). The user is signed out and lands on a goodbye screen.

States

State What the user sees
Hub loaded Rows render statically. The user's avatar at the top updates from auth context.
Edit field saving The field shows a small spinner; on success a brief checkmark.
Field save error Inline red error under the field; the value reverts to the previous on next focus.
Avatar uploading Avatar greys out with a spinner. On success it pops in.
Theme change Whole app re-themes instantly — no transition flicker.
Language change Strings swap on the next render; the user does not need to restart.
Logout in flight Buttons disable; a spinner replaces "Logout".
Delete confirmed The Account screen briefly shows "Deleting..." then navigates to the goodbye screen.
Profile of self viewed at /u/:handle Same profile, but the action bar shows "Edit profile" instead of "Message"/"Add friend".
Public share view (signed-out) Degraded profile — no friend actions, no mutual count, no message affordance. Sign-up CTA is the primary call to action.

Edge cases & gotchas

  • The mini card and the full profile are two different reads. The profile-mini popover (friend markers, radar rows, mention chips, chat headers) is a lean identity card: name, username, avatar, bio, and whether you are already friends. The full profile at /u/:handle is a separate aggregated read that adds recent moments, upcoming and past public events, mutual friends, and the exact friendship status that drives the action bar. Opening the full profile always refetches; the mini card never carries the heavy payload.
  • Email change requires OTP. The change is not committed until the OTP from the new address is entered. If the user cancels mid-flow, their old email stays.
  • Password change does not invalidate other sessions immediately. Existing sessions on other devices remain valid until they expire or refresh fails. (Product behavior — see product/domains/.)
  • Delete is async. The user is signed out and shown a goodbye screen immediately, but the actual data removal is queued: media purge, friend invalidation, and asynchronous cleanup happen behind the scenes. Friends may briefly still see the user in their list until the cache invalidates.
  • Theme = System follows the OS setting and updates in real time when the OS toggles between light and dark.
  • Username collision on edit. (Currently username is set at onboarding only — see Frontend → Routing. The username field is not editable from /hub/profile-edit.)
  • Avatar removal. Tapping the avatar picker offers a "Remove" affordance. Confirming sets the avatar to null; the app renders the default initials avatar.
  • Friends-only moments on someone else's profile. Visible only if you're an accepted friend. Otherwise that strip is omitted — no "this is hidden" placeholder.
  • Mutual-friend count. Shown on a stranger's profile when both have friends in common. Computed server-side; the user sees a flat number, no list.
  • Backgrounding during a save. The save completes asynchronously; on resume the field shows the latest saved value.
  • Language persistence. The language preference is stored both locally and server-side, so re-installing the app on the same account restores the user's language.
  • Logout does not revoke refresh tokens immediately. The token is invalidated locally; server-side it is invalidated on next refresh. The window is short but exists. (Auth detail — see backend.)

See also