Skip to main content
Version: 0.2

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 returns null when unconfigured.
  • src/auth/session.ts: thin wrapper over Supabase Auth (email/password, OAuth, anonymous), exposing a typed User | null and 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

The brain is never gated

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.

StateHow it is reacheduser.providerWhat it unlocks
UnauthenticatedDefault on first load (getUser() -> null)Not applicableBrain only; placeholder avatar
AnonymoussignInAnonymously(): lazily, from the Prompt Playgroundemail (no real email)A real auth.uid() to own a per-session project
AuthenticatedsignInWithPassword() or signInWithOAuth()email / github / googleFull observability, owned projects
Anonymous is not the default

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().

Static subtitle caveat

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.