# سجلُّ الحالات والحلول — مرجعُ المزالق > دليلٌ عمليٌّ لكلِّ مشكلةٍ عالجناها (وما يُشبهها مستقبلاً). كلُّ حالة: **العَرَض ← الجذر ← الحلّ ← الموضع**. رتّبناها بالمجال. عند مواجهة سلوكٍ غريب، ابحثْ هنا أولاً. --- ## 🔊 الصوت والنطق ### ح‑1 · الشدّةُ تُسمَع كالسكون (أوّل، محرّم، عدّ) - **العَرَض:** كلماتٌ بشدّةٍ يَنطقها العصبيُّ/الآليُّ ناقصةً («أَوْل» بدل «أوّل»). - **الجذر:** ترتيبُ التشكيل «حرف+حركة+شدّة» (وما يُنتجه NFC)؛ المحرّكُ يتوقّع «حرف+شدّة+حركة». - **الحلّ:** `forSynthesis`/`reorderMarks` في `src/arabic-normalize.js`، تُطبَّق على نصّ التركيب فقط. تفصيلٌ كاملٌ في [`audio-pronunciation.md`](audio-pronunciation). ### ح‑2 · لفظُ الجلالة «الله» مشوّهٌ مع حركة الإعراب (lhi/lho) - **العَرَض:** اللهِ → «lhi»، اللهُ → «lho». - **الجذر:** قاعدةُ espeak الخاصّةُ لـ«الله» تعملُ للكلمة المجرّدة وحدها؛ حركةُ الإعراب تكسرُها. - **الحلّ:** `jalalahBare` يستبدلها بإملاءٍ صوتيٍّ بمدٍّ صريح «اَلّاه + الحركة» (ولـ«لله/بالله/اللهمّ» نظائرُها). ### ح‑3 · المقطعُ لا يُنطَق رغم وجود التسجيل (مطابقة حرفيّة) - **العَرَض:** «وّ» تُسمَع في الاستوديو لكنّها لا تُنطَق داخل كلمة. - **الجذر:** المطابقةُ حرفيّةٌ 100%؛ اختلافُ ترتيبِ التشكيل/مسافةٍ بين النصّين. - **الحلّ:** وحِّدِ الترتيبَ على الطرفين (`reorderMarks` في `segment()`)؛ بعد أيِّ محتوًى منطوق: `npm run gen:audio`. ### ح‑4 · أوّلُ تشغيلٍ لمقطعٍ يرتدُّ للنطق الآليّ - **الجذر:** التشغيلُ قبل جهوزيّة المقطع. - **الحلّ:** `playUrlAsync` ينتظر `canplay`؛ `primeVoices` قبل أوّل تشغيلٍ في قارئ القصص. ### ح‑7 · نطقُ نصٍّ يُعرَض مجرّداً (مرجعٌ منطوقٌ مشكول) - **العَرَض:** كلماتٌ/أحرفٌ تُعرَض مجرّدةً عمداً (كلماتُ لعبة الدمج `content/words.js`، أسماءُ الأحرف «ألف») فيَنطقها المحرّكُ بالحدس خطأً (ألف→«allaf»، ذرة→«dharra»). - **الجذر:** لا يصحُّ تشكيلُ نصّ العرض (يكسرُ اللعبةَ/الشكل)، والمحرّكُ يَحدِس المجرّد. - **الحلّ:** `src/spoken-ref.js` — خريطةُ «عرض مجرّد ← مرجعٌ منطوقٌ مشكول» يستعملها المولّدان (`gen-audio`/`gen-piper`) عبر `spokenRef(t)` ثمّ `forSynthesis`، **والمفتاحُ يبقى المجرَّدَ** فلا يتأثّر البحث. أسماءُ الأعداد مشكولةٌ أصلاً (`numbers-ar.js`)؛ عبارات الآليّ `robo-phrases.js` مشكولةٌ في المصدر مباشرةً. - **مزلق:** تشكيلُ نصٍّ كان مفتاحاً (كعبارات `NOTICES`) **يغيّر المفتاحَ** فتُولَّد مقاطعُ جديدةٌ وتَيتَمُ القديمةُ ⇒ نظِّفِ المقاطعَ غيرَ المُشار إليها في `tts-manifest.json` من `public/tts/`. ### ح‑5 · أسماءُ الأعداد 0–100 لا تُنطَق - **الجذر:** تشكيلُ `numbers-ar.js`/`numerals.js` لا يطابق `letters.js` حرفيّاً (المطابقة حرفيّة). - **الحلّ:** وحِّدِ التشكيلَ (صِفر، واحِد، ثَلاثة…). ### ح‑6 · الانتقالُ التلقائيّ يَعلَق على جهازٍ بلا أصواتٍ عربيّة - **الجذر:** `speechSynthesis` لا يُطلق `onend` إن لم تَجهز الأصوات. - **الحلّ:** مهلةُ أمانٍ في `speak.js → ttsPath` تُطلق `onend`. --- ## 📦 PWA · البناء · النشر ### ب‑1 · كودٌ/مقاطعُ قديمةٌ بعد إعادة البناء - **الجذر:** عاملُ الخدمة (Service Worker) يُخبّئ كلَّ شيء. - **الحلّ:** إنتاجاً `skipWaiting+clientsClaim+cleanupOutdatedCaches`؛ تطويراً حارسٌ في `sound-prefs.js` يُلغي SW القديم ويمسح الكاش عند `import.meta.env.DEV`. عند الاستعصاء: أعِد التحميل مرّتين أو ألغِ تسجيلَ SW. ### ب‑2 · صفحةٌ جديدةٌ لا تظهر في البناء - **الجذر:** كلُّ `*.html` نقطةُ دخولٍ مستقلّة. - **الحلّ:** أضِفها إلى `vite.config.js → rollupOptions.input` وإلّا لم تُبنَ. ### ب‑3 · `/app/*` تُرجِع 404 رغم نجاح النشر - **الجذر:** نوعُ بناء GitHub Pages `legacy` لا `workflow`. - **الحلّ:** `gh api --method PUT .../pages -f build_type=workflow` ثمّ أعِد تشغيل المسار. ### ب‑4 · تلاوةُ الحصري (~79م) لا يجوز تضمينُها في precache - **الحلّ:** `globIgnores` لها + `runtimeCaching` (CacheFirst، `quran-husary`) فتعمل دون إنترنت بعد أوّل استماع. النصُّ مُضمَّنٌ فيعمل دائماً. ### ب‑5 · `vite: Permission denied` على ext4 - **الحلّ:** `chmod +x node_modules/.bin/*`. ### ح‑12 · نصٌّ منطوقٌ لا مقطعَ له ⇐ يُنطَقُ بصوتِ النظام (أو يصمت) - **العَرَض:** عبارةٌ جديدةٌ في لعبةٍ أو ردٍّ للآليِّ تُنطَقُ بصوتٍ رديءٍ (أو تصمتُ على لينكس). - **الجذر:** مطابقةُ المقاطعِ **حرفيّةٌ بالنصِّ الكامل**؛ فأيُّ نصٍّ لم يمرَّ على `gen:piper` لا مقطعَ له. ويشملُ ذلك **النصوصَ المركَّبةَ وقتَ التشغيل** (`` `${a} ${b}` ``) — استعمِلْ `roboSayChain([a,b])` فيُنطَقَ كلُّ جزءٍ بمقطعِه. - **الحلّ:** أضِفِ العبارةَ إلى مصدرٍ يقرؤُه المولّد (`GAME_INTROS`/`NOTICES`/…) **مشكولةً بالكامل** ثمّ `npm run gen:piper`. **التوليدُ تفاضليٌّ** (يتخطّى الموجود) فيستغرقُ ثوانيَ لا 25 دقيقة؛ و`PIPER_FORCE=1` يُعيدُ الكلَّ عندَ تغييرِ النموذج. - **مزلقٌ مرافق:** ما يستوردُه المولّدُ يجبُ أن **يخلوَ من DOM** — استيرادُ وحدةِ واجهةٍ (كـ`orient-widget`) يجرُّ `voices-bundled.json` فيفشلُ في Node. ضعِ النصوصَ في `content/`. - **نطقٌ خاطئٌ رغمَ التشكيل؟** استعمِلْ `SYNTH_FIX` في `gen-piper.mjs`: يُغيّرُ **نصَّ التركيبِ فقط** والمفتاحُ يبقى، فلا يتغيّرُ العرضُ ولا المطابقة (مثالٌ: «الوَقْت» كان يُسمَعُ «الوَخْت»). --- ## 📦 التحزيمُ الأصليّ (Linux · Android) > النظامُ الكاملُ موثّقٌ في [`packaging.md`](packaging). المنسّق: `scripts/build-packages.sh` (`npm run pkg:linux|pkg:apk|pkg:all`). يَلُفُّ مخرجَ `npm run build` نفسَه: Linux بـelectron-builder، Android بـCapacitor. ### ز‑1 · DEB يفشل: `Please specify project homepage` - **الحلّ:** أضِفْ `homepage` (و`author`) في `package.json`؛ يتطلّبهما electron-builder لوسمِ DEB/RPM. ### ز‑2 · RPM يفشل عبر electron-builder (`rpmbuild failed`) - **الجذر:** `fpm` المرفقُ (1.9.3) قديمٌ لا يوافقُ `rpmbuild` الحديث (4.20+) — يُولّد spec مرفوضًا. (AppImage/DEB لا يستعملان rpmbuild فينجحان.) - **الحلّ:** ابْنِ DEB ثمّ حوّلْه إلى RPM بـ`fakeroot alien --to-rpm` (`rpm_from_deb` في السكربت). يتطلّب `alien`+`fakeroot`. ### ز‑3 · APK يفشل: `Could not resolve project :capacitor-android` - **الجذر:** `npm install --no-save @capacitor/assets` **حذفَ** حزمَ Capacitor المثبّتةَ سابقًا بـ`--no-save` (غيرُ مذكورةٍ في `package.json` = «زائدة») فاختفى `node_modules/@capacitor/android`. - **الحلّ:** ضَعْ `@capacitor/core`/`cli`/`android` (@^6، صغيرةٌ بلا تَنّات) **في `package.json`**؛ و`assets --no-save` بعدها لا يَحذِفُها. حافِظْ على توافقِ إصداراتها (نفسُ الـmajor). أبقِ الثقيلَ (`electron`/`electron-builder`/`@capacitor/assets`+`sharp`) خارجَ `package.json` (عند الطلب) ليبقى نشرُ الويب خفيفًا، و**نظّفِ `package-lock.json`** بـ`npm install --package-lock-only`. ### ز‑4 · بناءٌ فاشلٌ يُبلّغُ «نجاحًا» بنسخةٍ قديمة - **الحلّ:** `build_apk` يَحذِفُ `app-debug.apk` قبل `gradlew` فلا يُنسَخَ APK قديمٌ عند الفشل. ### ز‑5 · أيقونةُ أندرويد وشاشةُ البدء - المصدرُ `assets/` (icon-foreground/background + splash/-dark). `npx @capacitor/assets generate --android` (داخل `build_apk` بعد `cap sync`) يكتبُ كلَّ المقاسات في `res/mipmap-*` (أيقونةٌ بلا نصّ) و`res/drawable-*` (شاشةٌ بالعنوان). `android/` مُولَّدٌ (gitignored) فالأيقونةُ تُعاد كتابتُها من `assets/` في كلّ بناء. --- ## 🧩 البنية · تكرارُ المنطق ### ب‑1 · إصلاحٌ في الوحدةِ المركزيّةِ لا يصلُ صفحةً تحملُ نسختَها المحلّيّة (v1.6) - **العَرَض:** تُصلِحُ سلوكًا في `src/versus.js` ويعملُ في صفحةٍ (`lang.html`) ولا يعملُ في أخرى (`play.html`) — والمستخدمُ يُبلِّغُ أنّ العلّةَ باقية. - **الجذر:** `play.html` يحملُ **نسخًا محلّيّةً** من `meCard`/`vsMakePlayers`/`vsSetup` (ولعبةُ الذاكرةِ نسخةً ثالثةً منفصلة) فلا يستوردُ المركزيَّ أصلًا. بينما `lang.html` يستوردُه فيَرِثُ كلَّ إصلاحٍ مجّانًا. - **الحلّ:** قبلَ إعلانِ إصلاحٍ «شامل»، ابحثْ عن نسخٍ محلّيّةٍ للدالّةِ نفسِها (`grep -n "function "`) — وأصلِحْها أو (الأفضل) اجعلْها تستوردُ المركزيّ. **التكرارُ هو مصدرُ هذا النوعِ من الأخطاء.** - **أثرٌ مشابهٌ عولِج:** منطقُ «مفتاحِ الطرفَين» في سجلِّ التنافسِ كان مكرَّرًا؛ صُدِّرَ `vsPairKey` مصدرًا واحدًا يستعملُه التجميعُ والحذفُ معًا (اختلافُهما كان سيَحذفُ سجلًّا غيرَ المقصود). ### ب‑2 · نداءُ دالّةٍ غيرِ موجودةٍ يُجهِضُ حلقةَ المؤقّتِ **صامتًا** (v1.6) - **العَرَض:** «اللعبُ ضدّ الوقت» ينتهي وقتُه فلا يحدثُ شيء: لا حركةَ تُلعَبُ ولا ينتقلُ الدورُ ولا يتفاعلُ الآليّ. بلا رسالةِ خطأٍ ظاهرة. - **الجذر:** `sfx.wrong()` — و`src/sfx.js` يُصدّرُ `success`/`heal`/`nope`/`tap` **فقط**. الاستثناءُ يقعُ داخلَ `requestAnimationFrame` فيُجهِضُ بقيّةَ الشيفرةِ بهدوء. بقيَ الخللُ في الذاكرةِ ودائرةٍ وعلامةَ **منذ v1.5.4 دونَ اكتشاف** (6 مواضعَ إجمالًا). - **الحلّ:** تحقّقْ من وجودِ الدالّةِ قبلَ استعمالِها. فحصٌ سريعٌ يكشفُ الكلَّ: `node -e '…' ` قارِنْ `sfx\.([a-z]+)\(` في كلِّ الملفّاتِ بمُصدَّراتِ `src/sfx.js`. - **قاعدةٌ عامّة:** أيُّ استثناءٍ داخلَ rAF/مؤقّتٍ يبتلعُ بقيّةَ المنطق — لا تفترضْ أنّ «لا شيءَ يحدثُ» يعني منطقًا خاطئًا؛ افحصِ الطرفيّةَ (console) أوّلًا. --- ### ب‑3 · صفحةٌ تظهرُ فارغةً — استدعاءُ إقلاعٍ قبلَ تعريفاتِ `let`/`const` (TDZ) (v1.7) - **العَرَض:** `quran-full.html` تُفتَحُ فارغةً تمامًا (لا أحزاب، لا رسالةَ بوّابة). - **الجذر:** كان الإقلاعُ (`renderHizbs()`) يُستدعى في **وسطِ** السكربت، فيَستدعي `stopAll()` التي تقرأُ `let mode`/`seq` المُعرَّفتَينِ **لاحقًا** في الملفّ. `let`/`const` مرفوعانِ لكنْ غيرُ مُهيَّأَينِ حتّى سطرِ تعريفِهما (**منطقةُ الموتِ الزمنيّة TDZ**) ⇒ `ReferenceError: Cannot access 'mode' before initialization` يُجهِضُ الوحدةَ كلَّها صامتًا فتبقى `#view` فارغة. - **الحلّ:** لُفَّ الإقلاعَ في دالّةٍ (`boot()`) تُستدعى في **نهايةِ السكربت** بعدَ كلِّ التعريفات — كما يفعلُ `quran.html` (سطرُ `renderList()` الأخير). القاعدة: أيُّ استدعاءٍ على المستوى الأعلى يَلمِسُ متغيّراتٍ يجب أن يأتيَ **بعدَ** تعريفِها، لا قبلَها. انظر [[#6-الأداءُ-الصامتُ-أسوأُ-من-العطلِ-الصريح]] (الاستثناءُ يبتلعُ المنطقَ صامتًا). ## 🗄️ التخزين · الحسابات ### خ‑1 · بياناتُ التطوير «تختفي» - **الجذر:** التخزين (localStorage/IndexedDB) مرتبطٌ بالأصل/المنفذ؛ تغيّرُ منفذِ Vite يُخفيها. - **الحلّ:** `server/preview.port=5174 + strictPort` مثبَّتان في `vite.config.js`. ### خ‑2 · تقدّمُ حسابٍ يطغى على آخر - **الجذر:** المفتاحُ `tilmithi_progress_v1__` يُحسَب مرّةً عند الاستيراد. - **الحلّ:** لقراءة حسابٍ آخر استعمل `getStatsFor(id)`/`loadProgressFor(id)`؛ وداخل المفتاح الواحد استعمل نمطَ **حمّل‑عدّل‑احفظ** دائماً. ### خ‑3 · لوحةُ الأهل تُكشَف منها حلقةُ «علّم الآليّ» - **الجذر:** الـoverlay لا يحجب الخلفيّة. - **الحلّ:** `parentOnly` يُعتِم الـoverlay؛ غيرُ الوالد يُطلَب كلمةَ المرور **كلَّ مرّة** (لا جلسةَ مؤقّتة)؛ الإغلاقُ يعيد للحساب الذي فُتحت منه. --- ## 🎨 الواجهة · السمة · التجاوب ### و‑1 · عناصرُ فاتحةٌ في الوضع الداكن - **الجذر:** ألوانٌ مثبّتةٌ يدويّاً (inline أو `#fff`) لا تتبع المتغيّرات. - **الحلّ:** استعمل `var(--card)`/`var(--bg)`/`var(--ink)` (تقنيةُ «لا تغيير في الفاتح، يدكَن في الداكن»)، أو انتقاءات `html[data-theme="dark"]` بـ`!important` للـinline. مطبَّقٌ على القرآن (ورقٌ داكن) والاستوديو ومسجّل التمرّن وعناصر النماذج. ### و‑2 · وميضُ السمة عند التحميل - **الحلّ:** سكربتٌ كلاسيكيٌّ صغيرٌ في `` يضبط `data-theme` قبل الرسم + نمطٌ حرجٌ مضمَّن؛ السمةُ المشتركةُ في `src/dark.css` تُستورَد عبر `theme.js`. ### و‑3 · أزرارٌ علويّةٌ متنافرةُ الأحجام تلتفُّ على الهاتف - **الحلّ:** `src/topbar.css` المشترَك (عبر `theme.js`): `.topbtn` بلا التفافِ نصّ + `.iconbtn` مربّعٌ موحّد + `.topbtns` يلتفُّ بأناقة + تصغيرٌ `@media(max-width:430px)`. ### و‑4 · `classList.add("")` يرمي استثناءً - **الحلّ:** احرسْ ضدّ القيمة الفارغة (كان سببَ تعطّل أزرار القراءة). ### و‑5 · نصُّ `