How people move through the app

This note is for product, design, and leadership: what the navigation model is trying to achieve, how it shows up for users, and which product decisions are baked in. Technical implementation lives in the codebase alongside these behaviors; a compact engineering pointer is at the end.


Why this matters

Navigation is not just “screens.” It shapes:


The three main areas (tabs)

The primary shell is three tabs: Inbox, Discover, and Conversations. The app boots into Discover by default.

Tab Who it is for (product framing) When it is available
Discover Everyone — browse and open profiles; default “home” Always surfaced as the app’s default entry.
Inbox Idols — questions and requests that need their attention Only when signed in as an idol. Hidden from the tab bar and guarded from direct entry otherwise.
Conversations Fans / members — ongoing shared message threads When signed in with a profile that can use conversations; not for anonymous-only states.

If someone hits a tab area they cannot use, for example Inbox while signed out, the app moves them to Discover instead of leaving them on a broken screen.


How someone opens a conversation

Links, notifications, and in-app taps do not all start from the same place. The product goal is always the same: end on the right conversation in the right context with the correct tab, predictable back behavior, and any invite or requested-answer-format parameters preserved.

[Diagram]

Canonical room resolver: external room entries first render a short loading state at /rooms/:id, load enough room context to identify the room idol, then replace the resolver with the role-correct tab room. If the signed-in user is the idol of that room, they land in Inbox. Everyone else lands in Conversations.

Discover-source room: when someone starts from a Discover profile CTA, the current implementation opens the fan conversation screen inside the Discover stack at /(tabs)/discover/conversations/:id. It is intentionally source-aware: the tab bar can visually highlight Conversations while the navigation stack still knows the user came from Discover, so back and tab-reset behavior can return them to the profile or browse screen underneath.


Signing in without losing context

When someone is not signed in and opens a room:

  1. The app puts the standard auth flow on top of the room destination.
  2. When they complete or dismiss auth, the underlying destination is still there with its original params, including values such as invite_token, accept_invite, and requested_answer_format.

Product intent: reduce drop-off and support tickets about “I signed in and lost the link.”

For other flows, the app can carry an internal “continue here after” link in a return_to URL parameter. That value is restricted to in-app paths, so marketing and security are not fighting external redirect abuse.


Two ways to sign in / join

Experience What it feels like When it tends to show up
Full-screen auth A standard modal flow over the app Opening a room while signed out, general account actions.
Bottom sheet (“join”) A compact card over dimmed content Invite links where sign-in and join should feel like one continuous sheet.

The join sheet is deliberately stricter than generic room auth: accepting or signing in from the sheet takes the user into the room with invite acceptance wired in. Closing the sheet without joining exits the invite flow and returns to Discover.


Tab bar: resets, reselects, and Discover

What actually happens (aligned with the tab reset implementation):

Discover-only nuance (“transient” rooms): room routes opened inside the Discover stack are treated as temporary on top of Discover, not as the permanent Discover home. A reset returns to the last meaningful non-room Discover route under the room, usually the profile or browse screen, or Discover home if nothing else applies.

[Diagram]

Reading the diagram: the Discover transient room branch is what preserves “I was on a profile, opened a room, now take me off the room without losing where I was browsing.” The default “no stack change” path is what preserves “I landed deep in Conversations; switching tabs once should not erase that.”


Links from outside the app

Notifications that include a room id open the canonical /rooms/:id route and then follow the same role-correct resolver path as other room entries. Notification payloads may also preserve requested answer format.

Web and legacy URLs are mapped into the current app structure where recognized: old Discover paths, room paths, join/invite paths, auth paths, settings paths, help paths, onboarding, stints, and other known app surfaces. Unrecognized paths are not forced into a random screen; if the mapper cannot produce an app href, navigation is a no-op for that target.

Branded web hosts (dm.offscript.app and staging) follow product rules: room_id query param → canonical room; /join/:token and /room_invites/:token → join flows; bare handle path → Discover profile.

Product takeaway: we can ship new in-app URLs without breaking every old link, as long as we extend the mapper when we retire or rename surfaces.


Optional configuration: tabs vs full-screen room

Today the product default is rooms inside the tab shell for the main idol and fan stacks, so tabs stay visible. The navigation helpers centralize a presentation-mode choice that can point source-aware Inbox and Conversations room targets at the canonical /rooms/:id route, useful groundwork for experiments, demos, or a future immersive mode. Because /rooms/:id currently behaves as a resolver, a true full-screen room experience still needs a route/rendering pass before shipping.


Risks and constraints leadership should know


Engineering pointer (for builders)

Implementation uses Expo Router (file-based routes under app/), shared path builders under src/navigation/, and helpers for anchored cross-tab room opens. Developer details live in src/navigation-technical.md. Canonical references: src/navigation/routes.js, src/navigation/actions.js, src/navigation/roomRouting.js, src/navigation/tabStackReset.js, src/navigation/returnTo.js, src/navigation/joinModalAuth.js, plus app/_layout.js, app/(tabs)/_layout.js, app/rooms/[id].js, app/join/[token].js, app/room_invites/[token].js, and app/mapExternalLinkToHref.js. Tests cover the route builders, link mapper, room actions, tab reset behavior, return_to, and invite/join flows under lib/__tests__, src/navigation/__tests__, and conversation component tests.


Document scope

This file describes mobile app navigation behavior and intent. Server API route configuration is separate (config/routes.js and backend docs).