How a JSON or JSON-like language enables the next generation of safe human and AI-generated UIs Introduzione in , Ho sottolineato che il JavaScript crudo generato dal codice generato da AI è un problema di sicurezza, e la flessibilità di JavaScript senza un framework può portare a codice difficile da gestire. o a (cDOM) con (JPRX se), se vogliamo fidarci di un LLM per costruire le nostre interfacce. Il futuro dell'UI generato da AI di A2 Home Informatica JSON Pointer espressioni regolari Aiuta anche se l'approccio si basa sugli standard del settore per i quali ci sono molte documentazioni e esempi che sono stati probabilmente consumati dai LLM durante la formazione. cCOM e JPRX fanno questo; incorporano concetti e sintassi da JSON Pointers, JSON Schema e XPath. Nel mio precedente articolo, per mostrare come funziona un cDOM e JPRX, ho usato un contatore reattivo, ma siamo reali: i contatori reattivi e le liste di attività sono facili. Qualsiasi framework sembra elegante quando la logica si adatta a un asciugamano. Per dimostrare che un approccio basato su JSON effettivamente mantiene, hai bisogno di un problema con lo stato confuso, i casi di margine e modalità distinte di funzionamento. Hai bisogno di un calcolatore. I calcolatori sono intrinsecamente intricati: Modalità di input: stiamo digitando un numero nuovo o appendiamo un numero esistente? Che cosa succede quando si colpisce "+" poi "-" poi "*" senza colpire uguali? DRY Logic: Come possiamo ridurre al minimo le differenze di codice tra 10 gestori per i pulsanti 0-9? Quindi, ho chiesto a Claude Opus di costruire una calcolatrice completamente funzionale in stile iOS utilizzando Espressioni JPRX e cDOM. zero custom JavaScript functions Il fatto che l’intelligenza artificiale possa produrre un calcolatore dichiarativo con poca prompting puramente dalla documentazione dimostra un altro punto che ho fatto nel mio precedente articolo: cDOM e JPRX non sono solo una nuova sintassi. Il codice Per ridurre i caratteri e il rumore delle citazioni consentendo una spiegazione in linea, sto usando cDOMC, una versione compressa di un cDOM. Un cDOM regolare non supporta i commenti e richiede citazioni intorno agli attributi e alle espressioni JPRX. Quando rappresentato con citazioni e senza commenti, il cDOM può essere trattato come un JSON regolare. { div: { class: "calculator", // A calculator feels stateless, but it's actually a strict state machine. // You're never just "typing a number"; you're either entering the first operand, // waiting for an operator, or entering the next operand. onmount: =state({ display: "0", // What you see on the screen expr: "", // History string, (e.g. "8 + 5 =") prev: "", // value stored before an operation op: "", // the active operator waiting: false // true when expecting a new number vs operator },{ name: "c", // the root name of our state, so we can express things like: /c/display schema: "polymorphic", // allow type changes, e.g. "0" or 0 scope: $this // scope the path to the current element }), children: [ // Display area { div: { class: "display", children: [ { div: { class: "expression",children[=/c/expr] }}, { div: { class: "result",children[=/c/display] }} ] } }, // Button grid { div: { class: "buttons", children: [ // Row 1: AC, ±, %, ÷ { button: { class: "btn btn-clear", onclick: =/c = { display: "0", expr: "", prev: "", op: "", waiting: false }, children: ["AC"] } }, { button: { class: "btn btn-function", onclick: =/c = { display: negate(/c/display), waiting: true, expr: "" }, children: ["±"] } }, { button: { class: "btn btn-function", onclick: =/c = { display: toPercent(/c/display), waiting: true, expr: "" }, children: ["%"] } }, // Divison is our first operator. This is where it gets tricky. // When you click `+`, you can't just link `prev` to `display`. // If you did, `prev` would update every time you selected a new digit for the**second**number, // breaking the math. We need a snapshot of the value at that exact moment. // Excel solves this with INDIRECT, effectively dereferencing a cell. JPRX borrows the same concept: { button: { class: "btn btn-operator", onclick: =/c = { prev: indirect(/c/display), // Capture the value right now expr: concat(/c/display, " ÷"), op: "/", waiting: true }, children: ["÷"] } }, // Row 2: 7, 8, 9, × // I have 10 number buttons. Do I write 10 handlers? Do I write a loop? In React or Vue, // you'd probably map over an array. With JPRX, the DOM is the data key and although map is available, // I represent the calculator using literals in this example. In a future article I will cover map. // By giving each button an `id` (e.g., `id: "7"`), we write a uniform logic expression that adapts // to whichever element triggered it. We just reference $this.id in JPRX and use an xpath to get the text // content for the child node, #../@id. In cDOM (not JPRX) '#' delimits the start of an xpath expression { button: { id: "7", class: "btn btn-number", onclick: =/c = { display: if(/c/waiting, $this.id, if(/c/display==0, $this.id, concat(/c/display, $this.id))), waiting: false }, children: [#../@id] // use xpath (starting char #) to get the text for the button from parent id } }, // Here's what is happening: // Waiting for input? (e.g., just hit `+`) → Replace the display with the button's ID. // Displaying "0"? → Replace it (avoids "07"). // Otherwise: → Append the button's ID. // This is replicated identically for every number button. No loops, no external helper functions. { button: { id: "8", class: "btn btn-number", onclick: =/c = { display: if(/c/waiting, $this.id, if(/c/display==0), $this.id, concat(/c/display, $this.id))), waiting: false }, children: [#../@id] } }, { button: { id: "9", class: "btn btn-number", onclick: =/c = { display: if(/c/waiting, $this.id, if(/c/display==0, $this.id, concat(/c/display, $this.id))), waiting: false }, children: [#../@id] } }, { button: { class: "btn btn-operator", onclick: =/c = { prev: indirect(/c/display), expr: concat(/c/display, " ×"), op: "*", waiting: true }, children: ["×"] } }, // Row 3: 4, 5, 6, − { button: { id: "4", class: "btn btn-number", onclick: =/c = { display: if(/c/waiting, $this.id, if(/c/display==0, $this.id, concat(/c/display, $this.id))), waiting: false }, children: [#../@id] } }, { button: { id: "5", class: "btn btn-number", onclick: =/c = { display: if(/c/waiting, $this.id, if(/c/display==0, $this.id, concat(/c/display, $this.id))), waiting: false }, children: [#../@id] } }, { button: { id: "6", class: "btn btn-number", onclick: =/c = { display: if(/c/waiting, $this.id, if(/c/display==0, $this.id, concat(/c/display, $this.id))), waiting: false }, children: [#../@id] } }, { button: { class: "btn btn-operator", onclick: =/c = { prev: indirect(/c/display), expr: concat(/c/display, " −"), op: "-", waiting: true }, children: ["−"] } }, // Row 4: 1, 2, 3, +, use set and eq just to demonstrate equivalence with = and == // the buttons below use 'set' in place of the infix operator '=', just to show a different way of doing things { button: { id: "1", class: "btn btn-number", onclick: =set(/c, { display: if(/c/waiting, $this.id, if(eq(/c/display, "0"), $this.id, concat(/c/display, $this.id))), waiting: false }), children: [#../@id] } }, { button: { id: "2", class: "btn btn-number", onclick: =set(/c, { display: if(/c/waiting, $this.id, if(eq(/c/display, "0"), $this.id, concat(/c/display, $this.id))), waiting: false }), children: [#../@id] } }, { button: { id: "3", class: "btn btn-number", onclick: =set(/c, { display: if(/c/waiting, $this.id, if(eq(/c/display, "0"), $this.id, concat(/c/display, $this.id))), waiting: false }), children: [#../@id] } }, { button: { class: "btn btn-operator", onclick: =set(/c, { prev: indirect(/c/display), expr: concat(/c/display, " +"), op: "+", waiting: true }), children: ["+"] } }, // Row 5: 0, ., = { button: { id: "0", class: "btn btn-number btn-wide", onclick: =set(/c, { display: if(/c/waiting, $this.id, if(eq(/c/display, "0"), "0", concat(/c/display, $this.id))), waiting: false }), children: [#../@id] } }, { button: { class: "btn btn-number", onclick: =set(/c, { display: if(/c/waiting, "0.", if(contains(/c/display, "."), /c/display, concat(/c/display, "."))), waiting: false }), children: ["."] } }, // Finally, the math. We need to say: // 1. Take the snapshot we stored // 2. Apply the current operator // 3. combine it with what's on screen now // This is the job of calc(). If prev == 8 and op == * and display = 5, then calc would be evaluated as calc("8 * 5") // To keep the syntax a little cleaner we also use $(<path>) as a shorthand for indirect. { button: { class: "btn btn-equals", onclick: =set(/c, { display: if(eq(/c/op, ""), /c/display, calc(concat("$('/c/prev') ", /c/op, " $('/c/display')"))), expr: concat(/c/expr, " ", /c/display, " ="), prev: "", op: "", waiting: true }), children: ["="] } } ] } }, // Branding { div: { class: "branding", children: [ { span: { children: [ "Built with ", { a: { href: "https://github.com/anywhichway/lightview", target: "_blank", children: ["Lightview"] } }, " cDOM • No custom JS!" ] } } ] } } ] } } Caricamento di cDOM tramite Lightview Hypermedia Lightview supporta la capacità di hypermedia simile a consentendo l’utilizzo del Attributi su quasi tutti gli elementi. HTMX src Semplicemente riferimento a File utilizzato : cDOM src <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="description" content="A beautiful calculator built with Lightview cDOM and JPRX reactive expressions - no custom JavaScript!"> <title>Calculator | Lightview cDOM</title> <link rel="stylesheet" href="calculator.css"> <!-- Load Lightview scripts --> <script src="/lightview.js"></script> <!-- DOM as JSON and reactivity support --> <script src="/lightview-x.js"></script> <!-- hypermedia support --> <script src="/lightview-cdom.js"></script> <-- cDOM/JPRX support --> </head> <body> <!-- The calculator cDOM is loaded via Lightview's hypermedia src attribute --> <div id="app" src="./calculator.cdomc"></div> </body> </html> Il Atributo funziona come un HTML o tag - Lightview raccoglie automaticamente il file, lo analizza e rende il contenuto reattivo nell'elemento target. Questo approccio: src <img> <script> .cdomc Perché costruire in questo modo? Potresti guardare a e chiedete: concat("$('/c/prev') ...") Perché nel mondo non scrivi solo parseFloat(prev) + parseFloat(curr) ? Se sei un codice umano per te stesso? probabilmente lo faresti. Lightview supporta i gestori JS standard per questo motivo. Se si costruisce un’infrastruttura per Aderire a un percorso dichiarativo basato su JSON offre cose che il codice crudo non può: AI Agents Sandboxing: Esegue in un ambiente controllato. La logica non può accedere alla 'finestra', effettuare richieste di recupero globale o eseguire il codice secondario arbitrario. Questo rende sicuro "scambiare caldo" la logica UI generata da un LLM in tempo reale. Portabilità: l'intera interfaccia utente - logica e tutto - è solo dati. Può essere inviato da un server, archiviato in un database o trasmesso in streaming da un modello di AI. Modello mentale: forza una chiara separazione tra le trasformazioni di stato e la struttura del punto di vista, che è esattamente come i LLM ragionano meglio. Questo calcolatore dimostra che "declarativo" non deve significare "stupido".Con i primiti giusti - stato, condizionali e riferimento basato sul percorso - è possibile costruire interazioni ricche e complesse senza mai lasciare la struttura dei dati. Il quadro più grande Questa serie non è solo una nuova libreria, si tratta di trovare il giusto livello di astrazione per l'era dell'IA. in , abbiamo esaminato i rischi di lasciare che i LLM scrivano script crudi e abbiamo introdotto la filosofia "Dati come interfaccia utente". Il futuro dell'UI generato da AI In questo articolo, abbiamo mostrato che "Dati come interfaccia utente" non significa "interfaccia utente stupida".Abbiamo gestito lo stato, il contesto, le snapshots dei dati, la matematica e la navigazione DOM con "xpath" senza eseguire una singola riga di JavaScript personalizzato. cDOM definisce la struttura. JPRX definisce il comportamento. È la reattività senza la compilazione e l'interfaccia utente senza i rischi di sicurezza. Prova te stesso Il calcolatore completo è disponibile a: Demo dal vivo: https://lightview.dev/docs/calculator.html Codice sorgente: https://github.com/anywhichway/lightview/blob/main/docs/calculator.html https://lightview.dev/docs/calculator.html https://github.com/anywhichway/lightview/blob/main/docs/calculator.html