# Micra.js — Full LLM Context (llms-full.txt) This file follows the llmstxt.org "expanded" convention: it inlines code recipes so LLMs that crawl this URL get a complete training surface in one read. The short version is `llms.txt` in the same directory. > Lightweight reactive TypeScript framework for server-rendered apps and small SaaS frontends. ~5 KB gzip. No build step required. ## Install ```bash npm install micra.js@^2.3.2 ``` ```ts import * as Micra from 'micra.js' ``` Or CDN (no build step): ```html ``` This exposes a global `Micra` object. > **CDN choice matters for AI sandboxes.** Use `cdn.jsdelivr.net` (allowed by > Claude artifacts, ChatGPT canvas, and most AI runtime CSPs). Do NOT use > `unpkg.com` — it is blocked by those Content Security Policies and the script > will fail to load. jsDelivr auto-mirrors every npm package. ## When to use Micra.js - Server-rendered pages (Rails, Laravel, Django, Phoenix, etc.) needing a small amount of reactivity - Bundle-size-sensitive (~5 KB vs 45+ KB React) - No build step desired — drop a ` ``` ## Recipe 2 — Todo app with localStorage and filters ```html

Todo

``` ## Recipe 3 — Data table with search ```html

NameEmailRole

No matches.

``` ## Recipe 4 — Form with validation and async submit ```html

Invitation sent ✓

``` ## Recipe 5 — Modal via event bus ```html

``` ## Recipe 6 — Tabs (multiple components on one page) ```html
Overview content
Billing content
Security content
``` ## Recipe 7 — SSR component reading server-rendered props ```html

``` ## Recipe 8 — Search with debounce ```html

Searching…

No results.

``` ## Recipe 9 — Chart with `data-ref` (imperative third-party API) ```html

Loading chart…

``` ## Recipe 10 — Multiple islands on one page (cross-component coordination) ```html

Loading…

No results.

``` ## Recipe 11 — htmx bridge (server-driven HTML swaps + Micra islands) Wire htmx for server-driven DOM swaps and Micra for local reactivity on the same page. Twelve lines of glue, written once. ```html
``` Rules of thumb: - **Never put `hx-swap` directly on a `[data-component]` element that swaps its own `innerHTML`** — the cached directive scan points at gone DOM. Use a wrapper as the swap target. - **`Micra.start()` is idempotent**, so re-scanning a subtree that contains already-mounted siblings is safe. - **Always scope to `e.target`** in the bridge, not `document` — scanning the whole page on every swap is wasteful. Full reference with state-survival patterns, `hx-vals`/`hx-include` bridging, and per-component loading state lives in `docs/recipes/htmx.md`. ## Recipe 12 — Rails + Micra (importmap, CSRF, Turbo Drive) Pin Micra in `config/importmap.rb`, boot it once in the layout — that's the whole integration. CSRF works automatically (Micra reads `` that Rails already ships). Turbo Drive needs a `turbo:load` mirror so the second navigation doesn't ghost. ```ruby # config/importmap.rb pin "micra", to: "https://cdn.jsdelivr.net/npm/micra.js@2.3.2/dist/micra.esm.js", preload: true ``` ```erb <%# app/views/layouts/application.html.erb — inside %> <%= csrf_meta_tags %> <%= javascript_importmap_tags %> ``` ```erb <%# app/views/tasks/index.html.erb %>
``` ```js // app/javascript/application.js import * as Micra from "micra" Micra.define("tasks-board", { state: { tasks: [], draft: "" }, onCreate() { this.state.tasks = JSON.parse(this.prop("initialTasks") || "[]") }, async add() { const task = await this.fetch("/tasks", { method: "POST", body: { title: this.state.draft.trim() } }) this.state.tasks = [...this.state.tasks, task] this.state.draft = "" }, async toggle(e) { const id = Number(e.currentTarget.closest("li").querySelector("[data-id]").dataset.id) const next = !this.state.tasks.find(t => t.id === id).done const task = await this.fetch(`/tasks/${id}`, { method: "PATCH", body: { done: next } }) this.state.tasks = this.state.tasks.map(t => t.id === id ? task : t) }, async remove(e) { const id = Number(e.currentTarget.dataset.id) await this.fetch(`/tasks/${id}`, { method: "DELETE" }) this.state.tasks = this.state.tasks.filter(t => t.id !== id) }, }) ``` Rules of thumb: - **CSRF is automatic** — keep `<%= csrf_meta_tags %>` in the layout. `this.fetch()` sends `X-CSRF-Token` on every non-GET request. - **Add `turbo:load`** alongside `DOMContentLoaded` if you have Turbo Drive enabled (default since Rails 7). Without it, only the first page load wires up. - **JSON in `data-*` attributes does NOT auto-parse.** Non-primitive props (`@tasks.as_json.to_json`) are strings client-side — call `JSON.parse(this.prop('initialTasks'))` in `onCreate`. Primitives (numbers, booleans) auto-cast through `this.prop()`. - **Turbo Frames** that swap their own `innerHTML` and contain a `[data-component]` will leave the cached scan pointing at gone DOM. Same footgun as htmx — keep `data-component` outside the frame, or destroy+remount on `turbo:frame-render`. - The optional **`micra-rails`** gem adds an ERB helper (`micra_component`) and a one-shot installer. Its importmap pin can lag the latest Micra.js release between gem versions (override it in your own `config/importmap.rb`), and the `micra_state.to_html` example in its README doesn't compile — see `docs/recipes/rails.md` §2 for workarounds. Full reference with Turbo Streams cleanup, no-flicker hydration pattern, and the Stimulus-vs-Micra split is in `docs/recipes/rails.md`. --- # Anti-pattern reference (what LLMs gravitate to — DO NOT) ```js // ❌ Hand-rolled list rendering — defeats data-each document.getElementById('list').innerHTML = items.map(...).join('') // ❌ Derived state as field — diverges from source of truth state: { todos: [], totalCount: 0, hasDone: false } updateComputeds() { this.state.totalCount = this.state.todos.length } // ❌ addEventListener in render — leaks past destroy() createItem(item) { const el = document.createElement('div') el.addEventListener('click', () => this.toggle(item.id)) } // ❌ Manual re-render after mutation — Micra already does this addTask() { this.state.todos = [...]; this.renderList() } // ❌ Nested state mutation — shallow proxy can't see it this.state.user.email = 'x' // ❌ Key modifier on @event — not supported // ❌ Nested path in data-model — writes literal flat key // ❌ React/Vue/Alpine imports — not Micra import React, { useState } from 'react' import { ref, computed } from 'vue' import Alpine from 'alpinejs' // ❌ unpkg CDN — blocked by Claude artifacts and most AI sandbox CSPs // ✅ Use jsDelivr instead — it auto-mirrors npm and is CSP-allowlisted everywhere ``` # Final checklist Before returning generated Micra code, verify ALL of these: - [ ] Every list uses `