# TemseeEdu Mobile App — Product Requirements & Build Plan (Flutter)

Status: **Planning only — not started.** This document defines what a companion mobile
app for TemseeEdu should be, why, and how it should be built, so that work can begin
later without re-deriving context. It is grounded in the actual current state of the
TemseeEdu codebase (see §2), not a generic mobile-app template.

## Table of contents

1. [Why a mobile app, and why now-later](#1-why-a-mobile-app-and-why-now-later)
2. [Current system context (what mobile has to plug into)](#2-current-system-context-what-mobile-has-to-plug-into)
3. [Goals and non-goals](#3-goals-and-non-goals)
4. [Personas and primary jobs](#4-personas-and-primary-jobs)
5. [Architecture decision: API layer](#5-architecture-decision-api-layer)
6. [Multi-tenancy on mobile](#6-multi-tenancy-on-mobile)
7. [Feature scope by phase](#7-feature-scope-by-phase)
8. [Screen-by-screen scope (Phase 1 & 2 detail)](#8-screen-by-screen-scope-phase-1--2-detail)
9. [Flutter technical approach](#9-flutter-technical-approach)
10. [Push notifications](#10-push-notifications)
11. [Payments on mobile](#11-payments-on-mobile)
12. [Security & authorization](#12-security--authorization)
13. [Non-functional requirements](#13-non-functional-requirements)
14. [Rollout plan](#14-rollout-plan)
15. [Effort estimate](#15-effort-estimate)
16. [Success metrics](#16-success-metrics)
17. [Open questions](#17-open-questions)

---

## 1. Why a mobile app, and why now-later

TemseeEdu today is a set of five server-rendered Filament panels reached through a
mobile browser. That works, but it loses to a native app on exactly the things parents
and teachers do most often and most urgently:

- **Push, not SMS.** Announcements and fee reminders currently go out by SMS (Arkesel)
  or email, both of which cost the school money per message and aren't guaranteed to be
  read promptly. A mobile app with push notifications gives a free, instant, high-open-rate
  channel for the same announcements — this is a direct cost saving for schools, not just
  a UX nicety.
- **Fee payment friction.** Parents currently have to open a mobile browser, navigate to
  the Parent Portal, log in, find the invoice, and go through a web Paystack checkout.
  A dedicated "Pay Now" push notification → one-tap-to-checkout flow removes most of that
  friction.
- **Teachers in the classroom.** Taking attendance or entering scores from a phone in a
  classroom (rather than a laptop in the staff room) is a real behavior change a purpose-built
  app enables that a responsive website doesn't, especially on patchy Ghanaian mobile data.
- **Brand presence.** A school-branded app icon on a parent's home screen is a retention
  and trust signal that a bookmarked URL isn't.

None of this requires rebuilding TemseeEdu — it requires exposing a slice of what
already exists through an API, and building a focused, fast, offline-tolerant client on
top of it. This document scopes exactly that slice.

## 2. Current system context (what mobile has to plug into)

These are load-bearing facts about the existing system, established by direct
inspection of the codebase — not assumptions. Re-verify before implementation if a lot
of time has passed since this document was written.

- **No API exists today.** There is no `routes/api.php`, no Sanctum/Passport dependency
  in `composer.json`, and no `app/Http/Controllers/Api` namespace. Every panel
  (`/admin`, `/temsee`, `/teacher`, `/parent`, `/portal`) is server-rendered Filament +
  Livewire. **Building the mobile app requires building a JSON API first** — this is not
  optional prerequisite work, it's Phase 0 of this plan (§7).
- **Auth guards, and what they cover:**
  | Guard | Driver | Covers | Panel(s) |
  |---|---|---|---|
  | `admin` | session | School Admin **and** Teacher accounts (same `users` table, differentiated by Spatie role/permission) | `/admin`, `/temsee`, `/teacher` |
  | `parent` | session | Guardian accounts | `/parent` |
  | `student` | session | Student accounts | `/portal` |

  There is **no separate `teacher` guard** — a teacher is a `User` row with a role. This
  matters for API design: a single `/api/v1/auth/login` against the `admin` guard's
  provider must return enough role/permission info for the app to decide whether to show
  the Teacher UI or (later) an Admin UI, rather than assuming "guard = app surface."
- **Multi-tenancy is domain-based** (`stancl/tenancy`, `InitializeTenancyByDomain`).
  There is no central "log in and pick your school" directory today — a browser hitting
  `edu.uptimacredit.com` is *already* inside that school's tenant context. A mobile app
  has no equivalent implicit context and must be told which school it's talking to
  before any API call (see §6).
- **Payments** go through Paystack server-side today
  (`app/Http/Controllers/PaystackController.php`, `PaystackCheckoutController.php`),
  card and Ghana mobile money, with an ownership check added this session (a parent may
  only checkout/download receipts for their own linked children — enforced via
  `ParentUser::students()`). **Any mobile payment endpoint must replicate this exact
  ownership check** — it is the one place a past security gap existed and was fixed; the
  API layer must not reopen it.
- **Notifications** flow through Laravel's notification system: database notifications
  (bell icon, all 4 panels), email (SMTP or Resend/SES, tenant-overridable), SMS
  (`App\Services\SmsService`, Arkesel/Hubtel/custom, Ghana E.164 normalization), and — as
  of this session — a working desktop web-push banner listening for Filament 4's real
  `notificationSent` event. There is **no FCM/APNs integration** yet; that's new
  infrastructure this plan adds (§10).
- **Branding is already centralized** in `App\Services\SchoolBranding` — school name,
  logo, favicon, primary/secondary color, all tenant-aware with sensible fallbacks. This
  is directly reusable: expose it as a public, unauthenticated API endpoint so the
  mobile app can theme itself per school before login, exactly like the web panels do.
- **Editions matter.** `config('temseeedu.edition')` is `cloud` or `school_edition`.
  Cloud is TemseeEdu's own multi-tenant SaaS; School Edition is a single-tenant
  self-hosted install a school runs on their own cPanel/VPS. A School Edition install has
  no relationship to any central TemseeEdu server (deliberately — see
  `deployment_guide.md`'s licensing notes). **The mobile app must work identically
  against a self-hosted School Edition domain as against a Cloud tenant domain** — it
  talks to "whatever school domain the user gave it," never to a TemseeEdu-operated
  backend. There is no app-side special-casing between the two editions beyond feature
  flags the API already exposes (`plugins.*`).

## 3. Goals and non-goals

**Goals (v1, i.e., Phases 0–3):**

- Parents can view their children's published results, attendance, and invoices, and
  pay a fee, from a native app with push notifications for announcements and fee
  reminders.
- Students can view their own published results, attendance, timetable, and exam
  schedule.
- Teachers can take attendance and enter scores for their timetabled classes from a
  phone, and see their own timetable/invigilation duties.
- The app is school-branded per tenant (logo, colors) and works against both Cloud and
  School Edition installs without code changes.

**Explicit non-goals for v1:**

- **No School Admin app.** The back office (fee setup, staff management, academic
  setup, website CMS, module configuration) stays web-only. It's inherently a
  desk/desktop workflow with dozens of interconnected screens; cramming it into a phone
  is a v2+ conversation at best, and only worth it if Phase 1–3 prove out engagement.
  §7 revisits this as an explicitly deferred Phase 4.
- **No offline-first data entry with conflict resolution.** The app should tolerate flaky
  connectivity (queue-and-retry for attendance/score submission, §9), but it is not
  building a full offline database sync engine.
- **No feature-module coverage** (Library, Transport, Hostel, Inventory, Payroll,
  Accounting, Alumni, Exams-hall-ticket generation, etc.) in v1. These are optional,
  unevenly adopted per school, and add a lot of surface area for comparatively low
  mobile-specific value. Revisit per-module only if a specific pilot school asks.
- **Not a re-architecture of the backend domain model.** The API layer is a read/write
  JSON view over what already exists in `app/` and `app-modules/`, not a new source of
  truth. If a business rule already exists server-side (e.g., "a published result can't
  be edited by non-Super-Admins"), the API enforces the *same* rule — it doesn't get to
  invent a looser one for mobile's convenience.

## 4. Personas and primary jobs

| Persona | Primary jobs on mobile | Frequency |
|---|---|---|
| **Parent** | Check results when published; pay/track fee invoices; get announcement & fee-reminder push; view child's attendance | Several times/week, spikes at result-publish and fee-due dates |
| **Student** | Check own results; check timetable/exam schedule; get announcements | Several times/week, spikes at result-publish |
| **Teacher** | Take attendance in class; enter scores after an assessment; check own timetable/duties | Daily during school hours |
| School Admin | *(v1: web only — not a mobile persona)* | n/a |

Parent is the primary persona: highest frequency of high-stakes actions (money, a
child's academic standing), the largest total user count per school, and the clearest
cost-saving story (push replacing paid SMS). **Recommendation: build Phase 1 for
Parents first**, not Teacher-first or a shared shell for all three at once — a focused
single-persona MVP ships faster and gives a real usage signal before the Student and
Teacher phases are built on the same API foundation.

## 5. Architecture decision: API layer

**Decision: build a versioned, token-authenticated JSON API using Laravel Sanctum,
mounted at `/api/v1/...`, tenant-resolved exactly like the existing web routes.**

Why Sanctum over Passport or a bespoke JWT: Sanctum is the Laravel-native fit for
"first-party mobile app talking to its own backend" (as opposed to third-party OAuth
clients, which is Passport's use case). It supports personal access tokens per user,
per-token abilities (useful for scoping a teacher token to teacher-only endpoints), and
needs no extra infrastructure. It is also the option every recent Laravel mobile-API
guide converges on, minimizing implementation risk.

Structure:

```
routes/api.php                          # new
app/Http/Controllers/Api/V1/            # new namespace
    Auth/LoginController.php            # POST /api/v1/auth/login  (per-guard)
    Auth/LogoutController.php
    Parent/ChildrenController.php
    Parent/InvoiceController.php
    Parent/ResultController.php
    Parent/AttendanceController.php
    Student/...
    Teacher/AttendanceController.php
    Teacher/ScoreController.php
    Public/BrandingController.php       # unauthenticated, theming
app/Http/Resources/Api/V1/...           # Laravel API Resources for consistent JSON shape
```

Each endpoint is a thin adapter over **existing** Eloquent models and existing
authorization rules (the same policies/ownership checks the Filament resources already
use) — it must not duplicate business logic, only expose it. Where a Filament
resource's query already scopes data correctly (e.g., a parent's invoice list scoped to
their linked children), the API controller reuses that same scoping code path rather
than re-deriving it.

Auth flow per guard:

```
POST /api/v1/auth/login
  { "guard": "parent" | "admin" | "student", "email", "password", "device_name" }
  → { "token": "...", "user": {...}, "role": "teacher" | "school_admin" | null }
```

`device_name` follows Sanctum convention (labels the token for later revocation, e.g.
"Amara's iPhone 13"). The `role` field in the response is what the Flutter app uses to
decide whether an `admin`-guard login shows the Teacher UI shell (a Teacher-role user)
— since, per §2, there's no separate teacher guard to branch on.

**2FA note:** School Admin and Teacher accounts can have TOTP 2FA enabled
(`RequireTwoFactor` middleware on the web side). The API login flow must support a
second step (`POST /api/v1/auth/2fa/verify`) mirroring the existing
`TwoFactorController` web flow, returning the Sanctum token only after the code is
verified.

## 6. Multi-tenancy on mobile

This is the one piece of the architecture with no direct web equivalent, because a
browser gets tenant context for free from the URL bar and a mobile app doesn't.

**Decision: the app asks the user for their school's web address (or a QR code /
deep link containing it) once, on first launch, and stores it as the API base URL for
all future requests.** This works identically for a Cloud tenant
(`ameschool.temseeedu.com`) and a School Edition install
(`edu.uptimacredit.com`) — the app never needs to know or care which edition it's
talking to.

Concretely:

1. **First-launch screen**: "Enter your school's website address" (a text field
   pre-filled with `https://`), plus a **"Scan QR code"** option. The school admin can
   generate/print this QR code from the Admin Portal (a small new Settings feature:
   render a QR encoding `https://{tenant-domain}`).
2. The app calls `GET /api/v1/public/branding` against that base URL to fetch the
   school's name, logo, and colors (via `SchoolBranding`, §2) and confirm it's a real
   TemseeEdu install before asking for login credentials. This also lets the app theme
   its own login screen per school, matching the web portals' tenant-branded look.
3. The base URL is persisted (secure local storage) and reused on every subsequent
   launch — the user only sees the "enter school" screen once, or again after an
   explicit "switch school" / logout-and-forget action.
4. **Multi-school parents** (a guardian with children at two different TemseeEdu
   schools) are an open question — see §17. The straightforward v1 answer is: one
   logged-in school per app installation at a time, with an explicit "Switch School"
   action that re-runs the first-launch flow and swaps the stored base URL + token. This
   is simple to build and honest about the current single-domain-per-tenant
   architecture; a true multi-school single-login experience would need a central
   directory service that doesn't exist today and is out of scope for v1.

## 7. Feature scope by phase

| Phase | Scope | Depends on |
|---|---|---|
| **Phase 0 — API foundation** | Sanctum install, `/api/v1` skeleton, per-guard login (+ 2FA step), API Resources for the core domain objects (Student, Invoice, Result, Attendance, Announcement), public branding endpoint, Postman/OpenAPI collection for the Flutter team to build against | Nothing — pure backend work |
| **Phase 1 — Parent app (MVP)** | School-picker + branded login; My Children switcher; published Results view (read-only); Invoices list + Pay Now (WebView Paystack checkout, §11) + receipt download; Attendance view (read-only); Announcements feed; push notifications for new announcements & fee reminders; profile/change-password | Phase 0 |
| **Phase 2 — Student app** | Same app shell, Student login; My Results; My Attendance; My Timetable; My Exams; Notifications feed. Mostly reuses Phase 1's UI patterns against different endpoints | Phase 0, benefits from Phase 1's shell |
| **Phase 3 — Teacher app** | Same app shell, Admin-guard login with Teacher role; Take Attendance (class + date picker, mark present/absent/late/excused, offline queue); Score Entry (subject/term, simple or 9-component CA form depending on school's grading mode); My Timetable; My Invigilations; staff Announcements | Phase 0 |
| **Phase 4 — Admin app (deferred, re-scope after Phase 1–3 usage data)** | Read-only dashboard KPIs, admissions pipeline approvals, one-tap result publishing, broadcast an announcement. Explicitly *not* committed to — revisit only if Phases 1–3 show strong engagement and a specific pilot school asks for it | Phases 0–3 proven |

Recommendation: ship Phase 1 alone to a pilot school (this document's real-world anchor
tenant, AME Zion Girls' High School, is a natural first pilot), get real usage data on
whether parents actually install and pay through it, *then* commit to Phase 2/3 rather
than building all three roles' apps before any real-world validation.

## 8. Screen-by-screen scope (Phase 1 & 2 detail)

Kept intentionally 1:1 with what already exists in the Parent/Student Filament panels
and the in-product user manual (`resources/views/user-manual.blade.php`), so v1 scope
is "the mobile-shaped version of what's already shipped," not new product design.

**Parent app (Phase 1):**

| Screen | Maps to (web equivalent) | Notes |
|---|---|---|
| School picker / QR scan | — (new, mobile-only) | §6 |
| Login (+ 2FA if enabled) | Parent Portal sign-in | |
| Home / My Children | `My Children` | Card per child, tap to switch active child context |
| Results | `Report Cards` | Published only, by term; PDF download/share |
| Invoices | `My Invoices` | List, balance, status chip; tap → detail → Pay Now |
| Pay Now | Paystack checkout (web) | WebView wrapping the existing checkout URL, §11 |
| Receipt | Receipt download (web) | PDF view/share, same ownership-checked endpoint |
| Attendance | (currently no dedicated parent attendance view on web — new) | Simple calendar/list of a child's daily attendance |
| Announcements | `Notifications` | Feed + push |
| Contact School | `Contact School` | Simple form → existing message endpoint |
| Profile / Change password / Logout | Account settings | |

**Student app (Phase 2):** same shell pattern, sourced from `My Results`, `My Academic
History`, `My Attendance`, `My Exams`, `My Timetable`, `My Documents`, `Notifications`
as documented in the in-product user manual's Student Portal reference table.

## 9. Flutter technical approach

- **State management: Riverpod.** Recommended over Bloc/Provider/GetX for this app's
  shape — mostly server-driven read views with a handful of forms (attendance, score
  entry, payment), not complex client-side state machines. Riverpod's async providers
  map cleanly onto "fetch this list, show loading/error/data" without Bloc's
  event-boilerplate overhead.
- **Networking:** `dio` with an interceptor that (a) attaches the stored Sanctum
  bearer token, (b) attaches the stored per-install base URL (§6) as the request root,
  (c) centrally handles 401 → force re-login, (d) retries idempotent GETs with backoff on
  network failure (relevant for patchy Ghanaian mobile data). Hand-written repository
  classes over a generated client (Retrofit-style codegen) are fine at this scope —
  revisit codegen only if the API surface grows past Phase 3.
- **Local storage:** `flutter_secure_storage` for the auth token and base URL;
  a lightweight cache (`Hive` or `sqflite`) for "last successfully loaded" results,
  attendance, and invoices so the app shows *something* instantly on a cold, offline
  launch, with a "last updated at" timestamp and pull-to-refresh.
- **Offline-tolerant writes (Teacher app, Phase 3):** attendance and score submissions
  queue locally and retry on connectivity return, with a visible "pending sync" badge —
  this is the one place offline handling is load-bearing (a teacher mid-lesson with no
  signal must not lose their work), everywhere else is read-mostly and a simple
  loading/error/retry pattern is sufficient.
- **Theming:** fetch `/api/v1/public/branding` at the school-picker step and generate a
  `ThemeData` from the returned primary/secondary color + logo, mirroring what
  `SchoolBranding` already drives on the web side. One shared theme engine, reused
  identically for Parent, Student, and (later) Teacher app variants — this is one
  codebase producing role-appropriate UI, not three separate Flutter projects (see
  decision in §17).
- **Package/App structure:** a single Flutter app with a role-aware shell chosen at
  login (`role: parent | student | teacher`), not three separate app store listings.
  One codebase, one release train, less duplicated UI work, and it matches the backend
  reality that a Teacher is just a differently-permissioned `admin`-guard user.

## 10. Push notifications

**New infrastructure, does not exist today.** Recommended: **Firebase Cloud Messaging
(FCM)** — a single unified send API that reaches both Android and iOS (Apple Push via
FCM's APNs bridge), which is simpler to operate than maintaining raw APNs certificates
directly, and has a generous free tier appropriate for a school's notification volume.

Backend work required:

- New table: `device_tokens` (`user_type`, `user_id`, `fcm_token`, `platform`,
  `last_seen_at`), tenant-scoped like every other tenant table.
- `POST /api/v1/devices` to register a token on login/app-open.
- A new `FcmChannel` implementing Laravel's `Notification` channel interface, added
  alongside the existing `mail`/`database`/SMS channels wherever Announcements and fee
  reminders are currently dispatched (`App\Notifications\...` classes) — this is an
  *additive* channel, not a replacement for SMS/email (some parents will only ever use
  the web/SMS path; the app is an additional reach channel, not a required one).
- **School Edition graceful degradation:** a self-hosted school may not configure FCM
  server credentials. The API's branding/config response should expose a
  `push_enabled: boolean` the app uses to fall back to polling (simple periodic
  "any new announcements/invoices since X" check) instead of silently doing nothing.

## 11. Payments on mobile

**Decision for v1: WebView-wrapped Paystack checkout**, reusing the exact
server-side checkout flow and ownership check that already exists
(`PaystackController::checkout`), rather than integrating Paystack's native Flutter SDK.
This ships faster, requires zero new backend payment logic, and inherits the existing
ownership-check fix (§2) automatically since it's the same server route. Revisit a
native SDK integration only if WebView checkout proves to be a real conversion-rate
problem in practice — not a default v1 assumption.

Apple/Google app store note: fee payment for a real-world service (school tuition) is
not subject to Apple/Google's in-app-purchase requirements (those apply to digital
goods/content consumed within the app) — this is a standard, well-established exemption
category (same one used by ride-hailing, food delivery, and other real-world-service
apps), but should be sanity-checked against current store guidelines close to
submission time, since store policy specifics do shift.

## 12. Security & authorization

Non-negotiable carry-overs from the web app, restated here because this is exactly the
category of thing that regresses quietly when a new client is built against "the same"
backend by someone who wasn't present for the original fix:

- **Ownership checks are mandatory on every parent/student-facing endpoint.** The
  precedent: this session found and fixed a real gap where a parent could
  view/pay another family's invoice by guessing its ID
  (`app-modules/Payments/app/Http/Controllers/{ReceiptController,PaystackController}.php`).
  Every new API controller touching a specific child/invoice/result **must** check
  `auth('parent')->user()->students()->pluck('id')->contains($studentId)` (or the
  student-guard equivalent: the record belongs to `auth('student')->id()`) before
  returning or mutating data — no exceptions, and this should be a code-review checklist
  item, not something re-derived per endpoint.
- **Published-results locking rule carries over unchanged.** A teacher's score-entry API
  endpoint must reject edits to already-published results exactly like the Filament
  resource does; the API is not a backdoor around that rule.
- **Token scope by guard.** A `parent`-guard token must not be usable against
  `teacher`/`admin` endpoints even if the same person somehow also has a staff account —
  Sanctum's per-token abilities should be set at issuance based on the guard used to log
  in, and every controller should assert the expected ability, not just "is
  authenticated."
- **Rate limiting** on `/api/v1/auth/login` (brute-force protection) and on payment
  initiation endpoints, mirroring the existing `throttle:installer` pattern already used
  elsewhere in the app.
- **Tenant isolation.** Every API request resolves tenant exactly as the web request
  does (domain-based). There is no "cross-tenant" API surface in this plan at all — a
  mobile app instance is bound to one school's domain for its lifetime (until an
  explicit "switch school," §6), which structurally prevents the class of bug where a
  request accidentally queries the wrong tenant's database.

## 13. Non-functional requirements

- **Performance:** every list endpoint paginated (never "return all invoices"); logo/
  avatar images cached client-side; API responses kept minimal (Laravel API Resources,
  not raw Eloquent `toJson()`, to avoid leaking unnecessary columns and to control
  payload size on slow connections).
- **Connectivity resilience:** designed for intermittent Ghanaian mobile data —
  timeouts + retry/backoff, cached last-known-good data shown with a staleness
  indicator rather than a blank error screen.
- **Backward compatibility:** the API is versioned (`/api/v1`) from day one specifically
  so a future breaking change ships as `/api/v2` without forcing every installed app to
  update simultaneously — school mobile devices update on their own schedule, not the
  backend's.
- **Accessibility:** standard Flutter accessibility support (screen reader labels,
  minimum tap target sizes, respect system font scaling) — no exotic requirements, just
  don't skip the defaults.
- **Privacy:** the app handles student PII (names, results, attendance) and payment
  flows — a privacy policy and terms of service (app-store-required) need to exist
  before submission; confirm whether these already exist for the web product or need
  drafting fresh.

## 14. Rollout plan

1. Build Phase 0 (API) against the existing dev/staging environment; write the
   Postman/OpenAPI collection as the contract for Flutter work to start against, so
   backend and mobile work can proceed in parallel once the contract is fixed.
2. Build Phase 1 (Parent MVP) Flutter app against that contract.
3. **Pilot with one real school** — AME Zion Girls' High School (this codebase's actual
   current tenant) is the natural first pilot, since it's already live with real data,
   Paystack integration, and SMS/email configured. Internal TestFlight/Play Console
   internal-testing track first, not a public store listing.
4. Gather real usage data (install rate, % of invoices paid via app vs web, push
   opt-in rate) before committing to Phase 2/3.
5. Public store submission only after the pilot validates the core Parent flow — a
   half-built multi-role app in the store is worse than a focused one that works well.

## 15. Effort estimate

Rough, directional sizing only — assumes one full-time Flutter developer and one
backend developer extending the Laravel API part-time alongside their other work. Treat
as a planning input, not a committed schedule.

| Phase | Rough effort |
|---|---|
| Phase 0 — API foundation (Sanctum, endpoints, resources, docs) | 3–4 weeks |
| Phase 1 — Parent app (MVP) | 5–7 weeks |
| Push notification infra (FCM, can overlap with Phase 1) | 1–2 weeks |
| Phase 2 — Student app | 3–4 weeks (reuses Phase 1 shell) |
| Phase 3 — Teacher app | 4–5 weeks (offline queue adds complexity) |
| Pilot, feedback, store submission prep | 2–3 weeks |

## 16. Success metrics

- **Install rate:** % of a pilot school's parents who install the app within one term
  of launch.
- **Payment channel shift:** % of fee invoices paid via the app vs. the existing web
  checkout, and whether total on-time payment rate improves.
- **Push adoption & effect:** push opt-in rate, and whether announcement/fee-reminder
  SMS volume (and therefore SMS cost) measurably drops after app rollout.
- **Teacher time-to-submit:** qualitative/quantitative check on whether attendance and
  score entry happen faster or more consistently once teachers can do it from a phone
  in the classroom rather than waiting for a staff-room computer.
- **Support load:** change in password-reset / "how do I..." support requests after
  launch (the in-product user manual, §2, is the existing baseline mitigation for this).

## 17. Open questions

These need a decision before or during Phase 0/1, but don't block writing this plan:

- **Multi-school parents.** §6 proposes one-school-per-login with an explicit switch
  action. Confirm this is acceptable, or scope a central school-directory service
  (Cloud-edition-only, since School Edition has no central server by design) if
  cross-school single-login is a hard requirement.
- **One app vs. per-role apps.** This document recommends a single Flutter codebase with
  a role-aware shell (§9). Revisit only if store-listing/branding reasons (e.g., wanting
  "TemseeEdu for Teachers" as a distinct, separately-marketed product) outweigh the
  extra maintenance cost of three app store listings.
- **Payment SDK.** WebView checkout is the v1 recommendation (§11); revisit native
  Paystack SDK integration if conversion data from the pilot says otherwise.
- **Phase 4 (Admin app).** Deliberately unscoped pending Phase 1–3 usage data — don't
  pre-build it.
- **Privacy policy / ToS ownership.** Needs an owner and a source of truth before app
  store submission; not something to draft silently inside this engineering plan.
- **FCM project ownership.** Whether push credentials are provisioned per-school
  (School Edition self-hosted schools configuring their own Firebase project) or
  centrally by TemseeEdu for Cloud tenants — affects the `push_enabled` config story in
  §10 and should be decided alongside Phase 0.
