@abide/abide 0.39.0 → 0.40.1

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 (71) hide show
  1. package/AGENTS.md +318 -146
  2. package/CHANGELOG.md +89 -0
  3. package/README.md +77 -79
  4. package/package.json +1 -1
  5. package/src/lib/server/rpc/parseArgs.ts +10 -1
  6. package/src/lib/server/rpc/runWithVerbTimeout.ts +7 -9
  7. package/src/lib/server/rpc/types/VerbHelper.ts +18 -21
  8. package/src/lib/server/runtime/SSR_SWAP_SCRIPT.ts +8 -7
  9. package/src/lib/server/sockets/createSocketDispatcher.ts +12 -3
  10. package/src/lib/server/sockets/defineSocket.ts +3 -1
  11. package/src/lib/shared/REF_JSON_HEADER.ts +9 -0
  12. package/src/lib/shared/REF_JSON_TAGS.ts +31 -0
  13. package/src/lib/shared/assertExhaustive.ts +14 -0
  14. package/src/lib/shared/buildRpcRequest.ts +8 -1
  15. package/src/lib/shared/decodeRefJson.ts +110 -0
  16. package/src/lib/shared/encodeRefJson.ts +106 -0
  17. package/src/lib/test/createTestSocketChannel.ts +6 -2
  18. package/src/lib/ui/compile/TS_PRINTER.ts +7 -0
  19. package/src/lib/ui/compile/analyzeComponent.ts +15 -16
  20. package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +66 -0
  21. package/src/lib/ui/compile/assertTranspiles.ts +19 -0
  22. package/src/lib/ui/compile/compileModule.ts +56 -34
  23. package/src/lib/ui/compile/compileSSR.ts +1 -3
  24. package/src/lib/ui/compile/compileShadow.ts +14 -7
  25. package/src/lib/ui/compile/desugarSignals.ts +168 -107
  26. package/src/lib/ui/compile/generateBuild.ts +49 -59
  27. package/src/lib/ui/compile/generateSSR.ts +30 -21
  28. package/src/lib/ui/compile/hoistCells.ts +2 -2
  29. package/src/lib/ui/compile/isWhitespaceText.ts +11 -0
  30. package/src/lib/ui/compile/lowerContext.ts +23 -10
  31. package/src/lib/ui/compile/lowerDocAccess.ts +29 -3
  32. package/src/lib/ui/compile/lowerScript.ts +64 -0
  33. package/src/lib/ui/compile/parseTemplate.ts +74 -0
  34. package/src/lib/ui/compile/renameSignalRefs.ts +160 -90
  35. package/src/lib/ui/compile/resolveBranches.ts +21 -0
  36. package/src/lib/ui/compile/stripEffects.ts +22 -17
  37. package/src/lib/ui/compile/types/AnalyzedComponent.ts +5 -0
  38. package/src/lib/ui/compile/types/TemplateNode.ts +12 -2
  39. package/src/lib/ui/createScope.ts +14 -0
  40. package/src/lib/ui/dom/appendText.ts +2 -3
  41. package/src/lib/ui/dom/appendTextAt.ts +2 -3
  42. package/src/lib/ui/dom/applyResolved.ts +5 -8
  43. package/src/lib/ui/dom/awaitBlock.ts +22 -2
  44. package/src/lib/ui/dom/each.ts +4 -0
  45. package/src/lib/ui/dom/on.ts +7 -0
  46. package/src/lib/ui/dom/parseRawNodes.ts +17 -0
  47. package/src/lib/ui/dom/switchBlock.ts +4 -0
  48. package/src/lib/ui/dom/when.ts +4 -0
  49. package/src/lib/ui/installInspectorBridge.ts +3 -1
  50. package/src/lib/ui/navigate.ts +28 -3
  51. package/src/lib/ui/renderToStream.ts +17 -9
  52. package/src/lib/ui/resumeSeedScript.ts +16 -6
  53. package/src/lib/ui/router.ts +108 -15
  54. package/src/lib/ui/runtime/NODE_STATE.ts +22 -0
  55. package/src/lib/ui/runtime/RESUME.ts +13 -6
  56. package/src/lib/ui/runtime/clientPage.ts +114 -9
  57. package/src/lib/ui/runtime/createComputedNode.ts +5 -3
  58. package/src/lib/ui/runtime/createEffectNode.ts +3 -1
  59. package/src/lib/ui/runtime/createSignalNode.ts +4 -2
  60. package/src/lib/ui/runtime/flushEffects.ts +8 -5
  61. package/src/lib/ui/runtime/historyEntries.ts +39 -1
  62. package/src/lib/ui/runtime/localStoragePersistence.ts +14 -8
  63. package/src/lib/ui/runtime/readNode.ts +8 -7
  64. package/src/lib/ui/runtime/runNode.ts +18 -2
  65. package/src/lib/ui/runtime/scope.ts +12 -1
  66. package/src/lib/ui/runtime/trigger.ts +40 -24
  67. package/src/lib/ui/runtime/types/ReactiveNode.ts +4 -1
  68. package/src/lib/ui/runtime/updateIfNecessary.ts +40 -0
  69. package/src/lib/ui/socketChannel.ts +5 -2
  70. package/src/lib/ui/tryEncodeResume.ts +20 -0
  71. package/src/lib/ui/types/Scope.ts +9 -3
package/AGENTS.md CHANGED
@@ -3,228 +3,400 @@
3
3
  > The exhaustive index of abide's public surface: every `exports` key appears once,
4
4
  > grouped by namespace, with its import specifier and a one-line spec — so an agent
5
5
  > grasps the whole API in one read and knows which file to open for depth. The README
6
- > is the curated 3-primitive intro; this is the complete map.
7
- >
8
- > No barrels: every public name has its own module path, and the namespace marks the
9
- > side it runs on — `abide/server/*` is server-only, `abide/ui/*` is client-only,
10
- > `abide/shared/*` is isomorphic (same callable, same behaviour on both sides; the
11
- > bundler swaps the runtime). Importing one name never drags in a side-effecting sibling.
12
- >
13
- > Package `@abide/abide`, runtime Bun `>= 1.3.0`, one dependency (`typescript`); the
14
- > import specifier (`abide/server/GET`) is the `@abide/abide/...` `exports` key, not the
15
- > on-disk file path.
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`.
16
13
 
17
14
  ## The premise
18
15
 
16
+ One typed verb declaration fans out to five surfaces:
19
17
  ```text
20
- getMessages (one declaration)
21
-
22
- ┌──────────┬─────────┼─────────┬──────────────┐
23
- ▼ ▼ ▼ ▼ ▼
24
- SSR call browser MCP tool CLI sub- OpenAPI
25
- cache(fn)() fetch (read-only) command operation
18
+ getMessages = GET(fn, { inputSchema })
19
+
20
+ ┌───────────┬─────────────┼────────────┬──────────────┐
21
+ SSR call browser fetch MCP tool CLI command OpenAPI op
22
+ cache(fn)() typed proxy() (read-only) abide ... cli /openapi.json
26
23
  ```
27
24
 
28
- A verb's `inputSchema` (any Standard Schema) unlocks the CLI and projects the OpenAPI op; a read-only `GET`/`HEAD` also auto-exposes an MCP tool. A mutating verb never auto-exposes to an agent — it opts in with `clients: { mcp: true }`. The same gating applies to sockets via their schema.
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 }`.
29
27
 
30
28
  ## File-based conventions
31
29
 
32
- The bundler reads these paths; their location is their identity (no manifest to register).
33
-
34
- | Path | Meaning |
35
- | ------------------------------ | ------------------------------------------------------------------------- |
36
- | `src/server/rpc/<name>.ts` | One verb export per file; export name is the URL stem (`/rpc/<name>`) |
37
- | `src/server/sockets/<name>.ts` | One `socket()` export per file; topic `<name>`, ws at `/__abide/sockets` |
38
- | `src/mcp/prompts/<name>.md` | MCP prompt: frontmatter (description + args) + `{{placeholder}}` body |
39
- | `src/mcp/resources/<name>` | MCP resource files; embedded in the standalone binary |
40
- | `src/server/config.ts` | Optional `env(schema)` validation, eager-imported at boot |
41
- | `src/app.ts` | Optional `AppModule` hooks (`init`/`handle`/`handleError`/`health`) |
42
- | `src/bundle/window.ts` | Optional desktop `BundleWindow` default export |
43
- | `src/ui/pages/**/page.abide` | A route; URL is the folder path |
44
- | `src/ui/pages/**/layout.abide` | Wraps every page at/below its folder; nested chains apply outermost-first |
45
- | `src/ui/app.html` | Optional custom shell; entry refs rewritten to hashed names |
46
- | `src/ui/public/` | Static assets served at site root; embedded when compiled |
47
- | `src/.abide/*.d.ts` | Framework codegen (`routes`/`rpc`/`sockets`/`publicAssets` types) |
48
- | `dist/_app/` | Client bundle output: hashed chunks + CSS, optional `.gz` siblings |
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/`.
49
48
 
50
49
  ## CLI
51
50
 
52
- | Command | Does |
53
- | -------------------------------------------- | ----------------------------------------------------------------------------------------------- |
54
- | `abide scaffold <name>` | Scaffold a project, install deps, and (in a TTY) start dev; `--no-install` / `--no-dev` opt out |
55
- | `abide dev` | Build the client + run the server with hot reload, watching `src/` |
56
- | `abide build` | One-shot client build into `dist/_app/` (no server) |
57
- | `abide start` | Run the production server against a built `dist/` |
58
- | `abide run <file> [args]` | Run a script under the abide preload (`.abide` compile, `$server` resolution) |
59
- | `abide compile [--target] [--out]` | Build a standalone server executable (bytecode, embedded assets) |
60
- | `abide cli [--target] [--out] [--platforms]` | Build the thin remote-client+server CLI binary, optionally cross-compiled |
61
- | `abide bundle` | Build a self-contained desktop app bundle for this platform |
62
- | `abide check` | Type-check all `.abide` templates and props; non-zero on errors |
63
- | `abide lsp` | Run the `.abide` language server over stdio |
64
- | `abide init-agent` | Write/refresh a `CLAUDE.md` pointer to this surface map |
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 |
62
+ | `abide init-agent` | Write/refresh a `CLAUDE.md` pointer to this surface map |
65
63
 
66
- Test suites compile `.abide` and rewrite verbs under `bun test` via `preload = ["@abide/abide/preload"]` in `bunfig.toml`'s `[test]` block.
64
+ Tests preload the framework: `preload = ["@abide/abide/preload"]` in `bunfig.toml`
65
+ registers `.abide` compilation so `bun test` runs the same runtime as the server.
67
66
 
68
- ## Server surface — `abide/server/*`
67
+ ## Authoring contracts
68
+
69
+ What you write into each convention path, and the contract the framework holds
70
+ you to. The per-export specs below are exhaustive; this is the shape of the code
71
+ that produces them.
72
+
73
+ ### RPC verb — `src/server/rpc/<name>.ts`
74
+
75
+ - One `export const <name> = METHOD(handler, opts?)` per file (`METHOD` ∈ `GET`/`POST`/`PUT`/`PATCH`/`DELETE`/`HEAD`); the file path is the route.
76
+ - 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.
77
+ - `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`).
78
+ - Query args arrive as **strings** — validate with `z.coerce.*`.
79
+ - 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.
80
+
81
+ ### Socket — `src/server/sockets/<name>.ts`
82
+
83
+ - One `export const <name> = socket({ schema, tail?, ttl?, clientPublish?, clients? })`; the file name is the topic.
84
+ - `.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).
85
+ - HTTP face `/__abide/sockets/<name>`: `GET` returns the tail, `POST` publishes (gated by `clientPublish`).
86
+ - Consume in a component with `tail(socket)` (latest-wins) or `tail(socket, { last: n })` (window).
87
+
88
+ ### Page & layout — `src/ui/pages/**`
89
+
90
+ - `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.
91
+ - `layout.abide` wraps every page at or below its folder; its `<slot/>` (compiled to an outlet) holds the nested page or child layout.
92
+ - Build links with `url('/pages/product/[id]', { id })` and move with `navigate(href)`.
93
+
94
+ ### App module & config — `src/app.ts`, `src/server/config.ts` (both optional)
95
+
96
+ - `src/app.ts` exports the `AppModule` hooks: `forwardHeaders`, `init`, `handle`, `handleError`, `health`.
97
+ - `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.
98
+
99
+ ### The isomorphism move
100
+
101
+ Import a server verb (or socket) straight into a `.abide` and call it: during SSR
102
+ the bundler keeps the real handler (it runs in-process), in the browser it swaps
103
+ to a `fetch` proxy of the same signature, same name. Wrap reads in `cache()` so the
104
+ SSR pass serializes the value into the snapshot and the client hydrates warm
105
+ instead of refetching.
106
+
107
+ ## .abide template grammar
108
+
109
+ A `.abide` file is one component: an optional `<script>` (module scope), HTML
110
+ markup with reactive interpolation and `<template>` control flow, and optional
111
+ component-scoped `<style>`. Ambient in `<script>` — no import — are `scope`,
112
+ `props`, `effect`, `html`, and `snippet`; everything else (`cache`, `page`,
113
+ `navigate`, `tail`, verbs, sockets, child components) is imported normally.
114
+
115
+ ```html
116
+ <script>
117
+ import { cache } from '@abide/abide/shared/cache'
118
+ import { getThing } from '$server/rpc/getThing.ts'
119
+ import Child from '$ui/Child.abide'
120
+
121
+ const { title = 'untitled' } = props<{ title?: string }>() // reactive props
122
+ let count = scope().state(0) // writable cell
123
+ const doubled = scope().computed(() => count * 2) // read-only derived
124
+ effect(() => console.log(count)) // re-runs on change
125
+ </script>
126
+
127
+ <h1>{title}: {doubled}</h1> <!-- {expr} interpolation -->
128
+ <button onclick={() => (count = count + 1)}>inc</button> <!-- on<event>={fn} -->
129
+ <Child label={title}><p>slot body</p></Child> <!-- caps tag = component -->
130
+
131
+ <style>h1 { font-weight: 600 }</style> <!-- component-scoped -->
132
+ ```
133
+
134
+ Reactive state — `scope()` is the only reactive surface:
135
+
136
+ | Form | Meaning |
137
+ | --- | --- |
138
+ | `scope().state(v)` | writable cell — read by name, assign with `=` |
139
+ | `scope().computed(fn)` | read-only derived; re-runs when its read cells change |
140
+ | `scope().linked(fn)` | local draft reseeded from upstream on source change |
141
+ | `effect(fn)` | side effect; re-runs on change; return a teardown |
142
+ | `props()` / `props<T>()` | destructure reactive props, defaults allowed |
143
+
144
+ Interpolation & bindings:
145
+
146
+ | Syntax | Effect |
147
+ | --- | --- |
148
+ | `{expr}` | reactive text, HTML-escaped; `{html(s)}` inserts trusted raw HTML |
149
+ | `name={expr}` | reactive attribute (omitted when nullish) |
150
+ | `on<event>={fn}` | event listener — `onclick`, `onsubmit`, … |
151
+ | `bind:value={cell}` | two-way bind to input / textarea / select |
152
+ | `bind:checked={cell}` | checkbox boolean bind |
153
+ | `bind:group={cell}` | radio (single value) / checkbox group (array) |
154
+ | `bind:value={{ get, set }}` | writable computed — a lens over derived state |
155
+
156
+ Control flow — native `<template>`:
157
+
158
+ | Directive | Shape |
159
+ | --- | --- |
160
+ | `<template if={c}>` … `<template elseif={c2}>` … `<template else>` | conditional chain (`else` must be last) |
161
+ | `<template each={list} as="x" key="x.id">` | keyed list |
162
+ | `<template await={p}>` `<template then="v">` `<template catch="e">` `<template finally>` | promise (streams; branch value bound by `then`/`catch`) |
163
+ | `<template switch={s}>` `<template case={v}>` … `<template default>` | first strict-`===` match |
164
+ | `<template try>` `<template catch="e">` `<template finally>` | synchronous error boundary |
165
+ | `<template name="row" args={p}>` … `{row(p)}` | snippet — a named builder that closes over scope, rendered like a function and passable as a prop |
166
+
167
+ Components & slots — a capitalized tag is an imported component; its attributes
168
+ are props; child markup fills the default `<slot/>`, and `<el slot="footer">` fills
169
+ `<slot name="footer">`. A `then`/`catch` branch (and any nested `<script>` / `<style>`)
170
+ is its own lexical scope.
171
+
172
+ ## Server surface — abide/server/*
69
173
 
70
174
  ### RPC verbs — @documentation rpc
71
175
 
72
- - `abide/server/GET`, `abide/server/POST`, `abide/server/PUT`, `abide/server/PATCH`, `abide/server/DELETE`, `abide/server/HEAD` declare a verb: `VERB(handler, opts?)`; the preload rewrites each to `defineVerb(method, url, …)` (server) or `remoteProxy` (client). `opts`: `inputSchema`, `outputSchema`, `filesSchema`, `clients`, `crossOrigin`, `maxBodySize`, `timeout`. The returned function is callable in-process as `fn(args, callOpts?)` and exposes `.raw(args, callOpts?)` (the `Response`), `.stream(args)` (frame iterable), and `.fetch(request)` (router entry). `callOpts` is a curated `Pick` of `RequestInit` for per-call transport `signal`, `keepalive`, `priority`, `cache`, `headers` — that the server handler never sees, so the call stays isomorphic: `signal` merges with the scope abort + client timeout (`AbortSignal.any`, ignored under `cache()`), and `headers` merge onto the framework headers with `traceparent`/offline/`content-type` winning.
73
- - `abide/server/rpc/defineVerb` — the rewrite target the verb helpers expand to: validates args against `inputSchema` (422 on issues), enforces `timeout` (504 + abort), and registers the verb for MCP/CLI/OpenAPI/inspector discovery.
176
+ - `@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.
177
+ - `@abide/abide/server/POST` — as `GET`, for `POST`.
178
+ - `@abide/abide/server/PUT` — as `GET`, for `PUT`.
179
+ - `@abide/abide/server/PATCH` — as `GET`, for `PATCH`.
180
+ - `@abide/abide/server/DELETE` — as `GET`, for `DELETE`.
181
+ - `@abide/abide/server/HEAD` — as `GET`, for `HEAD`.
182
+ - `@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.
74
183
 
75
- ### Responses — @documentation response
184
+ ### Response helpers — @documentation response
76
185
 
77
- - `abide/server/json` — `json(data, init?)`: `application/json`, `no-store`; `undefined` → `204` so `T | undefined` round-trips.
78
- - `abide/server/jsonl` — `jsonl(asyncIterable, init?)`: newline-delimited JSON frames; errors emit a final `{"$error":…}` line.
79
- - `abide/server/sse` — `sse(asyncIterable, init?)`: `text/event-stream` with 15s keepalives; errors emit an `event: error` frame.
80
- - `abide/server/error` — `error(status, message?, init?)`: `text/plain` error `Response` typed `never` so error branches don't widen the inferred return.
81
- - `abide/server/redirect` — `redirect(url, status=302, init?)`: redirect `Response` (accepts relative URLs), typed `never`.
82
- - `abide/shared/HttpError` — `new HttpError(response)`: carries `status`/`statusText`/`response`; thrown by a proxy call on a non-2xx.
186
+ - `@abide/abide/server/json` — JSON `Response`, `Cache-Control: no-store`; `undefined` → 204; phantom `TypedResponse` brand carries the return type.
187
+ - `@abide/abide/server/jsonl` — wraps `AsyncIterable<Frame>` as JSON Lines; frame errors emit a `{"$error":...}` line; cancellation flows to `iter.return()`.
188
+ - `@abide/abide/server/sse` — wraps `AsyncIterable<Frame>` as Server-Sent Events; 15s keepalive comments, error frames as `event: error`.
189
+ - `@abide/abide/server/error` — plain-text error `Response(status, message?, init?)`; defaults to the standard status text; `TypedResponse<never>` so error branches union cleanly.
190
+ - `@abide/abide/server/redirect` — redirect `Response(url, status=302, init?)`; relative URLs allowed, 301/302/303/307/308, `TypedResponse<never>`.
83
191
 
84
192
  ### Request scope — @documentation request-scope
85
193
 
86
- - `abide/server/request` — `request()`: the inbound `Request` for the current SSR/RPC pass (`AsyncLocalStorage`); throws outside a request scope.
87
- - `abide/server/cookies` — `cookies()`: live `Bun.CookieMap` with `.set`/`.delete` writing `Set-Cookie`.
88
- - `abide/server/server` — `server()`: the active `Bun.serve` instance (an in-process stand-in under CLI/MCP/test dispatch).
194
+ - `@abide/abide/server/request` — the inbound `Request` for the current SSR/RPC pass (AsyncLocalStorage); throws outside request scope.
195
+ - `@abide/abide/server/cookies` — `Bun.CookieMap` for the in-flight request; `.set()`/`.delete()` flush to `Set-Cookie`; throws outside scope.
196
+ - `@abide/abide/server/server` — the active `Bun.serve` instance (getter, shows in stack traces); in-process server inside scope when none is bound.
89
197
 
90
198
  ### Configuration — @documentation configuration
91
199
 
92
- - `abide/server/env` — `env(schema)`: validate `Bun.env` against a Standard Schema at module top level (boot fails loudly); also feeds the bundle setup form.
200
+ - `@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.
201
+
202
+ ### Reachability — @documentation observability
203
+
204
+ - `@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).
93
205
 
94
206
  ### Sockets — @documentation sockets
95
207
 
96
- - `abide/server/socket` — `socket({ schema?, tail?, ttl?, clientPublish?, clients? })`: declare a broadcast topic. Returns a `Socket<T>` (isomorphic `AsyncIterable<T>`) with `.publish(msg)` and `.tail(count?, hooks?)`. `tail` retains frames for late joiners, `ttl` lazily evicts old ones, `clientPublish` (default `false`) gates the HTTP/ws publish, `schema` validates publishes and flips on the MCP/CLI read faces.
97
- - `abide/server/sockets/defineSocket` — the server-side construction the preload rewrites `socket()` to; owns the retained buffer, fan-out via `server.publish`, and per-subscriber queues.
208
+ - `@abide/abide/server/socket` — `Socket<T>` declaration; the bundler placeholder throws if called outside `export const <file> = ...` in `src/server/sockets/`.
209
+ - `@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.
98
210
 
99
- ### App, agent, inspector — @documentation plumbing
211
+ ### Agent — @documentation agent
100
212
 
101
- - `abide/server/AppModule` — the type of `src/app.ts`'s optional hooks: `init` (boot + cleanup), `handle` (middleware), `handleError`, `health` (merges into `/__abide/health`), `forwardHeaders`.
102
- - `abide/server/agent` — `agent(engine, messages)`: run a model engine against the current request's MCP surface (caller auth forwarded into every tool call), yielding neutral `AgentFrame`s to wrap in `jsonl`/`sse`.
103
- - `abide/server/InspectorContext` — the capability bundle (`app`, `loadSurface`, `cacheSnapshot`, `inFlightSnapshot`, `onRecord`) handed to `@abide/inspector` when `ABIDE_ENABLE_INSPECTOR=true`.
213
+ - `@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.
104
214
 
105
215
  ### Prompts — @documentation plumbing
106
216
 
107
- - `abide/server/prompts/definePrompt` — `definePrompt(name, { description?, jsonSchema?, render })`: the target the resolver compiles `src/mcp/prompts/<name>.md` to; registers an MCP prompt.
108
- - `abide/server/prompts/renderPromptTemplate` — `renderPromptTemplate(template, args)`: substitute `{{name}}` placeholders (missing empty string).
217
+ - `@abide/abide/server/prompts/definePrompt` — prompt factory: `name` + `{ description, jsonSchema, render }`; registered for MCP enumeration (the resolver generates these from `src/server/prompts/*.md`).
218
+ - `@abide/abide/server/prompts/renderPromptTemplate` — substitute `{{name}}` placeholders in a markdown template from a `Record<string,string>`; missing args render empty.
109
219
 
110
- ## Isomorphic surface`abide/shared/*`
220
+ ### App + inspector hooks @documentation plumbing
111
221
 
112
- ### Cache@documentation cache
222
+ - `@abide/abide/server/AppModule`the `src/app.ts` hook contract: `forwardHeaders`, `init(ctx)`, `handle(req, next)`, `handleError(err, req)`, `health(req)`.
223
+ - `@abide/abide/server/InspectorContext` — capabilities handed to `@abide/inspector` when enabled: identity, `loadSurface()`, `cacheSnapshot()`, `inFlightSnapshot()`, `onRecord(listener)`.
113
224
 
114
- - `abide/shared/cache` — `cache(fn)`: a memoizing invoker. `cache(fn)(args)` reads (SSR-snapshotted, hydrated warm); `.raw`/`.stream` mirror the verb; `.invalidate(selector?, args?)` drops entries; `.patch(selector, updater, …)` optimistic-updates; `.on(subscribable, handler)` folds socket frames into cached values.
225
+ ## Isomorphic surface — abide/shared/*
115
226
 
116
- ### Probes — @documentation probes
227
+ ### RPC contract — @documentation rpc
228
+
229
+ - `@abide/abide/shared/withJsonSchema` — attach a `toJSONSchema()` to a Standard Schema, enabling projection to OpenAPI, MCP tools, CLI help, and bundle forms.
230
+
231
+ ### Errors — @documentation response
232
+
233
+ - `@abide/abide/shared/HttpError` — `Error` subclass thrown by remote calls on non-2xx; carries `status`, `statusText`, and the raw `Response`.
234
+
235
+ ### Cache — @documentation cache
117
236
 
118
- - `abide/shared/pending` — `pending(fn?, args?)`: `true` while a matching call is unsettled (read-only probe).
119
- - `abide/shared/refreshing` — `refreshing(fn?, args?)`: `true` while a matching entry holds a value but a fresher fetch is in flight.
120
- - `abide/shared/online` — `online()`: `navigator.onLine` (client) / offline-header (SSR); reactive.
237
+ - `@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.
121
238
 
122
239
  ### Templating — @documentation templating
123
240
 
124
- - `abide/shared/html` — `html\`…\``(or`html(str)`): mark a string as trusted raw HTML so interpolation injects rather than escapes.
125
- - `abide/shared/snippet` — `snippet(payload)`: brand a payload so an interpolation mounts it instead of escaping (the runtime side of `<template name>`).
126
- - `abide/shared/withJsonSchema` — `withJsonSchema(schema, toJsonSchema)`: attach a JSON Schema to a Standard Schema for OpenAPI/MCP/CLI/form projection.
241
+ - `@abide/abide/shared/html` — mark a string as trusted raw HTML inserted verbatim in interpolations; plain-call and tagged-template forms.
242
+ - `@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.
127
243
 
128
- ### Page & URL — @documentation page · url
244
+ ### Page — @documentation page
129
245
 
130
- - `abide/shared/page` — `page`: a reactive proxy of `route`/`params`/`url`/`navigating` (server reads request scope, client reads the router).
131
- - `abide/shared/url` — `url(path, ...args)`: typed, base-correct URL builder for RPC paths, page routes, and assets.
246
+ - `@abide/abide/shared/page` — reactive page snapshot: matched route, decoded params, browser-space URL, and a `navigating` flag; isomorphic.
247
+
248
+ ### Probes — @documentation probes
249
+
250
+ - `@abide/abide/shared/pending` — reactive: any cache call in-flight, any stream awaiting its first frame, or a tagged group pending.
251
+ - `@abide/abide/shared/refreshing` — reactive: a cache call holds a value while revalidating, or a stream reconnects with its window retained.
252
+ - `@abide/abide/shared/online` — reactive connectivity: `navigator.onLine` on the client, the request's `OFFLINE_HEADER` on the server.
253
+ - `@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.
254
+
255
+ ### URL — @documentation url
256
+
257
+ - `@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.
132
258
 
133
259
  ### Observability — @documentation observability
134
260
 
135
- - `abide/shared/health` — `health()`: reactive `{ reachable, }` polled from `/__abide/health`, composing app `health()` fields and `navigator.onLine`.
136
- - `abide/shared/log` — `log`/`.warn`/`.error`/`.trace(label, fn)` on the always-on channel; `log.channel(name)` is `DEBUG`-gated; TSV by default, JSON under `ABIDE_LOG_FORMAT=json`.
137
- - `abide/shared/trace` — `trace()`: the W3C `traceparent` for the current request, or `undefined` outside scope.
138
- - `abide/shared/createSubscriber` — `createSubscriber(start)`: open-on-first-read / close-on-last-reader resource lifecycle for reactive sources.
139
- - `abide/server/reachable` — `reachable(host)` (server-only): cached liveness probe; HEADs the origin and background-polls per `ABIDE_REACHABLE_TTL`, so reads resolve instantly off the warm value.
261
+ - `@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.
262
+ - `@abide/abide/shared/trace` — the request's W3C `traceparent` string for propagation; `undefined` outside request scope.
140
263
 
141
- ## UI surface `abide/ui/*` (client-only)
264
+ ### Subscribers@documentation plumbing
265
+
266
+ - `@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.
267
+
268
+ ## UI surface — abide/ui/* (client-only)
142
269
 
143
270
  ### Reactive state — @documentation reactive-state
144
271
 
145
- - `abide/ui/scope` — `scope()`: the sole reactive surface. `.state(initial?, transform?)` (writable cell), `.computed(fn)` (read-only), `.linked(seed, transform?)` (local draft reseeded from upstream). Bare `state`/`computed`/`linked` are a compile error; a writable computed is expressed at the binding (`bind:value={{ get, set }}`). Also carries doc/persistence/undo/broadcast methods.
272
+ - `@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 }}`.
146
273
 
147
- ### Effect — @documentation effect
274
+ ### Effects — @documentation effect
148
275
 
149
- - `abide/ui/effect` — `effect(fn)`: run now, re-run when a read cell changes; `fn` may be async and may return a teardown. Returns a disposer. In scope inside `.abide` without import.
276
+ - `@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.
150
277
 
151
- ### Tail & navigate — @documentation tail · navigate
278
+ ### Tail — @documentation tail
152
279
 
153
- - `abide/ui/tail` — `tail(subscribable)`: latest frame of a `Socket`/stream (reactive); window form `tail(s, { last })` array; `tail.error`/`tail.status` probes.
154
- - `abide/ui/navigate` — `navigate(path, replace?)`: client navigation through the router.
280
+ - `@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)`.
155
281
 
156
- ### Outbox — @documentation ui
282
+ ### Navigation — @documentation navigate
157
283
 
158
- - `abide/ui/outbox` — `outbox({ key, send, store?, online?, onDrop? })`: a durable FIFO mutation queue (`.enqueue`/`.pending`/`.flush`) for offline-tolerant writes.
284
+ - `@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.
159
285
 
160
- ### Runtime & client entry — @documentation plumbing
286
+ ### Outbox — @documentation ui
161
287
 
162
- - `abide/ui/enterScope`, `abide/ui/exitScope` — push/pop the lexical scope around an SSR render.
163
- - `abide/ui/router`, `abide/ui/startClient`, `abide/ui/renderToStream` — the client router, the official client entry (reads `window.__SSR__`, seeds cache, starts the router), and the out-of-order SSR streamer.
164
- - `abide/ui/remoteProxy`, `abide/ui/socketProxy`the bundler-emitted client substitutes that swap a server verb/socket import for a `fetch` proxy / ws-multiplexed `Socket`.
165
- - `abide/ui/dom/*` — the compiled-template DOM runtime (one node-builder per construct): `mount`, `mountChild`, `mergeProps`, `spreadProps`, `restProps`, `spreadAttrs`, `readCall`, `mountSlot`, `outlet`, `hydrate`, `skeleton`, `cloneStatic`, `anchorCursor`, `text`, `appendText`, `appendTextAt`, `appendSnippet`, `appendStatic`, `attr`, `on`, `attach`, `each`, `eachAsync`, `when`, `awaitBlock`, `tryBlock`, `switchBlock`, `applyResolved`. `outlet` is the marker-range a layout `<slot/>` compiles to (no wrapper element — the router fills it with the next chain layer as a direct child). `readCall` guards a method call lowered onto a reactive-doc read (`model.draft.trim()` → `readCall(model.read("draft"), …)`) so a nullish read throws naming the scope path and member, not the engine's opaque `undefined is not an object`. A `{...expr}` spreads onto a component (its props, via `mergeProps`/`spreadProps`) or a native element (its attributes, via `spreadAttrs` — each key reactive, an `on<event>` function becomes a listener); keys resolve last-wins in source order. A component reads the props it doesn't name with `const { foo, ...rest } = props()` — `rest` (a `restProps` bag of the unconsumed props) can itself be spread onward (`<input {...rest}/>`).
166
- - `abide/ui/runtime/*`render-pass helpers: `escapeKey` (JSON-Pointer key escaping), `nextBlockId`, `enterRenderPass`, `exitRenderPass`.
288
+ - `@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.
289
+
290
+ ### Client glue@documentation plumbing
291
+
292
+ - `@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.
293
+ - `@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.
294
+ - `@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.
295
+ - `@abide/abide/ui/remoteProxy` — client remote function: build the `Request` (base, mount, offline header, traceparent), decode by Content-Type, throw `HttpError` on non-2xx.
296
+ - `@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.
297
+ - `@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.
298
+
299
+ ### DOM runtime — @documentation plumbing
300
+
301
+ Compiler emit targets, not written by hand — the runtime the compiled `.abide`
302
+ output calls into to build, hydrate, and reconcile the DOM.
303
+
304
+ - `@abide/abide/ui/dom/mount` — mount a top-level page/layout into a host element under an ownership scope; returns a disposer.
305
+ - `@abide/abide/ui/dom/mountChild` — mount a child component as a marker-bounded range, with optional hot-reload support.
306
+ - `@abide/abide/ui/dom/mountSlot` — mount a slot's content as a marker-bounded range that renders once without re-render.
307
+ - `@abide/abide/ui/dom/outlet` — a layout's empty outlet boundary for the router to fill with the next chain layer.
308
+ - `@abide/abide/ui/dom/hydrate` — adopt existing server-rendered DOM instead of rebuilding, attaching listeners and effects to preserve focus/scroll.
309
+ - `@abide/abide/ui/dom/skeleton` — realize a compiled skeleton under a parent, returning the element and anchor holes for binding.
310
+ - `@abide/abide/ui/dom/cloneStatic` — append a fully-static subtree by cloning a cached template (create) or claiming server nodes (hydrate).
311
+ - `@abide/abide/ui/dom/appendStatic` — a static text node, claimed on hydrate or appended on create.
312
+ - `@abide/abide/ui/dom/anchorCursor` — position a control-flow block or slot by its skeleton anchor, returning the insertion reference.
313
+ - `@abide/abide/ui/dom/text` — a text node whose content tracks a reactive read, updating fine-grained on cell changes.
314
+ - `@abide/abide/ui/dom/appendText` — a reactive interpolation branching on value kind: escaped text, snippet builder, or raw HTML.
315
+ - `@abide/abide/ui/dom/appendTextAt` — as `appendText`, positioned at a skeleton anchor with live re-parsing.
316
+ - `@abide/abide/ui/dom/appendSnippet` — a snippet call that mounts/remounts its builder in a marker-bounded range on argument change.
317
+ - `@abide/abide/ui/dom/attr` — bind an element attribute to a reactive read with present/absent semantics.
318
+ - `@abide/abide/ui/dom/on` — attach an event listener registered with the ownership scope for cleanup on dispose.
319
+ - `@abide/abide/ui/dom/attach` — run an attachment at build time and register its optional teardown with the ownership scope.
320
+ - `@abide/abide/ui/dom/each` — keyed list binding with reconciliation by key, minimal DOM moves, optional hydration.
321
+ - `@abide/abide/ui/dom/eachAsync` — async keyed list appending rows as an iterator yields, reconciling by key with error handling.
322
+ - `@abide/abide/ui/dom/when` — conditional binding that swaps range content on condition flip; an unchanged condition is a no-op.
323
+ - `@abide/abide/ui/dom/awaitBlock` — async binding with pending/resolved/error branches, reactive re-run, and hydration by precedence.
324
+ - `@abide/abide/ui/dom/tryBlock` — synchronous error boundary building guarded content or the error branch on throw.
325
+ - `@abide/abide/ui/dom/switchBlock` — multi-branch binding picking the first case matching the subject (strict `===`).
326
+ - `@abide/abide/ui/dom/applyResolved` — bundle-side consumer of streamed resolution chunks, swapping pending DOM and seeding cache.
327
+ - `@abide/abide/ui/dom/mergeProps` — compose a child's props from layered sources (explicit, spreads, slot), last-writer-wins.
328
+ - `@abide/abide/ui/dom/spreadProps` — wrap a reactive spread layer so every key resolves to a live value thunk.
329
+ - `@abide/abide/ui/dom/restProps` — return unconsumed prop values as a live object, unwrapping thunks, excluding consumed/slot keys.
330
+ - `@abide/abide/ui/dom/spreadAttrs` — spread an object's keys onto a native element, binding event listeners and reactive attributes.
331
+ - `@abide/abide/ui/dom/readCall` — guard a method call on a reactive-document read, with friendly errors for nullish/non-callable members.
332
+
333
+ ### Render-pass runtime — @documentation plumbing
334
+
335
+ - `@abide/abide/ui/runtime/escapeKey` — JSON-Pointer-escape reactive-doc path keys (`~`→`~0`, `/`→`~1`) so composite keys survive path joining.
336
+ - `@abide/abide/ui/runtime/nextBlockId` — next block id in the render pass (per await/try block).
337
+ - `@abide/abide/ui/runtime/enterRenderPass` — mark entry into a render/mount, resetting the block-id counter at depth 0.
338
+ - `@abide/abide/ui/runtime/exitRenderPass` — mark exit from a render/mount, unwinding the depth counter.
167
339
 
168
340
  ## Build / tooling — @documentation building
169
341
 
170
- - `abide/build` — `build({ cwd?, minify?, compress?, clean? })`: build the client bundle to `dist/_app`, emitting gzip siblings when `compress`.
171
- - `abide/compile` — `compile({ cwd?, target?, outfile?, buildClient? })`: produce a standalone Bun server executable (bytecode, embedded compressed assets).
172
- - `abide/preload` — the Bun preload (`bunfig.toml`) that installs the `.abide`, resolver, and CSS-noop plugins and rewrites verb/socket imports.
173
- - `abide/resolver-plugin` — `abideResolverPlugin({ cwd?, embedAssets?, target? })`: virtualizes every generated module and rewrites verbs/sockets per side.
174
- - `abide/ui-plugin` — `abideUiPlugin`: the Bun loader compiling `.abide` SFCs to ES modules (scoped `<style>` via virtual imports; a `layout.abide`'s `<slot/>` lowers to the marker-range `outlet` the router fills as a direct child, no wrapper element).
175
- - `abide/tsconfig` — `tsconfig.app.json` for consumers to extend (`bundler` resolution, `strict`, `noEmit`, `bun` types).
342
+ - `@abide/abide/build` — build the client bundle with optional gzip, via atomic directory swaps.
343
+ - `@abide/abide/compile` — produce a standalone Bun server executable with embedded assets.
344
+ - `@abide/abide/preload` — Bun preload that registers the `.abide` loader and resolver before builds.
345
+ - `@abide/abide/resolver-plugin` — Bun plugin resolving bare/extensionless paths, generating manifests, and handling virtual modules.
346
+ - `@abide/abide/ui-plugin` — Bun plugin that loads `.abide` single-file components, compiling them to ES modules with scoped styles.
347
+ - `@abide/abide/tsconfig` — the strict ESNext + bundler-resolution + Bun-types TypeScript config consumers extend.
176
348
 
177
349
  ## Desktop bundle — @documentation bundle
178
350
 
179
- - `abide/bundle/BundleWindow` — the type for `src/bundle/window.ts`'s default export: `{ title?, width?, height?, menu?, config? }`, baked into the launcher.
180
- - `abide/bundle/BundleMenu`, `abide/bundle/BundleMenuItem` — a custom top-level menu and its entries (`separator` / `emit` a `abide:menu` event / `navigate`; optional Cmd `shortcut`).
181
- - `abide/bundle/onMenu` — `onMenu(handler)` / `onMenu(name, handler)`: subscribe to menu clicks (inert in SSR and plain browser tabs); returns an unsubscribe.
182
- - `abide/bundle/bundled` — `bundled()`: `true` when running inside the abide desktop bundle (isomorphic).
183
- - `abide/server/appDataDir` — `appDataDir()`: the per-user data dir keyed by program name (pure; where abide stores `.env` / last-connection).
351
+ - `@abide/abide/server/appDataDir` — per-user app data directory keyed by the bundler-injected program name; cwd-independent, pure.
352
+ - `@abide/abide/bundle/BundleWindow` — type for the bundle window config: title, size, custom menus, setup form schema.
353
+ - `@abide/abide/bundle/BundleMenu` — type for a top-level menu inserted between the standard Edit and Window menus.
354
+ - `@abide/abide/bundle/BundleMenuItem` — type for one menu entry: separator, clickable emit, or navigate.
355
+ - `@abide/abide/bundle/onMenu` — subscribe to bundle menu clicks (optional filter); returns an unsubscribe.
356
+ - `@abide/abide/bundle/bundled` — true inside the desktop webview, false as a standalone web app.
184
357
 
185
358
  ## MCP — @documentation mcp
186
359
 
187
- - `abide/mcp/createMcpServer` — `createMcpServer(opts?)`: the framework-internal MCP server behind `/__abide/mcp`; tools derive from `clients.mcp` verbs (auto-on for read-only) and sockets (`<name>-tail`, `<name>-publish`), with inbound-request auth and an optional `authorize` hook.
360
+ - `@abide/abide/mcp/createMcpServer` — build an MCP server bound to the project's RPC registry, deriving tools and sockets with auth.
188
361
 
189
362
  ## Testing — @documentation testing
190
363
 
191
- - `abide/test/createTestApp` — `createTestApp(cwd?)`: a test harness with typed `app.rpc.<verb>` / `app.sockets.<name>` maps over the project's virtual modules.
192
- - `abide/test/createScriptedSurface` — `createScriptedSurface(tools?)`: a scripted `AgentSurface` recording every tool `call` for agent-engine tests.
193
- - `abide/test/assertAgentFrameConformance` — `assertAgentFrameConformance(stream)`: assert a frame stream meets the neutral `AgentFrame` contract (one terminal `done`, paired `tool_use`/`tool_result`).
364
+ - `@abide/abide/test/createTestApp` — build a test app with augmentable verb/socket maps and bundled virtual modules.
365
+ - `@abide/abide/test/createScriptedSurface` — a scripted `AgentSurface` with declarative tool stubs and recorded calls.
366
+ - `@abide/abide/test/assertAgentFrameConformance` — collect an engine's frame stream and validate it against the neutral `AgentFrame` contract.
194
367
 
195
368
  ## Generated machine surfaces
196
369
 
197
- | Route | Purpose |
198
- | ---------------------------------- | ------------------------------------------------------------------------------- |
199
- | `GET /openapi.json` | OpenAPI spec of the public RPC surface, built on first request |
200
- | `POST /__abide/mcp` | MCP JSON-RPC endpoint (tools from verbs/sockets, prompts, resources) |
201
- | `GET/POST /__abide/sockets` | WebSocket multiplex upgrade (one connection per client) |
202
- | `GET/POST /__abide/sockets/<name>` | A socket's HTTP face: `GET` retained tail, `POST` publish |
203
- | `GET /__abide/health` | Health probe JSON (identity + app `health()` fields), pre-auth |
204
- | `GET /__abide/identity` | Legacy-shaped alias of the health payload |
205
- | `GET /__abide/cli` | Platform-detecting install script; `/__abide/cli/<platform>` streams the binary |
206
- | `GET /__abide/inspector` | Opt-in inspector UI + data (when enabled and `@abide/inspector` installed) |
207
- | `GET /__abide/hot/<id>` | Dev-only component hot-module fetch (`.abide` HMR) |
370
+ | Route | Serves |
371
+ | --- | --- |
372
+ | `/openapi.json` | OpenAPI 3.1 spec of every RPC verb (methods, params, schemas, operationIds) |
373
+ | `/__abide/health`, `/__abide/identity` | Health JSON: app `health()` fields merged with `{ abide: version, name }` |
374
+ | `/__abide/mcp` | MCP endpoint; delegates to `mcp.handle(request)`, publishing the surface on first access |
375
+ | `/__abide/sockets` | The single ws multiplex for every defined socket; per-name HTTP face for tail/publish |
376
+ | `/__abide/cli` | Platform-detecting shell installer that downloads and runs the CLI binary |
377
+ | `/__abide/inspector` | Inspector UI + data when `ABIDE_ENABLE_INSPECTOR=true` (operator-only) |
378
+ | `/__abide/hot/<moduleId>` | Dev-only HMR: compiles an edited `.abide` to importable JS; 404 falls back to reload |
208
379
 
209
380
  ## Environment variables
210
381
 
211
- | Var | Effect |
212
- | ----------------------------- | ----------------------------------------------------------------------- |
213
- | `PORT` | Bind port; unset scans upward from 3000 |
214
- | `APP_URL` | Base URL whose pathname becomes the mount base (default `/`) |
215
- | `ABIDE_APP_URL` | Base URL baked into the downloaded CLI binary |
216
- | `ABIDE_APP_TOKEN` | Bearer token baked into the CLI binary (only if the request was authed) |
217
- | `ABIDE_CLIENT_TIMEOUT` | Client RPC timeout, ms (1–600000); unset is unbounded |
218
- | `ABIDE_MAX_REQUEST_BODY_SIZE` | Server request-body ceiling (default ~128MB) |
219
- | `ABIDE_IDLE_TIMEOUT` | Per-connection idle timeout, s (0–255, default 10) |
220
- | `ABIDE_REACHABLE_TIMEOUT` | Per-probe `reachable()` timeout, ms (default 3000) |
221
- | `ABIDE_REACHABLE_TTL` | `reachable()` poll cadence, ms (default 30000) |
222
- | `ABIDE_DATA_DIR` | Override the app data dir (used as-is, no program name appended) |
223
- | `ABIDE_LOG_FORMAT` | `json` emits log records as JSON instead of TSV |
224
- | `DEBUG` | Gate diagnostic log channels (e.g. `abide`, `abide:build`, `-abide`) |
225
- | `ABIDE_ENABLE_INSPECTOR` | `true` activates the opt-in inspector surface |
226
- | `ABIDE_INSPECT` | Enable webview devtools in the desktop bundle |
382
+ | Variable | Effect |
383
+ | --- | --- |
384
+ | `PORT` | Listener port (0–65535); honored exactly if set, else scans up from 3000 |
385
+ | `APP_URL` | Mount base path from the URL's pathname (`https://x/v2` → `/v2`); mounts server URLs + shell |
386
+ | `ABIDE_APP_URL` | App origin sent to the CLI download tarball's `.env` |
387
+ | `ABIDE_APP_TOKEN` | Bearer token forwarded into the CLI tarball `.env` when the request carries `Authorization` |
388
+ | `ABIDE_CLIENT_TIMEOUT` | Client RPC fetch timeout (ms, 1–600000); shipped to the browser via SSR |
389
+ | `ABIDE_MAX_REQUEST_BODY_SIZE` | Server-wide request body ceiling (bytes); default ~Bun default |
390
+ | `ABIDE_IDLE_TIMEOUT` | Per-connection idle timeout (s, 0–255); default 10 |
391
+ | `ABIDE_DATA_DIR` | Override the platform app-data dir (used as-is, no program name appended) |
392
+ | `ABIDE_REACHABLE_TIMEOUT` | Per-HEAD reachability probe timeout (ms, 100–60000); default 3000 |
393
+ | `ABIDE_REACHABLE_TTL` | Reachability poll cadence (ms, 1000–600000); default 30000 |
394
+ | `ABIDE_LOG_FORMAT` | `json` emits JSON log records to stdout instead of TSV |
395
+ | `DEBUG` | npm-debug channel selection (`abide`, `abide:*`, `-abide` to negate request-close logs) |
396
+ | `ABIDE_ENABLE_INSPECTOR` | `true` mounts `@abide/inspector` on `/__abide/inspector` |
397
+ | `ABIDE_INSPECT` | Enable the native webview inspector (right-click Inspect) in bundle windows |
227
398
 
228
399
  ---
229
400
 
230
- This map mirrors `package.json`'s `exports`. After adding or renaming an export, run `bun run packages/abide/scripts/readmeSurfaces.ts` to re-derive the slugs and catch any untagged export, then regenerate this file.
401
+ Mirrors `package.json` `exports`. After adding or renaming an export, run
402
+ `bun run packages/abide/scripts/readmeSurfaces.ts` and update this map.