UI as Data — שליחת רכיבי ממשק כמבנה נתונים מאומת
מאת צוות מדיה דיל · 12.08.2026 · Generative UI · 5 דק׳
למה סוכן AI לא צריך לייצר HTML, ואיך גישת UI as Data — מבנה נתונים מאומת מול קוד חופשי — פותרת אבטחה, עקביות ובדיקות בבת אחת.
מפתח שמנסה "לפתור" ממשק דינמי בדרך המהירה ביותר לפעמים נופל בפיתוי הכי מסוכן: לבקש מהמודל לייצר HTML ישירות ולהזריק אותו לעמוד עם innerHTML או dangerouslySetInnerHTML. זה עובד בדמו, ונשבר בפרודקשן בכל דרך אפשרית — פרצת XSS ראשונה שמישהו ינסה, עיצוב לא עקבי בין קריאה לקריאה, ואין שום דרך לבדוק "האם הממשק הזה תקין" לפני שהוא מוצג למשתמש. UI as Data הוא העיקרון המנוגד: הסוכן לעולם לא מייצר קוד או markup, הוא מייצר מבנה נתונים מאומת — אובייקט JSON שמתאר *מה* להציג, לא *איך*. ה-Frontend הוא זה שאחראי בלעדית על תרגום הנתון לפיקסלים. ההבדל הזה נשמע פרטני, אבל הוא כל ההבדל בין מערכת שאפשר לסמוך עליה למערכת שמפחידה כל צוות אבטחה שרואה אותה.
למה HTML חופשי הוא הבחירה הלא נכונה
כשמודל שפה מייצר HTML או JSX ישירות, שלוש בעיות מצטברות מייד. הראשונה היא אבטחה: כל טקסט חופשי שמגיע ממודל (שיכול בעצמו להיות מושפע מקלט משתמש עוין, prompt injection, או תוכן חיצוני שהמודל קרא) עלול להכיל תגיות script, event handlers, או קישורים זדוניים. השנייה היא עקביות: אין שום ערובה שהמודל ישתמש באותם class names, אותם מרווחים, או אותה היררכיית כותרות פעמיים ברצף. השלישית, והכי חמורה בטווח הארוך, היא חוסר יכולת בדיקה — אי אפשר לכתוב טסט שבודק "פלט המודל תקין" כשהפלט הוא מחרוזת טקסט חופשית כמעט אינסופית באפשרויות.
UI as Data פותר את שלוש הבעיות בבת אחת, כי הוא מצמצם את מרחב הפלט האפשרי לסכימה סופית וניתנת לאימות. זה בדיוק אותו רציונל שעומד מאחורי Structured Outputs במודלים מודרניים — הכפייה של פלט לפי JSON Schema במקום טקסט חופשי — רק שכאן הסכימה מתארת לא רק נתונים עסקיים אלא ממשק שלם.
סכימה כחוזה, לא כהמלצה
ליבת הגישה היא סכימה פורמלית (בדרך כלל JSON Schema או Zod) שמגדירה במדויק אילו "צורות" ממשק חוקיות. כל component type מוגדר עם השדות המחייבים שלו, הטיפוסים המותרים, וההגבלות (למשל: שדה מסוג select חייב לכלול לפחות אפשרות אחת). המודל מקבל את הסכימה הזו כחלק מההנחיה שלו, ופלט שלא עומד בה נדחה עוד לפני שהוא מגיע לרינדור.
const CardSchema = z.object({
type: z.literal("confirmation-card"),
title: z.string().max(80),
fields: z.array(z.object({
label: z.string(),
value: z.string()
})).max(6),
actions: z.array(z.enum(["confirm", "cancel"]))
});
ולידציה כזו רצה בשרת, לפני שהנתון בכלל מגיע ללקוח — כך שאין תלות באמון בקוד ה-Frontend לבדוק שוב, אבל בפרויקטים רציניים כדאי לאמת פעמיים: גם בשרת, כהגנה ראשונה, וגם בלקוח, כהגנה אחרונה לפני רינדור בפועל, כי אף ערוץ בודד לא אמין ב-100% לאורך זמן.
הפרדה בין נתון לתצוגה
יתרון מרכזי של גישת UI as Data הוא שאותו נתון בדיוק אפשר לרנדר בכמה דרכים שונות בלי לשנות שורת קוד בשכבת המודל. אותו אובייקט confirmation-card יכול להיות מוצג כ-Modal בדסקטופ, כ-Bottom Sheet במובייל, וכהודעת טקסט מסודרת בערוץ שאין בו רינדור גרפי בכלל (כמו SMS). זה עובד כי הנתון עצמו לא מכיל שום פרט ויזואלי — לא צבע, לא גודל, לא מיקום — רק תוכן וכוונה. שכבת הרינדור, שיכולה להיבנות סביב Design System קיים, היא זו שממפה כל component type לביטוי ויזואלי ספציפי לפי ההקשר שבו הוא מוצג.
ההפרדה הזו גם פותרת בעיית נגישות שקל לפספס: מכיוון שהמודל לא שולט ב-markup, אין סיכון שהוא ישכח attribute חשוב כמו aria-label. שכבת הרינדור הקבועה יכולה להבטיח שכל קומפוננטה עומדת בדרישות נגישות WCAG פעם אחת, ולתחזק את זה מרכזית — במקום לתקן בעיות נגישות בכל פלט חדש שהמודל מייצר.
מה עדיין נשאר בידי המודל
חשוב להבהיר את הגבול: UI as Data לא אומר שהמודל מאבד שליטה על החוויה — הוא בוחר אילו רכיבים להציג, באיזה סדר, עם אילו נתונים, ואילו שאלות לשאול. זו עדיין החלטה עשירה ומשמעותית. מה שהוא מאבד הוא היכולת "לצייר פיקסלים" ישירות, וזה בדיוק הוויתור הנכון: המודל טוב בהבנת כוונה והקשר, לא טוב באופן עקבי בעיצוב ויזואלי ובקוד שמקפיד על תקנים. חלוקת האחריות הזו — כוונה למודל, מימוש לקוד קבוע — היא הבסיס שעליו כל שאר ארכיטקטורת ה-Generative UI נבנית.
גרסאות סכימה וניהול שינויים לאורך זמן
מוצר חי משתנה: מוסיפים component type חדש, משנים שדה קיים, או מוציאים משהו לגמלאות. הבעיה היא שהמודל, ה-Backend וה-Frontend לא תמיד מתעדכנים בו-זמנית — ייתכן שגרסת קליינט ישנה עדיין בשימוש אצל חלק מהמשתמשים בזמן שהסכימה כבר התקדמה. לכן כדאי לנהל את הסכימה עצמה כמו כל חוזה API אחר: עם מספר גרסה מפורש, תאימות לאחור לפרק זמן מוגדר, ומדיניות ברורה להוצאה משימוש הדרגתית של שדות ישנים במקום מחיקה מיידית. מודל שמייצר פלט לפי סכימה מיושנת צריך עדיין לעבור ולידציה בהצלחה, ולא לגרום לשגיאת רינדור אצל משתמש שפשוט לא רענן את הדפדפן בזמן.
מומלץ גם לשמור לוג של כל פלט מודל שנכשל בולידציה, כולל הסכימה שנבדקה מולה. לאורך זמן, הלוג הזה הופך לכלי האבחון החשוב ביותר לשיפור הפרומפט ולזיהוי פערים אמיתיים בין מה שהמוצר צריך לאפשר לבין מה שהסכימה כרגע תומכת בו — הרבה יותר יעיל מניחוש אינטואיטיבי איזה רכיב חדש להוסיף בסבב הפיתוח הבא.
הקשר לפרוטוקולי תקשורת סוכן
UI as Data הוא העיקרון, לא הפרוטוקול עצמו — הוא זה שמכתיב שהמבנה שעובר בין הסוכן ל-Frontend הוא תמיד נתון מאומת, בלי קשר לאיזה ערוץ תקשורת בפועל מעביר אותו. בפרויקטים שמשתמשים בפרוטוקול הודעת רכיב ייעודי בין סוכן לממשק, הסכימה של UI as Data היא בדיוק מה שממלא את שדה ה-payload בכל הודעה כזו. וכשהסוכן פועל ברקע לאורך זמן ומזרים עדכונים חיים, כל אירוע רינדור שעובר בזרם עדיין כפוף לאותה סכימה מאומתת בדיוק, כדי שגם רינדור בזמן אמת לא יפתח פרצה שאין בגרסה הסינכרונית הרגילה. העיקרון הזה נשאר יציב גם כשהתשתית מתחתיו משתנה — ואם מחליפים ספק מודל, גרסת פרוטוקול, או אפילו מסגרת Frontend שלמה, שכבת הסכימה היא זו שממשיכה להגן על המערכת לאורך כל השינויים הללו.
תגיות: UI as Data · JSON Schema · Structured Outputs · Generative UI · Security · Design Systems