OTP screen rewrite: 6 rounded boxes, auto-advance focus, auto-submit on the
6th digit, hardware-backspace on empty boxes (intercepted via Focus.onKeyEvent
since TextField.onChanged doesn't fire on already-empty input), resend
cooldown sourced from /api/shared/config/otp, and an inline error message
under the boxes instead of a SnackBar.
Several bugs fixed inline that surfaced during testing:
- ref.listen inside build() accumulates listeners on every rebuild — the
resend countdown's per-second setState was piling up duplicate listeners
so one error triggered N callback fires. Moved to ref.listenManual in
initState; subscription disposed in dispose().
- RouterNotifier was calling notifyListeners() on every auth state change
including AsyncError, which rebuilt the Navigator/Scaffold mid-snackbar
and visually duplicated the error toast. Now skips AsyncError and
same-data-variant transitions.
- ScaffoldMessenger.showSnackBar from a Riverpod listener callback could
still render twice even with hideCurrentSnackBar — replaced with an
inline error widget to sidestep the snackbar machinery entirely.
- register_screen now uses context.go instead of context.push for the
OTP route, so re-submitting the phone form doesn't stack multiple
OtpScreen instances with active subscriptions.
Lockout UX: AuthErrorInfo wraps the error message + code + retry_after_seconds
parsed from the backend's structured error response. On rate-limit codes
(OTP_COOLDOWN, OTP_RATE_LIMIT_PHONE, OTP_RATE_LIMIT_IP), the OTP screen
extends "Kirim ulang kode" cooldown to match the server's wait, and the
register screen disables "Kirim OTP" with a "Coba lagi dalam …" countdown.
formatCountdown() in core/constants.dart renders Xd under 90 seconds and
Xm Yd above (clearer than raw seconds for long lockouts).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Backend: getOrCreateCustomer with phone fallback for re-login
- Backend: PATCH /api/client/auth/profile for display name update
- Client app: AuthNeedsDisplayNameData state + SetDisplayNameScreen
- Client app: ApiClient.patch method
- Both apps: handle verificationCompleted for auto-verify (test numbers)
- Both apps: skip credential sign-in if already auto-verified
- Remove debug prints from mitra auth + OTP screens
- Fix ChatRequestNotifier.startListening skips when accepting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove flutter_bloc and equatable dependencies from both apps
- Delete all 10 old bloc files (5 per app)
- Fix 6 remaining screens that used context.read<ApiClient>() from
flutter_bloc → converted to ConsumerStatefulWidget/ConsumerWidget
with ref.read(apiClientProvider)
- Both apps now use Riverpod exclusively for state management
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add phase3.1 requirement and implementation plan docs
- Add Riverpod dependencies to both client_app and mitra_app
- Wrap both app roots with ProviderScope
- Migrate client_app AuthBloc → AuthNotifier (@riverpod annotation)
- Migrate client_app ChatOpeningBloc → chatPricingProvider (FutureProvider)
- Update router to use Riverpod-based auth state for redirects
- Update all auth screens (display name, register, OTP, force register)
- Update home screen and pricing bottom sheet
- Add android:usesCleartextTraffic for dev HTTP access on both apps
- mitra_app prepared with ProviderScope + ApiClient provider (blocs next)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Upgrade Fastify 4→5 with all plugins (@fastify/websocket 11, cors 11, sensible 6)
- Migrate all SSE endpoints to WebSocket + FCM push (mitra chat requests, customer pairing status)
- Add flutter_local_notifications for foreground push notifications with sound
- Add splash screen to both apps (hide auth loading flash)
- Introduce constants/enums across entire codebase (no raw string literals)
- Move price tiers from hardcoded array to app_config DB (data-driven, includes 1-min test tier)
- Add session ownership validation on all shared chat routes
- Add ownership checks on endSession, respondToExtension, requestExtension
- Fix session timer: auto-complete expired/stale sessions on server restart
- Add 5-min grace period for abandoned closing sessions
- Fix extension flow: proper session_resumed handling, clearExtensionRequest, closure grace timer cleanup
- Fix chat screens: ConnectChat in initState, session status check on connect
- Fix customer expired view: 5-min countdown, closure state priority over expired state
- Fix mitra extension UI: loading spinner, disable buttons, handle EXTENSION_RESOLVED error
- Fix GoRouter navigation consistency (no more Navigator.pushNamed)
- Fix goodbye view keyboard overflow (SingleChildScrollView)
- Add active session card on customer home screen with refresh on navigate back
- Fix PricingBottomSheet extension mode (RequestExtension instead of new pairing)
- Send session_resumed to both parties on extension accept
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Integrated Firebase SDK in both Flutter apps (google-services, firebase_options)
- Fixed auth flow, API client, and pairing/status blocs for dev environment
- Added full Flutter project scaffolds (android, ios, web, etc.)
- Added phase 3 chat engine requirement document
- Added bugreport zip pattern to gitignore
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>