# el-ui Framework Specification Version 1.1.0 — April 30, 2026 --- ## Overview el-ui is a frontend framework where state is an Engram graph and reactivity is spreading activation. Every framework must answer the same question: what re-renders when state changes? el-ui's answer is: whatever the spreading activation algorithm determines is relevant. | Framework | Reactivity model | |-----------|-----------------| | React | Virtual DOM diffing | | Vue | Dependency tracking (Proxy-based reactive primitives) | | Svelte | Compile-time static analysis | | Solid | Fine-grained signal-based subscriptions | | **el-ui** | **Spreading activation over a typed state graph** | The core insight: component state is represented as a graph of related nodes, and the same spreading activation algorithm used by the Engram knowledge engine determines which components need 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 Model ### 1.1 The Model Every piece of state in an el-ui application is a **node** in an in-browser Engram graph. Nodes have: | Field | Type | Description | |-------|------|-------------| | `id` | String (UUID) | Node's permanent identity | | `type` | String | `'state'`, `'route'`, or user-defined | | `name` | String | State variable name | | `content` | Any | Current value | | `importance` | Float [0, 1] | Salience weight; boosted on update | Nodes are connected by directed **edges** with: | Field | Type | Description | |-------|------|-------------| | `weight` | Float [0, 1] | Connection strength | | `relation` | String | Semantic label of the relationship | ### 1.2 Activation Formula When state changes via `setState()`, spreading activation runs from the changed node through the graph: ``` strength = parent_strength × edge.weight × target.importance ``` This formula is identical to the Engram core spreading activation formula, with the semantic similarity factor (cosine_sim) omitted because the in-browser graph does not yet carry embedding vectors. In a future version connected to the Engram server, the full four-factor formula applies: ``` strength = parent_strength × edge.weight × target.importance × cosine_sim(query, target) ``` ### 1.3 Why Multiplication, Not Addition Addition allows many weak signals to accumulate into false relevance. The brain's associative memory is conjunctive: an activated path requires ALL its links to be strong. Multiplication enforces this. If any factor is near zero — weak edge, dormant node, or irrelevant connection — the path dies immediately. This is not a performance optimization; it is a semantic property of associative retrieval. ### 1.4 Pruning Threshold 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. All nodes not reached above the threshold are not considered for re-render. ### 1.5 Importance Boost (Long-Term Potentiation Analog) When a state node is updated, its `importance` increases by 0.1 (capped at 1.0): ``` node.importance = Math.min(1.0, node.importance + 0.1) ``` Recently-changed state becomes more salient — it activates more easily in future propagation. This mirrors long-term potentiation: frequently-used pathways become lower-resistance. State that changes often gains importance; state that never changes fades toward background salience. ### 1.6 Activation Surface The **activation surface** is the set of nodes reached during a spreading activation pass, i.e., all nodes with `strength > PRUNE_THRESHOLD`. Components subscribed to any node in the activation surface receive update notifications and re-render. --- ## 2. Component Definition Syntax Components are defined in `.el` files (shared extension with the El programming language). A component is a specialized el module with four optional sections. ### 2.1 Component Structure ``` component ComponentName { props { // prop declarations } state { // state declarations } fn methodName(param: Type) -> ReturnType { // method body } template { // HTML template } } ``` All four sections are optional. Components with no template render an empty string. Multiple `fn` blocks are allowed; multiple `props` or `state` blocks are not (only the last one is used). ### 2.2 Props Props are read-only inputs from the parent component. ``` props { label: String // required prop variant: String = "primary" // optional with default disabled: Bool = false // boolean with default onClick: Fn() -> Void // function prop } ``` Supported prop types: `String`, `Int`, `Float`, `Bool`, `Fn(...) -> T`. **Note:** Array (`[T]`) and optional (`T?`) type syntax is not currently parsed by the compiler. The parser reads a single identifier as the type name. Props are accessed in generated code as `this._props_{name}` (not `this.props.name`). In method and template bodies, the compiler exposes each prop as a local constant named `{name}`. ### 2.3 State State declarations create nodes in the component's Engram graph. Every state variable requires an initial value. ``` state { count: Int = 0 label: String = "hello" active: Bool = false } ``` Each state variable becomes: 1. A node in `this._graph` (seeded at construction time with `type: 'state'`), tracked by ID in `this._stateNodes`. 2. A mirrored value in `this._state` for direct read access. 3. A reactive binding: changing the value via `setState()` triggers activation and re-render. In method and template bodies, the compiler exposes each state variable as a local constant named `{name}` (reading from `this._state`). ### 2.4 Methods Methods are `fn` definitions inside the component body. State assignments in method bodies compile to `setState()` calls. ``` fn increment() -> Void { count = count + 1 // compiles to: __self.setState('count', count + 1) } ``` The generated JavaScript method exposes all state variables as local `const` bindings before executing the body. The variable `__self` refers to the component instance. --- ## 3. Template Syntax ### 3.1 Interpolation Any expression embedded in `{expr}`: ```

{count}

{"Hello, " + name + "!"}

{active ? "on" : "off"} ``` ### 3.2 Event Binding DOM events bound with `on:event={handler}`: ``` value = e.target.value} />
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 on elements. The renderer binds handlers at mount time using `new Function(...)` evaluated in the component's scope (`__self`), and rebinds after each patch. ### 3.3 Dynamic Attributes ```
``` ### 3.4 Static Attributes ```
``` ### 3.5 Boolean Attributes The parser recognises a fixed set of boolean attribute names: `disabled`, `checked`, `readonly`, `required`, `multiple`, `selected`. These emit the attribute name when the expression is truthy, nothing when falsy. ``` ``` A standalone attribute with no value (e.g., `
` | | `RawText(s)` | Inline expression text, text content | Non-template code tokens include keywords (`component`, `props`, `state`, `fn`, `template`, `if`, `else`, `return`), identifiers, literals (`String`, `Int`, `Float`, `Bool`), operators, and punctuation. ### 7.3 Parser Hand-written recursive descent parser. Produces `Vec`. 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 allowed) 5. `template { ... }` — template tree (optional) The template parses as a tree of `TemplateNode` values: | Variant | Description | |---------|-------------| | `Element { tag, attrs, children }` | HTML element with attributes and children | | `Component { name, props }` | Uppercase-initial component reference | | `Text(s)` | Literal text content | | `Interpolation(expr)` | `{expr}` — expression interpolation | | `If { condition, then, else_ }` | `{#if}...{:else}...{/if}` | | `Each { items, item_name, children }` | `{#each items as item}...{/each}` | | `Activate { query, result_name, children }` | `{#activate "query" as name}...{/activate}` | Method bodies are captured as raw source text and passed through to the code generator with simple string transformations. **Unknown `{#blockname}` tags** produce a parse error (`unknown block tag: #name`). ### 7.4 Code Generator — Multi-Target The code generator (`src/codegen.rs`) supports three compilation targets: #### `CodegenTarget::Web` (default) — Legacy JavaScript Emits an ES2022 JavaScript module using the el-ui JS runtime (`graph.js`, `renderer.js`, `router.js`, `index.js`). This is the primary target for browser-based applications today. For each component: 1. Emits a class extending `Component` from the runtime. 2. Constructor: seeds state nodes into `this._graph`, sets prop values as `this._props_{name}`, subscribes to activation events. 3. Emits `setState(name, value)` which calls `this._graph.update()` to trigger activation. 4. Emits `render()` returning a template literal string. 5. Translates state assignments: `count = count + 1` → `__self.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. Emits `${__self._child(ComponentClass, props)}` for component references. #### `CodegenTarget::Server` — Rust SSR Emits a Rust module where each component is a struct implementing `render_to_html(&self) -> String` via `el_platform::ServerBackend`. **Note:** The `build_node_tree()` implementation in this target is a stub — it returns a placeholder `PlatformNode::element("div")`. Full template-to-PlatformNode codegen is not yet implemented for the server target. #### `CodegenTarget::Native(Platform)` — Rust Native / WASM Emits a Rust module where each component builds a `PlatformNode` tree via the semantic primitive layer and mounts it via the `el_platform` backend for the given platform. Supported platforms: | Platform | Backend | Output format | |----------|---------|---------------| | `Platform::Web` | `WebBackend` (web-sys) | Rust compiled to WASM — NOT JavaScript | | `Platform::Ios` | `IosBackend` | Rust (objc2-ui-kit) | | `Platform::Android` | `AndroidBackend` | Kotlin (Jetpack Compose via build.rs) | | `Platform::Macos` | `MacosBackend` | Rust (objc2-app-kit) | | `Platform::Linux` | `LinuxBackend` | Rust (gtk4-rs) | | `Platform::Windows` | `WindowsBackend` | Rust (windows-rs / WinUI 3) | **Note:** The `build_node_tree()` implementation in all native targets is currently a stub returning `PlatformNode::element("div")`. Full AST-to-semantic-primitive lowering is not yet wired in — the semantic module and per-platform codegen functions exist and are correct, but the compiler does not yet call them from the AST walk. ### 7.5 Semantic Primitive Layer (`semantic.rs`) The semantic layer is the bridge between the template AST and platform-specific code generation. It defines EBD-driven concepts — *what* something is, not *how* it looks: **AppearanceConcept** — what a control is: - `Action` — primary action trigger (blue/filled on most platforms) - `Destructive` — dangerous/irreversible action (red tint) - `Secondary` — subdued / less-prominent action - `Navigation` — link-like / back-navigation element - `Informational` — read-only display element - `Structural` — layout container with no interactive meaning **LayoutConcept** — how children are arranged: - `Stack` — vertical (Column / VStack / GtkBox vertical) - `Row` — horizontal (Row / HStack / GtkBox horizontal) - `Grid` — two-dimensional grid - `Overlay` — z-axis layering (ZStack / GtkOverlay) - `Scroll` — scrollable container **TextConcept** — the semantic role of text: - `Heading { level: u8 }` — heading at level 1–6 - `Body` — default body text - `Caption` — small secondary text - `Label` — control label - `Code` — monospaced code text **SemanticPrimitive** — the EBD building blocks: - `Button { label, appearance, on_press }` — interactive button - `Text { content, concept }` — text display - `Container { children, layout }` — layout container - `Input { binding, hint, appearance }` — text input bound to state - `Image { src, alt }` — image element - `Toggle { binding, label }` — checkbox/switch bound to boolean state - `List { items_binding, item_name, item_template }` — homogeneous list Appearance is inferred from explicit `appearance="..."` attributes, CSS class names, or HTML tag heuristics via `infer_appearance()`. Layout and text concepts are inferred from tag names via `infer_layout_concept()` and `infer_text_concept()`. ### 7.6 CLI ```bash el-ui-compiler App.el # compiles to App.js (Web target, default) el-ui-compiler App.el -o app.js # explicit output path ``` The CLI always uses `CodegenTarget::Web`. Target selection (`--target server`, `--target ios`, etc.) is available as a `CodegenTarget` enum in the library API but is not exposed as a CLI flag in the current implementation. --- ## 8. Runtime Architecture ### 8.1 Module Structure | Module | Purpose | |--------|---------| | `graph.js` | Engram graph: nodes, edges, activation, search, subscribe, dump | | `activation.js` | Standalone activation utilities: `spreadActivation`, `activationStrength`, `reachableNodes` | | `renderer.js` | DOM patching, event binding | | `router.js` | Graph-based routing | | `index.js` | `Component` base class, `mount()`, re-exports | ### 8.2 Component Lifecycle | Lifecycle event | Description | |-----------------|-------------| | `constructor(props)` | Seeds state graph, subscribes to activation | | `onMount()` | Called after first render and DOM attachment | | `render()` | Returns HTML string; called on activation | **Note:** `onDestroy()` is documented in prior drafts but is not implemented in the base `Component` class or the `Renderer`. It will not be called by the runtime. ### 8.3 Mounting ```javascript import { mount } from './el-ui.js'; import { App } from './app.js'; const component = mount(App, '#app', { optionalProp: 'value' }); // mount() returns the live Component instance ``` `mount` instantiates the root component, creates a `Renderer`, calls `renderer.mount()` which calls `render()`, sets `root.innerHTML`, binds events, then calls `onMount()`. ### 8.4 DOM Patching Strategy (v0.1) The renderer uses full string re-render on every state change: `root.innerHTML = component.render()`. The activated node set argument to `patch()` is accepted but unused — targeted patching is planned for v0.2. After patching, the renderer attempts to restore focus to the previously focused element by `id`. Event handlers are rebound after every patch by scanning the new DOM for `data-el-{event}` attributes. Handlers are compiled via `new Function('__self', ...)` — this requires a permissive Content Security Policy in v0.1. --- ## 9. Platform Backend Architecture (`el-platform`) The `el-platform` crate defines the `PlatformBackend` trait that all rendering backends implement: ```rust pub trait PlatformBackend: Send + Sync { fn name(&self) -> &'static str; fn create_element(&self, tag: &str) -> PlatformResult; fn create_text(&self, content: &str) -> PlatformResult; fn set_attribute(&self, node: &mut PlatformNode, name: &str, value: &str) -> PlatformResult<()>; fn remove_attribute(&self, node: &mut PlatformNode, name: &str) -> PlatformResult<()>; fn append_child(&self, parent: &mut PlatformNode, child: PlatformNode) -> PlatformResult<()>; fn remove_child(&self, parent: &mut PlatformNode, child_index: usize) -> PlatformResult<()>; fn replace_child(&self, parent: &mut PlatformNode, index: usize, new_child: PlatformNode) -> PlatformResult<()>; fn bind_event(&self, node: &mut PlatformNode, event: &str, handler: EventHandler) -> PlatformResult<()>; fn render_to_string(&self, node: &PlatformNode) -> PlatformResult; fn mount(&self, root: PlatformNode, container_id: &str) -> PlatformResult<()>; fn patch(&self, old: &PlatformNode, new: &PlatformNode) -> PlatformResult<()>; fn supports_ssr(&self) -> bool { false } } ``` The `ServerBackend` is the only backend where `supports_ssr()` returns `true` and `render_to_string()` produces real HTML output. All other backends are platform-native. Platform is selected via `manifest.el`: ```toml [platform] target = "web" # web | server | ios | android | macos | linux | windows ssr = true ``` --- ## 10. Comparison to Other Frameworks ### 10.1 vs. React React uses virtual DOM diffing: on every render, it constructs a virtual tree and diffs it against the previous tree to identify minimal DOM mutations. The unit of re-render is the component; re-render triggers are driven by `setState`, `useReducer`, or context changes. el-ui does not compute a virtual DOM. The spreading activation pass determines the re-render set from the graph topology. State relationships that exist in the graph are automatically propagated; relationships that do not exist are not. There is no need to declare dependencies, use `useMemo`, or prevent unnecessary renders with `React.memo` — the graph structure encodes the dependency information directly. ### 10.2 vs. Vue Vue uses a Proxy-based reactive system: state values are wrapped in reactive proxies, and reads from these proxies during render are tracked as dependencies. When a reactive value changes, all components that read it during their last render are re-rendered. el-ui's graph model is explicit: relationships between state nodes are declared via graph edges, not inferred from read tracking. This makes dependencies inspectable and modifiable at runtime. It also enables semantic queries (`{#activate}`) that find relevant state by meaning rather than by structural reference. ### 10.3 vs. Svelte Svelte uses compile-time analysis to determine which parts of the DOM update when which state changes. The compiler emits targeted DOM mutations. There is no runtime overhead for a virtual DOM or a reactive system. el-ui uses a runtime spreading activation pass. This is more expensive than Svelte's compile-time approach for small, known-static state graphs. The advantage is dynamic state topology: new nodes and edges can be added at runtime without recompilation, enabling state graphs that evolve with program execution. ### 10.4 Core Differentiator No prior framework uses Hebbian spreading activation as the re-render decision mechanism. The multiplicative activation formula, the self-seeded activation from a root node, the importance-boost on state change (LTP analog), and the semantic `{#activate}` query construct have no analogues in any shipping frontend framework as of April 2026. --- ## 11. Versioning Roadmap | Version | Key changes | |---------|-------------| | v0.1.x | Current. Full re-render on state change. String-based `{#activate}` search. JS (Web) target only via CLI. Native targets available via library API but `build_node_tree()` is stub. | | v0.2.x | Targeted DOM patching (only nodes in activation surface). `{#activate}` with embedding-based semantic search. CLI `--target` flag for native targets. Full AST→semantic→PlatformNode lowering. | | v0.3.x | WASM-compiled Engram core replacing JavaScript graph. Sealed artifact support. `onDestroy()` lifecycle. | | v1.0.0 | Stable API. Full production sealing. LSP integration with spreading activation autocomplete. |