SSOT Template Markup Language — إعلان واجهة مستخدم مستقل عن الإطار.

كود الواجهة الأمامية يخلط شيئين: قراراتك وتوصيلات الإطار. أي API تستدعي، أي حقول تعرض، بأي ترتيب، تحت أي شروط — هذه القرارات تُدفن في React hooks أو Vue composables. عند تبديل الإطار أو إعادة الهيكلة، تُعاد تفسير القرارات أو تُفقد.

STML يفصل بينهما. القرارات تبقى في HTML قياسي مع سمات data-*. توصيلات الإطار تُولَّد أو تُفوَّض.

مستودع GitHub

البنية

┌─────────────────────────────────┐
│  STML (specs/)                  │  قراراتك. مستقل عن الإطار.
│  ما الذي تجلبه وتعرضه وترسله   │  ينجو من أي إعادة كتابة.
├─────────────────────────────────┤
│  توليد الكود / LLM              │  يولّد توصيلات الإطار.
│  React, Vue, Svelte, أي إطار   │  قابل للاستبدال.
├─────────────────────────────────┤
│  وقت التشغيل (artifacts/)       │  React TSX, Vue SFC, إلخ.
│  hooks، حالة، عرض              │  مُولَّد. لا تحرره.
└─────────────────────────────────┘

ما يتم الحفاظ عليه

كل شيء في HTML الخاص بـ STML هو قرار المستخدم:

  • أي نقاط نهاية API تستدعيها (data-fetch, data-action)
  • أي حقول تعرضها أو تجمعها (data-bind, data-field)
  • بأي ترتيب تظهر العناصر (بنية DOM)
  • كيف تبدو (فئات Tailwind، وسوم HTML)
  • أي نص يراه المستخدمون (عناوين، placeholders، تسميات الأزرار)
  • أي شروط تتحكم في الرؤية (data-state)
  • أي مكونات تعالج UX خاص (data-component)
  • سلوك القوائم — ترقيم الصفحات، الترتيب، التصفية

ليس React. ليس Vue. إنه ما تفعله صفحتك، مُعلَن في HTML5 قياسي.

مثال

<main class="max-w-4xl mx-auto p-6">
  <h1 class="text-2xl font-bold mb-6">حجوزاتي</h1>

  <section data-fetch="ListMyReservations"
           data-paginate data-sort="StartAt:desc" data-filter="Status">
    <ul data-each="reservations" class="space-y-3">
      <li class="flex justify-between p-4 border rounded">
        <span data-bind="RoomID" class="font-semibold"></span>
        <span data-bind="Status" class="text-sm text-gray-500"></span>
      </li>
    </ul>
    <p data-state="reservations.empty" class="text-gray-400">لا توجد حجوزات</p>
  </section>

  <div data-action="CreateReservation">
    <input data-field="RoomID" type="number" placeholder="رقم الغرفة" />
    <button type="submit">حجز</button>
  </div>
</main>

نفس الملف يمكن أن ينتج أهدافاً مختلفة:

الهدفالطريقة
React TSXstml gen (توليد كود حتمي مدمج)
Vue SFCأعطِ STML + OpenAPI لـ LLM: “نفّذ بـ Vue”
Svelteأعطِ STML + OpenAPI لـ LLM: “نفّذ بـ Svelte”
Flutterأعطِ STML + OpenAPI لـ LLM: “نفّذ بـ Flutter”

القرارات تُحفظ. فقط توصيلات الإطار تتغير.

التحقق

المتحقق المدمج يفحص STML مقابل OpenAPI قبل توليد أي كود:

  • هل operationId موجود؟ هل طريقة HTTP صحيحة؟
  • هل تتطابق حقول الطلب والاستجابة والمعاملات مع المخطط؟
  • هل أعمدة الترتيب/التصفية/التضمين ضمن القوائم المسموحة؟
  • هل المكونات المُشار إليها موجودة؟

يكتشف عدم تطابق الواجهة-API في وقت CI، ليس في وقت التشغيل.

stml validate specs/my-project    # 12 فحصاً رمزياً مقابل OpenAPI

سمات data-*

السمةما تُعلنه
data-fetchتحميل بيانات من نقطة نهاية GET
data-actionإرسال بيانات إلى نقطة نهاية POST/PUT/DELETE
data-fieldجمع حقل من جسم الطلب
data-bindعرض حقل من الاستجابة
data-param-*العملية تحتاج معامل مسار/استعلام
data-eachالتكرار على حقل مصفوفة
data-stateالعرض المشروط
data-componentالتفويض لمكون مخصص
data-paginateقائمة مُرقَّمة الصفحات
data-sortقائمة قابلة للترتيب (العمود والاتجاه الافتراضي)
data-filterقائمة قابلة للتصفية (أي الأعمدة)
data-includeتضمين موارد مرتبطة

الترخيص

MIT — GitHub