UNPKG

lightview

Version:

A reactive UI library with features of Bau, Juris, and HTMX plus safe LLM UI generation

1,182 lines 51.5 kB
<script src="/lightview-router.js?base=/index.html"></script>

<style>
    /* Syntax Tabs */
    .syntax-tabs {
        display: inline-flex;
        gap: 0.25rem;
        padding: 0.25rem;
        background: #f1f5f9;
        border-radius: 8px;
        border: 1px solid #e2e8f0;
        margin-bottom: 1rem;
    }

    [data-theme="dark"] .syntax-tabs {
        background: #1e293b;
        border-color: #334155;
    }

    .syntax-tab {
        padding: 0.5rem 1rem;
        font-size: 0.875rem;
        font-weight: 500;
        border: none;
        background: transparent;
        border-radius: 6px;
        cursor: pointer;
        color: #64748b;
        transition: all 0.2s;
    }

    .syntax-tab:hover {
        background: #ffffff;
        color: #1e293b;
    }

    [data-theme="dark"] .syntax-tab:hover {
        background: #334155;
        color: #f1f5f9;
    }

    .syntax-tab-active {
        background: #ffffff !important;
        color: #6366f1 !important;
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
    }

    [data-theme="dark"] .syntax-tab-active {
        background: #0f172a !important;
        color: #818cf8 !important;
    }
</style>

<script>
    globalThis.switchCDOMTab = (tabId) => {
        const tabs = ['jprx', 'cdomc', 'operators'];
        tabs.forEach(t => {
            const btn = document.getElementById(`tab-btn-${t}`);
            const pane = document.getElementById(`pane-${t}`);
            if (t === tabId) {
                btn.classList.add('syntax-tab-active');
                pane.style.display = 'block';
            } else {
                btn.classList.remove('syntax-tab-active');
                pane.style.display = 'none';
            }
        });
    };
</script>

<div class="docs-layout">
    <aside class="docs-sidebar" src="/docs/cdom-nav.html"></aside>

    <main class="docs-content">
        <h1>cDOM (Computational DOM)</h1>
        <p class="text-secondary" style="font-size: 1.125rem;">
            A declarative, expression-based way to build reactive UIs.
        </p>

        <div class="experimental-notice"
            style="margin: 1.5rem 0; padding: 1.25rem; border-radius: var(--site-radius); background: var(--site-accent-light); border: 1px solid var(--site-warning); color: var(--site-text);">
            <div style="display: flex; gap: 0.75rem; align-items: flex-start;">
                <span style="font-size: 1.5rem;">⚠️</span>
                <div>
                    <strong style="display: block; margin-bottom: 0.25rem; font-size: 1.1rem;">Experimental
                        Feature</strong>
                    <p style="margin: 0; font-size: 0.95rem; opacity: 0.9;">
                        cDOM and JPRX are currently in an <strong>experimental</strong> phase. The expression syntax,
                        helper functions, and integration patterns are subject to change as we continue to evolve the
                        library. Use with caution in production environments.
                    </p>
                    <p
                        style="margin: 0.75rem 0 0 0; padding-top: 0.75rem; border-top: 1px solid var(--site-border); font-size: 0.95rem; opacity: 0.9;">
                        <strong>v2.5.0 Syntax Update:</strong> Starting with Lightview v2.5.0 and JPRX v1.4.0,
                        expressions use a new wrapper syntax:
                        <code
                            style="background: var(--site-bg-secondary); padding: 0.125rem 0.375rem; border-radius: 3px;">=(expr)</code>
                        for JPRX and
                        <code
                            style="background: var(--site-bg-secondary); padding: 0.125rem 0.375rem; border-radius: 3px;">#(xpath)</code>
                        for XPath.
                        The legacy prefix-only syntax (e.g., <code
                            style="background: var(--site-bg-secondary); padding: 0.125rem 0.375rem; border-radius: 3px;">=path</code>)
                        will be <strong>fully deprecated after March 31, 2026</strong>.
                    </p>
                </div>
            </div>
        </div>

        <!-- ===== OVERVIEW ===== -->
        <h2 id="overview">Overview</h2>
        <p>
            The <strong>Computational DOM (cDOM)</strong> is a way to describe user interfaces using reactive
            expressions
            that feel as natural as spreadsheet formulas. Instead of writing JavaScript logic to update your UI,
            you define the <em>relationships</em> between your data and your elements.
        </p>
        <p>
            cDOM was designed so that both developers and LLMs can generate safe user interfaces that humans
            can interact with across platforms.
        </p>
        <p>
            cDOM uses <strong>JPRX (JSON Pointer Reactive eXpressions)</strong> as its expression language. JPRX
            extends <a href="https://www.rfc-editor.org/rfc/rfc6901" target="_blank">JSON Pointer (RFC 6901)</a>
            with reactivity, relative paths, and helper functions. cDOM also supports <strong>standard XPath</strong>
            for powerful DOM navigation during element construction. Together with deep integration for
            <a href="https://json-schema.org/" target="_blank">JSON Schema</a> (Standard Draft 7+), cDOM provides
            industrial-strength data validation and automatic type coercion.
        </p>

        <!-- ===== SIMPLE EXAMPLE ===== -->
        <h2 id="simple-example">Simple Example</h2>
        <p>Here's a simple counter in cDOM. Notice how the UI is purely declarative — no JavaScript logic:</p>
        <div id="simple-counter-demo">
            <pre><script>
                examplify(document.currentScript.nextElementSibling, {
                    at: document.currentScript.parentElement,
                    scripts: ['/lightview.js', '/lightview-x.js', '/lightview-cdom.js'],
                    type: 'module',
                    height: '200px',
                    autoRun: true,
                    controls: false
                });
            </script><code>await import('/lightview-cdom.js');
const { parseJPRX, hydrate } = globalThis.LightviewCDOM;
const { $ } = Lightview;

const cdom = `{
    "div": {
        "id": "Counter",
        "onmount": "=state({ count: 0 }, { name: 'local', schema: 'auto', scope: $this })",
        "children": [
            { "h2": "#(../../@id)" }, // XPath: text node -> h2 -> div
            { "p": ["Count: ", "=(/local/count)"] },
            { "button": { "onclick": "=(++/local/count)", "children": ["+"] } },
            { "button": { "onclick": "=(--/local/count)", "children": ["-"] } }
        ]
    }
}`;

$('#example').content(hydrate(parseJPRX(cdom)));</code></pre>
        </div>
        <p>
            This defines a counter with:
        </p>
        <ul>
            <li><code>onmount</code> — A Lightview lifecycle hook where we initialize state.</li>
            <li><code>#../../@id</code> — An XPath expression that extracts the <code>id</code> from the grandparent
                element.</li>
            <li><code>=state(...)</code> — An initializer that creates local reactive state.</li>
            <li><code>scope: $this</code> — Explicitly attaches the state to the current element.</li>
            <li><code>=/local/count</code> — A path that reactively displays the count value.</li>
        </ul>
        <p style="font-size: 0.95rem; font-style: italic; color: var(--site-text-secondary);">
            <strong>Note:</strong> In cDOM, expressions use a wrapper syntax for clarity: <code>#(xpath)</code> for
            <strong>XPath</strong> expressions that
            navigate and extract data from the DOM, and <code>=(expr)</code> for <strong>JPRX</strong>
            expressions that navigate or apply functions to reactive state. Legacy syntax without parentheses is still
            supported.
        </p>
        <p>
            The UI automatically updates whenever <code>count</code> changes — no manual DOM manipulation required. It
            also uses XPath to navigate the DOM structure during construction.
        </p>

        <!-- ===== ADVANTAGES ===== -->
        <h2 id="advantages">Advantages</h2>
        <div class="feature-grid" style="margin: 2rem 0;">
            <div class="feature-card">
                <h3 class="feature-title">🛡️ Enhanced Security</h3>
                <p>
                    cDOM strictly avoids <code>eval()</code> and direct HTML injection. By using a custom
                    high-performance parser and a registry of pre-defined helper functions, it provides a safe sandbox
                    for dynamic content, making it highly resistant to XSS attacks.
                </p>
            </div>
            <div class="feature-card">
                <h3 class="feature-title">🤖 LLM Friendly</h3>
                <p>
                    Large Language Models excel at generating structured data and formulaic expressions. cDOM's
                    declarative nature and concise syntax make it far easier for AI to generate correct,
                    bug-free UI components compared to traditional JavaScript-heavy frameworks.
                </p>
            </div>
        </div>

        <!-- ===== XPATH NAVIGATION ===== -->
        <h2 id="xpath-navigation">Using XPath</h2>
        <p>
            cDOM allows elements to navigate and reference the DOM structure during construction using standard
            <strong>XPath</strong>. This is strictly a <strong>cDOM feature</strong> (not JPRX) used for structural
            navigation.
        </p>
        <p>
            XPath is incredibly useful for keeping your definitions <strong>DRY (Don't Repeat Yourself)</strong> by
            referencing existing attributes instead of repeating values.
        </p>
        <div id="xpath-demo">
            <pre><script>
                examplify(document.currentScript.nextElementSibling, {
                    at: document.currentScript.parentElement,
                    scripts: ['/lightview.js', '/lightview-x.js', '/lightview-cdom.js'],
                    type: 'module',
                    height: '180px',
                    autoRun: true,
                    controls: false
                });
            </script><code>await import('/lightview-cdom.js');
const { parseJPRX, hydrate } = globalThis.LightviewCDOM;
const { $ } = Lightview;

const cdom = `{
    // The cdom below uses a compressed syntax. It does not require quotes 
    // around properties or JPRX/XPath values and supports comments. 
    // It is JSON-like, but not JSON.
    div: {
        id: "profile-container",
        class: "card",
        data-theme: "dark",
        children: [
            { h3: "User Profile" },
            { button: { 
                id: "7", 
                // XPath #(../@id) gets the "7" from this button's id
                // XPath #(../../@id) gets "profile-container" from the g-parent div
                children: ["Button ", #(../@id), " in section ", #(../../@id)] 
            }}
        ]
    }
}`;

$('#example').content(hydrate(parseJPRX(cdom)));</code></pre>
        </div>
        <p>
            In the example above, the button's text is derived entirely from its own <code>id</code> and its
            parent's <code>id</code> using <code>#(../@id)</code> and <code>#(../../@id)</code>.
        </p>
        <p>
            For more details, see the <a href="/docs/cdom-xpath.html">Full XPath Documentation</a>.
        </p>

        <!-- ===== JPRX INTRODUCTION ===== -->
        <h2 id="JPRX">JPRX (JSON Pointer Reactive eXpressions)</h2>
        <p>
            <strong>JPRX</strong> is the expression language that powers cDOM. It extends
            <a href="https://www.rfc-editor.org/rfc/rfc6901" target="_blank">JSON Pointer (RFC 6901)</a>
            with reactivity, relative paths, and helper functions.
        </p>

        <h3 id="JPRX-delimiters">Expression Syntax</h3>
        <p>
            JPRX expressions use a <strong>wrapper syntax</strong> for unambiguous parsing. The recommended syntax is
            <code>=(expression)</code>:
        </p>
        <table class="api-table">
            <thead>
                <tr>
                    <th>Syntax</th>
                    <th>Purpose</th>
                    <th>Example</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td><code>=(/path)</code></td>
                    <td>Access a path in the global registry</td>
                    <td><code>=(/users/0/name)</code></td>
                </tr>
                <tr>
                    <td><code>=(function(...))</code></td>
                    <td>Call a helper function</td>
                    <td><code>=(sum(/items...price))</code></td>
                </tr>
                <tr>
                    <td><code>#(xpath)</code></td>
                    <td>XPath expression for DOM navigation</td>
                    <td><code>#(../../@id)</code></td>
                </tr>
            </tbody>
        </table>
        <p>
            The wrapper syntax <code>=()</code> and <code>#()</code> makes expressions unambiguous and eliminates
            conflicts with literal strings.
            Legacy syntax without parentheses (e.g., <code>=/path</code>) is still supported for backward compatibility.
        </p>

        <h3 id="JPRX-literal-strings">Literal Strings</h3>
        <p>
            With the new wrapper syntax, you no longer need escape sequences. Any string that doesn't match the wrapper
            pattern
            is treated as a literal:
        </p>
        <div class="code-block" style="margin-bottom: 1rem;">
            <pre><code>// JPRX expression (wrapped):
{ "p": "=(/user/name)" }  // Resolves the path /user/name

// Literal strings (no wrapper):
{ "p": "=E=mc²" }         // Renders as "=E=mc²"
{ "p": "=42" }            // Renders as "=42"
{ "p": "#ff0000" }        // Renders as "#ff0000" (hex color)</code></pre>
        </div>
        <p>
            The wrapper syntax eliminates ambiguity: <code>=(expr)</code> is always an expression, while strings without
            the wrapper pattern are always literals.
        </p>

        <h3 id="JPRX-anatomy">Anatomy of a Path</h3>
        <p>
            Inside a JPRX expression, paths follow JSON Pointer with these extensions:
        </p>
        <table class="api-table">
            <thead>
                <tr>
                    <th>Path</th>
                    <th>Description</th>
                    <th>Origin</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td><code>/users/0/name</code></td>
                    <td>Absolute path (property access)</td>
                    <td>JSON Pointer</td>
                </tr>
                <tr>
                    <td><code>/items[2]</code></td>
                    <td>Array index (bracket notation)</td>
                    <td>JPRX extension</td>
                </tr>
                <tr>
                    <td><code>./child</code></td>
                    <td>Relative to current context</td>
                    <td>JPRX extension</td>
                </tr>
                <tr>
                    <td><code>../sibling</code></td>
                    <td>Parent context access</td>
                    <td>JPRX extension</td>
                </tr>
                <tr>
                    <td><code>/items...price</code></td>
                    <td>Extract <code>price</code> from all items (explosion)</td>
                    <td>JPRX extension</td>
                </tr>
            </tbody>
        </table>

        <h4>Function Calls</h4>
        <p>
            Paths can contain function calls to transform data:
        </p>
        <div class="code-block">
            <pre><code>=currency(sum(map(filter(/orders, eq(_/status, 'paid')), _/total)...))</code></pre>
        </div>
        <ul>
            <li><strong>/orders</strong>: Access the <code>orders</code> collection (JSON Pointer)</li>
            <li><strong>filter(..., eq(...))</strong>: Keep only "paid" orders</li>
            <li><strong>map(..., _/total)</strong>: Extract the <code>total</code> from each order</li>
            <li><strong>...</strong>: Explode the array into individual arguments</li>
            <li><strong>sum(...)</strong>: Add up all the totals</li>
            <li><strong>=currency(...)</strong>: Format as currency string</li>
        </ul>

        <h3 id="JPRX-placeholders">Placeholders</h3>
        <table class="api-table">
            <thead>
                <tr>
                    <th>Placeholder</th>
                    <th>Description</th>
                    <th>Example</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td><code>_</code></td>
                    <td>Current item during iteration</td>
                    <td><code>=map(/items, _/name)</code></td>
                </tr>
                <tr>
                    <td><code>$event</code></td>
                    <td>Event object in handlers</td>
                    <td><code>=set(=/selected, $event/target/value)</code></td>
                </tr>
            </tbody>
        </table>

        <h3 id="JPRX-explosion">Explosion & Mapping (<code>...</code>)</h3>
        <p>
            The <code>...</code> operator is a powerful JPRX extension used for mapping properties from arrays and
            spreading array elements as function arguments.
        </p>

        <h4>Mapping & Auto-Explosion: <code>path...property</code></h4>
        <p>
            When used between a path to an array and a property name, it acts as a shorthand for mapping. It extracts
            the specified property from every object in the array. <strong>Inside a function call, it automatically
                spreads (explodes) the resulting values as individual arguments.</strong>
        </p>
        <div class="code-block" style="margin-bottom: 1.5rem;">
            <pre><code>// Correct: Maps 'price' and automatically spreads results as arguments to sum()
=sum(/cart/items...price)

// Mapping also works for direct display (returns an array)
{ p: ["Prices: ", =/cart/items...price] }</code></pre>
        </div>

        <h4>Argument Spreading: <code>path...</code></h4>
        <p>
            When an array path is followed by a trailing <code>...</code> at the end of the path expression (and no
            property name follows), the array elements are spread as individual arguments. Use this when you have a
            direct reference to an array.
        </p>
        <div class="code-block">
            <pre><code>// Passes each number in the array as a separate argument
=sum(/listOfNumbers...)</code></pre>
        </div>

        <div class="warning-notice"
            style="margin-top: 1.5rem; padding: 1rem; background: rgba(239, 68, 68, 0.1); border-left: 4px solid #ef4444; border-radius: 4px;">
            <strong>Warning:</strong> Do not combine infix mapping with trailing dots. <code>/items...price...</code> is
            invalid because the trailing dots are interpreted as part of the property name (looking for a property
            literally named "price...").
        </div>

        <!-- ===== COMPARISON TO EXCEL ===== -->
        <h2 id="comparison">Comparison to Excel</h2>
        <p>
            Think of your UI as a spreadsheet. In Excel, if Cell C1 has the formula <code>=A1+B1</code>, C1
            updates automatically whenever A1 or B1 changes.
        </p>
        <p>
            cDOM brings this exact paradigm to the web. Every attribute and text node can be a "cell" that
            computes its value based on other "cells" (reactive signals).
        </p>

        <table class="api-table">
            <thead>
                <tr>
                    <th>Feature</th>
                    <th>Excel</th>
                    <th>cDOM / JPRX</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td><strong>Reactive Unit</strong></td>
                    <td>Cell</td>
                    <td>Signal / State Proxy</td>
                </tr>
                <tr>
                    <td><strong>Formulas</strong></td>
                    <td><code>=SUM(A1:A10)</code></td>
                    <td><code>=sum(/items...price)</code></td>
                </tr>
                <tr>
                    <td><strong>Path Resolution</strong></td>
                    <td>Cell References (A1, $B$2)</td>
                    <td>JSON Pointer paths (./name, =/global)</td>
                </tr>
                <tr>
                    <td><strong>Recalculation</strong></td>
                    <td>Automatic on change</td>
                    <td>Automatic on change</td>
                </tr>
            </tbody>
        </table>

        <!-- ===== LIGHTVIEW INTEGRATION ===== -->
        <h2 id="integration">Lightview Integration</h2>
        <p>
            cDOM integrates seamlessly with Lightview's existing DOM formats. You can use JPRX expressions
            in any of Lightview's hypermedia formats just by loading <code>lightview-cdom.js</code>:
        </p>
        <h3>vDOM (Virtual DOM)</h3>
        <p>JPRX expressions work directly in vDOM arrays or objects:</p>
        <div class="code-block">
            <pre><code>// vDOM formal object
{
    tag: "div",
    attributes: { class: "counter" },
    children: [
        { tag: "p", children: ["Count: ", "=/local/count"] },
        { tag: "button", attributes: { onclick: "=++/local/count }, children: ["+"] }
    ]
}</code></pre>
        </div>

        <h3>oDOM (Object DOM)</h3>
        <p>JPRX expressions work in oDOM objects:</p>
        <div class="code-block">
            <pre><code>{
    div: {
        class: "counter",
        children: [
            { p: { children: ["Count: ", "=/local/count] } },
            { button: { onclick: "=++/local/count", children: ["+"] } }
        ]
    }
}</code></pre>
        </div>

        <h3>cDOM Shorthand with JPRX</h3>
        <p>cDOM's concise shorthand syntax. Note that cDOM does not require quoting attribute names or JPRX
            expressions like vDOM and oDOM do and you can include comments:</p>
        <div class="code-block">
            <pre><code>{
    div: {
        class: "counter",
        // This is a JPRX comment
        children: [
            { p: ["Count: ", =/local/count] },
            { button: { onclick: =++/local/count, children: ["+"] } }
        ]
    }
}</code></pre>
        </div>

        <!-- ===== DOM PATCHES ===== -->
        <h2 id="dom-patches">DOM Patches & Decentralized Layouts</h2>
        <p>
            cDOM supports <strong>Decentralized Layouts</strong>, allowing components to "move themselves" to their
            rightful
            home in the DOM upon being created. This is especially powerful for LLM-driven streaming UIs.
        </p>

        <h3 id="helpers-move">=move(target, location?)</h3>
        <p>
            The <code>=move</code> helper (typically used in <code>onmount</code>) teleports the host element to a
            different part of the document.
        </p>
        <div class="code-block">
            <pre><code>{
    div: {
        id: "weather-widget",
        onmount: =move('#sidebar', 'afterbegin'),
        content: "Sunny, 75°F"
    }
}</code></pre>
        </div>

        <h4>Identity & Patching</h4>
        <p>
            If the moving element has a unique <code>id</code> and an element with that same ID already exists at the
            destination,
            the existing element is <strong>replaced</strong>. This turns <code>=move</code> into an idempotent
            <strong>patch</strong>
            command — simply stream the new version of the component with the same ID, and it will update the UI
            automatically.
        </p>

        <h4>Placement Locations</h4>
        <table class="api-table">
            <thead>
                <tr>
                    <th>Location</th>
                    <th>Result</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td><code>inner</code> / <code>shadow</code></td>
                    <td>Replaces all children of the target.</td>
                </tr>
                <tr>
                    <td><code>afterbegin</code> / <code>prepend</code></td>
                    <td>Inserts at the start of the target.</td>
                </tr>
                <tr>
                    <td><code>beforeend</code> / <code>append</code></td>
                    <td>Inserts at the end of the target (default).</td>
                </tr>
                <tr>
                    <td><code>outer</code> / <code>replace</code></td>
                    <td>Replaces the target node itself.</td>
                </tr>
                <tr>
                    <td><code>beforebegin</code> / <code>afterend</code></td>
                    <td>Inserts before or after the target node.</td>
                </tr>
            </tbody>
        </table>


        <h2 id="state-binding">State & Binding</h2>
        <p>
            cDOM does not use attribute directives for state it uses lifecycle events and helpers instead.
        </p>

        <h3 id="lifecycle-state">Lifecycle State</h3>
        <p>
            In Lightview, you initialize state within the <code>onmount</code> hook. The <code>=state</code> and
            <code>=signal</code> helpers allow you to create reactive data that is tied to the DOM structure.
        </p>

        <h4 id="scope-resolution">Scope & Name Resolution</h4>
        <p>
            When JPRX encounters a name (e.g., <code>=/profile/name</code>), it doesn't just look in a global registry.
            Instead, it performs an <strong>up-tree search</strong>:
        </p>
        <ol>
            <li>It starts at the element where the expression is defined.</li>
            <li>It looks for a registered signal or state with that name.</li>
            <li>If not found, it moves to the parent element and repeats the search.</li>
            <li>This continues all the way to the <code>document</code> root, finally checking the global registry if
                needed.</li>
        </ol>

        <h4>The <code>$this</code> Placeholder</h4>
        <p>
            The <code>$this</code> keyword is a special placeholder that represents the <strong>current
                element</strong>. When used in the <code>scope</code> option of <code>=state</code> or
            <code>=signal</code>, it instructs Lightview to register the name specifically at that element's level in
            the DOM.
        </p>
        <div class="code-block" style="margin-bottom: 1.5rem;">
            <pre><code>// Scoping state to the current element creates a local namespace
{ "onmount": "=state({ count: 0 }, { name: 'local', scope: $this })" }</code></pre>
        </div>
        <p>
            This mechanism is what allows you to create <strong>reusable components</strong>. Each instance of a
            component can have its own "local" state because the search for <code>local</code> will stop at the first
            element it finds that has it registered—typically the root of the component instance.
        </p>

        <h4>Using a Registered Schema</h4>
        <div class="code-block" style="margin-bottom: 1rem;">
            <pre><code>// 1. Register centrally in JS
Lightview.registerSchema('User', { name: 'string', age: 'number' });

// 2. Use in cDOM
{ "onmount": "=state({}, { name: 'profile', schema: 'User', scope: $this })" }</code></pre>
        </div>

        <h4>Standard Schema Behaviors</h4>
        <p>
            When using the <code>schema</code> option, you can use these standard behaviors:
        </p>
        <ul style="margin-bottom: 2rem;">
            <li><strong>"auto"</strong>: Infers a fixed schema from the initial value. Strict type checking (throws on
                mismatch).</li>
            <li><strong>"dynamic"</strong>: Like auto, but allows adding new properties to the state object.</li>
            <li><strong>"polymorphic"</strong>: The most powerful setting. It includes <strong>"dynamic"</strong>
                behavior and automatically <strong>coerces</strong> values to match the inferred type (e.g., "50" ->
                50).
            </li>
        </ul>

        <h4>Example: Polymorphic Coercion</h4>
        <div class="code-block" style="margin-bottom: 2rem;">
            <pre><code>{ 
    div: { 
        "onmount": "=state({ count: 0 }, { 
            name: 'local', 
            schema: 'polymorphic',
            scope: $this 
        })", 
        children: [
            { p: ["Typing '10' into a bind will save it as the number 10."] }
        ]
    }
}</code></pre>
        </div>


        <h3 id="bind-helper">Two-Way Binding ($bind)</h3>
        <p>
            Two-way data binding is achieved via the <code>=bind(path)</code> helper.
        </p>
        <div class="code-block">
            <pre><code>{ input: { type: "text", value: "=bind(/profile/name)", placeholder: "Enter name" } }
{ input: { type: "checkbox", checked: "=bind(/settings/enabled)" } }</code></pre>
        </div>

        <h4 id="binding-transformations">Handling Transformations</h4>
        <p>
            Because <code>=bind</code> is strict (it only accepts direct paths), you cannot pass a computation
            like <code>=bind(upper(/name))</code>. To transform data during binding, you have two choices:
        </p>
        <ul>
            <li>
                <strong>Manual Transformation:</strong> Use an <code>oninput</code> handler:
                <code>{ oninput: "=set(/name, upper($event/target/value))" }</code>
            </li>
            <li>
                <strong>Schema Transformation:</strong> Define a <code>transform</code> in your schema:
                <div class="code-block" style="margin-top: 0.5rem;">
                    <pre><code>Lightview.registerSchema('Profile', { 
    name: { type: 'string', transform: 'upper' } 
});</code></pre>
                </div>
                The state manager will automatically apply the <a
                    href="/docs/api/state.html#transformations">transformation</a>
                during the write-back phase of <code>=bind</code>.
            </li>
        </ul>

        <!-- ===== SHOPPING CART EXAMPLE ===== -->
        <h2 id="shopping-cart">Shopping Cart Example</h2>
        <p>A more complete example showing reactive expressions with data transformations:</p>

        <div id="shopping-cart-example">
            <pre><script>
                examplify(document.currentScript.nextElementSibling, {
                    at: document.currentScript.parentElement,
                    scripts: ['/lightview.js', '/lightview-x.js', '/lightview-cdom.js'],
                    type: 'module',
                    height: '250px',
                    autoRun: true
                });
            </script><code contenteditable="true">// Shopping Cart: Demonstrating $map and $currency helpers
await import('/lightview-cdom.js');
const { parseJPRX, hydrate } = globalThis.LightviewCDOM;
const { $ } = Lightview;

const cdomString = `{ 
    div: {
        "onmount": "=state({
            cart: {
                items: [
                    { name: 'Apple', price: 1.00 },
                    { name: 'Orange', price: 2.00 }
                ]
            }
        }, 'store')",
        children: [
            { h3: "Shopping Cart" },
            { ul: { 
                children: =(map(/store/cart/items, { li: { children: [_/name, " - ", currency(_/price)] } }))
            }},
            { p: { 
                style: "font-weight: bold; margin-top: 1rem;",
                children: ["Total: ", =(currency(sum(/store/cart/items...price)))]
            }}
        ]
    }
}`;

const hydrated = hydrate(parseJPRX(cdomString));
$('#example').content(hydrated);
</code></pre></code></pre>
        </div>

        <!-- ===== INTERACTIVE EXAMPLE ===== -->
        <h2 id="interactive-example">Interactive Example</h2>
        <p>
            Choose a syntax to see how the same reactive counter can be defined using standard JSON, concise cDOMC, or
            operator syntax.
        </p>

        <div role="tablist" class="syntax-tabs">
            <button id="tab-btn-jprx" class="syntax-tab syntax-tab-active" onclick="switchCDOMTab('jprx')">JPRX
                (Standard)</button>
            <button id="tab-btn-cdomc" class="syntax-tab" onclick="switchCDOMTab('cdomc')">cDOMC</button>
            <button id="tab-btn-operators" class="syntax-tab" onclick="switchCDOMTab('operators')">Operators</button>
        </div>

        <!-- JPRX Pane -->
        <div id="pane-jprx">
            <pre><script>
                examplify(document.currentScript.nextElementSibling, {
                    at: document.currentScript.parentElement,
                    scripts: ['/lightview.js', '/lightview-x.js', '/lightview-cdom.js'],
                    type: 'module',
                    height: '250px',
                    autoRun: true
                });
            </script><code contenteditable="true">// JPRX: Standard JSON format (strict)
await import('/lightview-cdom.js');
const { parseJPRX, hydrate } = globalThis.LightviewCDOM;
const { $ } = Lightview;

const cdomString = `{
    "div": {
        onmount: "=signal(0, 'count')",
        "children": [
            { "h3": ["Standard JPRX Counter"] },
            { "p": { "children": ["Count: ", "=(/count)"] }},
            { "div": { "children": [
                { "button": { "onclick": "=(decrement(/count))", "children": ["-"] } },
                { "button": { "onclick": "=(increment(/count))", "children": ["+"] } }
            ]}}
        ]
    }
}`;

const hydrated = hydrate(parseJPRX(cdomString));
$('#example').content(hydrated);
</code></pre>
        </div>

        <!-- cDOMC Pane -->
        <div id="pane-cdomc" style="display: none;">
            <pre><script>
                examplify(document.currentScript.nextElementSibling, {
                    at: document.currentScript.parentElement,
                    scripts: ['/lightview.js', '/lightview-x.js', '/lightview-cdom.js'],
                    type: 'module',
                    height: '250px'
                });
            </script><code contenteditable="true">// cDOMC: Compressed format (unquoted keys, comments)
await import('/lightview-cdom.js');
const { parseJPRX, hydrate } = globalThis.LightviewCDOM;
const { $ } = ightview;


const cdomString = `{
    div: {
        onmount: =signal(0, 'count'),
        children: [
            { h3: ["Compressed Counter"] },
            { p: { children: ["Count: ", =(/count)] }},
            { div: { children: [
                { button: { onclick: =(decrement(/count)), children: ["-"] } },
                { button: { onclick: =(increment(/count)), children: ["+"] } }
            ]}}
        ]
    }
}`;

const hydrated = hydrate(parseJPRX(cdomString));
$('#example').content(hydrated);
globalThis.LightviewCDOM.activate(hydrated.domEl);
</code></pre>
        </div>

        <!-- Operators Pane -->
        <div id="pane-operators" style="display: none;">
            <pre><script>
                examplify(document.currentScript.nextElementSibling, {
                    at: document.currentScript.parentElement,
                    scripts: ['/lightview.js', '/lightview-x.js', '/lightview-cdom.js'],
                    type: 'module',
                    height: '250px'
                });
            </script><code contenteditable="true">// Operators: Using =++/path and =--/path
await import('/lightview-cdom.js');
const { parseJPRX, hydrate } = globalThis.LightviewCDOM;
const { $ } = Lightview;

const cdomString = `{
    div: {
        onmount: =signal(0, 'count'),
        children: [
            { h3: ["Operator Counter"] },
            { p: { children: ["Count: ", =(/count)] }},
            { div: { children: [
                // Prefix operators: =(--/count) and =(++/count)
                { button: { onclick: =(--/count), children: ["-"] } },
                { button: { onclick: =(++/count), children: ["+"] } }
            ]}}
        ]
    }
}`;

const hydrated = hydrate(parseJPRX(cdomString));
$('#example').content(hydrated);
globalThis.LightviewCDOM.activate(hydrated.domEl);
</code></pre>
        </div>


        <!-- ===== OPERATOR SYNTAX EXAMPLE ===== -->
        <h2 id="operator-syntax">Operator Syntax</h2>
        <p>
            JPRX supports <strong>prefix</strong> and <strong>postfix</strong> operator syntax as alternatives to
            function calls.
            This makes expressions more concise and familiar to JavaScript developers.
        </p>

        <h3>Equivalent Expressions</h3>
        <table class="api-table">
            <thead>
                <tr>
                    <th>Function Syntax</th>
                    <th>Operator Syntax</th>
                    <th>Description</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td><code>=increment(/count)</code></td>
                    <td><code>=++/count</code> or <code>=/count++</code></td>
                    <td>Increment by 1</td>
                </tr>
                <tr>
                    <td><code>=decrement(/count)</code></td>
                    <td><code>=--/count</code> or <code>=/count--</code></td>
                    <td>Decrement by 1</td>
                </tr>
                <tr>
                    <td><code>=toggle(/enabled)</code></td>
                    <td><code>=!!/enabled</code></td>
                    <td>Toggle boolean (prefix only)</td>
                </tr>
            </tbody>
        </table>



        <!-- ===== EVENTS ===== -->
        <h2 id="events">Events and Interaction</h2>
        <p>
            cDOM handles user interactions gracefully, whether in standalone applications or LLM-driven environments.
        </p>

        <h3 id="events-manual">Manual Implementation</h3>
        <p>
            Use standard event attributes with JPRX expressions:
        </p>
        <div class="code-block">
            <pre><code>{ button: { onclick: =++/count, children: ["Click Me"] } }
{ input: { oninput: =set(/name, $event/target/value) } }</code></pre>
        </div>

        <h3 id="events-llm">LLM-Generated Interaction</h3>
        <p>
            LLMs can generate event handlers to register interest in user actions or trigger UI updates:
        </p>
        <div class="code-block">
            <pre><code>// 1. Notify LLM of an event
{ button: { onclick: =fetch('/api/notify', { method: 'POST', body: $event }), children: ["Notify"] } }

// 2. Request a UI Patch from LLM
{ button: { onclick: =mount('/api/update', { method: 'POST', body: $event }), children: ["Update UI"] } }</code></pre>
        </div>
        <p>
            The <code>$event</code> placeholder gives the LLM full context of the interaction. When using
            <code>=mount</code>, the server should respond with cDOM, vDOM, or oDOM content (see the
            <a href="#helpers-network">Network section</a> for more details).
        </p>
        <p>
            By default, <code>=mount</code> will <strong>append</strong> the response to the <code>body</code> and
            then let it "rip itself out" and <strong>teleport</strong> to its final destination if the response
            contains a <code>=move</code> helper. This "Safe Landing" strategy ensures decentralized layouts
            work seamlessly without the patcher needing to know the exact destination.
        </p>

        <h3 id="events-lifecycle">The Interaction Lifecycle</h3>
        <div class="feature-grid" style="margin: 2rem 0;">
            <div class="feature-card">
                <h4 class="feature-title">🤖 LLM-Driven Flow</h4>
                <ol style="margin-top: 1rem; font-size: 0.9em; line-height: 1.6;">
                    <li><strong>LLM</strong> generates UI structure as JSON</li>
                    <li><strong>Server</strong> serves the JSON to the client</li>
                    <li><strong>Client</strong> renders cDOM and activates reactivity</li>
                    <li><strong>User</strong> interacts (e.g., clicks a button)</li>
                    <li><strong>Client</strong> sends interaction data to server</li>
                    <li><strong>Server</strong> relays event to LLM</li>
                    <li><strong>LLM</strong> sends UI patch back</li>
                    <li><strong>Client</strong> merges update, UI refreshes</li>
                </ol>
            </div>
            <div class="feature-card">
                <h4 class="feature-title">🏠 Standalone Flow</h4>
                <ol style="margin-top: 1rem; font-size: 0.9em; line-height: 1.6;">
                    <li><strong>cDOM</strong> defined in source code</li>
                    <li><strong>Client</strong> activates UI on page load</li>
                    <li><strong>User</strong> interacts (e.g., toggles a switch)</li>
                    <li><strong>State</strong> updates immediately</li>
                    <li><strong>UI</strong> recalculates reactively (0 latency)</li>
                    <li><strong>Server</strong> only for persistence/APIs</li>
                </ol>
            </div>
        </div>

        <!-- ===== JAVASCRIPT API ===== -->
        <h2 id="js-api">JavaScript API</h2>
        <p>Interact with cDOM programmatically using the <code>LightviewCDOM</code> global object.</p>

        <h3 id="api-activate">activate(root)</h3>
        <p>
            Scans the DOM from <code>root</code> (defaults to <code>document.body</code>) and
            initializes all cDOM directives.
        </p>
        <div class="code-block">
            <pre><code>LightviewCDOM.activate();
LightviewCDOM.activate(document.getElementById('myApp'));</code></pre>
        </div>

        <h3 id="api-hydrate">hydrate(object)</h3>
        <p>
            Converts <code>=/</code> prefixed strings into reactive computed signals.
        </p>
        <div class="code-block">
            <pre><code>const config = { title: "Dashboard", total: "=/cart/items...price" };
const liveConfig = LightviewCDOM.hydrate(config);</code></pre>
        </div>

        <h3 id="api-parseJPRX">parseJPRX(string)</h3>
        <p>
            Parses cDOM's concise syntax (unquoted keys, JPRX expressions) into a JavaScript object.
        </p>
        <div class="code-block">
            <pre><code>const obj = LightviewCDOM.parseJPRX(`{ div: { children: [=/name] } }`);</code></pre>
        </div>

        <h3 id="api-registerSchema">registerSchema(name, definition)</h3>
        <p>
            Registers a reusable schema for state validation and initialization. Supported behaviors include
            <code>"auto"</code> (strict/fixed), <code>"dynamic"</code> (strict/expandable), and
            <code>"polymorphic"</code> (coerce/expandable).
        </p>
        <div class="code-block">
            <pre><code>Lightview.registerSchema('User', { name: 'string', age: 'number' });
// Usable in JPRX: =state({}, { name: 'profile', schema: 'User' })</code></pre>
        </div>

        <h3 id="api-registerHelper">registerHelper(name, fn, options?)</h3>
        <p>
            Registers a custom helper function for use in JPRX expressions.
        </p>
        <div class="code-block">
            <pre><code>LightviewCDOM.registerHelper('double', (x) => x * 2);
// Now usable: =double(/count)</code></pre>
        </div>

        <!-- ===== JPRX HELPERS ===== -->
        <h2 id="helpers">JPRX Helper Functions</h2>
        <p>
            cDOM includes a rich set of built-in helpers for common transformations.
            For security, only registered helpers are available — no access to <code>globalThis</code>.
        </p>

        <h3 id="helpers-math">Math</h3>
        <p>Basic arithmetic operations.</p>
        <div class="code-block">
            <pre><code>+, add, -, sub, *, mul, /, div, round, ceil, floor, abs, mod, pow, sqrt</code></pre>
        </div>

        <h3 id="helpers-stats">Stats</h3>
        <p>Aggregate calculations.</p>
        <div class="code-block">
            <pre><code>sum, avg, min, max, median, stdev, var</code></pre>
        </div>

        <h3 id="helpers-string">String</h3>
        <p>Text manipulation.</p>
        <div class="code-block">
            <pre><code>upper, lower, trim, capitalize, titleCase, contains, startsWith, endsWith, replace, split, len, join, concat, default</code></pre>
        </div>

        <h3 id="helpers-array">Array</h3>
        <p>Collection processing.</p>
        <div class="code-block">
            <pre><code>count, map, filter, find, unique, sort, reverse, first, last, slice, flatten, join, len, length</code></pre>
        </div>

        <h3 id="helpers-logic">Logic & Comparison</h3>
        <p>Boolean logic and comparisons.</p>

        <h4 class="text-xs font-bold opacity-60 uppercase mb-2">Named Operators</h4>
        <div class="code-block mb-4">
            <pre><code>if, and, or, not, eq, neq, gt, lt, gte, lte, between, in</code></pre>
        </div>

        <h4 class="text-xs font-bold opacity-60 uppercase mb-2">Aliases</h4>
        <div class="code-block mb-4">
            <pre><code>&&, ||, !, ==, ===, !=, >, <, >=, <=</code></pre>
        </div>

        <p class="text-sm italic opacity-80" style="margin-top: 1rem;">
            Example: <code>=if(gt(=/count, 10), 'Large', 'Small')</code>
        </p>

        <h3 id="helpers-conditional">Conditional Aggregates</h3>
        <p>Statistical functions with predicates.</p>
        <div class="code-block">
            <pre><code>sumIf, countIf, avgIf</code></pre>
        </div>

        <h3 id="helpers-formatting">Formatting</h3>
        <p>Display formatting.</p>
        <div class="code-block">
            <pre><code>number, currency, percent, thousands</code></pre>
        </div>
        <p class="text-xs italic opacity-70" style="margin-top: 0.5rem;">
            <strong>Explosion Operator:</strong> In JPRX, <code>...</code> is placed at the end of a path
            (e.g., <code>=/items...price</code>) to expand arrays into arguments.
        </p>

        <h3 id="helpers-datetime">DateTime</h3>
        <p>Date operations.</p>
        <div class="code-block">
            <pre><code>now, today, date, formatDate, year, month, day, weekday, addDays, dateDiff</code></pre>
        </div>

        <h3 id="helpers-lookup">Lookup</h3>
        <p>Data retrieval from indexed structures.</p>
        <div class="code-block">
            <pre><code>lookup, vlookup, index, match</code></pre>
        </div>

        <h3 id="helpers-mutation">State & Lifecycle</h3>
        <p>Initialize and modify reactive state.</p>
        <div class="code-block">
            <pre><code>state, signal, bind, set, increment (++), decrement (--), toggle (!!), push, pop, assign, clear</code></pre>
        </div>

        <h3 id="helpers-network">Network</h3>
        <p>HTTP requests.</p>
        <div class="code-block" style="margin-bottom: 2rem;">
            <pre><code>fetch(url, options?)
<span id="helpers-mount"></span>mount(url, options?)</span></code></pre>
        </div>

        <h3 id="helpers-dom">DOM & XPath</h3>
        <p>Structural navigation and manipulation. For fetching and mounting remote content, see the
            <a href="#helpers-network">mount()</a> helper in the Network section.
        </p>
        <div class="code-block">
            <pre><code>move(selector, location?), xpath(expression)</code></pre>
        </div>
        <p style="margin-top: 1rem;">
            While <code>move</code> is for local placement, <code>mount</code> is used for remote Hypermedia updates.
            It fetches content (cDOM, vDOM, or oDOM) and injects it into the DOM. If the content contains a
            <code>=move</code> helper, it will automatically relocate itself upon mounting.
        </p>
        <table class="api-table">
            <thead>
                <tr>
                    <th>Option</th>
                    <th>Default</th>
                    <th>Description</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td><code>method</code></td>
                    <td><code>"GET"</code></td>
                    <td>HTTP method (GET, POST, etc.)</td>
                </tr>
                <tr>
                    <td><code>body</code></td>
                    <td><code>undefined</code></td>
                    <td>Request body. If an <strong>object</strong>, it is automatically stringified to JSON and the
                        <code>Content-Type</code> is set to <code>application/json</code>.
                    </td>
                </tr>
                <tr>
                    <td><code>headers</code></td>
                    <td><code>{}</code></td>
                    <td>Additional HTTP headers.</td>
                </tr>
                <tr>
                    <td><code>target</code></td>
                    <td><code>"body"</code></td>
                    <td>(<code>mount</code> only) CSS selector for destination.</td>
                </tr>
                <tr>
                    <td><code>location</code></td>
                    <td><code>"beforeend"</code></td>
                    <td>(<code>mount</code> only) Placement relative to target.</td>
                </tr>
            </tbody>
        </table>
        <p class="text-xs italic opacity-70" style="margin-top: 0.5rem;">
            <strong>xpath(expression):</strong> Returns a reactive computed signal based on the DOM structure
            at evaluation time. Note that full MutationObserver reactivity is currently a TODO.
            For static DOM navigation, use the <code>#</code> prefix in cDOM.
        </p>
    </main>
</div>