v1.7.1 — dev mode vs user mode (two-password split)
The dashboard is shared with the static; the dev DB has FRU/TOP/DSR/M9–M12 backfill from development that real users shouldn't see. Splitting the experience: developers retain eve…
Why
The dashboard is shared with the static; the dev DB has FRU/TOP/DSR/M9–M12 backfill from development that real users shouldn't see. Splitting the experience: developers retain everything (dev data, Abilities review queue, field stats, encounter "show all"); users get a clean static-scoped view + onboarding.
Added — DEV_PASSWORD env + User.is_developer
- Schema: new
users.is_developercolumn (Boolean, NOT NULL, default false). Migrationda8534b86685_user_is_developer. - Config: optional
DEV_PASSWORDenv var. When set, logging in with this password marks the user asis_developer=True.AUTH_PASSWORD(existing) gives non-dev access. Either may be unset. - Middleware (api/main.py): validates against both passwords; stashes which one matched on
request.state.auth_match. - Auth dependency (api/auth.py):
Contextnow carriesis_developer._resolve_is_developerpriorities: middleware match > username-equals-AUTH_USERNAME fallback (backwards compat for fresh installs with no DEV_PASSWORD configured). ensure_user_and_membership: on first sighting, dev users auto-join Default Static (id=1); non-dev users get their OWN static auto-created named "{username}'s raid" (no longer auto-joined to Default). On subsequent logins,is_developeris refreshed from the password match — flipping passwords promotes / demotes without DB poking.
API
GET /api/menow returnsis_developer: bool.POST /api/staticsalways auto-switches the user to the new static (was: only if currently on Default Static — a check that no longer triggered under the per-user-static model).
UI (web/src/me.jsx — new shared useMe() hook)
- New
MeProviderinmain.jsxwraps the app, fetches/api/meonce, exposes{me, error, refresh}via React Context. All consumers (StaticSwitcher, App, Home, Reports, Encounters) read from this instead of refetching. - Header (web/src/App.jsx) shows a yellow
dev modepill next to the brand when the user is a developer. - Tab nav: the Abilities tab is hidden from non-dev users. If a stale URL hash points there, the app redirects to Home.
- Home (web/src/Home.jsx): non-dev users with no watched reports get a welcome screen — "Welcome, {username}" + two numbered onboarding cards ("Add your roster" → Roster, "Watch a report" → Reports) + prog points. Returning users / devs see the stat-grid as before.
- Reports:
FieldStatspanel hidden for non-dev users (it's pure backfill telemetry). - Encounters: "show all encounters" toggle hidden for non-dev users (they only ever care about encounters they have data for).
- StaticSwitcher now consumes
useMe()rather than fetching/api/meitself — switching statics refreshes the shared context, so the dev pill / hidden tabs / Home onboarding all stay consistent.
Tests
- 5 new in tests/test_dev_mode.py: DEV_PASSWORD promotes to dev; AUTH_PASSWORD keeps non-dev; wrong password 401s; existing user gets promoted on next login with dev_pw (or demoted vice versa); legacy single-AUTH_USERNAME fallback. Plus the existing multi-static test updated for the per-user-static model.
- 401 tests passing (396 → 401).
Operator notes
- To roll this out: set
DEV_PASSWORD=<long random>in.env.prod. Tell only yourself. ShareAUTH_PASSWORDwith the static. They log in with their own username + AUTH_PASSWORD → get their own static, no dev clutter. - The legacy
aoiuser (or whoever matchesAUTH_USERNAME) keeps dev mode even withoutDEV_PASSWORDset, so existing setups don't break.