---
name: curriculum-kit
description: >
  The shared component/behaviour kit that every curriculum site in this repo
  builds on — living at `kit/` in the repo root. It is the single source of
  truth for MECHANISM (markdown→structure parsing, prediction gates, progress
  state, code blocks and runners, lab injection and 1:1 verification, motion
  primitives, bidi helpers, Cloudflare Pages build) and it deliberately ships
  ZERO appearance: no palette, no fonts, no page structure, no defaults that
  would make two curricula look alike. Read this skill whenever building or
  extending a curriculum site, adding a reusable component, or deciding whether
  something belongs in the shared kit versus in one curriculum. Enforces the
  admission rule (nothing enters the kit until built twice in two different
  curricula), the style/function separation contract, and the anti-sameness
  guard.
license: MIT
---

# Curriculum Kit

العدّة المشتركة في `kit/` بجذر المستودع. تُقرأ مع `curriculum-site-design` —
تلك تحكم **ماذا نصمّم ولماذا**، وهذه تحكم **من أين يأتي الميكانيزم**.

---

## المشكلة التي وُجدت لأجلها، والمشكلة التي قد تصنعها

**المشكلة:** كل منهج جديد يعيد بناء نفس الميكانيزم من الصفر — تحليل الماركداون،
بوّابات التنبّؤ، حفظ التقدّم، حقن المختبرات، فحوص ثنائية اللغة، مجلد النشر. عملٌ
مكرّر، وأخطاء متكرّرة، وجودة متذبذبة.

**المشكلة المقابلة، وهي أخطر:** عدّة مشتركة تجرّ معها مظهراً مشتركاً، فتصير كل
المناهج بنفس الجوّ — وهذا يناقض قانون الاشتقاق مناقضةً مباشرة.

**الحلّ هو خطّ فصل واحد يُحرَس بلا تهاون:**

> **العدّة تملك الميكانيزم. المنهج يملك المعنى والمظهر والبنية.**

---

## عقد الفصل (ثلاث قواعد تقنية صارمة)

**١. العدّة لا تصدّر لوناً ولا خطّاً ولا مقاساً — أبداً.**
تصدّر أسماء متغيّرات CSS **تستهلكها**، والمنهج يعرّف قيمها. أي قيمة لونية حرفية
داخل `kit/` عيبٌ يُصلَح فوراً.

```css
/* داخل العدّة: مسموح — بنية وسلوك فقط */
.ck-gate { border: 1px solid var(--ck-line); border-radius: var(--ck-r, 4px); }
/* ممنوع منعاً باتاً */
.ck-gate { border-color: #223140; }
```

**٢. العدّة لا تفرض بنية صفحة.** لا تخطيط، لا شريط جانبي، لا ترتيب أقسام. تعطي
**بدائل** (primitives) يركّبها المنهج كما يقتضي قالبه هو.

**٣. كل ما هو خاصّ بموضوع يخرج كإعداد (config)، لا يُشفَّر داخل العدّة.**
جدول الأدوار، جدول العلامات، عائلات اللون، سجلّ المختبرات — كلها تُمرَّر من
المنهج.

```js
// المنهج يمرّر، والعدّة تنفّذ
buildStructure(raw, {
  roles:   [{ role:'brief', match:/النبذة/, label:'النبذة' }, /* … */],
  markers: [{ id:'blast', match:/^>\s*🧨/, kind:'quote' }, /* … */],
});
```

---

## قاعدة القبول (الحارس ضدّ التعميم المبكر)

> **لا يدخل شيء العدّة حتى يُبنى مرّتين في منهجين مختلفين.**

أول مرة: اكتبه داخل المنهج. ثاني مرة: إن كان **نفس الميكانيزم بمظهر مختلف**،
انقله للعدّة واترك المظهر في المنهجين. إن كان مختلف الميكانيزم أيضاً، فهما شيئان
لا شيء واحد — **لا تُوحّدهما**.

وقاعدة عكسية: إن استُعمل مكوّن في العدّة **بنفس المظهر** في منهجين، فذلك إنذار
بأن أحدهما لم يشتقّ تصميمه. راجِع جدول اشتقاقه.

---

## ماذا يدخل وماذا لا يدخل

| يدخل العدّة | يبقى في المنهج |
|---|---|
| تحليل الماركداون إلى بنية (بإعداد مُمرَّر) | جدول الأدوار والعلامات نفسه |
| آلة بوّابة التنبّؤ (قفل/تثبيت/كشف/حفظ) | نصّها، وشكلها، ومتى تُستعمل |
| مخزن التقدّم (ثلاثة أشياء فقط) | ما يُعرَض منه وكيف |
| تصيير الكود بـShiki على متغيّرات CSS | لوحة التوكنة، ووسوم البلوكات |
| مهايئات التشغيل (تشغيل كود، عرض مخرَج، أخطاء ومهلة) | أي لغة، وأي محرّك، وأي بلوك يستحقّ التشغيل |
| هيكل المختبرات: سجلّ + حقن + فحص ١:١ | المختبر نفسه ومحاكاته وحالته المفاجئة |
| بدائل الحركة المحترمة لـ`prefers-reduced-motion` | أيّها تُستعمل ولماذا (بصفّ في الجدول) |
| مساعدات ثنائية اللغة و`slugify` موحّد | — |
| هيكل الفحوص الآلية | الثوابت المفحوصة (كم إقليماً، كم علامة) |
| بناء مجلد نشر Cloudflare Pages | — |
| لوحة أوامر وفهرس بحث | ما يدخل الفهرس |
| **صفر ألوان · صفر خطوط · صفر تخطيط** | كل المظهر، وكل البنية، وكل المعنى |

---

## بناؤها تدريجيّ، لا مُسبَق

**لا تبنِ وحدة في العدّة قبل أن يطلبها منهج فعليّ.** الوحدة المُتخيَّلة مسبقاً
تُبنى على افتراضات، ثم يُلوى المنهج ليناسبها — وهذا انقلاب على قانون الاشتقاق.

الترتيب الطبيعي: منهج يحتاج شيئاً ← يُبنى داخله ← منهج ثانٍ يحتاج مثله ← يُنقل
للعدّة ← يُوثَّق هنا.

---

## الأساس التقني

### React + Vite + TypeScript، والمخرَج ملفّ `index.html` وأصول مُجزَّأة

بناء Vite العادي. **ليس SSG.** الناتج مجلد ساكن يُرفَع كما هو على أي استضافة
ثابتة، بلا خادم وبلا إعداد.

المواقع هنا **تطبيقات React غنيّة**، لا وثائق مزخرفة. المحرّرات تعمل، والمختبرات
تعمل، والحالة محفوظة. من يبني صفحة نصّ طويل بشريط جانبي فقد أخطأ المهمّة —
راجِع `curriculum-site-design` §٣.

**التوجيه:** `HashRouter` افتراضاً — الروابط العميقة تعمل على أي استضافة بلا
قواعد إعادة كتابة. على استضافة تدعم احتياط SPA (مثل `_redirects` في Cloudflare
Pages) يجوز التحويل إلى `BrowserRouter` لروابط أنظف.

**`base: './'` ومسارات نسبية** — ليعمل المخرَج في الجذر وفي مجلد فرعي سواء.

**بطاقة المشاركة:** واحدة لكل منهج في `index.html` (SPA لا يعطي وسوماً لكل مسار
بلا تصيير مسبق). إن احتاج منهج بطاقةً لكل إقليم، تُولَّد صفحات stub صغيرة وقت
البناء — لا تفعلها بلا حاجة.

### إعادة الاستخدام: npm workspaces

مساحة عمل واحدة في الجذر، و`kit` حزمة فيها. كل موقع يعلن `"@t3lm/kit": "*"`.
تثبيت واحد (المستودع يبقى خفيفاً)، وإصلاح في العدّة يصل الجميع فوراً، والعدّة
**تُشحَن كمصدر TypeScript** بلا خطوة بناء لها.

في `vite.config.ts` للموقع:

```ts
optimizeDeps: { exclude: ['@t3lm/kit'] }   // كي تُصرَّف مصادر العدّة لا تُحزَم مسبقاً
```

### ثلاث طبقات للمكوّنات — وهي الفصل بين الوظيفي والأسلوبي

| الطبقة | ماذا تملك | أين تعيش | إعادة الاستخدام |
|---|---|---|---|
| **١ — بدائل بلا رأي** | سلوك فقط: hooks وآلات حالة. **صفر DOM، صفر أصناف** | العدّة | حرّة دائماً |
| **٢ — مكوّنات دلالية** | أقلّ DOM ممكن + أصناف `ck-*`. **صفر قيم لون** | العدّة | تحتاج **صفّاً في جدول الاشتقاق** |
| **٣ — مكوّنات المنهج** | الشكل كاملاً | داخل المنهج | لا تُشارَك |

**القاعدة:** إن وجدت نفسك تنقل الطبقة ٣ إلى العدّة، فأنت تنقل **مظهراً** — توقّف.

### المحرّر — `@t3lm/kit/editor`

المكوّن الأثقل في العدّة، وهو الدليل العملي على أن عقد الفصل قابل للتطبيق حتى في
أعقد حالة: **صفر لون في كوده**، والمظهر كلّه من متغيّرات `--ck-ed-*`.

```tsx
import { Runner, createGoRuntime } from '@t3lm/kit/editor';
import '@t3lm/kit/editor/vscode-dark.css';   // اختياري: مظهر VS Code حرفياً

<Runner initial={code} lang="go" filename="main.go"
        runtime={createGoRuntime(PROXY)}
        fallback={{ label: 'Playground ↗', href: 'https://go.dev/play/' }} />
```

ما فيه:

- **CodeMirror 6** بمحرّر حقيقي: أرقام أسطر، طيّ، أقواس متطابقة، تراجع، بحث،
  تحديد مستطيل، وإبراز تطابقات التحديد.
- **دعم التاب** (`indentWithTab`) مع `Escape` مخرجاً للوصولية. و`indentUnit`
  يتبع اللغة — تاب لـGo (gofmt)، مسافات لغيرها.
- **إظهار المسافات عند التحديد فقط**: نقطة `·` لكل مسافة، وسهم `→` للتاب — كما
  `renderWhitespace: "selection"` في VS Code. تُرسم في `::before` بموضع مطلق،
  فلا تُزيح النصّ ولا بكسل.
- **أنماط اللغات تُحمَّل كسولاً** — صفحة بلا محرّر لا تدفع ثمن أي منها.
- **زرّ تشغيل** (و`⌘/Ctrl+Enter`)، ونسخ، واسترجاع للأصل، ولوحة مخرَج فيها
  stdout/stderr والحالة والزمن بالمللي ثانية.
- **حقل `fidelity`** يُعرض دائماً حين يخالف المحرّك بيئة المتعلّم. الصدق عن حدود
  المحرّك جزء من المكوّن لا إضافة عليه.

**سجلّ المحرّكات:** `javascriptRuntime` (تنفيذ حقيقي في Web Worker معزول بمهلة)
· `createGoRuntime(endpoint)` (مترجم `gc` الحقيقي عبر وسيط؛ بلا وسيط يُصرَّح بعدم
التوفّر ولا يُحاكى شيء) · `createWasiRuntime(...)` (لغات WASI عبر `@runno/wasi`
كاعتمادية **اختيارية** يثبّتها من يحتاجها؛ وتشغيلها يتطلّب عزل الأصول COOP/COEP —
قرار استضافة يُتخذ قبل البناء).

**حمّل المحرّر كسولاً** (`lazy`) — حزمته الأساسية بمئات الكيلوبايتات، ولا يجوز أن
تدفعها صفحة بلا محرّر.

### عقد المكوّن العالمي (يسري على كل ما تضعه في العدّة)

المكوّن المشترك **لا يفرض قراراً يخصّ المنهج**. أربعة محاور، وكلها للمستورِد:

**١. اللون.** بلا قيمة واحدة. كل شيء `var(--ck-*)` مشتقٌّ من هوية المنهج.

**٢. الشكل.** لا نصف قطر حرفيّ ولا سماكة حدّ حرفية — منهج حادّ الحواف وآخر
ناعمها وثالث بلا حدود. تُمرَّر توكنز: `--ck-r` → `--ck-ed-r`/`--ck-ed-r-sm`،
و`--ck-border-w` → `--ck-ed-border`.

**٣. النصّ.** كل كلمة يراها المستخدم قابلة للتجاوز عبر `labels` مع افتراضات
معقولة. كلمة مثبَّتة في العدّة تفرض صوتاً واحداً على كل المناهج.

```tsx
<Runner labels={{ run: 'شغّل', status: { timeout: 'انتهى الوقت' } }} />
```

**٤. المنفذ.** `className` يمرّ دائماً إلى الجذر، والأصناف الداخلية (`ck-*`)
دلاليّة ومستقرّة — تُعامَل كواجهة عامّة يبني عليها المنهج.

**٥. الحالة مستقلّة لكل نسخة، ومحفوظة بهويّتها.** أي مكوّن يحمل حالة يكتبها
المستخدم (محرّر، بوّابة، إجابة) يأخذ **مفتاح هويّة مستقرّاً** ويحفظ تحته وحده.

هذا ليس تحسيناً بل تصحيح لخطأ صامت: React يعيد استعمال المكوّن حين يتطابق موضعه
في الشجرة، فبلا هويّة مستقرّة **ينتقل ما كتبه القارئ من صفحة إلى أخرى** — يفتح
الفصل التالي فيجد كوده السابق. لذلك:

- مفتاح مشتقّ من موضع المحتوى (إقليم:مقطع:ترتيب)، لا من فهرس المصفوفة.
- يُمرَّر إلى `key` في React ليُعاد البناء عند تغيّره.
- والحفظ في `localStorage` تحت نطاق المكوّن، والاسترجاع يتبع المفتاح لا الموضع.

**وقاعدة تنفيذ:** حين تحتاج سلوكاً بصرياً يوازي سلوكاً موجوداً في المكتبة
الأساسية، **استعمل بدائلها لا يدك**. مثال: إبراز نظائر التحديد رُسم بـ`layer` و
`RectangleMarker` — الآلية نفسها التي يرسم بها `drawSelection` — فجاءت الهندسة
مطابقة للتحديد بحكم البناء. الزخرفة اليدوية كانت تُلوّن صندوق المحرف لا صندوق
السطر، فتخرج أقصر ومربكة.

**٦. الخطّ الأحاديّ لسطح الكود، لا لواجهته.** المكوّن الذي يعرض كوداً أو مخرَجاً
يضع الأحاديّ على **سطح المحتوى وحده** (`--ck-ed-font`، `--ck-term-font`)، ويترك
إطاره — الأزرار والأسماء والملاحظات — على خطّ الصفحة (`--ck-*-ui-font`).

السبب: نصّ الإطار بشريّ وقد يكون عربياً، وخليّة المونو الثابتة تفكّ وصل حروفه.
والتمرير الكسول لـ`font-family` من الجذر يبتلع الإطار معه، فالفصل يُكتب صراحةً.
أمّا المخرَج فيبقى أحادياً وLTR حتى لو كان عربياً — لأنه **جاء من طرفية كذلك**،
وتجميله كذبٌ على شكل صدق.

**٧. العدّة لا تصنع أصولاً.** لا أيقونة مرسومة بيدك، ولا شعار، ولا رمز مصنوع
ليشبه رمزاً معروفاً. الأيقونات تُستورَد من **طقم حقيقي** (lucide وأمثاله) كحزمة
npm تُهزّ أشجارها، لا كطلب شبكة.

السبب: الرمز المصنوع بمحرف أو بمسار SVG مرتجَل لا يشبه أصله لا من قريب ولا من
بعيد، ويُقرأ كعطب. ولأن الأيقونة **مظهر**، فالمنهج هو من يمرّرها — لذلك قيم
`labels` من نوع `ReactNode` لا `string`. العدّة لا تختار رمزاً كما لا تختار
لوناً. والاستثناء الوحيد: رسمٌ **مشتقّ من المحتوى نفسه** (مخطّط بايتات، مصوّر
بنية) — فذاك ليس أيقونة بل شرح.

**٨. لا يُشغَّل شيء إلا بفعل القارئ، ولا يُعرَض مسجَّلٌ بهيئة حيّ.**

- لا تشغيل تلقائي عند الظهور، ولا عند كشف جواب. التشغيل فعلُه هو.
- المخرَج الذي سجّله المؤلّف **لا يُلبَس هيئة طرفية**: المحثّ `$` وشارة «تمّ»
  وزمن التنفيذ كلّها تقول «جرى هذا الآن»، وقولها عن نصٍّ منسوخ كذب. للمسجَّل
  هيئته: مصدرٌ معلوم، ونصٌّ محفوظ، ودعوةٌ لتشغيله بنفسه.
- وحيثما اختلف المحرّك داخل المتصفّح عن الأداة الحقيقية، **يُعلَن الاختلاف قبل
  التشغيل لا بعده** (`notice`). الإنذار بعد أن يبني القارئ اعتقاده لا ينفع.

**٩. المسودّة تُختَم ببصمة أصلها.** المفتاح المستقرّ (المحور ٥) يمنع تسرّب الحال
بين المحرّرات، لكنه لا يمنع تسرّبه **عبر الزمن**: يعدّل المؤلّف الماركداون أو
يُعاد ترقيم البلوكات، فتُحيي مسودّةٌ محفوظة كوداً لم يعد لهذا الموضع. لذلك:

- هويّة البلوك من **محتواه** (بصمة) لا من ترتيبه في المصفوفة.
- وكل مسودّة تُحفَظ مختومةً ببصمة نصّها الأصل، وتُهمَل عند اختلافها.
- ووسمٌ ظاهر (`مسودّتك`) كلما كان المعروض كتابةَ القارئ لا كود المؤلّف. الغموض
  هو العطب: من رأى كوداً لا يعرف من أين جاء، لا يثق بشيء بعده.

**١٠. المساعدة تُطلَب ولا تُدفَع.** أي مكوّن يحمل تلميحاً أو حلّاً أو كشفاً:

- **يُفتَح بفعل المستخدم وحده.** لا مؤقّت، ولا عدّ محاولات، ولا فتحٌ تلقائيّ.
- **ويتأخّر الكشف عشر ثوانٍ بعد الطلب** (قابلة للتجاوز من المنهج). التأخير يكبح
  النقر الانعكاسيّ بلا أن يراقب أحد: إدمان التلميح يضرّ النقل (d = −0.42)،
  والمساعدة المفروضة تتخلّف عن المطلوبة (0.12 مقابل 0.35).
- **ولا يُقفَل تقدّمٌ خلف تقييمٍ ذاتيّ.** يُعرَض نموذج الخبير، ولا يُطلَب من
  المستخدم أن يصحّح نفسه ليمضي.

**الفحص:** `node tools/doctor.mjs` يرفض أي لون حرفيّ أو نصف قطر حرفيّ في
`kit/src` وفي أي موقع مبنيّ على العدّة.

### الأنماط: CSS عادي بمتغيّرات

**لا Tailwind ولا مكتبة مكوّنات جاهزة.** كلاهما يفرض مفردات ومظهراً مشتركين عبر
المناهج — وهذا هو التشابه الذي نحاربه.

العدّة تشحن: `reset.css` (تصفير محايد) · `contract.css` (**إعلان أسماء
المتغيّرات بلا قيم**) · `editor.css` (بنية المحرّر) · `derive.css` (يشتقّ توكنز
المحرّر من هوية المنهج) · `presets/vscode-dark.css` (الاستثناء المعلَن).

**اللون الحرفيّ خارج ملفّ توكنز المنهج محرَّم**، ويفحصه `tools/doctor.mjs`
بالملفّ والسطر — في العدّة وفي كل موقع مبنيّ عليها.

### التثبيت كتطبيق (PWA)

- `manifest.webmanifest`: `start_url: "."`، `scope: "."`، `display: "standalone"`،
  وأيقونات `192` و`512` **و`512` maskable** (بدونها تُقصّ الأيقونة على أندرويد)،
  و`apple-touch-icon`.
- **عامل خدمة مكتوب بأيدينا** (~٥٠ سطراً) لا Workbox: cache-first للأصول مع
  تحديث في الخلفية، ورقم إصدار في اسم المخزن يُبطل القديم عند كل بناء.
- **زرّ تثبيت صريح** عند `beforeinstallprompt`، بلا إلحاح.

### الأيقونات وبطاقات المشاركة

تُولَّد وقت البناء: أيقونة SVG مصدرية واحدة ⇒ كل المقاسات؛ و`satori` ثم
`@resvg/resvg-js` لبطاقة `1200×630`. **العدّة تملك خطّ الإنتاج، والمنهج يملك
قالب البطاقة** — نفس الفصل مطبَّقاً على الصور.

### استثناء من قاعدة القبول

قاعدة «لا يدخل شيء حتى يُبنى مرّتين» تخصّ **المكوّنات**. طبقة البناء والنشر
(عامل الخدمة، خطّ إنتاج الصور، الفحوص) مشتركة من البداية: ميكانيزم صرف بلا مظهر.

---

## ملكيّة المحتوى (قاعدة حرجة)

الماركداون في `regions/` و`appendix/` داخل مجلد المنهج هو **المصدر الوحيد**. إن
احتاج البناء نسخةً داخل `site/`، فهي **مولَّدة عند كل بناء ولا تُحرَّر**. ضع في
رأسها تحذيراً، واجعل الفحص يرفض التعديل اليدوي.

---

## محرّمات

- لا لون ولا خطّ ولا مقاس في العدّة.
- لا بنية صفحة مفروضة.
- لا وحدة تُبنى قبل أن يطلبها منهج حقيقي.
- لا توحيد لشيئين يختلف ميكانيزمهما.
- لا نصّ محتوى داخل العدّة.
- لا اعتماد على شبكة وقت التصفّح.
- لا Tailwind ولا مكتبة مكوّنات جاهزة.
- لا صفحات نصّ ساكن مملّة — نحن نبني تطبيقات React.
- لا لون ولا نصف قطر ولا نصّ مثبَّت في مكوّن عالمي.
- لا إعادة اختراع لبديل موجود في المكتبة الأساسية.

---

## فحص ذاتي

1. هل في `kit/` أي قيمة لون أو اسم خطّ؟ ابحث عنها واحذفها.
2. هل تفرض العدّة تخطيطاً؟ حوّله إلى بدائل.
3. هل كل وحدة فيها بُنيت في منهجين فعليّين؟
4. هل مكوّن يظهر بنفس المظهر في منهجين؟ أحدهما لم يشتقّ — راجع جدوله.
5. هل كل ما هو خاصّ بموضوع مُمرَّر كإعداد؟
6. هل المخرَج يعمل بلا شبكة، وبلا أصل خارجي؟
