@abide/abide 0.41.1 → 0.43.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 (167) hide show
  1. package/AGENTS.md +234 -294
  2. package/CHANGELOG.md +82 -0
  3. package/README.md +51 -95
  4. package/package.json +3 -2
  5. package/src/abideLsp.ts +46 -8
  6. package/src/abideResolverPlugin.ts +107 -19
  7. package/src/buildCli.ts +1 -1
  8. package/src/discoveryEntry.ts +3 -3
  9. package/src/lib/bundle/BundleMenuItem.ts +1 -1
  10. package/src/lib/cli/createClient.ts +12 -12
  11. package/src/lib/cli/dispatchCommand.ts +1 -1
  12. package/src/lib/cli/printSessionStatus.ts +1 -1
  13. package/src/lib/cli/resolveCliTarget.ts +1 -1
  14. package/src/lib/cli/runCli.ts +1 -1
  15. package/src/lib/cli/types/CliManifest.ts +1 -1
  16. package/src/lib/cli/types/CliManifestEntry.ts +2 -2
  17. package/src/lib/mcp/annotationsForMethod.ts +5 -5
  18. package/src/lib/mcp/createMcpServer.ts +3 -3
  19. package/src/lib/mcp/mcpSurface.ts +14 -14
  20. package/src/lib/mcp/types/McpServerOptions.ts +1 -1
  21. package/src/lib/server/DELETE.ts +4 -4
  22. package/src/lib/server/GET.ts +4 -4
  23. package/src/lib/server/HEAD.ts +4 -4
  24. package/src/lib/server/PATCH.ts +4 -4
  25. package/src/lib/server/POST.ts +4 -4
  26. package/src/lib/server/PUT.ts +4 -4
  27. package/src/lib/server/agent.ts +4 -4
  28. package/src/lib/server/env.ts +1 -1
  29. package/src/lib/server/error.ts +49 -7
  30. package/src/lib/server/json.ts +1 -1
  31. package/src/lib/server/rpc/buildErrorConstructors.ts +19 -0
  32. package/src/lib/server/rpc/{defineVerb.ts → defineRpc.ts} +52 -34
  33. package/src/lib/server/rpc/{dispatchVerbInProcess.ts → dispatchRpcInProcess.ts} +3 -3
  34. package/src/lib/server/rpc/fieldErrorsFromIssues.ts +23 -0
  35. package/src/lib/server/rpc/{findVerbByCommandName.ts → findRpcByCommandName.ts} +5 -5
  36. package/src/lib/server/rpc/parseArgs.ts +6 -6
  37. package/src/lib/server/rpc/readBodyWithinLimit.ts +2 -2
  38. package/src/lib/server/rpc/registerRpc.ts +6 -0
  39. package/src/lib/server/rpc/{verbRegistry.ts → rpcRegistry.ts} +4 -4
  40. package/src/lib/server/rpc/{runWithVerbTimeout.ts → runWithRpcTimeout.ts} +4 -4
  41. package/src/lib/server/rpc/types/RemoteHandler.ts +9 -3
  42. package/src/lib/server/rpc/types/{VerbHelper.ts → RpcHelper.ts} +57 -23
  43. package/src/lib/server/rpc/types/{VerbRegistryEntry.ts → RpcRegistryEntry.ts} +3 -3
  44. package/src/lib/server/rpc/types/TypedResponse.ts +2 -2
  45. package/src/lib/server/rpc/unprocessed.ts +6 -7
  46. package/src/lib/server/rpc/validationError.ts +17 -0
  47. package/src/lib/server/runtime/buildInspectorSurface.ts +7 -7
  48. package/src/lib/server/runtime/buildOpenApiSpec.ts +6 -6
  49. package/src/lib/server/runtime/createRouteDispatcher.ts +7 -7
  50. package/src/lib/server/runtime/createServer.ts +4 -4
  51. package/src/lib/server/runtime/crossOriginForbidden.ts +1 -1
  52. package/src/lib/server/runtime/crossOriginGate.ts +4 -4
  53. package/src/lib/server/runtime/devClientFingerprint.ts +2 -1
  54. package/src/lib/server/runtime/logExposedSurfaces.ts +4 -4
  55. package/src/lib/server/runtime/registryManifests.ts +2 -2
  56. package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
  57. package/src/lib/server/runtime/types/InspectorRpc.ts +27 -0
  58. package/src/lib/server/runtime/types/InspectorSurface.ts +4 -4
  59. package/src/lib/server/runtime/types/RequestStore.ts +1 -1
  60. package/src/lib/server/runtime/warnUnguardedMcp.ts +2 -2
  61. package/src/lib/server/sockets/createSocketDispatcher.ts +15 -14
  62. package/src/lib/server/sockets/types/SocketOperation.ts +2 -2
  63. package/src/lib/shared/HttpError.ts +15 -1
  64. package/src/lib/shared/UNREACHABLE_STATUSES.ts +13 -0
  65. package/src/lib/shared/buildRpcRequest.ts +5 -5
  66. package/src/lib/shared/cache.ts +50 -168
  67. package/src/lib/shared/carriesBodyArgs.ts +4 -4
  68. package/src/lib/shared/createCacheStore.ts +12 -0
  69. package/src/lib/shared/createRemoteFunction.ts +11 -6
  70. package/src/lib/shared/decodeResponse.ts +2 -2
  71. package/src/lib/shared/detectRpcMethod.ts +17 -0
  72. package/src/lib/shared/emitLogRecord.ts +3 -3
  73. package/src/lib/shared/escapeRegex.ts +9 -0
  74. package/src/lib/shared/extraForwardHeaders.ts +1 -1
  75. package/src/lib/shared/fileName.ts +9 -0
  76. package/src/lib/shared/fileStem.ts +3 -1
  77. package/src/lib/shared/findExportCallSite.ts +30 -273
  78. package/src/lib/shared/forwardHeaders.ts +2 -2
  79. package/src/lib/shared/httpErrorFor.ts +26 -0
  80. package/src/lib/shared/isAsciiWhitespace.ts +5 -0
  81. package/src/lib/shared/isIdentPart.ts +9 -0
  82. package/src/lib/shared/isIdentStart.ts +8 -0
  83. package/src/lib/shared/isReadOnlyMethod.ts +4 -4
  84. package/src/lib/shared/keyForRemoteCall.ts +4 -4
  85. package/src/lib/shared/log.ts +1 -1
  86. package/src/lib/shared/outboxProbeSlot.ts +20 -0
  87. package/src/lib/shared/pending.ts +4 -1
  88. package/src/lib/shared/prepareRpcModule.ts +96 -14
  89. package/src/lib/shared/probeRegistries.ts +17 -4
  90. package/src/lib/shared/programNameForPackage.ts +3 -1
  91. package/src/lib/shared/queryStringFromArgs.ts +1 -1
  92. package/src/lib/shared/remoteMetaStore.ts +2 -2
  93. package/src/lib/shared/resolveClientFlags.ts +1 -1
  94. package/src/lib/shared/skipNonCode.ts +230 -0
  95. package/src/lib/shared/streamResponse.ts +9 -6
  96. package/src/lib/shared/stripImport.ts +6 -4
  97. package/src/lib/shared/types/CacheEntry.ts +2 -4
  98. package/src/lib/shared/types/CacheOnContext.ts +1 -11
  99. package/src/lib/shared/types/CacheStore.ts +13 -7
  100. package/src/lib/shared/types/ClientFlags.ts +1 -1
  101. package/src/lib/shared/types/ErrorConstructors.ts +17 -0
  102. package/src/lib/shared/types/ErrorDescriptor.ts +10 -0
  103. package/src/lib/shared/types/ErrorSpec.ts +11 -0
  104. package/src/lib/shared/types/HttpMethod.ts +1 -0
  105. package/src/lib/shared/types/Outbox.ts +9 -0
  106. package/src/lib/shared/types/OutboxEntry.ts +27 -0
  107. package/src/lib/shared/types/RawRemoteFunction.ts +2 -2
  108. package/src/lib/shared/types/RemoteCallable.ts +2 -2
  109. package/src/lib/shared/types/RemoteFunction.ts +21 -6
  110. package/src/lib/shared/types/RequestScopeInfo.ts +1 -1
  111. package/src/lib/shared/types/RpcErrorGuard.ts +40 -0
  112. package/src/lib/shared/types/RpcInvoker.ts +1 -1
  113. package/src/lib/shared/types/StandardSchemaV1.ts +1 -1
  114. package/src/lib/shared/types/ValidationErrorData.ts +20 -0
  115. package/src/lib/shared/url.ts +3 -3
  116. package/src/lib/shared/writeRpcDts.ts +7 -7
  117. package/src/lib/shared/writeTestRpcDts.ts +3 -3
  118. package/src/lib/test/createTestApp.ts +10 -10
  119. package/src/lib/ui/compile/ABIDE_SEMANTIC_TOKENS_LEGEND.ts +45 -0
  120. package/src/lib/ui/compile/abideUiPlugin.ts +2 -1
  121. package/src/lib/ui/compile/awaitPlan.ts +48 -0
  122. package/src/lib/ui/compile/compileShadow.ts +117 -31
  123. package/src/lib/ui/compile/createShadowLanguageService.ts +48 -0
  124. package/src/lib/ui/compile/desugarSignals.ts +42 -6
  125. package/src/lib/ui/compile/encodeSemanticTokens.ts +37 -0
  126. package/src/lib/ui/compile/generateBuild.ts +45 -62
  127. package/src/lib/ui/compile/generateSSR.ts +90 -60
  128. package/src/lib/ui/compile/ifPlan.ts +30 -0
  129. package/src/lib/ui/compile/makeVarNamer.ts +10 -0
  130. package/src/lib/ui/compile/offsetToPosition.ts +9 -0
  131. package/src/lib/ui/compile/parseTemplate.ts +565 -115
  132. package/src/lib/ui/compile/stripEffects.ts +17 -9
  133. package/src/lib/ui/compile/structuralBlockTokens.ts +101 -0
  134. package/src/lib/ui/compile/switchPlan.ts +22 -0
  135. package/src/lib/ui/compile/tryPlan.ts +29 -0
  136. package/src/lib/ui/compile/types/SemanticToken.ts +11 -0
  137. package/src/lib/ui/compile/types/TemplateAttr.ts +8 -2
  138. package/src/lib/ui/compile/types/TemplateNode.ts +15 -2
  139. package/src/lib/ui/createScope.ts +20 -2
  140. package/src/lib/ui/dom/appendText.ts +1 -5
  141. package/src/lib/ui/dom/applyResolved.ts +1 -5
  142. package/src/lib/ui/dom/disposeRange.ts +6 -10
  143. package/src/lib/ui/dom/hydrate.ts +2 -5
  144. package/src/lib/ui/dom/isComment.ts +6 -0
  145. package/src/lib/ui/dom/mount.ts +1 -2
  146. package/src/lib/ui/dom/withScope.ts +6 -9
  147. package/src/lib/ui/effect.ts +4 -2
  148. package/src/lib/ui/navigate.ts +40 -13
  149. package/src/lib/ui/outbox.ts +30 -110
  150. package/src/lib/ui/remoteProxy.ts +160 -10
  151. package/src/lib/ui/router.ts +3 -3
  152. package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +281 -0
  153. package/src/lib/ui/rpcOutbox/outboxRegistry.ts +69 -0
  154. package/src/lib/ui/runtime/createDoc.ts +3 -3
  155. package/src/lib/ui/runtime/pathSegments.ts +10 -0
  156. package/src/lib/ui/socketChannel.ts +7 -1
  157. package/src/lib/ui/types/Scope.ts +20 -5
  158. package/template/.zed/settings.json +4 -0
  159. package/template/CLAUDE.md +1 -1
  160. package/template/src/server/rpc/getHello.ts +3 -3
  161. package/template/src/ui/pages/page.abide +4 -4
  162. package/template/test/app.test.ts +2 -2
  163. package/src/lib/server/rpc/registerVerb.ts +0 -6
  164. package/src/lib/server/runtime/types/InspectorVerb.ts +0 -27
  165. package/src/lib/shared/detectVerbMethod.ts +0 -17
  166. package/src/lib/shared/types/HttpVerb.ts +0 -1
  167. package/src/lib/ui/types/Outbox.ts +0 -14
package/AGENTS.md CHANGED
@@ -1,404 +1,344 @@
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, so
5
- > an agent grasps the whole API in one read. The README is the curated
6
- > three-primitive intro (RPCs, sockets, components); this map is complete.
7
- > `CONTEXT.md` is the glossary; `docs/adr/` holds the rationale.
8
- >
9
- > **No barrels.** Every public name has its own module path —
10
- > `@abide/abide/server/GET`, `@abide/abide/shared/cache`, `@abide/abide/ui/scope`. The namespace
11
- > marks the side a name runs on: `@abide/abide/server/*` is server-only,
12
- > `@abide/abide/ui/*` is client-only, `@abide/abide/shared/*` is isomorphic (same callable,
13
- > same behaviour on both sides — the bundler swaps the runtime). There is no
14
- > umbrella `index.ts`, so importing one name never drags in side-effecting
15
- > siblings.
16
- >
17
- > Package `@abide/abide`, runtime Bun ≥ 1.3.0, one direct dependency
18
- > (`typescript`); `tailwindcss` + `bun-plugin-tailwind` are optional peers.
19
- > Import specifiers below are `package.json` `exports` keys, not file paths —
20
- > `@abide/abide/server/GET` resolves to `src/lib/server/GET.ts`.
3
+ > The exhaustive index of abide's public surface: every `exports` key appears once, grouped by namespace, with its import specifier and a one-line spec. This is the whole-API read; the README is the curated 3-primitive intro. CONTEXT.md is the glossary, `docs/adr/` the rationale.
4
+
5
+ **No barrels.** Every public name has its own module path (`abide/server/GET`, `abide/shared/cache`, `abide/ui/scope`) — there is no umbrella `index.ts`, so importing one name never drags side-effecting siblings into the bundle. The namespace marks the side a name runs on: `abide/server/*` is server-only, `abide/ui/*` is client-only, `abide/shared/*` is isomorphic (same callable, same behaviour on both sides; the bundler swaps the runtime). The bullets below name each export by its import specifier in the `abide/…` shorthand (the published package is `@abide/abide`, so `abide/server/GET` imports from `@abide/abide/server/GET`) — these are import specifiers, not source file paths.
6
+
7
+ Package `@abide/abide`. Runtime: Bun ≥ 1.3.0. One direct dependency (TypeScript); `tailwindcss` + `bun-plugin-tailwind` are optional peers for styling.
21
8
 
22
9
  ## The premise
23
10
 
24
- One typed verb declaration fans out to every consumer:
11
+ One typed RPC declaration fans out to every surface:
25
12
 
26
13
  ```text
27
- getMessages = GET(fn, { inputSchema })
28
-
29
- ┌─────────────┬─────────┼──────────┬──────────────┐
30
-
31
- SSR call browser MCP tool CLI sub- OpenAPI
32
- cache(fn)() fetch (read) command operation
33
- proxy
14
+ export const getMessages = GET(fn, { inputSchema })
15
+
16
+ ┌────────────┬────────────┬────────────┬────────────┐
17
+
18
+ SSR call browser MCP tool CLI cmd OpenAPI
19
+ cache(fn)() fetch proxy (read-only) abide cli /openapi.json
34
20
  ```
35
21
 
36
- A Standard Schema unlocks the CLI for every verb and MCP for read-only verbs
37
- (`GET` / `HEAD`); a mutating verb never auto-exposes to MCP — it opts in with
38
- `clients: { mcp: true }`. The same gating applies to sockets: a schema flips
39
- the MCP/CLI read faces on.
22
+ The Standard Schema is the gate: an `inputSchema` unlocks the CLI subcommand and, for read-only methods (GET/HEAD), the MCP tool automatically. A mutating method (POST/PUT/PATCH/DELETE) never auto-exposes to MCP — it opts in with `clients: { mcp: true }`.
40
23
 
41
24
  ## File-based conventions
42
25
 
43
- The bundler reads these paths by convention (`cwd` is the app root):
44
-
45
- | Path | Meaning |
46
- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
47
- | `src/server/rpc/<name>.ts` | One RPC verb per file; export name = filename = URL under `/rpc/`. Server: real handler; client: `remoteProxy`. |
48
- | `src/server/sockets/<name>.ts` | One socket per file; export name = filename. Server: `defineSocket`; client: `socketProxy`. |
49
- | `src/server/config.ts` | Optional. Eager-imported for boot-time `env()` validation (virtual `abide:config`). |
50
- | `src/mcp/prompts/*.md` | MCP prompts; frontmatter (description, JSON Schema) + `{{name}}` body. Rewritten to `definePrompt`. |
51
- | `src/mcp/resources/**` | MCP resource files, gzip-embedded into the standalone binary. |
52
- | `src/app.ts` | Optional `AppModule` `init` / `handle` / `handleError` / `health` / `forwardHeaders` hooks (virtual `abide:app`). |
53
- | `src/bundle/window.ts` | Optional desktop-bundle config (`BundleWindow`); baked into the launcher. |
54
- | `src/bundle/disconnected.abide` | Optional custom connect screen; falls back to the built-in component. |
55
- | `src/ui/pages/**/page.abide` | A page; its folder path is the route. `[id]` folder = dynamic segment → `page.params.id`. |
56
- | `src/ui/pages/**/layout.abide` | A layout wrapping pages at/below its folder; its `<slot/>` is the router outlet. |
57
- | `src/ui/public/**` | Static assets served at site root; gzip-embedded into the standalone binary. |
58
- | `src/.abide/*.d.ts` | Generated types written each build — `routes`, `rpc`, `sockets`, `health`, `publicAssets`, and test surfaces. |
59
- | `dist/_app/` | Client bundle output (hashed chunks; prod adds precompressed `.gz` siblings). |
26
+ The bundler reads these paths; the path is the identity.
27
+
28
+ | Path | Meaning |
29
+ | --- | --- |
30
+ | `src/server/rpc/<name>.ts` | one RPC export; URL is `/rpc/<name>` |
31
+ | `src/server/sockets/<name>.ts` | one socket export; socket identity is `<name>` |
32
+ | `src/mcp/prompts/<name>.md` | one MCP prompt; identity is `<name>` (front-matter + `{{arg}}` body) |
33
+ | `src/server/config.ts` | the `env(schema)` config, read at boot |
34
+ | `src/app.ts` | optional `AppModule` hooks (`init`, `handle`, `handleError`) |
35
+ | `src/bundle/window.ts` | optional `BundleWindow` default export (desktop bundle) |
36
+ | `src/ui/pages/**/page.abide` | a page; the directory is the route, `[id]` a dynamic segment, `[...rest]` a catch-all |
37
+ | `src/ui/pages/**/layout.abide` | a layout wrapping every page below it via its `<slot/>` outlet |
38
+ | `src/.abide/*.d.ts` | generated type surfaces (RPC routes, health fields, test client) |
39
+ | `public/` | static assets served as-is |
40
+ | `dist/_app/` | the built client bundle (`abide build`) |
60
41
 
61
42
  ## CLI
62
43
 
63
- ```text
64
- abide scaffold <name> scaffold the bundled template, install deps, start dev
65
- abide dev build client + run server with hot reload
66
- abide build single client build into dist/_app
67
- abide start run the production server against a built dist/
68
- abide run <file> [...] run a script under the abide preload (same runtime)
69
- abide compile build a standalone server executable
70
- abide cli build a thin CLI binary (remote client + bundled server)
71
- abide bundle build a movable self-contained desktop app bundle
72
- abide check type-check every .abide template + props
73
- abide lsp run the .abide language server over stdio (JSON-RPC)
74
- abide init-agent write/refresh the CLAUDE.md pointer to this surface map
75
- ```
76
-
77
- `bun test` in a scaffolded app preloads abide via `bunfig.toml`
78
- (`[test] preload = ["@abide/abide/preload"]`) so `.abide` modules resolve.
44
+ | Command | Does |
45
+ | --- | --- |
46
+ | `abide scaffold <name>` | scaffold a project, install it, start dev (`--no-install` / `--no-dev` to skip) |
47
+ | `abide dev` | build + run with hot reload |
48
+ | `abide build` | build the client into `dist/_app/` |
49
+ | `abide check` | type-check `.abide` templates + props |
50
+ | `abide start` | run the production server against `dist/` |
51
+ | `abide run <file> [args…]` | run a script under the abide preload (same runtime as the server) |
52
+ | `abide compile [--target] [--out]` | build a standalone server executable |
53
+ | `abide cli [--target] [--out] [--platforms]` | build the CLI binary (a thin remote client that ships the server beside it) |
54
+ | `abide bundle` | build a movable, self-contained desktop app bundle for this platform |
55
+ | `abide lsp` | the `.abide` language server (editor integration) |
56
+ | `abide init-agent` | write/refresh a CLAUDE.md pointer to this surface map (non-scaffolded projects) |
57
+
58
+ For tests, add `preload = ["@abide/abide/preload"]` under `[test]` in `bunfig.toml` and use `bun test`.
79
59
 
80
60
  ## Authoring contracts
81
61
 
82
- **RPC verb** — `export const <name> = GET(handler, opts?)` (same shape for
83
- `POST` / `PUT` / `PATCH` / `DELETE` / `HEAD`). The handler receives the parsed
84
- args bag (`InferOutput<inputSchema>`, merged with validated files when
85
- `filesSchema` is set), may read `request()` / `cookies()` / `server()` from
86
- request scope, and returns `json` / `jsonl` / `sse` / `error` / `redirect` or a
87
- raw `Response`. `opts`: `inputSchema` (validates args, infers the type, gates
88
- CLI + read-MCP), `outputSchema` (documents the 200 body for OpenAPI/MCP),
89
- `filesSchema` (multipart File parts), `clients: { browser, mcp, cli }` (explicit
90
- surface targeting; explicit wins over schema auto-flip), `crossOrigin` (exempt
91
- a mutating verb from the same-origin gate), `maxBodySize` (per-verb 413
92
- ceiling), `timeout` (handler deadline in ms; 504 on every surface). Query args
93
- arrive as strings — use `z.coerce.*`. Consume four ways: `cache(verb)(args)`
94
- in-process, the swapped browser `fetch`, `verb.raw(args)` for the `Response`,
95
- `verb.stream(args)` to iterate a streaming body.
96
-
97
- **Socket** — `export const <name> = socket({ schema?, tail?, ttl?,
98
- clientPublish?, clients? })`. `tail` retains the last N frames for late joiners
99
- and the read faces; `ttl` lazily evicts frames older than N ms; `clientPublish`
100
- (default off) gates browser publishes; `schema` validates publishes
101
- synchronously and flips MCP/CLI on. Isomorphic `AsyncIterable<T>`: bare
102
- iteration is the live stream, `.tail(count?)` seeds from retention,
103
- `.publish(msg)` fans out in-process + over the multiplexed ws.
104
-
105
- **Page / layout** — a `[id]` folder segment becomes `page.params.id`. A layout's
106
- `<slot/>` is the outlet the next layer fills. Read `page` (route, params, url,
107
- navigating) and call `navigate(path)` for SPA transitions.
108
-
109
- **App / config** — `src/app.ts` default-exports an `AppModule`; `src/server/config.ts`
110
- runs `env(schema)` at boot.
111
-
112
- **Isomorphism move** — wrap an SSR read in `cache(fn)()` so the value serializes
113
- into the document and the client hydrates warm instead of refetching.
62
+ - **RPC** — `export const <name> = METHOD(handler, opts?)` in `src/server/rpc/<name>.ts`. The handler receives the parsed args (`InferOutput<inputSchema>`, or the raw `Args` when schemaless), reads request state via `request()` / `cookies()`, and returns `json` / `jsonl` / `sse` / `error` / `redirect` / a raw `Response`. `opts`: `inputSchema`, `outputSchema`, `filesSchema` (multipart upload), `errors`, `clients: { browser, mcp, cli }`, `crossOrigin` (CSRF exemption), `maxBodySize` (413 past it), `timeout` (504, every surface), `outbox` (durable mutating delivery). Query args (GET/DELETE/HEAD) arrive as strings — use `z.coerce.*`. Consume four ways: `cache(fn)()` in-process, the swapped `fetch` proxy in the browser, `fn.raw(args)` (untouched `Response`), `fn.stream(args)` (frame stream). A non-2xx throws `HttpError`; `fn.isError(err, name)` type-guards it against the rpc's own `errors` (plus `'validation'` / `'queued'`), narrowing `.kind` and the matching `.data`.
63
+ - **Socket** — `export const <name> = socket(opts)` in `src/server/sockets/<name>.ts`. A `Socket<T>` is an `AsyncIterable<T>`: bare iteration is the live stream; `<name>.tail(count?)` seeds from the retained tail; `<name>.publish(m)` is isomorphic. `opts`: `schema` (validates publishes, infers `T`, flips MCP/CLI faces on), `tail` (retained frame count), `ttl` (lazy age eviction), `clientPublish` (allow browser publishes), `clients`.
64
+ - **Page / layout** `.abide` files under `src/ui/pages/`. A `[id]` segment lands on `page.params.id`; a layout's `<slot/>` is the outlet the child fills; read the route via `page`, navigate via `url` / `navigate`.
65
+ - **app.ts / config.ts** — `app.ts` exports optional `AppModule` hooks (`init` cleanup, `handle` middleware, `handleError`); `config.ts` exports `env(schema)`, validated synchronously at boot.
66
+ - **Isomorphism move** read through `cache()` during SSR so the value bakes into the HTML and the matching `{#await}` adopts the server DOM warm on hydration (no refetch).
114
67
 
115
68
  ## .abide template grammar
116
69
 
117
- A component file is `<script>` (module-level JS: imports, handlers,
118
- reactive declarations) + optional `<template name="…">` snippet definitions +
119
- markup + an optional component-scoped `<style>`. Ambient in-scope names need no
120
- import: `scope`, `props` / `prop`, `effect`, `html`, `snippet`.
70
+ A component is one `.abide` file: valid HTML with an optional `<script lang="ts">`, `{expr}` bindings, `{#…}` control-flow blocks, `<template name>` snippets, capitalised component tags, and a component-scoped `<style>`. The browser's parser is the tree-builder, so SVG/MathML and foreign content work. Ambient in every component (no import): `scope`, `props`, `effect`; `html` and `snippet` are the templating brands.
121
71
 
122
- **Reactive state**
72
+ **Reactive state** — reached through `scope()` (destructure `const { state, computed, linked } = scope()` once, or call `scope().state(…)` directly). A bare `state` / `computed` / `linked` with no `scope()` in view is a compile error.
123
73
 
124
- | Form | Meaning |
125
- | ---------------------------------- | ---------------------------------------------------------------------------------- |
126
- | `scope().state(v)` | Writable reactive cell (`{ value }`); the sole writable surface. |
127
- | `scope().computed(fn)` | Read-only derived cell; re-runs when its reads change. |
128
- | `scope().linked(initial, reseed?)` | Cell bound to a reactive-document path; reflects/drives the root doc. |
129
- | `effect(fn)` | Runs now, re-runs on dep change; may return a teardown; returns a disposer. |
130
- | `props()` / `prop(name)` | Read the component's props (thunks for reactivity), e.g. `const { id } = props()`. |
74
+ | Form | Is |
75
+ | --- | --- |
76
+ | `scope().state(value, transform?)` | a writable cell read/reassigned as a plain variable; optional `transform(next, prev)` coerces every write |
77
+ | `scope().linked(fn, transform?)` | a writable local draft seeded from upstream — reseeds when the source changes, edits stay local |
78
+ | `scope().computed(fn)` | a read-only derived value — re-runs when a cell it reads changes |
79
+ | `scope().effect(fn)` | a client-only side effect (stripped from SSR); return a function to clean up |
80
+ | `props()` | destructure component props (`const { name = fallback, ...rest } = props()`), each a reactive read |
81
+
82
+ A two-way binding over derived state is an accessor at the bind site (`bind:value={{ get, set }}`), not a writable primitive.
131
83
 
132
84
  **Bindings**
133
85
 
134
- | Form | Meaning |
135
- | --------------------------- | -------------------------------------------------------------------------------------------- |
136
- | `{expr}` | Escaped text interpolation; raw markup via the `html` tag; `{snippet(args)}` mounts a snippet. |
137
- | `name={expr}` | Reactive attribute (boolean `true` sets bare, falsy removes). |
138
- | `on<event>={fn}` | Event listener, e.g. `onclick={handler}`. |
139
- | `bind:value={x}` | Two-way input binding. |
140
- | `bind:checked={x}` | Two-way checkbox binding. |
141
- | `bind:group={x}` | Radio/checkbox group (radio one value, checkbox → array). |
142
- | `bind:value={{ get, set }}` | Writable computed at the binding site. |
143
- | `{...expr}` | Spread keys as attributes (elements) or props (components). |
144
- | `attach={fn}` | Build-time element attachment with optional teardown. |
145
-
146
- **Control flow** — native `<template>` tags:
147
-
148
- | Form | Meaning |
149
- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
150
- | `<template if={c}>` / `<template elseif={c}>` / `<template else>` | Conditional, source-order, re-evaluated reactively. |
151
- | `<template each={list} as="x" key="x.id" index="i">` | Keyed list; reorder/re-key updates rows in place; `index` is reactive. |
152
- | `<template each await={asyncIter} as="x" key="…">` | Async keyed list; rows append as the iterator yields. |
153
- | `<template await={p}>` / `<template then="v">` / `<template catch="e">` / `<template finally>` | Promise branches; `await={p} then="v"` on one tag renders inline (blocking) on the server. |
154
- | `<template switch={s}>` / `<template case="v">` / `<template default>` | First strict (`===`) match wins. |
155
- | `<template try>` / `<template catch="e">` / `<template finally>` | Synchronous render error boundary. |
156
- | `<template name="row" args={item}>` … `{row(item)}` | Snippet definition + call. |
157
-
158
- **Components & slots** — a capitalised tag (`<Card title={x}>`) mounts a child;
159
- attributes become props, children fill the child's `<slot>` (with fallback when
160
- empty). `<style>` is scoped to the component and its children. Component files
161
- end in `.abide`. `then`/`each` value names lexically shadow same-named
162
- component state inside the block.
163
-
164
- ## Server surface — abide/server/\* (server-only)
165
-
166
- ### RPC — @documentation rpc
167
-
168
- - `@abide/abide/server/GET` — declare a `GET` verb; args from the query string. Schema auto-exposes CLI + read-MCP.
169
- - `@abide/abide/server/POST` — declare a `POST` verb; args from JSON/form/multipart body (body wins on collision).
170
- - `@abide/abide/server/PUT` — declare a `PUT` verb (body args; mutating).
171
- - `@abide/abide/server/PATCH` — declare a `PATCH` verb (body args; mutating).
172
- - `@abide/abide/server/DELETE` — declare a `DELETE` verb (query args; mutating).
173
- - `@abide/abide/server/HEAD` — declare a `HEAD` verb (query args; read-only, headers-only response).
174
-
175
- ### Response — @documentation response
176
-
177
- - `@abide/abide/server/json` — `json(data, init?)`: JSON `TypedResponse` (`Cache-Control: no-store`); `undefined` → 204.
178
- - `@abide/abide/server/jsonl` — `jsonl(asyncIterable, init?)`: JSONL stream, one value per line; errors emit a `{"$error"}` final line.
179
- - `@abide/abide/server/sse` — `sse(asyncIterable, init?)`: Server-Sent Events with 15s keepalive comments; errors as an `error` event.
180
- - `@abide/abide/server/error` — `error(status, message?, init?)`: plain-text error `TypedResponse<never>` (narrows out of the success union).
181
- - `@abide/abide/server/redirect` — `redirect(url, status=302, init?)`: redirect accepting relative URLs; `TypedResponse<never>`.
86
+ | Syntax | Binds |
87
+ | --- | --- |
88
+ | `{expr}` | reactive text / snippet / `html` interpolation (escaped unless `html`-branded) |
89
+ | `name={expr}` | reactive attribute (`false`/`null`/`undefined` removes it) |
90
+ | `on<event>={fn}` | event listener (`onclick`, `onsubmit`, …) |
91
+ | `bind:value={x}` / `bind:checked={x}` / `bind:group={x}` | two-way form binding |
92
+ | `bind:value={{ get, set }}` | two-way binding over derived state |
93
+ | `{...expr}` | spread props (component) or attributes (element) |
182
94
 
183
- ### Request scope @documentation request-scope
95
+ **Control flow**mustache blocks (`{#…}` open, `{:…}` branch, `{/…}` close):
184
96
 
185
- - `@abide/abide/server/request` `request()`: the inbound `Request` from request-scoped ALS; throws outside scope.
186
- - `@abide/abide/server/cookies` `cookies()`: live `Bun.CookieMap`; writes flush to `Set-Cookie`; throws outside scope.
187
- - `@abide/abide/server/server` `server()`: the active `Bun.serve` instance (in-process shim for CLI/MCP/test dispatch).
97
+ | Block | Form |
98
+ | --- | --- |
99
+ | if | `{#if cond}` `{:elseif cond}` `{:else}` `{/if}` |
100
+ | for | `{#for item of list by key}` … `{/for}` (`item, i of list`; `{#for await x of source …}` over an AsyncIterable) |
101
+ | await | `{#await promise}` pending `{:then value}` … `{:catch error}` … `{/await}` (inline `{#await p then v}`) |
102
+ | switch | `{#switch subject}` `{:case value}` … `{:default}` … `{/switch}` |
103
+ | try | `{#try}` … `{:catch error}` … `{:finally}` … `{/try}` |
104
+ | snippet | `<template name="row" args={{ msg }}>…</template>`, called as `{row({ msg })}` |
188
105
 
189
- ### Configuration@documentation configuration
106
+ **Components & slots** a capitalised tag (`<Avatar name={…} />`) mounts a child `.abide` component as a marker-bounded range (no wrapper element); its children fill the child's `<slot>`. `<style>` is component-scoped.
107
+
108
+ ## Server surface — abide/server/*
190
109
 
191
- - `@abide/abide/server/env``env(schema)`: validate `process.env` against a Standard Schema at boot (synchronous; reports all issues).
110
+ ### RPC helpers @documentation rpc
111
+
112
+ - `abide/server/GET` — declare a read RPC; the bundler rewrites it to a server `defineRpc` or a client `remoteProxy`. Calling it directly throws.
113
+ - `abide/server/POST` — declare a creating mutating RPC (carries the durable `outbox` overloads).
114
+ - `abide/server/PUT` — declare a replacing mutating RPC.
115
+ - `abide/server/PATCH` — declare a partial-update mutating RPC.
116
+ - `abide/server/DELETE` — declare a deleting mutating RPC.
117
+ - `abide/server/HEAD` — declare a metadata-only read RPC.
192
118
 
193
119
  ### Sockets — @documentation sockets
194
120
 
195
- - `@abide/abide/server/socket` — `socket({ schema?, tail?, ttl?, clientPublish?, clients? })`: declare a broadcast topic; isomorphic `Socket<T>`.
121
+ - `abide/server/socket` — declare a broadcast `Socket<T>` in `src/server/sockets/<name>.ts`; opts (`schema`, `tail`, `ttl`, `clientPublish`, `clients`) live on the server, the client proxy discards them.
196
122
 
197
- ### Agent — @documentation agent
123
+ ### Responses — @documentation response
124
+
125
+ - `abide/server/json` — JSON `Response` with `Cache-Control: no-store` default; `json(undefined)` emits a 204 that round-trips back to `undefined`. Brands `T` so the RPC infers `Return`.
126
+ - `abide/server/jsonl` — wrap an `AsyncIterable<Frame>` as a JSON Lines (`application/jsonl`) streaming response; cancellation flows into the generator's `return()`, errors become a final `{"$error":…}` line.
127
+ - `abide/server/sse` — wrap an `AsyncIterable<Frame>` as Server-Sent Events (`text/event-stream`) with a 15s keepalive; errors become an `event: error` frame.
128
+ - `abide/server/error` — return a non-2xx wire error (`TypedResponse<never>`); the caller's `await fn(args)` throws `HttpError`, so it never pollutes the inferred success `Return`.
129
+ - `abide/server/redirect` — return a 3xx redirect (`TypedResponse<never>`), same inference-neutral shape as `error`.
130
+
131
+ ### Request scope — @documentation request-scope
132
+
133
+ - `abide/server/request` — the inbound `Request` for the current SSR/RPC pass (AsyncLocalStorage); throws outside a request scope.
134
+ - `abide/server/cookies` — the request's `Bun.CookieMap`; reads parse the inbound header, writes flush as `Set-Cookie` on return. Lazy; throws outside a scope.
135
+ - `abide/server/server` — the active `Bun.serve` instance; returns a no-op in-process server under a scope without a booted server (CLI/MCP/test), throws before init outside any scope.
136
+
137
+ ### Configuration — @documentation configuration
198
138
 
199
- - `@abide/abide/server/agent` — `agent(engine, messages)`: run an `AgentEngine` against the request's MCP surface; yields `AgentFrame`s (wrap in `jsonl`/`sse`). Exports the `AgentEngine` / `AgentSurface` / `NeutralMessage` / `AgentFrame` types; provider engines ship in `@abide/<provider>`.
139
+ - `abide/server/env` — validate `Bun.env` against a Standard Schema at boot and return the typed config; reports every issue at once on failure, registers the schema for the bundle setup form.
200
140
 
201
141
  ### Observability — @documentation observability
202
142
 
203
- - `@abide/abide/server/reachable` — `reachable(host)`: cached outbound reachability HEAD (first call probes, then polls every TTL); any HTTP response counts as up. Tuned by `ABIDE_REACHABLE_TTL` / `ABIDE_REACHABLE_TIMEOUT`.
143
+ - `abide/server/reachable` — server-only outbound reachability: `await reachable(host)` HEADs the origin, caches the verdict, and background-polls every TTL so later calls resolve instantly.
144
+
145
+ ### Bundle data — @documentation bundle
146
+
147
+ - `abide/server/appDataDir` — the running desktop bundle's per-user data dir, keyed by the program name; cwd-independent, pure.
148
+
149
+ ### Agent — @documentation agent
150
+
151
+ - `abide/server/agent` — run a provider engine against the app's own MCP surface (`agent(engine, messages)`) and yield a neutral `AgentFrame` stream; the handler frames it with `jsonl()`/`sse()`. Exports `NeutralMessage` / `AgentFrame` / `AgentEngine`.
204
152
 
205
153
  ### Plumbing — @documentation plumbing
206
154
 
207
- - `@abide/abide/server/AppModule` — type of `src/app.ts`: optional `forwardHeaders`, `init`, `handle`, `handleError`, `health` hooks.
208
- - `@abide/abide/server/InspectorContext` — type of the capability bundle the core injects into `@abide/inspector` when `ABIDE_ENABLE_INSPECTOR=true`.
209
- - `@abide/abide/server/rpc/defineVerb` — `defineVerb(method, url, handler, opts?)`: low-level builder the GET/POST/… helpers wrap.
210
- - `@abide/abide/server/sockets/defineSocket` — `defineSocket(name, opts?)`: server-side `Socket` construction (per-subscriber queue, shared retained tail).
155
+ - `abide/server/AppModule` — the optional `src/app.ts` hook shape (`init` → cleanup, single-middleware `handle`, `handleError`).
156
+ - `abide/server/InspectorContext` — the capability bundle core injects into `@abide/inspector` when `ABIDE_ENABLE_INSPECTOR=true`, so the inspector stays a pure consumer.
157
+ - `abide/server/rpc/defineRpc` — server-side RPC construction from method + URL + handler; the bundler's server-target rewrite emits it. Records the synthesized Request for `cache()`.
158
+ - `abide/server/sockets/defineSocket` — server-side socket construction; the bundler's server-target rewrite binds the file-path name into real fan-out over `server.publish`.
159
+ - `abide/server/prompts/definePrompt` — register an MCP prompt from a name + options; the resolver plugin emits it per `src/mcp/prompts/<name>.md`.
160
+ - `abide/server/prompts/renderPromptTemplate` — substitute `{{name}}` placeholders in a prompt body (missing args collapse to empty).
211
161
 
212
- ## Isomorphic surface — abide/shared/\* (same callable both sides)
162
+ ## Isomorphic surface — abide/shared/*
213
163
 
214
- ### Cache — @documentation cache
164
+ ### Responses — @documentation response
215
165
 
216
- - `@abide/abide/shared/cache` — `cache(fn, opts?)`: coalesce/memoize a remote verb or producer per store (request-scoped server, tab-scoped client). `opts`: `global` (process store), `ttl`, `swr`. Methods: `cache.invalidate`, `cache.on`, `cache.patch`. Warm SSR reads resolve synchronously on hydrate.
166
+ - `abide/shared/HttpError` — thrown by remote-function calls on a non-2xx response; carries the raw `Response` (status/`statusText`/headers/body) plus the typed-error layer parsed off a `{ $abideError, data }` body: `.kind` (the declared error name, `'validation'`, or the framework-reserved `'queued'`) and `.data` (`unknown` — narrow yourself; `ValidationErrorData` for `'validation'`). Both undefined for a plain `error(status, text)`.
167
+ - `abide/shared/ValidationErrorData` — the `data` a `kind: 'validation'` HttpError (422) carries: `{ issues, fields }` — the raw Standard Schema issue list plus the form-friendly field → first-message map.
217
168
 
218
- ### Templating — @documentation templating
169
+ ### Schema projection — @documentation rpc
219
170
 
220
- - `@abide/abide/shared/html` — `html` (tagged template or `html(str)`): mark a string as trusted raw HTML so `{expr}` inserts nodes verbatim (no auto-escape).
221
- - `@abide/abide/shared/snippet` — `snippet(payload)`: brand a snippet payload (client DOM builder / server HTML string) so `{expr}` mounts it.
171
+ - `abide/shared/withJsonSchema` — attach a `toJSONSchema()` projection to a Standard Schema whose library lacks one, feeding OpenAPI / MCP / CLI / the setup form.
222
172
 
223
- ### Response — @documentation response
173
+ ### Templating — @documentation templating
224
174
 
225
- - `@abide/abide/shared/HttpError` — `class HttpError extends Error`: thrown by remote calls on non-2xx; carries `status`, `statusText`, the raw `response`.
175
+ - `abide/shared/html` — a `` html`…` `` tag marking trusted raw markup that a `{expr}` inserts verbatim (registered-Symbol brand, survives bundle copies).
176
+ - `abide/shared/snippet` — the `Snippet<Payload>` brand a `<template name>` carries: a DOM builder on the client, pre-rendered HTML on the server.
226
177
 
227
- ### RPC — @documentation rpc
178
+ ### Cache — @documentation cache
228
179
 
229
- - `@abide/abide/shared/withJsonSchema` — `withJsonSchema(schema, toJsonSchema)`: attach a `toJSONSchema()` method (for schemas lacking one) feeding OpenAPI/MCP/CLI/bundle forms.
180
+ - `abide/shared/cache` — curry a call against a store: `cache(fn, options?)` returns an invoker that coalesces in-flight calls and retains by `ttl` (undefined = forever, 0 = dedupe-only). Reactive (subscribes the reading scope), isomorphic, `options.global` for the process store. `cache.invalidate` / `cache.on` address entries by selector.
230
181
 
231
182
  ### Page — @documentation page
232
183
 
233
- - `@abide/abide/shared/page` — reactive `page` proxy: `{ route, params, url, navigating }`; server reads per-request store, client reads the router snapshot.
184
+ - `abide/shared/page` — the reactive page proxy (matched route, decoded `params`, browser-space `url`, `navigating`); isomorphic, re-runs readers on navigation.
234
185
 
235
- ### URL — @documentation url
186
+ ### Probes — @documentation probes
236
187
 
237
- - `@abide/abide/shared/url` — `url(path, ...args)`: base-correct, typed in-app URLs for RPCs (args query), page routes (`[name]` params), and assets; external URLs pass through untouched.
188
+ - `abide/shared/pending` — reactive "no value yet" probe over the cache + tail registries; selector grammar `pending(fn)` / `pending(fn, args)` / `pending({ tags })` / `pending(subscribable)`. Counts undelivered durable-outbox entries.
189
+ - `abide/shared/refreshing` — reactive "holding a value while a fresher source is in flight" probe; same selector grammar.
190
+ - `abide/shared/online` — the client/server-reported connectivity boolean (the inbound counterpart to `reachable`).
238
191
 
239
- ### Probes — @documentation probes
192
+ ### URL — @documentation url
240
193
 
241
- - `@abide/abide/shared/pending` — `pending(selector?, args?)`: reactive any/specific/exact/tagged call in flight, or a stream awaiting its first frame.
242
- - `@abide/abide/shared/refreshing` — `refreshing(selector?, args?)`: reactive — a value held while fresh data is in flight (SWR refetch / reconnect with retained value).
243
- - `@abide/abide/shared/online` — `online()`: reactive connectivity (client `navigator.onLine`; server reads the caller's reported state); reports, never acts.
194
+ - `abide/shared/url` — typed URL builder: `url('/rpc/search', { q })` types args against the generated `RpcRoutes`, falling through to page/asset paths; base-correct.
244
195
 
245
196
  ### Observability — @documentation observability
246
197
 
247
- - `@abide/abide/shared/health` — `health()`: reactive backend health (`reachable` + app fields) polled from `/__abide/health` only while read; SSR-seeded.
248
- - `@abide/abide/shared/log` — `log(...)` / `.warn` / `.error` / `.trace(label, fn)` / `.channel(name)`: request-scoped logger; TSV or JSON (`ABIDE_LOG_FORMAT`).
249
- - `@abide/abide/shared/trace` — `trace()`: the current request's W3C `traceparent`, or `undefined` outside request scope; isomorphic.
198
+ - `abide/shared/health` — reactive backend health (`reachable` + the app `health()` hook's fields), polled from `/__abide/health` only while a tracking scope reads it; constant `{ reachable: true }` on the server.
199
+ - `abide/shared/log` — the unified logger: `log`/`warn`/`error`/`trace` on the app's always-on channel, `log.channel(name)` on a DEBUG-gated channel; tsv default or JSON per line (`ABIDE_LOG_FORMAT=json`). Carries request-scope context.
200
+ - `abide/shared/trace` — the current request's W3C `traceparent`, isomorphic (ALS on the server, `__SSR__` stamp in the browser); undefined outside a scope.
250
201
 
251
202
  ### Plumbing — @documentation plumbing
252
203
 
253
- - `@abide/abide/shared/createSubscriber` — `createSubscriber(start)`: open-on-first-read / close-on-last-reader resource lifecycle on the signal core.
204
+ - `abide/shared/createSubscriber` — abide-ui-native open-on-first-read / close-on-last-reader subscriber over abide's own signal core (the lifecycle `cache`/`tail`/`health` reuse).
254
205
 
255
- ## UI surface — abide/ui/\* (client-only)
206
+ ## UI surface — abide/ui/* (client-only)
256
207
 
257
208
  ### Reactive state — @documentation reactive-state
258
209
 
259
- - `@abide/abide/ui/scope` — `scope(address?)`: resolve the lexical reactive scope; `.state` / `.computed` / `.linked` are the reactive cells (see grammar).
210
+ - `abide/ui/scope` — resolve a lexical scope: `scope()` is the current one (compiler-established per level), `scope('/')` the tree root; the returned value is passable to children/helpers.
260
211
 
261
- ### Effect — @documentation effect
212
+ ### Streaming — @documentation tail
262
213
 
263
- - `@abide/abide/ui/effect` — `effect(fn)`: run now, re-run on dep change, optional teardown return; returns a disposer.
214
+ - `abide/ui/tail` — reactive consumer for a `Subscribable<T>` (a `Socket<T>` or `fn.stream(args)`): bare form is latest-wins (`T | undefined`), `{ last: n }` is a live window (`T[]`); reconnect keeps the value and flags `refreshing`. No-op on the server. `tail.error` / `tail.status` address the same entry.
264
215
 
265
- ### Tail — @documentation tail
216
+ ### Navigation — @documentation navigate
266
217
 
267
- - `@abide/abide/ui/tail` — `tail(subscribable, opts?)`: reactive consumer of a `Socket<T>` or `fn.stream` result (latest-wins, or windowed via `{ last }`); seeds from retention; no-op on the server.
218
+ - `abide/ui/navigate` — programmatic SPA navigation typed off the page routes; `NavigateOptions` carries `replace` / `keepScroll`. Delegates path-building to `url`.
268
219
 
269
- ### Navigate — @documentation navigate
220
+ ### Outbox — @documentation ui
270
221
 
271
- - `@abide/abide/ui/navigate` — `navigate(path, { replace?, keepScroll? })`: client navigation; writes history and re-mounts the page chain.
272
-
273
- ### UI — @documentation ui
274
-
275
- - `@abide/abide/ui/outbox` — `outbox({ key, send, store, online, onDrop })`: durable FIFO mutation queue for local-first writes; drains while online, retries on reconnect, at-least-once.
222
+ - `abide/ui/outbox` — the global reactive view of every durable-RPC outbox: a flat list of undelivered entries tagged with their `rpc`, `outbox.retry()` to drain all queues. A single RPC's slice is `rpc.outbox`. Empty on the server. Exports `GlobalOutboxEntry`.
276
223
 
277
224
  ### Plumbing — @documentation plumbing
278
225
 
279
- - `@abide/abide/ui/enterScope` — `enterScope()`: open a fresh lexical scope for an SSR render; returns the previous to restore.
280
- - `@abide/abide/ui/exitScope` — `exitScope(previous)`: restore the scope `enterScope` saved.
281
- - `@abide/abide/ui/router` — `router(host, loaders, layoutLoaders, probe?)`: History-API router; diffs the layout/page chain on nav. Returns a disposer.
282
- - `@abide/abide/ui/startClient` — `startClient(routes, layoutRoutes, target)`: official client entry; reads `__SSR__`, seeds cache, starts the router.
283
- - `@abide/abide/ui/renderToStream` — `renderToStream(render)`: out-of-order SSR streaming; yields the shell, then one `<abide-resolve>` fragment per streaming await.
284
- - `@abide/abide/ui/remoteProxy` — `remoteProxy(method, url)`: client substitute for a verb handler; identical `RemoteFunction` shape so `cache()` matches both sides.
285
- - `@abide/abide/ui/socketProxy` — `socketProxy(name)`: client substitute for a server `Socket`; subscribes over the multiplexed ws channel.
286
- - `@abide/abide/ui/dom/mount` — mount a top-level page/layout into a host under an ownership scope; returns a disposer.
287
- - `@abide/abide/ui/dom/mountChild` — mount a child component as a marker-bounded range (no wrapper element); records the instance for hot reload.
288
- - `@abide/abide/ui/dom/mergeProps` — compose child props from ordered layers (thunks, spreads, slot) via a Proxy; last layer wins.
289
- - `@abide/abide/ui/dom/spreadProps` — wrap a `{...source}` spread so each key resolves to a live thunk.
290
- - `@abide/abide/ui/dom/restProps` — the unconsumed rest of a component's props as a live, enumerable object.
291
- - `@abide/abide/ui/dom/spreadAttrs` — spread an object onto a native element: `on*` keys as listeners, others as reactive attributes.
292
- - `@abide/abide/ui/dom/readCall` — guarded reactive-doc method call with scope-path error messages.
293
- - `@abide/abide/ui/dom/hydrate` — adopt server-rendered DOM, attaching listeners/effects in place with a claim cursor.
294
- - `@abide/abide/ui/dom/text` — a text node whose content tracks `read()`.
295
- - `@abide/abide/ui/dom/appendText` — reactive `{expr}` interpolation (escaped text, raw `html`, or snippet).
296
- - `@abide/abide/ui/dom/appendTextAt` — reactive `{expr}` at a skeleton anchor comment, interleaved with element siblings.
297
- - `@abide/abide/ui/dom/appendSnippet` — `{snippet(args)}` interpolation in a marker-bounded range; re-reads args reactively.
298
- - `@abide/abide/ui/dom/appendStatic` — a static (non-reactive) text node, created or claimed from SSR.
299
- - `@abide/abide/ui/dom/cloneStatic` — append a fully-static subtree by template clone (create) or claim (hydrate).
300
- - `@abide/abide/ui/dom/skeleton` — realize a compiled skeleton under a parent; returns element holes (by path) and anchors (in order).
301
- - `@abide/abide/ui/dom/anchorCursor` — position a skeleton-anchored control-flow block; parks the claim cursor (hydrate) or returns a create reference.
302
- - `@abide/abide/ui/dom/mountSlot` — mount a component's `<slot>` content in a marker-bounded range (rendered once).
303
- - `@abide/abide/ui/dom/outlet` — a layout's `<slot/>` outlet (comment-bounded boundary the router fills with the next layer).
304
- - `@abide/abide/ui/dom/attr` — bind an element attribute to `read()` (boolean present/absent semantics).
305
- - `@abide/abide/ui/dom/on` — attach an event listener pinned to its ownership scope.
306
- - `@abide/abide/ui/dom/attach` — run a build-time attachment against an element with optional teardown.
307
- - `@abide/abide/ui/dom/each` — keyed list binding; reconciles by key, reorders/re-keys in place; claims SSR rows on hydrate.
308
- - `@abide/abide/ui/dom/eachAsync` — async keyed list over an `AsyncIterable`; rows append/reconcile as the iterator yields.
309
- - `@abide/abide/ui/dom/when` — conditional binding; swaps the range on truthy↔falsy flips; adopts the server branch on hydrate.
310
- - `@abide/abide/ui/dom/awaitBlock` — async binding (pending/then/catch); patches a re-settling `then` value in place rather than rebuilding.
311
- - `@abide/abide/ui/dom/tryBlock` — synchronous error boundary; builds the catch subtree if the guarded one throws (rendered once).
312
- - `@abide/abide/ui/dom/switchBlock` — multi-branch binding; picks the first matching case, else default; swaps on subject change.
313
- - `@abide/abide/ui/dom/applyResolved` — bundle-side consumer of an SSR stream chunk; routes cache-seed/resolve frames into await boundaries.
314
- - `@abide/abide/ui/runtime/escapeKey` — escape one object key into a JSON-Pointer token (`~`→`~0`, `/`→`~1`).
315
- - `@abide/abide/ui/runtime/nextBlockId` — the next block id in the current render pass (await/try blocks, document order).
316
- - `@abide/abide/ui/runtime/enterRenderPass` — mark entry into a render/mount pass; depth 0 resets the block-id counter.
317
- - `@abide/abide/ui/runtime/exitRenderPass` — mark exit from a render/mount pass, unwinding the depth.
226
+ - `abide/ui/effect` — abide's reaction primitive: run `fn` now, re-run on dep change, optional (async) teardown; returns a dispose. The runtime target the compiler emits for `scope().effect`.
227
+ - `abide/ui/enterScope` — open a fresh lexical scope for an SSR render, returning the previous to restore.
228
+ - `abide/ui/exitScope` — restore the scope `enterScope` saved.
229
+ - `abide/ui/remoteProxy` — the client-side RPC substitute the bundler emits per export: fetches, decodes by Content-Type, throws `HttpError`; a durable (`outbox`) RPC parks an unreachable request for replay as a side-effect. Exports `DurableOptions`.
230
+ - `abide/ui/socketProxy` — the client-side socket substitute: subscribe/publish over the multiplexed ws channel, same `Socket` shape as the server.
231
+ - `abide/ui/startClient` — the official client entry: read `__SSR__`, seed the warm cache store, install the mount base, start the router.
232
+ - `abide/ui/router` — the History-API client router: match the path, import the page chunk + its layout chain, mount the chain into nested `<slot/>` outlets.
233
+ - `abide/ui/renderToStream` — out-of-order SSR streaming: shell first, then one `<abide-resolve>` fragment per streaming `{#await}` as it settles.
234
+ - `abide/ui/runtime/escapeKey` — escape one object key into a JSON-Pointer token (`~0`/`~1`) so a key with `/` or `~` survives a `/`-joined path.
235
+ - `abide/ui/runtime/nextBlockId` — the next block id in the current render pass (document order), so page and child ids don't collide.
236
+ - `abide/ui/runtime/enterRenderPass` — mark entry into a render/mount; the outermost resets the block-id counter.
237
+ - `abide/ui/runtime/exitRenderPass` — mark exit from a render/mount, unwinding the depth.
238
+ - `abide/ui/dom/mount` — mount a top-level page/layout into a host element under an ownership scope; returns a disposer.
239
+ - `abide/ui/dom/mountChild` — mount a child component as a marker-bounded range (no wrapper); hot path keeps the range for in-place HMR re-fill.
240
+ - `abide/ui/dom/mountSlot` — mount a component's `<slot>` content as a marker range (parent `$children` or fallback); renders once.
241
+ - `abide/ui/dom/outlet` — a layout's `<slot/>` outlet as an empty comment boundary the router fills with the next chain layer.
242
+ - `abide/ui/dom/mergeProps` — compose a child's props from ordered layers (explicit thunks, spreads, the `$children` slot), last layer wins per key.
243
+ - `abide/ui/dom/spreadProps` — wrap a `{...source}` props layer so each key resolves to a live value thunk; the source thunk re-reads reactively.
244
+ - `abide/ui/dom/restProps` — the unconsumed props of a `const { a, ...rest } = props()` as a live object (thunks unwrapped; plain page params returned as-is).
245
+ - `abide/ui/dom/spreadAttrs` — spread an object's keys onto a native element (`<div {...rest}>`): keys enumerated once, each value live; `on*` keys attach as listeners.
246
+ - `abide/ui/dom/attr` — bind one element attribute to `read()` (boolean present/absent, else stringified); one effect per attribute.
247
+ - `abide/ui/dom/on` — attach an event listener scoped to the owning component (the `onclick={…}` runtime target), pinned to its `scope()`.
248
+ - `abide/ui/dom/attach` — run a node-lifetime attachment at build time with optional teardown (the dual of `on`); may be async.
249
+ - `abide/ui/dom/text` — a text node tracking `read()` via one effect (plain text, `String()`-coerced).
250
+ - `abide/ui/dom/appendText` — a reactive `{expr}` interpolation (escaped text, snippet, or `html`); claims and splits merged SSR text on hydrate.
251
+ - `abide/ui/dom/appendTextAt` — a reactive `{expr}` mounted at a skeleton anchor, for text interleaved with element siblings.
252
+ - `abide/ui/dom/appendStatic` — a static (non-reactive) text node, claimed/split from SSR text on hydrate.
253
+ - `abide/ui/dom/appendSnippet` — a `{snippet(args)}` interpolation mounted in a marker-bounded range; reactive in its args.
254
+ - `abide/ui/dom/cloneStatic` — append a fully-static subtree from a compiler skeleton string (byte-identical to SSR markup).
255
+ - `abide/ui/dom/skeleton` — realize a compiled skeleton under a parent and return its element holes (pre-order) + anchor holes (document order); the parser is the tree-builder, so foreign content lands namespaced.
256
+ - `abide/ui/dom/anchorCursor` — position a skeleton-anchored control-flow block or slot relative to its `<!--a-->` marker.
257
+ - `abide/ui/dom/readCall` — a guarded method call on a reactive-doc read, so a nullish throw names the authored scope path instead of the desugared call.
258
+ - `abide/ui/dom/hydrate` — adopt server-rendered DOM: run `build` with a claim cursor so dom helpers take existing nodes; returns a disposer.
259
+ - `abide/ui/dom/each` — the keyed `{#for by key}` runtime: each row a marker-bounded range reconciled by key.
260
+ - `abide/ui/dom/eachAsync` — the async `{#for await by key}` runtime over an `AsyncIterable`; SSR renders no rows.
261
+ - `abide/ui/dom/when` — the `{#if}` (+ `{:else}`) runtime: a branch range swapped on a truthy↔falsy flip.
262
+ - `abide/ui/dom/awaitBlock` — the `{#await}` runtime: pending resolved/error branch on settle, each a range.
263
+ - `abide/ui/dom/tryBlock` — the `{#try}` runtime: a synchronous error boundary; build throw `{:catch}` branch, no catch re-throws.
264
+ - `abide/ui/dom/switchBlock` — the `{#switch}` runtime: first `===`-matching case (or default), each a range.
265
+ - `abide/ui/dom/applyResolved` — bundle-side consumer of an SSR stream chunk (streaming SPA nav, socket-delivered SSR): seed cache partitions and swap await boundaries.
318
266
 
319
267
  ## Build / tooling
320
268
 
321
269
  ### Building — @documentation building
322
270
 
323
- - `@abide/abide/build` — `build(opts)`: build the client bundle into `dist/_app` (optional gzip); returns success.
324
- - `@abide/abide/compile` — `compile(opts)`: build a standalone server executable (runs the client build first); returns the binary path.
271
+ - `abide/build` — build the client bundle into `dist/_app` (Bun.build + the `.abide` loader, virtual-module resolver, optional Tailwind; `compress` writes `.gz` siblings).
272
+ - `abide/compile` — produce a standalone Bun server executable (runs `build` first to embed assets); returns the binary path.
325
273
 
326
274
  ### Plumbing — @documentation plumbing
327
275
 
328
- - `@abide/abide/preload` — the Bun plugin stack (`abideUiPlugin` + resolver) registered via `bunfig.toml` preload.
329
- - `@abide/abide/resolver-plugin` — `abideResolverPlugin({ cwd, embedAssets, target })`: resolves the `abide:*` virtual modules and convention dirs.
330
- - `@abide/abide/ui-plugin` — `abideUiPlugin`: Bun plugin that compiles `.abide` single-file components (with scoped CSS) to ES modules.
331
- - `@abide/abide/tsconfig` — `tsconfig.app.json`: the app TypeScript config (ESNext, DOM lib, Bun types, strict, no-emit).
276
+ - `abide/preload` — the Bun preload entry that installs the `.abide` loader + resolver for `abide run` and `bun test`.
277
+ - `abide/resolver-plugin` — the Bun plugin wiring every build-time virtual import (`abide:rpc` / `abide:sockets` / `abide:pages` / `abide:prompts` / `abide:app` …).
278
+ - `abide/ui-plugin` — the Bun plugin that compiles each `.abide` single-file component (and `layout.abide`) to its ES module.
279
+ - `abide/tsconfig` — the shareable base `tsconfig` (`tsconfig.app.json`) projects extend.
332
280
 
333
- ## Desktop bundle
281
+ ## Desktop bundle — abide/bundle/*
334
282
 
335
283
  ### Bundle — @documentation bundle
336
284
 
337
- - `@abide/abide/server/appDataDir` — `appDataDir()`: the running bundle's per-user data directory (keyed by injected program name; cwd-independent). Overridable via `ABIDE_DATA_DIR`.
338
- - `@abide/abide/bundle/BundleWindow` — type of `src/bundle/window.ts`: window config (title, size, menu, `configSchema`).
339
- - `@abide/abide/bundle/BundleMenu` — type of a top-level menu-bar menu (label + items).
340
- - `@abide/abide/bundle/BundleMenuItem` — type of one menu entry (divider, emit event, or navigate URL).
341
- - `@abide/abide/bundle/onMenu` — `onMenu(name?, handler)`: subscribe to bundle menu clicks (`abide:menu` CustomEvent).
342
- - `@abide/abide/bundle/bundled` — `bundled()`: true when running inside the abide desktop bundle.
285
+ - `abide/bundle/BundleWindow` — the optional `src/bundle/window.ts` default-export shape (title, size, menus); baked into the launcher.
286
+ - `abide/bundle/BundleMenu` — a top-level menu inserted into the macOS menu bar (`label` + `items`).
287
+ - `abide/bundle/BundleMenuItem` — one menu entry: a divider or a clickable item that dispatches an `abide:menu` CustomEvent into the page.
288
+ - `abide/bundle/onMenu` — subscribe to bundle menu clicks (catch-all or per-name), returning an unsubscribe.
289
+ - `abide/bundle/bundled` — `true` when the code runs inside the abide desktop bundle; isomorphic, detected per side.
343
290
 
344
- ## MCP
291
+ ## MCP — abide/mcp/*
345
292
 
346
293
  ### MCP — @documentation mcp
347
294
 
348
- - `@abide/abide/mcp/createMcpServer` — `createMcpServer(opts)`: MCP server bound to the project registry; derives tools from verbs/sockets, handles auth.
349
-
350
- ### Prompts — @documentation plumbing
351
-
352
- - `@abide/abide/server/prompts/definePrompt` — `definePrompt(name, opts)`: build a `Prompt` from an `src/mcp/prompts/*.md` file (the bundler emits the call).
353
- - `@abide/abide/server/prompts/renderPromptTemplate` — `renderPromptTemplate(template, args)`: substitute `{{name}}` placeholders (missing → empty string).
295
+ - `abide/mcp/createMcpServer` — construct the MCP server bound to the project's RPC registry; its `handle(request)` backs `/__abide/mcp`. Framework-internal (the `abide:mcp` virtual default-constructs it).
354
296
 
355
- ## Testing
297
+ ## Testing — abide/test/*
356
298
 
357
299
  ### Testing — @documentation testing
358
300
 
359
- - `@abide/abide/test/createTestApp` — `createTestApp(opts)`: in-memory test server with a typed `app.rpc.<verb>` / `app.sockets.<name>` surface.
301
+ - `abide/test/createTestApp` — an in-process test app exposing `app.rpc.<rpc>` / `app.sockets.<name>` typed against the project's generated surface (no network, no imports).
360
302
 
361
303
  ### Plumbing — @documentation plumbing
362
304
 
363
- - `@abide/abide/test/createScriptedSurface` — `createScriptedSurface(tools)`: a scripted `AgentSurface` for engine tests (records calls, stubs results).
364
- - `@abide/abide/test/assertAgentFrameConformance` — `assertAgentFrameConformance(stream)`: assert engine frame-stream invariants (done frame, tool use/result pairing).
305
+ - `abide/test/createScriptedSurface` — a scripted `AgentSurface` for engine tests: declarative tool stubs in, an MCP surface out, every `call` recorded.
306
+ - `abide/test/assertAgentFrameConformance` — collect an engine's frame stream and assert the neutral `AgentFrame` contract (one terminal `done`, every `tool_use` answered, ).
365
307
 
366
308
  ## Generated machine surfaces
367
309
 
368
310
  Runtime routes the framework serves:
369
311
 
370
- | Route | Serves |
371
- | -------------------- | ----------------------------------------------------------------------------------------- |
372
- | `/openapi.json` | OpenAPI document for the `/rpc/*` HTTP surface. |
373
- | `/__abide/mcp` | MCP endpoint (POST) routed to `createMcpServer`. |
374
- | `/__abide/health` | Unauthenticated health payload (`reachable` + `src/app.ts` `health` fields). |
375
- | `/__abide/sockets` | WebSocket multiplex for every declared socket; `/<name>` adds the HTTP tail/publish face. |
376
- | `/__abide/cli` | CLI download (`/<platform>`): gzipped tarball with the thin binary + `.env`. |
377
- | `/__abide/hot/<id>` | Dev-only hot-module endpoint for edited `.abide` components. |
378
- | `/__abide/identity` | Server identity (app name + version) for CLI connectivity probes. |
379
- | `/__abide/inspector` | Inspector UI, gated by `ABIDE_ENABLE_INSPECTOR` and the installed package. |
312
+ | Route | Serves |
313
+ | --- | --- |
314
+ | `/openapi.json` | the OpenAPI document projected from every RPC schema |
315
+ | `/__abide/mcp` | the MCP endpoint (tools/prompts from the RPC + prompt registries) |
316
+ | `/__abide/health` | the health payload (`reachable` + the app `health()` hook's fields) |
317
+ | `/__abide/sockets` | the multiplexed WebSocket hub (and `/__abide/sockets/<name>` HTTP face) |
318
+ | `/__abide/cli` | the CLI manifest the `abide cli` binary reads |
319
+ | `/__abide/identity` | the server identity the bundle/CLI client probes |
320
+ | `/__abide/inspector` | the inspector surface (when `ABIDE_ENABLE_INSPECTOR=true`) |
321
+ | `/__abide/hot` | the dev hot-reload channel |
380
322
 
381
323
  ## Environment variables
382
324
 
383
- | Variable | Effect |
384
- | ----------------------------- | --------------------------------------------------------------------------------------------- |
385
- | `PORT` | Port the server binds (scans upward from the default if taken). |
386
- | `APP_URL` | Request origin the CLI download writes into the downloaded `.env` (`ABIDE_APP_URL`). |
387
- | `ABIDE_APP_URL` | Server URL baked into a CLI binary at build time; runtime-overridable. |
388
- | `ABIDE_APP_TOKEN` | Bearer token written into the CLI download's `.env` when the request carries `Authorization`. |
389
- | `ABIDE_CLIENT_TIMEOUT` | Client-side RPC fetch timeout in ms (1–600000); shipped via `__SSR__`. |
390
- | `ABIDE_MAX_REQUEST_BODY_SIZE` | Server-wide request body ceiling in bytes. |
391
- | `ABIDE_IDLE_TIMEOUT` | Per-connection idle timeout in seconds (0–255, default 10); streaming opts out. |
392
- | `ABIDE_REACHABLE_TIMEOUT` | `reachable()` probe timeout in ms (100–60000, default 3000). |
393
- | `ABIDE_REACHABLE_TTL` | `reachable()` poll cadence / freshness in ms (1000–600000, default 30000). |
394
- | `ABIDE_DATA_DIR` | Override for the app data directory (used as-is, no program name appended). |
395
- | `ABIDE_LOG_FORMAT` | `json` for one JSON object per line; default is tab-separated. |
396
- | `DEBUG` | Diagnostic channel gate (e.g. `DEBUG=abide:*` or `abide:build`). |
397
- | `ABIDE_ENABLE_INSPECTOR` | `true` activates the optional inspector. |
398
- | `ABIDE_INSPECT` | Enable DevTools inspection in a desktop bundle's webview. |
325
+ | Variable | Effect |
326
+ | --- | --- |
327
+ | `PORT` | server listen port (unset → abide scans from a default) |
328
+ | `APP_URL` | public origin; its pathname becomes the app's mount base (sub-path hosting) |
329
+ | `ABIDE_APP_URL` | runtime URL of the server the bundle/CLI client connects to |
330
+ | `ABIDE_APP_TOKEN` | bearer token the bundle/CLI client sends to an authenticated remote server |
331
+ | `ABIDE_CLIENT_TIMEOUT` | client-side per-call fetch timeout (ms); a breach surfaces as a 504 `HttpError` |
332
+ | `ABIDE_MAX_REQUEST_BODY_SIZE` | default inbound request-body byte cap (a per-RPC `maxBodySize` overrides) |
333
+ | `ABIDE_IDLE_TIMEOUT` | Bun per-connection idle timeout (seconds) |
334
+ | `ABIDE_DATA_DIR` | override the per-user app data dir on every platform (used as-is) |
335
+ | `ABIDE_REACHABLE_TTL` | `reachable()` poll cadence / freshness window (ms) |
336
+ | `ABIDE_REACHABLE_TIMEOUT` | `reachable()` per-HEAD probe bound (ms) |
337
+ | `ABIDE_LOG_FORMAT` | `json` switches the logger from tsv to one JSON object per line |
338
+ | `DEBUG` | enable DEBUG-gated diagnostic log channels (npm-debug conventions, e.g. `abide`, `abide:*`) |
339
+ | `ABIDE_ENABLE_INSPECTOR` | inject the `InspectorContext` into `@abide/inspector` (exposes all traffic; trusted use only) |
340
+ | `ABIDE_INSPECT` | enable the desktop bundle's right-click → Inspect web-inspector (off in release bundles) |
399
341
 
400
342
  ---
401
343
 
402
- This map mirrors `package.json` `exports`. After adding or renaming an export,
403
- run `bun run packages/abide/scripts/readmeSurfaces.ts` and update the matching
404
- bullet here.
344
+ This map mirrors the `exports` map in `package.json`. After adding or renaming an export, run `bun run packages/abide/scripts/readmeSurfaces.ts` and update the matching section here.