@abide/abide 0.41.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/AGENTS.md +333 -326
  2. package/CHANGELOG.md +92 -0
  3. package/README.md +91 -83
  4. package/package.json +1 -1
  5. package/src/abideLsp.ts +46 -8
  6. package/src/abideResolverPlugin.ts +3 -5
  7. package/src/lib/server/runtime/devClientFingerprint.ts +2 -1
  8. package/src/lib/shared/escapeRegex.ts +9 -0
  9. package/src/lib/shared/fileName.ts +9 -0
  10. package/src/lib/shared/fileStem.ts +3 -1
  11. package/src/lib/shared/programNameForPackage.ts +3 -1
  12. package/src/lib/shared/stripImport.ts +3 -1
  13. package/src/lib/ui/compile/ABIDE_SEMANTIC_TOKENS_LEGEND.ts +45 -0
  14. package/src/lib/ui/compile/SSR_ESCAPE.ts +3 -1
  15. package/src/lib/ui/compile/abideUiPlugin.ts +2 -1
  16. package/src/lib/ui/compile/compileShadow.ts +46 -9
  17. package/src/lib/ui/compile/createShadowLanguageService.ts +48 -0
  18. package/src/lib/ui/compile/encodeSemanticTokens.ts +37 -0
  19. package/src/lib/ui/compile/generateBuild.ts +13 -6
  20. package/src/lib/ui/compile/generateSSR.ts +23 -14
  21. package/src/lib/ui/compile/makeVarNamer.ts +10 -0
  22. package/src/lib/ui/compile/offsetToPosition.ts +9 -0
  23. package/src/lib/ui/compile/parseTemplate.ts +524 -104
  24. package/src/lib/ui/compile/skeletonContext.ts +67 -79
  25. package/src/lib/ui/compile/structuralBlockTokens.ts +101 -0
  26. package/src/lib/ui/compile/templateAnchorAdapter.ts +61 -0
  27. package/src/lib/ui/compile/templateElementAdapter.ts +44 -0
  28. package/src/lib/ui/compile/types/SemanticToken.ts +11 -0
  29. package/src/lib/ui/compile/types/TemplateNode.ts +9 -0
  30. package/src/lib/ui/compile/walkAnchorOrder.ts +57 -0
  31. package/src/lib/ui/compile/walkElementOrder.ts +60 -0
  32. package/src/lib/ui/dom/appendSnippet.ts +9 -8
  33. package/src/lib/ui/dom/appendText.ts +1 -5
  34. package/src/lib/ui/dom/applyResolved.ts +4 -9
  35. package/src/lib/ui/dom/awaitBlock.ts +37 -31
  36. package/src/lib/ui/dom/buildDetachedRange.ts +30 -0
  37. package/src/lib/ui/dom/depthZeroNodes.ts +34 -0
  38. package/src/lib/ui/dom/domAnchorAdapter.ts +29 -0
  39. package/src/lib/ui/dom/domElementAdapter.ts +20 -0
  40. package/src/lib/ui/dom/each.ts +7 -11
  41. package/src/lib/ui/dom/eachAsync.ts +12 -17
  42. package/src/lib/ui/dom/isComment.ts +6 -0
  43. package/src/lib/ui/dom/isElement.ts +6 -0
  44. package/src/lib/ui/dom/markerDepthDelta.ts +19 -0
  45. package/src/lib/ui/dom/mountRange.ts +4 -3
  46. package/src/lib/ui/dom/mountSlot.ts +4 -3
  47. package/src/lib/ui/dom/on.ts +6 -1
  48. package/src/lib/ui/dom/replaceRange.ts +24 -0
  49. package/src/lib/ui/dom/skeleton.ts +35 -92
  50. package/src/lib/ui/dom/switchBlock.ts +13 -10
  51. package/src/lib/ui/dom/when.ts +13 -10
  52. package/src/lib/ui/runtime/RANGE_MARKER.ts +16 -0
  53. package/src/lib/ui/runtime/batch.ts +22 -0
  54. package/src/lib/ui/runtime/clientPage.ts +3 -8
  55. package/src/lib/ui/runtime/createDoc.ts +12 -16
  56. package/src/lib/ui/runtime/pathSegments.ts +10 -0
  57. package/src/lib/ui/seedResolved.ts +28 -0
  58. package/src/lib/ui/startClient.ts +6 -4
  59. package/src/lib/ui/types/ResolvedFrame.ts +15 -0
  60. package/template/.zed/settings.json +4 -0
  61. package/template/src/ui/pages/page.abide +4 -4
package/AGENTS.md CHANGED
@@ -1,403 +1,410 @@
1
1
  # AGENTS.md — abide complete surface map
2
2
 
3
- > The exhaustive index of abide's public surface: every `exports` key appears once,
4
- > grouped by namespace, with its import specifier and a one-line spec so an agent
5
- > grasps the whole API in one read and knows which file to open for depth. The README
6
- > is the curated three-primitive intro (RPCs, sockets, components); `CONTEXT.md` is the
7
- > glossary and `docs/adr/` the rationale. No barrels: every public name has its own
8
- > module path, and the namespace marks the side it runs on — `abide/server/*` is
9
- > server-only, `abide/ui/*` client-only, `abide/shared/*` isomorphic (same callable,
10
- > same behaviour on both sides). Package `@abide/abide`, single Bun runtime (≥ 1.3.0),
11
- > one direct dependency (`typescript`). Bullets show the import specifier; the file
12
- > path it resolves to lives in `package.json` `exports`.
3
+ > The exhaustive index of abide's public surface: every `exports` key appears
4
+ > once, grouped by namespace, with its import specifier and a one-line spec, so
5
+ > an agent grasps the whole API in one read. The README is the curated
6
+ > three-primitive intro (RPCs, sockets, components); this map is complete.
7
+ > `CONTEXT.md` is the glossary; `docs/adr/` holds the rationale.
8
+ >
9
+ > **No barrels.** Every public name has its own module path —
10
+ > `@abide/abide/server/GET`, `@abide/abide/shared/cache`, `@abide/abide/ui/scope`. The namespace
11
+ > marks the side a name runs on: `@abide/abide/server/*` is server-only,
12
+ > `@abide/abide/ui/*` is client-only, `@abide/abide/shared/*` is isomorphic (same callable,
13
+ > same behaviour on both sides — the bundler swaps the runtime). There is no
14
+ > umbrella `index.ts`, so importing one name never drags in side-effecting
15
+ > siblings.
16
+ >
17
+ > Package `@abide/abide`, runtime Bun ≥ 1.3.0, one direct dependency
18
+ > (`typescript`); `tailwindcss` + `bun-plugin-tailwind` are optional peers.
19
+ > Import specifiers below are `package.json` `exports` keys, not file paths —
20
+ > `@abide/abide/server/GET` resolves to `src/lib/server/GET.ts`.
13
21
 
14
22
  ## The premise
15
23
 
16
- One typed verb declaration fans out to five surfaces:
24
+ One typed verb declaration fans out to every consumer:
25
+
17
26
  ```text
18
27
  getMessages = GET(fn, { inputSchema })
19
28
 
20
- ┌───────────┬─────────────┼────────────┬──────────────┐
21
- SSR call browser fetch MCP tool CLI command OpenAPI op
22
- cache(fn)() typed proxy() (read-only) abide ... cli /openapi.json
29
+ ┌─────────────┬─────────┼──────────┬──────────────┐
30
+ ▼ ▼ ▼ ▼ ▼
31
+ SSR call browser MCP tool CLI sub- OpenAPI
32
+ cache(fn)() fetch (read) command operation
33
+ proxy
23
34
  ```
24
35
 
25
- A schema unlocks the CLI everywhere and MCP for read-only verbs; a mutating verb
26
- never auto-exposes to MCP — it needs explicit `clients: { mcp: true }`.
36
+ A Standard Schema unlocks the CLI for every verb and MCP for read-only verbs
37
+ (`GET` / `HEAD`); a mutating verb never auto-exposes to MCP — it opts in with
38
+ `clients: { mcp: true }`. The same gating applies to sockets: a schema flips
39
+ the MCP/CLI read faces on.
27
40
 
28
41
  ## File-based conventions
29
42
 
30
- The bundler reads these paths by convention:
31
-
32
- | Path | Meaning |
33
- | --- | --- |
34
- | `src/server/rpc/<name>.ts` | One HTTP verb per file; the path is the route, the schema projects MCP/CLI/OpenAPI |
35
- | `src/server/sockets/<name>.ts` | One broadcast socket per file; file name is the topic |
36
- | `src/server/prompts/<name>.md` | One MCP prompt per file; `{{name}}` placeholders, optional frontmatter |
37
- | `src/server/config.ts` | Optional `env(schema)` validation, run at boot |
38
- | `src/app.ts` | Optional `AppModule` lifecycle hooks (`init`/`handle`/`handleError`/`health`/`forwardHeaders`) |
39
- | `src/bundle/window.ts` | Optional desktop `BundleWindow` config (title, size, menus, setup form) |
40
- | `src/ui/pages/<path>/page.abide` | A route; folder path is the URL, `[id]` segments are params |
41
- | `src/ui/pages/<path>/layout.abide` | A layout wrapping pages at or below its folder |
42
- | `src/ui/public/` | Static assets served from `/`, embedded in the standalone binary |
43
- | `src/.abide/*.d.ts` | Generated route/rpc/health types do not hand-edit |
44
- | `dist/_app/` | Generated client bundle (code-split, hashed chunks) |
45
-
46
- Aliases: `$server` `src/server/`, `$ui` `src/ui/`, `$shared` `src/shared/`,
47
- `$mcp` → `src/mcp/`, `$cli` → `src/cli/`.
43
+ The bundler reads these paths by convention (`cwd` is the app root):
44
+
45
+ | Path | Meaning |
46
+ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
47
+ | `src/server/rpc/<name>.ts` | One RPC verb per file; export name = filename = URL under `/rpc/`. Server: real handler; client: `remoteProxy`. |
48
+ | `src/server/sockets/<name>.ts` | One socket per file; export name = filename. Server: `defineSocket`; client: `socketProxy`. |
49
+ | `src/server/config.ts` | Optional. Eager-imported for boot-time `env()` validation (virtual `abide:config`). |
50
+ | `src/mcp/prompts/*.md` | MCP prompts; frontmatter (description, JSON Schema) + `{{name}}` body. Rewritten to `definePrompt`. |
51
+ | `src/mcp/resources/**` | MCP resource files, gzip-embedded into the standalone binary. |
52
+ | `src/app.ts` | Optional `AppModule` `init` / `handle` / `handleError` / `health` / `forwardHeaders` hooks (virtual `abide:app`). |
53
+ | `src/bundle/window.ts` | Optional desktop-bundle config (`BundleWindow`); baked into the launcher. |
54
+ | `src/bundle/disconnected.abide` | Optional custom connect screen; falls back to the built-in component. |
55
+ | `src/ui/pages/**/page.abide` | A page; its folder path is the route. `[id]` folder = dynamic segment → `page.params.id`. |
56
+ | `src/ui/pages/**/layout.abide` | A layout wrapping pages at/below its folder; its `<slot/>` is the router outlet. |
57
+ | `src/ui/public/**` | Static assets served at site root; gzip-embedded into the standalone binary. |
58
+ | `src/.abide/*.d.ts` | Generated types written each build — `routes`, `rpc`, `sockets`, `health`, `publicAssets`, and test surfaces. |
59
+ | `dist/_app/` | Client bundle output (hashed chunks; prod adds precompressed `.gz` siblings). |
48
60
 
49
61
  ## CLI
50
62
 
51
- | Command | Does |
52
- | --- | --- |
53
- | `abide scaffold <name>` | Copy the template, install, start dev (`--no-install`, `--no-dev`) |
54
- | `abide dev` | Build + run with hot reload; watches `src/`, rebuilds client, restarts server |
55
- | `abide build` | One-shot client build into `dist/_app/` |
56
- | `abide check` | Type-check `.abide` templates + props via the shadow language service |
57
- | `abide start` | Run the production server against a pre-built `dist/` |
58
- | `abide run <file> [args]` | Run a script under the abide preload (jobs, tests) |
59
- | `abide compile` | Build a standalone server executable with embedded assets |
60
- | `abide cli` | Build a thin CLI binary that ships the server (`--platforms` cross-compiles) |
61
- | `abide bundle` | Build a movable, self-contained desktop app bundle for this platform (unsigned `.app` on macOS, flat dir elsewhere) |
62
- | `abide lsp` | Run the `.abide` language server over stdio (JSON-RPC editor diagnostics) |
63
- | `abide init-agent` | Write/refresh a `CLAUDE.md` pointer to this surface map |
64
-
65
- Tests preload the framework: `preload = ["@abide/abide/preload"]` in `bunfig.toml`
66
- registers `.abide` compilation so `bun test` runs the same runtime as the server.
67
-
68
- ## Authoring contracts
69
-
70
- What you write into each convention path, and the contract the framework holds
71
- you to. The per-export specs below are exhaustive; this is the shape of the code
72
- that produces them.
73
-
74
- ### RPC verb — `src/server/rpc/<name>.ts`
75
-
76
- - One `export const <name> = METHOD(handler, opts?)` per file (`METHOD` ∈ `GET`/`POST`/`PUT`/`PATCH`/`DELETE`/`HEAD`); the file path is the route.
77
- - The handler receives the **validated** input — `InferOutput<inputSchema>`, or `{}` when schemaless — and may read request scope via `request()` / `cookies()`. It returns `json()`, `jsonl()`, `sse()`, `error()`, `redirect()`, or a raw `Response`; the union of those branches is the verb's typed result.
78
- - `opts`: `{ inputSchema?, outputSchema?, filesSchema?, clients?: { browser, mcp, cli }, crossOrigin?, maxBodySize?, timeout? }`. A Standard Schema flips MCP + CLI on automatically; a **mutating** verb still needs explicit `clients: { mcp: true }`. `crossOrigin: true` exempts a mutating verb from the same-origin CSRF check. `timeout` (ms) bounds the handler on every surface (504 past it). `filesSchema` validates multipart `File` parts (the caller sends `FormData`).
79
- - Query args arrive as **strings** — validate with `z.coerce.*`.
80
- - Consume four ways: `cache(verb)(args)` in-process (SSR, warm hydration), the same call over a swapped `fetch` in the browser, `verb.raw(args)` for the undecoded `Response`, `verb.stream(args)` for an iterable body view.
81
-
82
- ### Socket — `src/server/sockets/<name>.ts`
83
-
84
- - One `export const <name> = socket({ schema, tail?, ttl?, clientPublish?, clients? })`; the file name is the topic.
85
- - `.publish(msg)` validates then broadcasts; `.tail(n?)` replays the retained window then streams live; bare `for await` is live-only. `tail` = frames retained, `ttl` = frame expiry (ms), `clientPublish` = allow a browser `POST` (default off, so route writes through a verb that validates first).
86
- - HTTP face `/__abide/sockets/<name>`: `GET` returns the tail, `POST` publishes (gated by `clientPublish`).
87
- - Consume in a component with `tail(socket)` (latest-wins) or `tail(socket, { last: n })` (window).
88
-
89
- ### Page & layout — `src/ui/pages/**`
90
-
91
- - `page.abide` is a route; the folder path is the URL. `[id]` segments become params read off the `page` proxy (`page.params.id`), typed via generated `src/.abide/routes.d.ts`, reactive across navigation.
92
- - `layout.abide` wraps every page at or below its folder; its `<slot/>` (compiled to an outlet) holds the nested page or child layout.
93
- - Build links with `url('/pages/product/[id]', { id })` and move with `navigate(href)`.
94
-
95
- ### App module & config — `src/app.ts`, `src/server/config.ts` (both optional)
63
+ ```text
64
+ abide scaffold <name> scaffold the bundled template, install deps, start dev
65
+ abide dev build client + run server with hot reload
66
+ abide build single client build into dist/_app
67
+ abide start run the production server against a built dist/
68
+ abide run <file> [...] run a script under the abide preload (same runtime)
69
+ abide compile build a standalone server executable
70
+ abide cli build a thin CLI binary (remote client + bundled server)
71
+ abide bundle build a movable self-contained desktop app bundle
72
+ abide check type-check every .abide template + props
73
+ abide lsp run the .abide language server over stdio (JSON-RPC)
74
+ abide init-agent write/refresh the CLAUDE.md pointer to this surface map
75
+ ```
96
76
 
97
- - `src/app.ts` exports the `AppModule` hooks: `forwardHeaders`, `init`, `handle`, `handleError`, `health`.
98
- - `src/server/config.ts` exports `env(schema)`, validated against `Bun.env` at boot; the typed result is your config and seeds the bundle launcher's setup form.
77
+ `bun test` in a scaffolded app preloads abide via `bunfig.toml`
78
+ (`[test] preload = ["@abide/abide/preload"]`) so `.abide` modules resolve.
99
79
 
100
- ### The isomorphism move
80
+ ## Authoring contracts
101
81
 
102
- Import a server verb (or socket) straight into a `.abide` and call it: during SSR
103
- the bundler keeps the real handler (it runs in-process), in the browser it swaps
104
- to a `fetch` proxy of the same signature, same name. Wrap reads in `cache()` so the
105
- SSR pass serializes the value into the snapshot and the client hydrates warm
106
- instead of refetching.
82
+ **RPC verb** `export const <name> = GET(handler, opts?)` (same shape for
83
+ `POST` / `PUT` / `PATCH` / `DELETE` / `HEAD`). The handler receives the parsed
84
+ args bag (`InferOutput<inputSchema>`, merged with validated files when
85
+ `filesSchema` is set), may read `request()` / `cookies()` / `server()` from
86
+ request scope, and returns `json` / `jsonl` / `sse` / `error` / `redirect` or a
87
+ raw `Response`. `opts`: `inputSchema` (validates args, infers the type, gates
88
+ CLI + read-MCP), `outputSchema` (documents the 200 body for OpenAPI/MCP),
89
+ `filesSchema` (multipart File parts), `clients: { browser, mcp, cli }` (explicit
90
+ surface targeting; explicit wins over schema auto-flip), `crossOrigin` (exempt
91
+ a mutating verb from the same-origin gate), `maxBodySize` (per-verb 413
92
+ ceiling), `timeout` (handler deadline in ms; 504 on every surface). Query args
93
+ arrive as strings — use `z.coerce.*`. Consume four ways: `cache(verb)(args)`
94
+ in-process, the swapped browser `fetch`, `verb.raw(args)` for the `Response`,
95
+ `verb.stream(args)` to iterate a streaming body.
96
+
97
+ **Socket** — `export const <name> = socket({ schema?, tail?, ttl?,
98
+ clientPublish?, clients? })`. `tail` retains the last N frames for late joiners
99
+ and the read faces; `ttl` lazily evicts frames older than N ms; `clientPublish`
100
+ (default off) gates browser publishes; `schema` validates publishes
101
+ synchronously and flips MCP/CLI on. Isomorphic `AsyncIterable<T>`: bare
102
+ iteration is the live stream, `.tail(count?)` seeds from retention,
103
+ `.publish(msg)` fans out in-process + over the multiplexed ws.
104
+
105
+ **Page / layout** — a `[id]` folder segment becomes `page.params.id`. A layout's
106
+ `<slot/>` is the outlet the next layer fills. Read `page` (route, params, url,
107
+ navigating) and call `navigate(path)` for SPA transitions.
108
+
109
+ **App / config** — `src/app.ts` default-exports an `AppModule`; `src/server/config.ts`
110
+ runs `env(schema)` at boot.
111
+
112
+ **Isomorphism move** — wrap an SSR read in `cache(fn)()` so the value serializes
113
+ into the document and the client hydrates warm instead of refetching.
107
114
 
108
115
  ## .abide template grammar
109
116
 
110
- A `.abide` file is one component: an optional `<script>` (module scope), HTML
111
- markup with reactive interpolation and `<template>` control flow, and optional
112
- component-scoped `<style>`. Ambient in `<script>` no import — are `scope`,
113
- `props`, `effect`, `html`, and `snippet`; everything else (`cache`, `page`,
114
- `navigate`, `tail`, verbs, sockets, child components) is imported normally.
115
-
116
- ```html
117
- <script>
118
- import { cache } from '@abide/abide/shared/cache'
119
- import { getThing } from '$server/rpc/getThing.ts'
120
- import Child from '$ui/Child.abide'
121
-
122
- const { title = 'untitled' } = props<{ title?: string }>() // reactive props
123
- let count = scope().state(0) // writable cell
124
- const doubled = scope().computed(() => count * 2) // read-only derived
125
- effect(() => console.log(count)) // re-runs on change
126
- </script>
127
-
128
- <h1>{title}: {doubled}</h1> <!-- {expr} interpolation -->
129
- <button onclick={() => (count = count + 1)}>inc</button> <!-- on<event>={fn} -->
130
- <Child label={title}><p>slot body</p></Child> <!-- caps tag = component -->
131
-
132
- <style>h1 { font-weight: 600 }</style> <!-- component-scoped -->
133
- ```
134
-
135
- Reactive state `scope()` is the only reactive surface:
136
-
137
- | Form | Meaning |
138
- | --- | --- |
139
- | `scope().state(v)` | writable cell read by name, assign with `=` |
140
- | `scope().computed(fn)` | read-only derived; re-runs when its read cells change |
141
- | `scope().linked(fn)` | local draft reseeded from upstream on source change |
142
- | `effect(fn)` | side effect; re-runs on change; return a teardown |
143
- | `props()` / `props<T>()` | destructure reactive props, defaults allowed |
144
-
145
- Interpolation & bindings:
146
-
147
- | Syntax | Effect |
148
- | --- | --- |
149
- | `{expr}` | reactive text, HTML-escaped; `{html(s)}` inserts trusted raw HTML |
150
- | `name={expr}` | reactive attribute (omitted when nullish) |
151
- | `on<event>={fn}` | event listener `onclick`, `onsubmit`, … |
152
- | `bind:value={cell}` | two-way bind to input / textarea / select |
153
- | `bind:checked={cell}` | checkbox boolean bind |
154
- | `bind:group={cell}` | radio (single value) / checkbox group (array) |
155
- | `bind:value={{ get, set }}` | writable computed a lens over derived state |
156
-
157
- Control flownative `<template>`:
158
-
159
- | Directive | Shape |
160
- | --- | --- |
161
- | `<template if={c}>` `<template elseif={c2}>` … `<template else>` | conditional chain (`else` must be last) |
162
- | `<template each={list} as="x" key="x.id" index="i">` | keyed list (`index` binds the row's reactive position) |
163
- | `<template await={p}>` `<template then="v">` `<template catch="e">` `<template finally>` | promise (streams; branch value bound by `then`/`catch`) |
164
- | `<template switch={s}>` `<template case={v}>` … `<template default>` | first strict-`===` match |
165
- | `<template try>` `<template catch="e">` `<template finally>` | synchronous error boundary |
166
- | `<template name="row" args={p}>` … `{row(p)}` | snippet — a named builder that closes over scope, rendered like a function and passable as a prop |
167
-
168
- Components & slots — a capitalized tag is an imported component; its attributes
169
- are props; child markup fills the default `<slot/>`, and `<el slot="footer">` fills
170
- `<slot name="footer">`. A `then`/`catch` branch (and any nested `<script>` / `<style>`)
171
- is its own lexical scope.
172
-
173
- ## Server surface — abide/server/*
174
-
175
- ### RPC verbs — @documentation rpc
176
-
177
- - `@abide/abide/server/GET` — HTTP verb helper; the bundler rewrites `export const x = GET(fn, opts)` in `src/server/rpc/<file>.ts` to `defineVerb` server-side and `remoteProxy` client-side.
178
- - `@abide/abide/server/POST` — as `GET`, for `POST`.
179
- - `@abide/abide/server/PUT` — as `GET`, for `PUT`.
180
- - `@abide/abide/server/PATCH` — as `GET`, for `PATCH`.
181
- - `@abide/abide/server/DELETE` — as `GET`, for `DELETE`.
182
- - `@abide/abide/server/HEAD` — as `GET`, for `HEAD`.
183
- - `@abide/abide/server/rpc/defineVerb` — the verb factory (plumbing): handler + options (`inputSchema`, `outputSchema`, `filesSchema`, `clients`, `timeout`, `maxBodySize`, `crossOrigin`); returns a `RemoteFunction` — plain call decodes by Content-Type and throws `HttpError` on non-2xx, `.raw(args)` returns the `Response`, `.stream(args)` an iterable body view; auto-exposes to CLI/MCP per schema + method.
184
-
185
- ### Response helpers — @documentation response
186
-
187
- - `@abide/abide/server/json` — JSON `Response`, `Cache-Control: no-store`; `undefined` → 204; phantom `TypedResponse` brand carries the return type.
188
- - `@abide/abide/server/jsonl` — wraps `AsyncIterable<Frame>` as JSON Lines; frame errors emit a `{"$error":...}` line; cancellation flows to `iter.return()`.
189
- - `@abide/abide/server/sse` — wraps `AsyncIterable<Frame>` as Server-Sent Events; 15s keepalive comments, error frames as `event: error`.
190
- - `@abide/abide/server/error` — plain-text error `Response(status, message?, init?)`; defaults to the standard status text; `TypedResponse<never>` so error branches union cleanly.
191
- - `@abide/abide/server/redirect` — redirect `Response(url, status=302, init?)`; relative URLs allowed, 301/302/303/307/308, `TypedResponse<never>`.
117
+ A component file is `<script>` (module-level JS: imports, handlers,
118
+ reactive declarations) + optional `<template name="…">` snippet definitions +
119
+ markup + an optional component-scoped `<style>`. Ambient in-scope names need no
120
+ import: `scope`, `props` / `prop`, `effect`, `html`, `snippet`.
121
+
122
+ **Reactive state**
123
+
124
+ | Form | Meaning |
125
+ | ---------------------------------- | ---------------------------------------------------------------------------------- |
126
+ | `scope().state(v)` | Writable reactive cell (`{ value }`); the sole writable surface. |
127
+ | `scope().computed(fn)` | Read-only derived cell; re-runs when its reads change. |
128
+ | `scope().linked(initial, reseed?)` | Cell bound to a reactive-document path; reflects/drives the root doc. |
129
+ | `effect(fn)` | Runs now, re-runs on dep change; may return a teardown; returns a disposer. |
130
+ | `props()` / `prop(name)` | Read the component's props (thunks for reactivity), e.g. `const { id } = props()`. |
131
+
132
+ **Bindings**
133
+
134
+ | Form | Meaning |
135
+ | --------------------------- | -------------------------------------------------------------------------------------------- |
136
+ | `{expr}` | Escaped text interpolation; raw markup via the `html` tag; `{snippet(args)}` mounts a snippet. |
137
+ | `name={expr}` | Reactive attribute (boolean `true` sets bare, falsy removes). |
138
+ | `on<event>={fn}` | Event listener, e.g. `onclick={handler}`. |
139
+ | `bind:value={x}` | Two-way input binding. |
140
+ | `bind:checked={x}` | Two-way checkbox binding. |
141
+ | `bind:group={x}` | Radio/checkbox group (radio → one value, checkbox → array). |
142
+ | `bind:value={{ get, set }}` | Writable computed at the binding site. |
143
+ | `{...expr}` | Spread keys as attributes (elements) or props (components). |
144
+ | `attach={fn}` | Build-time element attachment with optional teardown. |
145
+
146
+ **Control flow** — `{#…}` blocks (each head reads as the JS clause it lowers to):
147
+
148
+ | Form | Meaning |
149
+ | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
150
+ | `{#if c}` / `{:else if c}` / `{:else}` / `{/if}` | Conditional, source-order, re-evaluated reactively. |
151
+ | `{#for x of list}` / `{#for x, i of list by x.id}` / `{/for}` | Keyed list (`for…of`); `, i` index reactive; `by` key reconciles rows in place; key defaults to item. |
152
+ | `{#for await x of asyncIter by x.id}` … `{:catch e}` / `{/for}` | Async keyed list (`for await…of`); rows append as the iterator yields. |
153
+ | `{#await p}` / `{:then v}` / `{:catch e}` / `{:finally}` / `{/await}` | Promise branches; pending content (before `{:then}`) streams. |
154
+ | `{#await p then v}` / `{/await}` | Blocking: no pending, resolved inline (SSR settles before the first flush). |
155
+ | `{#switch s}` / `{:case v}` / `{:default}` / `{/switch}` | First strict (`===`) match wins. |
156
+ | `{#try}` / `{:catch e}` / `{:finally}` / `{/try}` | Synchronous render error boundary. |
157
+
158
+ **Reusable markup**`<template>`:
159
+
160
+ | Form | Meaning |
161
+ | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
162
+ | `<template name="row" args={item}>` `{row(item)}` | Named template (snippet) definition + call. A `<template>` with a control-flow attribute (`if`/`each`/…) is a compile error — use the `{#…}` block. |
163
+
164
+ **Components & slots** a capitalised tag (`<Card title={x}>`) mounts a child;
165
+ attributes become props, children fill the child's `<slot>` (with fallback when
166
+ empty). `<style>` is scoped to the component and its children. Component files
167
+ end in `.abide`. A block's binding names (`{:then v}`, `{#for x of …}`) lexically
168
+ shadow same-named component state inside the block.
169
+
170
+ ## Server surface abide/server/\* (server-only)
171
+
172
+ ### RPC @documentation rpc
173
+
174
+ - `@abide/abide/server/GET` — declare a `GET` verb; args from the query string. Schema auto-exposes CLI + read-MCP.
175
+ - `@abide/abide/server/POST`declare a `POST` verb; args from JSON/form/multipart body (body wins on collision).
176
+ - `@abide/abide/server/PUT` declare a `PUT` verb (body args; mutating).
177
+ - `@abide/abide/server/PATCH` declare a `PATCH` verb (body args; mutating).
178
+ - `@abide/abide/server/DELETE` declare a `DELETE` verb (query args; mutating).
179
+ - `@abide/abide/server/HEAD` — declare a `HEAD` verb (query args; read-only, headers-only response).
180
+
181
+ ### Response — @documentation response
182
+
183
+ - `@abide/abide/server/json` — `json(data, init?)`: JSON `TypedResponse` (`Cache-Control: no-store`); `undefined` → 204.
184
+ - `@abide/abide/server/jsonl` — `jsonl(asyncIterable, init?)`: JSONL stream, one value per line; errors emit a `{"$error"}` final line.
185
+ - `@abide/abide/server/sse` — `sse(asyncIterable, init?)`: Server-Sent Events with 15s keepalive comments; errors as an `error` event.
186
+ - `@abide/abide/server/error` — `error(status, message?, init?)`: plain-text error `TypedResponse<never>` (narrows out of the success union).
187
+ - `@abide/abide/server/redirect` — `redirect(url, status=302, init?)`: redirect accepting relative URLs; `TypedResponse<never>`.
192
188
 
193
189
  ### Request scope — @documentation request-scope
194
190
 
195
- - `@abide/abide/server/request` — the inbound `Request` for the current SSR/RPC pass (AsyncLocalStorage); throws outside request scope.
196
- - `@abide/abide/server/cookies` — `Bun.CookieMap` for the in-flight request; `.set()`/`.delete()` flush to `Set-Cookie`; throws outside scope.
197
- - `@abide/abide/server/server` — the active `Bun.serve` instance (getter, shows in stack traces); in-process server inside scope when none is bound.
191
+ - `@abide/abide/server/request` — `request()`: the inbound `Request` from request-scoped ALS; throws outside scope.
192
+ - `@abide/abide/server/cookies` — `cookies()`: live `Bun.CookieMap`; writes flush to `Set-Cookie`; throws outside scope.
193
+ - `@abide/abide/server/server` — `server()`: the active `Bun.serve` instance (in-process shim for CLI/MCP/test dispatch).
198
194
 
199
195
  ### Configuration — @documentation configuration
200
196
 
201
- - `@abide/abide/server/env` — validate `Bun.env` against a Standard Schema, returns typed config; fails fast listing all issues; registers the schema for the bundle launcher's setup form.
202
-
203
- ### Reachability — @documentation observability
204
-
205
- - `@abide/abide/server/reachable` — server-only probe of an external host: awaits a `HEAD` (`ABIDE_REACHABLE_TIMEOUT`), then background-polls at `ABIDE_REACHABLE_TTL`; returns a boolean, status-agnostic (any completed HTTP counts).
197
+ - `@abide/abide/server/env` — `env(schema)`: validate `process.env` against a Standard Schema at boot (synchronous; reports all issues).
206
198
 
207
199
  ### Sockets — @documentation sockets
208
200
 
209
- - `@abide/abide/server/socket` — `Socket<T>` declaration; the bundler placeholder throws if called outside `export const <file> = ...` in `src/server/sockets/`.
210
- - `@abide/abide/server/sockets/defineSocket` — server socket factory (plumbing): `name` + `{ tail, ttl, schema, clientPublish, clients }`; `.publish(msg)` validates then broadcasts via `server.publish`, `.tail(count?, hooks?)` replays history then streams live, bare iteration is live-only; auto-MCP/CLI when a schema is present.
201
+ - `@abide/abide/server/socket` — `socket({ schema?, tail?, ttl?, clientPublish?, clients? })`: declare a broadcast topic; isomorphic `Socket<T>`.
211
202
 
212
203
  ### Agent — @documentation agent
213
204
 
214
- - `@abide/abide/server/agent` — run the AgentEngine (surface + messages `AsyncIterable<AgentFrame>`) against the request's MCP surface; wrap the stream with `jsonl()`/`sse()`, auth forwarded per tool.
205
+ - `@abide/abide/server/agent` — `agent(engine, messages)`: run an `AgentEngine` against the request's MCP surface; yields `AgentFrame`s (wrap in `jsonl`/`sse`). Exports the `AgentEngine` / `AgentSurface` / `NeutralMessage` / `AgentFrame` types; provider engines ship in `@abide/<provider>`.
215
206
 
216
- ### Prompts — @documentation plumbing
207
+ ### Observability — @documentation observability
217
208
 
218
- - `@abide/abide/server/prompts/definePrompt` — prompt factory: `name` + `{ description, jsonSchema, render }`; registered for MCP enumeration (the resolver generates these from `src/server/prompts/*.md`).
219
- - `@abide/abide/server/prompts/renderPromptTemplate` — substitute `{{name}}` placeholders in a markdown template from a `Record<string,string>`; missing args render empty.
209
+ - `@abide/abide/server/reachable` — `reachable(host)`: cached outbound reachability HEAD (first call probes, then polls every TTL); any HTTP response counts as up. Tuned by `ABIDE_REACHABLE_TTL` / `ABIDE_REACHABLE_TIMEOUT`.
220
210
 
221
- ### App + inspector hooks — @documentation plumbing
211
+ ### Plumbing — @documentation plumbing
222
212
 
223
- - `@abide/abide/server/AppModule` — the `src/app.ts` hook contract: `forwardHeaders`, `init(ctx)`, `handle(req, next)`, `handleError(err, req)`, `health(req)`.
224
- - `@abide/abide/server/InspectorContext` — capabilities handed to `@abide/inspector` when enabled: identity, `loadSurface()`, `cacheSnapshot()`, `inFlightSnapshot()`, `onRecord(listener)`.
213
+ - `@abide/abide/server/AppModule` — type of `src/app.ts`: optional `forwardHeaders`, `init`, `handle`, `handleError`, `health` hooks.
214
+ - `@abide/abide/server/InspectorContext` — type of the capability bundle the core injects into `@abide/inspector` when `ABIDE_ENABLE_INSPECTOR=true`.
215
+ - `@abide/abide/server/rpc/defineVerb` — `defineVerb(method, url, handler, opts?)`: low-level builder the GET/POST/… helpers wrap.
216
+ - `@abide/abide/server/sockets/defineSocket` — `defineSocket(name, opts?)`: server-side `Socket` construction (per-subscriber queue, shared retained tail).
225
217
 
226
- ## Isomorphic surface — abide/shared/*
218
+ ## Isomorphic surface — abide/shared/\* (same callable both sides)
227
219
 
228
- ### RPC contract — @documentation rpc
220
+ ### Cache — @documentation cache
229
221
 
230
- - `@abide/abide/shared/withJsonSchema` — attach a `toJSONSchema()` to a Standard Schema, enabling projection to OpenAPI, MCP tools, CLI help, and bundle forms.
222
+ - `@abide/abide/shared/cache` — `cache(fn, opts?)`: coalesce/memoize a remote verb or producer per store (request-scoped server, tab-scoped client). `opts`: `global` (process store), `ttl`, `swr`. Methods: `cache.invalidate`, `cache.on`, `cache.patch`. Warm SSR reads resolve synchronously on hydrate.
231
223
 
232
- ### Errors — @documentation response
224
+ ### Templating — @documentation templating
233
225
 
234
- - `@abide/abide/shared/HttpError` — `Error` subclass thrown by remote calls on non-2xx; carries `status`, `statusText`, and the raw `Response`.
226
+ - `@abide/abide/shared/html` — `html` (tagged template or `html(str)`): mark a string as trusted raw HTML so `{expr}` inserts nodes verbatim (no auto-escape).
227
+ - `@abide/abide/shared/snippet` — `snippet(payload)`: brand a snippet payload (client DOM builder / server HTML string) so `{expr}` mounts it.
235
228
 
236
- ### Cache — @documentation cache
229
+ ### Response — @documentation response
237
230
 
238
- - `@abide/abide/shared/cache` — wrap a sync/async callable (remote or producer) with deduped, optional-TTL storage; SSR-aware streaming hydration, stale-while-revalidate, and optimistic mutation.
231
+ - `@abide/abide/shared/HttpError` — `class HttpError extends Error`: thrown by remote calls on non-2xx; carries `status`, `statusText`, the raw `response`.
239
232
 
240
- ### Templating — @documentation templating
233
+ ### RPC — @documentation rpc
241
234
 
242
- - `@abide/abide/shared/html` — mark a string as trusted raw HTML inserted verbatim in interpolations; plain-call and tagged-template forms.
243
- - `@abide/abide/shared/snippet` — brand a snippet payload (DOM builder on the client, HTML string on the server) so interpolations mount it instead of escaping.
235
+ - `@abide/abide/shared/withJsonSchema` — `withJsonSchema(schema, toJsonSchema)`: attach a `toJSONSchema()` method (for schemas lacking one) feeding OpenAPI/MCP/CLI/bundle forms.
244
236
 
245
237
  ### Page — @documentation page
246
238
 
247
- - `@abide/abide/shared/page` — reactive page snapshot: matched route, decoded params, browser-space URL, and a `navigating` flag; isomorphic.
239
+ - `@abide/abide/shared/page` — reactive `page` proxy: `{ route, params, url, navigating }`; server reads per-request store, client reads the router snapshot.
248
240
 
249
- ### Probes — @documentation probes
241
+ ### URL — @documentation url
250
242
 
251
- - `@abide/abide/shared/pending` — reactive: any cache call in-flight, any stream awaiting its first frame, or a tagged group pending.
252
- - `@abide/abide/shared/refreshing` — reactive: a cache call holds a value while revalidating, or a stream reconnects with its window retained.
253
- - `@abide/abide/shared/online` — reactive connectivity: `navigator.onLine` on the client, the request's `OFFLINE_HEADER` on the server.
254
- - `@abide/abide/shared/health` — reactive backend health: a `reachable` flag polled from `/__abide/health` every 10s while visible, plus app `health()` fields; `navigator.onLine` composes at read.
243
+ - `@abide/abide/shared/url` — `url(path, ...args)`: base-correct, typed in-app URLs for RPCs (args query), page routes (`[name]` params), and assets; external URLs pass through untouched.
255
244
 
256
- ### URL — @documentation url
245
+ ### Probes — @documentation probes
257
246
 
258
- - `@abide/abide/shared/url` — resolve in-app URLs to base-correct typed form: verb args serialize to query, page routes interpolate params, assets pass through; external URLs untouched.
247
+ - `@abide/abide/shared/pending` — `pending(selector?, args?)`: reactive any/specific/exact/tagged call in flight, or a stream awaiting its first frame.
248
+ - `@abide/abide/shared/refreshing` — `refreshing(selector?, args?)`: reactive — a value held while fresh data is in flight (SWR refetch / reconnect with retained value).
249
+ - `@abide/abide/shared/online` — `online()`: reactive connectivity (client `navigator.onLine`; server reads the caller's reported state); reports, never acts.
259
250
 
260
251
  ### Observability — @documentation observability
261
252
 
262
- - `@abide/abide/shared/log` — unified logger with request-scope context (trace id, elapsed, verb+path); `log`/`warn`/`error`/`trace` on the always-on channel, `log.channel(name)` is `DEBUG`-gated.
263
- - `@abide/abide/shared/trace` — the request's W3C `traceparent` string for propagation; `undefined` outside request scope.
253
+ - `@abide/abide/shared/health` — `health()`: reactive backend health (`reachable` + app fields) polled from `/__abide/health` only while read; SSR-seeded.
254
+ - `@abide/abide/shared/log` — `log(...)` / `.warn` / `.error` / `.trace(label, fn)` / `.channel(name)`: request-scoped logger; TSV or JSON (`ABIDE_LOG_FORMAT`).
255
+ - `@abide/abide/shared/trace` — `trace()`: the current request's W3C `traceparent`, or `undefined` outside request scope; isomorphic.
264
256
 
265
- ### Subscribers — @documentation plumbing
257
+ ### Plumbing — @documentation plumbing
266
258
 
267
- - `@abide/abide/shared/createSubscriber` — lazy resource lifecycle: `start()` opens on first tracked read, close deferred to a microtask on last disposal, subscribers shared by reference.
259
+ - `@abide/abide/shared/createSubscriber` — `createSubscriber(start)`: open-on-first-read / close-on-last-reader resource lifecycle on the signal core.
268
260
 
269
- ## UI surface — abide/ui/* (client-only)
261
+ ## UI surface — abide/ui/\* (client-only)
270
262
 
271
263
  ### Reactive state — @documentation reactive-state
272
264
 
273
- - `@abide/abide/ui/scope` — the sole reactive surface: `scope()` resolves the current lexical scope (`scope('/')` the root); returned scope has `.state()` (writable cell), `.computed()` (read-only derived), `.linked()` (draft reseeded from upstream). A writable computed is expressed at the binding, `bind:value={{ get, set }}`.
265
+ - `@abide/abide/ui/scope` — `scope(address?)`: resolve the lexical reactive scope; `.state` / `.computed` / `.linked` are the reactive cells (see grammar).
274
266
 
275
- ### Effects — @documentation effect
267
+ ### Effect — @documentation effect
276
268
 
277
- - `@abide/abide/ui/effect` — run `fn` now and re-run when its read cells change; `fn` may return a teardown; captures lexical scope. Client-only.
269
+ - `@abide/abide/ui/effect` — `effect(fn)`: run now, re-run on dep change, optional teardown return; returns a disposer.
278
270
 
279
271
  ### Tail — @documentation tail
280
272
 
281
- - `@abide/abide/ui/tail` — reactive stream consumer: latest-wins `T | undefined`, or a window `T[]` with `{ last: n }`; reconnect retains the window and flags `refreshing`; seeds from `Subscribable.tail(count)`.
282
-
283
- ### Navigation — @documentation navigate
284
-
285
- - `@abide/abide/ui/navigate` — client navigation: push/replace a history entry, update the reactive route (re-mounts), bucket scroll per entry; `keepScroll` carries the live offset across.
286
-
287
- ### Outbox — @documentation ui
288
-
289
- - `@abide/abide/ui/outbox` — durable FIFO mutation queue for local-first writes: enqueue appends and drains head-first while online, drops permanently-failed entries via `onDrop`, resumes on reconnect.
290
-
291
- ### Client glue — @documentation plumbing
292
-
293
- - `@abide/abide/ui/router` — client router on the History API: matches patterns, imports page/layout chunks on demand, diffs and rebuilds divergent layers, restores scroll per entry, probes the server gate on SPA nav.
294
- - `@abide/abide/ui/startClient` — the official client entry: seed cache from the SSR snapshot, install the base, start the router (adopts SSR DOM), returns a disposer.
295
- - `@abide/abide/ui/renderToStream` — out-of-order SSR streaming: shell first, then resolved `{#await}` fragments in completion order with a resume seed; blocking awaits render inline.
296
- - `@abide/abide/ui/remoteProxy` — client remote function: build the `Request` (base, mount, offline header, traceparent), decode by Content-Type, throw `HttpError` on non-2xx.
297
- - `@abide/abide/ui/socketProxy` — client `Socket`: bare iteration is the live stream, `.tail(n)` seeds from the retained tail, `.publish` sends a server-validated frame; over the multiplexed ws.
298
- - `@abide/abide/ui/enterScope` / `@abide/abide/ui/exitScope` open and restore an isolated lexical scope around an SSR render so `scope()` and model state don't bleed.
299
-
300
- ### DOM runtime @documentation plumbing
301
-
302
- Compiler emit targets, not written by hand the runtime the compiled `.abide`
303
- output calls into to build, hydrate, and reconcile the DOM.
304
-
305
- - `@abide/abide/ui/dom/mount` — mount a top-level page/layout into a host element under an ownership scope; returns a disposer.
306
- - `@abide/abide/ui/dom/mountChild` — mount a child component as a marker-bounded range, with optional hot-reload support.
307
- - `@abide/abide/ui/dom/mountSlot` — mount a slot's content as a marker-bounded range that renders once without re-render.
308
- - `@abide/abide/ui/dom/outlet` — a layout's empty outlet boundary for the router to fill with the next chain layer.
309
- - `@abide/abide/ui/dom/hydrate` — adopt existing server-rendered DOM instead of rebuilding, attaching listeners and effects to preserve focus/scroll.
310
- - `@abide/abide/ui/dom/skeleton` — realize a compiled skeleton under a parent, returning the element and anchor holes for binding.
311
- - `@abide/abide/ui/dom/cloneStatic` — append a fully-static subtree by cloning a cached template (create) or claiming server nodes (hydrate).
312
- - `@abide/abide/ui/dom/appendStatic` — a static text node, claimed on hydrate or appended on create.
313
- - `@abide/abide/ui/dom/anchorCursor` — position a control-flow block or slot by its skeleton anchor, returning the insertion reference.
314
- - `@abide/abide/ui/dom/text` — a text node whose content tracks a reactive read, updating fine-grained on cell changes.
315
- - `@abide/abide/ui/dom/appendText` — a reactive interpolation branching on value kind: escaped text, snippet builder, or raw HTML.
316
- - `@abide/abide/ui/dom/appendTextAt` — as `appendText`, positioned at a skeleton anchor with live re-parsing.
317
- - `@abide/abide/ui/dom/appendSnippet` — a snippet call that mounts/remounts its builder in a marker-bounded range on argument change.
318
- - `@abide/abide/ui/dom/attr` — bind an element attribute to a reactive read with present/absent semantics.
319
- - `@abide/abide/ui/dom/on` — attach an event listener registered with the ownership scope for cleanup on dispose.
320
- - `@abide/abide/ui/dom/attach` — run an attachment at build time and register its optional teardown with the ownership scope.
321
- - `@abide/abide/ui/dom/each` — keyed list binding with reconciliation by key, minimal DOM moves, optional hydration.
322
- - `@abide/abide/ui/dom/eachAsync` — async keyed list appending rows as an iterator yields, reconciling by key with error handling.
323
- - `@abide/abide/ui/dom/when` — conditional binding that swaps range content on condition flip; an unchanged condition is a no-op.
324
- - `@abide/abide/ui/dom/awaitBlock` — async binding with pending/resolved/error branches, reactive re-run, and hydration by precedence.
325
- - `@abide/abide/ui/dom/tryBlock` — synchronous error boundary building guarded content or the error branch on throw.
326
- - `@abide/abide/ui/dom/switchBlock` — multi-branch binding picking the first case matching the subject (strict `===`).
327
- - `@abide/abide/ui/dom/applyResolved` — bundle-side consumer of streamed resolution chunks, swapping pending DOM and seeding cache.
328
- - `@abide/abide/ui/dom/mergeProps` — compose a child's props from layered sources (explicit, spreads, slot), last-writer-wins.
329
- - `@abide/abide/ui/dom/spreadProps` — wrap a reactive spread layer so every key resolves to a live value thunk.
330
- - `@abide/abide/ui/dom/restProps` — return unconsumed prop values as a live object, unwrapping thunks, excluding consumed/slot keys.
331
- - `@abide/abide/ui/dom/spreadAttrs` — spread an object's keys onto a native element, binding event listeners and reactive attributes.
332
- - `@abide/abide/ui/dom/readCall` — guard a method call on a reactive-document read, with friendly errors for nullish/non-callable members.
333
-
334
- ### Render-pass runtime — @documentation plumbing
335
-
336
- - `@abide/abide/ui/runtime/escapeKey` — JSON-Pointer-escape reactive-doc path keys (`~`→`~0`, `/`→`~1`) so composite keys survive path joining.
337
- - `@abide/abide/ui/runtime/nextBlockId` — next block id in the render pass (per await/try block).
338
- - `@abide/abide/ui/runtime/enterRenderPass` — mark entry into a render/mount, resetting the block-id counter at depth 0.
339
- - `@abide/abide/ui/runtime/exitRenderPass` — mark exit from a render/mount, unwinding the depth counter.
340
-
341
- ## Build / tooling — @documentation building
342
-
343
- - `@abide/abide/build` — build the client bundle with optional gzip, via atomic directory swaps.
344
- - `@abide/abide/compile` — produce a standalone Bun server executable with embedded assets.
345
- - `@abide/abide/preload` — Bun preload that registers the `.abide` loader and resolver before builds.
346
- - `@abide/abide/resolver-plugin` — Bun plugin resolving bare/extensionless paths, generating manifests, and handling virtual modules.
347
- - `@abide/abide/ui-plugin` — Bun plugin that loads `.abide` single-file components, compiling them to ES modules with scoped styles.
348
- - `@abide/abide/tsconfig` — the strict ESNext + bundler-resolution + Bun-types TypeScript config consumers extend.
349
-
350
- ## Desktop bundle — @documentation bundle
351
-
352
- - `@abide/abide/server/appDataDir` — per-user app data directory keyed by the bundler-injected program name; cwd-independent, pure.
353
- - `@abide/abide/bundle/BundleWindow` — type for the bundle window config: title, size, custom menus, setup form schema.
354
- - `@abide/abide/bundle/BundleMenu` — type for a top-level menu inserted between the standard Edit and Window menus.
355
- - `@abide/abide/bundle/BundleMenuItem` — type for one menu entry: separator, clickable emit, or navigate.
356
- - `@abide/abide/bundle/onMenu` — subscribe to bundle menu clicks (optional filter); returns an unsubscribe.
357
- - `@abide/abide/bundle/bundled` — true inside the desktop webview, false as a standalone web app.
358
-
359
- ## MCP — @documentation mcp
360
-
361
- - `@abide/abide/mcp/createMcpServer` — build an MCP server bound to the project's RPC registry, deriving tools and sockets with auth.
362
-
363
- ## Testing — @documentation testing
364
-
365
- - `@abide/abide/test/createTestApp` — build a test app with augmentable verb/socket maps and bundled virtual modules.
366
- - `@abide/abide/test/createScriptedSurface` — a scripted `AgentSurface` with declarative tool stubs and recorded calls.
367
- - `@abide/abide/test/assertAgentFrameConformance` — collect an engine's frame stream and validate it against the neutral `AgentFrame` contract.
273
+ - `@abide/abide/ui/tail` — `tail(subscribable, opts?)`: reactive consumer of a `Socket<T>` or `fn.stream` result (latest-wins, or windowed via `{ last }`); seeds from retention; no-op on the server.
274
+
275
+ ### Navigate — @documentation navigate
276
+
277
+ - `@abide/abide/ui/navigate` — `navigate(path, { replace?, keepScroll? })`: client navigation; writes history and re-mounts the page chain.
278
+
279
+ ### UI — @documentation ui
280
+
281
+ - `@abide/abide/ui/outbox` — `outbox({ key, send, store, online, onDrop })`: durable FIFO mutation queue for local-first writes; drains while online, retries on reconnect, at-least-once.
282
+
283
+ ### Plumbing — @documentation plumbing
284
+
285
+ - `@abide/abide/ui/enterScope` — `enterScope()`: open a fresh lexical scope for an SSR render; returns the previous to restore.
286
+ - `@abide/abide/ui/exitScope` — `exitScope(previous)`: restore the scope `enterScope` saved.
287
+ - `@abide/abide/ui/router` — `router(host, loaders, layoutLoaders, probe?)`: History-API router; diffs the layout/page chain on nav. Returns a disposer.
288
+ - `@abide/abide/ui/startClient` — `startClient(routes, layoutRoutes, target)`: official client entry; reads `__SSR__`, seeds cache, starts the router.
289
+ - `@abide/abide/ui/renderToStream` — `renderToStream(render)`: out-of-order SSR streaming; yields the shell, then one `<abide-resolve>` fragment per streaming await.
290
+ - `@abide/abide/ui/remoteProxy` `remoteProxy(method, url)`: client substitute for a verb handler; identical `RemoteFunction` shape so `cache()` matches both sides.
291
+ - `@abide/abide/ui/socketProxy` — `socketProxy(name)`: client substitute for a server `Socket`; subscribes over the multiplexed ws channel.
292
+ - `@abide/abide/ui/dom/mount`mount a top-level page/layout into a host under an ownership scope; returns a disposer.
293
+ - `@abide/abide/ui/dom/mountChild` — mount a child component as a marker-bounded range (no wrapper element); records the instance for hot reload.
294
+ - `@abide/abide/ui/dom/mergeProps` compose child props from ordered layers (thunks, spreads, slot) via a Proxy; last layer wins.
295
+ - `@abide/abide/ui/dom/spreadProps` wrap a `{...source}` spread so each key resolves to a live thunk.
296
+ - `@abide/abide/ui/dom/restProps` — the unconsumed rest of a component's props as a live, enumerable object.
297
+ - `@abide/abide/ui/dom/spreadAttrs` — spread an object onto a native element: `on*` keys as listeners, others as reactive attributes.
298
+ - `@abide/abide/ui/dom/readCall` — guarded reactive-doc method call with scope-path error messages.
299
+ - `@abide/abide/ui/dom/hydrate` — adopt server-rendered DOM, attaching listeners/effects in place with a claim cursor.
300
+ - `@abide/abide/ui/dom/text` — a text node whose content tracks `read()`.
301
+ - `@abide/abide/ui/dom/appendText` — reactive `{expr}` interpolation (escaped text, raw `html`, or snippet).
302
+ - `@abide/abide/ui/dom/appendTextAt` — reactive `{expr}` at a skeleton anchor comment, interleaved with element siblings.
303
+ - `@abide/abide/ui/dom/appendSnippet` — `{snippet(args)}` interpolation in a marker-bounded range; re-reads args reactively.
304
+ - `@abide/abide/ui/dom/appendStatic` — a static (non-reactive) text node, created or claimed from SSR.
305
+ - `@abide/abide/ui/dom/cloneStatic` — append a fully-static subtree by template clone (create) or claim (hydrate).
306
+ - `@abide/abide/ui/dom/skeleton` — realize a compiled skeleton under a parent; returns element holes (by path) and anchors (in order).
307
+ - `@abide/abide/ui/dom/anchorCursor` — position a skeleton-anchored control-flow block; parks the claim cursor (hydrate) or returns a create reference.
308
+ - `@abide/abide/ui/dom/mountSlot` — mount a component's `<slot>` content in a marker-bounded range (rendered once).
309
+ - `@abide/abide/ui/dom/outlet` — a layout's `<slot/>` outlet (comment-bounded boundary the router fills with the next layer).
310
+ - `@abide/abide/ui/dom/attr` — bind an element attribute to `read()` (boolean present/absent semantics).
311
+ - `@abide/abide/ui/dom/on` — attach an event listener pinned to its ownership scope.
312
+ - `@abide/abide/ui/dom/attach` — run a build-time attachment against an element with optional teardown.
313
+ - `@abide/abide/ui/dom/each` — keyed list binding; reconciles by key, reorders/re-keys in place; claims SSR rows on hydrate.
314
+ - `@abide/abide/ui/dom/eachAsync` — async keyed list over an `AsyncIterable`; rows append/reconcile as the iterator yields.
315
+ - `@abide/abide/ui/dom/when` — conditional binding; swaps the range on truthy↔falsy flips; adopts the server branch on hydrate.
316
+ - `@abide/abide/ui/dom/awaitBlock` — async binding (pending/then/catch); patches a re-settling `then` value in place rather than rebuilding.
317
+ - `@abide/abide/ui/dom/tryBlock` — synchronous error boundary; builds the catch subtree if the guarded one throws (rendered once).
318
+ - `@abide/abide/ui/dom/switchBlock` — multi-branch binding; picks the first matching case, else default; swaps on subject change.
319
+ - `@abide/abide/ui/dom/applyResolved` — bundle-side consumer of an SSR stream chunk; routes cache-seed/resolve frames into await boundaries.
320
+ - `@abide/abide/ui/runtime/escapeKey` — escape one object key into a JSON-Pointer token (`~`→`~0`, `/`→`~1`).
321
+ - `@abide/abide/ui/runtime/nextBlockId` — the next block id in the current render pass (await/try blocks, document order).
322
+ - `@abide/abide/ui/runtime/enterRenderPass` — mark entry into a render/mount pass; depth 0 resets the block-id counter.
323
+ - `@abide/abide/ui/runtime/exitRenderPass` — mark exit from a render/mount pass, unwinding the depth.
324
+
325
+ ## Build / tooling
326
+
327
+ ### Building — @documentation building
328
+
329
+ - `@abide/abide/build` — `build(opts)`: build the client bundle into `dist/_app` (optional gzip); returns success.
330
+ - `@abide/abide/compile` — `compile(opts)`: build a standalone server executable (runs the client build first); returns the binary path.
331
+
332
+ ### Plumbing — @documentation plumbing
333
+
334
+ - `@abide/abide/preload` — the Bun plugin stack (`abideUiPlugin` + resolver) registered via `bunfig.toml` preload.
335
+ - `@abide/abide/resolver-plugin` — `abideResolverPlugin({ cwd, embedAssets, target })`: resolves the `abide:*` virtual modules and convention dirs.
336
+ - `@abide/abide/ui-plugin` — `abideUiPlugin`: Bun plugin that compiles `.abide` single-file components (with scoped CSS) to ES modules.
337
+ - `@abide/abide/tsconfig` — `tsconfig.app.json`: the app TypeScript config (ESNext, DOM lib, Bun types, strict, no-emit).
338
+
339
+ ## Desktop bundle
340
+
341
+ ### Bundle — @documentation bundle
342
+
343
+ - `@abide/abide/server/appDataDir` — `appDataDir()`: the running bundle's per-user data directory (keyed by injected program name; cwd-independent). Overridable via `ABIDE_DATA_DIR`.
344
+ - `@abide/abide/bundle/BundleWindow` — type of `src/bundle/window.ts`: window config (title, size, menu, `configSchema`).
345
+ - `@abide/abide/bundle/BundleMenu` — type of a top-level menu-bar menu (label + items).
346
+ - `@abide/abide/bundle/BundleMenuItem` — type of one menu entry (divider, emit event, or navigate URL).
347
+ - `@abide/abide/bundle/onMenu` — `onMenu(name?, handler)`: subscribe to bundle menu clicks (`abide:menu` CustomEvent).
348
+ - `@abide/abide/bundle/bundled` — `bundled()`: true when running inside the abide desktop bundle.
349
+
350
+ ## MCP
351
+
352
+ ### MCP — @documentation mcp
353
+
354
+ - `@abide/abide/mcp/createMcpServer` — `createMcpServer(opts)`: MCP server bound to the project registry; derives tools from verbs/sockets, handles auth.
355
+
356
+ ### Prompts — @documentation plumbing
357
+
358
+ - `@abide/abide/server/prompts/definePrompt` — `definePrompt(name, opts)`: build a `Prompt` from an `src/mcp/prompts/*.md` file (the bundler emits the call).
359
+ - `@abide/abide/server/prompts/renderPromptTemplate` — `renderPromptTemplate(template, args)`: substitute `{{name}}` placeholders (missing empty string).
360
+
361
+ ## Testing
362
+
363
+ ### Testing — @documentation testing
364
+
365
+ - `@abide/abide/test/createTestApp` — `createTestApp(opts)`: in-memory test server with a typed `app.rpc.<verb>` / `app.sockets.<name>` surface.
366
+
367
+ ### Plumbing — @documentation plumbing
368
+
369
+ - `@abide/abide/test/createScriptedSurface` — `createScriptedSurface(tools)`: a scripted `AgentSurface` for engine tests (records calls, stubs results).
370
+ - `@abide/abide/test/assertAgentFrameConformance` — `assertAgentFrameConformance(stream)`: assert engine frame-stream invariants (done frame, tool use/result pairing).
368
371
 
369
372
  ## Generated machine surfaces
370
373
 
371
- | Route | Serves |
372
- | --- | --- |
373
- | `/openapi.json` | OpenAPI 3.1 spec of every RPC verb (methods, params, schemas, operationIds) |
374
- | `/__abide/health`, `/__abide/identity` | Health JSON: app `health()` fields merged with `{ abide: version, name }` |
375
- | `/__abide/mcp` | MCP endpoint; delegates to `mcp.handle(request)`, publishing the surface on first access |
376
- | `/__abide/sockets` | The single ws multiplex for every defined socket; per-name HTTP face for tail/publish |
377
- | `/__abide/cli` | Platform-detecting shell installer that downloads and runs the CLI binary |
378
- | `/__abide/inspector` | Inspector UI + data when `ABIDE_ENABLE_INSPECTOR=true` (operator-only) |
379
- | `/__abide/hot/<moduleId>` | Dev-only HMR: compiles an edited `.abide` to importable JS; 404 falls back to reload |
374
+ Runtime routes the framework serves:
375
+
376
+ | Route | Serves |
377
+ | -------------------- | ----------------------------------------------------------------------------------------- |
378
+ | `/openapi.json` | OpenAPI document for the `/rpc/*` HTTP surface. |
379
+ | `/__abide/mcp` | MCP endpoint (POST) routed to `createMcpServer`. |
380
+ | `/__abide/health` | Unauthenticated health payload (`reachable` + `src/app.ts` `health` fields). |
381
+ | `/__abide/sockets` | WebSocket multiplex for every declared socket; `/<name>` adds the HTTP tail/publish face. |
382
+ | `/__abide/cli` | CLI download (`/<platform>`): gzipped tarball with the thin binary + `.env`. |
383
+ | `/__abide/hot/<id>` | Dev-only hot-module endpoint for edited `.abide` components. |
384
+ | `/__abide/identity` | Server identity (app name + version) for CLI connectivity probes. |
385
+ | `/__abide/inspector` | Inspector UI, gated by `ABIDE_ENABLE_INSPECTOR` and the installed package. |
380
386
 
381
387
  ## Environment variables
382
388
 
383
- | Variable | Effect |
384
- | --- | --- |
385
- | `PORT` | Listener port (0–65535); honored exactly if set, else scans up from 3000 |
386
- | `APP_URL` | Mount base path from the URL's pathname (`https://x/v2` → `/v2`); mounts server URLs + shell |
387
- | `ABIDE_APP_URL` | App origin sent to the CLI download tarball's `.env` |
388
- | `ABIDE_APP_TOKEN` | Bearer token forwarded into the CLI tarball `.env` when the request carries `Authorization` |
389
- | `ABIDE_CLIENT_TIMEOUT` | Client RPC fetch timeout (ms, 1–600000); shipped to the browser via SSR |
390
- | `ABIDE_MAX_REQUEST_BODY_SIZE` | Server-wide request body ceiling (bytes); default ~Bun default |
391
- | `ABIDE_IDLE_TIMEOUT` | Per-connection idle timeout (s, 0–255); default 10 |
392
- | `ABIDE_DATA_DIR` | Override the platform app-data dir (used as-is, no program name appended) |
393
- | `ABIDE_REACHABLE_TIMEOUT` | Per-HEAD reachability probe timeout (ms, 10060000); default 3000 |
394
- | `ABIDE_REACHABLE_TTL` | Reachability poll cadence (ms, 1000–600000); default 30000 |
395
- | `ABIDE_LOG_FORMAT` | `json` emits JSON log records to stdout instead of TSV |
396
- | `DEBUG` | npm-debug channel selection (`abide`, `abide:*`, `-abide` to negate request-close logs) |
397
- | `ABIDE_ENABLE_INSPECTOR` | `true` mounts `@abide/inspector` on `/__abide/inspector` |
398
- | `ABIDE_INSPECT` | Enable the native webview inspector (right-click Inspect) in bundle windows |
389
+ | Variable | Effect |
390
+ | ----------------------------- | --------------------------------------------------------------------------------------------- |
391
+ | `PORT` | Port the server binds (scans upward from the default if taken). |
392
+ | `APP_URL` | Request origin the CLI download writes into the downloaded `.env` (`ABIDE_APP_URL`). |
393
+ | `ABIDE_APP_URL` | Server URL baked into a CLI binary at build time; runtime-overridable. |
394
+ | `ABIDE_APP_TOKEN` | Bearer token written into the CLI download's `.env` when the request carries `Authorization`. |
395
+ | `ABIDE_CLIENT_TIMEOUT` | Client-side RPC fetch timeout in ms (1–600000); shipped via `__SSR__`. |
396
+ | `ABIDE_MAX_REQUEST_BODY_SIZE` | Server-wide request body ceiling in bytes. |
397
+ | `ABIDE_IDLE_TIMEOUT` | Per-connection idle timeout in seconds (0–255, default 10); streaming opts out. |
398
+ | `ABIDE_REACHABLE_TIMEOUT` | `reachable()` probe timeout in ms (100–60000, default 3000). |
399
+ | `ABIDE_REACHABLE_TTL` | `reachable()` poll cadence / freshness in ms (1000600000, default 30000). |
400
+ | `ABIDE_DATA_DIR` | Override for the app data directory (used as-is, no program name appended). |
401
+ | `ABIDE_LOG_FORMAT` | `json` for one JSON object per line; default is tab-separated. |
402
+ | `DEBUG` | Diagnostic channel gate (e.g. `DEBUG=abide:*` or `abide:build`). |
403
+ | `ABIDE_ENABLE_INSPECTOR` | `true` activates the optional inspector. |
404
+ | `ABIDE_INSPECT` | Enable DevTools inspection in a desktop bundle's webview. |
399
405
 
400
406
  ---
401
407
 
402
- Mirrors `package.json` `exports`. After adding or renaming an export, run
403
- `bun run packages/abide/scripts/readmeSurfaces.ts` and update this map.
408
+ This map mirrors `package.json` `exports`. After adding or renaming an export,
409
+ run `bun run packages/abide/scripts/readmeSurfaces.ts` and update the matching
410
+ bullet here.