| name | mobile-app-flows |
| description | Understand and explore the Omi Flutter mobile app's UI flows, navigation patterns, and widget architecture. Use when developing features, fixing bugs, or verifying changes in app/lib/ Dart files. Provides agent-flutter commands to explore the live app, understand how screens connect, and verify your work. |
| allowed-tools | Bash, Read, Glob, Grep |
Omi Mobile App — Flows & Exploration
This skill teaches you the Omi Flutter mobile app's navigation structure, screen architecture, and widget patterns. Use it when developing features (to understand how the app works), fixing bugs (to navigate to the affected screen), or verifying changes (to confirm your code works in the live app).
How to Explore the App
You can interact with the running app via agent-flutter — a CLI that taps widgets, reads the widget tree, and captures screenshots through Flutter's Marionette debug protocol.
Setup
adb devices
adb shell "settings put system system_locales en-US"
adb shell "setprop persist.sys.locale en-US"
cd app && flutter run -d emulator-5554 --flavor dev > /tmp/omi-flutter.log 2>&1 &
AGENT_FLUTTER_LOG=/tmp/omi-flutter.log agent-flutter connect
agent-flutter snapshot -i --json
Prerequisites:
- AVD name:
omi-dev (check: $ANDROID_HOME/emulator/emulator -list-avds)
- KVM access required: user must be in
kvm group (sg kvm -c "..." if not in current session)
- App package:
com.friend.ios.dev (dev flavor)
- System language must be English — non-English IME breaks
fill commands
- App must be authenticated and connected to the correct backend (local, dev, or prod — depends on the task)
- Marionette already integrated:
marionette_flutter: ^0.3.0 in pubspec.yaml
Setup (iOS physical device)
Full iOS physical-device playbook (setup, driving, verification, troubleshooting): IOS_DEVICE_TESTING.md.
Verified 2026-07-11 on an iPhone XR (iOS 18.7.9), app 1.0.543, prod-flavor debug build, agent-flutter CLI + marionette MCP. iOS Simulator has no BLE — use a physical device for anything beyond onboarding/UI checks (see also iOS Simulator Known Limitations below).
flutter devices
cd app && flutter run -d <device-id> --flavor prod > /tmp/omi-flutter.log 2>&1 &
AGENT_FLUTTER_LOG=/tmp/omi-flutter.log agent-flutter connect
agent-flutter snapshot -i --json
Gotchas:
- Fresh git worktrees need real prod config seeded from the primary checkout before building the
prod flavor: copy app/.env, app/lib/firebase_options_prod.dart, app/ios/Config/Prod/GoogleService-Info.plist, app/lib/env/prod_env.g.dart. The test.sh bootstrap seeds dev-placeholder versions that point at the wrong Firebase project.
- adb-backed commands do NOT work on iOS:
back, press x y, dismiss, text --press, text --fill. Use in-app back buttons (find the top-left IconButton ref) and marionette ref presses only.
fill @ref can silently no-op on keyless TextFields on iOS — it reports success but the controller stays empty. Verify via the marionette MCP get_interactive_elements (shows the TextEditingController contents). Durable fix: add a ValueKey to the field, hot reload (sheet state survives), then enter text by key.
snapshot labels are empty with marionette_flutter 0.3.0 on iOS. Primary orientation/assertion tool is agent-flutter text (semantic text dump) — assert outcomes by text presence (e.g. the copy snackbar text). Target elements by type + bounds from snapshot -i.
agent-flutter screenshot output path must be under /tmp.
- Check behavior via the run log:
grep -iE 'exception|error' /tmp/omi-flutter.log after each flow. A PlatformException 4001 (Intercom push token, notifications not granted) is benign.
- iOS terminates the debug connection if the app is backgrounded/locked too long ("The OS has terminated the Flutter debug connection for being inactive") — keep the device unlocked; reconnecting requires relaunching
flutter run.
- General key guidance (same as Android): prefer
find key "name"; when a control can't be targeted, add a ValueKey in source + hot reload rather than fighting coordinates.
Commands
| Command | Purpose | Example |
|---|
snapshot -i --json | See all interactive widgets with refs, types, bounds | agent-flutter snapshot -i --json |
press @ref | Tap a widget by ref | agent-flutter press @e3 |
press x y | Tap by coordinates (ADB input tap) | agent-flutter press 540 1200 |
press @ref --adb | Tap by ref using ADB (for stale refs) | agent-flutter press @e3 --adb |
dismiss | Dismiss system dialogs (location, permissions) | agent-flutter dismiss |
find type X press | Find widget by type and tap | agent-flutter find type button press |
find text "X" press | Find by visible text and tap | agent-flutter find text "Settings" press |
find type X --index N press | Tap Nth match (0-indexed) | agent-flutter find type switch --index 0 press |
fill @ref "text" | Type into text field | agent-flutter fill @e7 "search" |
scroll down/up | Scroll current view | agent-flutter scroll down |
back | Android back button | agent-flutter back |
screenshot PATH | Capture current screen | agent-flutter screenshot /tmp/screen.png |
Key rules:
- Refs go stale frequently (Flutter rebuilds widget tree aggressively) — always re-snapshot before every interaction, not just after mutations.
find type X is more stable than hardcoded @ref numbers.
AGENT_FLUTTER_LOG must point to flutter run stdout (not logcat).
- After hot restart:
disconnect → wait 3s → connect.
- Widget text labels are often null — use
type, flutterType, or bounds to identify.
back, press x y, dismiss, and the --adb/--press/--fill text flags are ADB-backed — Android only. On a physical iOS device use marionette ref-based commands instead (see Setup (iOS physical device) above).
Recovery
adb -s emulator-5554 shell am start -n com.friend.ios.dev/com.friend.ios.MainActivity
agent-flutter disconnect && agent-flutter connect
kill -SIGUSR2 $(pgrep -f "flutter_tools.*run" | head -1)
sleep 3 && agent-flutter disconnect && agent-flutter connect
App Navigation Architecture
Screen Map
Onboarding (wrapper.dart) — step wizard
├── 0: Auth (auth.dart) — Google/Apple sign-in
├── 1: AI Consent
├── 2: Name (name_widget.dart)
├── 3: Primary Language (primary_language_widget.dart)
├── 4: Found Omi (found_omi_widget.dart)
├── 5: Permissions (permissions_widget.dart)
├── 6: User Review (user_review_page.dart)
├── 9: Speech Profile (speech_profile_widget.dart)
├── 10: Knowledge Graph (knowledge_graph_step.dart)
└── 11: Complete (complete_screen.dart) → Home
Home (home/page.dart) — main app after auth, 4-slot bottom nav
├── ["Ask Omi anything…" input bar] → Chat (chat/page.dart) — full-width bar above bottom nav, not a tab
│ ├── Message history, "Ask anything" field, AI responses
│ └── AI-message action row: Copy ("✨ Message copied to clipboard" snackbar), thumbs up, thumbs down, Share
├── [mic in the bar] → Chat with voice auto-start
├── [battery/record widget, top left] (battery_info_widget.dart)
│ ├── Device connected → battery pill → Connected Device (home/device.dart); phone icon → Phone Calls
│ └── No device → Connect → Connect Device page; Record pill → Conversation Capturing (conversation_capturing/page.dart)
│ └── Chevron → record options sheet (Phone Mic record / Phone Call)
├── [settings gear, top right] → Settings sheet (settings_drawer.dart) — present on every slot
│
├── [slot 0] Home (home/home_content.dart)
│ ├── Conversation capture widget, today's tasks widget
│ ├── Daily Recaps → Daily Summary Detail; "View All" → Conversations
│ ├── Mind Map → Memory Graph (memory_graph_page.dart) (only with ≥3 conversations)
│ └── Get-started tiles (only with <3 conversations)
│
├── [slot 1] Conversations (conversations_page.dart)
│ ├── Folder tabs (All, Starred, custom folders)
│ ├── Top-bar extras on this slot: sync icon (when paired/pending), search, calendar date filter
│ └── Conversation item (GestureDetector row) → Detail (conversation_detail/page.dart)
│ ├── Transcript, Summary, Action Items tabs, share, audio (search hidden on Action Items tab)
│ ├── Pull-down menu: Copy Transcript / Copy Summary / Copy ID / Share Audio (if audio) / Link Event / Test Prompt
│ └── Back via in-app top-left IconButton — not the OS/adb back gesture
│
├── [slot 2] Tasks (action_items_page.dart)
│ ├── Categories: Today, Tomorrow, Later, No Deadline, Overdue
│ ├── FAB → Create task sheet (action_item_form_sheet.dart)
│ ├── Task checkboxes, drag-drop reorder, task → goal linking
│ └── Top-bar extras on this slot: export → Task Integrations, completed toggle
│
└── [slot 3] Apps (apps/page.dart) — "Search 1500+ Apps" / "Featured" (explore_install_page.dart)
├── Popular apps (horizontal scroll)
├── Category sections → Category apps page
├── App item → App Detail (app_detail/app_detail.dart)
│ └── Reviews, capabilities, install/enable, Chat button → Chat
└── Top-bar "+" on this slot → Add App / Add MCP Server
Settings sheet (settings_drawer.dart) — rows in order
├── Profile (profile.dart)
│ ├── Name → Change name dialog
│ ├── Email (read-only)
│ ├── Language → Language Settings (language_settings_page.dart)
│ ├── Custom Vocabulary (custom_vocabulary_page.dart)
│ ├── Speech Profile (speech_profile/page.dart)
│ ├── Identifying Others (people.dart)
│ ├── Payment Methods (payments/payments_page.dart)
│ ├── Conversation Display (conversation_display_settings.dart)
│ ├── Data Privacy (data_privacy_page.dart)
│ └── Delete Account (delete_account.dart)
├── Notifications (notifications_settings_page.dart)
│ ├── Frequency slider (0-5)
│ ├── Daily Summary toggle + time picker
│ └── Daily Reflection toggle
├── Plan & Usage (usage_page.dart) (only in some subscription states)
├── Offline Sync (sync_page.dart)
│ ├── Local storage, recordings list
│ ├── Fast transfer settings
│ └── Private cloud sync
├── Device Settings (device_settings.dart) (only when a device is connected)
│ ├── Device info (name, ID, firmware, SD card)
│ ├── LED brightness slider, mic gain slider
│ └── Double tap action picker
├── Integrations (integrations_page.dart) — BETA; also opens from conversation detail
│ └── Google Calendar, Gmail, Apple Health
├── Permissions (permissions_page.dart) — Microphone, Bluetooth, Location, Background Activity
├── Memories (memories/page.dart) — not a bottom-nav tab; reached here or via /memories, /facts deep links
│ ├── Search bar, "This device" filter chip
│ ├── FAB (bottom-right) → New Memory sheet (memory_dialog.dart) — the "Create new memory" text row
│ │ is NOT tappable, only the FAB is; field/button ValueKeys `memory_content_field` / `memory_save_button` (PR #9484)
│ ├── Memory item → Quick edit sheet (memory_edit_sheet.dart)
│ ├── Graph → Memory Graph (memory_graph_page.dart)
│ └── Management → Category management sheet
├── Feedback/Bug → feedback.omi.me (Intercom platforms only)
├── Help Center → help.omi.me (Intercom platforms only)
├── Developer Settings (developer.dart)
│ ├── Custom STT provider config
│ ├── API key management
│ └── MCP API keys
├── What's New → Changelog sheet
├── Get Omi for Mac → App Store link
├── Referral Program (referral_page.dart) — NEW
└── Sign Out → Confirmation dialog
(Settings search reaches a few extra destinations not in the visible list, e.g. Background Mode on Android.)
Transcription Settings (transcription_settings_page.dart) — not in settings drawer; reached from
Plan & Usage, Developer Settings, or the Plans sheet
├── Source toggle: Omi Cloud vs Custom STT
├── Provider selector, API key, model config
└── Advanced JSON editors, logs viewer
Phone Calls (phone_calls_page.dart) — from battery-widget phone icon, record options, or get-started tile
├── Phone Setup Intro when no verified numbers
├── Contacts / Keypad tabs; call → Active Call page
└── Gear → Phone Call Settings (phone_call_settings_page.dart) — verified numbers list, delete button
Plans sheet (plans_sheet.dart) — from Plan & Usage, chat quota-exceeded, phone-calls upsell
Persona Profile (persona_profile.dart) — AI clone management
├── Avatar (100x100), name with verified badge
├── Share Public Link button
├── Make Public toggle
└── 10 social link rows (omi, Twitter active; others Coming Soon)
└── Twitter → Social Handle Entry → Verify Identity → Clone Success
Connected Device (home/device.dart) — requires BLE
├── Device name, connection status, battery
├── Actions: Firmware Update, SD Card Sync, Disconnect, Unpair
└── Device info: Product, Model, Manufacturer, Firmware, ID, Serial
Speech Profile (speech_profile/page.dart)
├── Device animation, intro text
├── Get Started / Do It Again button
├── Question flow: text, progress bar, skip
└── Listen to Speech Profile (if samples exist)
Widget Patterns
Bottom navigation bar:
- Android: 4
InkWell widgets at bounds.y > 780, sorted left-to-right by bounds.x
- Detect with:
snapshot -i --json → filter flutterType == 'InkWell' and bounds.y > 780
- Navigate home: press the leftmost one
- iOS (verified 2026-07-11, iPhone XR, 414pt-wide screen): 4 slots at y≈816, x=20/114/207/300, each w=94.
Left to right: slot 0 = Home, slot 1 = Conversations (folder tabs All/Starred/…), slot 2 = Tasks,
slot 3 = Apps marketplace ("Search 1500+ Apps" / "Featured")
Chat entry point (not a bottom-nav tab):
- Open chat by tapping the "Ask Omi anything…" input bar on the home screen — a full-width gesture
element directly above the bottom nav (~y=756, w≈382 on a 414pt-wide screen; verified iOS 2026-07-11)
Settings gear:
- Android: rightmost
button widget in the top bar; detect by sorting buttons by bounds.x descending, take first
- iOS (verified 2026-07-11): single top-right icon on home at ~x=362, y=58 → Settings sheet (Profile,
Notifications, Offline Sync, Permissions, Memories, Developer Settings, …, Sign Out)
Settings rows:
gesture widgets with bounds.width > 300
- Position-based: Profile is y=150-200, Developer Settings is y=400-520 after scrolling
Switch toggles:
- Type
switch in snapshots
- Press to toggle ON/OFF (no separate ON/OFF actions)
Bottom sheet pickers:
- Open when you press a settings row
- Language items appear as
gesture rows with bounds.y > 380
- Many items — use scroll if needed
Conversation feed rows (iOS, verified 2026-07-11):
GestureDetector widgets, h≈84–96; detail opens on tap
- Go back with the in-app top-left
IconButton (x=8, y=56, w=40) — not the OS/adb back gesture
Chat AI-message actions (iOS, verified 2026-07-11):
- Four small
InkWells (w≈12–14) under each AI message at x≈22/54/88/122; the first is copy —
pressing it shows the "✨ Message copied to clipboard" snackbar
Changing Locale
DEVICE=emulator-5554; APP_PKG=com.friend.ios.dev
adb -s $DEVICE shell "run-as $APP_PKG cat shared_prefs/FlutterSharedPreferences.xml" | grep app_locale
adb -s $DEVICE shell "run-as $APP_PKG cat shared_prefs/FlutterSharedPreferences.xml" > /tmp/prefs.xml
sed -i 's|flutter.app_locale">[^<]*|flutter.app_locale">es|' /tmp/prefs.xml
adb -s $DEVICE push /tmp/prefs.xml /data/local/tmp/FlutterSharedPreferences.xml
adb -s $DEVICE shell "run-as $APP_PKG cp /data/local/tmp/FlutterSharedPreferences.xml shared_prefs/FlutterSharedPreferences.xml"
kill -SIGUSR2 $(pgrep -f "flutter_tools.*run" | head -1)
sleep 3 && agent-flutter disconnect && agent-flutter connect
Prerequisites Reference
Every flow lists prerequisites: — conditions that MUST be true before running. These describe the real user state — no bypasses, no shortcuts. Test the app the way users experience it.
| Prerequisite | What it means | How to achieve (Android) | How to achieve (iOS) |
|---|
auth_ready | User completed sign-in (Google or Apple), app shows home screen | Run bash setup.sh android → launch app → complete Google Sign-In flow → complete onboarding | Run bash setup.sh ios → launch on simulator or device → complete Google/Apple Sign-In → complete onboarding |
signed_out | Fresh app, user NOT signed in, shows Get Started screen | Uninstall + reinstall, or clear app data via Settings → Apps → Omi → Clear Data | Delete app from simulator/device and reinstall |
microphone_permission | App has mic permission granted | When app requests mic permission during use, tap "Allow". Or pre-grant: adb shell pm grant com.friend.ios.dev android.permission.RECORD_AUDIO | When app requests mic permission, tap "Allow" in the iOS permission dialog |
ble_on | Bluetooth enabled on device | Enable Bluetooth in device Settings → Connected Devices. Emulators/simulators do not support BLE — requires physical device | Enable Bluetooth in device Settings. iOS Simulator has no BLE — requires physical iPhone |
omi_device_connected | Omi hardware paired and connected via BLE | Power on Omi device within BLE range → app auto-discovers on home screen → tap Connect. Physical device only | Same — power on Omi, app discovers it. Physical iPhone only |
phone_number_verified | Phone number added and verified in settings | Settings → Phone Calls → add phone number → receive SMS → enter code. Requires real phone number | Same flow — requires real phone number that receives SMS |
developer_settings_enabled | Developer Settings screen is open | Settings drawer → scroll down → tap "Developer Settings" (visible to all users) | Same navigation path |
adb_access | Shell access for locale/prefs manipulation (Android only) | Debug build + adb in PATH. Verify: adb shell run-as com.friend.ios.dev ls shared_prefs/ |
Prerequisite dependency chain
signed_out ─── (fresh install, no prior state)
auth_ready ─── launch app → sign in with Google/Apple → complete onboarding
│ (this is the REAL user flow — no bypasses)
├── microphone_permission (grant when prompted, or pre-grant via platform tools)
├── developer_settings_enabled (navigate in-app to Settings → Developer Settings)
├── phone_number_verified (in-app SMS verification — manual step)
├── ble_on + omi_device_connected (physical device + physical Omi hardware)
└── adb_access (Android debug builds only — for locale manipulation)
Platform setup
cd app && bash setup.sh android
cd app && bash setup.sh ios
Important: Both platforms require completing the real sign-in and onboarding flows. Never bypass auth or onboarding — these are user-facing flows that must work correctly.
YAML Flow Schema (v2)
Each flow file uses schema v2:
version: 2
name: string
description: string
app: com.friend.ios.dev
evidence:
video: true
covers: [string]
preconditions: [string]
steps: [Step]
Each step has these fields:
| Field | Required | Description |
|---|
id | yes | Step ID (S1, S2, ...) |
name | yes | Short description |
do | yes | Instructions for executing |
verify | no | true = must be verified even during snapshot replay |
expect | no | Assertions (text_visible, interactive_count) |
evidence | no | Screenshot filename (.webp preferred) |
note | no | Edge case notes |
Supported expect kinds:
| Kind | Fields | Example |
|---|
text_visible | values: [string] | Verify specific text appears |
interactive_count | min: number | Verify minimum interactive widgets |
Navigation Graph (flow-walker verified)
Real navigation edges verified by flow-walker run11 on Pixel 7a (26 screens, 44 edges, depth 3). Screen names mapped from fingerprint IDs to semantic names.
Note: run11 predates the current bottom nav — its tab labels below reflect the old layout (Conversations/Action Items/Memories/Apps as tabs). Today slot 0 is Home, Tasks is slot 2, Apps is slot 3, and Memories lives under Settings — see Screen Map above. The per-screen element counts and edges remain useful.
Home (24 elements: 17 gesture, 3 icon, 4 inkwell)
├── [tab 0] Conversations (17 el: 1 FAB, 9 gesture, 3 icon, 4 inkwell)
│ ├── Conversation Detail (27 el: 18 gesture, 4 icon, 4 inkwell, 1 textformfield)
│ │ └── Notifications Settings (14 el: 10 gesture, 1 icon, 1 inkwell, 1 switch, 1 textbutton)
│ ├── Settings Drawer (13 el: 13 gesture)
│ │ ├── Language Settings (13 el: 10 gesture, 1 icon, 1 switch, 1 textbutton)
│ │ │ ├── Sub-detail (5 el: 4 gesture, 1 icon)
│ │ │ ├── Sub-settings (7 el: 1 elevated, 5 gesture, 1 icon)
│ │ │ │ ├── Settings Confirmation (3 el: 1 gesture, 2 textbutton)
│ │ │ │ └── Settings Form (16 el: 1 elevated, 9 gesture, 3 icon, 1 inkwell, 2 textfield)
│ │ │ └── Filter Sheet (13 el: 6 gesture, 5 inkwell, 2 textbutton)
│ │ └── Back → Home
│ ├── Sub-detail (2 el: gesture, icon)
│ ├── Sub-page (3 el: 2 gesture, 1 icon)
│ └── Back → Home
├── [tab 1] Action Items (17 el: 1 FAB, 9 gesture, 3 icon, 4 inkwell)
│ └── Notifications Settings (same as above)
├── [tab 2] Memories (25 el: 15 gesture, 4 icon, 5 inkwell, 1 textfield)
│ ├── Notifications Settings
│ ├── Memory Detail (9 el: 5 gesture, 2 icon, 2 inkwell)
│ ├── Category Page (7 el: 4 gesture, 3 inkwell)
│ └── Memory Search (18 el: 12 gesture, 4 icon, 1 inkwell, 1 textfield)
├── [tab 3] Apps (19 el: 2 elevated, 1 FAB, 9 gesture, 1 icon, 5 inkwell, 1 textfield)
│ ├── Notifications Settings
│ ├── Confirmation Dialog (4 el: 1 elevated, 1 gesture, 2 icon)
│ ├── Apps Sub-page (14 el: 3 elevated, 6 gesture, 1 icon, 4 inkwell)
│ ├── App Form (5 el: 1 elevated, 2 gesture, 1 icon, 1 textfield)
│ └── Back → Home
├── [Ask Omi] Chat (22 el: 19 gesture, 1 textbutton, 2 textfield)
│ ├── Delete Account (6 el: 1 checkbox, 4 gesture, 1 textfield)
│ └── Back → Home
├── [record] Capturing (19 el: 2 elevated, 1 FAB, 9 gesture, 1 icon, 5 inkwell, 1 textfield)
├── [settings] Settings Drawer (13 el: 13 gesture)
├── [gesture] Sign Up (5 el: 1 elevated, 1 gesture, 3 textfield)
├── [gesture] Confirmation (2 el: gesture, textbutton)
└── [gesture] Conversations Alt (17 el: 10 gesture, 2 icon, 4 inkwell, 1 textfield)
Screen Fingerprint Mapping
For flow-walker compatibility — maps auto-generated fingerprint names to semantic flow names.
| Fingerprint ID | Element Count | Semantic Name | Flow File |
|---|
92a58e321064 | 24 | Home | home-navigation, tab-navigation |
113cc0d4f097 | 17 | Conversations (FAB) | conversations |
59c8c3dba1aa | 17 | Conversations Alt | conversations |
2108e83364c2 | 19 | Apps | apps |
2b9f6e0d087b | 25 | Memories | memories |
13f37b3018c5 | 22 | Chat | chat |
49f053d00b0f | 27 | Conversation Detail | conversation-detail |
011f78c61152 | 14 | Notifications Settings | settings-notifications |
1b40a175a5fd | 13 | Language Settings | language-change |
4b666d4ec8e3 | 13 | Settings Drawer | home-navigation |
0f852433acc6 | 6 | Delete Account | delete-account |
a74411638a9c | 5 | Sign Up Form | onboarding |
0ba3aef7f00f | 2 | Confirmation Dialog | — |
8407c23c9698 | 2 | Sub-detail | — |
9580b20fb6e0 | 3 | Sub-page | — |
dacfbda46c10 | 5 | Sub-detail (settings) | — |
e8ea54a72f46 | 7 | Sub-settings | — |
0766ec584fb4 | 13 | Filter Sheet | apps |
f94e0140ed7e |
Verified Flows (v2 flow-walker)
Only flows that have been executed, verified, and pushed through the v2 flow-walker pipeline are listed here. Each has a .snapshot.json for fast replay.
| Flow | Steps | Prerequisites | What it tests | Reports |
|---|
flows/login.yaml | 5 | signed_out | Auth screen → Google Sign-In → consent → OAuth → home | normal, replay |
flows/onboarding.yaml | 9 | signed_out + auth | Name → Language → Found Omi → Permissions → Review → Speech → Knowledge → Complete → Home | normal, replay |
flows/logout.yaml | 5 | auth_ready | Home → Settings → Sign Out → Confirm → Auth screen | normal, replay |
flows/ask-omi-chat.yaml | 9 | auth_ready | Home → Ask Omi → Chat page → Type message → AI response → Chat apps drawer → Home | normal |
When you modify a Dart file, check if any flow's covers: includes it. If so, that flow describes the user journey your change affects — use it to understand context and verify your work.
Verification & Evidence
After making changes, verify them in the live app:
- Navigate to the affected screen using the commands above
- Check that your changes appear (snapshot, screenshot)
- Test interactions (press buttons, fill fields, scroll)
- Capture evidence:
agent-flutter screenshot /tmp/evidence.png
- Generate video:
ffmpeg -framerate 1 -pattern_type glob -i '/tmp/e2e-*.png' -vf "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:-1:-1" -c:v libx264 -pix_fmt yuv420p /tmp/report.mp4
Decision Tree
| Problem | Solution |
|---|
| Widget not found | Re-snapshot, try scrolling, check if on wrong screen, match by bounds position |
| Ref expired between commands | Use press x y with coordinates from last snapshot bounds, or press @ref --adb |
| System dialog blocking (location, permissions) | agent-flutter dismiss — detects and dismisses via ADB |
| "No isolate with Marionette" | ADB foreground + disconnect + reconnect |
| Snapshot returns 0 interactive elements | Marionette lost widget tree — disconnect then connect to re-attach |
| Bottom nav tabs not detected | back until nav bar appears, filter InkWell y > 780 |
| Hot restart breaks connection | Wait 3s → disconnect → connect |
| Text labels null | Match by type, flutterType, or bounds — Marionette doesn't extract child text |
| Non-English IME breaks text input | Set system locale to English: adb shell "settings put system system_locales en-US" |
iOS Simulator Known Limitations
For a physical iOS device (has BLE, avoids most limitations below), see Setup (iOS physical device) under How to Explore the App.
| Issue | Workaround |
|---|
| ASWebAuthenticationSession blocks Google Sign-In (system dialog, not automatable) | Use FirebaseAuth.instance.signInWithCustomToken(token) via VM Service evaluate |
agent-flutter scroll fails (uses ADB/transport.swipe, Simulator window disconnected) | Scroll via VM Service Dart evaluation: pos.jumpTo() on Scrollable widgets |
| Simulator.app window shows Home Screen (not app) when booted via SSH user | Use simctl screenshot for true device state; use Marionette/VM Service for interaction |
No simctl touch/swipe API | VM Service evaluate for Flutter-level interaction; cliclick only works if Simulator window is synced |
| iOS keychain persists through app uninstall | xcrun simctl erase <UDID> for full reset |
| iOS onboarding is shorter than Android (language only → home) | Mark skipped steps as pass with platform note |
VM Service scroll expression (target app root library):
(() { var count = 0; void visit(Element el) { if (el.widget is Scrollable) { try { final s = (el as StatefulElement).state as ScrollableState; final p = s.position; if (p.maxScrollExtent > 0 && p.pixels < p.maxScrollExtent) { p.jumpTo((p.pixels + 500).clamp(0.0, p.maxScrollExtent)); count++; } } catch (_) {} } el.visitChildren(visit); } WidgetsBinding.instance.rootElement?.visitChildren(visit); return count; })()
iOS simulator auth (dev Firebase tokens rejected by prod API — needs local backend):
API_BASE_URL=http://<your-backend-host>:<port>/
USE_WEB_AUTH=false
USE_AUTH_CUSTOM_TOKEN=true
Guard Conditions
NEVER:
- Use development env vars to bypass auth — test with real auth flows
- Set
hasCompletedOnboarding to skip onboarding — test the real flow
- Modify source code to make tests pass — report the failure instead
- Commit screenshots to git — use GCS upload for PR evidence