Files
el/ui/spec/framework.md
2026-04-29 08:50:26 -05:00

18 KiB
Raw Permalink Blame History

el-ui Framework Specification

Version 0.1.0 — April 2026


Overview

el-ui is a frontend framework where state is an Engram graph and reactivity is spreading activation.

Every framework answers the same question differently: what re-renders when state changes?

Framework Reactivity model
React Virtual DOM diffing
Vue Dependency tracking (Proxy-based)
Svelte Compile-time analysis
el-ui Spreading activation over a state graph

The core insight: treating component state as a graph of related nodes, and using the same spreading activation algorithm as the Engram knowledge engine to determine what to update. Reactivity is not declared, tracked, or diffed — it is activated and propagated, exactly as associative memory works in biological neural networks.


1. Activation-Based Reactivity

1.1 The Model

Every piece of state in an el-ui application is a node in an in-browser Engram graph. Nodes have:

  • id — UUID, the node's identity
  • type'state', 'route', or user-defined
  • name — the state variable name
  • content — the current value
  • importance — a salience weight [0, 1], boosted when the node is activated

Nodes are connected by edges with a weight [0, 1] and a relation label.

When state changes (via setState()), spreading activation runs from the changed node:

strength = parent_strength × edge.weight × target.importance

This is multiplicative, matching the Engram core engine (engram-core/src/activation.rs). Every factor must be non-trivial for a path to propagate — weak edges, dormant nodes, and irrelevant connections die immediately. Components subscribed to nodes in the resulting activation surface update. Everything else stays still.

1.2 Why Multiplication, Not Addition

Addition allows many weak signals to accumulate into false relevance — an observation from the Engram architecture. The brain's associative memory is conjunctive: a path requires ALL its links to be strong. Multiplication enforces this. If any factor is near zero, the path dies.

1.3 Pruning

Paths with activation strength below PRUNE_THRESHOLD (default: 0.01) are cut. This prevents exponential blowup in large state graphs and models the brain's attention filter.

1.4 Importance Boost

When a node is updated, its importance increases by 0.1 (capped at 1.0). Recently-changed state is more salient — it activates more easily in future propagation. This mirrors long-term potentiation: frequently-used pathways become lower-resistance.


2. Component Definition Syntax

Components are defined in .el files (the same extension as el source). A component is a specialized el module.

2.1 Structure

component ComponentName {
    props {
        // optional prop declarations
    }

    state {
        // optional state declarations
    }

    fn methodName(param: Type) -> ReturnType {
        // method body
    }

    template {
        // HTML template
    }
}

All four sections (props, state, methods, template) are optional. Components with no template render an empty string.

2.2 Props

Props are inputs from the parent component. They are read-only inside the component.

props {
    label: String               // required
    variant: String = "primary" // optional, with default
    disabled: Bool = false      // boolean with default
    onClick: Fn() -> Void       // function prop
}

Prop types: String, Int, Float, Bool, Fn(...) -> T, [T], T?.

2.3 State

State declarations create nodes in the component's Engram graph.

state {
    count: Int = 0
    label: String = "hello"
    active: Bool = false
}

Each state variable becomes:

  1. A node in this._graph (seeded at construction time)
  2. A reactive binding: changing the value via setState() triggers activation

2.4 Methods

Methods are fn definitions inside the component body. They have access to the component's current state and can call setState().

fn increment() -> Void {
    count = count + 1
}

State assignments in method bodies (count = count + 1) are compiled to setState('count', count + 1) calls.


3. Template Syntax

3.1 Interpolation

Embed any expression with {expr}:

<h1>{count}</h1>
<p>{"Hello, " + name + "!"}</p>
<span>{active ? "on" : "off"}</span>

3.2 Event Binding

Bind DOM events with on:event={handler}:

<button on:click={() => count = count + 1}>+</button>
<input on:input={(e) => value = e.target.value} />
<div on:mouseenter={() => hovered = true} on:mouseleave={() => hovered = false} />

Supported events: click, input, change, mouseenter, mouseleave, keydown, keyup, keypress, focus, blur, submit, mousedown, mouseup, dblclick, contextmenu.

The compiler emits data-el-{event} attributes. The renderer binds handlers at mount time and rebinds after each patch.

3.3 Dynamic Attributes

<div class={active ? "btn-active" : "btn"}></div>
<a href={currentUrl}></a>

3.4 Static Attributes

<div class="container"></div>
<input type="text" />

3.5 Boolean Attributes

<button disabled={isDisabled}>Submit</button>
<input checked={isChecked} />

Boolean attributes emit the attribute name if the expression is truthy, nothing if falsy.

3.6 Component Usage

Components are invoked by their name (uppercase first letter). Props are passed as attributes:

<Button label="Click me" variant="primary" onClick={() => handleClick()} />
<Counter />
<UserCard name={currentUser.name} />

Lowercase tags are HTML elements. Uppercase tags are el-ui components.

3.7 Conditional Rendering — {#if}

{#if condition}
    <div>shown when true</div>
{/if}

{#if loggedIn}
    <Dashboard />
{:else}
    <Login />
{/if}

The condition is any JavaScript-compatible boolean expression.

3.8 List Rendering — {#each}

{#each items as item}
    <li>{item}</li>
{/each}

{#each users as user}
    <UserCard name={user.name} />
{/each}

The items expression must evaluate to an array.

3.9 Semantic Activation — {#activate} (The Novel Construct)

{#activate "recent premium subscribers" as results}
    <UserCard name={results.name} />
{/activate}

This is el-ui's distinguishing feature. {#activate} runs a semantic query against the application's state graph at render time. The query string is a natural language description. The runtime:

  1. Searches the graph for nodes whose content or name matches the query.
  2. Sorts results by their activation score (importance × text match score).
  3. Renders the template body once for each result, binding the result as results.

In v0.1, the search is string-based. In future versions, it will use embedding vectors and cosine similarity — the same semantic search engine as the Engram knowledge database. This means state queries become conceptual rather than structural: instead of items.filter(i => i.type === 'premium'), you write {#activate "premium subscribers"} and the activation surface finds semantically matching nodes.


4. The State Graph

4.1 Graph Structure

Each component instance owns an Engram graph (this._graph). State nodes are seeded at construction time. The graph is shared between a component and its children (they operate on the same activation surface).

4.2 Node Types

Type Description
'state' Component state variable
'route' Router path node
User-defined Any custom node type via this._graph.seed()

4.3 Seeding Nodes

const nodeId = graph.seed({ type: 'state', name: 'count', content: 0, importance: 0.5 });

Returns a UUID string that permanently identifies the node.

4.4 Connecting Nodes

graph.connect(fromId, toId, { weight: 0.8, relation: 'derived' });

Connects two nodes with a directed edge. Higher weight = stronger activation path. Use this to encode semantic relationships between state (e.g., a derived value has an edge from its source).

4.5 Subscribing to Activation

const unsubscribe = graph.subscribe(nodeId, (node) => {
    console.log('node activated:', node.content);
});
// Later:
unsubscribe();
const results = graph.search("recent items", "state");
// Returns array of { id, name, content, importance, score } sorted by score

5. The {#activate} Construct — Semantic State Queries

5.1 Philosophy

Traditional reactivity systems force you to structure your state queries around your storage schema. If you need "all premium users with recent activity", you write:

users.filter(u => u.isPremium && u.lastActive > threshold)

This is structural — it knows the shape of your data.

el-ui's {#activate} is semantic — it knows the meaning of your query:

{#activate "premium users with recent activity" as users}

The activation engine finds nodes that semantically match the query, regardless of their exact structure.

5.2 Compilation

{#activate "query" as results} compiles to:

${(this._graph.search("query") || []).map((results) => `...template...`).join('')}

5.3 Future: Full Semantic Activation

In a future version connected to an Engram database, {#activate} will:

  1. Embed the query string into the same vector space as node embeddings.
  2. Seed activation at the closest semantic nodes.
  3. Spread outward via the graph's weighted edges.
  4. Return all nodes above the activation threshold.

This produces a truly associative query: finding nodes that are semantically related to the query, not just textually matching.


6. Router — Graph-Based Routing

6.1 Overview

Routes are nodes in the application graph. Navigation activates the target route node. Components subscribed to routing update automatically via spreading activation.

6.2 Usage

import { Router, mount } from './el-ui.js';
import { Home, About, NotFound } from './app.js';

const graph = component._graph;
const router = new Router(graph, {
    '/': Home,
    '/about': About,
    '*': NotFound,
});

// Navigate programmatically
router.navigate('/about');

// Get current component
const CurrentPage = router.currentComponent();

// Subscribe to route changes
const unsub = router.subscribe((path) => console.log('navigated to', path));

6.3 Route Node Connections

The router connects parent routes to child routes via edges (weight: 0.8, relation: 'subroute'). Navigating to /about/team activates the /about route node as well (activation spreads upward through the subroute chain).

6.4 Path Matching

  1. Exact match: /about matches /about
  2. Prefix match (longest wins): /about matches /about/team
  3. Wildcard: '*' catches all unmatched paths

7. Plugin API

7.1 Custom Node Types

Extend the graph with application-specific node types:

import { mount } from './el-ui.js';
import { App } from './app.js';

const component = mount(App, '#app');

// Add a custom "user session" node
const sessionNodeId = component._graph.seed({
    type: 'session',
    name: 'currentUser',
    content: { id: '123', name: 'Alice' },
    importance: 0.9,
});

// Connect it to a state node for reactive updates
component._graph.connect(sessionNodeId, component._stateNodes['userId'], {
    weight: 1.0,
    relation: 'owns',
});

7.2 Custom Activation Sources

Trigger activation from any node, not just state updates:

import { spreadActivation } from './el-ui.js';

// Run a custom activation query
const results = spreadActivation(graph, [myNodeId], {
    maxDepth: 4,
    limit: 10,
    pruneThreshold: 0.05,
});

results.forEach(({ nodeId, strength, node }) => {
    console.log(`${node.name}: ${strength.toFixed(3)}`);
});

7.3 Component Lifecycle Extension

class MyComponent extends Component {
    constructor(props) {
        super(props);
        // Seed custom nodes
        this._externalData = this._graph.seed({
            type: 'external',
            name: 'fetchedData',
            content: null,
            importance: 0.7,
        });
    }

    onMount() {
        // Side effects after first render
        fetch('/api/data')
            .then(r => r.json())
            .then(data => {
                this._graph.update(this._externalData, data);
            });
    }

    render() {
        const data = this._graph.get(this._externalData)?.content;
        return data ? `<div>${data.title}</div>` : `<div>Loading...</div>`;
    }
}

8. Compilation Pipeline

8.1 Overview

source.el  →  [lexer]  →  [parser]  →  [codegen]  →  output.js

8.2 Lexer (crates/el-ui-compiler/src/lexer.rs)

The lexer is a single-pass, O(n) tokenizer. It is context-sensitive: when it encounters the template keyword followed by {, it switches to template mode, producing template-specific tokens:

Token Description
HashIdent(kw) {#if}, {#each}, {#activate}
SlashIdent(kw) {/if}, {/each}, {/activate}
ColonIdent(kw) {:else}
OnColon(event) on:click, on:input, etc.
SelfClose />
CloseTag(name) </div>
RawText(s) Inline expression text, text content

8.3 Parser (crates/el-ui-compiler/src/parser.rs)

Hand-written recursive descent. Produces Vec<Component>. Each component is parsed as:

  1. component Name { — component declaration
  2. props { ... } — prop definitions (optional)
  3. state { ... } — state definitions (optional)
  4. fn method(...) -> Type { ... } — methods (optional, multiple)
  5. template { ... } — template tree (optional)

The template is parsed as a tree of TemplateNode values. HTML elements, component references, interpolations, and block directives are all represented as nodes in this tree.

8.4 Code Generator (crates/el-ui-compiler/src/codegen.rs)

The code generator transforms the AST into a JavaScript ES2022 module. For each component:

  1. Emits a class extending Component from the runtime.
  2. In the constructor: seeds state nodes into this._graph, subscribes to activation events.
  3. Emits setState(), which calls this._graph.update() to trigger activation.
  4. Emits a render() method returning a template literal string.
  5. Translates state assignments in methods: count = count + 1this.setState('count', count + 1).
  6. Translates template interpolations: {count}${count}.
  7. Emits event handlers as data-el-{event} attributes for the renderer to bind.

8.5 CLI

# Compile a single .el file
el-ui-compiler App.el -o app.js

# Default output: same name, .js extension
el-ui-compiler App.el   # produces App.js

9. Production Build — Quantum-Sealed via engram-crypto

The production build pipeline follows the el sealed artifact format:

# 1. Compile .el to .js
el-ui-compiler App.el -o app.js --target prod

# 2. Seal the JavaScript bundle
ENGRAM_SEAL_KEY=my-deploy-key el seal app.js -o app.sealed

The sealed artifact is an ENGRAM01 sealed bundle (same format as the el production target):

Offset  Size    Field
──────  ──────  ─────────────────────────────────────────
0       8       Magic: b"ENGRAM01"
8       2       Format version: u16 big-endian (currently 1)
10      *       JSON: { algorithm_id, signature, encapsulated_key, nonce, ciphertext }

AES-256-GCM encryption. The key is derived from the deployment binding (environment variable, machine fingerprint, or none). Without the key, the bundle is indistinguishable from random bytes. No static analysis tool can extract the application logic, queries, or API keys from a sealed bundle.

Why "quantum-sealed": AES-256 is quantum-resistant at 128-bit quantum security (Grover's algorithm provides only a quadratic speedup). The algorithm_id field is forward-compatible with ML-KEM when it stabilizes.

9.1 The {#activate} Query is Protected

A key benefit of production sealing: {#activate} query strings are embedded in the compiled bundle and encrypted with the application logic. Proprietary semantic queries (which encode business logic about how your application understands its data) are invisible to competitors who decompile your application.


10. Runtime Size Target

The dist/el-ui.js runtime targets under 15KB minified and gzipped. As of v0.1:

Module Purpose ~Size
graph.js Engram graph (nodes, edges, activation, search, subscribe) ~3KB
activation.js Standalone activation utilities ~1.5KB
renderer.js DOM patching, event binding ~2KB
router.js Graph-based routing ~1.5KB
index.js Component base class, mount(), re-exports ~1KB

Total: ~9KB source, ~4KB minified+gzipped (estimated).


11. Versioning and Compatibility

el-ui follows semantic versioning.

  • v0.1.x — Initial release. Full re-render on state change. String-based {#activate} search.
  • v0.2.x — Targeted DOM patching (patch only nodes in the activation surface). {#activate} with embedding-based semantic search.
  • v0.3.x — ML-KEM sealed artifacts. Engram database integration for compile-time semantic type checking of {#activate} queries.
  • v1.0.0 — Stable API. Full production sealing. LSP integration with spreading activation autocomplete.

12. Relationship to el

el-ui .el files share the .el extension with el source files. Components are specialized el modules — in a future version, an .el file can mix component definitions with el type definitions, constants, and utility functions in a single compilation unit.

The spreading activation algorithm in graph.js and activation.js faithfully mirrors engram-core/src/activation.rs:

  • Same BFS-based traversal
  • Same multiplicative strength formula
  • Same pruning threshold semantics
  • Same winner-take-most rule (strongest path to each node wins)

The in-browser graph is a lightweight implementation without Engram's full embedding vector machinery. In production, a WASM-compiled Engram core can replace the JavaScript graph entirely, enabling true semantic activation with cosine similarity over embedding vectors.