Skip to main content

Navigation

Status: Scaffolded 2026-09-24

Routing is file-based through expo-router. Two route groups split the app into signed-out and signed-in worlds, and one component decides which world you are in.

Route groups​

app/
_layout.tsx
(auth)/
_layout.tsx stack, no header
welcome.tsx
login.tsx
register.tsx
magic-link.tsx enter email, then "check your inbox"
(tabs)/
_layout.tsx tab bar
index.tsx Home
places.tsx
events.tsx
feed.tsx
messages.tsx
profile/
_layout.tsx stack, pushed over the tabs
index.tsx edit.tsx settings.tsx privacy.tsx account.tsx
companion.tsx presentation: modal

Group names in parentheses do not appear in the URL, so /login and /places are the paths regardless of the folder they sit in.

The five tabs​

Home (this week's events and your groves), Places (the map), Events, Feed, Messages. The order follows the loop in SOUL: plan with places and events, act, share in the feed, coordinate in messages. There is no Profile tab and no Search tab. Profile is reached from the avatar in the header; search is inside Places and Events where it has a scope.

Profile stack​

profile/ is a stack outside (tabs) so it pushes over the tab bar rather than replacing a tab. It is hidden from the tab bar with href: null in the tabs layout. Its screens are the member's own view only (there is no route that shows another member's profile, see ADR 0004): index (handle, avatar, home area), edit, settings (theme, notification preferences), privacy (the publicPostsEnabled and discoverable switches, with plain explanations), and account (sessions, delete account).

Companion modal​

companion.tsx is declared in the root layout with presentation: 'modal' so it slides up over any tab and dismisses back to where you were. It has no tab and no persistent state of its own; the conversation list comes from the API and unpinned conversations expire (companion).

AuthGate​

AuthGate wraps the root layout's navigator. On mount it asks authStore to restore: read the token from SecureStore, call GET /api/me, and settle into signedIn or signedOut. While restoring it renders the splash; nothing navigates. Then, on every segment change:

  • signedOut and the current segment is not (auth): replace with /welcome.
  • signedIn and the current segment is (auth): replace with / (Home).

A 401 from any later request sets signedOut and the same rule sends the member to /welcome. There is no per-screen auth check; the gate is the one place.

The URL scheme is vegangrove. Routes map directly: vegangrove://places/<slug>, vegangrove://events/<slug>, vegangrove://groves/<slug>. The magic-link email points at the web page from MAGIC_LINK_BASE_URL; on a phone that page offers an "open in app" link to vegangrove://auth/magic?token=..., which (auth)/magic-link.tsx handles by calling verify and letting AuthGate do the rest. Universal links and App Links on vegangrove.org, which would skip the web hop, are a roadmap item (open questions). A deep link that lands while signedOut is remembered and replayed after sign-in.