For AI assistants (llms.txt)
This is the contents of /llms.txt: the rules an assistant needs to write correct Aegis code. Point your tool at that URL.
Aegis is a zero-build reactive UI engine: one ES module (aegis.js), no dependencies, no compiler.
Server renders HTML; Aegis hydrates islands ([data-aegis]) and adds fine-grained reactivity with signals.
Read aegis.d.ts for exact types, ERRORS.md for warning codes, test.html (in the repository) for working examples.
Rules (the mistakes assistants make most)#
- Reactive text is a signal or a function:
${count}or${() => count.value * 2}. NEVER${count.value}— that is a one-time snapshot (E019). - Same for helpers:
text(el, count)/show(() => open.value, …)/cls(el, 'on', isOn). A plain value renders once. - Never build HTML from data with
innerHTML. Data goes through${}inhtml``(text nodes, never parsed). Server HTML goes throughswap(). - Everything that subscribes (effects, listeners, timers, resources) must be created inside a component setup or
scope.run(); it is disposed with the scope. Outside a scope you get E001. on(el, 'click', fn)— notaddEventListener;interval/timeout/observe— not the raw APIs. They clean themselves up.- Islands:
island(name, ({ props, html }) => html…, { types })+ server<div data-aegis="name" data-x="…">; the same component function works as a custom element viaelement(tag, Component, { props }).hydrate(document)runs automatically afterisland()/register().register(name, (el, data, ctx) => …)is the positional form. data-*props are strings; declare{ types: { count: Number, on: Boolean } }inregister()to coerce. Big JSON goes in<script type="application/json">inside the island →data.props.- Async work lives in
resource()/mutation()— not ineffect(async …)(E016). - In
ctxyou get:el, slot, signal, computed, effect, batch, provide, inject, when, selector, on, delegate, bind, text, attr, cls, style, clsMap, styleMap, html, show, list, interval, timeout, observe, resize, mutate, guardedFetch (pre-bound), debounced, throttled, poll, onDispose. Everything else —importfrom./aegis.js.
Canonical island#
import { register, resource, mutation, api } from './aegis.js';
register('todos', (el, data, { signal, html, when, list }) => {
const todos = resource(data.url, { cache: true, staleTime: 30_000 }); // { data, loading, validating, error, refresh }
const draft = signal('');
const add = mutation((text) => api.post(data.url, { text }), {
resources: [todos],
optimistic: (text) => todos.mutate(l => [...l, { id: 'tmp', text }]),
invalidates: [data.url],
});
return html`
<form @submit.prevent=${() => { add(draft.value); draft.value = ''; }}>
<input bind:value=${draft} placeholder="New todo">
<button ?disabled=${add.pending}>Add</button>
</form>
${when(todos, {
loading: () => html`<p class="skeleton">…</p>`,
error: (e, retry) => html`<p>${e.message} <button @click=${retry}>Retry</button></p>`,
data: (rows, rowsSignal) => html`<ul>${list(rowsSignal, t => html`<li>${t.text}</li>`, { key: 'id' })}</ul>`,
})}`;
}, { types: { url: String } });Server side:
<div data-aegis="todos" data-url="/api/todos"></div>
<script type="module">import './aegis.js';</script> <!-- register() lives in your module; hydrate() is automatic -->Template syntax (html``)#
| Syntax | Meaning |
|---|---|
${sig} / ${() => expr} |
reactive text |
${() => cond ? html… : null} |
reactive child (node, template, array, text) |
${list(items, row => html<li>…, { key: 'id' })} |
keyed list, rows are exactly what you return |
${show(open, () => html…, null, { transition: 'fade' })} |
conditional branch with its own scope |
@click=${fn} @submit.prevent @keydown.enter @click.outside @input.debounce.300 |
events with modifiers |
.value=${sig} ?disabled=${sig} bind:value=${sig} :title=${sig} |
property / boolean attribute / two-way / reactive attribute |
class=${{ active: sig }} style=${{ '--x': sig }} |
class objects, style objects, custom properties |
<canvas ${attach(el => { const c = new Chart(el); return () => c.destroy(); })}> |
third-party widget lifecycle |
<input ${ref}> |
ref() |
API by task#
- State:
signal,computed,effect,batch,untrack,reactive,store,linked,persisted,selector,until,watch,from,history - Context:
createContext,provide,inject - Data:
resource({ cache },{ offline },{ params, loader }),mutation,streamResource,sse,prefetch,infiniteResource,settled,seed - HTTP:
configure({ csrf: 'django' }),request,api.get|post|put|patch|delete,HttpError,withRetry - DOM:
html,when,list,show,text,attr,cls,style,cssVars,bind,attach,ref,clone,flushSync - Islands:
island,element,register,hydrate(data-aegis-load="visible(300px)|idle(1500)|interaction(click)"),component,mount,destroy,defineElement,scaffold - Server HTML:
swap(morph),boost,tpl,adopt,jsonScript - Router:
router({ '/users/:id': { loader, handler, guard } }, { transition: true }),r.search('page', { parse: Number }) - Forms:
wireForm(formEl, { schema, rules }),form(defaults, { rules, schema }), rulesrequired minLen maxLen pattern email min max matches maxSize mime maxFiles - Motion:
transition,spring,springSignal,tween,flip,animate,media,reducedMotion,theme,defaults.motion - Layout:
size,inView,viewport - A11y:
trap,modal,roving,announce - Testing:
reset(),root(),settled(),onWarn(),__AEGIS_DEV__ = 'strict',defaults.fetcher,flushSync(),flush() - Debugging:
stats(),dev.profile(),dev.of(el),dev.inspect(),dev.graph(),trace(sig),effect(fn, { trace: true });recipes/andplayground.htmlhold runnable examples
Dev codes#
See ERRORS.md. Warnings print what / why / fix and appear once per place.