Notechondria

Version: 0.1.63 Build Date: 2026-04-23T00:00

What's Changed

Bug — fresh OAuth sign-in rejected with "Invalid token"

  • The log was showing "Session established" followed milliseconds later by "Session rejected: Backend.Auth/GET /api/v1/front-page/ — Invalid token." on the very next request. Root cause: the token minted by GoogleOAuthApiView / GitHubOAuthApiView is validated by DRF's TokenAuthentication, which raises AuthenticationFailed on any unknown token — even against permissions.AllowAny views — so a token that was issued but never persisted (DB reset between OAuth mint and first use, migration rollback, LB split-brain, etc.) produces a 401 on every subsequent call instead of a clear error.
  • Fix: applyAuthPayload in frontend/editor_app/lib/app_shell.dart now verifies the fresh token with widget.client.checkSession(token) before persisting it or triggering _loadInitialData. If the backend rejects the token (401, invalid-token message), we clear _token/_profile/ _settings, log a named error at Editor.Auth/applyAuthPayload explaining the backend-state mismatch, and show a user-visible SnackBar: "Fresh token rejected by backend. Please sign in again." Non-401 errors (network blip, 5xx) are logged at warning and we continue so _loadInitialData can surface them with more context. No more confusing "offline fallback" immediately after a successful OAuth callback.

UX — Cancel the login dialog mid-submit

  • Previously the Close button on EmailPasswordDialog disabled itself during _submitting, so a slow OAuth / cold-start backend would strand the user staring at a "Working..." spinner with no way out. The left button is now always enabled; it reads Close when idle and Cancel when a submit is in flight. Popping the dialog makes the in-flight onSubmit Future's result a no-op because _submit guards on mounted after the await.

UX — API base URL field guidance

  • The shared AppPreferencesCard (app_preferences_card.dart) now has informative default hintText / helperText so the user is told what to type before they try:
    • hintText: https://your-backend.example.com/api/v1
    • helperText: "Include the /api/v1 suffix. The app will auto-append it if missing, but pasting the full URL is safer."
    • Callers can still override via the existing apiBaseHintText / apiBaseHelperText props.

Splash — uniform particle size

  • Background particles in the Krebs-cycle splash no longer vary in size (previously 0.75 + rng.nextDouble() * 0.75, giving a 2× spread). Per request, all particles render at the same scale (1.0); visual variation comes entirely from the existing per-particle alpha pulse and the ring-proximity fade. Cycle-step byproduct formulas (CO₂, NADH, FADH₂, GTP) already render at a fixed scale and were not touched — they're in a different draw class from the roaming background particles.

Files Changed

  • VERSION — bumped 0.1.62 → 0.1.63.
  • frontend/editor_app/lib/app_shell.dartapplyAuthPayload pre-validates the fresh token via checkSession and bails with a clear error if the backend rejects it.
  • frontend/notechondria_shared/lib/src/components/auth_dialogs.dartEmailPasswordDialog left action always enabled; label switches CloseCancel based on _submitting.
  • frontend/notechondria_shared/lib/src/settings/app_preferences_card.dart — default API base hintText + helperText added.
  • frontend/notechondria_shared/lib/src/components/splash_screen.dartSplashParticle.size fixed at 1.0 instead of a random 0.75..1.5 range.

Notes / follow-ups

  • The OAuth checkSession pre-validation is editor_app only this round. Planner and portal follow the same applyAuthPayload pattern but live in their own app_shell.dart copies; tracked in docs/TODO.md under Bugs.
  • If the fresh-token-rejected flow keeps firing, the backend is truly losing Token rows between mint and next use. Check the Render / Northflank DB state and the Django authtoken_token table. The frontend fix above surfaces the error clearly; it doesn't paper over a genuine backend problem.
  • "Cancel" during an in-flight login lets the backend finish the request in background. True request cancellation would need the http package's cancelable fork or dart:async futures with an abort token. Low priority since the session result is discarded by the mounted guard.