# Mobile APP Working Agreement

이 문서는 `apps/mobile`에서 ZERRO APP을 개발할 때의 앱 전용 작업 기준이다. `apps/app` 경로는 현재 존재하지 않으며, 단일 Expo 앱의 정본 경로는 `apps/mobile`이다.

## Scope

| 항목 | 결정 |
|---|---|
| App path | `apps/mobile` |
| Runtime | Expo SDK 56, React Native `0.85.3`, React `19.2.3` |
| Local dev client | `expo-dev-client` 기반 development build |
| App package | `com.bootalk_appdev.zerro` |
| UX model | 단일 앱 + 온보딩 role routing |
| Role UX | 배출자, 배차담당자, 운전자, 처리자는 분리된 사용자 경험으로 구현 |
| Development model | Figma-first screen-based development |
| E2E model | Maestro deterministic flow + mandatory `callstack/agent-device` UX verification |

## Source of Truth

앱 작업자는 아래 순서로 읽는다.

1. `apps/mobile/AGENTS.md` 또는 `apps/mobile/CLAUDE.md`
2. `docs/40-mobile/WORKING_AGREEMENT.md`
3. `docs/00-governance/DEVELOPMENT_WORKFLOW.md`
4. `docs/10-product/FIGMA_INVENTORY.md`
5. `docs/10-product/MVP_SCOPE.md`
6. `docs/10-product/UI_PRODUCTION_READINESS.md`
7. `docs/10-product/RBAC_MATRIX.md`
8. `docs/20-contracts/API_CONTRACT.md`
9. `docs/40-mobile/policies/PROVIDER_POLICY.md`
10. `apps/mobile/README.md`
11. `apps/mobile/DESIGN.md`
12. `apps/mobile/e2e/TEST_COVERAGE_GRAPH.md`

## Figma-first Screen Loop

한 화면은 Figma node 하나를 구현 단위로 삼는다. 화면을 크게 재해석하지 않고, 먼저 Figma의 실제 구조를 읽은 뒤 코드화한다.

모든 앱 화면/상호작용 작업은 `agent-device` 또는 그에 준하는 실제 emulator/simulator evidence로 UX를 관찰하는 것을 완료 조건으로 삼는다. 단, 매 Codex 요청마다 full Maestro/agent-device loop를 반복하지 않는다. 한 화면을 구현하는 동안 의미 있는 전환점마다 evidence checkpoint를 남겨 context를 잃지 않게 하고, PR 완료 전에는 최종 route/interaction을 실제 device surface에서 확인한다. typecheck, lint, unit test, Maestro는 필요하지만, 실제 device surface를 보지 않은 PR은 완료로 보지 않는다. 현재 `agent-device` selector snapshot은 sparse snapshot 이슈가 남아 있으므로, snapshot이 Android nav node만 반환하면 clean screenshot과 failureKind를 함께 남기고 `apps/mobile/COMMON_MISTAKES.md` 기준으로 별도 이슈로 분리한다.

## Screenshot Evidence Policy

UX index, Figma 비교 리포트, PR 본문, 주간 보고처럼 사람이 직접 보는 시각 증거는 실제 사용자가 보는 앱 화면과 최대한 동일해야 한다. 따라서 Android screenshot의 기본값은 red box/ref badge overlay가 없는 clean ADB capture다.

```bash
adb -s emulator-5554 exec-out screencap -p > artifacts/mobile/agent-device/<screen-slug>.png
```

- `agent-device snapshot`은 접근성 tree, selector, `testID` proof다. visual parity 증거로 단독 사용하지 않는다.
- `agent-device screenshot --overlay-refs` 또는 wrapper가 생성한 red box/E-label overlay 이미지는 selector/debug proof로만 사용한다.
- overlay 이미지를 남겨야 하면 파일명과 caption에 `overlay` 또는 `selector-debug`를 포함하고, UX index/Figma 비교의 primary actual screenshot으로 쓰지 않는다.
- Maestro `latest-after-flow.png`는 red box overlay가 없고 최신 flow 직후 화면이면 visual evidence로 사용할 수 있다.
- clean screenshot을 확보하지 못한 항목은 `actual missing`, `proxy`, `overlay-only` 중 하나로 명확히 표시한다.

`MVP`는 기능 범위를 자르는 용어이지 UI 품질을 낮추는 근거가 아니다. 새로 구현하거나 기존 화면을 확장하는 경우 production-level UI 후보를 목표로 하며, `docs/10-product/UI_PRODUCTION_READINESS.md`의 state completeness, responsive fit, role clarity, interaction polish, testability 기준을 확인한다. 아직 실제 provider/backend가 없어 production UI가 불가능한 영역은 화면에서 placeholder, disabled reason, empty/error state로 명확히 표현하고 `TEST_COVERAGE_GRAPH.md` 또는 PR에 남긴다.

고객센터, 전화, 지도, 파일 viewer, upload provider, 외부 링크는 `docs/40-mobile/policies/PROVIDER_POLICY.md`를 따른다. 화면에서 provider SDK, credential, raw URL 조립을 직접 다루지 않고 adapter boundary와 provider-pending UI를 먼저 고정한다.

1. Figma MCP로 target Frame의 `get_design_context`, metadata, screenshot을 확보한다.
2. 화면 이름, node id, role, route, state, CTA, empty/error/loading state를 screen contract로 적는다.
3. 색상, spacing, radius, typography, icon, bottom tab, card, chip, badge 반복 형태를 token/component 후보로 분류한다.
4. `@zerro/contracts`와 `@zerro/mocks` 기반 mock data를 먼저 연결한다.
5. Expo 앱 화면을 구현한다.
6. Maestro flow로 핵심 happy path와 role routing을 고정한다.
7. `agent-device`로 실제 emulator/simulator 화면을 AI agent가 탐색하고 selector snapshot, 조작 로그, 필요 시 debug screenshot을 남긴다. UX/Figma 비교용 시각 증거는 clean screenshot으로 별도 남긴다. 이 단계는 PR 완료 전 필수이며, 개발 중에는 아래 Evidence Checkpoint Policy에 따라 군데군데 수행한다.
8. Figma screenshot과 app screenshot의 주요 차이를 기록하고, 수용 가능한 차이와 수정해야 할 차이를 구분한다.
9. 같은 형태가 2개 이상의 화면에서 반복되면 shared component 또는 token으로 승격한다.

## Evidence Checkpoint Policy

한 화면 개발은 여러 Codex 요청에 걸쳐 진행될 수 있다. 매 요청마다 emulator를 재부팅하고 Maestro/agent-device 전체 flow를 실행하면 작업 속도와 안정성이 떨어지므로, evidence는 아래 checkpoint에서 남긴다.

| Checkpoint | Evidence |
|---|---|
| Figma 해석 완료 | target node id, variant, screenshot/context 요약 |
| 첫 runtime 진입 | 앱에서 target route/surface가 열린 clean screenshot 또는 selector snapshot |
| 주요 navigation/CTA 연결 | Maestro flow 또는 agent-device 조작 결과 |
| 상태 variant/branch 추가 | selector/unit test 또는 화면별 screenshot |
| PR 완료 직전 | typecheck/test/lint/static gate, 관련 Maestro flow, clean screenshot, agent-device selector snapshot |
| troubleshooting 발생 | `COMMON_MISTAKES.md` 또는 PR notes에 원인, failureKind, 복구 절차 |

Evidence는 git에 포함하지 않는 `artifacts/mobile/**`에 남기는 것을 기본으로 한다. PR에는 artifact 경로, 실행 명령, 통과/실패 원인을 적는다. 검수자는 `artifacts/mobile/index.html`에서 시작하고, 화면 hierarchy는 `artifacts/mobile/ux-flow/index.html`, Figma 비교는 `artifacts/mobile/ux-flow/figma-visual-audit/index.html`, 실행 결과는 `artifacts/mobile/maestro/index.html`에서 확인한다. 이 HTML들은 source-of-truth code가 아니라 `docs/80-artifacts/ux-flow/mobile-index.json`과 Markdown 정본에서 생성되는 검토 보조 artifact다.

PR merge 시점에 device QA가 pending이었다가 후속 세션에서 통과한 경우, 기존 PR 번호만 적지 않는다. `apps/mobile/e2e/TEST_COVERAGE_GRAPH.md`의 current coverage/back-policy 항목과 `docs/70-progress/CURRENT.md`에 후속 verification이라는 사실, Maestro artifact, clean screenshot, `agent-device` selector snapshot 경로를 함께 기록한다.

## Component and Token Rules

- Figma에서 반복되는 card, status badge, metric tile, bottom tab, header, CTA, list item은 두 번째 사용 시점부터 공통화 후보로 표시한다.
- raw hex color, ad-hoc spacing, ad-hoc radius는 화면 파일에 흩뿌리지 않는다. token 파일이 생기면 token 경유를 기본값으로 한다.
- 앱 디자인 토큰, Pretendard font policy, Figma asset 저장/검증 기준은 `apps/mobile/DESIGN.md`를 따른다.
- production-level UI 후보로 볼 수 없는 임시 spacing, 임시 copy, 임시 color, 임시 icon은 PR에 의도적 placeholder로 명시하고 후속 제거 조건을 적는다.
- Figma node별 reference screenshot과 UI asset은 `apps/mobile/assets/figma/README.md`의 `reference/`, `ui/` 규칙을 따른다.
- 앱 컴포넌트는 Web/PC 컴포넌트와 구현을 공유하지 않는다. 공유 가능한 것은 용어, role, status, API shape, fixture, token 의미다.
- 화면 단위 absolute positioning은 피한다. 단, Figma와 동일한 고정 mobile frame을 검증해야 하는 경우에는 이유와 범위를 PR에 남긴다.
- 한국어 visible copy는 Figma 또는 요구사항 문서 기준을 따른다. 테스트 안정성을 위해 visible copy를 selector로 남용하지 않는다.
- 사용자가 보는 visible copy에는 내부 구현 용어를 직접 노출하지 않는다. `mock`, `backend`, `provider`, `testID`, `badge`, `route`, `contract`, `handoff`, `API 계약`은 docs/testID/PR에는 쓸 수 있지만 앱 화면에서는 "준비 중", "연결 준비 중", "담당자 확인 필요", "다시 시도"처럼 사용자 행동과 상태를 설명하는 말로 바꾼다.

## Testability Rules

Maestro와 `agent-device`가 화면을 안정적으로 조작할 수 있게 모든 interactive element는 테스트 친화적으로 만든다.

- 모든 버튼, tab, chip, card CTA, text input, list row, modal action에는 stable `testID`를 둔다.
- `testID`는 영어 소문자와 `.` 또는 `-`만 사용하고, copy 변경에 영향받지 않게 만든다.
- 권장 패턴은 `role.surface.element.action`이다. 예: `emitter.home.request-create.cta`, `driver.route.start.cta`.
- 접근성 snapshot이 의미를 읽을 수 있도록 `accessibilityRole`, `accessibilityLabel`, disabled state를 함께 챙긴다.
- 동적 list item은 business id 또는 fixture id를 포함한다. index 기반 id는 정렬 변경에 취약하므로 피한다.
- UI 자동화가 필요한 action을 icon-only로 만들면 반드시 접근성 label을 둔다.

## Test Coverage Policy

Test coverage는 JaCoCo처럼 숫자만 맞추는 gate가 아니라, mock-first 화면 개발에서 계약과 분기 로직이 보호되는지 확인하는 보조 안전장치다.

- `@zerro/contracts`, `@zerro/mocks`, role/domain selector, status 계산, mutation branch를 바꾸면 unit/domain test와 coverage 확인을 PR 검증에 포함한다.
- coverage threshold가 설정된 package는 threshold를 통과해야 한다. threshold가 아직 없는 package는 실행한 test 범위와 남은 coverage gap을 PR 또는 `.omo/evidence`에 남긴다.
- line/statement coverage가 높아도 UI route, CTA, Android Back, modal/sheet, keyboard, permission, error/empty state가 검증된 것으로 보지 않는다. 화면 UX는 Maestro, `agent-device`, `TEST_COVERAGE_GRAPH.md`로 별도 확인한다.
- coverage를 올리기 위해 의미 없는 snapshot/assertion을 추가하지 않는다. 실제 status branch, validation branch, role/RBAC branch, lifecycle event branch를 보호하는 테스트를 우선한다.
- coverage가 낮은데 구현을 먼저 진행해야 하는 경우, 이유와 후속 보강 조건을 PR에 명시한다.

## Navigation and Back Handling

Expo Router는 앱 진입과 `/role/[role]` 같은 top-level route boundary를 맡는다. 역할 내부 화면 전환은 각 role app의 screen/surface state가 맡으므로, Android hardware Back이 내부 surface stack을 건너뛰고 온보딩 route로 빠지지 않게 관리해야 한다.

- header back CTA와 Android hardware Back은 같은 internal back handler를 호출한다.
- role home은 Expo Router back boundary다. role home에서 Android Back은 온보딩으로 돌아가도 되지만, role 내부 modal/detail/list/wizard/contract surface에서는 먼저 내부 stack을 소비한다.
- 배출자 chat room, 배출 상세, 배출 현황, request wizard처럼 nested state가 있는 화면은 이전 진입점을 명시적으로 보관한다. 예: chat room에서 배출 상세로 들어가면 back target은 history list가 아니라 원래 chat room이다.
- modal/contact sheet/search mode/emergency contact/camera/complete surface처럼 더 작은 child state가 있으면 Android Back은 가장 안쪽 child state부터 닫는다.
- screen routing이나 back hierarchy가 바뀌면 Maestro flow와 `apps/mobile/e2e/TEST_COVERAGE_GRAPH.md`를 함께 갱신한다.

## Maestro Strategy

Maestro는 반복 가능한 deterministic E2E gate다. 주요 flow는 YAML로 고정하고, PR마다 같은 명령으로 다시 실행할 수 있어야 한다.

Maestro 실행은 `scripts/e2e-maestro-dev-client.mjs` harness를 기준으로 한다. 이 harness는 Android emulator에서 접근 가능한 `10.0.2.2:8081` dev-client deep link로 앱을 열고, stale `agent-device`/Maestro process와 ADB forwarding을 정리한 뒤 YAML flow를 실행한다. `Unable to load script` red screen, Metro 미실행, Maestro driver startup timeout, 일반 flow 실패는 `artifacts/mobile/maestro/<flow>/latest.json`의 `failureKind`로 분리한다.

`pnpm --filter @zerro/mobile e2e:android:bootstrap`은 현재 Maestro 선행 gate가 아니라 `agent-device` selector proof 복구용 diagnostic command다. 앱 clean screenshot이 정상인데 `agent-device snapshot`이 Android `Back`, `Home`, `Recents`만 반환하면 화면 로직 실패로 보지 않고 `agent_device_sparse_snapshot` 계층으로 분리한다. Maestro driver package uninstall/reset은 기본 동작이 아니며, 복구 목적일 때만 명시 옵션으로 수행한다.

초기 계획 경로:

```txt
apps/mobile/e2e/maestro/
  role-routing.yaml
  emitter-home.yaml
  dispatcher-home.yaml
  driver-dispatch-schedule.yaml
  processor-home.yaml
```

Maestro flow는 다음 기준으로 작성한다.

- 첫 baseline은 role selection, 역할별 home 진입, 핵심 CTA 노출 확인이다.
- flow 이름에는 role과 screen을 포함한다.
- selector는 `testID`를 우선하고, visible text selector는 보조로만 사용한다.
- 실패 시 screenshot, video, commands JSON, report artifact를 남긴다.
- mock data seed가 바뀌면 flow와 fixture 변경을 같은 PR에 포함한다.
- screen routing, button route, 기능 검증 coverage가 바뀌면 `apps/mobile/e2e/TEST_COVERAGE_GRAPH.md`를 같은 PR에서 갱신한다.
- CI에 올리기 전까지는 local emulator/simulator evidence를 PR에 남긴다.

Maestro flow는 `apps/mobile/e2e/maestro/`에 둔다. repo dependency로 CLI를 추가하지 않고, local PATH의 Maestro CLI를 사용한다. 실행 script는 `apps/mobile/package.json`의 `e2e:maestro*`를 기준으로 하며, artifact는 repo root 기준 `artifacts/mobile/maestro/`에 남긴다.

## Agent-device Strategy

`callstack/agent-device`는 AI based E2E와 exploratory verification을 위한 실제 device control layer다. Maestro가 정해진 회귀 flow를 반복한다면, `agent-device`는 AI agent가 실제 화면을 보고 막힘, 접근성 누락, 예외 상태, 터치 불가능 영역을 찾는 데 사용한다. ZERRO APP 개발에서는 `agent-device` 관찰이 선택 사항이 아니라 모든 앱 화면/상호작용 작업의 UX gate다.

사용 목적:

- Android Emulator 또는 iOS Simulator에서 실제 앱을 열고 현재 UI snapshot을 확인한다.
- snapshot ref를 기반으로 tap, type, scroll, wait, assertion을 수행한다.
- clean screenshot, selector snapshot, log, crash context, replay script 같은 증거를 필요한 순간에 수집한다.
- 탐색 중 유효한 flow가 발견되면 Maestro flow 또는 `.ad` replay script 후보로 승격한다.

Android local loop 예시:

```bash
pnpm --filter @zerro/mobile e2e:android:bootstrap
pnpm --filter @zerro/mobile e2e:agent-device:devices
pnpm --filter @zerro/mobile e2e:agent-device:attach
pnpm --filter @zerro/mobile e2e:agent-device:snapshot
adb -s emulator-5554 exec-out screencap -p > artifacts/mobile/agent-device/<screen-slug>.png
```

`agent-device`는 `apps/mobile` devDependency와 `scripts/e2e-agent-device.mjs` wrapper를 기준으로 실행한다. wrapper는 Android SDK `platform-tools`를 `PATH`에 주입하고, current worktree의 stale daemon이 `adb not found in PATH`를 반환하면 daemon을 재시작한 뒤 1회 재시도한다. 실행 script는 `apps/mobile/package.json`의 `e2e:agent-device:*`를 기준으로 한다. UX/Figma 비교용 screenshot artifact는 red box overlay가 없는 clean ADB capture를 repo root 기준 `artifacts/mobile/agent-device/`에 남긴다.

Maestro flow 직후 화면을 보존해 확인할 때는 `e2e:agent-device:open`을 쓰지 않는다. `open` script는 `--relaunch`를 포함한 exploratory bootstrap이므로 화면을 초기화할 수 있다. after-flow evidence는 `e2e:agent-device:attach`로 package session만 만든 뒤 selector `snapshot`을 실행하고, visual evidence는 clean ADB capture로 남긴다. Android는 macOS desktop용 `--surface frontmost-app` attach를 사용하지 않는다.

Known limitation:

- `agent-device snapshot -i`가 앱 화면 대신 Android navigation node만 반환하는 경우가 있다. 이 경우 clean screenshot이 정상 앱 화면이면 PR evidence에는 screenshot을 visual truth로 남기고, selector proof 실패는 `apps/mobile/COMMON_MISTAKES.md`의 `agent_device_sparse_snapshot` troubleshooting으로 연결한다.

## Local Verification Gate

앱 화면 작업 PR은 최소 아래 증거를 남긴다.

| 범위 | 필수 확인 |
|---|---|
| Type | `pnpm --filter @zerro/mobile check` |
| Unit / coverage | contracts, mocks, domain selector, mutation branch 변경 시 targeted test와 coverage 확인 |
| Asset | `pnpm --filter @zerro/mobile assets:verify` |
| Runtime | Expo dev server 또는 native run으로 target route 진입 |
| Role | 해당 role 외 접근/표시가 섞이지 않는지 확인 |
| Mock | `@zerro/contracts`, `@zerro/mocks` 기준 fixture 사용 |
| Maestro | 작성된 flow가 있으면 local 실행 결과와 artifact 경로 |
| Agent-device | 모든 앱 화면/상호작용 변경에서 실제 emulator/simulator 조작 결과와 selector snapshot proof |
| Clean screenshot | UX/Figma/PR 보고용 red-box overlay 없는 ADB 또는 overlay-free Maestro screenshot artifact |
| Visual | 관련 Figma node와 app screenshot의 주요 차이 기록 |

## Mobile Observability

APP Sentry는 `@sentry/react-native`와 Expo/Metro plugin을 기준으로 한다. Sentry package version은 latest가 아니라 `expo install --check`가 기대하는 Expo SDK 호환 버전을 우선한다. Expo SDK 56 기준 현재 호환선은 `@sentry/react-native@7.11.0`이다.

로컬과 CI는 `EXPO_PUBLIC_SENTRY_DSN`이 없으면 이벤트를 전송하지 않는다. DSN이 없을 때는 root wrapper도 identity로 두어 local dev-client/React Navigation mount timing에 Sentry wrapper side effect가 끼지 않게 한다.

환경 변수:

```bash
EXPO_PUBLIC_SENTRY_DSN=...
EXPO_PUBLIC_APP_ENV=development
SENTRY_ORG=...
SENTRY_PROJECT=...
SENTRY_AUTH_TOKEN=...
```

- `EXPO_PUBLIC_SENTRY_DSN`: runtime event 전송용 public DSN.
- `SENTRY_ORG`, `SENTRY_PROJECT`, `SENTRY_AUTH_TOKEN`: release/native build 또는 EAS Update sourcemap upload용. token은 commit하지 않는다.
- Sentry dependency 변경 또는 fresh install 뒤에는 Metro를 `--clear`로 재시작하고 Android prebuild/debug build/install을 다시 실행해 `:sentry_react-native:*` Gradle task가 현재 버전으로 포함되는지 확인한다.
- mock API error 전송 범위는 `docs/20-contracts/API_CONTRACT.md`의 `Mobile Sentry Error Reporting Policy`를 따른다. APP에서 Sentry issue로 보고하는 대상은 critical logic failure로 제한한다.
- validation, 권한, not found, conflict, workflow mismatch, 단발성 network/provider 실패는 화면 상태 또는 breadcrumb만 남기고 issue로 만들지 않는다.
- Sentry context는 bounded metadata만 허용한다. raw backend body, field value, 전화번호, 파일명, 인증값은 전송하지 않는다.

## Done Criteria

- 관련 Figma node id가 PR에 적혀 있다.
- route, role, state, CTA, mock API 계약이 문서 또는 PR에 명시되어 있다.
- interactive element에 stable `testID`와 접근성 정보가 있다.
- typecheck가 통과한다.
- 가능한 범위에서 Maestro flow가 통과한다.
- `agent-device`로 실제 device surface를 열고, 해당 변경의 핵심 UX를 조작했으며, selector snapshot 또는 조작 로그를 남겼다.
- UX/Figma/PR 보고용 screenshot은 red box overlay 없는 clean capture로 남겼다.
- Figma 대비 의도적 차이가 있으면 이유를 남겼다.
- production-level UI gate 중 통과/미흡/placeholder 항목을 PR 또는 관련 문서에 남겼다.
- 외부 링크, 전화, 지도, 파일 viewer, upload provider를 건드렸다면 `docs/40-mobile/policies/PROVIDER_POLICY.md`의 adapter/state/testID 기준을 따른다.

## Open Decisions

| 항목 | 결정 시점 |
|---|---|
| `.ad` replay script 보관 위치 | `apps/mobile/e2e/agent-device/replays/`를 기본으로 하되 첫 replay 승격 PR에서 확정 |
| pixel-level screenshot diff threshold | design token baseline이 만들어진 뒤 |
