How a JSON or JSON-like language enables the next generation of safe human and AI-generated UIs Pengenalan dalam , Saya menunjukkan bahawa JavaScript mentah yang dihasilkan oleh kod yang dihasilkan oleh AI adalah masalah keselamatan, dan fleksibiliti JavaScript tanpa kerangka kerja boleh menyebabkan kod yang sukar untuk dikendalikan. atau a (cDOM) dengan (JPRX jika), jika kita mahu mempercayai LLM untuk membina antara muka kami. Masa depan AI-generated UI A2 yang Komputer Rumah JSON Pointer Ekspresi biasa Ia juga membantu jika pendekatan ini berdasarkan piawaian industri yang terdapat banyak dokumen dan contoh yang mungkin telah dikonsumsi oleh LLM semasa latihan. cCOM dan JPRX melakukan ini; mereka menggabungkan konsep dan sintaks dari JSON Pointers, JSON Schema, dan XPath. Dalam artikel terdahulu saya, untuk menunjukkan bagaimana cDOM dan JPRX bekerja, saya menggunakan pengiraan reaktif, tetapi marilah kita menjadi sebenar: pengiraan reaktif dan senarai tugas mudah. Mana-mana kerangka kerja kelihatan elegan apabila logik cocok pada serbet. Untuk membuktikan pendekatan berasaskan JSON benar-benar bertahan, anda memerlukan masalah dengan keadaan berantakan, kes-kes tepi, dan mod operasi yang berbeza. Perhitungan secara intrinsik adalah sukar: Mod input: Adakah kita memasukkan nombor baru atau melampirkan nombor yang sedia ada? Rantai: Apa yang berlaku apabila anda memukul '+' kemudian '-' kemudian '*' tanpa memukul sama? DRY Logic: Bagaimana kita mengurangkan perbezaan kod antara 10 pengendali untuk butang 0-9? Jadi, saya meminta Claude Opus untuk membina kalkulator sepenuhnya berfungsi, gaya iOS menggunakan - hanya ekspresi deklaratif cDOM dan JPRX. zero custom JavaScript functions Fakta bahawa AI boleh menghasilkan kalkulator deklaratif dengan sedikit prompting semata-mata daripada dokumen menunjukkan satu lagi perkara yang saya buat dalam artikel sebelumnya: cDOM dan JPRX bukan semata-mata sintaks baru. kod yang Untuk mengurangkan huruf dan bunyi kutipan sambil membenarkan penjelasan dalam baris, saya menggunakan cDOMC, versi dikompres cDOM. cDOM biasa tidak menyokong komen dan memerlukan kutipan di sekitar atribut dan ungkapan JPRX. Apabila diwakili dengan kutipan dan tanpa komen, cDOM boleh diperlakukan sebagai JSON biasa. { 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!" ] } } ] } } ] } } Pengenalan kepada Lightview Hypermedia Lightview menyokong keupayaan hypermedia yang serupa dengan Dengan membolehkan penggunaan daripada mempunyai atribut pada hampir mana-mana elemen. HTMX src Hanya rujukan a Fail yang digunakan : 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> yang atribut berfungsi seperti HTML atau Tag - Lightview secara automatik mengambil fail, menganalisisnya, dan menukar kandungan reaktif kepada elemen sasaran. pendekatan ini: src <img> <script> .cdomc Mengapa dibina dengan cara ini? Anda boleh melihat dan bertanya : concat("$('/c/prev') ...") Mengapa di dunia ini anda tidak hanya menulis parseFloat(prev) + parseFloat(curr) ? Jika anda seorang penulisan kod manusia untuk diri anda sendiri? Anda mungkin akan. Lightview menyokong pengendali JS standard untuk alasan itu. Jika anda mempunyai infrastruktur untuk Mengekalkan laluan deklaratif, berasaskan JSON menawarkan perkara-perkara yang kod mentah tidak boleh: AI Agents Sandboxing: Ia dijalankan dalam persekitaran yang dikendalikan. logik tidak boleh mengakses 'jendela', membuat permintaan pemulihan global, atau menjalankan kod sekunder wajar. Ini menjadikan ia selamat untuk "perubahan panas" logik UI yang dihasilkan oleh LLM dalam masa nyata. Portabiliti: Semua UI ini - logik dan semua - hanyalah data. ia boleh dihantar dari pelayan, disimpan dalam pangkalan data, atau disiarkan daripada model AI. Model Mental: Ia memaksa pemisahan yang jelas antara transformasi negara dan struktur pandangan, yang merupakan cara LLM berpendapat yang terbaik. Kalkulator ini membuktikan bahawa "deklaratif" tidak perlu bermaksud "tidak bodoh."Dengan primitif yang betul - keadaan, syarat, dan rujukan berasaskan laluan - anda boleh membina interaksi yang kaya dan kompleks tanpa pernah meninggalkan struktur data. imej yang lebih besar siri ini bukan sahaja tentang perpustakaan baru. ia adalah tentang mencari lapisan abstraksi yang betul untuk zaman AI. dalam , kami melihat risiko membiarkan LLM menulis skrip mentah dan memperkenalkan falsafah "Data sebagai UI". Masa depan AI-generated UI Dalam artikel ini, kami menunjukkan bahawa "Data sebagai UI" tidak bermaksud "UI bodoh."Kami menangani keadaan, konteks, snapshots data, matematik, dan navigasi DOM dengan 'xpath' tanpa menjalankan satu baris JavaScript tersuai. cDOM mendefinisikan struktur. JPRX mendefinisikan tingkah laku. Ia adalah reaktiviti tanpa kompilasi dan UI tanpa risiko keselamatan. Cuba sendiri Kalkulator lengkap boleh didapati di: Demo Langsung: https://lightview.dev/docs/calculator.html Kod sumber: 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