lightview 2.3.5 → 2.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,190 @@
1
+ # XPath Integration for cDOM - Implementation Summary
2
+
3
+ ## Overview
4
+ XPath support has been added to cDOM, allowing developers to navigate the DOM structure during element definition. This enables referencing parent/ancestor attributes without duplication.
5
+
6
+ ## Features Implemented
7
+
8
+ ### 1. Static XPath (`#` prefix)
9
+ - **Syntax:** `#xpath-expression`
10
+ - **Evaluation:** Once during DOM construction (two-pass approach)
11
+ - **Reactivity:** None - becomes a static string value
12
+ - **Escape Sequence:** `'#` produces literal `#` string
13
+
14
+ **Example:**
15
+ ```javascript
16
+ {
17
+ tag: "button",
18
+ attributes: {
19
+ "data-parent-id": "#../@id",
20
+ "aria-labelledby": "#../../@label-id"
21
+ },
22
+ children: ["#../@aria-label"]
23
+ }
24
+ ```
25
+
26
+ ### 2. Reactive XPath (`=xpath()` helper)
27
+ - **Syntax:** `=xpath('expression')`
28
+ - **Evaluation:** As JPRX helper, returns computed signal
29
+ - **Reactivity:** Re-evaluates when observed DOM changes (TODO: full MutationObserver)
30
+ - **Composable:** Can mix with JPRX expressions
31
+
32
+ **Example:**
33
+ ```javascript
34
+ {
35
+ attributes: {
36
+ title: "=xpath('../@id')",
37
+ class: "=concat(xpath('../@disabled'), ' ', $/theme)"
38
+ }
39
+ }
40
+ ```
41
+
42
+ ## Allowed XPath Axes (Backward-Only)
43
+ ✅ `self::` or `.` - current node
44
+ ✅ `parent::` or `..` - parent node
45
+ ✅ `ancestor::` - all ancestors
46
+ ✅ `ancestor-or-self::`
47
+ ✅ `preceding-sibling::` - earlier siblings
48
+ ✅ `preceding::` - all preceding nodes
49
+
50
+ ❌ `child::` - forward reference (not allowed)
51
+ ❌ `descendant::` - forward reference (not allowed)
52
+ ❌ `following::` - forward reference (not allowed)
53
+ ❌ `following-sibling::` - forward reference (not allowed)
54
+
55
+ ## Implementation Details
56
+
57
+ ### Files Modified
58
+
59
+ 1. **`jprx/parser.js`**
60
+ - Added `preprocessXPath()` function to quote unquoted `#xpath` expressions in cDOMC
61
+ - Handles escape sequences (`'#`)
62
+ - Skips quoted strings and comments
63
+
64
+ 2. **`src/lightview-cdom.js`**
65
+ - Updated `hydrate()` to detect and mark `#xpath` expressions
66
+ - Added `validateXPath()` to ensure no forward-looking axes
67
+ - Added `resolveStaticXPath()` for two-pass XPath resolution
68
+ - Registered `registerDOMHelpers` for reactive xpath() helper
69
+ - Exported `resolveStaticXPath` for global access
70
+
71
+ 3. **`src/lightview.js`**
72
+ - Updated `makeReactiveAttributes()` to handle `__xpath__` markers from hydration
73
+ - Updated `processChildren()` to handle XPath markers in children arrays
74
+ - Added call to `resolveStaticXPath()` after DOM tree construction
75
+
76
+ 4. **`jprx/helpers/dom.js`** (NEW)
77
+ - Created reactive `xpath()` helper function
78
+ - Returns computed signal for XPath evaluation
79
+ - Validates against forward-looking axes
80
+ - TODO: Add Mutation Observer for full reactivity
81
+
82
+ 5. **`jprx/index.js`**
83
+ - Exported `registerDOMHelpers`
84
+
85
+ ### Two-Pass Resolution Flow
86
+
87
+ **Pass 1: Build DOM Tree**
88
+ 1. `parseCDOMC()` preprocesses input, quoting unquoted `#xpath` expressions
89
+ 2. `hydrate()` detects `#xpath` strings, marks with `{ __xpath__: 'expr', __static__: true }`
90
+ 3. `element()` creates DOM nodes
91
+ 4. `makeReactiveAttributes()` sets `data-xpath-{attrname}` markers on nodes
92
+ 5. `processChildren()` creates text nodes with `__xpathExpr` property
93
+ 6. Tree is fully constructed via `appendChild()`
94
+
95
+ **Pass 2: Resolve XPath**
96
+ 7. `resolveStaticXPath()` called after tree construction
97
+ 8. Walks tree with `TreeWalker`
98
+ 9. Finds nodes with `data-xpath-*` attributes or `__xpathExpr` properties
99
+ 10. Validates XPath expressions (no forward-looking)
100
+ 11. Calls browser's `document.evaluate()` with node as context
101
+ 12. Sets resolved values and cleans up markers
102
+
103
+ ## Usage Examples
104
+
105
+ ### cDOMC (Unquoted)
106
+ ```javascript
107
+ {
108
+ tag: div,
109
+ attributes: {
110
+ id: parent-div,
111
+ data-theme: dark
112
+ },
113
+ children: [{
114
+ tag: button,
115
+ attributes: {
116
+ data-parent-id: #../@id,
117
+ class: "=concat(xpath('../@data-theme'), '-button')"
118
+ },
119
+ children: [#../@aria-label]
120
+ }]
121
+ }
122
+ ```
123
+
124
+ ### JSON
125
+ ```javascript
126
+ {
127
+ "tag": "div",
128
+ "attributes": {
129
+ "id": "parent-div",
130
+ "data-theme": "dark"
131
+ },
132
+ "children": [{
133
+ "tag": "button",
134
+ "attributes": {
135
+ "data-parent-id": "#../@id",
136
+ "class": "=concat(xpath('../@data-theme'), '-button')"
137
+ },
138
+ "children": ["#../@aria-label"]
139
+ }]
140
+ }
141
+ ```
142
+
143
+ ## Testing
144
+
145
+ Test file created: `test-xpath.html`
146
+
147
+ Open in browser to verify:
148
+ - Static XPath in attributes
149
+ - Parent attribute access
150
+ - Escape sequences
151
+
152
+ ## Key Features
153
+
154
+ 1. **Unquoted XPath in cDOMC**: Fully supported through structural integration with the parser character loop.
155
+ - ✅ Working: `children: [#../@id]`
156
+ - ✅ Working: `children: [#div[@id='1']]` (Complex paths with brackets now work!)
157
+
158
+ 2. **Reactive xpath() helper**: Currently evaluates once via computed signal. Full reactivity with MutationObserver is TODO.
159
+
160
+ 3. **XPath complexity**: Works with paths like `../@id`, `../../@attr`, and predicate-based paths.
161
+
162
+ ## Implementation: Structural Parser Integration
163
+
164
+ Instead of a string preprocessor hack, XPath support is now integrated directly into the `parseCDOMC` word parser.
165
+
166
+ - When `parseWord` encounters `#`, it enters a permissive "expression mode" (similar to `=` JPRX).
167
+ - In this mode, it tracks nesting depth for `[]`, `{}`, and `()`.
168
+ - It handles internal quotes within the XPath safely.
169
+ - It only terminates the unquoted word when nesting depth is `0` and it encounters a structural cDOMC delimiter (e.g., `]`, `}`, or `,`).
170
+
171
+ ## Bug Fixes Applied
172
+
173
+ 1. **Structural Delimiters**: Fixed the issue where unquoted XPath would consume the closing bracket of its container.
174
+ 2. **Complex Paths**: Enabled support for square brackets and other structural characters within the XPath itself.
175
+ 3. **Text node context**: Fixed XPath evaluation to use the text node itself as context, not its parent element.
176
+
177
+ ## Next Steps
178
+
179
+ 1. Add MutationObserver to reactive xpath() helper for full reactivity
180
+ 2. Add comprehensive unit tests for both static and reactive XPath
181
+ 3. Document XPath support in cDOM documentation
182
+ 4. Add more examples showing different XPath axes
183
+ 5. Performance optimization for large DOMs
184
+
185
+ ## Lint Issues
186
+
187
+ Note: The following lint warnings exist but don't affect functionality:
188
+ - **File length**: `/mnt/c/Users/Owner/AntigravityProjects/lightview/jprx/parser.js` has 1552 lines (limit 500)
189
+ - This is a large parser file; could be split/refactored in the future
190
+ - **Escape characters**: Some unnecessary escapes in regex (cosmetic issue)
package/_headers CHANGED
@@ -2,3 +2,8 @@
2
2
  Access-Control-Allow-Origin: *
3
3
  Access-Control-Allow-Methods: GET, OPTIONS
4
4
  Access-Control-Allow-Headers: *
5
+
6
+ /*.cdom
7
+ Content-Type: application/cdom
8
+ /*.cdomc
9
+ Content-Type: application/cdomc
package/_routes.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 1,
3
+ "include": [
4
+ "/*"
5
+ ],
6
+ "exclude": []
7
+ }
package/build-bundles.mjs CHANGED
@@ -102,4 +102,14 @@ if (isWatch) {
102
102
  }, 300); // 300ms debounce
103
103
  }
104
104
  });
105
+
106
+ // Also watch jprx directory for parser changes
107
+ watch(resolve(__dirname, 'jprx'), { recursive: true }, (event, filename) => {
108
+ if (filename && !filename.includes('~')) {
109
+ clearTimeout(debounceTimer);
110
+ debounceTimer = setTimeout(() => {
111
+ runBuilds();
112
+ }, 300);
113
+ }
114
+ });
105
115
  }
@@ -0,0 +1,283 @@
1
+ # Building a Fully Functional Calculator with Zero Custom JavaScript
2
+
3
+ *How a JSON or JSON-like language enables the next generation of safe human and AI-generated UIs*
4
+
5
+ ---
6
+
7
+ In [The Future of AI-Generated UI](https://hackernoon.com/the-future-of-ai-generated-ui-why-i-built-cdom-and-jprx), I made the case that raw JavaScript is a security nightmare for AI-generated code. I argued that we need declarative, sandboxed formats like **cDOM** (structure) and **JPRX** (behavior) if we ever want to trust an LLM to build our interfaces.
8
+
9
+ But let's be real: reactive counters and to-do lists are easy. Any framework looks elegant when the logic fits on a napkin. To prove a JSON-based approach actually holds up in production, you need a problem with messy state, edge cases, and distinct modes of operation.
10
+
11
+ You need a calculator.
12
+
13
+ Calculators are inherently tricky:
14
+ - **Input Modes**: Are we typing a fresh number or appending to an existing one?
15
+ - **Chaining**: What happens when you hit `+` then `-` then `*` without hitting equals?
16
+ - **DRY Logic**: How do we avoid writing 10 separate handlers for buttons 0-9?
17
+
18
+ So I built (well actually Claude Opus built) a fully functional, iOS-style calculator using **zero custom JavaScript functions** - just declarative cDOM and JPRX expressions.
19
+
20
+ ## The Result
21
+
22
+ A fully functional, iOS-style interface is handled purely by the expression language. No hidden `script` tags, no helper functions.
23
+
24
+ ![Calculator Screenshot](calculator-screenshot.png)
25
+
26
+ ## The State Machine
27
+
28
+ 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 typing the second operand.
29
+
30
+ We model this brain directly in the component's mount event:
31
+
32
+ ```javascript
33
+ onmount: =state({
34
+ display: "0", // What you see on screen
35
+ expr: "", // History string (e.g., "8 + 5 =")
36
+ prev: "", // The value stored before an operation
37
+ op: "", // The active operator (+, -, *, /)
38
+ waiting: false // True when expecting new number input vs an operator
39
+ }, { name: "c", schema: "polymorphic", scope: $this })
40
+ ```
41
+
42
+ This creates a reactive state object named `c`. We can read it anywhere with paths like `/c/display`.
43
+
44
+ ## Solving the "10 Buttons" Problem
45
+
46
+ The classic DRY (Don't Repeat Yourself) challenge: 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.
47
+
48
+ With JPRX, the DOM *is* the data key.
49
+
50
+ By giving each button an `id` (e.g., `id: "7"`), we write a **single logic expression** that adapts to whichever element triggered it. We just reference `$this.id`:
51
+
52
+ ```javascript
53
+ { button: {
54
+ id: "7",
55
+ class: "btn btn-number",
56
+ onclick: =set(/c, {
57
+ display: if(/c/waiting,
58
+ $this.id,
59
+ if(eq(/c/display, "0"),
60
+ $this.id,
61
+ concat(/c/display, $this.id)
62
+ )
63
+ ),
64
+ waiting: false
65
+ }),
66
+ children: ["7"]
67
+ } }
68
+ ```
69
+
70
+ Here's what is happening:
71
+ 1. **Waiting for input?** (e.g., just hit `+`) → Replace the display with the button's ID.
72
+ 2. **Displaying "0"?** → Replace it (avoids "07").
73
+ 3. **Otherwise:** → Append the button's ID.
74
+
75
+ This works identically for every number button. No loops, no external helper functions.
76
+
77
+ ## Operators and "Indirect" Values
78
+
79
+ 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.
80
+
81
+ We need a snapshot of the value *at that exact moment*.
82
+
83
+ Excel solves this with `INDIRECT`, effectively dereferencing a cell. JPRX borrows the same concept:
84
+
85
+ ```javascript
86
+ { button: {
87
+ class: "btn btn-operator",
88
+ onclick: =set(/c, {
89
+ prev: indirect(/c/display), // Capture the value right NOW
90
+ expr: concat(/c/display, " +"),
91
+ op: "+",
92
+ waiting: true
93
+ }),
94
+ children: ["+"]
95
+ } }
96
+ ```
97
+
98
+ `indirect(/c/display)` reads the path and returns its **value** at that exact moment. It essentially dereferences the pointer.
99
+
100
+ ## The `calc()` Helper
101
+
102
+ Finally, the math. We need to say: *"Take the snapshot we stored, apply the current operator, and combine it with what's on screen now."*
103
+
104
+ This is the job of `calc()`. To keep the syntax clean, we use `$('path')` as a shorthand for capturing values right before evaluation.
105
+
106
+ > **Note:** `$('/path')` is just syntactic sugar for `indirect('/path')`.
107
+
108
+ ```javascript
109
+ { button: {
110
+ class: "btn btn-equals",
111
+ onclick: =set(/c, {
112
+ display: if(eq(/c/op, ""),
113
+ /c/display, // No op? Do nothing.
114
+ calc(concat("$('/c/prev') ", /c/op, " $('/c/display')"))
115
+ ),
116
+ expr: concat(/c/expr, " ", /c/display, " ="),
117
+ prev: "",
118
+ op: "",
119
+ waiting: true
120
+ }),
121
+ children: ["="]
122
+ } }
123
+ ```
124
+
125
+ If `prev` is 8, `op` is `*`, and `display` is 5, `calc` receives the string `"8 * 5"` and evaluates it safely.
126
+
127
+ ## The Complete cDOMC Source
128
+
129
+ Here is the full source code for the calculator in `.cdomc` format:
130
+
131
+ > **Note:** cDOMC (compressed cDOM) supports comments and does not require quoting property names, making it more readable and maintainable than strict JSON. Lightview also supports parsing cDOM from regular JSON with quotes, so you can use it with any JSON parser.
132
+
133
+ ```javascript
134
+ // Pure cDOM/JPRX Calculator - No custom JavaScript helpers needed!
135
+ // Uses only core JPRX helpers:
136
+ // - state, set: state management
137
+ // - if, eq: conditionals
138
+ // - concat, contains: string operations
139
+ // - negate, toPercent: math operations
140
+ // - calc with $(): expression evaluation
141
+ {
142
+ div: {
143
+ class: "calculator",
144
+ onmount: =state({
145
+ display: "0",
146
+ expr: "",
147
+ prev: "",
148
+ op: "",
149
+ waiting: false
150
+ }, { name: "c", schema: "polymorphic", scope: $this }),
151
+ children: [
152
+ // Display area
153
+ {
154
+ div: {
155
+ class: "display",
156
+ children: [
157
+ { div: { class: "expression", children: [=/c/expr] } },
158
+ { div: { class: "result", children: [=/c/display] } }
159
+ ]
160
+ }
161
+ },
162
+ // Button grid
163
+ {
164
+ div: {
165
+ class: "buttons",
166
+ children: [
167
+ // Row 1: AC, ±, %, ÷
168
+ { button: { class: "btn btn-clear", onclick: =set(/c, { display: "0", expr: "", prev: "", op: "", waiting: false }), children: ["AC"] } },
169
+ { button: { class: "btn btn-function", onclick: =set(/c, { display: negate(/c/display), waiting: true, expr: "" }), children: ["±"] } },
170
+ { button: { class: "btn btn-function", onclick: =set(/c, { display: toPercent(/c/display), waiting: true, expr: "" }), children: ["%"] } },
171
+ { button: { class: "btn btn-operator", onclick: =set(/c, { prev: indirect(/c/display), expr: concat(/c/display, " ÷"), op: "/", waiting: true }), children: ["÷"] } },
172
+
173
+ // Row 2: 7, 8, 9, ×
174
+ { button: { id: "7", 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: ["7"] } },
175
+ { button: { id: "8", 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: ["8"] } },
176
+ { button: { id: "9", 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: ["9"] } },
177
+ { button: { class: "btn btn-operator", onclick: =set(/c, { prev: indirect(/c/display), expr: concat(/c/display, " ×"), op: "*", waiting: true }), children: ["×"] } },
178
+
179
+ // Row 3: 4, 5, 6, −
180
+ { button: { id: "4", 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: ["4"] } },
181
+ { button: { id: "5", 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: ["5"] } },
182
+ { button: { id: "6", 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: ["6"] } },
183
+ { button: { class: "btn btn-operator", onclick: =set(/c, { prev: indirect(/c/display), expr: concat(/c/display, " −"), op: "-", waiting: true }), children: ["−"] } },
184
+
185
+ // Row 4: 1, 2, 3, +
186
+ { 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: ["1"] } },
187
+ { 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: ["2"] } },
188
+ { 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: ["3"] } },
189
+ { button: { class: "btn btn-operator", onclick: =set(/c, { prev: indirect(/c/display), expr: concat(/c/display, " +"), op: "+", waiting: true }), children: ["+"] } },
190
+
191
+ // Row 5: 0, ., =
192
+ { button: { class: "btn btn-number btn-wide", onclick: =set(/c, { display: if(/c/waiting, "0", if(eq(/c/display, "0"), "0", concat(/c/display, "0"))), waiting: false }), children: ["0"] } },
193
+ { 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: ["."] } },
194
+ { 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: ["="] } }
195
+ ]
196
+ }
197
+ },
198
+ // Branding
199
+ {
200
+ div: {
201
+ class: "branding",
202
+ children: [
203
+ { span: { children: ["Built with ", { a: { href: "https://github.com/anywhichway/lightview", target: "_blank", children: ["Lightview"] } }, " cDOM • No custom JS!"] } }
204
+ ]
205
+ }
206
+ }
207
+ ]
208
+ }
209
+ }
210
+ ```
211
+
212
+ ## Loading cDOM via Hypermedia
213
+
214
+ Lightview supports loading cDOM content from external files using its hypermedia capability. Simply add a `src` attribute to any element, and Lightview will fetch and hydrate the cDOM content:
215
+
216
+ ```html
217
+ <!DOCTYPE html>
218
+ <html lang="en">
219
+
220
+ <head>
221
+ <meta charset="UTF-8">
222
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
223
+ <meta name="description"
224
+ content="A beautiful calculator built with Lightview cDOM and JPRX reactive expressions - no custom JavaScript!">
225
+ <title>Calculator | Lightview cDOM</title>
226
+ <link rel="stylesheet" href="calculator.css">
227
+ <!-- Load Lightview scripts -->
228
+ <script src="/lightview.js"></script> <!-- DOM as JSON and reactivity support -->
229
+ <script src="/lightview-x.js"></script> <!-- hypermedia support -->
230
+ <script src="/lightview-cdom.js"></script> <-- cDOM/JPRX support -->
231
+ </head>
232
+
233
+ <body>
234
+ <!-- The calculator cDOM is loaded via Lightview's hypermedia src attribute -->
235
+ <div id="app" src="./calculator.cdomc"></div>
236
+ </body>
237
+
238
+ </html>
239
+ ```
240
+
241
+ The `src` attribute works like an HTML `<img>` or `<script>` tag—Lightview automatically fetches the `.cdomc` file, parses it, and renders the reactive content into the target element. This approach:
242
+
243
+ 1. **Separates concerns:** Your HTML remains minimal while cDOM handles the component logic.
244
+ 2. **Enables caching:** The browser can cache `.cdomc` files like any other static asset.
245
+ 3. **Supports composition:** You can load different cDOM components into different parts of your page.
246
+
247
+ ## Why Build This Way?
248
+
249
+ You might look at `concat("$('/c/prev') ...")` and ask: *Why in the world wouldn't you just write `parseFloat(prev) + parseFloat(curr)`?*
250
+
251
+ If you are a human writing code for yourself? You probably should. Lightview supports standard JS handlers for exactly that reason.
252
+
253
+ But if you are building infrastructure for **AI Agents**, the calculus changes. Sticking to a declarative, JSON-based path offers things raw code can't:
254
+
255
+ 1. **Sandboxing:** It executes in a controlled environment. The logic can't access `window`, make global fetch requests, or execute arbitrary secondary code. This makes it safe to "hot swap" UI logic generated by an LLM in real-time.
256
+ 2. **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.
257
+ 3. **Mental Model:** It forces a clear separation between state transformations and view structure, which is exactly how LLMs reason best.
258
+
259
+ This calculator proves that "declarative" doesn't have to mean "dumb." With the right primitives—state, conditionals, and path-based referencing—you can build rich, complex interactions without ever leaving the data structure.
260
+
261
+ > **A Note on Authorship:** This calculator wasn't hand-crafted. It was generated by **Claude Opus** after reading the [cDOM documentation](https://lightview.dev/docs/cdom). The fact that an AI could produce a robust, edge-case-handling calculator purely from documentation proves the point: cDOM and JPRX aren't just new syntax. They are a protocol for human-machine collaboration.
262
+
263
+ ## The Bigger Picture
264
+
265
+ This series isn't just about a new library. It's about finding the right abstraction layer for the AI age.
266
+
267
+ **In [Part 1](https://hackernoon.com/the-future-of-ai-generated-ui-why-i-built-cdom-and-jprx)**, we looked at the security risks of letting LLMs write raw scripts and introduced the "Data as UI" philosophy.
268
+
269
+ **In this article**, we proved that "Data as UI" doesn't mean "dumb UI." We handled state, context, data snapshots, and math without executing a single line of imperative code.
270
+
271
+ cDOM defines structure. JPRX defines behavior. It’s reactivity without the compilation and UI without the security risks.
272
+
273
+ ### Try It Yourself
274
+
275
+ The complete calculator is available at:
276
+
277
+ **[Live Demo](https://lightview.dev/docs/calculator.html)**
278
+
279
+ **[Source Code](https://github.com/anywhichway/lightview/blob/main/docs/calculator.html)**
280
+
281
+ ---
282
+
283
+ *Lightview is an open-source reactive UI library. Learn more at [github.com/anywhichway/lightview](https://github.com/anywhichway/lightview).*