How a JSON or JSON-like language enables the next generation of safe human and AI-generated UIs Ievads uz , Es norādīju, ka neapstrādāts JavaScript, ko rada AI ģenerēts kods, ir drošības problēma, un JavaScript elastība bez rāmja var izraisīt grūti pārvaldāmu kodu. vai a (cDOM) ar (JPRX ja), ja mēs vēlamies uzticēties LLM, lai izveidotu mūsu saskarnes. AI ģenerētās UI nākotne A2E Datoru māja JSON Pointer regulāras izteiksmes Tas arī palīdz, ja pieeja ir balstīta uz nozares standartiem, par kuriem ir daudz dokumentācijas un piemēru, kurus, iespējams, ir patērējuši LLM apmācības laikā. cCOM un JPRX to dara; tie ietver jēdzienus un sintaksi no JSON Pointers, JSON Schema un XPath. Manā iepriekšējā rakstā, lai parādītu, kā darbojas cDOM un JPRX, es izmantoju reaktīvo skaitītāju, bet ļaujiet mums būt reāliem: reaktīvie skaitītāji un uzdevumu saraksti ir viegli. Jebkura sistēma izskatās eleganta, kad loģika iederas salvetē. Lai pierādītu, ka JSON balstīta pieeja patiešām saglabājas, jums ir nepieciešama problēma ar neskaidru stāvokli, malām un atšķirīgiem darbības veidiem. Kalkulatori pēc būtības ir sarežģīti: Ievades režīmi: Vai mēs ievadām jaunu numuru vai pievienojam esošo? Ķēdes: Kas notiek, kad jūs hit "+" tad "-" tad "*" bez hit vienādi? DRY loģika: kā mēs samazinām koda atšķirības starp 10 apstrādātājiem 0-9 pogām? Tātad, es lūdzu Claude Opus izveidot pilnībā funkcionālu, iOS stila kalkulatoru, izmantojot - tikai deklaratīvās cDOM un JPRX izteiksmes. zero custom JavaScript functions Fakts, ka AI varētu ražot deklaratīvu kalkulatoru ar nelielu pamudinājumu tikai no dokumentācijas, demonstrē vēl vienu punktu, ko es izdarīju savā agrākajā rakstā: cDOM un JPRX nav tikai jauna sintakse. Kods Lai samazinātu rakstzīmes un citātu troksni, vienlaikus atļaujot iekšējo paskaidrojumu, es izmantoju cDOMC, kas ir saspiesta cDOM versija. Parastā cDOM nepieļauj komentārus un prasa citātus ap atribūtiem un JPRX izteiksmēm. { 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!" ] } } ] } } ] } } Lejupielādēt cDOM, izmantojot Lightview Hypermedia Lightview atbalsta hipermediju iespējas, kas ir līdzīgas Ņemot vērā iespēju izmantot Attiecas uz gandrīz jebkuru elementu. HTMX src Vienkārša atsauce a failu izmantošana : 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> Tās Atribūts darbojas kā HTML vai Tag - Lightview automātiski failu, analizē to un pārvērš reaktīvo saturu mērķa elementā. src <img> <script> .cdomc Kāpēc veidot šādā veidā? Jūs varat apskatīt un jautājiet: concat("$('/c/prev') ...") Kāpēc pasaulē jūs vienkārši nerakstītu parseFloat(prev) + parseFloat(curr) ? Ja jūs esat cilvēka kods rakstīšanai sev? jūs, iespējams, būtu. Lightview atbalsta standarta JS apstrādātāji tieši tāpēc. Ja jums ir nepieciešama infrastruktūra, lai Pieskaroties deklarētam, JSON balstītam ceļam, tiek piedāvātas lietas, ko neapstrādāts kods nevar: AI Agents Sandboxing: Tas darbojas kontrolētā vidē. loģika nevar piekļūt "logu", veikt globālos uztveršanas pieprasījumus vai izpildīt patvaļīgu sekundāro kodu. Pārnesamība: Šis viss UI - loģika un viss - ir tikai dati. to var nosūtīt no servera, uzglabāt datubāzē vai straumēt no AI modeļa. Garīgais modelis: Tas nosaka skaidru atdalīšanu starp valsts transformācijām un skatu struktūru, kas ir tieši tas, kā LLM vislabāk pamato. Ar pareizajiem primīviem - stāvokli, nosacījumiem un maršrutu balstītu atsauci - jūs varat izveidot bagātīgas, sarežģītas mijiedarbības, nekad neatstājot datu struktūru. Lielākais attēls Šī sērija nav tikai par jaunu bibliotēku, tā ir par pareizās abstrakcijas slāņa atrašanu AI laikmetam. uz , mēs apskatījām riskus, ļaujot LLM rakstīt neapstrādātus skriptus un ieviesa "Datu kā UI" filozofiju. AI ģenerētās UI nākotne Šajā rakstā mēs parādījām, ka "Dati kā UI" nenozīmē "dumb UI." Mēs apstrādājām stāvokli, kontekstu, datu snapshots, matemātiku un DOM navigāciju ar "xpath", neveicot vienu līniju pielāgotu JavaScript. cDOM definē struktūru. JPRX definē uzvedību. Tas ir reaktivitāte bez kompilācijas un UI bez drošības riskiem. Izmēģini pats Pilns kalkulators ir pieejams: Live Demo: https://lightview.dev/docs/calculator.html Avota kods: 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