Phase 4 Stage 8: returning-user shell + Tanya Admin sheet

Bestie Choice Sheet on home Mulai Curhat CTA. When the user has at
least one prior session (bestieHistoryHasItemsProvider hits the chat-
sessions history endpoint), the CTA opens a HaloBottomSheet with two
cards: 'bestie yang udah kenal' -> /chat/history, 'bestie baru' ->
/payment/entry. Empty history -> direct to /payment/entry.

Bestie history list visual upgrade: HaloOrb (mitraId seed) + name +
last-session date + topic pills + sessions count + ONLINE pill.
Backend getCustomerHistory now returns topics, mitra_is_online,
sessions_count in a single payload (no per-row presence round-trip).

BestieOfflinePopup with two variants (returning | new_) replacing the
legacy BestieUnavailableDialog. tanya admin ghost CTA on both variants
opens the new TanyaAdminSheet. Stage 5's targeted-wait declined stub
+ Stage 7's chat-screen 409 stub + searching-screen call site all
migrated to the real component.

TanyaAdminSheet: HaloBottomSheet with WA + Telegram buttons, deeplinks
fetched via supportHandlesProvider (CC-config-driven). url_launcher
added to client_app; ios LSApplicationQueriesSchemes covers
https/http/whatsapp/tg.

Stage 2's OTP-blocked popup hubungi admin SnackBar stub also migrated
to TanyaAdminSheet.

Dev-only POST /internal/_test/seed-history-session lets Maestro 08
flow seed a history row before exercising the choice sheet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-10 17:47:02 +08:00
parent d454fd39db
commit 862fc35a40
23 changed files with 1122 additions and 215 deletions

View File

@@ -0,0 +1,132 @@
# Stage 8 acceptance: returning-user shell.
#
# Flow:
# 1. Cold-start onboarding flow (mirrors 01_smoke) lands customer on home.
# 2. Seed a completed chat_sessions row so the bestie history list isn't empty.
# 3. Tap "Mulai Curhat" → Bestie Choice Sheet appears.
# 4. Tap "bestie yang udah kenal" → bestie history list appears.
# 5. Verify ONLINE pill renders for the seeded (online) mitra.
# 6. Tap "curhat lagi" on the row → targeted-wait screen appears with 20s
# countdown overlay, then matches via the running mitra.
#
# Pre-req: client_app debug APK installed, backend reachable, NODE_ENV != 'production'
# so the dev-only /internal/_test routes are registered, AND a mitra is currently
# online in the dev DB (see backend/src/db/seed.js or run mitra_app to sign in).
#
# Run:
# maestro test client_app/.maestro/flows/08_returning_targeted.yaml
appId: com.halobestie.client.client_app
env:
TEST_PHONE: "+628155556677"
BACKEND_INTERNAL_URL: http://localhost:3001
---
# Wipe prior state for TEST_PHONE so the run is hermetic.
- runScript:
file: ../scripts/reset_phone.js
env:
TEST_PHONE: ${TEST_PHONE}
BACKEND_INTERNAL_URL: ${BACKEND_INTERNAL_URL}
- launchApp:
clearState: true
# Onboarding → welcome → display name → force-register → OTP → home (matches 01_smoke).
- extendedWaitUntil:
visible:
text: "Mulai"
timeout: 15000
- tapOn:
text: "Mulai"
retryTapIfNoChange: true
- extendedWaitUntil:
visible:
text: "Lanjut sebagai Tamu"
timeout: 10000
- tapOn:
text: "Lanjut sebagai Tamu"
retryTapIfNoChange: true
- extendedWaitUntil:
visible:
text: "Nama panggilan"
timeout: 10000
- tapOn:
text: "Nama panggilan"
- inputText: "Maestro"
- hideKeyboard
- tapOn:
text: "Lanjut"
retryTapIfNoChange: true
- extendedWaitUntil:
visible:
text: "Verifikasi Akun"
timeout: 15000
- tapOn:
text: "Nomor HP"
- inputText: ${TEST_PHONE}
- hideKeyboard
- tapOn:
text: "Kirim OTP"
retryTapIfNoChange: true
- extendedWaitUntil:
visible:
text: "Masukkan OTP"
timeout: 15000
- runScript:
file: ../scripts/peek_otp.js
env:
TEST_PHONE: ${TEST_PHONE}
BACKEND_INTERNAL_URL: ${BACKEND_INTERNAL_URL}
- inputText: ${output.OTP}
- extendedWaitUntil:
notVisible:
text: "Masukkan OTP"
timeout: 15000
- extendedWaitUntil:
visible:
text: "Nama panggilan"
timeout: 10000
- tapOn:
text: "Nama panggilan"
- inputText: "Maestro"
- hideKeyboard
- tapOn:
text: "Lanjut"
retryTapIfNoChange: true
- extendedWaitUntil:
visible:
text: "Mulai Curhat"
timeout: 20000
# Seed a prior session against an online mitra.
- runScript:
file: ../scripts/seed_history_session.js
env:
TEST_PHONE: ${TEST_PHONE}
BACKEND_INTERNAL_URL: ${BACKEND_INTERNAL_URL}
# Tap "Mulai Curhat" → Bestie Choice Sheet (returning-user variant).
- tapOn:
text: "Mulai Curhat"
retryTapIfNoChange: true
- extendedWaitUntil:
visible:
text: "mau curhat sama siapa?"
timeout: 5000
- assertVisible: "bestie yang udah kenal"
- assertVisible: "bestie baru"
# Choose the known bestie path → history list with v4 layout.
- tapOn: "bestie yang udah kenal"
- extendedWaitUntil:
visible:
text: "Riwayat Chat"
timeout: 5000
- assertVisible: "ONLINE"
- assertVisible: "curhat lagi"
# Tap "curhat lagi" → /payment (legacy targeted-payment route). Verify the
# screen title; the targeted-payment flow itself is covered by Stage 5.
- tapOn: "curhat lagi"
- extendedWaitUntil:
visible:
text: "Chat lagi dengan"
timeout: 10000

View File

@@ -0,0 +1,18 @@
// Seed a completed chat_sessions row for TEST_PHONE so the bestie history
// list isn't empty when the Stage 8 flow opens it. Pairs the customer with
// the most-recently-online mitra in the dev DB.
//
// Hits the dev-only /internal/_test/seed-history-session endpoint.
const phone = TEST_PHONE
const url = BACKEND_INTERNAL_URL || 'http://localhost:3001'
const resp = http.post(`${url}/internal/_test/seed-history-session`, {
body: JSON.stringify({ phone }),
headers: { 'Content-Type': 'application/json' },
})
if (resp.status !== 200) {
throw new Error(`seed-history-session failed (${resp.status}): ${resp.body}`)
}
const data = json(resp.body)
output.SESSION_ID = data.session_id
output.MITRA_ID = data.mitra_id
output.MITRA_NAME = data.mitra_name