Distillate

Text to evidenceLesson 8openfrom namaq

The rules behind a branch tree, and where they disagree

The shared-openings page and quiz group words by their letters and split nodes by their marks. Most of the surprises in the قال رب tree come from that, and from one family of marks the context rule does not cover yet.

This lesson describes /quran/branches and the "Shared openings" quiz as they stand on Namaq main at commit 5a0da54, after PR #422 (context marks and the ayah context overlay), #423 (root guard and the browse journey) and #424 (layout only). It states every rule the code applies, with the line that applies it, so that each rule can be checked against what the owner intends. It changes nothing. The last two sections take the owner's example, ?o=قال+رب, and the Musa ayat, explain which rule produced each node, and list the places where the code and the stated rules part.

Line numbers refer to src/lib/quran/branches.ts at 5a0da54 unless another file is named. Every count below comes from running the code on the committed src/app/quran/branches/ayat.json (3,140 ayat).

Three ways to compare a word

Everything else in this lesson follows from one fact. The code compares two words in three different ways, two for building trees and one for grading, and each part of the feature picks one of them.

Name in the codeWhat it removes or foldsSo these count as equalUsed by
normalizeWord (src/lib/quran/normalize.ts, lines 2 to 12)every vowel mark, shadda, sukun, dagger alif, maddah, tatweel and Quranic sign; folds أ إ آ ٱ to ا, ى to ي, ة to هإِنِّى, إِنِّىٓ and أَنَّىٰ (all become اني)opening groups, phrase ids and ?o=, pieces, the phrase tree
spell (line 98)tatweel and word joiner, the signs U+06D6 to U+06ED, a shadda on the first letter, a sukun on the last letter. Keeps every other markلَهُمْ and لَهُم; مَّتَـٰعًۭا and مَتَـٰعًۭاthe drawn tree, the first-word column, ?w=, quiz trees
quiz letters (src/lib/quiz/branchAnswers.ts, line 10)like normalizeWord, and also folds the hamza seatseverything a reader can type for one wordgrading typed answers

On top of the first two, a root guard (lines 85 and 86) adds the word's root from the Quranic Arabic Corpus to the key. Two words with the same letters but different roots, or one rooted and one rootless, never share a group or a node. In the whole dataset only one phrase id belongs to two root groups: قال رب اني is split into قَالَ رَبِّ إِنِّى (7 ayat, rootless إنّي) and قَالَ رَبِّ أَنَّىٰ (2 ayat, root أ ن ي).

Every mark spell removes, normalizeWord removes too, so the letters key is always the looser of the two. A word that differs only in kept marks is one word to search and two words to the tree. Across all 3,140 ayat, 1,293 letter forms are written with more than one spell spelling. Most of those differences are a short vowel or tanween (1,101 forms involve one), 255 involve a maddah and 121 a dagger alif; a form can count under more than one mark.

PartCompares bySize on the whole dataset
Grouping (sharedOpenings)letters and root816 openings over 3,140 ayat, 259 of them with more than one first-word spelling
Phrases (listPhrases)letters and root1,774 phrases
Pieces (pieces)letters and root935 pieces
Drawn tree and quiz tree (branch)spell and rootone node per spelling

Building the data

npm run quran:branches runs scripts/quran/buildBranches.ts. It reads the 604 reader pages, the same text /quran/read shows.

  1. readerAyat (line 29) splits the pages into ayat and drops tokens that are only marks, such as pause signs and ۞.
  2. conjunctions (line 57) and withConjunctions (lines 61 to 69) mark an ayah conj: "و" or "ف" when the corpus splits a w: or f: prefix off its first word. The oath و (w:P, the (?!P\+) on line 57) is not a conjunction, so وَٱلْفَجْرِ keeps its و.
  3. withRoots (lines 71 to 83) stores each word's root, and fails the build when a word has no STEM annotation.
  4. sharedOpenings (line 131) keeps only ayat whose opening another ayah shares.
  5. withNext (lines 255 to 261) attaches the next ayah of the same surah to every ayah that "continues" (section 6).

Grouping ayat into openings

Drawing the tree

branch (line 282) draws a group as grow(grown(shown(ayat)), 0, spell, ...).

A leading و or ف, and the first-word column

Next-ayah leaves

Phrase search, ?o=, ?r= and ?w=

Pieces, the list view

pieces (lines 329 to 366) cuts each opening into pieces of at most PIECE_MAX = 6 ayat. It walks the letters-only phraseTree, not the drawn tree:

The quiz

branchQuestions (src/lib/quiz/branchQuestions.ts) makes one question per piece of six ayat or fewer, then draws that piece with branch, the marks tree (line 31). It skips the question when that tree's root has fewer than two children or ends an ayah (line 32). The prompt shows the root's words, the first-word spellings if any, and the number of children. Grading (matchTreeAnswers) compares typed letters only: an inner node needs all its words, a leaf its first two, and sibling subtrees of the same shape may be swapped. It yields 872 questions over the whole Qur'an.

The owner's example: ?o=قال+رب

The phrase id قال رب matches 26 ayat. The root node is قَالَ. It has two children, رَبِّ (24 ayat) and رَبُّ (26:24, 26:28). Under رَبِّ the tree splits again, into twelve branches:

Branch after قَالَ رَبِّAyat
أَنَّىٰ يَكُونُ لِى غُلَـٰمٌۭ, then two leaves3:40, 19:8
ٱجْعَل لِّىٓ ءَايَةًۭ …, then two leaves3:41, 19:10
إِنِّى, then five leaves5:25, 19:4, 28:16, 28:33, 71:5
ٱغْفِرْ لِى, then two leaves7:151, 38:35
إِنِّىٓ, then two leaves11:47, 26:12
ٱلسِّجْنُ أَحَبُّ …12:33
فَأَنظِرْنِىٓ إِلَىٰ يَوْمِ يُبْعَثُونَ15:36, 38:79
بِمَآ, then two leaves15:39, 28:17
ٱشْرَحْ لِى صَدْرِى20:25
لِمَ حَشَرْتَنِىٓ …20:125
ٱنصُرْنِى, then two leaves23:26, 23:39, 29:30
إِنَّ قَوْمِى كَذَّبُونِ26:117

Each of the six words the owner named, and the rule that placed it:

WordsSame letters?What the code didRuleVerdict against the owner's rules
رَبِّ / رَبُّyes (رب, root ر ب ب)one opening group, split into two nodes at word 2normalizeWord groups (section 3), spell splits on kasra against damma (section 4)Works as stated. These are different words: رَبِّ is the vocative "my Lord", رَبُّ in 26:24 and 26:28 is Musa's answer "Lord of the heavens".
إِنَّ / إِنِّىno (ان against اني)separate nodesany keyWorks as stated. إنّ and إنّي are different words.
إِنِّى / إِنِّىٓyesseparate nodes, 5 and 2 ayatspell keeps the maddah U+0653, which is outside U+06D6 to U+06EDGap. The maddah is there only because the next word starts with a hamza (أَعُوذُ in 11:47, أَخَافُ in 26:12). It comes from the neighbouring word, the same family as the marks #422 stopped counting.
أَنَّىٰ / إِنِّى, إِنِّىٓyes for normalizeWord (اني), no for spellseparate nodes; a separate phrase under ?r=marks split the nodes; only the root guard keeps the phrases apartRight result, fragile reason. Different words ("how" against "indeed I"). Without the root guard, search would treat them as one phrase, because normalizeWord folds ى into ي.

Two more effects of the two keys show on this page:

The Musa ayat

Three trees put ayat of Musa through the same rules:

TreeFirst nodeSecond nodeMark that splits themWhere the mark comes from
?o=قال+موسيمُوسَىٰ: 7:104, 7:128, 10:84, 10:88, 28:37مُوسَىٰٓ: 10:77, 14:8, 40:27maddah U+0653the next word starts with a hamza (أَتَقُولُونَ, إِن, إِنِّى)
?o=ولقد+ءاتينامُوسَى: 8 ayat, such as 2:87مُوسَىٰ: 17:101, 21:48dagger alif U+0670the mushaf leaves it out before a hamzat wasl (ٱلْكِتَـٰبَ in 2:87)
?o=قال+ربرَبِّ: 24 ayatرَبُّ: 26:24, 26:28kasra against dammathe word itself (section 10)

In the wider ?o=ولقد+ءاتينا tree, ءَاتَيْنَا (14 ayat) and ءَاتَيْنَآ (21:51, before إِبْرَٰهِيمَ) also split on a maddah. The first tree also splits its first word into وَقَالَ (6 ayat) and قَالَ (7:128, 10:77), and the piece list cuts the قال spelling into its own piece. The quiz question built from that piece shows only «قَالَ» with two branches, and the reader must type موسى into both.

Every same-letter split in the dataset

To see whether the example is an exception or a pattern, the analysis walked every opening's drawn tree and collected each pair of sibling nodes whose first words have the same letters (normalizeWord) but different spell. There are 196 such pairs. Each falls in one of four kinds:

KindPairsExamplesSource of the mark
maddah on the last long vowel (U+0653, or آ)112إِنِّى/إِنِّىٓ, مُوسَىٰ/مُوسَىٰٓ, ٱلَّذِى/ٱلَّذِىٓ (38/13 ayat after هُوَ), مَا/مَآ, كَفَرُوا۟/كَفَرُوٓا۟, تَتَّخِذُوا۟/تَتَّخِذُوٓا۟next word starts with a hamza
dagger alif on a final ى7مُوسَى/مُوسَىٰ, إِلَى/إِلَىٰ, عَلَى/عَلَىٰnext word starts with a hamzat wasl
a vowel where the other has none44لَهُم/لَهُمُ, مِن/مِنَ, عَن/عَنِ, قُلْ/قُلِ, قَالَت/قَالَتِjoining vowel before a hamzat wasl
a different word or case33رَبِّ/رَبُّ, إِن/إِنَّ, ٱللَّهُ/ٱللَّهَ, كَذَّبَتْ/كُذِّبَتْ, جَنَّـٰتُ/جَنَّـٰتٌthe word itself

Only the last kind is a difference in the word. The first three are all marks the neighbouring word puts there.

In the quiz, 151 of the 872 questions have two branches at their first split that the reader must type with the same letters. Ignoring a maddah on the last long vowel (including the وٓا۟ endings) would merge those two branches in 94 of them, and ignoring a final dagger alif as well, in 96. In 95 questions every same-letter pair would merge.

Retrieval

Why do 10:77 قَالَ مُوسَىٰٓ and 7:128 قَالَ مُوسَىٰ end up in different nodes?

Gaps and contradictions to decide

Each item names what the code does, the rule it meets or breaks, and a question for the owner. None is fixed here.

  1. The maddah from a following hamza still splits nodes. 112 of 196 same-letter pairs. The rule from 2026-10-21 says marks that come from the neighbouring word do not count, and the code lists only three. The maddah on a word's last long vowel (إِنِّىٓ, مُوسَىٰٓ, ٱلَّذِىٓ, قَالُوٓا۟) is in the same family. A maddah inside a word (جَآءَ, ٱلسَّمَآءِ) belongs to the word and is a different case. Should a final maddah stop counting?
  2. The docs contradict item 1. docs/quiz-ayah-branches.md (line 28) names تَتَّخِذُوا۟ and تَتَّخِذُوٓا۟ as the example of nodes that stay separate. That pair differs only by a maddah from the next word. If item 1 changes, this example changes with it.
  3. The dagger alif dropped before a wasl splits nodes. 7 pairs, such as مُوسَى/مُوسَىٰ. Same family as item 1. Should a final dagger alif stop counting?
  4. The joining vowel before a wasl splits nodes, by the owner's choice. 44 pairs. Option D kept لَهُمُ apart from لَهُم. By origin it is a neighbour's mark like the dropped sukun. Keep option D as it is, or extend it?
  5. Groups, phrases and pieces use letters; the tree and the quiz tree use marks. Consequences seen above: the قال رب page shows a phrase that its tree does not have as one node; قَالَ رَبِّ إِنِّى is a piece of 7 that the quiz skips, though the page draws 5 and 2; the quiz question for قال موسى shows only «قَالَ» as its opening, because the marks tree splits right after it. Should pieces and the quiz cut on the same key as the drawn tree?
  6. "Diacritics removed" is narrower than the code. The plan chose to fold the alif and ya spellings, and normalizeWord folds ى into ي and أ إ آ ٱ into ا. أَنَّىٰ and إِنِّى then share letters, and only the root guard separates them. ء ؤ ئ are not folded, so the id is ولقد ءاتينا موسي, while the suggestion box tolerates hamza spellings. Is ى against ي a letter difference that should keep two words apart in the grouping key, or does the plan's folding stand?
  7. A quiz question can ask for the same letters twice. 151 questions. The grading pairs such branches by what follows, so they can be answered, but the reader sees two fields for one word. It goes away entirely for 95 of them if items 1 and 3 change.

How to close the gaps

These are proposals, not changes. The numbers come from applying each one to a scratch copy of the code and running it on the same data.

Fix A: two more context marks in spell (items 1 to 3). After the existing three removals, spell (line 98) would also drop a maddah on the last pronounced letter (\u0653 at the end, or before a final silent alif as in قَالُوٓا۟) and a dagger alif after a final ى. A maddah inside a word (جَآءَ) stays. The one-line change:

.replace(/\u0652$/u, '').normalize('NFD')
  .replace(/\u0653(?=\u0627?$)/u, '')
  .replace(/(\u0649)\u0670$/u, '$1')
  .normalize('NFC')

Measured effect:

NowWith fix A
Same-letter sibling pairs in all trees19675 (the 44 joining vowels and the real differences)
Quiz questions with a same-letter pair at the first split151 of 87256
Quiz questions with a same-letter pair anywhere16364
?o=قال+ربإِنِّى (5) and إِنِّىٓ (2)one node إِنِّى (7)
?o=قال+موسيمُوسَىٰ (5) and مُوسَىٰٓ (3)one node, root reads قَالَ مُوسَىٰ (8)
?o=ولقد+ءاتيناءَاتَيْنَا / ءَاتَيْنَآ, then مُوسَى / مُوسَىٰءَاتَيْنَا (15), then مُوسَى (10)

رَبِّ / رَبُّ, إِنَّ / إِنِّى and أَنَّىٰ stay apart, as they should. The merged nodes keep each ayah's own spelling in forms, so the amber context button shows the maddah where it was. Three things move with the change. One quiz test fails as expected: src/lib/quiz/branchQuestions.test.ts asserts that تَتَّخِذُوا۟ and تَتَّخِذُوٓا۟ are separate nodes, and that test and line 28 of docs/quiz-ayah-branches.md flip. ayat.json must be rebuilt (npm run quran:branches), because continues decides the next-ayah leaves with spell. The plan's list of ignored marks gains two entries.

Fix B: the joining vowel (item 4). The same pattern with [\u064E-\u0650]$ would merge لَهُم and لَهُمُ, but it cannot tell a joining vowel from a case ending: it would also merge ٱللَّهُ with ٱللَّهَ and خَلَقَ with خَـٰلِقُ. A safe version needs the next word: drop a final short vowel only when the word after it starts with a hamzat wasl (ٱ). That is a change to how spell is called, since it sees one word at a time today. Option D keeps these apart on purpose, so this one waits for your call.

Fix C: one key for pieces and quiz (item 5). pieces and branchQuestions would walk branch (the marks tree) instead of phraseTree. Then قَالَ رَبِّ إِنِّى is cut the way the page draws it, and the قال موسى question opens with قَالَ مُوسَىٰ instead of «قَالَ». After fix A most of these mismatches disappear anyway, so fix C matters only for real differences such as رَبِّ / رَبُّ.

Item 6 needs no code if the plan's folding stands. Item 7 shrinks from 151 to 56 with fix A, and the rest are joining vowels (fix B) or real differences, which the quiz is right to ask.

Primary sources

src/lib/quran/branches.ts holds grouping, the tree, pieces and phrases; src/lib/quran/normalize.ts holds normalizeWord; src/app/quran/branches/page.tsx reads ?o=, ?r= and ?w=; src/lib/quiz/branchQuestions.ts and src/lib/quiz/branchAnswers.ts build and grade the quiz; docs/plans/quran-shared-openings.md and docs/quiz-ayah-branches.md state the intended rules. Earlier lessons on this feature: The ayah next door and Grading a tree without losing the text.