QuranFlow Rebuild — Implementation Plan
Build plan for taking the Sakīna design (mockups in ../mockup/) to a
production iOS/Android app and coach tool. Companion to FEEDBACK-SYNTHESIS.md
(what stakeholders asked for) and ../mockup/REVIEW.md (the adjudicated
design decisions). This is the "IT revamp" — a full replacement of the current
app and its backend, not an incremental patch.
Context this plan assumes: a small org (1 program lead, 1 platform lead, ~11 part-time coaches), a student base on iOS today with Android demand likely next, and a coach population that is overwhelmingly mobile (9/11 mobile-only or mobile+desktop, 1 desktop-only per the questionnaire). No committed engineering headcount is named here — sizing in Section 5 is in engineer-weeks so it can be mapped to whoever ends up on it, whether that's one contractor or a small agency.
1. Technology recommendation
Client: React Native (Expo), not SwiftUI, not Flutter
Recommendation: React Native with Expo (EAS Build/Submit, Expo Router).
Rationale:
- Two platforms are in scope, one is not optional-later. The synthesis explicitly flags Android as "likely later" for students and a desktop-leaning coach app. SwiftUI ships iOS only — a second native codebase for Android would double the maintenance surface for a team with no dedicated iOS+Android pair. React Native gets both from one codebase on day one, which matters more for a coach app that a 1-in-11 user wants on desktop than it might for a bigger team that could afford two native apps.
- Single small team, likely one primary developer. RN's larger hiring pool and shared JS/TS skillset across student app, coach app, and (if needed) an admin web panel reduces bus-factor risk versus Swift-only expertise. See Risk R2 below — this is a real constraint, not a nicety.
- Audio is the hard requirement, and RN handles it adequately, not
effortlessly. Recording, resumable upload, scrubbing playback, and
waveform rendering are all solvable in RN (
expo-av/expo-audio, background upload viaexpo-file-system+ a resumable protocol, waveform via pre-computed peaks rendered in-app). It is not as clean as AVFoundation directly, budget explicit spike time for it (see Section 3). - Push notifications and Zoom deep links are both well-trodden RN territory
(Expo push service or FCM/APNs directly,
Linking/expo-linkingforzoommtg://URI schemes with a web-link fallback). Nothing here pushes toward native. - Coach desktop want is a minority-but-real need, not a v1 requirement. Expo Router + React Native Web gets a workable coach web view later without a second UI codebase, if and when it's prioritized. This is the tie-breaker against Flutter, which has no realistic path to reusing the same UI code as a React-based admin/coach web panel.
- Flutter is a reasonable second choice, not the pick. Its performance and single-codebase story are comparable to RN's, but it puts the team on Dart, a language with a smaller local hiring pool than JS/TS, and the org already has web/JS-literate people (the process-site and mockups in this repo are built with bun/JS tooling) — momentum favors staying in that ecosystem.
What would change this recommendation:
- If the org hires a dedicated iOS engineer and Android is genuinely deferred indefinitely (not "later" but "no"), native SwiftUI becomes competitive — better platform-idiomatic audio/notifications, no bridge overhead. Revisit if that hire happens before Phase 1 starts.
- If Arabic text rendering (Section 3) turns out to need custom text layout beyond what a WebView or RN's text primitives can do performantly, a native module (Swift/Kotlin) wrapping a font-shaping library may be needed regardless of the outer framework — this is additive to RN, not a reason to abandon it.
Backend: Managed BaaS over custom API
Recommendation: a managed backend-as-a-service (Firebase or Supabase), with the third-party integrations (Zoom, email, Stripe) as Cloud Functions/Edge Functions rather than a bespoke server.
Rationale:
- Team size is the deciding factor. A program lead and a platform lead are not going to operate a custom API's infra (deploys, scaling, patching, on-call) alongside everything else they run. A managed BaaS removes an entire operational category: auth, database, file storage (audio files), and realtime sync come out of the box.
- The data model (submissions, feedback, annotations, appointments, messages) is relational/document-shaped but not exotic — no need for a custom data engine. Supabase (Postgres + Row Level Security) is a good fit if the team wants SQL and stronger relational guarantees for the submission/feedback/attendance joins described in the wishlist; Firebase (Firestore) is a good fit if the team wants less schema ceremony and tighter native push/RN integration. Lean Supabase if the team is comfortable with SQL — the wishlist's business rules (2 submissions/week, 5–10 min revoke window, cross-teacher shared notes, semester calendar states) are naturally expressed as relational constraints and views, and Postgres RLS maps cleanly onto "coach sees own students, sees shared notes across coaches" access patterns.
- Integrations (Zoom, email, Stripe) don't need a general-purpose API server — they need scheduled/triggered functions: a webhook receiver for Stripe, a scheduled function for reminder emails/push, a Zoom API call to fetch attendance duration. Supabase Edge Functions or Firebase Cloud Functions cover this without standing up and hosting a separate service.
- Custom API becomes justified only if the org later needs bespoke business logic too complex for BaaS functions (e.g., the backlogged student-centric scheduling algorithm) at a scale where BaaS query limitations bite, or if they hire a backend-focused engineer who'd rather own infra than rent it. Not true today.
- Note: the current backend "of unknown quality" is being fully replaced, so there's no migration-path constraint pulling toward a like-for-like replacement — pick for the next five years, not for parity with what exists.
2. Build phases
Each phase ships something a student or coach can actually use, not just
scaffolding. Screen labels reference ../mockup/src/main.jsx artboard
labels.
Phase 1 — Core loop: auth, four tabs, lesson → recording → submission
The minimum viable app: a student can log in, see their week, watch a lesson, record and submit an attempt.
Screens (by artboard label):
- Section 00: Today, Learn — syllabus, Sessions — Tuesday timeline (static/no booking yet), Profile
- Section 01: Lesson — Idghām with ghunnah, Video player — chapter complete, Recording — Ready / In progress / Review / Submitted / No attempts
- Section 05: Onboarding steps 1–6
- Section 02 (partial): Personal details, Qur'an font picker (needed for Arabic rendering to work day one)
Backend surface:
- Auth (email/password or magic link — match whatever the org already uses for enrollment)
- Student, Course, Level, Lesson, Video-progress, Submission (audio file + status: not-started/in-progress/submitted/revoked) tables/collections
- Audio upload pipeline (record → resumable upload → storage) — see Section 3
- Weekly rhythm state machine: current week number, submission deadline, attempts-remaining counter, 2/week limit + revoke window enforcement
- Push notification registration (even if no notifications are sent yet — wire the token collection now)
Exit criteria: a real student can complete onboarding, watch this week's lesson, record an attempt, submit it, and see it move through Ready → In Progress → Submitted → (revoke window closes) with no coach-side app yet (feedback can be seeded manually/by hand for pilot testing). This phase is usable standalone if coaches temporarily keep using their current review process for one more cycle while Phase 2 builds.
Phase 2 — Feedback loop closure + coach review app
This is the single most-requested fix across all four feedback sources ("teachers can't see the assignment text while reviewing audio" is called the most damning fact in the current app) and makes coaches, the bottleneck users, actually adopt the new system.
Screens:
- Section F1: Submission — feedback ready, Submission — in review/revoke window, Practice the corrections, Practice — review take
- Section 02: Submissions archive, Notifications sheet, Notification settings
- Section 06: Submissions — empty archive, Notifications — all caught up, Recording — mic permission
- Section C (coach app, full): Coach — review queue, Coach — review a submission, Coach — student record, Coach — my schedule
Backend surface:
- Coach role + coach-student assignment table
- Feedback object: text-span-anchored annotations on assignment text, coach audio reply, pass/fail, timestamp, linked to the submission
- Full multi-semester submission/feedback history per student (queryable by both student and any coach who has taught them — cross-coach shared notes per the wishlist)
- Practice-take handling: takes attached to feedback, don't burn attempt count (per REVIEW.md C3)
- Coach queue view (submissions awaiting review, sorted/filtered)
- Notification triggers: feedback-ready push to student, new-submission push to coach
Exit criteria: a coach can open the queue, see assignment text and audio side by side, leave annotated + voice feedback, and the student sees it arrive, practices, and resubmits — all in-app, replacing notebooks/Excel/ second devices. Pilot with 2–3 coaches here (see Risk R3) before wider rollout.
Phase 3 — Schedule, notifications, announcements/messaging
Solves the second-most damaging problem class: timezone bugs, no attendance signal, and the community-board replacement decided on the coaches' call.
Screens:
- Section 00a: Sessions — Recordings
- Section F2: Week at a glance
- Section F3: Announcements, Messages — Ust. Amina
- Section 03: Live class — Sisters' QRC
- Section 04 (billing can slide to Phase 4 if tight, see note below)
SakSessionDetail/SakSessionDetailQRC(not on canvas, but required here — they're the notification-tap deep-link destinations per REVIEW.md C4)
Backend surface:
- Session/class schedule data model with UTC storage (see Section 3) — recurring sessions (weekly class, QRC) and one-off (drop-in Q&A)
- Zoom integration: deep link generation (
zoommtg://), attendance duration fetch (feeds Phase 4's auto-attendance) - Reminder scheduling: 15-min student / 30-min teacher pushes, calendar add (.ics or platform calendar intent)
- Announcement board: one-way, audience-filtered by year/level
- 1:1 messaging: guarded (length warning ~800 chars, weekly cap), coach ↔ student only, routes from both SakCoach and Profile per REVIEW.md B5
- Notification inbox aggregating session reminders + announcements + feedback-ready, with announcement rows deep-linking to the board (REVIEW.md C1/B4)
Exit criteria: every displayed time is correct in the viewer's local zone (this is the phase to run the DST/timezone test matrix from Section 3 against, not defer); a student gets a real Zoom link at the real local time; a coach and student can message inside guardrails; the community-board replacement is live for both years.
Phase 4 — Year-2 appointments, attendance automation, issue reporting, subscriptions
The long tail: features that matter but don't block Year-1 students' daily loop, plus the business-operations surface.
Screens:
- Section F4: Report an issue, Today — pre-semester countdown
- Section F5: Book your one-to-one, Appointment — recurring weekly, Availability (Year-2 booking)
- Section 04: Subscription & billing, Manage subscription
- Section 06: Today — rest day, Profile — first week (remaining states)
Backend surface:
- Appointment lifecycle: hybrid booking (recurring 15-week lock vs week-to-week), 15-min reminder, attendance capture, notes visible to any teacher who's met the student, auto-email escalation after 2 consecutive absences
- Auto-attendance from Zoom duration ≥75% (absorbs the Time Clock Wizard workflow, feeds coach hour-logging)
- Report Issue: auto-attached screen/app-version/account context, routed wherever the team currently triages support (email, ticketing tool). Global reachability without persistent UI: hook the iOS screenshot and shake gestures to open the report sheet with the screen attached (the TestFlight pattern). Decided 2026-07-16 in place of the floating button the teachers' call proposed — a permanent visible button invites misuse as a support/messaging channel.
- Live-session RSVP: a one-tap "Going / Can't make it" response on the session detail (now in the mockup), feeding a per-session headcount for staff. Attendance itself stays automatic (Zoom join data) and is shown to the student as a quiet summary line ("You've joined 6 of 8 circles"), never asked for — requested on the teachers' call and in the coaches' questionnaire (Rania: a way for students "to mark if they will attend")
- Stripe integration: subscription state, cancellation flow (framed as choice, not failure, per the mockup's enrollment section), webhook-driven status sync back to the student's access level
- Student availability capture during Year-2 booking/enrollment — not first-run onboarding (REVIEW.md C5) — feeds Year-2 scheduling
Exit criteria: Year-2 students can book and manage a 1:1 without an email chain; attendance is a real number instead of an assumption; billing state changes (upgrade/cancel/lapse) reflect correctly in what content a student can access.
Note: if Phase 3 runs long, subscription/billing (Section 04) can move earlier or later relative to appointments — it has no dependency on the scheduling work and can be sequenced independently based on business urgency (e.g., if Stripe migration has a deadline, pull it into Phase 3).
3. Cross-cutting workstreams
These don't belong to one phase — get them right early because retrofitting is expensive.
Timezone / DST correctness
Strategy: store every timestamp in UTC, render in the viewer's IANA timezone (not a fixed offset like "EST" — REVIEW.md's B3 finding, bare EST/EDT strings that had to be swept out of an earlier mockup iteration, is exactly the bug class to avoid), derive the timezone from device settings at render time, never trust a stored offset for the future.
This needs a dedicated test matrix, not ad hoc QA, because the reported production bug (calendar shows 7 PM for an 8 PM class) is a class of error that silently reintroduces itself:
- A US Eastern viewer and a Toronto viewer during the US/Canada DST gap (US switches ~1–3 weeks apart from EU some years — actually same week currently, but the code must not assume that)
- A Europe/EU viewer during the EU-vs-US DST transition week (the actual gap period where US and EU clocks are offset by a different amount than usual)
- Turkey (no DST since 2016, fixed UTC+3) and Egypt (DST history has flip-flopped by government decree in recent years) as no-DST or irregular-DST cases that break any hardcoded "add 7 hours" logic
- A recurring weekly booking that spans a DST transition — does the local wall-clock time stay fixed (correct) or does the UTC instant stay fixed (wrong, shows up as a 1-hour drift for half the recurrence)
Recommend this become an explicit automated test suite (fixed set of UTC instants × viewer timezones × expected rendered string), not manual spot-checks, given it's already bitten production once.
Audio pipeline
- Record: platform recording API via RN wrapper, capture at a consistent bitrate/format for consistent playback across devices
- Upload: resumable (chunked or tus-protocol-style) — coaches and students are plausibly on inconsistent connections; a dropped upload should resume, not restart or silently fail
- Playback + scrubbing: needs frame-accurate seek for coaches reviewing specific passages, and for students re-listening to feedback at a specific timestamp (feedback is timestamped per the wishlist)
- Waveforms: compute once server-side or client-side at upload time and store the peak data alongside the audio file — don't recompute on every playback
- Budget an early spike (before Phase 1 commits to final APIs) to confirm RN audio libraries meet the scrubbing/waveform bar; this is the piece most likely to need a native module fallback.
- Interruption survival (from the coaches' questionnaire): recordings in the current app are lost to incoming phone calls and to force-logout when a second device signs in. Recording must survive audio-session interruptions (pause + resume with the partial take intact) and an auth change must never discard an in-progress recording. Also from the questionnaire: feedback comment text is truncated in the current app — any text field that stores more than it displays is a bug class to test for explicitly.
Arabic text rendering — flag early, spike first
This is the hardest UI problem in the whole rebuild and should get a spike in Phase 1 before the recording/annotation screens are built on top of it, not discovered mid-Phase-2. The requirement (from the coach feedback: "mark up submission text... text-span anchored annotations") means annotations must anchor to specific words within Quranic Arabic text, which involves:
- Correct right-to-left, contextual letter-shaping rendering (standard in modern text stacks, but verify RN's text engine + chosen Quranic font handle it without breaking on diacritics)
- Word-level (not just character-offset) anchoring for annotations, robust to font/size changes and the font-picker feature (Section 02) — an annotation anchored to a pixel position breaks the moment the user changes font or text size; anchor to a semantic word/ayah index instead computed from the Uthmani/Quranic text data source, not from rendered glyph positions.
- Correct rendering of tajweed marks / makharij diagram overlays mentioned in the coach questionnaire, without fighting the RTL layout
Recommend: build a throwaway prototype of "render a verse, place a marker on word 4, change font size, marker stays on word 4" before committing the annotation feature's data model. If this doesn't work cleanly in RN's text primitives, a native text-rendering module is the fallback — decide before Phase 2, not during it.
Push / email infrastructure
- Push: Expo push service (wraps APNs/FCM) is sufficient given the BaaS choice; if Firebase is the BaaS, use FCM directly for both push and potentially in-app messaging.
- Email: transactional email provider (existing tool if the org already has one for enrollment, otherwise a standard provider) for the escalation emails (2 consecutive absences), receipts (Stripe), and any digest/ reminder emails that duplicate the push channel for reliability.
- Both need a single "notification dispatch" service layer (a BaaS Cloud Function) so timing, dedup, and localization logic live in one place rather than being reimplemented per trigger (session reminder, feedback ready, announcement, escalation).
Analytics for the attendance/engagement gap
The team's own words: "100% of the time currently, we are going off of assumptions" about attendance. Since Phase 4 introduces real attendance and Zoom-duration data, instrument from day one so historical data exists when leadership eventually asks for trends:
- Event logging for: session join/leave, lesson video watch completion, submission timing (on-time vs. late in week), revoke usage, message volume, coach response latency
- This can be as simple as a BaaS analytics table + scheduled export, it does not need a dedicated analytics platform at this org's scale — but it needs to start recording from Phase 1, not be bolted on retroactively when someone asks for a report in year two.
- The backlogged "AI-aggregated post-session survey trends" item depends on this data existing; build the capture now even though the AI analysis is explicitly out of scope for v1.
4. Risks and mitigations
R1 — Rocky launch is the default expectation, not a worst case. A full rebuild replacing an unknown-quality backend will have launch bugs. Mitigate with: the Report Issue button (Phase 4, but consider pulling its backend — auto-attached context capture — earlier if feasible, since it's cheap and valuable from day one of any beta), feature flags on newer/riskier surfaces (messaging guardrails, auto-attendance, appointment booking) so a bad rollout can be toggled off without a full app release, and a staged rollout (pilot cohort → one year group → everyone) rather than a big-bang release to the full student body.
R2 — Single-developer bus factor. Nothing here names a team beyond "the org" — if this is built by one contractor/developer, that's a structural risk independent of the tech choice. Mitigate with: RN/TS (larger substitute-hire pool than a native+BaaS mixed stack would require), a managed BaaS specifically because it reduces the amount of bespoke infrastructure only one person understands, and insisting on written architecture notes / a handoff doc as a deliverable at the end of each phase, not just at project end — so a mid-project developer change doesn't require reverse-engineering everything from scratch.
R3 — Coach adoption depends on the review workspace actually being better than a spreadsheet, not on it existing. 8/11 coaches maintain a patchwork workaround today; if Phase 2's review queue is clunkier or slower than what they've built for themselves, they will route around it, and the single most-requested fix in this whole project will fail to land. Mitigate with: pilot Phase 2 with 2–3 coaches (ideally including the desktop-preferring coach and a high-volume mobile coach) before rolling to all 11, treat their first two weeks of real usage as a gate before wider release, and make sure the pilot includes actual review of live students, not a demo dataset — workflow friction only surfaces under real load.
R4 — Timezone bugs recur. Already burned production once (7 PM vs 8 PM bug) and is exactly the kind of defect that regresses silently when a new screen is added by someone who didn't know the rule. Mitigate with the explicit test matrix in Section 3, and a lint-level rule/code-review checklist item: no screen may render a hardcoded offset string ("EST"/"EDT") — always derive from device timezone at render time.
R5 — Arabic annotation anchoring is underspecified until the spike runs. If word-anchored annotation turns out to be harder than expected in RN, it could blow out Phase 2's timeline specifically (the feature most tied to coach adoption, per R3). Mitigate by running the spike in Phase 1 (Section 3), not discovering the problem mid-Phase-2 when the review queue UI is already built on a broken assumption.
R6 — BaaS lock-in / limits. Managed BaaS trades control for speed; if the org later needs the backlogged student-centric scheduling algorithm or heavier custom logic, BaaS query/function limitations could force a partial migration. Mitigate by choosing Supabase (Postgres underneath) over Firestore if this is a concern — Postgres data is far more portable to a custom API later than a Firestore export would be.
5. Suggested team/effort framing
Rough sizing in engineer-weeks, assuming one competent full-stack RN/BaaS developer as the throughline (with a designer/reviewer checking against the mockups, not building). These are ranges reflecting real uncertainty (especially Arabic rendering and audio), not padded-safe estimates — treat the high end as "if the spikes go badly," not as buffer to ignore.
| Phase | Scope | Estimate (engineer-weeks) |
|---|---|---|
| Spike (pre-Phase 1) | Arabic word-anchored annotation prototype, audio record/upload/scrub prototype | 2–4 |
| Phase 1 | Auth, 4 tabs, onboarding, lesson/recording/submission loop | 6–9 |
| Phase 2 | Feedback loop (annotations, practice loop) + full coach app | 7–10 |
| Phase 3 | Schedule, Zoom integration, reminders, announcements/messaging, timezone hardening | 6–8 |
| Phase 4 | Year-2 appointments, auto-attendance, issue reporting, Stripe/subscriptions | 5–7 |
| Total | 26–38 engineer-weeks (roughly 6–9 months for one developer, less with parallel help) |
Notes on this sizing:
- Coach pilot cycles (R3) and DST test-matrix validation (R4) are inside the phase estimates above, not additional — but a pilot that reveals real workflow gaps could add 1–2 weeks to Phase 2 for rework, which is exactly why the pilot needs to start early within that phase rather than at its end.
- Store/app-review turnaround (Apple/Google) is real calendar time, not engineering time — pad the calendar (not the estimate) by ~1–2 weeks around each phase's release if each phase ships as a real release rather than an internal build.
- If a second developer is added, Phases 3 and 4 parallelize better than Phases 1 and 2 (which are sequentially dependent — the coach app in Phase 2 needs Phase 1's submission model to exist first).