How a JSON or JSON-like language enables the next generation of safe human and AI-generated UIs 導入 イン I pointed that the raw JavaScript generated by AI-generated code is a security problem, and the flexibility of JavaScript without a framework can result in hard-to-manage code. I argued that we need declarative, sandboxed formats like あるいはA (cDOM) with (JPRXの場合)、当社のインターフェイスを構築するためにLLMを信頼したい場合。 AI-Generated UIの未来 A2さん コンピュータホーム JSON Pointer 規則式 また、アプローチが業界標準に基づいている場合にも役立ちます. cCOM と JPRX は、トレーニング中に LLM によって消費された可能性のある多くの文書と例があります. cCOM と JPRX は、JSON Pointers、JSON Schema、および XPath からの概念と構文を組み込んでいます。 私の前の記事では、cDOMとJPRXがどのように動作するかを示すために、私は反応カウンターを使用しましたが、現実的になるようにしましょう:反応カウンターとタスクリストは簡単です。 論理がデッキに合ったときにどんなフレームワークもエレガントに見えます。 JSONベースのアプローチが実際に持っていることを証明するには、混乱状態、エッジケース、および異なる操作モードの問題が必要です。 計算機は本質的に難しい: 入力モード: 新しい番号を入力するか、既存の番号に添付するか? チェーニング: 「+」、それから「-」、それから「*」を打つと、等数を打たないとどうなるでしょうか? DRY Logic: ボタン 0-9 の 10 つのハンドラー間のコードの違いを最小限に抑えるにはどうすればよいですか? だから、私はクロード・オプスに、完全に機能するiOSスタイルの計算機を構築するように頼んだ。 cDOM と JPRX 表現のみを指定します。 zero custom JavaScript functions AIがドキュメンタリーから純粋に推奨するものが少ない宣言計算機を生成できるという事実は、以前の記事で私が述べたもう一つの点を示しています: cDOMとJPRXは単に新しいシンタクスではありません。 コード 文字と引用ノイズを減らすために、インライン説明を許可しながら、私はcDOMCを使用しています. 通常のcDOMはコメントをサポートしず、属性やJPRX表現の周りの引用が必要です. 引用とコメントなしで表されるとき、cDOMは通常のJSONとして扱うことができます. { 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!" ] } } ] } } ] } } cDOM を Lightview Hypermedia でロードする Lightview は、ハイパーメディア機能をサポートしています。 利用を許可することにより、 ほぼすべての要素に属します。 HTMX src 単なる参照 A ファイル使用 : 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> THE 属性はHTMLのように機能します。 または タグ - Lightview は自動的に ファイルを解析し、反応したコンテンツをターゲット要素に変換します。 src <img> <script> .cdomc なんでこんな風に作るの? あなたは見るかもしれない と尋ねる。 concat("$('/c/prev') ...") なぜ世の中、あなたはただ書かないのか parseFloat(prev) + parseFloat(curr) ? もしあなたが自分自身のための人間のコードを書くなら? あなたはおそらくそうでしょう。 Lightview はその理由で標準の JS トレーダーをサポートしています。 しかし、あなたがインフラを構築している場合は、 宣言的な JSON ベースのパスに固執すると、原始コードができないものを提供します。 AI Agents サンドボクシング: コントロールされた環境で実行します。 論理は「ウィンドウ」にアクセスできず、グローバル・キャッチ・リクエストを作成したり、任意のサンドボクシング・コードを実行したりできません。 これにより、LLMによって生成されたUIの論理をリアルタイムで「ホット・スワップ」するのが安全になります。 Portability: This entire UI—logic and all—is just data. It can be sent from a server, stored in a database, or streamed from an AI model. サーバーから送信され、データベースに保存され、AIモデルからストリーミングされることができます。 精神モデル:それは、州変革とビジョン構造の間の明確な分離を強制し、それが正確にLLMが最もよい理由である。 この計算機は、「宣言」は「バカ」を意味する必要がないことを証明します。 正しい原理値 - 状態、条件値、およびパスベースの参照 - あなたは、データ構造を離れることなく、豊富で複雑な相互作用を構築することができます。 より大きな画像 このシリーズは、新しいライブラリについてだけではなく、AI時代に適した抽象層を見つけることについてです。 イン , we looked at the risks of letting LLMs write raw scripts and introduced the "Data as UI" philosophy. , we looked at the risks of letting LLMs write raw scripts and introduced the "Data as UI" philosophy. , we looked at the risks of letting LLMs write raw scripts and introduced the "Data as UI" philosophy. AI-Generated UIの未来 この記事では、「Data as UI」は「dumb UI」を意味しないことを示しました。我々は、ステータス、コンテキスト、データのスナップショット、数学、およびDOMのナビゲーションを「xpath」で処理し、カスタムJavaScriptの1行も実行しなかった。 cDOM は構造を定義します. JPRX は行動を定義します. それはコンパイルなしの反応であり、UI はセキュリティのリスクなしです。 自分で試してみて 完全な計算機は、以下で入手できます。 ライブデモ: https://lightview.dev/docs/calculator.html ソースコード: 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