@abide/abide 0.46.0 → 0.48.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 (194) hide show
  1. package/AGENTS.md +370 -320
  2. package/CHANGELOG.md +94 -0
  3. package/README.md +203 -168
  4. package/package.json +12 -8
  5. package/src/abideLsp.ts +11 -12
  6. package/src/abideResolverPlugin.ts +9 -4
  7. package/src/lib/bundle/disconnected.abide +3 -0
  8. package/src/lib/cli/printCommandHelp.ts +43 -0
  9. package/src/lib/cli/printSessionHelp.ts +2 -1
  10. package/src/lib/cli/{printHelp.ts → printTopLevelHelp.ts} +3 -40
  11. package/src/lib/cli/runCli.ts +2 -1
  12. package/src/lib/mcp/buildPrompts.ts +25 -0
  13. package/src/lib/mcp/dispatchMcpRequest.ts +8 -6
  14. package/src/lib/mcp/mcpResourceServerSlot.ts +5 -10
  15. package/src/lib/mcp/mcpSurface.ts +13 -252
  16. package/src/lib/mcp/mcpTools.ts +187 -0
  17. package/src/lib/mcp/renderPrompt.ts +25 -0
  18. package/src/lib/mcp/types/McpSurface.ts +15 -0
  19. package/src/lib/mcp/types/PromptDescriptor.ts +5 -0
  20. package/src/lib/mcp/types/PromptMessage.ts +2 -0
  21. package/src/lib/mcp/types/ToolDescriptor.ts +7 -0
  22. package/src/lib/mcp/types/ToolResult.ts +2 -0
  23. package/src/lib/server/agent.ts +1 -1
  24. package/src/lib/server/jsonl.ts +2 -2
  25. package/src/lib/server/rpc/defineRpc.ts +5 -0
  26. package/src/lib/server/runtime/buildCacheSnapshot.ts +6 -6
  27. package/src/lib/server/runtime/containsTraversal.ts +21 -17
  28. package/src/lib/server/runtime/createAppAssetServer.ts +11 -16
  29. package/src/lib/server/runtime/createServer.ts +101 -71
  30. package/src/lib/server/runtime/createUiPageRenderer.ts +23 -4
  31. package/src/lib/server/runtime/devClientFingerprint.ts +3 -3
  32. package/src/lib/server/runtime/devHotModuleResponse.ts +2 -1
  33. package/src/lib/server/runtime/finalizeResponse.ts +32 -0
  34. package/src/lib/server/runtime/snapshotEntryFromCache.ts +8 -17
  35. package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
  36. package/src/lib/server/runtime/types/InspectorCacheSnapshot.ts +4 -4
  37. package/src/lib/server/runtime/types/InspectorContext.ts +1 -1
  38. package/src/lib/server/socket.ts +1 -1
  39. package/src/lib/server/sockets/createSocketDispatcher.ts +28 -5
  40. package/src/lib/server/sockets/defineSocket.ts +26 -7
  41. package/src/lib/server/sockets/types/SocketRegistryEntry.ts +1 -1
  42. package/src/lib/server/sockets/types/SocketRoutes.ts +1 -1
  43. package/src/lib/server/sse.ts +2 -2
  44. package/src/lib/shared/DEFER.ts +8 -0
  45. package/src/lib/shared/activeCacheStore.ts +2 -2
  46. package/src/lib/shared/activePage.ts +2 -2
  47. package/src/lib/shared/attachRpcSelectorMethods.ts +42 -0
  48. package/src/lib/shared/attachSocketSelectorMethods.ts +34 -0
  49. package/src/lib/shared/basePath.ts +2 -2
  50. package/src/lib/shared/baseSlot.ts +6 -5
  51. package/src/lib/shared/bodyValueForKind.ts +1 -2
  52. package/src/lib/shared/buildSocketOverChannel.ts +40 -4
  53. package/src/lib/shared/cache.ts +522 -117
  54. package/src/lib/shared/cacheEntryFromSnapshot.ts +3 -22
  55. package/src/lib/shared/cacheStoreSlot.ts +9 -5
  56. package/src/lib/shared/cacheStores.ts +3 -3
  57. package/src/lib/{server/runtime → shared}/createReachable.ts +2 -2
  58. package/src/lib/shared/createRemoteFunction.ts +58 -9
  59. package/src/lib/shared/createResolverSlot.ts +24 -31
  60. package/src/lib/shared/createSocketSubRegistry.ts +2 -2
  61. package/src/lib/shared/decodeRefJson.ts +4 -2
  62. package/src/lib/shared/decodeResponse.ts +8 -6
  63. package/src/lib/shared/done.ts +14 -0
  64. package/src/lib/shared/encodeRefJson.ts +4 -2
  65. package/src/lib/shared/hydratingSlot.ts +12 -0
  66. package/src/lib/shared/isLayoutFile.ts +12 -0
  67. package/src/lib/shared/keyForRemoteCall.ts +2 -1
  68. package/src/lib/shared/keyPrefixForRemote.ts +12 -0
  69. package/src/lib/shared/matchRoute.ts +175 -0
  70. package/src/lib/shared/normalizePathname.ts +11 -0
  71. package/src/lib/shared/pageSlot.ts +14 -5
  72. package/src/lib/shared/pageUrlForFile.ts +3 -3
  73. package/src/lib/shared/parseRouteSegments.ts +16 -7
  74. package/src/lib/shared/patch.ts +41 -0
  75. package/src/lib/shared/peek.ts +35 -0
  76. package/src/lib/shared/prepareRemoteExport.ts +40 -0
  77. package/src/lib/shared/prepareRpcModule.ts +98 -16
  78. package/src/lib/shared/prepareSocketModule.ts +11 -15
  79. package/src/lib/shared/queryStringFromArgs.ts +11 -0
  80. package/src/lib/shared/reachable.ts +102 -0
  81. package/src/lib/shared/refresh.ts +22 -0
  82. package/src/lib/shared/requestScopeSlot.ts +10 -7
  83. package/src/lib/shared/routeParamsShape.ts +9 -9
  84. package/src/lib/shared/rpcErrorRegistry.ts +69 -0
  85. package/src/lib/shared/selectorPrefix.ts +3 -10
  86. package/src/lib/shared/setOwnProperty.ts +19 -0
  87. package/src/lib/shared/sharedCacheStore.ts +14 -0
  88. package/src/lib/shared/sharedCacheStoreSlot.ts +10 -0
  89. package/src/lib/shared/snippet.ts +1 -1
  90. package/src/lib/shared/subscribableProbes.ts +111 -0
  91. package/src/lib/shared/tailProbeSlot.ts +8 -1
  92. package/src/lib/shared/types/CacheEntry.ts +9 -15
  93. package/src/lib/shared/types/CacheOptions.ts +23 -11
  94. package/src/lib/shared/types/CacheSnapshotEntry.ts +0 -5
  95. package/src/lib/shared/types/CacheStats.ts +1 -1
  96. package/src/lib/shared/types/RemoteCallable.ts +25 -7
  97. package/src/lib/shared/types/RemoteFunction.ts +48 -13
  98. package/src/lib/shared/types/ResolverSlot.ts +7 -4
  99. package/src/lib/shared/types/RpcError.ts +26 -0
  100. package/src/lib/shared/types/SmartReadOptions.ts +36 -0
  101. package/src/lib/shared/types/Socket.ts +54 -0
  102. package/src/lib/shared/types/SsrBootState.ts +27 -0
  103. package/src/lib/shared/types/SsrPayload.ts +21 -0
  104. package/src/lib/shared/types/Subscribable.ts +13 -13
  105. package/src/lib/shared/types/TailHooks.ts +2 -1
  106. package/src/lib/shared/url.ts +29 -14
  107. package/src/lib/shared/wakeHydrationPeeks.ts +20 -0
  108. package/src/lib/shared/writeTestSocketsDts.ts +1 -1
  109. package/src/lib/test/createScriptedSurface.ts +1 -1
  110. package/src/lib/test/createTestApp.ts +8 -8
  111. package/src/lib/test/createTestSocketChannel.ts +3 -3
  112. package/src/lib/ui/compile/REACTIVE_CALLEES.ts +5 -6
  113. package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +10 -7
  114. package/src/lib/ui/compile/abideUiPlugin.ts +2 -2
  115. package/src/lib/ui/compile/analyzeComponent.ts +28 -6
  116. package/src/lib/ui/compile/compileModule.ts +137 -11
  117. package/src/lib/ui/compile/compileShadow.ts +101 -84
  118. package/src/lib/ui/compile/desugarSignals.ts +120 -107
  119. package/src/lib/ui/compile/generateBuild.ts +48 -16
  120. package/src/lib/ui/compile/generateSSR.ts +117 -14
  121. package/src/lib/ui/compile/identifierReferencePattern.ts +13 -0
  122. package/src/lib/ui/compile/lowerContext.ts +10 -4
  123. package/src/lib/ui/compile/lowerScript.ts +72 -5
  124. package/src/lib/ui/compile/parseTemplate.ts +1 -14
  125. package/src/lib/ui/compile/prepareNestedScript.ts +31 -17
  126. package/src/lib/ui/compile/resolveReactiveExport.ts +95 -0
  127. package/src/lib/ui/compile/signalCallee.ts +24 -0
  128. package/src/lib/ui/compile/stripEffects.ts +14 -11
  129. package/src/lib/ui/compile/types/AnalyzedComponent.ts +5 -0
  130. package/src/lib/ui/compile/types/TemplateNode.ts +0 -5
  131. package/src/lib/ui/dom/appendStatic.ts +9 -1
  132. package/src/lib/ui/dom/appendText.ts +15 -3
  133. package/src/lib/ui/dom/assertClaimedText.ts +20 -0
  134. package/src/lib/ui/dom/awaitBlock.ts +19 -83
  135. package/src/lib/ui/dom/bindSelectValue.ts +48 -0
  136. package/src/lib/ui/dom/each.ts +12 -1
  137. package/src/lib/ui/dom/hydrate.ts +10 -0
  138. package/src/lib/ui/dom/mountChild.ts +2 -5
  139. package/src/lib/ui/dom/mountRange.ts +0 -75
  140. package/src/lib/ui/effect.ts +4 -3
  141. package/src/lib/ui/installHotBridge.ts +4 -2
  142. package/src/lib/ui/remoteProxy.ts +18 -2
  143. package/src/lib/ui/renderToStream.ts +9 -13
  144. package/src/lib/ui/resumeSeedScript.ts +2 -2
  145. package/src/lib/ui/router.ts +21 -2
  146. package/src/lib/ui/runtime/RESUME.ts +0 -6
  147. package/src/lib/ui/runtime/ambientScopeBacking.ts +1 -1
  148. package/src/lib/ui/runtime/types/SsrRender.ts +3 -4
  149. package/src/lib/ui/scope.ts +6 -3
  150. package/src/lib/ui/seedBootState.ts +53 -0
  151. package/src/lib/ui/socketChannel.ts +2 -2
  152. package/src/lib/ui/socketProxy.ts +9 -3
  153. package/src/lib/ui/startClient.ts +17 -31
  154. package/src/lib/ui/state.ts +44 -13
  155. package/src/lib/ui/sync.ts +11 -5
  156. package/src/lib/ui/tryEncodeResume.ts +2 -5
  157. package/src/lib/ui/types/Scope.ts +14 -10
  158. package/src/lib/ui/watch.ts +140 -0
  159. package/src/serverEntry.ts +13 -11
  160. package/template/CLAUDE.md +1 -1
  161. package/template/package.json +1 -1
  162. package/template/src/server/rpc/getHello.ts +3 -3
  163. package/template/src/ui/pages/page.abide +2 -2
  164. package/template/test/app.test.ts +1 -1
  165. package/src/lib/server/reachable.ts +0 -45
  166. package/src/lib/server/sockets/types/Socket.ts +0 -23
  167. package/src/lib/shared/CACHE_WRAPPED.ts +0 -9
  168. package/src/lib/shared/DEFER_MIN_ARRAY_LENGTH.ts +0 -14
  169. package/src/lib/shared/baseResolver.ts +0 -10
  170. package/src/lib/shared/cacheKeyOf.ts +0 -7
  171. package/src/lib/shared/cacheKeyStore.ts +0 -8
  172. package/src/lib/shared/cacheStoreResolver.ts +0 -12
  173. package/src/lib/shared/globalCacheStore.ts +0 -15
  174. package/src/lib/shared/globalCacheStoreResolver.ts +0 -12
  175. package/src/lib/shared/globalCacheStoreSlot.ts +0 -9
  176. package/src/lib/shared/pageResolver.ts +0 -16
  177. package/src/lib/shared/recordCacheKey.ts +0 -6
  178. package/src/lib/shared/requestScopeResolver.ts +0 -12
  179. package/src/lib/shared/setBaseResolver.ts +0 -4
  180. package/src/lib/shared/setCacheStoreResolver.ts +0 -4
  181. package/src/lib/shared/setGlobalCacheStoreResolver.ts +0 -4
  182. package/src/lib/shared/setPageResolver.ts +0 -4
  183. package/src/lib/shared/setRequestScopeResolver.ts +0 -4
  184. package/src/lib/shared/toBunRoutePattern.ts +0 -28
  185. package/src/lib/shared/types/TailOptions.ts +0 -10
  186. package/src/lib/ui/deferResume.ts +0 -36
  187. package/src/lib/ui/dom/firstElementBetween.ts +0 -14
  188. package/src/lib/ui/matchRoute.ts +0 -106
  189. package/src/lib/ui/runtime/scheduleWake.ts +0 -28
  190. package/src/lib/ui/runtime/whenIdle.ts +0 -21
  191. package/src/lib/ui/runtime/whenVisible.ts +0 -105
  192. package/src/lib/ui/tail.ts +0 -324
  193. /package/src/lib/{server/sockets → shared}/types/SocketClientFrame.ts +0 -0
  194. /package/src/lib/{server/sockets → shared}/types/SocketServerFrame.ts +0 -0
package/AGENTS.md CHANGED
@@ -1,420 +1,470 @@
1
1
  # AGENTS.md — abide complete surface map
2
2
 
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.
5
- > This is the whole-API read; the README is the curated 3-primitive intro.
6
- > CONTEXT.md is the glossary, `docs/adr/` the rationale.
7
-
8
- **No barrels.** Every public name has its own module path
9
- (`abide/server/GET`, `abide/shared/cache`, `abide/ui/scope`)there is no
10
- umbrella `index.ts`, so importing one name never drags side-effecting siblings
11
- into the bundle. The namespace marks the side a name runs on: `abide/server/*`
12
- is server-only, `abide/ui/*` is client-only, `abide/shared/*` is isomorphic
13
- (same callable, same behaviour on both sides; the bundler swaps the runtime).
14
-
15
- Package: `@abide/abide`, one runtime (Bun `>=1.3.0`), one direct dependency
16
- (TypeScript). The bullets below name each export by its import specifier in the
17
- `abide/…` shorthand — the published package is `@abide/abide`, so
18
- `abide/server/GET` imports from `@abide/abide/server/GET`. These are import
19
- specifiers, not source file paths.
3
+ > This file is the exhaustive public-surface map of `@abide/abide`: every
4
+ > `exports` key, grouped by namespace, with its import specifier and a one-line
5
+ > spec. The README is the curated three-primitive intro; `CONTEXT.md` is the
6
+ > domain glossary; `docs/adr/` holds the rationale behind decisions. Ground
7
+ > rules: there are **no barrels** — every public name has its own module path,
8
+ > and the namespace marks the side it runs on (`abide/server/*` server-only,
9
+ > `abide/ui/*` client-only, `abide/shared/*` isomorphicsame callable, same
10
+ > behavior on both sides). Package `@abide/abide`, runtime Bun 1.3, one
11
+ > direct dependency (TypeScript). Import specifiers below are `exports`-map
12
+ > keys (`@abide/abide/server/GET`), not source file paths.
20
13
 
21
14
  ## The premise
22
15
 
23
- One typed RPC declaration fans out to every surface:
16
+ One typed declaration fans out to every surface:
24
17
 
25
18
  ```text
26
- export const getMessages = GET(fn, { inputSchema })
27
-
28
- ┌───────────────┬─────────────┼─────────────┬───────────────┐
29
- ▼ ▼ ▼ ▼ ▼
30
- SSR call browser fetch MCP tool CLI command OpenAPI op
31
- cache(fn)() typed proxy (read-only) getMessages /openapi.json
32
- (in-process) (swapped fn())
19
+ src/server/rpc/getMessages.ts
20
+
21
+ ├─ SSR / server await getMessages({ room }) in-process, no HTTP
22
+ ├─ browser await getMessages({ room }) typed fetch proxy
23
+ ├─ HTTP GET /rpc/getMessages?room=…
24
+ ├─ CLI my-app get-messages --room
25
+ ├─ MCP tool: get-messages
26
+ └─ OpenAPI operation in /openapi.json
33
27
  ```
34
28
 
35
- A schema makes the handler safe to advertise off-browser: it turns the CLI on
36
- for any method and auto-exposes read-only methods (`GET`/`HEAD`) as MCP tools.
37
- A mutating method (`POST`/`PUT`/`PATCH`/`DELETE`) never auto-exposes to MCP — it
38
- needs an explicit `clients: { mcp: true }`. Explicit `clients` always wins.
29
+ An `inputSchema` (any Standard Schema library zod, valibot, arktype,
30
+ unadapted) is the gate: it unlocks the CLI, and for read-only methods
31
+ (GET/HEAD) the MCP tool. A mutating method (POST/PUT/PATCH/DELETE) never
32
+ auto-exposes to MCP — it requires explicit `clients: { mcp: true }`.
39
33
 
40
34
  ## File-based conventions
41
35
 
42
- The bundler reads meaning from these paths (the project's own `src/`, not the
43
- package):
44
-
45
36
  | Path | Meaning |
46
37
  | --- | --- |
47
- | `src/server/rpc/<name>.ts` | One RPC; export name = file stem, URL = `/rpc/<path>` |
48
- | `src/server/sockets/<name>.ts` | One socket; export name = file stem, name = path |
49
- | `src/mcp/prompts/<name>.md` | An MCP prompt (front-matter + `{{placeholder}}` body) |
50
- | `src/server/config.ts` | Eager-imported config module (the `env()` call site) |
51
- | `src/app.ts` | Optional `AppModule` hooks (`init`/`handle`/`handleError`/`health`) |
52
- | `src/bundle/window.ts` | Default-exported `BundleWindow` (desktop bundle config) |
53
- | `src/ui/pages/**/page.abide` | A route; its URL is the folder path (`[name]` dynamic) |
54
- | `src/ui/pages/**/layout.abide` | A layout; wraps the chain below via its outlet |
55
- | `src/ui/public/` | Static assets, gzip-embedded and served at root |
56
- | `src/.abide/*.d.ts` | Generated type augmentations (routes, rpc, sockets, health) |
57
- | `dist/_app/` | Client build output |
38
+ | `src/server/rpc/<name>.ts` | One RPC per file; the export name must match the file stem; the file path becomes the URL `/rpc/<name>` (subdirectories nest into the path) |
39
+ | `src/server/sockets/<name>.ts` | One broadcast socket per file; export name = file stem = topic name |
40
+ | `src/mcp/prompts/<name>.md` | An MCP prompt template; `{{arg}}` placeholders become the prompt's arguments |
41
+ | `src/mcp/resources/**` | Files served as MCP resources (gzip-embedded into builds) |
42
+ | `src/server/config.ts` | Boot-time `env()` validation; eager-imported so a bad environment fails the boot |
43
+ | `src/app.ts` | Optional `AppModule` hooks: `init`, `handle`, `handleError`, `health`, `forwardHeaders` |
44
+ | `src/bundle/window.ts` | Optional `BundleWindow` default export configuring the desktop bundle's window and menus |
45
+ | `src/ui/pages/**/page.abide` | A routed page; the directory path is the route a `[id]` folder is a path param, `[[id]]` an optional param, `[...rest]` a catch-all |
46
+ | `src/ui/pages/**/layout.abide` | A layout wrapping the pages below it; its `{children()}` is the router outlet |
47
+ | `src/ui/public/` | Static assets served as-is |
48
+ | `src/.abide/*.d.ts` | Generated typing (rpc args for `url()`, page routes, health fields, test rpc/socket clients, public asset paths) |
49
+ | `dist/` | Build output — `dist/_app` client bundle, `dist/cli-thin/<platform>/` CLI tarballs |
50
+
51
+ Project import aliases resolve to the five top-level source dirs: `$server`,
52
+ `$ui`, `$shared`, `$mcp`, `$cli` (e.g. `$server/rpc/getMessages`,
53
+ `$ui/pages/...`). `$server/rpc/*` and `$server/sockets/*` are proxied into
54
+ client bundles; any other `$server/*` import from client code is a
55
+ side-crossing error.
58
56
 
59
57
  ## CLI
60
58
 
61
59
  | Command | Does |
62
60
  | --- | --- |
63
- | `bunx abide scaffold <name>` | Scaffold a project, install, start dev (`--no-install` / `--no-dev` opt out; non-TTY never auto-starts) |
64
- | `abide dev` | Build the client + run the server with hot reload (rebuild/restart on `src/` changes) |
65
- | `abide build` | One client build into `dist/_app/` (no server) |
66
- | `abide start` | Run the production server against a built `dist/` |
67
- | `abide check` | Type-check every `.abide` template + props; non-zero exit on errors |
68
- | `abide run <file> [args]` | Run a script under the abide preload (same runtime as the server) |
69
- | `abide compile [--target] [--out]` | Build a standalone server executable |
70
- | `abide cli [--target] [--out] [--platforms]` | Build the thin CLI binary (ships the server beside it) |
71
- | `abide bundle` | Build a movable, self-contained desktop app bundle (unsigned) |
72
- | `abide lsp` | Run the `.abide` language server over stdio (editor diagnostics) |
73
- | `abide init-agent` | Write/refresh a root `CLAUDE.md` pointer to this surface map |
74
-
75
- For `bun test`, add `preload = ["@abide/abide/preload"]` under `[test]` in
76
- `bunfig.toml`.
61
+ | `bunx abide scaffold <name>` | Scaffolds the bundled template, installs it, and (interactive TTY only) starts the dev server; `--no-install` / `--no-dev` opt out |
62
+ | `abide dev` | Dev orchestrator: builds the client, runs the server as a child, watches `src/`, rebuilds + restarts on change, live-reloads the browser |
63
+ | `abide build` | One-shot client build into `dist/_app` (CI / static deploys) |
64
+ | `abide start` | Runs the production server against an already-built `dist/` |
65
+ | `abide run <file> [args...]` | Runs any script under the abide preload — same runtime as the server (`.abide` compilation, `abide/*` + `$` alias resolution) |
66
+ | `abide compile [--target=…] [--out=…]` | Compiles a standalone server executable (client assets embedded) |
67
+ | `abide cli [--target=…] [--out=…] [--platforms=a,b,c]` | Builds the thin CLI binary (rpc manifest baked in) that talks to a remote server or starts a local one; `--platforms` cross-compiles into `dist/cli-thin/<platform>/` |
68
+ | `abide bundle` | Assembles a movable, self-contained desktop app bundle (server binary + launcher + webview) for the host platform; unsigned |
69
+ | `abide check` | Type-checks every `.abide` component's template + props through its shadow; non-zero exit on errors |
70
+ | `abide lsp` | Runs the `.abide` language server over stdio (JSON-RPC) for editor diagnostics |
71
+ | `abide init-agent` | Writes/refreshes the CLAUDE.md pointer to this surface map for non-scaffolded projects |
72
+
73
+ For tests, add `preload = ["@abide/abide/preload"]` under `[test]` in
74
+ `bunfig.toml` and use `bun test`.
77
75
 
78
76
  ## Authoring contracts
79
77
 
80
- **RPC** (`src/server/rpc/<name>.ts`) `export const <name> = METHOD(handler, opts?)`.
81
- The handler receives `InferOutput<inputSchema>` (or the bare arg type when
82
- schemaless), reads `request()` / `cookies()` for request scope, and returns a
83
- `json` / `jsonl` / `sse` / `error` / `redirect` helper Response or a raw
84
- `Response`. A named, client-branchable error is raised by RETURNING an
85
- `error.typed(name, status, schema?)` constructor the RPC infers its typed-error
86
- surface from whichever constructors the handler returns, so there is no `errors:`
87
- option. `opts`: `inputSchema`, `outputSchema` (OpenAPI 200 + MCP
88
- `outputSchema`), `filesSchema` (multipart File parts; send a `FormData`),
89
- `clients` (`{ browser, mcp, cli }`), `crossOrigin` (exempt a mutating RPC from the
90
- same-origin CSRF gate), `timeout` (ms handler deadline 504 every surface),
91
- `maxBodySize` (received-byte ceiling 413), and mutating helpers only —
92
- `outbox` (durable replay; `GET(fn, { outbox })` is a compile error). Query args
93
- arrive as strings use `z.coerce.*`. Consume: `cache(fn)(args)` in-process,
94
- the swapped browser proxy `fn(args)`, `fn.raw(args)` for the `Response`,
95
- `fn.stream(args)` for a frame `Subscribable`.
96
-
97
- **Socket** (`src/server/sockets/<name>.ts`) `export const <name> = socket(opts)`.
98
- `opts`: `schema` (validates publishes, infers `T`, turns mcp/cli on), `tail`
99
- (retained-frame count for replay), `ttl` (lazy eviction ms), `clientPublish`
100
- (allow client `POST` publishes), `clients`. A `Socket<T>` is an
101
- `AsyncIterable<T>` (bare `for await` = live, no replay); `.tail(count?)` opens a
102
- replay-seeded subscription; `.publish(m)` is isomorphic.
103
-
104
- **Page / layout** (`src/ui/pages/**`) a `page.abide`'s URL is its folder path;
105
- `[id]` segments arrive as `page.params.id`. A `layout.abide` wraps the layer
106
- below at its outlet (`{children()}`). Read `page` for route/params/url, call
107
- `url()` / `navigate()` for base-correct links.
108
-
109
- **App** (`src/app.ts`) optional `AppModule`: `init({ server })` (returns an
110
- optional teardown), `handle(request, next)` (single middleware), `handleError`,
111
- `health(request)` (fields merged into `/__abide/health`), `forwardHeaders`.
112
- **Config** (`src/server/config.ts`) `export const config = env(schema)` at
113
- module top level.
114
-
115
- **Isomorphism** — wrap an RPC in `cache(fn)(args)` so an SSR `await` bakes the
116
- value into the initial HTML and the client hydrates warm off the serialized
117
- snapshot; the same call in the browser dedupes and fetches.
118
-
119
- ## .abide template grammar
120
-
121
- A component file is a leading `<script>` (module imports + reactive setup),
122
- markup, and an optional root `<style>`. Ambient names the compiler injects (no
123
- import): `scope`, `props`, `effect`, `snippet`. `html` is imported
124
- (`abide/ui/html`). A *root* `<style>` is component-scoped; a `<script>` or
125
- `<style>` nested inside a control-flow branch is scoped to that branch (a nested
126
- `<script>` declares branch-local `scope().state/linked/computed`, re-seeded per
127
- mount, and may not carry module imports — those hoist to the leading `<script>`).
128
-
129
- **Reactive state** — reached only through `scope()`; the documented default is
130
- the destructure-once idiom: `const { state, computed, linked, effect } = scope()`
131
- at the top, then bare calls.
132
-
133
- | Form | Meaning |
134
- | --- | --- |
135
- | `let x = state(v, transform?)` | Writable state; assign `x = …` to update |
136
- | `const d = computed(() => …)` | Read-only derived |
137
- | `const l = linked(fn, transform?)` | Writable, re-seeds when `fn`'s deps change |
138
- | `effect(() => { })` | Reaction re-run on dep change; client-only (SSR strips it) |
139
- | `const { a = d, ...rest } = props()` | Ambient prop reader (defaults + rest) |
140
-
141
- Bare `state`/`computed`/`linked`/`effect`/`derived` with no `scope()` destructure
142
- in scope is a compile error. `computed` is always read-only express a writable
143
- computed at the binding (`bind:value={{ get, set }}`).
144
-
145
- **Bindings / directives**
146
-
147
- | Syntax | Meaning |
78
+ **RPC** — the handler receives the schema-validated args
79
+ (`InferOutput<inputSchema>`); typed generics on the helper are a compile error
80
+ — type the parameter, let the body infer. Inside it, `request()` / `cookies()`
81
+ / `server()` read the request scope. Return `json(data)` (or `jsonl` / `sse`
82
+ for streams, `error` / `redirect`, or a raw `Response`). Options:
83
+ `inputSchema`, `outputSchema`, `filesSchema` (validates uploaded `File` parts,
84
+ kept out of the JSON-Schema projection), `clients: { browser, mcp, cli }`,
85
+ `crossOrigin` (exempts a mutating rpc from the same-origin CSRF gate),
86
+ `timeout` (handler deadline in ms 504 on every surface, composed into
87
+ `request().signal`), `maxBodySize` (per-rpc 413 cap), `outbox` (durable
88
+ delivery, mutating methods only). Query args on GET/HEAD/DELETE travel as
89
+ strings coerce in the schema (`z.coerce.number()`). A body rpc also accepts
90
+ a `FormData` in place of typed args (the upload escape hatch): text fields
91
+ validate as args, `File` parts validate against `filesSchema`.
92
+
93
+ **Consuming an rpc** — the bare call `fn(args, opts?)` IS the smart read:
94
+ cached, coalesced, reactive, stale-while-revalidate for replayable (GET/HEAD)
95
+ reads; the second arg takes `{ ttl, tags, throttle, debounce, shared, n }`
96
+ (retention and the refetch clock not transport). `ttl` defaults to `0` on
97
+ the server (coalesce-only the request is the atomic unit, nothing is
98
+ retained past it) and `Infinity` on the client (retain until invalidate/
99
+ refresh). `shared: true` selects the process-level store instead of the
100
+ request-scoped default (server) it does not by itself retain, pair it with
101
+ an explicit `ttl` (e.g. `{ shared: true, ttl: Infinity }`) to memoise across
102
+ requests, for an external endpoint, never per-user data; on the client it is
103
+ a no-op (one tab store). A read with no request in flight (e.g. a background
104
+ job) also resolves against the process-level store. During SSR the same call
105
+ resolves
106
+ in-process and its value is baked into the HTML so hydration starts warm —
107
+ there is no `cache()` wrapper; the bare call carries the caching. Around it:
108
+ `fn.raw(args, init?)` returns the raw `Response` (per-call transport options —
109
+ `signal`, `headers`, `keepalive`, live here); `fn.refresh(args?)`
110
+ refetches keeping the stale value visible; `fn.patch(args?, updater)` mutates
111
+ the retained value locally (absent on streaming rpcs); `fn.peek(args?)` reads
112
+ it synchronously; `fn.pending(args?)` / `fn.refreshing(args?)` are reactive
113
+ probes; `fn.error(args?)` is the rpc's last typed error; `fn.watch(args?,
114
+ handler)` pipes each resolved value to a handler (client-only; SSR-inert);
115
+ `fn.isError(e, kind?)` type-guards a caught error against the rpc's declared
116
+ error kinds; `fn.outbox()` exposes a durable rpc's parked-write queue. A
117
+ handler that returns `jsonl()`/`sse()` makes the bare call return a
118
+ `Subscribable` (`for await` it) — detected at build, nothing to declare;
119
+ awaiting a streaming call is a compile error.
120
+
121
+ **Typed errors** declare a constructor with
122
+ `error.typed(name, status, schema?)` and `return` it from the handler; the
123
+ client's `HttpError` then carries `kind` (the name) and `data` (the schema's
124
+ payload), narrowed via `rpc.isError`. The framework reserves
125
+ `kind: 'validation'` (422, `data: ValidationErrorData`) and `kind: 'queued'`
126
+ (a durable call parked while unreachable; `data` is the parked `OutboxEntry`).
127
+
128
+ **Durable outbox** — an `outbox: true` (mutating) rpc still fetches and throws
129
+ normally, but an unreachable server (transport failure or 502/503/504/52x)
130
+ parks the request for replay as a side-effect and throws `kind: 'queued'`;
131
+ once a backlog exists new calls park straight to the tail so writes stay
132
+ ordered. Nothing auto-drains: replay via `rpc.outbox.retry()` or the global
133
+ `outbox.retry()`; cancel via an entry's `controller.abort()`.
134
+
135
+ **Socket** `socket<T>(opts)` or `socket({ schema, … })` (with a schema, `T`
136
+ infers and publishes validate). Options: `tail` (retained frames, default 1
137
+ `tail: 0` opts out), `ttl` (retained frames expire lazily after N ms),
138
+ `clientPublish` (accept browser/HTTP publishes, off by default), `clients`
139
+ (mcp/cli exposure; a schema flips both on by default). The socket IS the
140
+ `AsyncIterable` iterating is the live stream, no replay. Members:
141
+ `broadcast(msg)` (isomorphic publish server fans out in-process + to remote
142
+ subscribers, client sends a validated `pub` frame), `tail(count?)` (a
143
+ subscription seeded with retained frames), `peek()` (latest retained frame),
144
+ `refresh()` (drop local frames and re-pull the server tail; server-side
145
+ no-op), `watch(handler)` `watch(socket, handler)` (client-only, SSR-inert),
146
+ `pending()` / `refreshing()` / `done()` / `error()` (reactive stream probes),
147
+ plus `name` and `clients`.
148
+
149
+ **Pages and layouts** — `src/ui/pages/blog/[id]/page.abide` serves
150
+ `/blog/<id>`; the param arrives as a prop (`const { id } = props()`) and on the
151
+ reactive `page.params`. A `[[name]]` folder is an optional segment (the route
152
+ matches with or without it; the param is absent when unmatched), `[...rest]`
153
+ a catch-all capturing the remaining path (last segment only). One matcher
154
+ resolves routes on both sides — at the first position where two matching
155
+ patterns differ, literal beats `[name]` beats `[[name]]` beats `[...rest]`. A `layout.abide` wraps every page below its directory
156
+ and renders the page at its `{children()}` outlet. Links are plain `<a href>`
157
+ (the router intercepts in-app hrefs); build paths with `url()` and navigate
158
+ programmatically with `navigate()`.
159
+
160
+ **app.ts / config.ts** — `src/app.ts` optionally exports the `AppModule`
161
+ hooks: `init({ server })` (boot; may return a cleanup run on SIGINT/SIGTERM),
162
+ `handle(request, next)` (single middleware), `handleError(error, request)`,
163
+ `health(request)` (fields merged into the `/__abide/health` payload — public
164
+ and unauthenticated, keep it cheap), and `forwardHeaders` (extra inbound
165
+ header names forwarded onto in-process rpc requests beyond the built-in
166
+ auth/identity set). `src/server/config.ts` holds the `env(schema)` call so a
167
+ bad environment fails at boot.
168
+
169
+ ## `.abide` template grammar
170
+
171
+ A component file is: an optional leading `<script>` (imports + author scope),
172
+ markup, optional `<style>` blocks. The compiler emits a client build and an
173
+ SSR render from the same parse; `abide check` / the LSP type-check the
174
+ template through a generated shadow. HTML comments are dropped; a bare
175
+ `<template>` is an inert element.
176
+
177
+ Reactive state is reached through **imported primitives**, resolved by import
178
+ binding (alias-safe) and lowered by the compiler — inside a component you read
179
+ and write the declared names as plain variables (`{count}`,
180
+ `onclick={() => (count += 1)}`); there is no `.value` in `.abide` authoring
181
+ and no `$state` sigils. In plain `.ts` modules the same imports are runtime
182
+ cells read/written through `.value`.
183
+
184
+ | Primitive | Import | Spec |
185
+ | --- | --- | --- |
186
+ | `state(initial, transform?)` | `@abide/abide/ui/state` | Writable cell. Plain `state(v)` lowers to a serializable doc slot (SSR-resumable); with `transform` the gate runs on every write (`(next, previous) => stored`) |
187
+ | `state.computed(fn)` | member of `state` | Read-only derived value, lazy, never serialized |
188
+ | `state.linked(fn, transform?)` | member of `state` | Writable cell re-seeded whenever the thunk's dependencies change |
189
+ | `state.share(key, value)` / `state.shared(key)` | members of `state` | Put a named value on the ambient scope / read the closest ancestor's |
190
+ | `watch(source, handler)` | `@abide/abide/ui/watch` | The single reaction primitive (client-only, stripped from SSR). Sources: a bare thunk `watch(() => …)` (auto-tracked effect), a state cell, a cell array, a socket/stream (`handler(frame)` per frame with reconnect replay), an rpc (`watch(fn, args?, handler)` — runs the smart read, `handler(value)` on each change). Returns a scope-tied disposer |
191
+ | `html(str)` / `` html`…` `` | `@abide/abide/ui/html` | Brands trusted raw HTML so `{expr}` inserts nodes instead of escaped text; plain `{value}` always escapes |
192
+ | `props()` | ambient (no import) | The prop reader: `const { name = fallback, ...rest } = props()`; a page's props are its route params |
193
+
194
+ Bindings and directives (the attribute kinds `readAttributes` parses):
195
+
196
+ | Form | Spec |
148
197
  | --- | --- |
149
- | `{expr}` | Escaped text interpolation |
150
- | `{html(...)}` | `html`-branded raw markup, plain call or tagged template (unescaped) |
151
- | `name={expr}` / `name="a {b}"` | Attribute value (plain or interpolated) |
152
- | `on<event>={fn}` | Event listener (`onclick`, `onsubmit`, ) |
153
- | `bind:value` / `bind:checked` / `bind:group` | Two-way form binds |
154
- | `bind:value={{ get, set }}` | Derived two-way binding |
155
- | `class:name={cond}` | Toggle a class |
156
- | `style:property={value}` | Set one style property |
157
- | `attach={fn}` | Run `fn(element)` at mount (returns optional teardown) |
158
- | `{...spread}` | Spread object keys props on a component, attributes on an element |
159
-
160
- **Control flow** — mustache blocks, not `<template>`:
161
-
162
- | Block | Form |
198
+ | `{expr}` | Text interpolation, escaped; a snippet or `html`-branded value mounts as nodes |
199
+ | `name={expr}` | Attribute/prop bound to an expression |
200
+ | `name="a {expr} b"` | Interpolated attribute — a literal `{` in a quoted value always interpolates (write `&lbrace;` for a literal brace) |
201
+ | `{...expr}` | Spread props onto a component, attributes onto a native element (rejected on `<template>`) |
202
+ | `on<event>={fn}` | Event listener (`onclick`, `onsubmit`, …); on a component it is a checked callback prop |
203
+ | `bind:value={cell}` | Two-way input/select/textarea binding. `<input type="number"/"range">` writes back a number; `<select>` re-applies against late-mounting options and `<select multiple>` binds an array of selected values |
204
+ | `bind:value={{ get, set }}` | Writable-computed binding: read via `get()`, write via `set(next)` |
205
+ | `bind:checked={cell}` / `bind:group={cell}` | Checkbox boolean / radio-group value (SSR emits boolean attributes bare — `checked`, `open`, `selected` on the matching option) |
206
+ | `class:name={cond}` | Toggles a class; merges with a reactive `class` base in one effect |
207
+ | `style:property={value}` | Sets one style property; merges with a reactive `style` base |
208
+ | `attach={fn}` | Runs `fn(element)` at build time; an optional returned teardown runs on dispose |
209
+
210
+ Control flow is mustache blocks (`{#…}` open, `{:…}` branch, `{/…}` close —
211
+ the close must name its block, and a branch outside its block is a parse
212
+ error):
213
+
214
+ | Block | Spec |
163
215
  | --- | --- |
164
- | If | `{#if c}…{:else if d}…{:else}…{/if}` |
165
- | For | `{#for item, i of list by key}…{/for}` |
166
- | For-await | `{#for await item of source}…{/for}` (over an `AsyncIterable`) |
167
- | Await | `{#await p}{:then v}…{:catch e}…{:finally}…{/await}` |
168
- | Switch | `{#switch s}{:case x}…{:default}…{/switch}` |
169
- | Try | `{#try}…{:catch e}…{:finally}…{/try}` |
170
- | Snippet | `{#snippet name(args)}…{/snippet}`, called `{name(args)}` |
171
-
172
- A capitalised tag is a child component; nested content renders where the child
173
- calls `{children()}` — the single slot, with `{#if children}…{:else}…{/if}` as
174
- the fallback form. No named slots.
216
+ | `{#if cond}…{:else if cond}…{:else}…{/if}` | Conditional chain (the branch keyword is `{:else if}`, with a space) |
217
+ | `{#for item, i of list by key}…{:catch e}…{/for}` | Keyed list; `, i` index and `by` key optional; `{#for await item of asyncIterable}` renders rows as they arrive (its `{:catch}` shows the stream error) |
218
+ | `{#await p}…{:then v}…{:catch e}…{:finally}…{/await}` | Async block. The branch form streams: SSR flushes the shell and streams the fragment out of order. The head form `{#await p then v}` is blocking — rendered inline (depth-first, serial) during the SSR pass |
219
+ | `{#switch subject}{:case match}…{:default}…{/switch}` | Multi-branch on a subject; only branches render — stray content is a compile error |
220
+ | `{#try}{:catch e}…{:finally}…{/try}` | Synchronous error boundary around a build/reactive throw |
221
+ | `{#snippet name(args)}…{/snippet}` | Declares a reusable builder, called as an interpolation: `{name(args)}`; a snippet value passes through props like any other value |
175
222
 
176
- > Removed forms throw a migration error: the `<slot>` element (use
177
- > `{children()}`), `<template name>` snippets (use `{#snippet}`), and all
178
- > `<template if>` / `<template each>` / `<template await>` / … control flow (use
179
- > the `{#…}` blocks). A bare `<template>` is now an inert element.
223
+ Components are capitalised tags (`<Panel prop={x}>…</Panel>`); nested content
224
+ renders where the component calls `{children()}` the single fill point
225
+ (`{#if children}{children()}{:else}…{/if}` for a fallback; there are no named
226
+ slots and no `<slot>` element). A layout's `{children()}` is the route outlet.
180
227
 
181
- ## Server surface abide/server/*
228
+ `<script>` and `<style>` are **not component-root-only**: either may sit
229
+ inside a control-flow branch, scoped to that branch. A nested `<script>`
230
+ declares branch-local `state` / `state.computed` / `state.linked` the same
231
+ imported way (re-seeded per mount; static `import` statements are illegal
232
+ there — imports live in the leading `<script>`). A **root** `<style>` is
233
+ component-scoped; a nested `<style>` scopes to its sibling subtree only.
182
234
 
183
- ### RPC @documentation rpc
235
+ Removed forms throw migration errors at parse time: the `<slot>` element (use
236
+ `{children()}`), `<template name>` snippets (use `{#snippet}`), and all
237
+ `<template if/each/await/switch/…>` control flow (use `{#…}` blocks).
184
238
 
185
- - `abide/server/GET`read RPC helper; `GET(fn, opts?)`, bound by the bundler from `src/server/rpc/`.
186
- - `abide/server/POST` — create/mutate RPC helper; accepts `outbox` for durable replay.
187
- - `abide/server/PUT` — replace RPC helper (mutating).
188
- - `abide/server/PATCH` — partial-update RPC helper (mutating).
189
- - `abide/server/DELETE` — delete RPC helper (mutating).
190
- - `abide/server/HEAD` — read RPC helper, no body (auto-exposes to MCP like GET).
239
+ ## Server surface — `abide/server/*`
191
240
 
192
- ### Sockets@documentation sockets
241
+ ### RPC`@documentation rpc`
193
242
 
194
- - `abide/server/socket` — `socket(opts)` declares a broadcast topic; one export per file under `src/server/sockets/`.
243
+ - `@abide/abide/server/GET` — GET rpc helper: `export const x = GET(handler, opts?)` inside `src/server/rpc/`; the bundler rewrites it to the server dispatcher or the browser proxy — calling it outside an rpc module throws.
244
+ - `@abide/abide/server/POST` — POST rpc helper (mutating: accepts `outbox`, JSON/FormData body).
245
+ - `@abide/abide/server/PUT` — PUT rpc helper (mutating).
246
+ - `@abide/abide/server/PATCH` — PATCH rpc helper (mutating).
247
+ - `@abide/abide/server/DELETE` — DELETE rpc helper (mutating; args travel in the query string).
248
+ - `@abide/abide/server/HEAD` — HEAD rpc helper (read-only).
195
249
 
196
- ### Response@documentation response
250
+ ### Responses`@documentation response`
197
251
 
198
- - `abide/server/json` — JSON `Response` with `Cache-Control: no-store` default; same shape as `Response.json`.
199
- - `abide/server/jsonl` — wraps an `AsyncIterable<Frame>` as a JSON Lines (`application/jsonl`) streaming Response.
200
- - `abide/server/sse` — wraps an `AsyncIterable<Frame>` as Server-Sent Events (`text/event-stream`).
201
- - `abide/server/error` — error Responses. `error(status, message?)` = plain-text (message defaults to the status reason phrase). `error.typed(name, status, schema?)` = a reusable named typed-error constructor; returning it from a handler IS the error (serializes `{ $abideError, data }` at `status`), and the RPC infers `rpc.isError(e, name)` narrowing (`.kind` + typed `.data`) from the returned constructors — no `errors:` option.
202
- - `abide/server/redirect` — redirect Response; accepts relative URLs, defaults to 302.
252
+ - `@abide/abide/server/json` — `json(data, init?)`: JSON response with `Cache-Control: no-store` default; `json(undefined)` emits 204 and round-trips back to `undefined`; carries the value type so the rpc's `Return` infers.
253
+ - `@abide/abide/server/jsonl` — `jsonl(asyncIterable, init?)`: JSON Lines streaming response; consumer cancel flows into the generator's `return`; a generator throw becomes a final `{"$error": message}` line.
254
+ - `@abide/abide/server/sse` — `sse(asyncIterable, init?)`: Server-Sent Events response with a 15s keepalive comment; errors emit an `event: error` frame carrying only the message.
255
+ - `@abide/abide/server/error` — `error(status, message?, init?)`: plain-text error response (message defaults to the reason phrase); the caller's await throws `HttpError`. Member `error.typed(name, status, schema?)` declares a reusable typed-error constructor the handler returns (see Authoring contracts).
256
+ - `@abide/abide/server/redirect` — `redirect(url, status = 302, init?)`: redirect response accepting relative URLs; 301/302/303/307/308.
203
257
 
204
- ### Request scope — @documentation request-scope
258
+ ### Request scope — `@documentation request-scope`
205
259
 
206
- - `abide/server/request` — the inbound `Request` for the current SSR/RPC pass (AsyncLocalStorage; throws outside a scope).
207
- - `abide/server/cookies` — the request's cookie jar (Bun `CookieMap`); writes flush as `Set-Cookie` on return.
208
- - `abide/server/server` — the active `Bun.serve` instance (a no-op in-process server under CLI/MCP/test dispatch).
260
+ - `@abide/abide/server/request` — `request()`: the inbound `Request` for the in-flight SSR/rpc pass (AsyncLocalStorage); throws outside a request scope.
261
+ - `@abide/abide/server/cookies` — `cookies()`: the request's cookie jar (Bun `CookieMap`) — reads parse the inbound header; `set`/`delete` flush as `Set-Cookie` when the handler returns.
262
+ - `@abide/abide/server/server` — `server()`: the active `Bun.serve` instance; a no-op stand-in during in-process dispatch (CLI/MCP/tests); throws before boot.
209
263
 
210
- ### Configuration — @documentation configuration
264
+ ### Configuration — `@documentation configuration`
211
265
 
212
- - `abide/server/env` — validate `Bun.env` against a Standard Schema at module top level; returns typed config or fails boot loudly.
266
+ - `@abide/abide/server/env` — `env(schema)`: validates `Bun.env` against a Standard Schema at module top level (synchronous; every issue reported at once) and returns the typed config; the schema also projects the bundle's first-run setup form.
213
267
 
214
- ### Observability@documentation observability
268
+ ### Sockets`@documentation sockets`
215
269
 
216
- - `abide/server/reachable` — `await reachable(host)` outbound reachability probe with warm TTL polling (server-only).
270
+ - `@abide/abide/server/socket` — `socket<T>(opts?)` / `socket({ schema, tail, ttl, clientPublish, clients })`: declares the broadcast topic inside `src/server/sockets/<name>.ts`; see Authoring contracts for the full `Socket<T>` member surface.
217
271
 
218
- ### Agent — @documentation agent
272
+ ### Agent — `@documentation agent`
219
273
 
220
- - `abide/server/agent` — `agent(engine, messages)` runs a model engine against the app's own MCP surface, returning the engine's `AgentFrame` stream (wrap in `jsonl()`/`sse()`); also exports `NeutralMessage`, `AgentFrame`, `AgentSurface`, `AgentEngine`.
274
+ - `@abide/abide/server/agent` — `agent(engine, messages)`: runs a provider engine (an `@abide/<provider>` package) against the app's own MCP surface inside an rpc's request scope and returns its `AgentFrame` stream; the handler picks the transport (`jsonl(agent(…))` / `sse(agent(…))`). The module also exports the neutral contract types: `NeutralMessage` (user/assistant/tool turns), `AgentFrame` (`text` deltas, `tool_use`, `tool_result`, `done` with a stop reason), `AgentSurface` (the gated tool/prompt/resource surface), and `AgentEngine` (surface + messages + origin in, frames out).
221
275
 
222
- ### Server plumbing — @documentation plumbing
276
+ ### Server plumbing — `@documentation plumbing`
223
277
 
224
- - `abide/server/AppModule` — type of the optional `src/app.ts` hooks (`init`/`handle`/`handleError`/`health`/`forwardHeaders`).
225
- - `abide/server/InspectorContext` — type the capabilities core injects into `@abide/inspector` when enabled.
226
- - `abide/server/rpc/defineRpc` — the runtime the RPC helpers compile to (method + URL + handler `RemoteFunction`); not called directly.
227
- - `abide/server/sockets/defineSocket` — the server-side `socket()` implementation the bundler binds the file name into.
228
- - `abide/server/prompts/definePrompt` — the runtime each `src/mcp/prompts/<file>.md` compiles to (registers an MCP prompt).
229
- - `abide/server/prompts/renderPromptTemplate` — substitutes `{{name}}` placeholders in a markdown prompt body (missing empty).
278
+ - `@abide/abide/server/AppModule` — the type of `src/app.ts`'s optional hooks (`init`, `handle`, `handleError`, `health`, `forwardHeaders`).
279
+ - `@abide/abide/server/InspectorContext` — the capability object core injects into `@abide/inspector` (`loadSurface`, `cacheSnapshot`, `inFlightSnapshot`, `onRecord`, app identity); keeps the inspector a pure consumer.
280
+ - `@abide/abide/server/rpc/defineRpc` — `defineRpc(method, url, handler, opts?)`: the server-side construction the bundler rewrites rpc helper calls into validation, timeout composition, client-flag resolution, registry entry.
281
+ - `@abide/abide/server/sockets/defineSocket` — `defineSocket(name, opts?)`: server-side socket construction (retained-tail buffer with lazy TTL eviction, per-subscriber queues, `server.publish` fan-out).
282
+ - `@abide/abide/server/prompts/definePrompt` — `definePrompt(name, opts)`: registers an MCP prompt; the resolver plugin generates one call per `src/mcp/prompts/<name>.md`.
283
+ - `@abide/abide/server/prompts/renderPromptTemplate` — `renderPromptTemplate(template, args)`: substitutes `{{name}}` placeholders in a prompt body (missing args collapse to empty).
230
284
 
231
- ## Isomorphic surface — abide/shared/*
285
+ ## Isomorphic surface — `abide/shared/*`
232
286
 
233
- ### Cache — @documentation cache
287
+ ### Cache mutators `@documentation cache`
234
288
 
235
- - `abide/shared/cache` — `cache(fn, options?)(args)` request/tab-scoped read with coalescing, `ttl`, `swr`, `tags`, `global`; SSR snapshot for warm hydration. Carries `cache.invalidate(selector?, args?)` and `cache.on(source, handler)` (event-driven invalidation).
289
+ - `@abide/abide/shared/refresh` — `refresh(selector?, args?)`: refetch every cached read matching the selector, keeping the stale value visible until the fresh one swaps in. Selector grammar: `(fn, args)` exact call, `(fn)` every args-variant, `({ tags })` a tagged group, `()` everything. `fn.refresh(args?)` is the pre-bound sugar.
290
+ - `@abide/abide/shared/patch` — `patch(fn, args?, updater)` / `patch({ tags }, updater)`: mutate the retained value(s) in place — reactive, no network; the optimistic-update / socket-frame primitive. `fn.patch(…)` is the sugar.
236
291
 
237
- ### Response@documentation response
292
+ ### Probes`@documentation probes`
238
293
 
239
- - `abide/shared/HttpError` thrown on a non-2xx remote call; carries the raw `Response` plus `kind`/`data`.
240
- - `abide/shared/ValidationErrorData` — the `data` payload (`kind: 'validation'`, status 422) of a validation failure: raw `issues` + form-friendly `fields`.
294
+ Probes report, never act reading one opens no fetch and no stream.
241
295
 
242
- ### RPC@documentation rpc
296
+ - `@abide/abide/shared/pending``pending(selector?, args?)`: reactive "no value yet" probe over calls and streams (global, per-rpc, per-call, tagged, per-subscribable; a durable rpc's parked writes count too).
297
+ - `@abide/abide/shared/refreshing` — `refreshing(selector?, args?)`: "holding a value while a fresher one is in flight" — the SWR reload / stream-reconnect badge.
298
+ - `@abide/abide/shared/peek` — `peek(fn, args?)` / `peek(socket)`: the retained value (or latest frame), synchronously, `T | undefined`; reactive inside a tracking scope.
299
+ - `@abide/abide/shared/done` — `done(subscribable)`: true once a stream closed (stream-only; a cache read's "done" is `!pending && !refreshing`).
300
+ - `@abide/abide/shared/online` — `online()`: reactive connectivity probe — browser `online`/`offline` events; server-side it reflects the *calling client's* reported connectivity (always true during SSR and outside a scope).
243
301
 
244
- - `abide/shared/withJsonSchema`attach a `toJSONSchema()` projection to a Standard Schema whose library lacks one (feeds OpenAPI/MCP/CLI).
302
+ ### Errors`@documentation response`
245
303
 
246
- ### Templating@documentation templating
304
+ - `@abide/abide/shared/HttpError`thrown by rpc calls on non-2xx; carries `status`, `statusText`, the raw `response`, and — for typed/validation/queued errors — `kind` + `data`.
305
+ - `@abide/abide/shared/ValidationErrorData` — the `data` shape of a `kind: 'validation'` failure: the raw Standard Schema `issues` plus a `fields` (field → first message) map.
247
306
 
248
- - `abide/shared/snippet`brands a payload so a `{expr}` interpolation mounts it (client builder / SSR string); the runtime behind `{#snippet}`. Also `Snippet<Payload>`, `snippetPayload`.
307
+ ### Schema projection `@documentation rpc`
249
308
 
250
- ### Probes@documentation probes
309
+ - `@abide/abide/shared/withJsonSchema``withJsonSchema(schema, toJsonSchema)`: attaches the `toJSONSchema()` projection to a Standard Schema whose library lacks one, feeding OpenAPI, MCP, CLI help, and the bundle setup form.
251
310
 
252
- - `abide/shared/pending`reactive in-flight probe over cache + tail registries; `pending()`, `pending(fn)`, `pending(fn, args)`, `pending({ tags })`.
253
- - `abide/shared/refreshing` — reactive revalidation probe (holding a value while a fresher one loads); same selector grammar as `pending`.
254
- - `abide/shared/online` — reactive network-connectivity probe (`navigator.onLine`, offline-reliable).
311
+ ### Observability`@documentation observability`
255
312
 
256
- ### Page@documentation page
313
+ - `@abide/abide/shared/health``health()`: reactive backend health — `{ reachable, abide, name, version, …app health-hook fields }`, polled from `/__abide/health` only while a tracking scope reads it; SSR-seeded so hydration starts warm; constant `{ reachable: true }` on the server. The `AppHealth`/`AppHealthMap` types augment from the generated `health.d.ts`.
314
+ - `@abide/abide/shared/reachable` — `await reachable(host?)`: outbound reachability, same callable both sides. The first call probes (HEAD) and starts a TTL background poll; later calls answer instantly off the warm value. Any completed HTTP response counts as reachable. No host asks about the app's own backend: constant true on the server and on a loopback origin (dev, desktop bundle — works offline); a deployed origin probes like any host. The browser probes no-cors and composes `navigator.onLine` in at read time (loopback exempt). Tuned by `ABIDE_REACHABLE_TTL` / `ABIDE_REACHABLE_TIMEOUT` (server env; the browser runs the defaults).
315
+ - `@abide/abide/shared/log` — the unified logger: `log(...)` / `.warn` / `.error` / `.trace` on the app's always-on channel, every record carrying request-scope context (short trace id, +elapsed, method+path); member `log.channel(name)` returns the same shape on a DEBUG-gated diagnostic channel. Renders tsv (default) or JSON per `ABIDE_LOG_FORMAT`.
316
+ - `@abide/abide/shared/trace` — `trace()`: the current request's W3C `traceparent` (client-side: the trace of the request that rendered the page), or undefined outside any scope.
257
317
 
258
- - `abide/shared/page`reactive page proxy: `route`, `params`, `url` (browser-space), `navigating`; isomorphic.
318
+ ### Page`@documentation page`
259
319
 
260
- ### URL@documentation url
320
+ - `@abide/abide/shared/page`the reactive page proxy: `page.route`, `page.params`, `page.url` (browser-space on both sides, mount base included), `page.navigating`; isomorphic, re-runs readers across navigations.
261
321
 
262
- - `abide/shared/url``url(path, args?)` resolves a base-correct, typed in-app URL (RPC query / page params / asset); also `RpcRoutes`, `PageRoutes`, `PublicAssets`, `PathParams` (augmentable typing seams).
322
+ ### URL`@documentation url`
263
323
 
264
- ### Observability@documentation observability
324
+ - `@abide/abide/shared/url``url(path, params?/args?)`: resolves any in-app URL to its base-correct form — a page route literal interpolates its `[name]` / `[[name]]` / `[...rest]` params (typed via `PathParams`; an absent optional drops its segment), a GET rpc path serializes typed args to the query, anything else is base-prefixed. Also exports the augmentable `RpcRoutes` / `PageRoutes` / `PublicAssets` maps and the `PathParams<P>` type.
265
325
 
266
- - `abide/shared/health`typed `health()` probe of `/__abide/health` (fields augmented from the app's `health()` hook); also `AppHealthMap`, `AppHealth`, `HealthState`.
267
- - `abide/shared/log` — unified logger carrying request-scope context; `log`/`.warn`/`.error`/`.trace` on the app channel, `log.channel(name)` for DEBUG-gated channels.
268
- - `abide/shared/trace` — the current request's W3C `traceparent`, or undefined outside a request scope; isomorphic.
326
+ ### Templating`@documentation templating`
269
327
 
270
- ### Isomorphic plumbing @documentation plumbing
328
+ - `@abide/abide/shared/snippet``snippet(payload)`: brands a snippet payload so a `{expr}` interpolation mounts it (client: a DOM builder; server: the rendered string); the compiler wraps `{#snippet}` bodies in this. Also exports the `Snippet<P>` type and `snippetPayload(value)` (the payload, or undefined for plain values).
271
329
 
272
- - `abide/shared/createSubscriber`abide-native open-on-first-read / close-on-last-reader subscriber over the signal core (cache/tail substrate).
330
+ ### Shared plumbing `@documentation plumbing`
273
331
 
274
- ## UI surface abide/ui/* (client-only)
332
+ - `@abide/abide/shared/createSubscriber``createSubscriber(start)`: open-on-first-tracked-read / close-on-last-reader resource lifecycle grounded in the signal core; the substrate under `health()`, `online()`, and the tail probes.
275
333
 
276
- ### Reactive state@documentation reactive-state
334
+ ## UI surface`abide/ui/*` (client-only)
277
335
 
278
- - `abide/ui/scope``scope()` resolves the current lexical scope; the sole reactive entry, carrying `state`/`computed`/`linked`/`effect` + data/context/capability methods (walk to the tree root via the handle's `.root()`).
336
+ ### Reactive state `@documentation reactive-state`
279
337
 
280
- ### Templating@documentation templating
338
+ - `@abide/abide/ui/state`the `state` primitive: `state(initial, transform?)` writable cell, `state.computed(fn)` read-only derived, `state.linked(fn, transform?)` writable-reseeded, `state.share(key, value)` / `state.shared(key)` ambient context. In `.abide` files the compiler lowers reads/writes to plain variable syntax; in `.ts` the cell is read/written through `.value`.
339
+ - `@abide/abide/ui/watch` — `watch(source, handler)`: the single reaction primitive over a thunk, cell, cell array, socket/stream, or rpc (see the grammar table). Client-only; the compiler strips author calls from SSR, and the `socket.watch` / `fn.watch` instance sugar is SSR-inert.
281
340
 
282
- - `abide/ui/html``html(string)` / `` html`…` `` marks trusted raw HTML so a `{expr}` inserts nodes instead of escaped text.
341
+ ### Templating`@documentation templating`
283
342
 
284
- ### Tail@documentation tail
343
+ - `@abide/abide/ui/html``html(string)` / `` html`…` ``: brands trusted raw HTML for unescaped interpolation; the tag does not escape its interpolations — only feed it values you trust.
285
344
 
286
- - `abide/ui/tail`reactive streaming consumer of a `Subscribable<T>` (socket / `fn.stream`); bare = latest frame, `{ last: n }` = window; `tail.error`, `tail.status`. No-op on the server.
345
+ ### Navigate`@documentation navigate`
287
346
 
288
- ### Navigate@documentation navigate
347
+ - `@abide/abide/ui/navigate``navigate(path, params?, options?)`: typed programmatic navigation off the route map; params interpolate through `url()` (base-correct). Options `{ replace, keepScroll }`. The module also exports `navigatePath(path, options?)` (already-resolved paths — the router's own entry, no re-basing) and the `NavigateOptions` type.
289
348
 
290
- - `abide/ui/navigate``navigate(path, params?, options?)` typed in-app navigation through `url()`; `replace`/`keepScroll`. Also `navigatePath` (already-resolved path) and `NavigateOptions`.
349
+ ### Outbox`@documentation ui`
291
350
 
292
- ### UI@documentation ui
351
+ - `@abide/abide/ui/outbox`the global reactive outbox: `outbox()` lists every durable rpc's undelivered entries (each tagged with its `rpc`), member `outbox.retry()` drains every queue. Empty list server-side. Types `GlobalOutbox`, `GlobalOutboxEntry`.
293
352
 
294
- - `abide/ui/outbox`global reactive view of every durable RPC's parked writes; callable for the list, `outbox.retry()` to drain. Also `GlobalOutbox`, `GlobalOutboxEntry`.
353
+ ### UI plumbing `@documentation plumbing`
295
354
 
296
- ### UI plumbing @documentation plumbing
355
+ Compiler/runtime machinerypublished so generated code, the type shadow, and
356
+ tests can import it, not for app code.
297
357
 
298
- - `abide/ui/effect` — the from-scratch effect primitive `scope().effect` lowers to (and the SSR strip target).
299
- - `abide/ui/enterScope` — opens an isolated lexical scope for an SSR render; returns the previous one.
300
- - `abide/ui/exitScope` — restores the scope `enterScope` saved (closes an SSR render's scope).
301
- - `abide/ui/remoteProxy` — client-side substitute for a server RPC (typed fetch over HTTP); also `DurableOptions`.
302
- - `abide/ui/socketProxy` — client-side substitute for a server `Socket` (subscribe over the multiplexed ws).
303
- - `abide/ui/router` — the client router: outlet boundaries, chain mounting, history-driven navigation.
304
- - `abide/ui/startClient` — boot entry that consumes the server's `__SSR__` payload and hydrates the page.
305
- - `abide/ui/renderToStream` — out-of-order SSR streaming: shell first, then one resolved fragment per streaming await block.
306
- - `abide/ui/dom/mount` — mount a top-level page/layout into a host under an ownership scope; returns a disposer.
307
- - `abide/ui/dom/mountChild` — mount a child component as a marker-bounded range (no wrapper element).
308
- - `abide/ui/dom/mergeProps` — compose a child's props from ordered explicit/spread/`$children` layers (last wins).
309
- - `abide/ui/dom/spreadProps` — wrap a `{...source}` prop-spread layer as live value thunks.
310
- - `abide/ui/dom/restProps` — the unconsumed props of a `const { a, ...rest } = props()` destructure.
311
- - `abide/ui/dom/spreadAttrs` — spread an object's keys onto a native element (`<div {...rest}>`).
312
- - `abide/ui/dom/readCall` — guarded method call on a reactive-doc read (legible throw naming the authored path).
313
- - `abide/ui/dom/hydrate` — adopt server-rendered DOM in place instead of rebuilding; returns a disposer.
314
- - `abide/ui/dom/text` — a text node tracking a `read()` (plain-text fast path).
315
- - `abide/ui/dom/appendText` — a reactive `{expr}` interpolation (escaped text / `{snippet}` / `html` raw).
316
- - `abide/ui/dom/appendTextAt` — `appendText` mounted at a skeleton anchor comment (text interleaved with elements).
317
- - `abide/ui/dom/appendSnippet` — mount a `{snippet(args)}` builder's nodes in a marker range, reactive in its args.
318
- - `abide/ui/dom/appendStatic` — a static (non-reactive) text node, claimed on hydrate.
319
- - `abide/ui/dom/cloneStatic` — append a fully-static subtree, byte-identical to the SSR markup.
320
- - `abide/ui/dom/skeleton` — clone a bound element's static skeleton with located holes/anchors.
321
- - `abide/ui/dom/anchorCursor` — position a skeleton-anchored control-flow block or slot.
322
- - `abide/ui/dom/mountSlot` — mount a component's `{children()}` content (or fallback) as a marker range.
323
- - `abide/ui/dom/outlet` — a layout's outlet boundary the router fills with the next chain layer.
324
- - `abide/ui/dom/attr` — bind one element attribute to a `read()` (present/absent boolean semantics).
325
- - `abide/ui/dom/on` — attach an event listener, owned by the current scope (the `onclick={…}` target).
326
- - `abide/ui/dom/attach` — run an `attach={fn}` against an element at build, owning its teardown.
327
- - `abide/ui/dom/each` — keyed list runtime (`{#for by key}`); each row a marker-bounded range.
328
- - `abide/ui/dom/eachAsync` — async keyed list runtime (`{#for await }`) over an `AsyncIterable`.
329
- - `abide/ui/dom/when` — conditional runtime (`{#if}` + optional `{:else}`).
330
- - `abide/ui/dom/awaitBlock` — async runtime (`{#await}` pending / then / catch ranges).
331
- - `abide/ui/dom/tryBlock` — synchronous error-boundary runtime (`{#try}`).
332
- - `abide/ui/dom/switchBlock` — multi-branch runtime (`{#switch}`, strict `===`, default fallback).
333
- - `abide/ui/dom/applyResolved` — bundle-side consumer of an SSR stream chunk (streaming nav / socket SSR).
334
- - `abide/ui/runtime/escapeKey` — escape one object key into a JSON-Pointer reference token (RFC 6901).
335
- - `abide/ui/runtime/nextBlockId` — the next block id in the current render pass (await/try, document order).
336
- - `abide/ui/runtime/enterRenderPass` — mark entry into a render/mount (resets the block-id counter at depth 0).
337
- - `abide/ui/runtime/exitRenderPass` — mark exit from a render/mount, unwinding the depth.
358
+ - `@abide/abide/ui/effect` — `effect(fn)`: the raw auto-tracked effect the compiler emits for bindings; authors use `watch`. Returns a disposer; SSR strips author calls.
359
+ - `@abide/abide/ui/currentScope` — `scope()`: the ambient lexical scope the internal lowering host for `state`/`effect` (`derive`/`linked`/`effect`/`share` land here).
360
+ - `@abide/abide/ui/enterRenderScope` — `enterScope()`: opens an isolated scope for an SSR render; returns the previous scope to restore.
361
+ - `@abide/abide/ui/exitRenderScope` — `exitScope(previous)`: restores the scope `enterScope` saved.
362
+ - `@abide/abide/ui/router` — `router(...)`: the client router — fills layout/page chains into comment-marker outlet boundaries, intercepts in-app links, buckets/restores scroll per history entry.
363
+ - `@abide/abide/ui/startClient` — `startClient(...)`: the client entry reads every `__SSR__` field into its shared slot (cache seed, health seed, client timeout, resume manifest), hydrates the chain, starts the router.
364
+ - `@abide/abide/ui/renderToStream` — `renderToStream(render)`: out-of-order SSR streaming shell first, then one `<abide-resolve>` fragment per streaming await block in completion order; blocking (`then`-head) awaits render inline.
365
+ - `@abide/abide/ui/remoteProxy` — `remoteProxy(method, url, opts?)`: the browser-side rpc stub the bundler emits (fetch, decode, HttpError, outbox parking, streaming); the `DurableOptions` type rides along.
366
+ - `@abide/abide/ui/socketProxy` — `socketProxy(name)`: the browser-side socket stub the identical `Socket<T>` shape over the page's lazily-opened multiplexed ws channel.
367
+ - `@abide/abide/ui/runtime/escapeKey` — JSON-Pointer-escapes one reactive-doc path key (`~`→`~0`, `/`→`~1`).
368
+ - `@abide/abide/ui/runtime/nextBlockId` — the next await/try block id in the current render pass (document order, shared across inlined children).
369
+ - `@abide/abide/ui/runtime/enterRenderPass` — marks entry into a render/mount; the outermost resets the block-id counter.
370
+ - `@abide/abide/ui/runtime/exitRenderPass` — unwinds `enterRenderPass`'s depth.
371
+ - `@abide/abide/ui/dom/mount` — mounts a top-level page/layout into a host under an ownership scope; returns the unmount.
372
+ - `@abide/abide/ui/dom/mountChild` — mounts a nested child component as a comment-marker range (dev builds also register it with the hot bridge).
373
+ - `@abide/abide/ui/dom/mountSlot` — mounts a component's passed-children content as a marker-bounded range.
374
+ - `@abide/abide/ui/dom/outlet` — a layout's outlet: an empty `<!--abide:outlet-->…<!--/abide:outlet-->` boundary the router fills.
375
+ - `@abide/abide/ui/dom/hydrate` — adopts server-rendered DOM instead of rebuilding: runs the build with a claim cursor over the existing nodes.
376
+ - `@abide/abide/ui/dom/skeleton` — the parsed-once static-structure clone path every bound element builds through; element holes by path, blocks by anchor comments.
377
+ - `@abide/abide/ui/dom/anchorCursor` — positions a skeleton-anchored block/slot at its `<!--a-->` anchor, in clone and hydrate modes alike.
378
+ - `@abide/abide/ui/dom/cloneStatic` — appends a fully-static subtree (no bindings, control flow, or listeners) by cloning.
379
+ - `@abide/abide/ui/dom/appendStatic` — a static text node: created (create mode) or claimed from server-rendered text (hydrate mode).
380
+ - `@abide/abide/ui/dom/appendText` — a reactive `{expr}` text node under a parent.
381
+ - `@abide/abide/ui/dom/appendTextAt` — a reactive text node mounted at a skeleton anchor (text interleaved with element siblings).
382
+ - `@abide/abide/ui/dom/appendSnippet` — mounts a `{snippet(args)}` interpolation's builder into a marker-bounded range.
383
+ - `@abide/abide/ui/dom/text` — a text node whose content tracks a reactive read.
384
+ - `@abide/abide/ui/dom/attr` — binds an element attribute to a read (boolean true → bare attribute, false/nullish removed).
385
+ - `@abide/abide/ui/dom/on` — attaches an event listener whose removal is registered with the ownership scope.
386
+ - `@abide/abide/ui/dom/attach` — runs an `attach={fn}` attachment and registers its optional teardown.
387
+ - `@abide/abide/ui/dom/bindSelectValue` — two-way `<select>` binding that re-applies the selection when the option set changes (late-mounting `{#for}`/async options; `multiple` binds an array).
388
+ - `@abide/abide/ui/dom/each` — keyed `{#for}` runtime: marker-bounded rows reconciled by key.
389
+ - `@abide/abide/ui/dom/eachAsync` — `{#for await}` runtime: rows append/reconcile as the AsyncIterable yields.
390
+ - `@abide/abide/ui/dom/when` — `{#if}` runtime (single-branch swap in a marker-bounded range).
391
+ - `@abide/abide/ui/dom/switchBlock` — `{#switch}` runtime (also `{#if}` chains with `{:else if}` branches).
392
+ - `@abide/abide/ui/dom/awaitBlock` — `{#await}` runtime: pending resolved/error branch swap, teardown-generation guarded.
393
+ - `@abide/abide/ui/dom/tryBlock` — `{#try}` runtime: synchronous error boundary around a subtree build.
394
+ - `@abide/abide/ui/dom/applyResolved` — consumes a streamed SSR `<abide-resolve>` chunk, swapping it into its await boundary (the bundle-side counterpart of the doc stream's inline scripts).
395
+ - `@abide/abide/ui/dom/mergeProps` — composes a child's props from explicit thunk runs, spread layers, and the trailing children layer.
396
+ - `@abide/abide/ui/dom/spreadProps` — wraps a `{...source}` spread layer so every key resolves to a live value thunk.
397
+ - `@abide/abide/ui/dom/restProps` — the live unconsumed-props object behind `const { …, ...rest } = props()`.
398
+ - `@abide/abide/ui/dom/spreadAttrs` — spreads an object's keys onto a native element (`<div {...rest}>`), keys enumerated once.
399
+ - `@abide/abide/ui/dom/readCall` — guarded method call on a reactive-doc read (the `model.draft.trim()` lowering).
338
400
 
339
401
  ## Build / tooling
340
402
 
341
- ### Building — @documentation building
403
+ ### Building — `@documentation building`
342
404
 
343
- - `abide/build` — build the client bundle into `dist/_app` (the `.abide` loader, virtual resolver, optional Tailwind, optional gzip).
344
- - `abide/compile` — produce a standalone Bun server executable (runs `build` first, embeds compressed assets).
405
+ - `@abide/abide/build` — `build({ cwd, … })`: builds the client bundle into `dist/_app` (`.abide` loader, virtual-module resolver, optional Tailwind); production builds also emit `.gz` siblings; staged and atomically swapped so a live dev server never sees a half-built dist.
406
+ - `@abide/abide/compile` — `compile({ cwd, target?, outfile? })`: produces a standalone server executable (runs the client build first and embeds the compressed assets); returns the binary path.
345
407
 
346
- ### Build plumbing — @documentation plumbing
408
+ ### Tooling plumbing — `@documentation plumbing`
347
409
 
348
- - `abide/preload` — the Bun preload that installs the `.abide` loader + resolver for the server/scripts/tests.
349
- - `abide/resolver-plugin` — resolves `$`-aliased / extensionless / directory imports Node-style; also the build's virtual-module loaders.
350
- - `abide/ui-plugin` — the Bun plugin that compiles `.abide` single-file components into ES modules.
351
- - `abide/tsconfig` — the shippable base `tsconfig.app.json` projects extend.
410
+ - `@abide/abide/preload` — the Bun preload installing the `.abide` loader, the virtual-module resolver, and a `.css` no-op loader — the same runtime for the server, scripts (`abide run`), and `bun test`.
411
+ - `@abide/abide/resolver-plugin` — the resolver plugin itself: `$`-alias + virtual-module (`abide:*`) resolution, rpc/socket module rewriting, side-crossing guards.
412
+ - `@abide/abide/ui-plugin` — the Bun plugin that compiles `.abide` single-file components to ES modules (layouts flagged by filename; scoped styles bundled into the entry stylesheet).
413
+ - `@abide/abide/tsconfig` — the base tsconfig apps extend (`bundler` resolution, strict, `types: ["bun"]`, erasable syntax only).
352
414
 
353
- ## Desktop bundle
415
+ ## Desktop bundle — `@documentation bundle`
354
416
 
355
- ### Bundle@documentation bundle
417
+ - `@abide/abide/bundle/BundleWindow`the type of `src/bundle/window.ts`'s default export: window title/size plus custom `menu` entries inserted between the standard Edit and Window menus.
418
+ - `@abide/abide/bundle/BundleMenu` — one top-level custom menu (`label` + `items`).
419
+ - `@abide/abide/bundle/BundleMenuItem` — one menu entry: a divider, an `emit` item dispatching an `abide:menu` CustomEvent into the page (optional Cmd `shortcut`), or a `navigate` item repointing the window itself.
420
+ - `@abide/abide/bundle/onMenu` — `onMenu(handler)` / `onMenu(name, handler)`: subscribes to bundle menu clicks; returns an unsubscribe; inert during SSR and in plain browser tabs.
421
+ - `@abide/abide/bundle/bundled` — `bundled()`: true inside the desktop bundle (client: webview init flag; server: launcher-spawned process), false in a plain browser tab or on a remote server.
422
+ - `@abide/abide/server/appDataDir` — `appDataDir()`: the running bundle's per-user data dir, keyed by the bundler-injected program name; pure path computation, cwd-independent (`ABIDE_DATA_DIR` overrides).
356
423
 
357
- - `abide/server/appDataDir`the running bundle's per-user data dir (keyed by program name; cwd-independent; server-side).
358
- - `abide/bundle/BundleWindow` — type of the default-exported `src/bundle/window.ts` window config (title/size/menus).
359
- - `abide/bundle/BundleMenu` — a top-level macOS menu (`label` + `items`).
360
- - `abide/bundle/BundleMenuItem` — a single menu entry (divider or a click that dispatches an `abide:menu` event).
361
- - `abide/bundle/onMenu` — subscribe to bundle menu clicks (catch-all or filtered); returns an unsubscribe.
362
- - `abide/bundle/bundled` — true when running inside the desktop bundle rather than a plain browser (isomorphic).
424
+ ## MCP`@documentation mcp`
363
425
 
364
- ## MCP
365
-
366
- ### MCP — @documentation mcp
367
-
368
- - `abide/mcp/createMcpServer` — construct the MCP server bound to the RPC registry (tools from `clients.mcp` RPCs + prompts + resources); `handle(request)` backs `/__abide/mcp`. Framework-internal.
426
+ - `@abide/abide/mcp/createMcpServer` — `createMcpServer(opts?)`: the MCP server behind `/__abide/mcp` — tools derived from every `clients.mcp` rpc and socket (a `<name>-tail` read tool, plus `<name>-publish` under `clientPublish`), prompts from `src/mcp/prompts/`, auth inherited from the inbound request, optional `authorize` hook. Framework-constructed; there is no user-authored server module.
369
427
 
370
428
  ## Testing
371
429
 
372
- ### Testing@documentation testing
373
-
374
- - `abide/test/createTestApp` — boot an in-process app for tests: typed `app.rpc.<rpc>` / `app.sockets.<name>` off the project's real surface. Also `TestApp`, `RpcClient`, `SocketClient`.
375
-
376
- ### Testing plumbing — @documentation plumbing
377
-
378
- - `abide/test/createScriptedSurface` — a scripted MCP surface for driving an `AgentEngine` in tests.
379
- - `abide/test/assertAgentFrameConformance` — assert an engine's frame stream satisfies the neutral `AgentFrame` contract.
430
+ - `@abide/abide/test/createTestApp``@documentation testing` — boots the app in-process for `bun test`: typed `app.rpc.<name>` / `app.sockets.<name>` clients (typed via the generated `testRpc.d.ts` / `testSockets.d.ts`), request scope included, no network.
431
+ - `@abide/abide/test/createScriptedSurface` — `@documentation plumbing` — a scripted `AgentSurface` for engine tests: declarative tool stubs in, an MCP surface out, every dispatched call recorded for assertions.
432
+ - `@abide/abide/test/assertAgentFrameConformance` — `@documentation plumbing` — collects an engine's frame stream and asserts the neutral `AgentFrame` contract (exactly one terminal `done`, paired `tool_use`/`tool_result`, string deltas); returns the frames for provider-specific assertions.
380
433
 
381
434
  ## Generated machine surfaces
382
435
 
383
- Runtime routes the framework serves (the internal `/__abide/config|dev|reload|…`
384
- routes are deliberate plumbing and not listed):
385
-
386
436
  | Route | Serves |
387
437
  | --- | --- |
388
- | `/openapi.json` | OpenAPI 3 document, generated from every schema-bearing RPC |
389
- | `/__abide/mcp` | MCP endpoint (streamable HTTP): tools, prompts, resources |
390
- | `/__abide/health` | Health JSON (the app's `health()` hook merged in) |
391
- | `/__abide/sockets` | Multiplexed WebSocket hub; HTTP face `/__abide/sockets/<name>` (GET tail, POST publish) |
392
- | `/__abide/cli` | CLI binary download (per-platform thin client + server) |
393
- | `/__abide/hot` | Dev hot-update stream the browser bridge consumes for HMR |
394
- | `/__abide/identity` | App identity (name/version) for CLI/bundle connect handshakes |
395
- | `/__abide/inspector` | Opt-in inspector UI (gated by `ABIDE_ENABLE_INSPECTOR`) |
438
+ | `/openapi.json` | The OpenAPI document projected from every rpc's method, URL, and schemas |
439
+ | `/__abide/mcp` | The MCP endpoint (tools from rpcs/sockets, prompts, resources); auth flows from the inbound request |
440
+ | `/__abide/health` | Liveness + identity JSON: framework version, app name/version, plus the app `health(request)` hook's fields; answered ahead of `app.handle` |
441
+ | `/__abide/identity` | Compatibility alias for the same payload with the legacy `abide: true` marker |
442
+ | `/__abide/sockets` | The single multiplexed WebSocket every client socket rides |
443
+ | `/__abide/sockets/<name>` | A socket's HTTP face: `GET` = retained tail as JSON (SSE stream under `Accept: text/event-stream`; `?tail=N` caps/seeds), `POST` = publish gated by `clientPublish`; 404 unless the socket is exposed to mcp/cli |
444
+ | `/__abide/cli` | `GET` = shell install script; `/__abide/cli/<platform>` streams the thin-CLI tarball (cli + server binaries, `.env` baked with `ABIDE_APP_URL`/`ABIDE_APP_TOKEN`) |
445
+ | `/__abide/inspector` | The `@abide/inspector` UI, mounted only under `ABIDE_ENABLE_INSPECTOR=true` |
446
+ | `/__abide/hot/<moduleId>` | Dev-only component hot-module endpoint backing `.abide` HMR |
396
447
 
397
448
  ## Environment variables
398
449
 
399
450
  | Variable | Effect |
400
451
  | --- | --- |
401
- | `PORT` | Server listen port |
402
- | `APP_URL` | Public origin; its pathname becomes the mount base (e.g. `/v2`) for `url()`/routing |
403
- | `ABIDE_APP_URL` | Default remote server URL the CLI/bundle connects to |
404
- | `ABIDE_APP_TOKEN` | Bearer token the CLI/bundle sends to a remote abide server |
405
- | `ABIDE_CLIENT_TIMEOUT` | ms ceiling on a browser/CLI remote RPC call (1–600000) |
406
- | `ABIDE_IDLE_TIMEOUT` | Bun per-connection idle timeout, seconds (default 10) |
407
- | `ABIDE_MAX_REQUEST_BODY_SIZE` | Server-wide max request body bytes |
408
- | `ABIDE_REACHABLE_TIMEOUT` | ms per `reachable()` probe (default 3000, 100–60000) |
409
- | `ABIDE_REACHABLE_TTL` | ms `reachable()` freshness / poll cadence (default 30000, 1000–600000) |
410
- | `ABIDE_DATA_DIR` | Override the per-user app data dir on every platform |
411
- | `ABIDE_LOG_FORMAT` | `json` emits structured log records instead of human lines |
412
- | `ABIDE_ENABLE_INSPECTOR` | `true` mounts the inspector (requires `@abide/inspector`) |
413
- | `ABIDE_INSPECT` | Non-empty enables the desktop webview devtools |
414
- | `DEBUG` | Enable diagnostic channels (`abide:cache`, `abide:rpc`, …); `-abide` silences all |
452
+ | `PORT` | Binds that exact port (a collision fails loudly); unset, the server finds an open port from the default |
453
+ | `APP_URL` | The app's public origin and optional mount base path (a bare `/v2` is tolerated); drives `url()` base-prefixing |
454
+ | `ABIDE_APP_URL` | The remote server a thin CLI binary talks to (baked into its downloaded `.env`) |
455
+ | `ABIDE_APP_TOKEN` | Bearer token the thin CLI sends; baked into the downloaded `.env` when the download request was authenticated |
456
+ | `ABIDE_CLIENT_TIMEOUT` | Default browser-side rpc timeout in ms read at server boot, shipped to the client via the SSR payload |
457
+ | `ABIDE_DATA_DIR` | Overrides the per-user data directory on every platform |
458
+ | `ABIDE_ENABLE_INSPECTOR` | `true` mounts `@abide/inspector` at `/__abide/inspector` (the package must be installed) |
459
+ | `ABIDE_IDLE_TIMEOUT` | Bun per-connection idle timeout in seconds (default 10) |
460
+ | `ABIDE_INSPECT` | Enables right-click Inspect in the desktop bundle's webview |
461
+ | `ABIDE_LOG_FORMAT` | `json` renders one JSON object per log line (default: tab-separated tsv) |
462
+ | `ABIDE_MAX_REQUEST_BODY_SIZE` | Server-wide max request body bytes (a per-rpc `maxBodySize` refines it) |
463
+ | `ABIDE_REACHABLE_TTL` | `reachable()` poll cadence / freshness in ms (default 30000) |
464
+ | `ABIDE_REACHABLE_TIMEOUT` | `reachable()` per-probe bound in ms (default 3000) |
465
+ | `DEBUG` | Channel-gated diagnostics (`DEBUG=abide:rpc`, `abide:sockets`, `abide:build`, …); `DEBUG=-abide` silences the framework's own channel |
415
466
 
416
467
  ---
417
468
 
418
- Mirrors the `exports` map in `package.json`; run
419
- `bun run packages/abide/scripts/readmeSurfaces.ts` after adding or renaming an
420
- export to catch any untagged export and re-derive the slug grouping.
469
+ This file mirrors `package.json`'s `exports`; after adding or renaming an
470
+ export, run `bun run packages/abide/scripts/readmeSurfaces.ts` and regenerate.