Chat-screen performance (customer + mitra): - Parent screens have zero `ref.watch` — only `ref.listen` for side effects - Body extracted into its own `ConsumerStatefulWidget`; AppBar parts split into narrow `.select` consumers (mode, sensitivity, timer) - Per-second timer ticks routed to dedicated providers (`chatRemainingSecondsProvider` + new `mitraChatRemainingSecondsProvider`) so WS `session_tick` frames don't invalidate the rest of the chat state Dispose-in-ref bug fix: - `home_screen.dart`, `payment_screen.dart`, `mitra_chat_screen.dart` — ref-using cleanup moved from `dispose()` to `deactivate()`. Modern Riverpod invalidates `ref` the moment `dispose()` runs; the resulting silent error corrupts the widget-tree finalize and the next screen appears frozen - `halo_lints` package added at repo root with `no_ref_in_dispose` rule to catch this pattern in CI / IDE analysis - `custom_lint` activated in both apps' `analysis_options.yaml` (was installed but never wired in — also brings `riverpod_lint`'s `avoid_ref_inside_state_dispose` online) - CLAUDE.md Pitfalls section added to client_app + mitra_app Phase 4 §3 retryable blast-failure (Option A): - Backend `expirePairingRequest` + all-rejected use `recordIntermediateFailure` instead of `failPaymentSession` so the payment session stays `confirmed` for re-blast - WS `pairing_failed` payload carries `is_terminal: false` on the retryable paths; client parses the flag and exposes `retryBlast()` - "Coba cari lagi" CTA on S7 Timeout now re-blasts on the same payment - Pairing service test updated to reflect the new semantics Customer waiting-payment screen navigation patch: - `_navigateTerminal` uses `Future.microtask` + `addPostFrameCallback` redundancy after a release-mode bug where polling stopped but `context.go` never fired, leaving the screen visually stuck on "menunggu pembayaran" See requirement/resume-2026-05-15.md for next-day pickup checklist (mitra release rebuild + S21 Ultra install + retest is the gating item). Bundles unrelated in-flight Phase 4 §2.x work that was already on disk (ESP screen removal, USP one-time gate scaffolding, bestie-availability public route, OTP service edits, Maestro flow tweaks) — kept together to avoid a partial-rebase mess. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6.7 KiB
S10 Chat Screen — Figma Rewrite + Bug Fixes (Test Plan)
Sub-plan of phase4-customer-flow-plan.md. Source-of-truth visuals: requirement/Figma/screens/session.jsx (S10Chat, lines 150–284) + requirement/Figma/screens/v3.jsx (HBChatExpiredBanner). Decided 2026-05-12: discard the pre-Figma S10 implementation and follow Figma strictly.
Scope
- Replace client_app/lib/features/chat/screens/chat_screen.dart with a Figma-faithful S10 implementation.
- Rewrite client_app/lib/features/chat/widgets/chat_expired_banner.dart to match
HBChatExpiredBanner(brand-pink, copy "habis nih... mau lanjutin curhat sama {name}?", whiteperpanjangchip). - Drop: the "[Bestie/User] Sudah Memasuki Ruangan" entry banners, the AppBar
akhiributton, the doodle-pattern background, the voice-call mode pill. - Add: HBOrb avatar (placeholder gradient), "online · ngetik..." inline status, SISA WAKTU pill, 3px progress bar under header, animated 3-dot typing pill in messages list, 2-minute soft-warning inline banner,
+attachment button on input bar (no-op for now),terenkripsi · gak disimpan 🔒footer.
Bug fixes shipped alongside the rewrite
Bug 1 — 3-min snackbar doesn't fire reliably
Symptom: when the chat timer drops below 3 minutes, no snackbar reminder ("sisa 3 menit lagi ya 🤍") appears.
Cause: chat_screen.dart only listened to the backend session_warning WebSocket event. In dev/test scenarios where the backend doesn't emit that event (e.g. force-confirmed payments via /internal/_test/force-confirm-payment), the snackbar never fires.
Fix: fire the snackbar locally as soon as chatRemainingSecondsProvider crosses below 180s, using _threeMinShown to dedupe with the backend event. Re-arm when an extension pushes remaining back above 180s so the next crossing also fires.
Bug 2 — Floating expired banner sticks after extension
Symptom: after extending a session via the time-up sheet, the floating "habis nih..." banner stays on-screen and the SISA WAKTU pill in the header is invisible (timer not ticking).
Cause: extension.service.js#finalizeExtension sends EXTENSION_RESPONSE to the customer without the freshly-extended expires_at. The client's local chatRemainingSecondsProvider is computed off chatState.expiresAt, which still points at the just-elapsed moment from the SESSION_EXPIRED snap. The provider yields 0 and returns. The new expires_at only reaches the client on the next periodic SESSION_TIMER ping — up to 60s later.
Fix (backend): include expires_at: extended.expires_at in the accept-side EXTENSION_RESPONSE payload (extension.service.js).
Fix (client): in chat_notifier.dart extensionResponse case, parse expires_at from the payload when accepted=true and update expiresAt on the state. The provider re-runs, computes a positive remaining, and the banner/pill recover immediately.
Test plan
Backend Vitest — test/services/extension.service.test.js (new, 2/2 passing)
| # | Test | Setup | Assert |
|---|---|---|---|
| 1 | Accepted extension broadcasts expires_at |
Seed active session w/ expires_at = now+30s, confirmed extension payment, pending extension row. Call respondToExtension(..., accepted=true). |
EXTENSION_RESPONSE payload sent to customer has accepted=true, duration_minutes=10, expires_at set, ~10min after the seeded baseline. DB row matches. |
| 2 | Rejected extension omits expires_at |
Same setup, accepted=false |
EXTENSION_RESPONSE payload has accepted=false and no expires_at (timer wasn't extended). |
Manual smoke (operator)
Both scenarios run on the emulator with the dev backend + a real-or-stubbed mitra. Use .maestro/scripts/mark_latest_payment_paid.js to force-confirm payments and skip Xendit.
S10-A. 3-min snackbar fires from local tick.
- Pair into a session with a short duration (e.g. 5 min).
- Wait until the SISA WAKTU pill shows ≤ 3:00.
- Expect: snackbar "sisa 3 menit lagi ya 🤍" appears once.
- Send a message — snackbar should NOT re-fire on rebuild.
S10-B. 3-min snackbar re-arms after extension.
- Continue from S10-A (snackbar already fired, timer < 3 min).
- Tap the soft-warning's
+30 menitto open the time-up sheet. - Pick a tier, force-confirm payment.
- Expect: SISA WAKTU pill resumes counting (e.g. ~14:00 if you picked +12min), progress bar refills, soft-warning + expired banner gone.
- Let the timer drift down again to < 3 min.
- Expect: snackbar fires again.
S10-C. Floating expired banner clears after extension.
- Pair into a session and let the timer expire (or use
.maestro/scripts/force_session_expires_at.jsto short-circuit). - Expect: floating brand-pink "habis nih... mau lanjutin curhat sama {name}?" banner appears, SISA WAKTU pill disappears (remaining ≤ 0).
- Tap
perpanjang, pick a tier, force-confirm payment. - Expect (within ~1s of backend ack): banner disappears, SISA WAKTU pill returns with the new remaining, progress bar redraws. Input bar is reactivated.
S10-D. Voice-call session (known gap, not a regression)
- Voice-call mode badge was dropped per strict-Figma. If voice-call sessions need an indicator, raise as a follow-up.
S10-E. Mid-session manual end (known gap)
- Figma S10 has no
akhiributton. PricingBottomSheet doesn't currently have a "cukup, akhiri sesi" option either, so manual end mid-session is unreachable until either (a) the time-up sheet grows that button or (b) we add an end-session affordance per business call.
Maestro automation
Deferred — the Phase 4 Stage 9 Semantics regression on SHome1st still blocks the upstream onboarding flows from reaching S10 in Maestro (see phase4-esp-removal-usp-gate.md "Known blocker"). Once those flows unblock, add:
09_chat_three_min_snackbar.yaml— covers S10-A + S10-B10_chat_extension_recovers_timer.yaml— covers S10-C
Done criteria
- Backend Vitest 2/2 green for
EXTENSION_RESPONSE.expires_at. flutter analyzeclean onchat_screen.dart+chat_expired_banner.dart+chat_notifier.dart.- Manual smoke S10-A, S10-B, S10-C all green on emulator-5554.
- Side-by-side visual diff vs
requirement/Figma/screens/session.jsx::S10Chat— no obvious drift.