How a JSON or JSON-like language enables the next generation of safe human and AI-generated UIs Introduksi Dalam , Saya menunjukkan bahwa JavaScript mentah yang dihasilkan oleh kode yang dihasilkan oleh AI adalah masalah keamanan, dan fleksibilitas JavaScript tanpa kerangka kerja dapat mengakibatkan kode yang sulit untuk dikelola. atau a (cDOM) dengan (JPRX jika), jika kita ingin mempercayai LLM untuk membangun antarmuka kami. Masa Depan AI-Generated UI A2 yang Rumah Komputer JSON Pointer Ekspresi reguler Ini juga membantu jika pendekatan didasarkan pada standar industri yang memiliki banyak dokumentasi dan contoh yang mungkin telah dikonsumsi oleh LLM selama pelatihan. cCOM dan JPRX melakukan ini; mereka menggabungkan konsep dan sintaks dari JSON Pointers, JSON Schema, dan XPath. Dalam artikel saya sebelumnya, untuk menunjukkan bagaimana cDOM dan JPRX bekerja, saya menggunakan counter reaktif, tetapi mari kita menjadi nyata: counter reaktif dan daftar hal yang harus dilakukan mudah. Setiap kerangka terlihat elegan ketika logika cocok pada handuk. Untuk membuktikan pendekatan berbasis JSON benar-benar bertahan, Anda membutuhkan masalah dengan keadaan yang kacau, kasus tepi, dan mode operasi yang berbeda. Anda membutuhkan kalkulator. Kalkulator secara intrinsiknya rumit: Mode input: Apakah kita mengetik nomor baru atau menempelkan pada nomor yang sudah ada? Rantai: Apa yang terjadi ketika Anda mengetuk '+' kemudian '-' kemudian '*' tanpa mengetuk sama? DRY Logic: Bagaimana kita meminimalkan perbedaan kode antara 10 pengendali untuk tombol 0-9? Jadi, saya meminta Claude Opus untuk membangun kalkulator sepenuhnya fungsional, gaya iOS menggunakan - hanya ekspresi deklaratif cDOM dan JPRX. zero custom JavaScript functions Fakta bahwa AI dapat menghasilkan kalkulator deklaratif dengan sedikit prompting murni dari dokumentasi menunjukkan titik lain yang saya buat dalam artikel saya sebelumnya: cDOM dan JPRX bukan hanya sintaks baru. Kode yang Untuk mengurangi karakter dan kebisingan kutipan sambil memungkinkan penjelasan inline, saya menggunakan cDOMC, versi dicampur dari cDOM. cDOM biasa tidak mendukung komentar dan membutuhkan kutipan di sekitar atribut dan ekspresi JPRX. Ketika diwakili dengan kutipan dan tanpa komentar, cDOM dapat 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!" ] } } ] } } ] } } Cara Menggunakan Lightview Hypermedia Lightview mendukung kemampuan hypermedia yang mirip dengan Dengan memungkinkan penggunaan dari Atribut pada hampir setiap elemen. HTMX src Referensi sederhana a File 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 bekerja seperti HTML atau Tag - Lightview secara otomatis file, menganalisisnya, dan membuat konten reaksi menjadi elemen target. pendekatan ini: src <img> <script> .cdomc Mengapa harus dibangun dengan cara ini? Anda bisa melihat dan bertanya : concat("$('/c/prev') ...") Mengapa di dunia ini engkau tidak hanya menulis parseFloat(prev) + parseFloat(curr) ? Jika Anda menulis kode manusia untuk diri sendiri? Anda mungkin akan. Lightview mendukung pengendali JS standar untuk alasan itu. Jika Anda ingin membangun infrastruktur untuk Menempel pada jalur deklaratif, berbasis JSON menawarkan hal-hal yang kode mentah tidak dapat: AI Agents Sandboxing: Ini berjalan dalam lingkungan yang dikendalikan. logika tidak dapat mengakses 'jendela', membuat permintaan pick up global, atau menjalankan kode sekunder acak. ini membuatnya aman untuk "hot swap" logika UI yang dihasilkan oleh LLM dalam waktu nyata. Portabilitas: seluruh UI ini – logika dan semua – hanyalah data. dapat dikirim dari server, disimpan dalam database, atau ditransmisikan dari model AI. Model Mental: Ini memaksa pemisahan yang jelas antara transformasi negara dan struktur pandangan, yang persis bagaimana LLM berpendapat yang terbaik. Kalkulator ini membuktikan bahwa "deklaratif" tidak harus berarti "tidak masuk akal."Dengan primitif yang tepat - keadaan, kondisi, dan referensi berbasis jalur - Anda dapat membangun interaksi yang kaya dan kompleks tanpa pernah meninggalkan struktur data. Gambar yang lebih besar Seri ini bukan hanya tentang perpustakaan baru, tetapi tentang menemukan lapisan abstraksi yang tepat untuk era AI. Dalam , kami melihat risiko membiarkan LLM menulis skrip mentah dan memperkenalkan filosofi "Data sebagai UI". Masa Depan AI-Generated UI Dalam artikel ini, kami menunjukkan bahwa "Data sebagai UI" tidak berarti "UI bodoh."Kami menangani status, konteks, snapshots data, matematika, dan navigasi DOM dengan 'xpath' tanpa menjalankan satu baris JavaScript kustom. cDOM mendefinisikan struktur.JPRX mendefinisikan perilaku.Ini adalah reaktivitas tanpa kompilasi dan UI tanpa risiko keamanan. Coba sendiri Kalkulator lengkap tersedia di: Demo langsung: https://lightview.dev/docs/calculator.html Kode 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