How a JSON or JSON-like language enables the next generation of safe human and AI-generated UIs Introduction nan nan , Mwen te montre ke JavaScript a brik ki te pwodwi pa kòd ki te kreye pa AI se yon pwoblèm sekirite, ak fleksibilite nan JavaScript san yon framework ka rezilta nan koòd la difisil yo jesyon. Mwen argumente ke nou bezwen deklaratif, sandboxed fòma tankou ou a (CDOM) ak (JPRX si), si nou vle konfyans yon LLM yo bati interfaces nou an. Tèm nan AI-genere UI A2 nan Kominikasyon nan kay JSON Pointer ekspresyon regilye Li ede tou si apwòch la se ki baze sou estanda endistriyèl pou ki gen anpil dokiman ak egzanp ki te pwobableman konsome pa LLMs pandan fòmasyon an. cCOM ak JPRX fè sa a; yo entegre konsèp ak sentaksyon soti nan JSON Pointers, JSON Schema, ak XPath. Nan atik precedent mwen an, yo montre ki jan yon cDOM ak JPRX travay, mwen te itilize yon kontre reaktè, men fè nou reyèl: kontre reaktè ak lis travay yo fasil. Nenpòt framework sanble elegant lè logik la kouvri sou yon serveur. Pou pwouve ke yon apwòch ki baze sou JSON reyèlman kenbe sou, ou bezwen yon pwoblèm ak estati chaj, ka kantite, ak mòd diferan nan operasyon. Ou bezwen yon kalkil. Calculators yo intrinsè tricky: Mod entwodiksyon: Èske nou anrejistre yon nimewo nouvo oswa ajoute yon nimewo ki deja egziste? Chaining: Ki sa ki rive lè ou koute '+' Lè sa a, '-' Lè sa a, '*' san yo pa koute egal? DRY Logic: Ki jan nou minimalize diferans kòd ant 10 manipilè pou bouton 0-9? Se konsa, mwen te mande Claude Opus yo bati yon konplètman fonksyonèl, iOS-style kalkilè lè l sèvi avèk - sèlman ekspresyon deklaratif cDOM ak JPRX. zero custom JavaScript functions Fakti ke AI ka pwodwi yon kalkil deklaratif ak mwens presizyon purman soti nan dokimantasyon demontre yon lòt pwen mwen te fè nan atik anvan m ': cDOM ak JPRX pa sèlman nouvo sentaks. Yo ka yon pwotokòl pou kolaborasyon moun-machin. Kòd la Pou diminye karaktè ak soumèt sitasyon pandan y ap pèmèt eksplike nan liy, mwen itilize cDOMC, yon vèsyon kompresyon nan yon cDOM. Yon cDOM regilye pa sipòte kòmantè ak mande pou sitasyon alantou atribit ak ekspresyon JPRX. Lè reprezante ak sitasyon ak san yo pa kòmantè, cDOM ka trete kòm JSON regilye. { 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!" ] } } ] } } ] } } Loading cDOM soti nan Lightview Hypermedia Lightview sipòte kapasite hypermedia menm jan ak Pa pèmèt itilize nan atribit sou prèske nenpòt eleman. HTMX src Simpleman referans a Dosye itilize : 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> nan atribit travay tankou yon HTML ou tag - Lightview otomatikman retire dosye, parse li, ak fè kontni a reaktè nan eleman objektif la. Sa a apwòch: src <img> <script> .cdomc Poukisa yo bati nan fason sa a? Ou ka gade nan Epi mande: concat("$('/c/prev') ...") Pou sa nan mond lan ou pa pral jis ekri parseFloat(prev) + parseFloat(curr) ? Si ou se yon òdinatè ekri kòd pou tèt ou? Ou pral pwobableman. Lightview sipòte manadjè JS estanda pou menm rezon sa a. Men, si ou ap bati enfrastrikti pou , kalkil la chanjman. Atravè yon deklaratif, JSON ki baze sou wout ofri bagay kodaj manje pa ka: AI Agents Sandboxing: Li te kouri nan yon anviwònman kontwole. Lojisyèl la pa ka jwenn aksè a 'wòch', fè demann global retouch, oswa kouri òdinè antretyen kòd. Sa a fè li an sekirite pou "hot swap" logik UI ki te kreye pa yon LLM nan tan reyèl. Pòtab: Tout UI sa a - logik ak tout - se jis done. Li ka voye soti nan yon sèvè, depoze nan yon baz done, oswa streame soti nan yon modèl AI. Mental modèl: Li mande pou yon separasyon klè ant transformasyon eta ak estrikti gade, ki se eksakteman ki jan LLMs rezon pi bon. Kalkilè sa a pwouve ke "declarative" pa bezwen vle di "dumb." Avèk primitif yo dwat - eta, kondisyone, ak referans ki baze sou wout - ou ka bati rich, entèakrasyon konplèks san yo pa janm kite estrikti a done. Pifò imaj Seri sa a se pa sèlman sou yon nouvo bibliyotèk. Li se sou rechèch lag la abstraksyon dwat pou èdtan an nan AI. nan , nou te tcheke nan risk yo ki pèmèt LLMs ekri skript brik ak prezante filozofi a "Data kòm UI". Tèm nan AI-genere UI Nan atik sa a, nou te montre ke "Data kòm UI" pa vle di "dumb UI." Nou te trete eta, konteks, date snapshots, matematik, ak DOM navigasyon ak "xpath" san yo pa kouri yon sèl liy nan JavaScript Custom. cDOM defines estrikti. JPRX defines comportement. Li se reactivity san kompilasyon an ak UI san risk sekirite. Tès li tèt ou Konplè kalkil la disponib nan: Live Demo: https://lightview.dev/docs/calculator.html Kòd sous: 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