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,290 @@
1
+ # Building a Fully Functional Calculator with Zero JavaScript Logic
2
+
3
+ *How cDOM and JPRX let you build complex UIs using pure declarative JSON*
4
+
5
+ ---
6
+
7
+ Last time, I showed how [cDOM lets you build simple reactive UIs with JSON](https://medium.com/@anywhichway/building-reactive-uis-with-json-no-javascript-required-b7b1c4a45321). But let's be honest: counters and shopping carts are easy. Almost any framework looks good when the logic is trivial.
8
+
9
+ To see if a declarative, JSON-based approach actually holds up in the real world, you need a problem with messy state, edge cases, and history.
10
+
11
+ You need a calculator.
12
+
13
+ Calculators are tricky because they're full of hidden complexity:
14
+ - Managing input modes (are we typing a new number or replacing the result?)
15
+ - Chaining operations (what happens when you hit `+` then `-`?)
16
+ - DOM interaction (how do we avoid writing 10 separate handlers for buttons 0-9?)
17
+
18
+ This is how I built a fully functional, iOS-style calculator using **zero custom JavaScript functions**—just declarative cDOM and JPRX expressions.
19
+
20
+ ## The Result
21
+
22
+ Here is what we are building. A clean, glassmorphic interface where every interaction—from typing decimals to chaining operations—is handled by the expression language itself.
23
+
24
+ ![Calculator Screenshot](calculator-screenshot.png)
25
+
26
+ ## The State Machine
27
+
28
+ First, we need to model the calculator's brain. It's not just a "current number"; it's a small state machine.
29
+
30
+ We define this state right in the JSON when the component mounts:
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
+ In a typical framework, you'd loop over an array of numbers to generate buttons, or write a generic `handleNumberClick` function to avoid repetitive code.
47
+
48
+ In JPRX, we can just use the DOM itself.
49
+
50
+ By assigning an `id` to each button, we can write **one single logic expression** that works for every number key. All we have to do is 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 the breakdown of that expression:
71
+ 1. **Are we waiting for new input?** (e.g., after hitting `+`) → Replace the display with the button's ID.
72
+ 2. **Is the current display "0"?** → Replace it (to avoid "07").
73
+ 3. **Otherwise:** → Append the button's ID to the current display string.
74
+
75
+ This works identically for every number button. No loops, no external helper functions.
76
+
77
+ ## Operators and "Indirect" Values
78
+
79
+ When you click `+` or `×`, the calculator needs to "freeze" the current number so you can type the next one. This brings up an interesting challenge in reactivity.
80
+
81
+ If we just saved a reference to `/c/display`, our stored value would keep changing as the user types. We need a snapshot.
82
+
83
+ Excel solves this with the `INDIRECT` function. JPRX does the same:
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
+ Evaluating the math is the final piece. We need a way to say: *"Take the previous value, apply the operator to the current display value, and give me the result."*
103
+
104
+ We use the `calc()` helper for this. To make it readable, `calc` supports a convenient shorthand: `$('path')`.
105
+
106
+ > **Note:** `$('/path')` is just syntactic sugar for `indirect('/path')` inside calculation strings. It tells the parser to fetch the value before doing the math.
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.
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 not just write a JavaScript function?*
250
+
251
+ And for many cases, you should! Lightview allows standard JS handlers whenever you want them. But sticking to the declarative path has distinct advantages:
252
+
253
+ 1. **Portability:** This entire UI—logic and all—is just JSON (well, JPRXC). It can be sent from a server, stored in a DB, or generated by an LLM.
254
+ 2. **Sandboxing:** It executes in a controlled environment. The logic can't access `window`, make global fetch requests (unless allowed), or mess with the DOM outside its scope.
255
+ 3. **Mental Model:** It forces you to think about the UI as a series of **state transformations** rather than imperative steps.
256
+
257
+ This calculator proves that "declarative" doesn't have to mean "dumb." With the right primitives—state, conditionals, and referencing—you can build rich, complex interactions without ever leaving the data structure.
258
+
259
+ > **A note on authorship:** This calculator wasn't hand-crafted by a human developer. It was generated by **Claude Opus** (Anthropic's LLM) after reading the [cDOM documentation](https://lightview.dev/docs/cdom) and the [JPRX npm package docs](https://www.npmjs.com/package/jprx). The fact that an AI could produce a fully functional, edge-case-handling calculator purely from documentation is exactly the point: cDOM and JPRX are formats that both humans and machines can reason about.
260
+
261
+ ## The Bigger Picture
262
+
263
+ This article is the second in a series exploring how far we can push declarative, JSON-based UI development by both humans and LLMs.
264
+
265
+ **In [Part 1](https://medium.com/@anywhichway/building-reactive-uis-with-json-no-javascript-required-b7b1c4a45321)**, we introduced **cDOM**, a structural replacement for HTML and JSX. We focused on:
266
+ * Defining the UI hierarchy using standard JSON objects.
267
+ * Connecting to servers and LLMs with `fetch()` and `mount()`.
268
+ * Building a live dashboard that sorts and filters without server round-trips.
269
+ * *Key Concepts:* Reactive paths, operators (`++`, `--`), explosion operator (`...`), `$this`/`$event`, scoped state
270
+ * *Helpers:* `state()`, `set()`, `fetch()`, `mount()`, `move()`, `sum()`, `filter()`, `map()`, `sort()`, `push()`, `bind()`, `if()`, `formatDate()`, `formatNumber()`
271
+
272
+ **In Part 2 (this article)**, we tackled the missing piece: complex application logic. With **JPRX**, we proved that you don't need imperative JavaScript functions to handle:
273
+ * **State Machines:** `if()`, `eq()`, `set()`, `state()`
274
+ * **Context Awareness:** `$this`, `$event`
275
+ * **Data Capture:** `indirect()`, `$()`
276
+ * **Logic & Math:** `calc()`, `concat()`, `contains()`, `negate()`, `toPercent()`
277
+
278
+ Together, cDOM and JPRX offer a cohesive system: cDOM defines the *structure*, while JPRX defines the *behavior*. You get the reactivity of modern signals and the component model of a Virtual DOM, but in a portable format that requires no compilation, no build steps, and no context switching between languages. It's just data, all the way down.
279
+
280
+ ### Try It Yourself
281
+
282
+ The complete calculator is available at:
283
+
284
+ **[Live Demo](https://lightview.dev/docs/calculator.html)**
285
+
286
+ **[Source Code](https://github.com/anywhichway/lightview/blob/main/docs/calculator.html)**
287
+
288
+ ---
289
+
290
+ *Lightview is an open-source reactive UI library. Learn more at [github.com/anywhichway/lightview](https://github.com/anywhichway/lightview).*
@@ -0,0 +1,236 @@
1
+ # Building Reactive UIs with JSON — No JavaScript Required
2
+
3
+ *A declarative approach that lets both humans and LLMs create rich, interactive user interfaces using nothing but JSON.*
4
+
5
+ ---
6
+
7
+ ## The Power of JSON-Based UIs
8
+
9
+ What if you could build fully reactive, interactive user interfaces without writing a single line of JavaScript? What if the same format that's trivial for developers to write is equally easy for AI agents to generate?
10
+
11
+ This is the promise of cDOM (Computational DOM) and its expression language, JPRX (JSON Pointer Reactive eXpressions).
12
+
13
+ ## Your First Reactive UI: A Counter in Pure JSON
14
+
15
+ Here's a complete, working interactive counter — no JavaScript code required:
16
+
17
+ ```json
18
+ {
19
+ "div": {
20
+ "onmount": "=state({ count: 0 }, { name: 'local', scope: $this })",
21
+ "children": [
22
+ { "p": ["Count: ", "=local/count"] },
23
+ { "button": { "onclick": "=local/count++", "children": ["+"] } }
24
+ ]
25
+ }
26
+ }
27
+ ```
28
+
29
+ That's the entire application. Let's break down what's happening:
30
+
31
+ - `state({ count: 0 })` initializes reactive state scoped to this element
32
+ - `=local/count` is a live binding—the text updates automatically when count changes
33
+ - `=local/count++` increments the counter directly when clicked
34
+ - The UI reacts instantly in the browser, no server needed
35
+
36
+ This is the spreadsheet paradigm applied to user interfaces. Just like writing `=SUM(A1:A10)` in Excel, you define the relationships and the system handles the updates.
37
+
38
+ ## Why This Matters for Humans
39
+
40
+ As a developer, you're freed from the boilerplate of event handlers, state management, and manual DOM updates. Instead of imperative code that says "when this happens, do these five things," you write declarative expressions that say "this value always equals that calculation."
41
+
42
+ Compare traditional JavaScript:
43
+
44
+ ```javascript
45
+ let count = 0;
46
+ const countDisplay = document.getElementById('count');
47
+ const button = document.getElementById('increment');
48
+ button.addEventListener('click', () => {
49
+ count++;
50
+ countDisplay.textContent = `Count: ${count}`;
51
+ });
52
+ ```
53
+
54
+ cDOM/JPRX:
55
+
56
+ ```json
57
+ {
58
+ "button": {
59
+ "onclick": "=/local/count++",
60
+ "children": ["Clicks:", "=/local/count"]
61
+ }
62
+ }
63
+ ```
64
+
65
+ ## Why This Matters for LLMs
66
+
67
+ For AI agents, cDOM provides a safe, structured format for generating UIs:
68
+
69
+ - **Context-Free Grammar:** No closures, no callbacks, no complex JavaScript patterns. An LLM generates JPRX as easily as it generates natural language.
70
+ - **Safe by Design:** No access to eval, arbitrary code execution, or unrestricted DOM access. The application developer controls what's possible through a catalog of helper functions.
71
+ - **Declarative:** Less chance to get lost with slop generation in the freedom of the JavaScript language.
72
+ - **Streamable:** Components are self-describing and self-contained. An LLM can stream UI updates one piece at a time without regenerating entire pages.
73
+
74
+ ## Connecting to Servers and LLMs
75
+
76
+ When you need server interaction, it's just another expression. Want to notify an LLM when a button is clicked?
77
+
78
+ ```json
79
+ {
80
+ "button": {
81
+ "onclick": "=fetch('/api/notify', { method: 'POST', body: $event })",
82
+ "children": ["Notify LLM"]
83
+ }
84
+ }
85
+ ```
86
+
87
+ The `=fetch` helper automatically:
88
+ - Sends a POST request to your endpoint
89
+ - Stringifies JSON bodies and sets the correct Content-Type
90
+ - Lets your backend (or LLM) process the event and respond
91
+
92
+ This is event registration, not constant chatter. The LLM only hears about the interactions you choose to wire up — no need to notify it of every mouse move or keystroke.
93
+
94
+ ## LLMs That Push UI Updates
95
+
96
+ Here's where things get powerful. What if the LLM wants to add a new component to the page?
97
+
98
+ ```json
99
+ {
100
+ "button": {
101
+ "onclick": "=mount('/api/get-widget')",
102
+ "children": ["Load Widget"]
103
+ }
104
+ }
105
+ ```
106
+
107
+ When clicked, `mount` fetches JSON from your endpoint and injects it as a live, reactive component. The LLM can respond with:
108
+
109
+ ```json
110
+ {
111
+ "div": {
112
+ "id": "weather-widget",
113
+ "onmount": "=move('#dashboard-sidebar', 'afterbegin')",
114
+ "children": ["Sunny, 75°F"]
115
+ }
116
+ }
117
+ ```
118
+
119
+ The widget automatically:
120
+ - Mounts safely to the document
121
+ - Moves itself to the specified location (`#dashboard-sidebar`)
122
+ - Replaces any existing widget with the same ID (idempotent updates)
123
+
124
+ The LLM doesn't need to understand your entire page structure — it just pushes components that know where they belong.
125
+
126
+ ## Three Levels of Capability
127
+
128
+ | Level | Use Case | Example Helper | Server Involved? |
129
+ |-------|----------|----------------|------------------|
130
+ | 1 | Client-only reactivity | `=state`, `=++`, `=sum()` | No |
131
+ | 2 | Notify server of actions | `=fetch` | Yes |
132
+ | 3 | Server pushes new UI | `=mount`, `=move` | Yes |
133
+
134
+ This layered approach means you can build:
135
+ - Fully offline apps that handle all interactions locally (Level 1)
136
+ - Hybrid apps where servers are notified selectively (Level 2)
137
+ - Agent-driven apps where LLMs control the UI in real-time (Level 3)
138
+
139
+ All using the same JSON format.
140
+
141
+ ## JPRX: Excel Formulas for UI
142
+
143
+ JPRX expressions are designed to feel familiar if you've ever used spreadsheets:
144
+
145
+ - **Reactive paths:** `=app/user/name` automatically updates when the data changes
146
+ - **Operators:** `=++count`, `=count--`, `=!!enabled`
147
+ - **Helper functions:** Over 100 functions covering math, strings, arrays, dates, and more
148
+ - `=sum(/cart/items...price)` - Sum all item prices
149
+ - `=filter(/users, age > 18)` - Filter array by condition
150
+ - `=formatDate(/order/date, 'MM/DD/YYYY')` - Format dates
151
+ - `=if(total > 100, 'Bulk', 'Regular')` - Conditional logic
152
+ - **State mutations:** `=set(/app/user/name, 'John')`, `=push(/cart/items, $newItem)`
153
+ - **Relative paths:** Use `../` to navigate up context hierarchies
154
+
155
+ If you can write `=SUM(A1:A10)` in Excel, you can write reactive UIs with JPRX.
156
+
157
+ ## A Real-World Example: Live Dashboard
158
+
159
+ Imagine a user asks an LLM:
160
+
161
+ > "Show me my sales performance for each region in Q4, highlight anything below target, and let me sort the results."
162
+
163
+ The LLM responds not with paragraphs of text, but with a live, interactive dashboard:
164
+
165
+ ```json
166
+ {
167
+ "div": {
168
+ "id": "sales-dashboard",
169
+ "onmount": "=fetch('/api/sales/q4').then(data => state({ data, sort: 'name' }, 'sales'))",
170
+ "children": [
171
+ { "h2": ["Q4 Sales Performance"] },
172
+ {
173
+ "table": {
174
+ "children": [
175
+ { "thead": [
176
+ { "tr": [
177
+ { "th": { "children": ["Region"], "onclick": "=set('sales/sort', 'name')" } },
178
+ { "th": { "children": ["Revenue"], "onclick": "=set('sales/sort', 'revenue')" } },
179
+ { "th": { "children": ["Target"], "onclick": "=set('sales/sort', 'target')" } },
180
+ { "th": { "children": ["Status"], "onclick": "=set('sales/sort', 'status')" } }
181
+ ]}
182
+ ]},
183
+ { "tbody": {
184
+ "children": "=map(sort(sales.data, (a, b) => a[sales.sort] > b[sales.sort] ? 1 : -1), region => ({ tr: { class: region.revenue < region.target ? 'below-target' : '', children: [ { td: [region.name] }, { td: ['$', formatNumber(region.revenue)] }, { td: ['$', formatNumber(region.target)] }, { td: [region.revenue >= region.target ? '✓' : '⚠'] } ] } }))"
185
+ }}
186
+ ]
187
+ }
188
+ }
189
+ ]
190
+ }
191
+ }
192
+ ```
193
+
194
+ This entire dashboard is:
195
+ - **Reactive:** All calculations update automatically
196
+ - **Self-contained:** No separate JavaScript files
197
+ - **Safe:** Uses only approved helper functions
198
+ - **Readable:** Both humans and LLMs can understand it
199
+
200
+ When the user sorts, no round trip to the server is required.
201
+
202
+ ## The Vision: Agent-First Applications
203
+
204
+ The next wave of applications won't be chatbots with UI bolted on. They'll be intelligent systems where conversation and application merge seamlessly.
205
+
206
+ A user shouldn't have to parse walls of text to understand complex data. An agent should be able to respond with:
207
+ - A live chart that filters
208
+ - A table that sorts
209
+ - A summary that recalculates
210
+ - All instantly reactive, no page reloads
211
+
212
+ cDOM makes this possible by giving both humans and LLMs a shared language for describing not just what the UI looks like, but how it behaves.
213
+
214
+ ## Getting Started
215
+
216
+ cDOM and JPRX are part of Lightview, a reactive UI library designed for both human developers and AI agents.
217
+
218
+ - **Documentation:** [lightview.dev](https://lightview.dev)
219
+ - **GitHub:** [github.com/anywhichway/lightview](https://github.com/anywhichway/lightview)
220
+ - **npm:**
221
+ - Full library: `lightview`
222
+ - Standalone parser: `jprx`
223
+
224
+ ---
225
+
226
+ *The Spreadsheet Moment for UI*
227
+
228
+ Spreadsheets democratized computation. You didn't need to be a programmer to define that `C3 = A1 + B2` and watch it update automatically.
229
+
230
+ cDOM aims to be that moment for user interfaces. A format where anyone — human or AI — can declare:
231
+ - This text shows the count
232
+ - This button increments it
233
+ - This chart sums the sales
234
+ - This table filters by region
235
+
236
+ And the system handles the rest. No JavaScript required.
@@ -0,0 +1,77 @@
1
+ // Pure cDOM/JPRX Calculator - No custom JavaScript helpers needed!
2
+ // Uses only core JPRX helpers:
3
+ // - state, =: state management
4
+ // - if, eq: conditionals
5
+ // - concat, contains: string operations
6
+ // - negate, toPercent: math operations
7
+ // - calc with $(): expression evaluation
8
+ // - XPath (#../@attr): DOM navigation for DRY button definitions
9
+ {
10
+ div: {
11
+ class: "calculator",
12
+ onmount: =state({
13
+ display: "0",
14
+ expr: "",
15
+ prev: "",
16
+ op: "",
17
+ waiting: false
18
+ }, { name: "c", schema: "polymorphic", scope: $this }),
19
+ children: [
20
+ // Display area
21
+ {
22
+ div: {
23
+ class: "display",
24
+ children: [
25
+ { div: { class: "expression", children: [=/c/expr] } },
26
+ { div: { class: "result", children: [=/c/display] } }
27
+ ]
28
+ }
29
+ },
30
+ // Button grid
31
+ {
32
+ div: {
33
+ class: "buttons",
34
+ children: [
35
+ // Row 1: AC, ±, %, ÷
36
+ { button: { class: "btn btn-clear", onclick: =/c = { display: "0", expr: "", prev: "", op: "", waiting: false }, children: ["AC"] } },
37
+ { button: { class: "btn btn-function", onclick: =/c = { display: negate(/c/display), waiting: true, expr: "" }, children: ["±"] } },
38
+ { button: { class: "btn btn-function", onclick: =/c = { display: toPercent(/c/display), waiting: true, expr: "" }, children: ["%"] } },
39
+ { button: { class: "btn btn-operator", onclick: =/c = { prev: indirect(/c/display), expr: concat(/c/display, " ÷"), op: "/", waiting: true }, children: ["÷"] } },
40
+
41
+ // Row 2: 7, 8, 9, ×
42
+ { 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] } },
43
+ { 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] } },
44
+ { 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] } },
45
+ { button: { class: "btn btn-operator", onclick: =/c = { prev: indirect(/c/display), expr: concat(/c/display, " ×"), op: "*", waiting: true }, children: ["×"] } },
46
+
47
+ // Row 3: 4, 5, 6, −
48
+ { 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] } },
49
+ { 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] } },
50
+ { 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] } },
51
+ { button: { class: "btn btn-operator", onclick: =/c = { prev: indirect(/c/display), expr: concat(/c/display, " −"), op: "-", waiting: true }, children: ["−"] } },
52
+
53
+ // Row 4: 1, 2, 3, +, use set and eq just to demonstrate equivalence with =
54
+ { 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] } },
55
+ { 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] } },
56
+ { 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] } },
57
+ { button: { class: "btn btn-operator", onclick: =set(/c, { prev: indirect(/c/display), expr: concat(/c/display, " +"), op: "+", waiting: true }), children: ["+"] } },
58
+
59
+ // Row 5: 0, ., =
60
+ { 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"] } },
61
+ { 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: ["."] } },
62
+ { 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: ["="] } }
63
+ ]
64
+ }
65
+ },
66
+ // Branding
67
+ {
68
+ div: {
69
+ class: "branding",
70
+ children: [
71
+ { span: { children: ["Built with ", { a: { href: "https://github.com/anywhichway/lightview", target: "_blank", children: ["Lightview"] } }, " cDOM • No custom JS!"] } }
72
+ ]
73
+ }
74
+ }
75
+ ]
76
+ }
77
+ }