What a developer needs to know first

Four ideas that are unlike anything in ordinary text software. Each of them fails without an exception or a red test; the reader just sees the wrong thing.

Platforms
All
Time
About 10 minutes

The shape of the thing

The Qur’an is one fixed text. It does not have editions in the sense a novel does, and none of what follows is about the text changing. All four ideas are about how the same unchanged text is divided, transmitted, printed and encoded, and each of those is a separate axis your code has to keep straight.

Surah numbers are the one part you can rely on completely.


Idea 1 — An ayah number depends on who is counting

Early scholars in different centres divided the identical text into verses at slightly different points. Not different words; different placement of the numbered breaks. Six of those traditions are canonical, and they give six different totals.

SystemArabicTotal ayatAssociated qurrāʾ
First Madinan
madani-first
المدني الأول6,214abu-jafar
Last Madinan
madani-last
المدني الأخير6,214nafi
Makkan
makki
المكي6,219ibn-kathir
Basran
basri
البصري6,204abu-amr, yaqub
Damascene
dimashqi
الدمشقي6,226ibn-amir
Kufanreference
kufi
الكوفي6,236asim, hamza, kisai, khalaf

Read at build time from public/demo/qiraat.json, which scripts/sync-demo-data.mjs built from the Qiraat Ayah Map repository at a pinned commit.

6,236 is not a constant

It is the Kufan total. Ḥafṣ follows the Kufan tradition, and Ḥafṣ is what most of the world prints, so the number looks universal. Hard-code it and your app is correct until the first user switches reading, then it truncates or overruns.

First and Last Madinan both total 6,214 and are still different systems: their boundaries differ in places that cancel out in the sum. You cannot identify a counting system by its total.

What to do: read the count from the data you loaded, because numbering systems differ (Quranic text, rule 5.2); store the counting system alongside any reference you persist, as the ayah key asks (Engineering, rule 2.1); and translate between systems with Qiraat Ayah Map rather than arithmetic. Full detail in Ayah-counting systems.


Idea 2 — There is more than one transmitted reading

This is the selector in our APIs. We ship seven:

KeyRiwayahCounting systemAyat in this muṣḥafSystem total
hafsḤafṣ ʿan ʿĀṣimKufi6,2366,236
shubahShuʿbah ʿan ʿĀṣimKufi6,2366,236
warshWarsh ʿan NāfiʿLast Madani6,2146,214
qalunQālūn ʿan NāfiʿLast Madani6,2146,214
durial-Dūrī ʿan Abī ʿAmrFirst Madani6,2176,214differs
susial-Sūsī ʿan Abī ʿAmrFirst Madani6,2186,214differs
bazzial-Bazzī ʿan Ibn KathīrBetaMakki6,2206,219differs

Muṣḥaf counts come from quran-text’s own files; system totals from qiraat-ayah-map. Both are read at build time. Where they differ, both numbers are correct about different questions — see below.

The differs marks in the last column are the second trap:

A muṣḥaf's ayah count is not its system's total

The published total of a counting system is a scholarly position. The ayah count of a particular printed muṣḥaf is what a publisher set in type. The al-Sūsī muṣḥaf we ship prints 6,218 ayat while its counting system totals 6,214. Neither is a typo; they answer different questions.

What to do: treat the riwayah as a property of your loaded data, not a display toggle — model it as an entity, not a column (Engineering, rule 1.2). It changes actual letters and actual counts, so anything you cached under one riwayah is invalid under another, which is what a cache key naming everything (Engineering, rule 8.1) is for.


Idea 3 — Arabic text is not one character per letter

The text looks fine on screen while your indices point at the wrong place.

Two files containing the same ayah, rendering identically in your browser, can compare unequal, because one stores a shadda before its vowel and the other after, or one uses a precomposed آ where the other uses ا plus a separate mark. Between two of our own repositories, joining one dataset's words with a single space matches the other's text in only 319 of 6,236 ayahs.

Offsets are only valid against the exact text they came from

We shipped this bug ourselves. Character offsets from one dataset applied to another dataset's text rendered 11 of 59 al-Fatiha annotations on the wrong letters, with nothing thrown.

What to do: never slice Arabic at an arbitrary index, since a cut falls on a grapheme cluster (Quranic text, rule 7.3) and length is not letters (rule 2.5); never match on an Arabic string to join datasets, because the word key is surah:ayah:word (Engineering, rule 2.2); and treat offsets as belonging to one specific text, which is offsets naming their text (Quranic text, rule 4.4).


Idea 4 — Prove you have the right text

A wrong-but-plausible text does not fail; it mislabels. The digest turns that into an error at load time, so every dataset we publish ships one and fails loudly (Quranic text, rule 8.4) on a mismatch.

What to do: check the digest when you load, and record which source and version you shipped — a manifest travels beside every file (Versioning and corrections, rule 1.2).


Joining datasets

You do not join our datasets on Arabic strings. You join them on a number: the word key (Engineering, rule 2.2).

Text, page coordinates and annotations all address the same word the same way, so highlighting, tapping and annotating line up without any of them agreeing on encoding.