← QuranFlow — the redesign

QuranFlow — Domain & Data Model Specification

Storage-agnostic domain model for the new QuranFlow backend. Every entity, field, and rule below traces to either the stakeholder feedback synthesis (FEEDBACK-SYNTHESIS.md), the adjudicated design review (../mockup/REVIEW.md), the simplification audit (../mockup/AUDIT.md), or the Sakīna mockup source it was built from (../mockup/src/*.jsx). Field names are suggestions, not a schema DDL — the backend team should map them onto whatever store (relational, document, etc.) they choose. Where mockup and source docs disagree, the mockup's adjudicated REVIEW.md resolution wins (per that document's own rule).


1. Entity catalogue

User

The single account type; role and per-role fields distinguish behavior.

Field Notes
id
name Display name, e.g. "Sarah M."
role y1_student | y2_student | coach | admin. Wishlist: Y1 = submission-centric, Y2 = appointment-centric (FEEDBACK-SYNTHESIS.md "Wishlist requirements").
email
timezone IANA zone, e.g. America/Toronto. One account = one city (REVIEW.md B3) — no per-device override. All times shown to a user render in this zone; the app never hardcodes EST (AUDIT.md "v3 mockup feedback").
level Current level, e.g. Level II. Students see content up to their current level; coaches see all levels (FEEDBACK-SYNTHESIS.md wishlist: "Content access").
semester_enrollment_id FK to the student's current Semester enrollment (below).
year Y1 | Y2 — drives submission-centric vs appointment-centric UI and feature gating (Year-2-only 1:1 rows, REVIEW.md C2).
coach_id FK to User (role coach) — the student's single assigned coach. One coach per student; two-coach model explicitly rejected (REVIEW.md "Out of scope": "Two-coach model for Year 2 — rejected for now"). Changeable via an explicit "Change coach" affordance (REVIEW.md A4), which reassigns this field but does not alter historical CoachNote/Feedback authorship.
quran_font_preference One of a fixed set (Amiri Quran / Amiri / Naskh) + font_size_pt (18–36). From SakFontPicker (sak-extras.jsx).
notification_preferences Per-type opt in/out — see Notification below.
created_at / onboarded_at

Relationships: a coach User has many student Users (via coach_id); a student has one coach at a time but historical Feedback/CoachNote rows may reference a different coach than the student's current one (substitute coaches, prior-semester coaches — see SakCoachStudent's intentional Ust. Rania reference, REVIEW.md A4).

Semester + Week

Field Notes
Semester.id
Semester.label e.g. "Fall 2026"
Semester.start_date
Semester.total_weeks Fixed at 15 — the strip is always total={15} (AUDIT.md "Consistency conventions locked": SakStrip total={15}).
Semester.state pre_semester | active | complete. Pre-semester state renders SakTodayCountdown (sak-feedback.jsx F5) instead of an empty Today tab — countdown to start_date, with prep checklist items (meet coach, pick font, watch welcome lesson).
Week.id
Week.semester_id FK
Week.number 1–15
Week.starts_at / Week.ends_at Week boundary; submission deadline is Sunday 11:59 PM student-local time (REVIEW.md A3: "Due Sunday · 11:59 PM", not a hardcoded EST string).

A student's "current week" is derived (Semester.active + today's date falls within Week.starts_at..ends_at), not stored redundantly per user.

Lesson / Video

Field Notes
id
week_id FK — lessons are weekly.
level Content gating: students see lessons up to their current level; coaches see all.
title e.g. "Idghām with ghunnah"
part_number Chapter/part within the syllabus TOC.
videos Ordered list of 3 Video sub-records: { n, title, duration_sec, transcript } (sak-extras.jsx SakVideoPlayer: hardcoded 3-video chapter).
VideoWatchProgress Per (user, video): percent_watched, resumed_at_sec, watched_at (nullable — set once percent_watched >= 90, the mockup's completion threshold, SakVideoPlayer progress >= 90). Copy consistency rule (AUDIT.md wishlist): a video is either "N of M remaining" or "watched" — pick one vocabulary and use it everywhere, never both on the same screen.

Assignment

Field Notes
id
week_id FK — one assignment per week per level.
surah / ayah_range e.g. Al-Mulk 67:1–3. Citation string format is SURAH · CH:V[–V] uppercase mono (SakPassageCard cite prop).
ayat Ordered list of āyah records: { num, text (Arabic), segments } — the same shape SakPassageCard renders and the same object a coach's word-anchored marks attach to (see Feedback.marks below). Scales from 1 to N āyāt; the passage card scrolls internally past ~5 lines rather than growing the screen (sak-feedback.jsx header comment, "LENGTH ROBUSTNESS").
rubric_id Optional FK — First Assessment / EOC (End of Chapter) rubrics surfaced before review (FEEDBACK-SYNTHESIS.md wishlist: "Assessment rubrics … surfaced before review").

Submission

The core Y1 object. One row per attempt (not per week) — a week can have up to 2 submissions (the weekly attempt limit) plus any number of revoked/re-recorded takes that don't count, and any number of practice takes attached afterward (see PracticeTake below).

Field Notes
id
student_id FK
week_id FK
assignment_id FK
attempt_number 1 or 2 (of the 2/week cap — FEEDBACK-SYNTHESIS.md: "2 submissions/week limit"). A revoked-and-re-recorded submission does not increment this (see revoke window below).
audio_url Student's recording.
duration_sec
submitted_at
revoke_expires_at submitted_at + 5–10 min (exact value configurable; mockup shows a live countdown, e.g. "6:52 left", SakSubmissionPending). While now < revoke_expires_at, the student can revoke-and-re-record without consuming an attempt (FEEDBACK-SYNTHESIS.md: "5–10 min revoke window that does NOT count against the limit"; REVIEW.md A1 — this is the only screen that owns revoke UI, SakSubmissionPending, not the immediately-prior "recording submitted" screen).
revoked_at Nullable — set if the student exercises the revoke window; the submission is voided and does not count toward the weekly cap.
status See state machine in §2. Values used in the mockup's SubStatusPill (sak-extras.jsx): review ("In review"), reviewed ("Feedback ready"), heard, practiced.
marked_heard_at Set when student taps "Mark as heard" (SakSubmissionFeedback).

Relationships: one Submission has zero-or-one Feedback (coach hasn't reviewed yet vs has); one Submission may have many PracticeTakes (attached after feedback, don't consume attempts).

Feedback

Coach's response to a Submission. One-to-one with Submission.

Field Notes
id
submission_id FK, unique
coach_id FK
audio_url Coach's spoken feedback (SakSubmissionFeedback's ink card, e.g. "6:48" audio).
duration_sec
marks Word-anchored annotations on the assignment text: list of { ayah_num, word_index (n), tone } where tone is danger | warm (mockup: red = correction needed, gold = praised — PassMark/SAK_MULK_1_3 in sak-feedback.jsx). This is the "visual/annotated feedback synced to audio" coaches ranked #2 (FEEDBACK-SYNTHESIS.md).
notes Ordered list of timestamped notes, each { mark_ref (n), tone, audio_timestamp_sec, title, body, ayah_ref } — stamped automatically at the coach's playhead position while recording (SakCoachReview: "stamped at playhead"). UI collapses to 3 visible + "Show N more" past that (SakFeedbackNotes, collapseAt = 3).
private_note Coach-only text, never shown to the student (SakCoachReview's "Private — just me" tab).
student_note Optional additional text shown to the student alongside the audio (SakCoachReview's "For Sarah" tab).
pass_fail Optional boolean/enum outcome, distinct from the loop-state status on Submission.
reviewed_at When the coach submitted feedback — this is what flips Submission.status reviewreviewed.
review_duration_sec Wall-clock time the coach spent in the review screen; feeds TimeLog/attendance auto-logging (SakCoachReview footer: "14:32 on this review · logged automatically" — replaces the manual Time Clock Wizard, FEEDBACK-SYNTHESIS.md coach ask #4).

PracticeTake

A re-recording the student makes after receiving feedback, to practice a correction. Does not consume a weekly attempt and is not itself submitted for new review — it attaches to the existing Feedback thread.

Field Notes
id
feedback_id FK — attaches to the feedback thread, not a new submission (REVIEW.md C3: "Attach to feedback", not "Submit to Ust. Amina").
correction_ref Which Feedback.notes[n] this take addresses (the stepper in SakRecPractice walks corrections one at a time, "Correction 1 of 3").
audio_url / duration_sec
recorded_at
No attempt_number, no attempts-dots UI — explicitly out of the weekly cap (REVIEW.md C3).

Session

Live scheduled events: classes, QRC (Qur'an Reading Circle), Office Hours, and (for Y2) one-to-one appointments (modeled separately, see Appointment, because appointments have a distinct per-student booking/attendance lifecycle; Session covers the shared/group event types).

Field Notes
id
type qrc | level_class | office_hours (mockup type chips: "QRC", "Class"/"Level Class", "Hours"/"Office Hours" — sak-schedule.jsx typeColors).
title e.g. "Sisters' Reading Circle", "Idghām review & practice".
coach_id FK — session leader.
starts_at_utc Stored UTC; rendered per-viewer in their own timezone. No per-item zone suffix stored or displayed (REVIEW.md B3: delete every per-item "EST"/"EDT" suffix; one zone banner per screen instead).
duration_min
recurrence_rule e.g. weekly on Tuesday — most Sessions repeat every week of the semester.
zoom_join_url
level_filter Optional — restricts which students see/attend (Level II class vs Level I).
cohort_size / registered_count For the "6 of 8 confirmed" cohort display (SakSessionDetail).
materials "Bring with you" list: { icon, title, sub } — e.g. reading passage, prerequisite video (DetailMatRow, SakSessionDetail).
agenda "What we'll cover" ordered list (DetailListRow).

SessionRecording (child of Session, one per occurrence):

Field Notes
id
session_id FK (a specific occurrence, not the recurrence rule)
occurred_at
video_url
duration_sec
Organized by week in a calendar/grouped view, not a flat list (FEEDBACK-SYNTHESIS.md wishlist: "Recordings organized in a calendar view"; implemented as SakScheduleRecordings's week-grouped list with a day strip).

SessionWatchProgress (per user, per SessionRecording): percent_watched (drives the thumbnail progress bar and "N% in" / "watched" chip, RecordingRow in sak-schedule.jsx).

Appointment

Year-2-only 1:1 booking. Distinct from Session because it has a per-student lifecycle (booked → attended/missed → escalation) rather than a shared roster.

Field Notes
id
student_id FK — Y2 only; Y1 accounts never see these (REVIEW.md C2 annotation).
coach_id FK — the student's single assigned coach.
booking_mode recurring | this_week_only — the hybrid model from the teachers' call (FEEDBACK-SYNTHESIS.md "Decisions already made": lock the same weekly slot for all 15 weeks, or book week-to-week). Vocabulary locked to exactly these two words in coach-facing UI (REVIEW.md D1: "Recurring" / "This week only" — not synonyms).
slot_start_utc Stored UTC. 20-minute slots (FEEDBACK-SYNTHESIS.md wishlist: "20-min slots"; SakBookAppointment slot grid).
duration_min Fixed 20.
week_id For this_week_only bookings; null/ignored for recurring (recurring bookings are described relative to the semester, not a single week — cancelling or swapping a specific week's occurrence is handled via AppointmentOccurrence below).
zoom_join_url
status active | cancelled — the recurring booking itself, not a single week's attendance.

AppointmentOccurrence (one per week, generated from the Appointment booking or from a one-off this_week_only booking):

Field Notes
id
appointment_id FK
week_id FK
scheduled_at_utc Normally inherited from the parent slot; may differ if swapped.
swapped_from_occurrence_id Nullable — set when the student uses "Swap this week's time" (ApptActionRow in sak-appointments.jsx: "Keep your slot for the other weeks"). Swap moves this week's occurrence only; the recurring slot is untouched.
attendance_status scheduled | attended | no_show | cancelled_by_student | cancelled_by_coach. Cancellation is guilt-free and accepted up to the last minute ("Even a minute before is fine — it frees the slot", ApptActionRow).
cancelled_at
consecutive_absence_count Derived/maintained counter across a student's occurrences; 2 consecutive no-shows trigger the auto email escalation (FEEDBACK-SYNTHESIS.md: "auto email escalation after 2 consecutive absences" — see Integration points, Email automation).

CoachNote

Shared, persistent notes about a student, visible to every coach who has ever met with that student — not scoped to the currently-assigned coach. This is what powers SakCoachStudent's "Coach notes" section, explicitly labeled "visible to all coaches", including the intentional Ust. Rania substitute-coach example (REVIEW.md A4).

Field Notes
id
student_id FK
author_coach_id FK — may not be the student's current assigned coach (substitute/prior coach).
body Free text.
week_id Optional context — "Week 6".
context_tag Optional, e.g. "one-to-one" (distinguishes a note written during an Appointment vs during async Feedback review — SakCoachStudent: "Ust. Rania · Week 6 · one-to-one").
created_at
A lightweight derived/rollup view, "Working on" tags (e.g. "Madd length", "Ghunnah hold"), is collated from the coach's last 3 notes — see Derived views §4.

Message

1:1 async thread between a student and their coach. Text or voice, with a weekly cap and a length guard.

Field Notes
id
thread_id One thread per (student, coach) pair — persists across coach reassignment for history, but new messages route to the current coach.
sender_id student or coach User.id
kind text | voice
body_text For text
audio_url / duration_sec For voice — coaches specifically asked for voice replies over the (underused, buggy-truncation) text box (FEEDBACK-SYNTHESIS.md coach ask #5).
sent_at
read_at Nullable
Weekly cap applies to the student's outbound message count per thread per week (mockup shows "2 of 3 messages left this week", SakMessageThread) — configurable count, resets each Week. Length guard: ~800-char warning on text composition (FEEDBACK-SYNTHESIS.md: "~800-char length warning, possible weekly caps"). Both guardrails are soft (warn, don't hard-block, except the weekly cap itself which does block once exhausted).

Replaces the old community board; this is 1:1 only (both Y1 and Y2) — one-to-many broadcast is Announcement, below.

Announcement

One-way board post. Replaces the community board (FEEDBACK-SYNTHESIS.md decision #2).

Field Notes
id
author_id Coach or admin User.id, or a system/"QF Team" author.
title
body
audience everyone | level (+ level_value) | year (+ year_value) — audience filters (FEEDBACK-SYNTHESIS.md: "audience filters by year/level"). Rendered as a chip on each row (AnnRow: "Level II", "Everyone").
pinned Boolean — pinned posts (e.g. break/schedule-change notices) sort to the top (SakAnnouncements).
posted_at
AnnouncementRead per (user, announcement): read_at, drives the unread dot and the notif-sheet "Announcements · N pinned" badge count (REVIEW.md C1, B4).

Business rule (REVIEW.md B4): when an announcement duplicates a fact also pushed as a Notification (e.g. a schedule change), the copy must be identical in both places — this is a content-authoring constraint, not a separate field, but worth encoding as a single source-of-truth: the Notification row of type announcement should reference the Announcement.id and render its title, not carry independent copy.

Notification

Per-user delivery record. Types match the mockup's NotifRow type map (sak-schedule.jsx): feedback, session, deadline, announcement.

Field Notes
id
user_id
type feedback | session | deadline | announcement
title / body
ref_id Polymorphic FK to the underlying object (Feedback.id, Session.id/AppointmentOccurrence.id, Week.id for a deadline, Announcement.id). Type-announcement rows deep-link to the board, not to inline content (REVIEW.md B4/C1).
created_at
read_at Nullable
push_sent_at Nullable — when the push notification was actually dispatched (may differ from created_at if batched/delayed by reminder-timing rules).

NotificationPreference (per user, per type): enabled: boolean. Announcements default ON (REVIEW.md B4 — the one exception; all other types' defaults are a product decision left to the backend/product team, but must be explicit per-type toggles, not one master switch, per FEEDBACK-SYNTHESIS.md's "per-type preferences" wishlist item and the mockup's SakNotifSheet/settings screens).

Reminder timing (business rules, not schema): 15-min reminder before student sessions/appointments (SakWeekAtGlance: "Reminders arrive 15 minutes before each session"; SakAppointmentDetail: "Reminder 15 minutes before · every week"); coaches get a 30-min reminder (FEEDBACK-SYNTHESIS.md: "15-min student / 30-min teacher reminders").

TimeLog / Attendance

Auto-derived, not manually entered — retires the "Time Clock Wizard" (FEEDBACK-SYNTHESIS.md coach ask #4).

Field Notes
id
coach_id
source zoom_session | feedback_review — two distinct auto-log paths.
ref_id FK to the Session occurrence or Feedback.id that generated this entry.
duration_sec For zoom_session: derived from the Zoom join-duration webhook. For feedback_review: wall-clock time spent in the coach review screen (Feedback.review_duration_sec, surfaced live in SakCoachReview: "14:32 on this review · logged automatically").
logged_at

Attendance (derived view over Session/AppointmentOccurrence + Zoom webhook data): a participant is marked attended if their Zoom join duration is ≥ 75% of the scheduled duration (FEEDBACK-SYNTHESIS.md coach ask #4: "auto-log hours/attendance from Zoom duration ≥75%"). This threshold directly sets AppointmentOccurrence.attendance_status and a parallel SessionAttendance { user_id, session_occurrence_id, attended: boolean, zoom_duration_pct } record for group sessions — addressing the "100% of the time currently, we are going off of assumptions" attendance black hole (FEEDBACK-SYNTHESIS.md top finding #4).

IssueReport

FEEDBACK-SYNTHESIS.md top finding #5: "Report Issue button on every screen"; SakReportIssue (sak-comms.jsx F4).

Field Notes
id
user_id
kind text | voice — "Narrate it instead" is an equal alternative to typing.
body_text For text
audio_url For voice
screen_context Auto-captured: current screen/route name (e.g. "Today").
app_version Auto-captured, e.g. "2.0.1".
account_context Auto-captured: user id/role snapshot at time of report (for support triage without asking the user to re-explain who they are).
submitted_at
All context fields are captured automatically; the user only supplies the description (mockup: "Screen, app version and account attach automatically").

2. State machines

Submission lifecycle

States (from SubStatusPill, sak-extras.jsx, and REVIEW.md A2's adjudication): in_reviewreviewedheardpracticed.

From To Trigger Notes
(none) in_review Student submits audio for an assignment (submitted_at set). Starts the revoke window (revoke_expires_at) and the coach's 48-hour reply-window clock (SakSubmissionPending: "Feedback within 48 hours").
in_review (voided, not a state) Student revokes within the window (revoked_at set before revoke_expires_at). Does not consume attempt_number; student re-records, creating effectively the same attempt slot.
in_review reviewed Coach completes Feedback (reviewed_at set). Per REVIEW.md A2: "reviewed" = feedback just arrived, not yet practiced. SubStatusPill renders this "Feedback ready" (warm/gold) — visually distinct from in_review's "In review" (muted/ink), a fix mandated in REVIEW.md A2 because the two were "rendering nearly identically."
reviewed heard Student taps "Mark as heard" on SakSubmissionFeedback (marked_heard_at set). Does not require a PracticeTake — "heard" only asserts the student listened/acknowledged.
reviewed or heard practiced Student records and attaches ≥1 PracticeTake to this Feedback ("Attach to feedback", SakRecPracticeReview). Practice takes never consume a weekly attempt_number (REVIEW.md C3) and do not create a new Submission row.
any (new Submission) Student submits attempt 2 of the week, or the following week's assignment. A new Submission row starts its own in_review state independent of prior weeks.

Weekly cap: at most 2 Submission rows with a non-voided (revoked_at IS NULL) status per (student, week) pair. Revoked submissions do not count.

Appointment lifecycle

Applies to AppointmentOccurrence.attendance_status, with the parent Appointment.booking_mode/status governing whether occurrences continue to be generated.

From To Trigger Notes
(none) booked (Appointment created, status: active) Student completes SakBookAppointment: chooses recurring (locks one weekly slot for all 15 weeks) or this_week_only (picks a slot for the current week alone). Hybrid model per the teachers' call (FEEDBACK-SYNTHESIS.md), resolving the "midnight slot-camping" / "students skipping weeks when their slot was taken" complaints.
booked scheduled (occurrence generated) Weekly job materializes an AppointmentOccurrence for the upcoming Week, inheriting the booked slot time. For this_week_only, exactly one occurrence is created and the parent Appointment does not recur further.
scheduled reminder sent T-minus 15 min (student) / T-minus 30 min (coach). Notification dispatch, not a stored state — see TimeLog/Notification integration.
scheduled attended Zoom join-duration webhook reports ≥75% of the 20-min slot for the student. Resets consecutive_absence_count to 0.
scheduled no_show Slot time passes with join duration < 75% (or no join at all). Increments consecutive_absence_count.
scheduled cancelled_by_student Student taps "Can't make it? Cancel this week" — accepted up to the last minute, no penalty ("guilt-free cancel"). Frees the slot for other students immediately; does not increment consecutive_absence_count (a cancellation is not an absence).
scheduled (this occurrence only) swapped Student taps "Swap this week's time" — books a different slot for this week only. Sets swapped_from_occurrence_id; the recurring Appointment slot is unaffected for future weeks.
no_show (×2 consecutive) escalation consecutive_absence_count reaches 2. Fires the auto email escalation to program staff (FEEDBACK-SYNTHESIS.md: "auto email escalation after 2 consecutive absences") — see Integration points, Email automation. Does not change attendance_status itself; it's a side-effecting rule layered on the counter.
booked Appointment.status: cancelled Student (or coach) cancels the recurring booking itself, not just one week. Stops future occurrence generation; historical occurrences are untouched.

Post-session survey loop (flagged in FEEDBACK-SYNTHESIS.md as a priority even ahead of the app) is out of scope for this model's stored entities beyond triggering off attendance_status transitions — see Backlog note in §5/6.


3. Business rules table

Rule Source
Weekly submission cap: 2 attempts per student per week. FEEDBACK-SYNTHESIS.md wishlist requirements
Revoke window: 5–10 min after submission (exact value configurable); revoking does not consume an attempt. SakSubmissionPending is the single owning screen for the countdown UI. FEEDBACK-SYNTHESIS.md; REVIEW.md A1
Practice takes attached to feedback never consume a weekly attempt. REVIEW.md C3
Submission status vocabulary is exactly in_review → reviewed → heard → practiced; in_review and reviewed must be visually and textually distinct. REVIEW.md A2
Coach reply SLA: 48 hours from submission. SakSubmissionPending copy ("Feedback within 48 hours"); coach queue's stated promise ("Your 48-hour promise holds", SakCoachQueue)
One coach per student (single assigned coach model); two-coach Y2 model rejected. REVIEW.md A4, "Out of scope"
Historical/cross-coach notes remain visible even after a coach change or when a substitute covers a session. REVIEW.md A4; CoachNote entity
All times render in the viewing user's own local timezone; never hardcode EST/EDT. FEEDBACK-SYNTHESIS.md top finding #3; REVIEW.md A5, B3
No per-item timezone suffix in the UI — one zone banner per screen instead. REVIEW.md B3
Hybrid appointment booking: recurring (lock slot for all 15 weeks) or this_week_only. Vocabulary locked to "Recurring" / "This week only." FEEDBACK-SYNTHESIS.md decisions; REVIEW.md D1
Appointment slots are 20 minutes. FEEDBACK-SYNTHESIS.md wishlist
Cancelling an appointment (even a minute before) is accepted with no penalty and frees the slot. SakAppointmentDetail (ApptActionRow copy)
Swapping a week's appointment time does not affect the recurring slot in other weeks. SakAppointmentDetail
Two consecutive missed appointments trigger an automated email escalation. FEEDBACK-SYNTHESIS.md wishlist
Attendance (sessions and appointments) is auto-derived from Zoom join duration ≥75% of scheduled duration — never self-reported. FEEDBACK-SYNTHESIS.md coach ask #4
Coach hours are logged automatically from feedback-review wall-clock time and Zoom session duration; no manual Time Clock Wizard entry. FEEDBACK-SYNTHESIS.md coach ask #4; SakCoachReview
1:1 messages: weekly cap on student-sent messages per thread (mockup default 3/week) and an ~800-char length warning on text composition. FEEDBACK-SYNTHESIS.md decisions
Announcements replace the community board; are one-way (student cannot reply on the board — must use Message to reach the coach). FEEDBACK-SYNTHESIS.md decisions; SakAnnouncements footer copy
Announcement audience filters: everyone / level / year. FEEDBACK-SYNTHESIS.md wishlist
Announcements notification toggle defaults ON (the one type that does). REVIEW.md B4
When the same fact is surfaced as both an Announcement and a Notification, the copy must match exactly. REVIEW.md B4
Students see content (lessons/levels) up to their current level; coaches see all levels. FEEDBACK-SYNTHESIS.md wishlist ("Content access")
Assessment rubrics (First Assessment, EOC) must be surfaced before a coach begins a review that includes them. FEEDBACK-SYNTHESIS.md wishlist
Recordings are organized in a calendar/week-grouped view, never a flat list. FEEDBACK-SYNTHESIS.md wishlist; SakScheduleRecordings
Report an Issue: screen name, app version, and account context are captured automatically; the user only supplies free-text or voice description. FEEDBACK-SYNTHESIS.md top finding #5; SakReportIssue
Video watch progress ≥90% marks a video "watched"/complete. SakVideoPlayer (completed = progress >= 90)
Pre-semester state: Today shows a countdown + prep checklist instead of an empty/blocked screen. SakTodayCountdown (F5); FEEDBACK-SYNTHESIS.md wishlist ("semester calendar with pre-semester countdown state")
Year-1 accounts never see one-to-one appointment rows/screens (Year-2-only feature). REVIEW.md C2
A coach's queue defaults to oldest-submission-first, with filters by level and by fewest-submissions-reviewed. SakCoachQueue (the coaches' own stated ask, FEEDBACK-SYNTHESIS.md "Unified coach workspace")

4. Derived views the UI needs


5. Integration points


6. Explicitly out of scope (backlog)

Carried forward from FEEDBACK-SYNTHESIS.md and REVIEW.md so the schema doesn't over-build for features not yet committed: