Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Murajah logo

Murajah · مراجعة

An iOS app that prevents the most painful loss in a memorizer's journey: the decay of verses you've already memorized.

An active Memorize → Memorized loop where you learn ayat with blind recall, then keep them alive with spaced repetition, and you can see the algorithm working.

Submitted to the Quran Foundation Hackathon (Ramadan 2026). iOS 17+, SwiftUI, SwiftData, on-device speech recognition.


Screenshots


Home. What's due, streak, current set.

Selection. Pick what you've memorized.

Library. Your memorization.

Reader. Mushaf with tajweed.

Blind recall. Placeholders preserve rhythm.

Feedback. Missed words tinted, score shown.

Review. SM-2 transition after every ayah.

Explainer. The algorithm is explainable.

Stats. Streak, accuracy, upcoming reviews.

The problem I picked

Millions reconnect with the Quran in Ramadan and emerge with new verses internalised. Within months, most of those verses fade. There's no daily structure to keep them alive, and traditional revision (open the mushaf, read aloud, hope) doesn't tell you which ayat are slipping or when to revisit them.

Murajah turns Quran retention into a measured, scheduled, observable practice. The same way Anki turned vocabulary retention into one. The user recites, the app listens, and the algorithm decides what comes back tomorrow.


What this is

Two interconnected tracks, plus a continuous mushaf reader and bookmarks tying them together.

1. Memorize · active learning

For verses the user is learning. MemorizingSessionView walks one un-mastered ayah at a time:

  • Read. Full text shown with translation. Hear cue plays the verse from the user's chosen reciter, +1 read increments the exposure counter. No gate, the user decides when to test.
  • Test. Text hidden behind word-shaped placeholders that preserve the ayah's length and rhythm without revealing letters. The user recites from memory, on-device speech recognition transcribes, WordAlignment scores them. Tap a placeholder to peek one word, toggle Show all to reveal.
  • Feedback. Verse fully revealed with missed words tinted rose. A score of 0.85 or better graduates the ayah to the SM-2 review loop, seeded with a 1-day initial interval.

When every ayah in the surah passes, the whole surah graduates: the MemorizedSet flips from .memorizing to .maintenance and a celebration screen offers a first review session right there.

2. Memorized · SM-2 maintenance

Once mastered, ayat enter the spaced-repetition loop. Murajah picks what's due each day, the user recites aloud. Standard SuperMemo SM-2 runs against a continuous 0...1 accuracy score from word alignment, with one adaptation: under 0.7 resets reps to 0 (the verse comes back tomorrow), above 0.85 grows the interval by ease factor.

Crucially, the algorithm is visible, not hidden. After every ayah a Schedule transition card shows old vs new interval (1d → 3d), ease delta (2.50 ▲ +0.13), the relative due date, and a 6-rung stage ladder (New, Learning, Practiced, Familiar, Strong, Mature). A ? button opens a full How Murajah works explainer with a worked 5-day example. Users don't have to trust the schedule blindly. They understand it.

3. Mushaf reader

Continuous flowing layout with quran.com-style 8-pointed rosette verse-end markers (rendered as inline UIImage, not OpenType substitutions, so they look identical on every device) and a calligraphic surah-name banner using the Tarteel surah-names v4 font.

Two rasms, switchable inline:

  • Uthmani. Madani mushaf with alef wasla ٱ and classical letterforms. Amiri when bundled, SF Arabic otherwise.
  • IndoPak. Real Imlaei rasm pulled from api.quran.com/api/v4/quran/verses/imlaei. Regular alef ا, modern letterforms.

Tajweed coloring is an orthogonal toggle. Qalqalah red, ghunnah orange, madd blue/green, idgham teal, ikhfa magenta, silent/wasl gray.

Tap any verse for an action sheet (Hear, Bookmark, Memorize this verse, Copy text). Single-verse memorize launches the active learning loop for just that ayah, so a struggling reader can pick anything they hit and start memorising immediately.

4. Bookmarks

@Model Bookmark keyed by verseKey. Saved verses get a saffron flag overlaid in the reader. A dedicated Bookmarks list sorts newest-first with verse text, translation, relative-time labels, swipe-to-delete, and tap-to-jump-back-to-reader. Bookmarks sync to the user's Quran.com collection via the QF Bookmarks API (see below).


Quran Foundation API integration

The hackathon criterion is at least one Content API plus at least one User API. Murajah ships two Content APIs (Quran, Audio) and two User APIs (Activity & Goals, Bookmarks).

Category Endpoint What it powers in Murajah
Content · Quran GET /content/api/v4/chapters The 114-surah picker
Content · Quran GET /content/api/v4/verses/by_chapter/:id Uthmani text plus Sahih International translation for any memorisation range, in one call
Content · Audio GET /content/api/v4/recitations/:id/by_ayah/:key Per-ayah audio for the user's reciter (5-reciter catalog). Feeds both AudioPlaybackService (mono) and SpatialAudioService (HRTF).
User · Activity & Goals POST /personal/api/v4/activities Every completed review session posts {type: "QURAN", duration_seconds, verse_keys, completed_at} so Murajah work rolls into the user's QF streak and daily goal
User · Bookmarks POST/DELETE /personal/api/v4/bookmarks A bookmark added in Murajah appears in the same user's Quran.com collection, and vice versa

User-scoped calls require an OAuth2 PKCE bearer token. For hackathon judging, pass a pre-issued token via MURAJAH_QF_USER_TOKEN. Without it the calls log no user token, local-only and the app falls back to SwiftData. The full PKCE flow needs a small backend proxy, explicitly out of scope for the iOS hackathon build.

LocalQuranProvider (bundled 6,236-ayah JSON) serves the same QuranProvider protocol so the app stays usable offline and when the API is rate-limited. Demos never break.


What makes it different

Three pedagogically-motivated features that distinguish Murajah from a generic Quran reader or a generic flashcard app.

Live word reveal during blind recall

In Memorize → Test, as speech.partialTranscript updates, MemorizingSessionView runs WordAlignment.align(canonical:recited:) on every transcript change and feeds matched indices into revealedIndices. Words emerge from their dashed placeholders one by one, in real time, in zaytun green, as the user recites them. formUnion(...) ensures the reveal only ever grows: a transient partial that drops a word doesn't yank the verse back behind its placeholders.

Spatial cue audio

SpatialAudioService wraps AVAudioEngine plus AVAudioEnvironmentNode with renderingAlgorithm = .HRTFHQ. The reciter is anchored to AVAudio3DPoint(x: 1.5, y: 0, z: -0.6), about 1.5 m to the right and slightly behind the listener. The user can't passively mouth along because the sound source is outside their head; they have to actively retrieve the rest of the ayah. Mirrors how a hifz teacher prompts from across the room. Toggle in Settings → Audio. Best in headphones.

Visible algorithm

Spaced repetition apps usually feel like a black box. Murajah surfaces the schedule decision every single time: the old interval, the new interval, the ease factor delta, the next due date in human language ("in 2 days"), and a six-rung stage ladder. The HowItWorksView sheet walks through a worked 5-day example so the user understands why a verse is coming back when it is. Trust through transparency.


Architecture

Murajah/
├── App/          MurajahApp · AppRouter · RootView (splash → onboarding → auth → shell) · AppState
├── Theme/        Colors (adaptive light/dark) · Typography (Amiri / SF Arabic / Tarteel) · Theme (Liquid Glass)
├── Models/       Surah · Verse · ScriptStyle · MemorizationPhase · Reciter · PersistedModels (SwiftData)
├── Engine/       SM2Engine · ArabicNormalizer · WordAlignment · ScheduleEngine · TajweedRenderer
├── Services/     QuranProvider · LocalQuranProvider · QFAPIClient · QFUserSync · ScriptProvider
│                 SpeechRecognitionService · AudioPlaybackService · SpatialAudioService
│                 NotificationService · StreakTracker · AuthService · MurajahEnvironment
├── Views/
│   ├── Splash/         SplashView (1.6 s halo-bloom cold-launch animation)
│   ├── Auth/           AuthView (Sign in with Apple plus guest mode)
│   ├── Onboarding/     OnboardingView (3-page intro)
│   ├── Shell/          MainShellView (4-tab Liquid Glass tab bar)
│   ├── Selection/      ContentSelectionView (rasm picker, "memorize" vs "already memorized")
│   ├── Dashboard/      DashboardView · WeakAyatView
│   ├── Library/        LibraryView (Memorizing plus Memorized) · BookmarksView
│   ├── Reader/         MushafReaderView · VerseActionSheet
│   ├── Memorize/       MemorizingSessionView (Read → Test → Feedback → Graduation)
│   ├── Session/        ReviewSessionView (the SM-2 review loop)
│   ├── Results/        SessionResultsView (per-ayah breakdown)
│   ├── Stats/          StatsView (window picker, summary tiles, upcoming, accuracy chart)
│   ├── Profile/        ProfileView · SettingsView · HowItWorksView
│   └── Components/     PrimaryButton · ScoreRing · StageLadder · MemoryBar · ScheduleTransitionCard
│                       ArabicVerseView · BlindVerseView · Chips · FlowLayout
└── Resources/
    ├── Quran/          surahs.json (114) · verses.json (6,236 Uthmani plus EN) · scripts.json (6,236 Imlaei plus tajweed)
    ├── SurahNameV4.ttf
    └── Assets.xcassets (AppIcon, MurajahLogo, AccentColor, LaunchBackground)

Why each engine piece exists

  • SM2Engine.swift. Standard SuperMemo SM-2. Continuous 0...1 accuracy mapped to 0...5 quality bucket. Under 0.7 resets reps, above 0.85 grows interval by ease.
  • ArabicNormalizer.swift. On-device transcription returns text without diacritics, often with hamza/alif variant differences. Collapses ZWNJ/ZWJ, tatweel, all alef variants, ta marbuta → ha, alef maksura → ya so a recitation matches even when tashkeel is omitted.
  • WordAlignment.swift. Forward greedy alignment with bounded 3-word look-ahead. Best Levenshtein match in a small window for each canonical token. Threshold 0.55 marks a word as missed. Returns per-token match info and a weighted accuracy.
  • ScheduleEngine.swift. SwiftData glue. recordBlindRecall(...) returns a BlindResult with graduation flag, recordReview(...) returns a ReviewTransition with old vs new interval and ease for the UI.
  • TajweedRenderer.swift. Parses bracket-tagged tajweed ([<class>[<text>]) into a SwiftUI AttributedString. Walks the source by Unicode scalar (not Character): Swift fuses [ with a following combining damma into a single grapheme, silently breaking naive parsers.
  • QuranProvider.swift. Single protocol over local and remote content. QFAPIClient falls through to LocalQuranProvider on any network or auth failure so demos never break.
  • SpeechRecognitionService.swift. SFSpeechRecognizer in on-device mode with ar-SA locale and .dictation task hint. Streams partial transcripts for the live UI.

Everything that touches audio or transcription is on-device. No recitation leaves the phone.


Running it

brew install xcodegen
xcodegen generate
open Murajah.xcodeproj

Run on a real device for live recitation. The simulator can't capture mic input, but every screen and the full SR loop are reachable via deep-link env vars (see below).

Bundle id: com.murajah.app. Set DEVELOPMENT_TEAM in project.yml (or in Xcode signing) before installing on device.

Bundled content

  • All 114 surahs in Resources/Quran/surahs.json
  • All 6,236 ayat. Uthmani plus Sahih International English in verses.json
  • All 6,236 ayat in Imlaei rasm plus tajweed-tagged Uthmani in scripts.json
  • Tarteel surah-names v4 font (ligature-based: surah001 → ornate Al-Fatiha calligraphy)

Info.plist's UIAppFonts also registers Amiri-Regular.ttf for Uthmani rendering. Drop the file into Resources/ to get the classical Quranic mushaf face; without it Typeface.arabic(...) falls through to SF Arabic.

Switching from bundled to live QF API

// Services/MurajahEnvironment.swift
enum MurajahEnvironment {
    static let useRemoteProvider = true
    static let qfClientId     = "<your client id>"
    static let qfClientSecret = "<your client secret>"
}

For per-user Activity plus Bookmarks calls, set the user token at run time:

SIMCTL_CHILD_MURAJAH_QF_USER_TOKEN=<token> \
SIMCTL_CHILD_MURAJAH_QF_BOOKMARK_COLLECTION=<uuid> \
    xcrun simctl launch booted com.murajah.app

QA / debug deep links

Var Effect
MURAJAH_INITIAL=onboarding|selection|library|stats|profile|settings|read|read_ikhlas|read_baqarah|memorize|session|weak|help|bookmarks Deep-link to a specific screen on launch
MURAJAH_SEED=1|memorizing|graduated|bookmarks Pre-populate SwiftData with a sample collection
MURAJAH_SCRIPT=uthmani|indopak Override the rasm in the reader
MURAJAH_TAJWEED=1 Force the tajweed toggle on
MURAJAH_AUTO_SCORE=<0.0...1.0> Synthesize a recitation result on the first ayah
MURAJAH_AUTO_FINISH=<0.0...1.0> Drive an entire session through to .completed
MURAJAH_MEMORIZE_STAGE=test|feedback|done Fast-forward the memorize flow
MURAJAH_VERSE_ACTION=1 Auto-open the verse action sheet on the first verse

Pass via SIMCTL_CHILD_<VAR> to xcrun simctl launch. Every screenshot in this README was captured with these flags.


Permissions

  • Microphone (NSMicrophoneUsageDescription). Captures recitation.
  • Speech recognition (NSSpeechRecognitionUsageDescription). On-device transcription. Asked on first Recite tap, not at launch.
  • Notifications. Optional. Requested only when the user enables daily reminders in Settings.

All audio and transcription stays on-device.


Brand & assets

The Murajah mark, a human profile with a tree that resembles a brain, lives as a vector PDF in Resources/Assets.xcassets/MurajahLogo.imageset and is imported as a template image so it can be tinted from SwiftUI:

Image("MurajahLogo")
    .renderingMode(.template)
    .resizable()
    .scaledToFit()
    .foregroundStyle(Palette.zaytunDeep)

It appears on the Auth screen, the first onboarding page, the dashboard greeting, and the splash. The 1024×1024 app icon (cream mark on a zaytun green field) is composed from the same source SVG via rsvg-convert plus Pillow and lives at Resources/Assets.xcassets/AppIcon.appiconset/icon-1024.png.


Roadmap

  • Tafsir API integration on the breakdown screen for missed words.
  • Apple Watch complication for streak.
  • Background audio session refinements for car / commute review.
  • Optional iCloud sync of ReviewItem and Bookmark (currently device-local).
  • Word-by-word color overlay so tajweed coloring works on IndoPak rasm too.

License

MIT. See LICENSE.

About

Murajah is a Quran recitation practice app submitted to the Quran Foundation Hackathon. It listens to your recitation and scores it against the canonical Uthmani text using on-device speech recognition, with an SM-2 spaced-repetition algorithm scheduling which verses to revise next.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages