Belated rename commit for foundation/el-ui — was missed in the workspace-wide crates→vessels pass earlier today. Same structural intent as the rename in the other repos: 'crates' is the Rust word, 'vessel' is El's, and the directory rename is the marker that this slot holds an El buildable unit even if its current contents are still Rust pending port. Plus the El ports themselves — manifest.el + src/main.el per sub- vessel (el-aop, el-auth, el-config, el-i18n, el-identity, el-layout, el-platform, el-publish, el-secrets, el-services, el-style, el-ui- compiler). The ui-compiler is a stub: elc only emits C right now; generating browser-target JS/Wasm is the biggest open language gap and gets its own project. Until then, el-ui-compiler emits a JS module that throws elc.backend_missing so callers fail loudly. Cross-repo path dependencies in Cargo.toml updated to vessels/.
29 KiB
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:
- A node in
this._graph(seeded at construction time withtype: 'state'), tracked by ID inthis._stateNodes. - A mirrored value in
this._statefor direct read access. - 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}:
<h1>{count}</h1>
<p>{"Hello, " + name + "!"}</p>
<span>{active ? "on" : "off"}</span>
3.2 Event Binding
DOM events bound 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 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
<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
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.
<button disabled={isDisabled}>Submit</button>
<input checked={isChecked} />
A standalone attribute with no value (e.g., <button disabled>) is also parsed and treated as BoolAttr { expr: "true" }.
3.6 Component Invocation
Uppercase-initial tags are el-ui components; lowercase tags are HTML elements:
<Button label="Click me" variant="primary" onClick={() => handleClick()} />
<Counter initialValue={5} />
<UserCard name={currentUser.name} />
The compiler emits ${__self._child(ComponentClass, { prop: value })} for component references. Child components share the parent's activation graph via _child().
3.7 Conditional Rendering — {#if}
{#if loggedIn}
<Dashboard />
{:else}
<Login />
{/if}
Compiles to a ternary template literal expression: ${(condition) ? ...:...}.
3.8 List Rendering — {#each}
{#each users as user}
<UserCard name={user.name} />
{/each}
The items expression must evaluate to an array. Compiles to ${(items).map((user) => ...).join('')}.
3.9 Semantic Activation — {#activate} (Novel Construct)
{#activate "recent premium subscribers" as results}
<UserCard name={results.name} />
{/activate}
{#activate} runs a semantic query against the component's state graph at render time. The query string is a natural language description. The runtime:
- Calls
this._graph.search("query")which performs a string-based substring match on nodenameandcontentfields. - Sorts results by score (combination of name/content match and node importance).
- Renders the template body once for each result, binding the result as the variable name.
Compiles to:
${((this._graph.search("query")) || []).map((results) => `...template...`).join('')}
In v0.1, the search is string-based (substring match on name and content.toString()). In future versions connected to an Engram database, {#activate} will use embedding vectors and cosine similarity.
Note on business logic protection: The spec previously described AES-256-GCM encryption of {#activate} query strings in sealed production bundles. This feature is not implemented — there is no --target prod flag in the current CLI and no el seal command.
4. State Graph
4.1 Graph Structure
Each component instance owns an Engram graph (this._graph). State nodes are seeded at construction. The graph is shared between a component and its children via _child() — they operate on the same activation surface.
4.2 State Node Creation
const nodeId = graph.seed({ type: 'state', name: 'count', content: 0 });
// importance defaults to 0.5 if omitted
Returns a UUID string that permanently identifies the node.
4.3 Edge Creation
graph.connect(fromId, toId, { weight: 0.8, relation: 'derived' });
Connects two nodes with a directed edge. Higher weight = stronger activation path. Default weight is 1.0, default relation is 'related'.
4.4 Node Retrieval
const node = graph.get(nodeId);
// Returns: { id, type, name, content, importance, edges: [edgeId, ...] }
Note: the runtime method is graph.get(id), not graph.getNode(id).
4.5 State Update
this.setState('count', newValue);
// or directly:
this._graph.update(nodeId, newValue);
setState looks up the node by state variable name, calls this._graph.update() which:
- Updates
content. - Increments
importanceby 0.1 (capped at 1.0). - Runs spreading activation from the changed node.
- Notifies all subscribers in the activation surface.
- Notifies direct subscribers on the changed node.
4.6 Activation Subscription
const unsubscribe = graph.subscribe(nodeId, (node) => {
// called whenever this node enters the activation surface
});
unsubscribe(); // stop listening
Components subscribe to their state nodes in the constructor. When spreading activation reaches a subscribed node, the subscriber callback fires and the component re-renders via this._renderer.patch().
4.7 Semantic Search
const results = graph.search("recent items", "state");
// Returns: [{ id, type, name, content, importance, score }] sorted by score
// nodeType argument is optional — filters by node.type if provided
In v0.1, search is string-based: name.includes(query) or content.toString().includes(query). Score is computed as (nameMatch ? 0.6 : 0) + (contentMatch ? 0.4 : 0) multiplied by node.importance.
4.8 Graph Dump (Debug)
const allNodes = graph.dump();
// Returns: array of all node objects — useful for DevTools / debugging
5. Spreading Activation Implementation (In-Browser)
The in-browser spreading activation algorithm in graph.js (and standalone utilities in activation.js) mirrors engram-core/src/activation.rs:
// Within graph.activate(seedId, maxDepth = 3, pruneThreshold = 0.01)
// Returns: Set<string> of activated node IDs
while (queue.length > 0) {
// Best-first: process highest-strength candidate
const { id, strength, depth } = bestInQueue();
if (depth >= maxDepth) continue;
for (const edgeId of node.edges) {
const edge = this.edges.get(edgeId);
const target = this.nodes.get(edge.to);
// Multiplicative formula
const targetStrength = strength * edge.weight * Math.max(0, target.importance);
if (targetStrength <= pruneThreshold) continue;
// Winner-take-most
if (targetStrength > prevBest) { /* enqueue */ }
}
}
Key parameters:
maxDepthdefault: 3 (not 4)pruneThresholddefault: 0.01limitdefault: 20 (applies tospreadActivation()utility;graph.activate()returns a Set with no limit)
Correspondence with engram-core:
- Same best-first BFS traversal
- Same multiplicative strength formula
- Same pruning threshold semantics (0.01)
- Same winner-take-most rule (strongest path to each node wins)
- Same depth limit semantics
The in-browser graph omits the cosine_sim factor because embedding vectors are not yet available in the browser.
5.1 Standalone Activation Utilities (activation.js)
import { spreadActivation, activationStrength, reachableNodes, PRUNE_THRESHOLD } from './el-ui.js';
// Multi-seed activation — returns sorted array of reached nodes (excluding seeds)
const reached = spreadActivation(graph, [seedId1, seedId2], { maxDepth: 3, limit: 20 });
// Returns: [{ nodeId, strength, hops, node }]
// Point-to-point strength — returns 0 if no path within maxDepth
const strength = activationStrength(graph, fromId, toId, maxDepth);
// All reachable nodes from a seed (includes seed) — returns Set<string>
const reachable = reachableNodes(graph, seedId, maxDepth);
6. Router — Graph-Based Routing
6.1 Overview
Routes are nodes in the application graph of type 'route'. Navigation activates the target route node. Components subscribed to routing update automatically via spreading activation.
Route nodes start with importance: 0.3. Navigating to a route boosts its importance by 0.2 (capped at 1.0), modeling browser history salience.
6.2 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.3 Path Matching
- Exact match:
/aboutmatches/about - Prefix match (longest wins):
/aboutmatches/about/team - Wildcard:
'*'catches all unmatched paths
6.4 API
const router = new Router(graph, {
'/': HomeComponent,
'/about': AboutComponent,
'*': NotFoundComponent,
});
router.navigate('/about');
router.navigate('/about', true); // replaceState instead of pushState
const CurrentPage = router.currentComponent();
const unsub = router.subscribe((path) => console.log('navigated to', path));
const href = router.href('/about'); // returns the path string (identity in v0.1)
The router also listens for popstate events (browser back/forward navigation) and activates the appropriate route node automatically.
7. Compilation Pipeline
7.1 Overview
source.el → [Lexer] → [Parser] → [Codegen] → output
The output format depends on the compilation target (see §7.4).
7.2 Lexer
The lexer (src/lexer.rs) 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} — block open |
SlashIdent(kw) |
{/if}, {/each}, {/activate} — block close |
ColonIdent(kw) |
{:else} — block continuation |
OnColon(event) |
on:click, on:input — event binding |
SelfClose |
/> |
CloseTag(name) |
</div> |
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<Component>. Each component is parsed as:
component Name {— component declarationprops { ... }— prop definitions (optional)state { ... }— state definitions (optional)fn method(...) -> Type { ... }— methods (optional, multiple allowed)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:
- Emits a class extending
Componentfrom the runtime. - Constructor: seeds state nodes into
this._graph, sets prop values asthis._props_{name}, subscribes to activation events. - Emits
setState(name, value)which callsthis._graph.update()to trigger activation. - Emits
render()returning a template literal string. - Translates state assignments:
count = count + 1→__self.setState('count', count + 1). - Translates template interpolations:
{count}→${count }. - Emits event handlers as
data-el-{event}attributes for the renderer to bind. - 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 actionNavigation— link-like / back-navigation elementInformational— read-only display elementStructural— 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 gridOverlay— z-axis layering (ZStack / GtkOverlay)Scroll— scrollable container
TextConcept — the semantic role of text:
Heading { level: u8 }— heading at level 1–6Body— default body textCaption— small secondary textLabel— control labelCode— monospaced code text
SemanticPrimitive — the EBD building blocks:
Button { label, appearance, on_press }— interactive buttonText { content, concept }— text displayContainer { children, layout }— layout containerInput { binding, hint, appearance }— text input bound to stateImage { src, alt }— image elementToggle { binding, label }— checkbox/switch bound to boolean stateList { 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
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
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:
pub trait PlatformBackend: Send + Sync {
fn name(&self) -> &'static str;
fn create_element(&self, tag: &str) -> PlatformResult<PlatformNode>;
fn create_text(&self, content: &str) -> PlatformResult<PlatformNode>;
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<String>;
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:
[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. |