Auth & user flow
EXEPERT is built so the 3D brain always runs, with or without an account. Authentication only gates the observability features (trace ingestion, projects, annotations). This page visualizes how a visitor moves through the app and what each authentication state unlocks.
The relevant modules are:
src/data/client.ts: builds the Supabase client from env vars, or returnsnullwhen unconfigured.src/auth/session.ts: thin wrapper over Supabase Auth (email/password, OAuth, anonymous), exposing a typedUser | nulland a change stream.src/auth/gate.ts: the nav avatar button + dropdown; mounts/unmounts the observability panel based on auth state.src/ui/signin-view.ts: the sign-in form / signed-in profile card inside the dropdown.src/data/project-state.ts: which project the user is currently viewing.
The big picture
boot() (src/main.ts) starts the renderer, builds the brain, and begins the
RAF loop before and independently of any auth check. If Supabase is
unconfigured the auth gate is skipped entirely and observability mounts straight
away against the Demo project: see Boot sequence.
The three authentication states
There is no general "Guest" role in the system. A first-time visitor is
simply null: no user object at all. A session only comes into existence
through one of three explicit paths.
| State | How it is reached | user.provider | What it unlocks |
|---|---|---|---|
| Unauthenticated | Default on first load (getUser() -> null) | Not applicable | Brain only; placeholder avatar |
| Anonymous | signInAnonymously(): lazily, from the Prompt Playground | email (no real email) | A real auth.uid() to own a per-session project |
| Authenticated | signInWithPassword() or signInWithOAuth() | email / github / google | Full observability, owned projects |
signInAnonymously() is not called on page load. It fires only inside
bootstrapPlaygroundSession() (src/ui/prompt-playground.ts) when there is no
existing user and you interact with the Prompt Playground: and only if
anonymous sign-ins are enabled on the Supabase project. Otherwise it reports a
typed failure and the UI shows a clear message instead of breaking.
Sign-in interaction (the avatar dropdown)
The nav avatar (button.auth-nav-avatar in #authSlot) toggles a dropdown
card. The card's interior is owned by signin-view.ts, which swaps between a
sign-in form and a signed-in profile panel via setPanel().
The card header text: "EXEPERT Observatory / Sign in to access your projects" :
is hardcoded in the markup (signin-view.ts) and does not change between
states. Only the .auth-body panels swap visibility. So a signed-in user can
still see that subtitle above their profile + Sign out button; it is a cosmetic
inconsistency, not a sign of a guest role.
Project scoping & the observability gate
Once authenticated, what the observability panel shows is scoped to the
active project (src/data/project-state.ts). It defaults to the Demo
project and switches reactively via a exepert:project-changed event.
When Supabase is offline, every observability feature degrades to a typed
supabase.not_configured error: the brain still boots, the panel stays empty
(or shows a "not configured" caption) rather than crashing. See the
UI guide for the panel internals.
Summary
- The 3D brain is unconditional: it boots and runs before any auth check.
- The default visitor is unauthenticated (
null), not a "Guest". - Anonymous sessions exist but are created lazily by the Prompt Playground, never on first load.
- Authentication (email/OAuth) is what mounts observability and unlocks owned, project-scoped data.