# Mobile app spec (v1) — one app, two roles

Last updated Oct 27, 2026 · Page: https://freebeeads.com/docs/repo/mobile-spec · Markdown: https://freebeeads.com/docs/md/repo/mobile-spec.md · All docs: https://freebeeads.com/llms-full.txt

**Decision:** ONE Flutter app for both advertisers and ops, role-gated at runtime from
`GET /v1/me` (`role: advertiser | ops`) — never two apps. Ops is 1–2 people; a second codebase
would double the maintenance to serve them. Same pattern the web portal already uses.

## What the app is FOR (and what it is not)

The responsive website already does booking, uploads, and dashboards on a phone. The app exists
for the one thing the web cannot do: **push notifications**, plus a faster daily-glance surface.

**In scope (v1):**
- Advertiser: login/signup · campaigns list (lifecycle chips) · campaign detail (stat row, funnel,
  age/gender, by-day, by-screen) · live view · creatives list with status · push notifications.
- Ops (same app, role-gated): approvals queue with media preview + approve/reject · fleet health
  with freshness dots · **device pairing** (type the 6-char code standing at the screen) · push
  when a creative lands in the queue.
- Deep links from pushes into the relevant screen.

**Out of scope (v1) — deep-link to the website instead:**
- The 4-step booking wizard, creative upload/replace, venue/zone/screen registry editing,
  CSV export, payments. These are rare, form-heavy, or desktop-natural.

## Architecture

```
mobile/
  lib/
    api/         generated-by-hand client for docs/API_CONTRACT.md (dio), token storage
    models/      plain Dart models mirroring contract payloads
    features/
      auth/      login, signup, session
      campaigns/ list + detail (advertiser)
      live/      live screens grid (both roles)
      creatives/ list + status (advertiser, read-only in v1)
      ops/       approvals, fleet, pairing
    design/      tokens ported from packages/shared/design-tokens/tokens.json
    push/        FCM registration + handlers (behind ENABLE_PUSH flag)
  test/          widget + unit tests
```

- **State:** Riverpod (or provider) — keep it boring, no code-gen heavy stacks.
- **API base:** `--dart-define=API_BASE=https://api.freebeeads.com`,
  defaulting to prod so a TestFlight build "just works".
- **Auth:** same email+password JWT as web; token in flutter_secure_storage; 401 → logout.
- **Design:** port the light token set — paper `#F7F6F3`, card white, ink `#16181A`,
  accent `#D85A30`, data ramp green/blue/amber/red, mono for numbers only, weights 400/500.
  The app must look like the same product as the website.

## Push notifications (the reason the app exists)

Events → notification (payload carries **generic text + ids only**, never audience numbers):
- Advertiser: creative approved / rejected (with reason) · campaign went live · weekly summary
  ("Your week on Gody — tap to see plays and watchers").
- Ops: new creative awaiting approval · screen offline > 30 min (fleet alarm).

Backend work required (phase B): `device_tokens` table (user_id, fcm_token, platform),
`POST /v1/me/push-token`, and a small notifier invoked at approve/reject, campaign-start, and
fleet-alarm points. Firebase project + APNs key needed before real sends; until then the app
registers behind a flag and logs.

## Phases

- **A — shell + read surfaces (this build):** auth, role-gated navigation, campaigns list/detail,
  live view, creatives list, ops approvals/fleet/pairing, tokens/design parity, tests, runs on
  iOS simulator + Android emulator against production.
- **B — push:** backend token endpoint + notifier, FCM/APNs wiring, deep links.
- **C — distribution:** TestFlight (Apple Developer account exists) + Android APK/internal track.
- **D — later:** in-app booking wizard, upload/replace, offline caching of dashboards.
