-
Notifications
You must be signed in to change notification settings - Fork 0
pitfalls and solutions
SalehGNUTUX edited this page Jul 24, 2026
·
3 revisions
دليلٌ عمليٌّ لكلِّ مشكلةٍ عالجناها (وما يُشبهها مستقبلاً). كلُّ حالة: العَرَض ← الجذر ← الحلّ ← الموضع. رتّبناها بالمجال. عند مواجهة سلوكٍ غريب، ابحثْ هنا أولاً.
- العَرَض: كلماتٌ بشدّةٍ يَنطقها العصبيُّ/الآليُّ ناقصةً («أَوْل» بدل «أوّل»).
- الجذر: ترتيبُ التشكيل «حرف+حركة+شدّة» (وما يُنتجه NFC)؛ المحرّكُ يتوقّع «حرف+شدّة+حركة».
-
الحلّ:
forSynthesis/reorderMarksفيsrc/arabic-normalize.js، تُطبَّق على نصّ التركيب فقط. تفصيلٌ كاملٌ فيaudio-pronunciation.md.
- العَرَض: اللهِ → «lhi»، اللهُ → «lho».
- الجذر: قاعدةُ espeak الخاصّةُ لـ«الله» تعملُ للكلمة المجرّدة وحدها؛ حركةُ الإعراب تكسرُها.
-
الحلّ:
jalalahBareيستبدلها بإملاءٍ صوتيٍّ بمدٍّ صريح «اَلّاه + الحركة» (ولـ«لله/بالله/اللهمّ» نظائرُها).
- العَرَض: «وّ» تُسمَع في الاستوديو لكنّها لا تُنطَق داخل كلمة.
- الجذر: المطابقةُ حرفيّةٌ 100%؛ اختلافُ ترتيبِ التشكيل/مسافةٍ بين النصّين.
-
الحلّ: وحِّدِ الترتيبَ على الطرفين (
reorderMarksفيsegment())؛ بعد أيِّ محتوًى منطوق:npm run gen:audio.
- الجذر: التشغيلُ قبل جهوزيّة المقطع.
-
الحلّ:
playUrlAsyncينتظرcanplay؛primeVoicesقبل أوّل تشغيلٍ في قارئ القصص.
-
العَرَض: كلماتٌ/أحرفٌ تُعرَض مجرّدةً عمداً (كلماتُ لعبة الدمج
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/.
-
الجذر: تشكيلُ
numbers-ar.js/numerals.jsلا يطابقletters.jsحرفيّاً (المطابقة حرفيّة). - الحلّ: وحِّدِ التشكيلَ (صِفر، واحِد، ثَلاثة…).
-
الجذر:
speechSynthesisلا يُطلقonendإن لم تَجهز الأصوات. -
الحلّ: مهلةُ أمانٍ في
speak.js → ttsPathتُطلقonend.
- الجذر: عاملُ الخدمة (Service Worker) يُخبّئ كلَّ شيء.
-
الحلّ: إنتاجاً
skipWaiting+clientsClaim+cleanupOutdatedCaches؛ تطويراً حارسٌ فيsound-prefs.jsيُلغي SW القديم ويمسح الكاش عندimport.meta.env.DEV. عند الاستعصاء: أعِد التحميل مرّتين أو ألغِ تسجيلَ SW.
-
الجذر: كلُّ
*.htmlنقطةُ دخولٍ مستقلّة. -
الحلّ: أضِفها إلى
vite.config.js → rollupOptions.inputوإلّا لم تُبنَ.
-
الجذر: نوعُ بناء GitHub Pages
legacyلاworkflow. -
الحلّ:
gh api --method PUT .../pages -f build_type=workflowثمّ أعِد تشغيل المسار.
-
الحلّ:
globIgnoresلها +runtimeCaching(CacheFirst،quran-husary) فتعمل دون إنترنت بعد أوّل استماع. النصُّ مُضمَّنٌ فيعمل دائماً.
-
الحلّ:
chmod +x node_modules/.bin/*.
- العَرَض: عبارةٌ جديدةٌ في لعبةٍ أو ردٍّ للآليِّ تُنطَقُ بصوتٍ رديءٍ (أو تصمتُ على لينكس).
-
الجذر: مطابقةُ المقاطعِ حرفيّةٌ بالنصِّ الكامل؛ فأيُّ نصٍّ لم يمرَّ على
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: يُغيّرُ نصَّ التركيبِ فقط والمفتاحُ يبقى، فلا يتغيّرُ العرضُ ولا المطابقة (مثالٌ: «الوَقْت» كان يُسمَعُ «الوَخْت»).
النظامُ الكاملُ موثّقٌ في
packaging.md. المنسّق:scripts/build-packages.sh(npm run pkg:linux|pkg:apk|pkg:all). يَلُفُّ مخرجَnpm run buildنفسَه: Linux بـelectron-builder، Android بـCapacitor.
-
الحلّ: أضِفْ
homepage(وauthor) فيpackage.json؛ يتطلّبهما electron-builder لوسمِ DEB/RPM.
-
الجذر:
fpmالمرفقُ (1.9.3) قديمٌ لا يوافقُrpmbuildالحديث (4.20+) — يُولّد spec مرفوضًا. (AppImage/DEB لا يستعملان rpmbuild فينجحان.) -
الحلّ: ابْنِ DEB ثمّ حوّلْه إلى RPM بـ
fakeroot alien --to-rpm(rpm_from_debفي السكربت). يتطلّبalien+fakeroot.
-
الجذر:
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.
-
الحلّ:
build_apkيَحذِفُapp-debug.apkقبلgradlewفلا يُنسَخَ APK قديمٌ عند الفشل.
- المصدرُ
assets/(icon-foreground/background + splash/-dark).npx @capacitor/assets generate --android(داخلbuild_apkبعدcap sync) يكتبُ كلَّ المقاسات فيres/mipmap-*(أيقونةٌ بلا نصّ) وres/drawable-*(شاشةٌ بالعنوان).android/مُولَّدٌ (gitignored) فالأيقونةُ تُعاد كتابتُها منassets/في كلّ بناء.
-
العَرَض: تُصلِحُ سلوكًا في
src/versus.jsويعملُ في صفحةٍ (lang.html) ولا يعملُ في أخرى (play.html) — والمستخدمُ يُبلِّغُ أنّ العلّةَ باقية. -
الجذر:
play.htmlيحملُ نسخًا محلّيّةً منmeCard/vsMakePlayers/vsSetup(ولعبةُ الذاكرةِ نسخةً ثالثةً منفصلة) فلا يستوردُ المركزيَّ أصلًا. بينماlang.htmlيستوردُه فيَرِثُ كلَّ إصلاحٍ مجّانًا. -
الحلّ: قبلَ إعلانِ إصلاحٍ «شامل»، ابحثْ عن نسخٍ محلّيّةٍ للدالّةِ نفسِها (
grep -n "function <name>") — وأصلِحْها أو (الأفضل) اجعلْها تستوردُ المركزيّ. التكرارُ هو مصدرُ هذا النوعِ من الأخطاء. -
أثرٌ مشابهٌ عولِج: منطقُ «مفتاحِ الطرفَين» في سجلِّ التنافسِ كان مكرَّرًا؛ صُدِّرَ
vsPairKeyمصدرًا واحدًا يستعملُه التجميعُ والحذفُ معًا (اختلافُهما كان سيَحذفُ سجلًّا غيرَ المقصود).
- العَرَض: «اللعبُ ضدّ الوقت» ينتهي وقتُه فلا يحدثُ شيء: لا حركةَ تُلعَبُ ولا ينتقلُ الدورُ ولا يتفاعلُ الآليّ. بلا رسالةِ خطأٍ ظاهرة.
-
الجذر:
sfx.wrong()— وsrc/sfx.jsيُصدّرُsuccess/heal/nope/tapفقط. الاستثناءُ يقعُ داخلَrequestAnimationFrameفيُجهِضُ بقيّةَ الشيفرةِ بهدوء. بقيَ الخللُ في الذاكرةِ ودائرةٍ وعلامةَ منذ v1.5.4 دونَ اكتشاف (6 مواضعَ إجمالًا). -
الحلّ: تحقّقْ من وجودِ الدالّةِ قبلَ استعمالِها. فحصٌ سريعٌ يكشفُ الكلَّ:
node -e '…'قارِنْsfx\.([a-z]+)\(في كلِّ الملفّاتِ بمُصدَّراتِsrc/sfx.js. - قاعدةٌ عامّة: أيُّ استثناءٍ داخلَ rAF/مؤقّتٍ يبتلعُ بقيّةَ المنطق — لا تفترضْ أنّ «لا شيءَ يحدثُ» يعني منطقًا خاطئًا؛ افحصِ الطرفيّةَ (console) أوّلًا.
-
العَرَض:
quran-full.htmlتُفتَحُ فارغةً تمامًا (لا أحزاب، لا رسالةَ بوّابة). -
الجذر: كان الإقلاعُ (
renderHizbs()) يُستدعى في وسطِ السكربت، فيَستدعيstopAll()التي تقرأُlet mode/seqالمُعرَّفتَينِ لاحقًا في الملفّ.let/constمرفوعانِ لكنْ غيرُ مُهيَّأَينِ حتّى سطرِ تعريفِهما (منطقةُ الموتِ الزمنيّة TDZ) ⇒ReferenceError: Cannot access 'mode' before initializationيُجهِضُ الوحدةَ كلَّها صامتًا فتبقى#viewفارغة. -
الحلّ: لُفَّ الإقلاعَ في دالّةٍ (
boot()) تُستدعى في نهايةِ السكربت بعدَ كلِّ التعريفات — كما يفعلُquran.html(سطرُrenderList()الأخير). القاعدة: أيُّ استدعاءٍ على المستوى الأعلى يَلمِسُ متغيّراتٍ يجب أن يأتيَ بعدَ تعريفِها، لا قبلَها. انظر #6-الأداءُ-الصامتُ-أسوأُ-من-العطلِ-الصريح (الاستثناءُ يبتلعُ المنطقَ صامتًا).
- الجذر: التخزين (localStorage/IndexedDB) مرتبطٌ بالأصل/المنفذ؛ تغيّرُ منفذِ Vite يُخفيها.
-
الحلّ:
server/preview.port=5174 + strictPortمثبَّتان فيvite.config.js.
-
الجذر: المفتاحُ
tilmithi_progress_v1__<id>يُحسَب مرّةً عند الاستيراد. -
الحلّ: لقراءة حسابٍ آخر استعمل
getStatsFor(id)/loadProgressFor(id)؛ وداخل المفتاح الواحد استعمل نمطَ حمّل‑عدّل‑احفظ دائماً.
- الجذر: الـoverlay لا يحجب الخلفيّة.
-
الحلّ:
parentOnlyيُعتِم الـoverlay؛ غيرُ الوالد يُطلَب كلمةَ المرور كلَّ مرّة (لا جلسةَ مؤقّتة)؛ الإغلاقُ يعيد للحساب الذي فُتحت منه.
-
الجذر: ألوانٌ مثبّتةٌ يدويّاً (inline أو
#fff) لا تتبع المتغيّرات. -
الحلّ: استعمل
var(--card)/var(--bg)/var(--ink)(تقنيةُ «لا تغيير في الفاتح، يدكَن في الداكن»)، أو انتقاءاتhtml[data-theme="dark"]بـ!importantللـinline. مطبَّقٌ على القرآن (ورقٌ داكن) والاستوديو ومسجّل التمرّن وعناصر النماذج.
-
الحلّ: سكربتٌ كلاسيكيٌّ صغيرٌ في
<head>يضبطdata-themeقبل الرسم + نمطٌ حرجٌ مضمَّن؛ السمةُ المشتركةُ فيsrc/dark.cssتُستورَد عبرtheme.js.
-
الحلّ:
src/topbar.cssالمشترَك (عبرtheme.js):.topbtnبلا التفافِ نصّ +.iconbtnمربّعٌ موحّد +.topbtnsيلتفُّ بأناقة + تصغيرٌ@media(max-width:430px).
- الحلّ: احرسْ ضدّ القيمة الفارغة (كان سببَ تعطّل أزرار القراءة).
-
العَرَض: أزرارٌ مُنسَّقةٌ بخلفيّةٍ
var(--card)يظهرُ نصُّها أسودَ (غيرَ مرئيٍّ) في الداكن (خياراتُ الساعة/الاتجاهات، بطاقاتُ «الزمان والمكان»، زرُّ «ابدأ الاختبار»، بطاقاتُ الصلاة). -
الجذر: عنصرُ
<button>لا يرثُcolorافتراضًا (يأخذُcanvastext≈أسود)، بخلافِ<div>/<a>التي ترثُ لونَ الـbody. فبطاقةٌ بصنفٍ واحدٍ تظهرُ سليمةً كـdivوخفيّةً كـbutton. -
الحلّ: أضِفْ
color:var(--ink)صراحةً لكلِّ صنفِ زرٍّ مُنسَّق (v1.5.2:clk-opt/or-opt/or-tile/or-per/mcard/pr-card). القاعدة: أيُّ<button>بخلفيّةِ بطاقةٍ يجب أن يضبطَcolorصراحةً.
-
النمط: لونُ الآليِّ المخصَّصُ يُطبَّقُ بـ
document.documentElement.style.setProperty("--robo-body", …)(على الجذر) لا على كلِّ نسخةٍ على حدة، فيَسري تلقائيًّا على كلِّ آليٍّ في الصفحة (المرافقُ العائم + بطاقةُ الدخولِ + ظهرُ بطاقاتِ الذاكرة) بقاعدةٍ واحدة.rc-body{fill:var(--robo-body,#B8C0CC)}. -
المزلق: قاعدةُ
.rc-bodyتُحقَنُ ضمنَ STYLE المرافقِ عند تركيبِه فقط (robo.mount()). فقبلَ التركيب (شاشةُ الدخول) يبقى اللونُ الافتراضيَّ (fillالمضمَّن) — مقبولٌ (ارتدادٌ سليم). ولإظهارِ اللونِ فورًا استُدعيَapplyRoboColor()مبكرًا في وحدةِ الصفحة. -
الملحقاتُ (accessory): تُحقَنُ نصًّا في
<g class="rc-acc">داخلَ الـSVG عند التركيبِ وبعدَ كلِّ حفظ (robo.refresh()←refreshLook()). رسومُها بإحداثيّاتِ viewBox 0 0 80 80. المُعرّفاتُ اللاتينيّةُ تبقىaccessoryبالكود، والعرضُ العربيُّ «ملحقات» لا «إكسسوارات».
-
المشكلة: بطاقاتُ الفهرسِ
<a href>تنتقلُ فورًا، فنطقُ اسمِ البطاقةِ (لغيرِ القارئ) يُقطَعُ بالانتقال. -
الحلّ: حين يُفعَّلُ التوگل (
isVoiceNavOn، مُطفأٌ افتراضيًّا): النقرةُ الأولىe.preventDefault()+ نطقُ الاسمِ + صنفُ.vnav-armed(إبرازٌ + «👆 المِسْ ثانيةً»)، والنقرةُ الثانيةُ على البطاقةِ نفسِها (خلالَ 5ث) تَدَعُ الرابطَ يعمل. مُطفأٌ ⇒ تنقّلٌ بنقرةٍ واحدةٍ للأكبر. النمطُ على.navcard[href]فقط (البطاقاتُ التي تفتحُ نوافذَ في الصفحةِ لا تُقطَع).
-
المطلوب: فلترٌ كهرمانيٌّ دافئٌ (
html.night-mode) للاستماعِ قبلَ النوم، فعّالٌ في القرآنِ والقصصِ لا الفهرسَ وحدَه. -
الحلّ: التوگلُ في الفهرسِ فقط، لكنّ
applyNightMode()يُستدعى كأثرٍ جانبيٍّ عند استيرادِsound-prefs(وحدةٌ يستوردُها كلُّ صفحة)، فيُطبَّقُ الصنفُ على<html>أينما فُتِح. آمنٌ في Node (كلُّه في try/catch، والوحدةُ للمتصفّحِ لا يستوردُها أيُّ أداةِ توليد).
-
الحلّ:
src/icons.js(مولَّدٌ بـnpm run gen:iconsمن Font Awesome Free solid) يُصدّرICONS(اسم→SVG) وEMOJI_ICON(إيموجي→اسم) وiconHtml(emoji)وinjectIcons(root). التلوينُ الحيويُّ لكلّ بطاقةٍ فيsrc/nav-icons.css(--ik+currentColor). الإيموجي تبقى حلًّا احتياطيّاً. -
الرخصة: Font Awesome Free — الأيقونات CC BY 4.0 (متوافقة مع GPL‑3.0)؛ انظر
CREDITS.md.
-
العَرَض:
iconHtml("clock")يطبعُ النصَّ «clock» بدل الأيقونة. -
الجذر:
iconHtml(emoji)يبحثُ فيEMOJI_ICON[emoji]؛ المفتاحُ النصّيُّ لا يُطابَق فتُرجَعُ السلسلةُ كما هي. -
الحلّ: للمفاتيحِ الدلاليّة استعمِلْ
ICONS[key]مباشرةً (كما فيclock.html:const icon=k=>ICONS[k]||"")، أوdata-icon="key"+injectIcons(). مزلقٌ آخر: إضافةُ أيقونةٍ يدويًّا إلىicons.js(كـclock/backup/trophy…) تُمحى إن أُعيدnpm run gen:iconsدون إضافةِ اسمِها إلىMAPفيtools/gen-fa-icons.mjs.
- العَرَض: بطاقاتُ الأقسامِ والألعابِ والأوسمةِ تُظهِرُ إيموجي النظامِ (تختلفُ بين أندرويدَ وسطحِ المكتبِ وقد تكونُ «تُفو»).
-
الجذر: رمزٌ لا تعيينَ له في
EMOJI_ICON، أو حقلٌ يُعرَضُ مباشرةً دونَiconHtml(كاقتطاعِ الرمزِ من نصِّ العنوان بـslice). -
الحلّ:
npm run check:icons— أداةٌ دائمةٌ تفحصُdata-iconوiconHtmlوحقولَic:في كلِّ الصفحاتِ والوحدات وتفشلُ إن وُجِدَ رمزٌ غيرُ معتمَد. الرموزُ الجديدةُ تُضافُ إلىtools/gen-fa-icons.mjs(MAP+EMOJI) ثمّnpm run gen:icons— لا تُحرَّرْsrc/icons.jsفهو مولَّدٌ ويُدهَس. (كشفت 24 رمزًا؛ 77←92 أيقونة.)
- العَرَض: أيقونةُ بطاقةٍ جديدةٍ تظهرُ ضخمةً بلا لون، بينما بطاقاتٌ أخرى سليمة.
-
الجذر:
src/nav-icons.cssيُقيّدُ ارتفاعَ الـSVG ويُلوّنُه لأصنافٍ بعينِها (.gcard/.lcard)؛ صنفُ البطاقةِ الجديدُ غيرُ مذكورٍ فيبقى الـSVG بلا قيد. وfont-sizeلا يؤثّرُ في<svg>بخلافِ الإيموجي النصّيّة. -
الحلّ: أضِفِ الصنفَ الجديدَ إلى قواعدِ
nav-icons.css(الحجمُ + اللونُ + لوحةُ الألوان) — لا ترقيعًا محلّيًّا في الصفحة.
-
src/vocab.jsيجمع كلَّ النصوص القصيرة (يقرؤه الاستوديو والمولّدات). أضِفِ المحتوى مرّةً فيتزامن. عبارات الآليّ فيsrc/robo-phrases.js(REACTIONS+NOTICES) — أشِرْ إليها بالاسم لا بالنصّ المكرّر. - بعد أيِّ نصٍّ منطوقٍ جديد:
npm run gen:audio(+gen:piperإن لزم).
-
مزلق حرجٌ (فقدانُ تسجيلات): تصديرُ الاستوديو يَشمل تسجيلاتِ الجهاز فقط لا المُدمَجَ. فلو استبدلنا المجموعةَ بالتصدير ضاع المُدمَجُ القديمُ غيرُ الموجود على هذا الجهاز (حدَث: 632→337). الحلّ:
gen-voices.mjsيُراكِم الآن:mergedFiles = {...prev.files, ...new}(يُبقي كلَّ القديم ويُضيف/يُحدّث الجديد). الحذفُ المقصودُ يدويّاً لا عبر تصديرٍ جزئيّ. -
الاستردادُ عند الفقد: المقاطعُ القديمةُ محفوظةٌ في git (إن لم تُرفَع) ⇒
git show HEAD:.../voices-bundled.jsonلمدخلاتها +git checkout HEAD -- <مساراتها>لملفّاتها، ثمّ دمجُها (اتّحاد). -
مزلق الترميز:
unzipالعاديّ يُفسِد اسمَ المجلّد العربيّ ⇒ استخرجْ بـpython مع استعادةِ UTF-8:name.encode('cp437').decode('utf-8').
- العَرَض: بعد تشكيل أسماء الأعداد في المصدر، لم تَعُدْ تسجيلاتُ «صالح» (المُسجَّلةُ على المفاتيح القديمة) تُنطَق ⇒ ارتدادٌ للآليّ.
-
القاعدة: الصوتُ البشريُّ يُطابِقُ المفتاحَ حرفيّاً (مشكولاً أو لا)؛ فالمفتاحُ (نصُّ المحتوى) ثابتٌ كما يُعرَض/يُسجَّل. التشكيلُ للنطق الآليّ/العصبيّ يكون في التركيب فقط عبر
spoken-ref.js(مفتاح→مرجعٌ مشكول) وforSynthesis— دون مساسِ المفتاح. (الأعدادُ مثالٌ: المفتاحُ «أربعة»، المرجعُ «أَرْبَعَة».)
- نصوصُ القصص/الدروس المعروضةُ المنطوقةُ يجب أن تكون مشكولةً بالكامل (قصّةُ الأرقام
numerals.jsكانت مجرّدةً جزئيّاً فأُسيءَ نطقُها). شكِّلِ المصدرَ (يفيدُ العرضَ والنطقَ معًا)، أو وفِّرْ مرجعًا مشكولًا فيspoken-ref.js. انظر #ح‑7.
- ترجمةُ
content/stories.jsبنفسid/cover/artفيcontent/lang-en/fr.js— أيُّ قصّةٍ جديدةٍ تُضاف نظيرتُها بنفس المفاتيح.
-
السياق: بانرُ المناسبات (
src/occasions.js) يُحوّلُ الميلاديَّ إلى هجريٍّ بخوارزميّةِ التقويم الجدوليّ (الكويتيّة، عبرَ رقمِ اليومِ اليوليانيّ) — محلّيٌّ بالكامل بلا إنترنتٍ ولا مكتبة. - المزلق: التقويمُ الجدوليُّ قد يختلفُ يومًا أو يومَين عن الرؤيةِ الشرعيّة/أمِّ القرى. دُقِّقَ على رمضان ١٤٤٦ والعيدَينِ ورأسِ السنةِ فكان مطابقًا، لكنّ الفوارقَ الحدّيّةَ واردة.
-
القاعدة: يُستعمَلُ للتهنئةِ والتذكيرِ العامِّ لا للعبادة (لا يُبنى عليه بدءُ صيامٍ ولا عيد). ملاحظةٌ صريحةٌ في رأسِ الملفّ.
new Date()مسموحٌ في كودِ التطبيقِ (لا في سكربتاتِ Workflow).
-
المزلق: صفحاتٌ أخرى قد تملكُ مُنسِّقَ أرقامٍ، أمّا
home.htmlفلا دالّةَAR()فيه — استعمالُها يرميReferenceErrorيُجهِضُ سكربتَ الوحدةِ صامتًا. اطبعِ الأرقامَ مباشرةً (${n}) موافقةً لسياسةِ الأرقامِ الغربيّةِ في الواجهة.
راجعْ
../CLAUDE.mdللنظرة المعماريّة المكثّفة، وPLAN.mdلسجلّ العمل المؤرَّخ.
📖 هذا الويكي مرآةٌ مولَّدةٌ من GT-MK/docs/ في المستودع — حرّرِ الملفَّ هناك ثمّ شغّلْ bash tools/publish-wiki.sh.
رخصةُ المشروعِ والوثائق: GPL-3.0.
البداية
المرجعُ التقنيّ
الخبرةُ المتراكمة