Notechondria
Version: 0.1.48 Build Date: 2026-04-20T09:00
What's Changed
Frontend: phased status indicator replaces the login spinner
User complaint: generic spinners don't reveal which step is stuck.
A CircularProgressIndicator tells you "something is happening"
but not "we're waiting on the network vs. waiting on disk vs.
waiting on the user's CPU to decrypt the response." The user asked
for a per-step label that counts up seconds, e.g.
"Sending request to backend… (1s)" → "Waiting for backend response… (5s)".
This round ships the widget + wires it into the login dialog. Other spinner sites (registration, email-verify, OAuth bind, etc.) follow the same shape and can adopt the widget incrementally.
New: PhasedStatusIndicator
frontend/notechondria_shared/lib/src/components/phased_status.dart
— a StatefulWidget that:
- Takes a
ValueListenable<String>for the phase label. - Resets its elapsed-seconds counter every time the phase string changes, so each step has its own countdown instead of a never-ending "total wait" clock (the latter tells you nothing about which step is stuck).
- Repaints every 200 ms so the seconds tick smoothly — cheap even on a debug build.
- Pins the counter to 1 s for the first 200 ms so users don't see a "0s" read that looks broken.
- Empty label hides the widget completely, so the same instance can represent idle + active states.
The widget is re-exported at frontend/notechondria_shared/lib/notechondria_shared.dart:49 so all three apps pick it up automatically.
Login dialog rewired
frontend/notechondria_shared/lib/src/components/auth_dialogs.dart:
_EmailPasswordDialogStategains aValueNotifier<String> _phaseand a_phaseFallbackTimer.- On tap, the phase is seeded synchronously to
"Sending request to backend"and a 2-second fallback timer flips it to"Waiting for backend response"if the network call hasn't resolved yet. - After the await returns we set
"Applying response"long enough for anysetState(_submitting = false)to land, then clear the phase. - The central
CircularProgressIndicator()is replaced byCenter(child: PhasedStatusIndicator(phase: _phase)). The small 16×16 spinner inside the indicator stays as a glyph so the widget still reads as "work in progress" at a glance — but the seconds suffix is the real information.
So a login that hangs for 7 seconds now shows:
[0–2s] Sending request to backend… (1s) → (2s)
[2–7s] Waiting for backend response… (1s) → (5s)
and the user can immediately tell the backend is the slow part, not client-side validation or JSON decoding.
Why only login this round
The user explicitly flagged the login prompt as the canonical
example. Generalizing the widget (phased_status.dart) means
registration, email-verify, OAuth callback, and the three
app_shell _splashStatus call sites can all adopt the same
pattern in follow-up commits without rewriting the pattern each
time. The main app bootstrap splash already shows a ticker-style
status via _LoadingStatusText — similar shape, different UI
placement — so it doesn't need the indicator, but an OAuth
bind-flow adoption is a natural next round.
Files Changed
New
docs/versions/0.1.48.md(this file).- frontend/notechondria_shared/lib/src/components/phased_status.dart.
Modified
VERSION: 0.1.47 → 0.1.48.- frontend/notechondria_shared/lib/notechondria_shared.dart:
export of
PhasedStatusIndicator. - frontend/notechondria_shared/lib/src/components/auth_dialogs.dart:
_EmailPasswordDialogStategains the phase notifier + fallback timer;CircularProgressIndicatorswapped forPhasedStatusIndicator.
Verification
editor_app/planner_app/portal_app:flutter analyzeissue counts unchanged vs 0.1.47 (54 / 70 / 68); no errors.flutter test test/smoke_test.dartpasses on all three.
Notes / follow-ups
- Remaining spinner sites.
EmailCodeDialog,InvitationCodeDialog,EmailRegistrationDialog,PasswordResetDialog(request + confirm) still use the "Working..." button label without a phased line. The widget is now available — pick each one up when touched for another reason. - OAuth bind callback. Each app_shell's
_handleOAuthCallbackalready has a_splashStatus.value = 'Linking $provider account'line. Consider switching that to a phased cadence too once we need a second iteration. - Backend-side phases. The soft 2-second fallback is a client heuristic; the backend doesn't push progress events. If a later round needs true server-side phases, route them through a streaming endpoint or a polling handshake — not this widget's concern.