Menu
Public repository / f6f5dc7059b40f7a4042c85f7c49597638a67522
AkurAI-Framework / AGENTS_FRONTEND.md
Latest change 4ed55d17e0979479dc70291811217113b6062fa0 - docs: split AGENTS.md into domain-indexed subfiles + add breaking-changes guide by Ólafur Búi Ólafsson
# AGENTS_FRONTEND.md — the no-build frontend
> The frontend is **hand-written ES modules — no bundler, no transpiler, no npm,
> no TypeScript** (constitutional principle #4). The server renders full HTML;
> JS only *enhances*. Win DX and memory by subtraction. Everything below lives in
> `site/frontend/`; the framework serves it directly.
## The hard rule
Browser JS is native ES modules that run as-is in any modern browser. **No build
step.** Do not introduce a bundler, transpiler, `package.json`, `node_modules`,
or `.ts` files for shipped frontend code. Modules import each other by relative
path (`import { signal } from "./signals.js"`); shared libraries are exposed on
`window` (`window.Signals`, `window.Islands`, `window.SpaRouter`) for non-module
consumers. The only Node tooling allowed is dev-time Playwright e2e
([`AGENTS_TESTING.md`](./AGENTS_TESTING.md)) — it never ships.
## Progressive enhancement is non-negotiable
Every page must render and work fully from server HTML alone. JS layers
interactivity on top of already-rendered nodes — it never owns first paint, never
re-renders, never flickers. If the script is absent or fails, the page degrades
to plain HTML (links do full navigations, forms POST normally). This is the
client-first principle (#3) made concrete.
## The deferred-module `readyState` guard
Scripts load as deferred modules, so by the time they run the DOM is *usually*
already parsed and `DOMContentLoaded` may have **already fired** — a bare
`addEventListener("DOMContentLoaded", …)` could never run. Every entry point
guards on `document.readyState` instead:
```js
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
```
Used in `app.js`, `signals.js`, `islands.js`, and `spa.js`. Use this pattern for
any new auto-init module — never a bare listener.
## The runtime modules
| File | Role | Key contract |
|------|------|--------------|
| `app.js` | per-page glue | Feature-detects its targets (`if (document.getElementById(...))`) so one script is safe on every page. Pings `/api/health`; runs the realtime SSE/WS demo only where the panels exist; binds `[data-clipboard]` copy. Pure DOM, no framework. |
| `signals.js` | reactivity runtime | `signal(value)` accessor (read tracks, write notifies on real change only via `Object.is`) + `effect(fn)` (re-runs on dependency change, deps recomputed each run). Declarative directives scanned per `[data-scope]` root: `data-text`, `data-show`, `data-on:EVENT`, `data-model`, `data-for`. Exposed as `window.Signals`. |
| `islands.js` | SSR islands / hydration | `registerIsland(name, setupFn)` + `hydrate(root?)`. Hydrates ONLY `[data-island="name"]` regions in place, reusing server-rendered nodes — no virtual DOM, no re-render. Initial state read from a child `<script type="application/json" data-island-state>` or a `data-state='{…}'` attribute, so signals start at exactly the server-printed value (no flash). Builds on `signals.js`; exposed as `window.Islands`. |
| `spa.js` | client-side router | Intercepts same-origin left-clicks, fetches the next page, swaps `<main>`, updates history + title, animates via the View Transitions API where available. **Pure enhancement** — bails on old browsers and falls back to a real navigation on any error, so it can never strand the user. Respects `target`, `download`, `rel=external`, `data-no-spa`, modified clicks, hash-only links. Exposed as `window.SpaRouter`. |
**`signals.js` security note:** directive expressions evaluate via `new Function`
+ `with` against a per-scope state object — that is `eval`-class power. Only ever
feed it **author-trusted markup, never user input.** Documented at
`/docs/reactivity`.
**`signals.js` vs `islands.js`:** `signals.js` scans `[data-scope]` and wires
declarative directives generically; islands are *named components* whose wiring
you write once in JS and whose existence/initial-state the server decides. They
compose freely — an island's `setupFn` may call `signal`/`effect` itself.
## SSR templates
Server-rendered HTML comes from `crates/template` (see
[`AGENTS_CRATES.md`](./AGENTS_CRATES.md)) rendering against a JSON context the CLI
assembles from `backend/page.json` + per-route data loaders. Syntax: `{{ x }}`
(escaped), `{{{ x }}}` (raw — used for the layout `content` slot and other
trusted HTML), `{% if %}`/`{% for %}`/`{% include %}`/`{# comment #}`. **Default
to escaped output;** reserve `{{{ }}}` for already-safe HTML. Route layouts wrap a
page in a shared shell via `{{{ content }}}` (`crates/cli/src/routes.rs`).
## Design system
Tokens are plain CSS custom properties declared once on `:root` in
`site/frontend/styles.css` — no preprocessor, no theme compiler. Components
**never hard-code** a colour, radius, or shadow; they reference a token so the
system stays coherent and themeable (dark-first; a light theme is a second
`:root` token set). The design system is documented in
`site/content/docs/design-system/{foundations,components}.md` with status legend
✅ shipped · 🟡 partial · ⬜ planned. The utility-class engine that backs it is
`crates/css` (scan classes → emit only-used utilities).
## The `preview` live-preview fence
Design-system and component docs use a ` ```preview ` markdown fence
(`crates/markdown`): the body renders **twice** — once as raw, live HTML inside
`.preview-demo` (you see the real component) and once as an escaped, copyable
source block inside `.preview-code`, wrapped in `.preview` with a `.preview-label`.
This is how a doc page shows a working component *and* its copy-paste source from
one source of truth. A normal fence still escapes its body. The `.preview*`
classes are styled in `styles.css`.
## Page inventory
`site/frontend/` holds the framework's own site: `index.html`, `docs.html`,
`components.html`, `reactivity.html`, `islands.html`, `spa.html`, `realtime.html`,
`guestbook.html`, plus shared `header.html`/`footer.html` and the demo pages
(`login`, `account`, `team`, `marketing`, `welcome`, `hello`). `install.sh` here
is the public installer ([`AGENTS_DEPLOY.md`](./AGENTS_DEPLOY.md)).