تُرجمت هذه الصفحة آليًا وقد تحتوي على أخطاء. اقرأ الأصل الإنجليزي

التوثيق

مقدمة

Native Shell (واختصارًا NSH) هو إطار عمل لواجهات المستخدم الأصلية (native) يعمل على كل المنصات. تبني تطبيقك مرة واحدة بنموذج تطبيق واحد، يتألف من مكوّنات (components) وإشارات (signals) وأنماط وواجهة API نفسها في كل مكان، ثم يشغّله NSH على iOS وAndroid وmacOS وWindows وLinux والويب، وفي كل مرة بوصفه تطبيقًا من تلك المنصة: مبنيًا من عروضها (views) الخاصة، ومحرَّرًا بنظام النص الخاص بها، ويُستخدم كما يتوقع مستخدموها. يمكنك كتابة التطبيق كاملًا بلغة Rust، أو كتابة الشاشات بـ TSX، أو الجمع بينهما على محرك واحد. وفوق المحرك نفسه، يمكن للتطبيقات التي أصدرتها أن تستقبل واجهات جديدة أثناء التشغيل، كتبها فريقك أو ولّدها نموذج ذكاء اصطناعي، على هيئة وحدات موقَّعة تعمل داخل بيئة معزولة (sandbox) يضبطها المضيف.

#ماذا يعني «أصلي»

للأصالة في NSH طبقتان: أساسات المنصة، وتفاعل المنصة.

أساسات المنصة. لكل منصة واجهة خلفية (backend) خاصة بها، مبنية على واجهة المستخدم التي تملكها المنصة أصلًا:

المنصةالعروضالنصإدخال النص
iOSUIKitCoreTextحقول النص وعروض النص الخاصة بالنظام
AndroidAndroid viewsStaticLayoutEditText
macOSAppKit، Core AnimationCoreTextNSTextField، NSTextView
Windowsنوافذ Win32 مرسومة بـ Direct2DDirectWriteمحرر NSH، مع طرق الإدخال الخاصة بالنظام (IMM32)
LinuxGTK 4PangoText وTextView من GTK
Webعناصر HTML منسّقة بـ CSSخطوط المتصفحمدخلات المتصفح

يأتي من المنصة تحديد النص وقائمته، وطرق الإدخال، وإمكانية الوصول، والتمرير، وما يضيفه أي إصدار جديد من نظام التشغيل إلى هذه الأمور يصل إلى التطبيق معه. أما المكوّنات الأصلية (Native components)، مثل الخريطة وعرض الويب ومشغّل الفيديو وأي عرض يسجّله المضيف، فهي عرض إضافي في التسلسل الهرمي نفسه، يُوزَّع ويُقتطع ويُمرَّر مع بقية العروض (المكوّنات الأصلية).

تفاعل المنصة. المكوّنات التي يوفّرها NSH في nsh/base تتصرف كما يتوقع مستخدمو كل جهاز، ولذلك يكتبها التطبيق مرة واحدة:

أجهزة سطح المكتب (ومتصفحاتها)الهواتف والأجهزة اللوحية
Menuنافذة منبثقة عند العنصر المُشغِّل؛ وتتتالى القوائم الفرعية بجوار عنصرهاورقة (sheet) من الأسفل؛ وتحل القائمة الفرعية محل عناصر قائمتها
ContextMenuالنقر بزر الفأرة الأيمن يفتحها عند المؤشرالضغط المطوّل بالإصبع على العنصر المُشغِّل يفتحها كورقة
Tooltipتظهر عندما يستقر المؤشر على العنصر المُشغِّلتظهر عندما يفتحها التطبيق
Popoverالضغط خارجها يغلقها ويصل إلى ما تحت المؤشرالضغط خارجها يغلقها

وتتبع الصفحات والحوارات ولوحة المفاتيح وشريط الحالة الفكرة نفسها: تدفع مكدّسات التنقل الصفحات وتسحبها بحركة كل منصة، ويؤدي زر الرجوع ومفتاح Escape والسحب للرجوع ما تؤديه في تلك المنصة (التنقل).

#من الهاتف إلى سطح المكتب إلى الويب

تعني تعددية المنصات في NSH أن كل جهاز يحصل على التطبيق الذي يتوقعه، لا على الشاشة نفسها مكبَّرة أو مصغَّرة:

  • نموذج تطبيق واحد. مكوّنات التطبيق وحالته ومنطقه وشاشاته مشتركة؛ وكل واجهة خلفية تربطها بمنصتها.
  • تخطيطات تعيد تشكيل نفسها. يوزّع Taffy تخطيطات Flexbox وCSS grid على كل المنصات، وتغيّر نقاط التوقف في Tailwind (md:، lg:) وحجم النافذة (window() في Rust) البنيةَ لا الحجم فقط: يعرض تطبيق محادثة قائمته الجانبية والمحادثات والرسائل جنبًا إلى جنب في نافذة عريضة، وشاشة واحدة في كل مرة على الهاتف (التخطيط، Tailwind).
  • الإدخال الذي يتوقعه كل جهاز. مؤشر وتمرير فوق العناصر (hover) والنقر الأيمن واختصارات لوحة المفاتيح والتركيز على سطح المكتب؛ ولمسات وإيماءات ولوحة مفاتيح برمجية وتنقل بين الصفحات على الهواتف. وحيث يريد التطبيق شيئًا خاصًا به لكل منصة، تتيح ذلك الخصائص desktop={{…}} وplatform.select (الشيفرة الخاصة بكل منصة).
  • مُختبَر على كل هدف. يشغّل nsh test اختبارات التطبيق دون واجهة (headless)، ويشغّل الاختبارات نفسها في نوافذ macOS وWindows وLinux وGPUI، وفي محاكي iOS أو جهاز Android أو متصفح، بإدخال المنصة الأصلي نفسه (الاختبار والتتبع).

لنأخذ تطبيقًا مثل عميل دردشة. يبنيه المطوّر على سطح المكتب باستخدام nsh run، ثم يفتحه في محاكي iOS باستخدام nsh run --target ios للتحقق من تخطيط الهاتف، ثم في متصفح باستخدام nsh run --target web، وفي كل مرة هو المشروع نفسه، ثم يصنع nsh package التطبيقات القابلة للتثبيت والموقع الإلكتروني. بذلك يستطيع مهندس واحد إنجاز ما كان يتطلب فريقًا لكل منصة، بينما تُبنى الواجهة الخلفية (backend) بالتوازي.

#Rust أولًا

بُني NSH بلغة Rust، وهي لغة من الدرجة الأولى لكتابة التطبيقات به:

  • تطبيقات كاملة بلغة Rust. الواجهة والمنطق والمكوّنات كلها بلغة Rust، تُترجم إلى شيفرة أصلية على كل منصة، مع ما توفره Rust من ملكية (ownership) وأنواع وخيوط ومنظومة crates.io. تعمل الحسابات بسرعة الشيفرة الأصلية، وتبقى البيانات الضخمة والتحليل وأعمال النظام في اللغة الأنسب لها (البناء باستخدام Rust).
  • TSX مسارًا ثانيًا. يناسب TSX الشاشات التي تُكتب بسرعة، والإضافات (plugins)، والتطبيقات الصغيرة، والواجهات التي تُسلَّم أثناء التشغيل. ويعمل على المحرك نفسه الذي تعمل عليه Rust، بالعناصر الأساسية والمكوّنات الأساسية والإشارات نفسها.
  • الاثنان معًا. يمكن لتطبيق Rust أن يستقبل شاشات TSX، وتستورد شاشات TSX مكوّنات التطبيق ووحداته وفئاته المكتوبة بلغة Rust.
  • سلسلة أدوات واحدة. ملف تنفيذي واحد هو nsh ينشئ التطبيقات ويبنيها ويفحص أنواعها ويشغّلها مع إعادة التحميل الفوري (hot reload) ويختبرها ويحزمها ويوقّعها لكل هدف. ويأتي بمُجمِّع TSX وفاحص TypeScript خاصين به، فيكفي المطوّر القادم من Rust أو Swift أو Kotlin أو C++ أن يثبّت Rust وnsh ثم يبدأ العمل (التثبيت، سطر الأوامر).

#تطبيق

العدّاد نفسه، بلغة Rust وبـ TSX:

use nsh_core::prelude::*;

#[component]
pub fn Counter() -> impl IntoElement {
    let count = signal(0);
    view()
        .flex_1()
        .items_center()
        .justify_center()
        .gap_3()
        .child(text(move || count.get().to_string()).text_5xl().font_weight(FontWeight::BOLD))
        .child(
            view()
                .rounded_lg()
                .bg(rgb(0x2563eb))
                .px_5()
                .py_3()
                .active(|s| s.opacity(0.6))
                .on_press(move || count.update(|n| *n += 1))
                .child(text("Tap").text_lg().text_color(white())),
        )
}

يتحول العرض (view()، <View>) إلى UIView على iOS، وViewGroup على Android، وNSView على macOS، وعنصر GTK على Linux، وعرض محتفَظ به (retained view) مرسوم بـ Direct2D على Windows، وعنصر HTML على الويب؛ ويتحول النص إلى عرض نص أصلي، وتعمل ملاحظات الضغط على الزر (.active(..)، activeStyle) على الخيط الرئيسي. وعند النقر، يغيّر count.update إشارة واحدة، ولا يُحدَّث إلا النص الذي يقرؤها: لا تُنفَّذ دالة المكوّن مرة أخرى. تطبيق Rust أصلي من البداية إلى النهاية؛ أما في تطبيق TSX فشيفرة الوحدة نفسها فقط هي JavaScript.

#العروض المحتفَظ بها

NSH محتفِظ (retained): كل عنصر هو عرض تحتفظ به المنصة، ولا يغيّر التحديث إلا ما تغيّر.

  • تحديثات دقيقة. إشارات مثل إشارات Solid، منفَّذة أصليًا: تعمل المكوّنات مرة واحدة، ويعيد أي تغيير تشغيلَ التعبيرات التي تقرأ الإشارة المتغيرة فقط.
  • ما تغيّر وحده يعبر بين الخيوط. تُوزَّع تغييرات الدورة الواحدة معًا وتُسلَّم إلى الخيط الرئيسي دفعةً واحدة من عمليات العروض في كل إطار؛ ولا تُرسَل الأنماط والإطارات التي لم تتغير، وتبقى العروض التي لم يمسّها شيء كما هي.
  • الرسوم المتحركة تعمل أصليًا. تُحسَب الرسوم المتحركة الزمنية والنابضية (spring) والمتباطئة (decay) والإيماءات والقيم المرتبطة بالتمرير على ساعة الإطارات الخاصة بالمنصة، دون استدعاء JS في كل إطار (الرسوم المتحركة).

يناسب هذا الشاشات التي تتكوّن منها أغلب التطبيقات، مثل المحادثات والبريد والنماذج والقوائم والمستندات والإعدادات ورد ذكاء اصطناعي يتدفق تباعًا، حيث يتغير عدد قليل من العروض في كل مرة وتنتظر البقية. تكاد هذه الشاشات تكون خاملة بين التحديثات، ويحافظ التمرير والرسوم المتحركة على 120 FPS على شاشات ProMotion. في اختبار أداء NSH، يستهلك جدول أسعار من 500 صف يُحدَّث 1,000 مرة في الثانية نسبة 18% من نواة واحدة على Linux في NSH Rust، مقابل 20% للشاشة نفسها مكتوبةً مباشرة على GPUI Fast. والحسابات الثقيلة مكانها Rust: يعمل TSX في QuickJS، وهو مفسِّر (interpreter)، بينما تعمل وحدة Rust بسرعة الشيفرة الأصلية حتى حين يستدعيها TSX، فيستغرق ترتيب 100,000 صف نحو 3 ms بهذه الطريقة، كما في Rust الصرفة (الأداء).

#كيف يعمل

سلسلة الأدوات ملف تنفيذي واحد هو nsh. يُكتب التطبيق بلغة Rust أو بـ TSX أو بهما معًا، ويسلك كل منهما مسارًا خاصًا به نحو محرك Rust نفسه.

#تطبيقات Rust

تطبيق Rust هو crate يُبنى بالكامل بـ Rust وNative، دون بيئة تشغيل JavaScript:

  1. البناء. يبني nsh المشغِّل (runner) الخاص بكل هدف، أي تطبيق iOS أو Android أو macOS أو Windows أو Linux أو الويب، مع ربط crate الخاص بك به. تُترجم المكوّنات والحالة والمعالجات والمحرك معًا إلى شيفرة أصلية.
  2. التشغيل. يعمل المكوّن الجذري للتطبيق على خيط المحرك بوصفه Rust. ولا يحتوي المشغِّل على محرك JavaScript: فـ QuickJS ليس جزءًا من تطبيق Rust على أي منصة.

#تطبيقات TSX

تطبيق TSX (وحدة) هو مجموعة ملفات .tsx / .ts بنقطة دخول، وهي عادةً App.tsx:

  1. البناء. يجمّع nsh ملفات الوحدة (باستخدام rolldown وOxc) ويترجم JSX بمُجمِّعه الخاص. تبقى عمليات الاستيراد من nsh وnsh/* استيرادات: تقدّمها بيئة التشغيل أصليًا، فلا تحتوي الحزمة إلا على شيفرتك. ثم تُترجم الحزمة إلى bytecode خاص بـ QuickJS.
  2. التشغيل. ينفّذ خيط المحرك الـ bytecode في QuickJS، بآلة افتراضية (VM) واحدة لكل عرض. يستدعي JSX المترجَم المُصيِّر (renderer) الأصلي مباشرة، ولا تفعل JavaScript أكثر من وصف الواجهة ومعالجة الأحداث؛ أما تحليل الأنماط وحل فئات Tailwind والمقارنة (diffing) والتخطيط والنص فتجري كلها في Rust.

#الفرق

تطبيق Rustتطبيق TSX
يُكتب بـRustTSX (TypeScript وJSX)
يُبنى إلىشيفرة أصلية مرتبطة بمشغِّل كل هدفQuickJS bytecode تحمّله بيئة التشغيل
محرك JavaScriptلا يوجدQuickJS، آلة افتراضية واحدة لكل عرض
أثناء التطوير (nsh run)يُعاد بناؤه وتشغيله عند كل حفظتحديثات فورية (hot updates) عند كل حفظ
بعد الإصداريُشحن مع التطبيقيُسلَّم أيضًا أثناء التشغيل على هيئة حزم موقَّعة، داخل بيئة معزولة
يناسبالتطبيقات الكاملة والحسابات الثقيلة وأعمال النظامالشاشات التي تُكتب بسرعة والإضافات والواجهات المولَّدة بالذكاء الاصطناعي

يمكن الجمع بين الاثنين: يمكن لتطبيق Rust أن يستقبل شاشات TSX، وتستورد شاشات TSX مكوّناته ووحداته وفئاته المكتوبة بـ Rust؛ ويربط مثل هذا التطبيق QuickJS لتشغيلها (البناء باستخدام Rust).

#المحرك المشترك

من المحرك فصاعدًا يتطابق المساران. تُجمع التغييرات في كل دورة، وتُوزَّع باستخدام Taffy (Flexbox وCSS grid)، وتُسلَّم إلى الخيط الرئيسي دفعةً واحدة من عمليات العروض في كل إطار؛ ويطبّق الخيط الرئيسي الدفعة على العروض الأصلية. يُقاس النص ويُوزَّع مرة واحدة على خيط المحرك (CoreText على منصات Apple، وStaticLayout على Android، وDirectWrite على Windows، وPango على Linux، ومقاييس خطوط المتصفح على الويب) ولا يُرسم إلا على الخيط الرئيسي.

على الويب يعمل المحرك نفسه داخل الصفحة: يعرض المتصفح عناصر HTML منسّقة بـ CSS، ويمرّرها، ويحدّد نصها، ويعالج الإدخال، وينشر nsh package --target web التطبيق موقعًا ثابتًا تُسمّى ملفاته بحسب محتواها، جاهزًا لأي CDN (الويب).

#واجهة المستخدم الديناميكية والواجهات المولّدة بالذكاء الاصطناعي

بدأ NSH كوسيلة تتيح للتطبيقات الأصلية (native) استقبال واجهات مستخدم بعد إصدارها، وما زال هذا من أبرز استخداماته. يضمّن التطبيق NshAppView (iOS) أو NshView (Android) أو AppView (macOS وWindows وLinux وتطبيقات GPUI) أو createPlayer (صفحة ويب) في المكان الذي يجب أن تظهر فيه الوحدة، ويشغّل NSH وحدات TSX داخله:

  • تُسلَّم بعد الإصدار. الوحدة حزمة موقّعة (.nshpkg) ينزّلها التطبيق وقت التشغيل. يتحقق التطبيق من توقيعها مقابل المفاتيح التي يثق بها ومقابل قائمة الإبطال قبل تشغيلها (الحزم الموقّعة). يشحن التطبيق المضيف بيئة التشغيل، وتبني الفرق وحداتها وفق واجهته البرمجية (API) وتسلّمها وفق جدولها الخاص.
  • يبقى المضيف متحكّماً. تعمل كل وحدة في آلة QuickJS VM خاصة بها، مع حدود للذاكرة ووقت الاستدعاء وعدد العُقد، ولا تصل إلا إلى ما يمنحه المضيف: مضيفات الشبكة وصفحات الويب وشريط الحالة (العزل والصلاحيات). ويمكن تشغيل عدة وحدات جنباً إلى جنب.
  • تبدو كبقية التطبيق. تُعرض الوحدات بواجهة المستخدم الأصلية للمنصة، ويمكن للمضيف تسجيل عروضه الأصلية الخاصة، مثل خريطة أو مشغّل أو مكوّن من نظام التصميم، لتستخدمها الوحدات (المكوّنات الأصلية).

وتتيح الآلية نفسها للتطبيق أن يجيب بواجهة. يكتب النموذج وحدة TSX صغيرة (نموذج إدخال أو مخطط أو بطاقة حجز)، ويتحقق خادمك منها ويبنيها ويوقّعها، ثم يعرضها التطبيق بشكل أصلي:

From a prompt to native UI: a model writes a TSX module; your server builds, checks and signs it; across the trust boundary, the app verifies the package, runs it in a sandbox with the limits and capabilities it set, and shows it with the platform's own UI. From a prompt to native UI: a model writes a TSX module; your server builds, checks and signs it; across the trust boundary, the app verifies the package, runs it in a sandbox with the limits and capabilities it set, and shows it with the platform's own UI.

على iOS، لا يتعدى دور المضيف بضعة أسطر:

let app = NshAppView()                         // a UIView: place it anywhere
try app.grantCapabilities(#"{ "network": { "hosts": ["api.example.com"] } }"#)
let info = try await app.loadPackage(at: downloadedFile, trust: trust)   // verified, then run

#ملائم للذكاء الاصطناعي

صُمّم NSH للبرمجيات التي تُكتب بمساعدة الذكاء الاصطناعي، سواء كانت تطبيقات كاملة أو شاشات مولَّدة:

  • نموذج تطبيق واحد لكل المنصات. يتعلّم النموذج طريقة واحدة لكتابة الواجهة، وتعمل النتيجة على الهواتف وأجهزة سطح المكتب والويب؛ ويكيّف NSH التفاعل لكل منصة، فلا حاجة إلى توليد الشيء نفسه ست مرات.
  • Rust أو TSX بحسب المهمة. Rust للتطبيقات الكاملة والوحدات الحسّاسة للأداء، وTSX للشاشات والإضافات والواجهات المولّدة عند الطلب، وكلاهما على محرّك واحد.
  • عقد دقيق ومولَّد. لكل عنصر وخاصية ومفتاح نمط ووحدة تصريحات TypeScript مولَّدة من مصادر Rust (.nsh/types)، وواجهات Rust البرمجية مكتوبة الأنواع، فيفحص المترجم الشيفرة المولَّدة. وتُقدَّم هذه الوثائق كنص عادي للنماذج (llms.txt، llms-full.txt).
  • سطر أوامر واحد للدورة كاملة. ينشئ الوكيل التطبيق ويبنيه ويشغّله على هدف ويجري اختباراته ويقرأ النتيجة ويصلح الأخطاء ويحزّمه، كل ذلك عبر nsh. ويعمل nsh test دون واجهة رسومية ودون جهاز، ويُبلغ عن الأخطاء والعروض الناتجة؛ كما يعمل المترجم داخل العملية نفسها (nsh_bundler::compile_module) وفي المتصفح (Playground).
  • آمن عند التشغيل حتى مع شيفرة غير موثوقة. الشيفرة المولَّدة التي تُسلَّم وقت التشغيل شيفرة غير موثوقة: تصل موقّعة، وتعمل في آلة افتراضية ذات حدود، ولا تصل إلا إلى الصلاحيات التي يمنحها المضيف.

#أين يعمل

المنصةالمتطلباتالواجهة الخلفيةالتضمين عبر
iOSiOS 15.0+UIKitNativeShell.xcframework
AndroidAndroid 9+Android viewsnsh.aar
macOSmacOS 14+AppKitAppView
WindowsWindows 10 1809+Win32, Direct2DAppView
LinuxGTK 4.14+GTK 4AppView
Webمتصفحات حديثةHTMLcreatePlayer، iframe
GPUIأجهزة سطح المكتبGPUIAppView (تطبيقات GPUI وGPUI Kit)
Mac CatalystmacOSUIKitللتطوير (--target catalyst)
One module, many hosts: the same bundle and Rust runtime on iOS, Android, Linux, macOS with AppKit and Windows with Win32 / Direct2D, GPUI on all three desktops, and web pages drawn with HTML. One module, many hosts: the same bundle and Rust runtime on iOS, Android, Linux, macOS with AppKit and Windows with Win32 / Direct2D, GPUI on all three desktops, and web pages drawn with HTML.

#Native Shell وGPUI Shell

تبني Longbridge النظام NSH لعملائها الخاصة. كانت الحاجة الأولى طبقة إضافات لتطبيق الهاتف، وكان GPUI Shell، الذي يجعل تطبيقات GPUI المكتبية قابلة للتوسعة بلغة JavaScript، نقطة الانطلاق. وعلى الهواتف، كانت واجهة ترسمها GPUI بنفسها ستضطر إلى إعادة بناء ما توفره المنصة أصلاً، مثل تحديد النص وقائمته (Copy وShare وTranslate وLook Up) ومحررات الإدخال وإمكانية الوصول والمواد البصرية التي يضيفها كل إصدار من iOS، ولكان على كل فريق جوّال أن يتبنّى حزمة واجهات جديدة. لذلك يعرض NSH بواجهة المستخدم الأصلية لكل منصة، وقد تطوّر من طبقة الإضافات تلك إلى إطار عمل لتطبيقات كاملة على كل منصة.

يختلف موقع كل منهما: يوسّع GPUI Shell تطبيقات GPUI المكتبية، أما Native Shell فهو إطار عمل للتطبيقات الأصلية على الهواتف وأجهزة سطح المكتب والويب، وتضمّن واجهته الخلفية GPUI تطبيقات NSH داخل تطبيقات GPUI وGPUI Kit (GPUI).

#كيف يقارن بغيره

بالنسبة للتطبيقات الكاملة، يقع NSH بين أطر العمل متعددة المنصات مثل React Native وFlutter وQt وGPUI وSlint وElectron وTauri؛ وبالنسبة للواجهات التي تُسلَّم بعد الإصدار، يقع بين عروض الويب (web views) والواجهات التي يقودها الخادم (server-driven UI) وحاويات البرامج المصغّرة (mini-programs). خياره هو واجهة كل منصة وتفاعلها الأصليان تحت نموذج تطبيق واحد، مع Rust لغةً للتطبيق وTSX بجانبها. يستعرض مقارنة المقاربات كل واحدة منها.

#الخطوات التالية