Vigil
← All news

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_developer column (Boolean, NOT NULL, default false). Migration da8534b86685_user_is_developer.
  • Config: optional DEV_PASSWORD env var. When set, logging in with this password marks the user as is_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): Context now carries is_developer. _resolve_is_developer priorities: 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_developer is refreshed from the password match — flipping passwords promotes / demotes without DB poking.

API

  • GET /api/me now returns is_developer: bool.
  • POST /api/statics always 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 MeProvider in main.jsx wraps the app, fetches /api/me once, 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 mode pill 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: FieldStats panel 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/me itself — 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. Share AUTH_PASSWORD with the static. They log in with their own username + AUTH_PASSWORD → get their own static, no dev clutter.
  • The legacy aoi user (or whoever matches AUTH_USERNAME) keeps dev mode even without DEV_PASSWORD set, so existing setups don't break.