# Eleva.js v1.2.0 (LLM Quick Reference) > Lightweight (~2.5KB gzipped) JavaScript framework with built-in AI agent support (AX). Compact rules and patterns for generating correct Eleva.js code. Zero dependencies. Agent plugin for LLM/AI integration. For full details, see `https://elevajs.com/llms-full.txt`. ## LLM Contract (Strict Summary) > **Sync note:** This file is curated by maintainers; keep it aligned with core docs and release notes. - Must use `signal()` for state and read/write via `.value` - Must use `@event="handler"` or `@event="() => expr"`; never raw expressions - Must use `:prop="value"` (no `ctx.`); use `ctx.` only for template interpolation - If using nested components, define them via `children` mapping; use kebab-case tags (e.g., `` → `{ "user-card": "UserCard" }`) - Must clean up timers/listeners in `onUnmount`; parent-removal unmount is synchronous (old cleanup completes before new mount) - Must namespace styles manually; no auto-scoping - Must use stable `key` in lists; avoid heavy logic in templates - If using Router/Store/Agent, install via `app.use()` and access via injected context (`router`, `store`, `agent`) ## Do / Don't (Quick) | Do | Don't | |----|-------| | `signal()` for all state | Direct DOM mutation | | `@click="handler"` | `onclick="..."` or raw expressions | | `:prop="value"` (no `ctx.`) | `ctx.prop` in bindings | | `key` on every list item | Index-based or missing keys | | Cleanup in `onUnmount` | Leak timers/listeners | | `app.use(Plugin)` for plugins | Direct plugin instantiation | ## Environment Requirements | Environment | Minimum Version | |-------------|-----------------| | **Node.js** | 18.0.0+ | | **Bun** | 1.0.0+ | | **Chrome** | 71+ | | **Firefox** | 69+ | | **Safari** | 12.1+ | | **Edge** | 79+ | ## Package Subpaths | Import | Format | Use Case | |--------|--------|----------| | `eleva` | ESM/CJS | Standard import (auto-detected) | | `eleva/plugins` | ESM/CJS | All plugins bundled | | `eleva/plugins/attr` | ESM/CJS/UMD | Attr plugin (tree-shakable) | | `eleva/plugins/router` | ESM/CJS/UMD | Router plugin (tree-shakable) | | `eleva/plugins/store` | ESM/CJS/UMD | Store plugin (tree-shakable) | | `eleva/plugins/agent` | ESM/CJS/UMD | Agent plugin (tree-shakable) | | `eleva/browser` | UMD | Browser ` ``` ### Plugin Usage by Import Method | Method | Import/Include | Usage | |--------|----------------|-------| | ES Modules | `import { Router, Store, Agent } from "eleva/plugins"` | `app.use(Router, {...})` | | CDN Bundled | `eleva-plugins.umd.min.js` | `app.use(ElevaPlugins.Router, {...})` | | CDN Individual | `plugins/.umd.min.js` | `app.use(ElevaRouter, {...})` / `app.use(ElevaStore, {...})` / `app.use(ElevaAgent, {...})` / `app.use(ElevaAttr, {...})` | > Use bundled for 2+ plugins (simpler). Individual for 1 plugin (smaller bundle). UMD global pattern: `Eleva` (e.g., `ElevaRouter`, `ElevaStore`, `ElevaAgent`, `ElevaAttr`). ## Core Concepts ### Signal-Based Reactivity - `signal(value)` - Creates reactive state - `signal.value` - Get/set the value - `signal.watch(callback)` - Subscribe to changes ### Template Syntax - `${ctx.value}` - Interpolate values (requires ctx. prefix) - `@click="handler"` - Event handlers (no ctx. prefix) - `:prop="value"` - Pass props to children (no ctx. prefix) > **Tip:** Use a string `template` when you don't need `ctx`; `@event` and `:prop` still work. Template literals can use `${...}` for outer scope variables (evaluated once). > **Tip:** Use a function `template` only when you need `${ctx...}` for reactive values. > **Tip:** Same rules apply to `style`; use a function for reactive values, a string otherwise. Style functions must be synchronous. ### Lifecycle Timing (Quick Table) | Hook | When | Notes | |------|------|-------| | `onBeforeMount` | Before first render | Can be async; blocks first paint | | `onMount` | After first render | Safe for DOM access | | `onBeforeUpdate` | Before re-render | Sync/async | | `onUpdate` | After re-render | Sync/async | | `onUnmount` | Before destroy | Sync; old child cleans up before new child mounts | > **Note:** When a parent re-render removes a child component, Eleva awaits child `onUnmount` synchronously after the DOM patch. Old components fully clean up before new ones mount—no race conditions. ## Core Signatures (Minimal) ```ts new Eleva(name: string) app.component(name: string, def: ComponentDefinition): Eleva app.mount(el: HTMLElement, nameOrDef: string|ComponentDefinition, props?: object): Promise app.use(plugin: ElevaPlugin, options?: object): Eleva|unknown signal(value: T): Signal ``` ## Best-Practice Checklist - Keep components small and focused; split large templates into child components - Use `signal()` for state and return only what templates/methods need - Use `children` mapping for nested components; keep selectors explicit - Clean up timers/listeners/async work in `onUnmount` - Prefer named handlers for complex logic; keep templates simple ## Project Structure (Quick) **Simple** (small apps, widgets): ``` src/ ├── main.js, app.js ├── components/*.js ├── utils/*.js └── styles/*.css ``` **Advanced** (multi-page SPAs with routing): ``` src/ ├── main.js, app.js, router.js, store.js ├── components/{ui,common}/*.js ├── layouts/*.js ├── pages/*.js ├── utils/*.js └── styles/*.css ``` > Component styles go in the `style` property; `styles/` is for globals only. ## Performance Checklist - Use stable `key` on list items - Batch signal updates in a single tick - Avoid heavy work in templates; compute in setup/methods - Prefer `onMount` for DOM reads; avoid layout thrash in `onUpdate` ## Anti-Patterns - Heavy synchronous work in `onBeforeMount` (blocks first paint) - Updating state inside `onUpdate` without a guard (can loop) - Inline complex expressions inside templates - Missing keys in list rendering ## Canonical Patterns ### Minimal Component ```javascript app.component("Counter", { setup: ({ signal }) => { const count = signal(0); return { count, inc: () => count.value++ }; }, template: (ctx) => `` }); ``` ### Child Mapping ```javascript app.component("Parent", { setup: ({ signal }) => ({ message: signal("Hi") }), template: ``, children: { "child-comp": "Child" } }); ``` > **Rule:** Use `:prop="value.value"` for static data, `:prop="value"` for reactive updates. > **Note:** Do not `JSON.stringify` props; pass objects/signals directly (use ids for static snapshots when needed). > **Tip:** `:prop` expressions are evaluated, so primitive ids work directly (e.g., `:postId="${post.id}"`). ### Child Events (Emitter with Cleanup) ```javascript app.component("ChildButton", { setup: ({ emitter }) => ({ addTodo: () => emitter.emit("todo:add", "Buy milk") }), template: `` }); app.component("Parent", { setup: ({ signal, emitter }) => { const todos = signal([]); let unsubscribe; return { todos, onMount: () => { unsubscribe = emitter.on("todo:add", (text) => { todos.value = [...todos.value, { id: Date.now(), text }]; }); }, onUnmount: () => { if (unsubscribe) unsubscribe(); } }; }, template: ``, children: { "child-button": "ChildButton" } }); ``` ### List Rendering (Keys) ```javascript template: (ctx) => `
    ${ctx.items.value.map((item) => `
  • ${item.name}
  • `).join("")}
` ``` ### Async Setup + Error Handling ```javascript setup: ({ signal }) => { const data = signal(null); const error = signal(null); return { onMount: async () => { try { data.value = await fetch("/api").then((r) => r.json()); } catch (e) { error.value = "Failed to load"; } } }; } ``` ### Style Function (With Context, Must Be Sync) ```javascript app.component("Card", { setup: ({ signal }) => { const theme = signal("light"); const toggleTheme = () => { theme.value = theme.value === "light" ? "dark" : "light"; }; return { theme, toggleTheme }; }, template: (ctx) => `

Hello

`, style: (ctx) => ` .card { padding: 12px; border: 1px solid #ddd; background: ${ctx.theme.value === "dark" ? "#222" : "#f9f9f9"}; color: ${ctx.theme.value === "dark" ? "#fff" : "#111"}; } ` }); ``` ## Plugins (Minimal) ### Router ```javascript import { Router } from "eleva/plugins"; app.use(Router, { mount: "#app", mode: "hash", // "hash" | "history" | "query" routes: [ { path: "/", component: HomePage }, { path: "/users/:id", component: UserPage }, { path: "*", component: NotFoundPage } ] }); // Note: Nested routes (children property) not supported. Use shared layouts with flat routes instead. ``` // In component: access via setup({ router }) setup: ({ router }) => ({ userId: router.params.id, // Live getter (always current value) goHome: () => router.navigate("/") }) ``` > **No `router:notFound` Event:** If no route matches and no `*` route exists, the router emits `router:error`. Use a catch-all route or handle via `router.onError(...)`. > **Note:** `router.params`, `router.query`, `router.path`, `router.meta` are convenience getters (not signals). For reactive templates, access via `router.current.value.params`. Use `watch()` only for side effects. ### Store ```javascript import { Store } from "eleva/plugins"; app.use(Store, { state: { count: 0, user: null }, actions: { increment: (state) => state.count.value++, setUser: (state, user) => state.user.value = user } }); // In component: access via setup({ store }) setup: ({ store }) => ({ count: store.state.count, increment: () => store.dispatch("increment") }) ``` > For guards, namespaces, persistence, and more, see `https://elevajs.com/llms-full.txt`. ### Agent **Method contract** (all methods on `ctx.agent` inside `setup()`): ``` register(name, handler, schema?) → void | throws AGENT_HANDLER_NOT_FUNCTION unregister(name) → void | warns if not found execute(name, payload?, scope?) → Promise | throws AGENT_PERMISSION_DENIED | AGENT_ACTION_NOT_FOUND | AGENT_SCHEMA_VIOLATION | AGENT_HANDLER_ERROR executeBatch(actions[], scope?) → Promise | parallel execution, rejects on first error executeSequence(actions[], scope?) → Promise | sequential, pipes result → next payload hasAction(name) → boolean | describeAction(name) → descriptor|null| listActions() → descriptor[] | describe(scope?) → manifest | full capability manifest for scope dispatch(command, scope?) → Promise | throws AGENT_COMMAND_INVALID_TYPE | AGENT_PERMISSION_DENIED onCommand(type, handler) → () => void | throws AGENT_HANDLER_NOT_FUNCTION getLog(filter?) → LogEntry[] | filter by type, since, action, status ("ok"|"error") clearLog() → void | inspect() → object | only if enableInspection: true snapshot() → Snapshot | only if enableInspection: true diff(snapA, snapB) → DiffResult | only if enableInspection: true ``` **Handler receives payload only:** `(payload) => result` — NOT `(ctx, name)`. **Permissions are scope objects:** `{ "scopeName": { actions: [...], commands: [...] } }` — NOT arrays. **All errors have `error.code`** — machine-readable codes for programmatic handling. **Destroyed-state guard:** Mutating methods (`register`, `unregister`, `execute`, `executeBatch`, `executeSequence`, `dispatch`, `onCommand`, `clearLog`) can throw `AGENT_DESTROYED` when called on stale references after uninstall. **Audit log records outcomes:** entries include `result`, `error`, `durationMs` after handler completion. **Schema validation is opt-in:** set `validateSchemas: true` in options to enforce `schema.input` on execute. **Agent emits events:** `agent:register`, `agent:unregister`, `agent:execute`, `agent:execute:error`, `agent:dispatch` via `eleva.emitter`. **Reactive signals:** `agent.actionCount` (Signal\) and `agent.lastActivity` (Signal\) on `ctx.agent`. **Auto-cleanup on unmount:** Actions registered and commands subscribed via `ctx.agent` in `setup()` are automatically cleaned up when the component unmounts. ```javascript import { Agent } from "eleva/plugins"; app.use(Agent, { actions: { greet: (payload) => `Hello, ${payload.name}!`, fetchUser: async (payload) => { const res = await fetch(`/api/users/${payload.id}`); return res.json(); } }, permissions: { "ui-agent": { actions: ["greet", "fetchUser"], commands: ["REFRESH"] } }, strictPermissions: false }); // In component: access via setup({ agent }) setup: ({ agent }) => ({ run: async () => { const result = await agent.execute("greet", { name: "World" }); const log = agent.getLog(); const tools = agent.listActions(); const desc = agent.describeAction("greet"); } }) // Command bus const unsub = agent.onCommand("REFRESH", (cmd) => console.log(cmd.payload)); await agent.dispatch({ type: "REFRESH", payload: { force: true } }); unsub(); // Cleanup ``` > For full type definitions, error catalog, permission logic, and state inspection, see `https://elevajs.com/llms-full.txt`. > **Machine-readable manifest:** `https://elevajs.com/agent-manifest.json` (JSON, for tool-calling integrations). ## Troubleshooting (Quick) - Event not firing: use `@click="handler"` or `@click="() => ..."` (not raw expressions) - Props undefined in child: use `:prop="value"` without `ctx.` - Memory leaks: clean up timers/listeners in `onUnmount` ## Security Model Eleva's TemplateEngine evaluates `@events` and `:props` expressions using JavaScript's Function constructor (not sandboxed). Templates must be developer-authored code, not user-generated content. **Safe Example:** ```javascript template: (ctx) => `

${ctx.name.value}

` ``` ## Documentation Links - Full reference: https://elevajs.com/llms-full.txt - Getting Started: https://elevajs.com/getting-started - Core Concepts: https://elevajs.com/core-concepts - Components: https://elevajs.com/components - Plugin System: https://elevajs.com/plugin-system - Best Practices: https://elevajs.com/best-practices - Examples: https://elevajs.com/examples/ - Golden AI Examples: https://elevajs.com/examples/ai/ - API Reference: https://elevajs.com/api/ - FAQ: https://elevajs.com/faq