@abide/abide 0.42.0 → 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 (146) hide show
  1. package/AGENTS.md +234 -300
  2. package/CHANGELOG.md +34 -0
  3. package/README.md +50 -93
  4. package/package.json +3 -2
  5. package/src/abideResolverPlugin.ts +104 -14
  6. package/src/buildCli.ts +1 -1
  7. package/src/discoveryEntry.ts +3 -3
  8. package/src/lib/bundle/BundleMenuItem.ts +1 -1
  9. package/src/lib/cli/createClient.ts +12 -12
  10. package/src/lib/cli/dispatchCommand.ts +1 -1
  11. package/src/lib/cli/printSessionStatus.ts +1 -1
  12. package/src/lib/cli/resolveCliTarget.ts +1 -1
  13. package/src/lib/cli/runCli.ts +1 -1
  14. package/src/lib/cli/types/CliManifest.ts +1 -1
  15. package/src/lib/cli/types/CliManifestEntry.ts +2 -2
  16. package/src/lib/mcp/annotationsForMethod.ts +5 -5
  17. package/src/lib/mcp/createMcpServer.ts +3 -3
  18. package/src/lib/mcp/mcpSurface.ts +14 -14
  19. package/src/lib/mcp/types/McpServerOptions.ts +1 -1
  20. package/src/lib/server/DELETE.ts +4 -4
  21. package/src/lib/server/GET.ts +4 -4
  22. package/src/lib/server/HEAD.ts +4 -4
  23. package/src/lib/server/PATCH.ts +4 -4
  24. package/src/lib/server/POST.ts +4 -4
  25. package/src/lib/server/PUT.ts +4 -4
  26. package/src/lib/server/agent.ts +4 -4
  27. package/src/lib/server/env.ts +1 -1
  28. package/src/lib/server/error.ts +49 -7
  29. package/src/lib/server/json.ts +1 -1
  30. package/src/lib/server/rpc/buildErrorConstructors.ts +19 -0
  31. package/src/lib/server/rpc/{defineVerb.ts → defineRpc.ts} +52 -34
  32. package/src/lib/server/rpc/{dispatchVerbInProcess.ts → dispatchRpcInProcess.ts} +3 -3
  33. package/src/lib/server/rpc/fieldErrorsFromIssues.ts +23 -0
  34. package/src/lib/server/rpc/{findVerbByCommandName.ts → findRpcByCommandName.ts} +5 -5
  35. package/src/lib/server/rpc/parseArgs.ts +6 -6
  36. package/src/lib/server/rpc/readBodyWithinLimit.ts +2 -2
  37. package/src/lib/server/rpc/registerRpc.ts +6 -0
  38. package/src/lib/server/rpc/{verbRegistry.ts → rpcRegistry.ts} +4 -4
  39. package/src/lib/server/rpc/{runWithVerbTimeout.ts → runWithRpcTimeout.ts} +4 -4
  40. package/src/lib/server/rpc/types/RemoteHandler.ts +9 -3
  41. package/src/lib/server/rpc/types/{VerbHelper.ts → RpcHelper.ts} +57 -23
  42. package/src/lib/server/rpc/types/{VerbRegistryEntry.ts → RpcRegistryEntry.ts} +3 -3
  43. package/src/lib/server/rpc/types/TypedResponse.ts +2 -2
  44. package/src/lib/server/rpc/unprocessed.ts +6 -7
  45. package/src/lib/server/rpc/validationError.ts +17 -0
  46. package/src/lib/server/runtime/buildInspectorSurface.ts +7 -7
  47. package/src/lib/server/runtime/buildOpenApiSpec.ts +6 -6
  48. package/src/lib/server/runtime/createRouteDispatcher.ts +7 -7
  49. package/src/lib/server/runtime/createServer.ts +4 -4
  50. package/src/lib/server/runtime/crossOriginForbidden.ts +1 -1
  51. package/src/lib/server/runtime/crossOriginGate.ts +4 -4
  52. package/src/lib/server/runtime/logExposedSurfaces.ts +4 -4
  53. package/src/lib/server/runtime/registryManifests.ts +2 -2
  54. package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
  55. package/src/lib/server/runtime/types/InspectorRpc.ts +27 -0
  56. package/src/lib/server/runtime/types/InspectorSurface.ts +4 -4
  57. package/src/lib/server/runtime/types/RequestStore.ts +1 -1
  58. package/src/lib/server/runtime/warnUnguardedMcp.ts +2 -2
  59. package/src/lib/server/sockets/createSocketDispatcher.ts +15 -14
  60. package/src/lib/server/sockets/types/SocketOperation.ts +2 -2
  61. package/src/lib/shared/HttpError.ts +15 -1
  62. package/src/lib/shared/UNREACHABLE_STATUSES.ts +13 -0
  63. package/src/lib/shared/buildRpcRequest.ts +5 -5
  64. package/src/lib/shared/cache.ts +50 -168
  65. package/src/lib/shared/carriesBodyArgs.ts +4 -4
  66. package/src/lib/shared/createCacheStore.ts +12 -0
  67. package/src/lib/shared/createRemoteFunction.ts +11 -6
  68. package/src/lib/shared/decodeResponse.ts +2 -2
  69. package/src/lib/shared/detectRpcMethod.ts +17 -0
  70. package/src/lib/shared/emitLogRecord.ts +3 -3
  71. package/src/lib/shared/extraForwardHeaders.ts +1 -1
  72. package/src/lib/shared/findExportCallSite.ts +30 -273
  73. package/src/lib/shared/forwardHeaders.ts +2 -2
  74. package/src/lib/shared/httpErrorFor.ts +26 -0
  75. package/src/lib/shared/isAsciiWhitespace.ts +5 -0
  76. package/src/lib/shared/isIdentPart.ts +9 -0
  77. package/src/lib/shared/isIdentStart.ts +8 -0
  78. package/src/lib/shared/isReadOnlyMethod.ts +4 -4
  79. package/src/lib/shared/keyForRemoteCall.ts +4 -4
  80. package/src/lib/shared/log.ts +1 -1
  81. package/src/lib/shared/outboxProbeSlot.ts +20 -0
  82. package/src/lib/shared/pending.ts +4 -1
  83. package/src/lib/shared/prepareRpcModule.ts +96 -14
  84. package/src/lib/shared/probeRegistries.ts +17 -4
  85. package/src/lib/shared/queryStringFromArgs.ts +1 -1
  86. package/src/lib/shared/remoteMetaStore.ts +2 -2
  87. package/src/lib/shared/resolveClientFlags.ts +1 -1
  88. package/src/lib/shared/skipNonCode.ts +230 -0
  89. package/src/lib/shared/streamResponse.ts +9 -6
  90. package/src/lib/shared/stripImport.ts +3 -3
  91. package/src/lib/shared/types/CacheEntry.ts +2 -4
  92. package/src/lib/shared/types/CacheOnContext.ts +1 -11
  93. package/src/lib/shared/types/CacheStore.ts +13 -7
  94. package/src/lib/shared/types/ClientFlags.ts +1 -1
  95. package/src/lib/shared/types/ErrorConstructors.ts +17 -0
  96. package/src/lib/shared/types/ErrorDescriptor.ts +10 -0
  97. package/src/lib/shared/types/ErrorSpec.ts +11 -0
  98. package/src/lib/shared/types/HttpMethod.ts +1 -0
  99. package/src/lib/shared/types/Outbox.ts +9 -0
  100. package/src/lib/shared/types/OutboxEntry.ts +27 -0
  101. package/src/lib/shared/types/RawRemoteFunction.ts +2 -2
  102. package/src/lib/shared/types/RemoteCallable.ts +2 -2
  103. package/src/lib/shared/types/RemoteFunction.ts +21 -6
  104. package/src/lib/shared/types/RequestScopeInfo.ts +1 -1
  105. package/src/lib/shared/types/RpcErrorGuard.ts +40 -0
  106. package/src/lib/shared/types/RpcInvoker.ts +1 -1
  107. package/src/lib/shared/types/StandardSchemaV1.ts +1 -1
  108. package/src/lib/shared/types/ValidationErrorData.ts +20 -0
  109. package/src/lib/shared/url.ts +3 -3
  110. package/src/lib/shared/writeRpcDts.ts +7 -7
  111. package/src/lib/shared/writeTestRpcDts.ts +3 -3
  112. package/src/lib/test/createTestApp.ts +10 -10
  113. package/src/lib/ui/compile/awaitPlan.ts +48 -0
  114. package/src/lib/ui/compile/compileShadow.ts +75 -21
  115. package/src/lib/ui/compile/desugarSignals.ts +42 -6
  116. package/src/lib/ui/compile/generateBuild.ts +43 -60
  117. package/src/lib/ui/compile/generateSSR.ts +84 -54
  118. package/src/lib/ui/compile/ifPlan.ts +30 -0
  119. package/src/lib/ui/compile/parseTemplate.ts +41 -11
  120. package/src/lib/ui/compile/stripEffects.ts +17 -9
  121. package/src/lib/ui/compile/switchPlan.ts +22 -0
  122. package/src/lib/ui/compile/tryPlan.ts +29 -0
  123. package/src/lib/ui/compile/types/TemplateAttr.ts +8 -2
  124. package/src/lib/ui/compile/types/TemplateNode.ts +6 -2
  125. package/src/lib/ui/createScope.ts +20 -2
  126. package/src/lib/ui/dom/disposeRange.ts +6 -10
  127. package/src/lib/ui/dom/hydrate.ts +2 -5
  128. package/src/lib/ui/dom/mount.ts +1 -2
  129. package/src/lib/ui/dom/withScope.ts +6 -9
  130. package/src/lib/ui/effect.ts +4 -2
  131. package/src/lib/ui/navigate.ts +40 -13
  132. package/src/lib/ui/outbox.ts +30 -110
  133. package/src/lib/ui/remoteProxy.ts +160 -10
  134. package/src/lib/ui/router.ts +3 -3
  135. package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +281 -0
  136. package/src/lib/ui/rpcOutbox/outboxRegistry.ts +69 -0
  137. package/src/lib/ui/socketChannel.ts +7 -1
  138. package/src/lib/ui/types/Scope.ts +20 -5
  139. package/template/CLAUDE.md +1 -1
  140. package/template/src/server/rpc/getHello.ts +3 -3
  141. package/template/test/app.test.ts +2 -2
  142. package/src/lib/server/rpc/registerVerb.ts +0 -6
  143. package/src/lib/server/runtime/types/InspectorVerb.ts +0 -27
  144. package/src/lib/shared/detectVerbMethod.ts +0 -17
  145. package/src/lib/shared/types/HttpVerb.ts +0 -1
  146. package/src/lib/ui/types/Outbox.ts +0 -14
package/AGENTS.md CHANGED
@@ -1,410 +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** — `{#…}` blocks (each head reads as the JS clause it lowers to):
147
-
148
- | Form | Meaning |
149
- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
150
- | `{#if c}` / `{:else if c}` / `{:else}` / `{/if}` | Conditional, source-order, re-evaluated reactively. |
151
- | `{#for x of list}` / `{#for x, i of list by x.id}` / `{/for}` | Keyed list (`for…of`); `, i` index reactive; `by` key reconciles rows in place; key defaults to item. |
152
- | `{#for await x of asyncIter by x.id}` … `{:catch e}` / `{/for}` | Async keyed list (`for await…of`); rows append as the iterator yields. |
153
- | `{#await p}` / `{:then v}` / `{:catch e}` / `{:finally}` / `{/await}` | Promise branches; pending content (before `{:then}`) streams. |
154
- | `{#await p then v}` / `{/await}` | Blocking: no pending, resolved inline (SSR settles before the first flush). |
155
- | `{#switch s}` / `{:case v}` / `{:default}` / `{/switch}` | First strict (`===`) match wins. |
156
- | `{#try}` / `{:catch e}` / `{:finally}` / `{/try}` | Synchronous render error boundary. |
157
-
158
- **Reusable markup** — `<template>`:
159
-
160
- | Form | Meaning |
161
- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
162
- | `<template name="row" args={item}>` … `{row(item)}` | Named template (snippet) definition + call. A `<template>` with a control-flow attribute (`if`/`each`/…) is a compile error — use the `{#…}` block. |
163
-
164
- **Components & slots** — a capitalised tag (`<Card title={x}>`) mounts a child;
165
- attributes become props, children fill the child's `<slot>` (with fallback when
166
- empty). `<style>` is scoped to the component and its children. Component files
167
- end in `.abide`. A block's binding names (`{:then v}`, `{#for x of …}`) lexically
168
- shadow same-named component state inside the block.
169
-
170
- ## Server surface — abide/server/\* (server-only)
171
-
172
- ### RPC — @documentation rpc
173
-
174
- - `@abide/abide/server/GET` — declare a `GET` verb; args from the query string. Schema auto-exposes CLI + read-MCP.
175
- - `@abide/abide/server/POST` — declare a `POST` verb; args from JSON/form/multipart body (body wins on collision).
176
- - `@abide/abide/server/PUT` — declare a `PUT` verb (body args; mutating).
177
- - `@abide/abide/server/PATCH` — declare a `PATCH` verb (body args; mutating).
178
- - `@abide/abide/server/DELETE` — declare a `DELETE` verb (query args; mutating).
179
- - `@abide/abide/server/HEAD` — declare a `HEAD` verb (query args; read-only, headers-only response).
180
-
181
- ### Response — @documentation response
182
-
183
- - `@abide/abide/server/json` — `json(data, init?)`: JSON `TypedResponse` (`Cache-Control: no-store`); `undefined` → 204.
184
- - `@abide/abide/server/jsonl` — `jsonl(asyncIterable, init?)`: JSONL stream, one value per line; errors emit a `{"$error"}` final line.
185
- - `@abide/abide/server/sse` — `sse(asyncIterable, init?)`: Server-Sent Events with 15s keepalive comments; errors as an `error` event.
186
- - `@abide/abide/server/error` — `error(status, message?, init?)`: plain-text error `TypedResponse<never>` (narrows out of the success union).
187
- - `@abide/abide/server/redirect` — `redirect(url, status=302, init?)`: redirect accepting relative URLs; `TypedResponse<never>`.
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) |
188
94
 
189
- ### Request scope @documentation request-scope
95
+ **Control flow**mustache blocks (`{#…}` open, `{:…}` branch, `{/…}` close):
190
96
 
191
- - `@abide/abide/server/request` `request()`: the inbound `Request` from request-scoped ALS; throws outside scope.
192
- - `@abide/abide/server/cookies` `cookies()`: live `Bun.CookieMap`; writes flush to `Set-Cookie`; throws outside scope.
193
- - `@abide/abide/server/server` `server()`: the active `Bun.serve` instance (in-process shim for CLI/MCP/test dispatch).
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 })}` |
194
105
 
195
- ### 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/*
196
109
 
197
- - `@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.
198
118
 
199
119
  ### Sockets — @documentation sockets
200
120
 
201
- - `@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.
202
122
 
203
- ### 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
204
138
 
205
- - `@abide/abide/server/agent` — `agent(engine, messages)`: run an `AgentEngine` against the request's MCP surface; yields `AgentFrame`s (wrap in `jsonl`/`sse`). Exports the `AgentEngine` / `AgentSurface` / `NeutralMessage` / `AgentFrame` types; provider engines ship in `@abide/<provider>`.
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.
206
140
 
207
141
  ### Observability — @documentation observability
208
142
 
209
- - `@abide/abide/server/reachable` — `reachable(host)`: cached outbound reachability HEAD (first call probes, then polls every TTL); any HTTP response counts as up. Tuned by `ABIDE_REACHABLE_TTL` / `ABIDE_REACHABLE_TIMEOUT`.
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`.
210
152
 
211
153
  ### Plumbing — @documentation plumbing
212
154
 
213
- - `@abide/abide/server/AppModule` — type of `src/app.ts`: optional `forwardHeaders`, `init`, `handle`, `handleError`, `health` hooks.
214
- - `@abide/abide/server/InspectorContext` — type of the capability bundle the core injects into `@abide/inspector` when `ABIDE_ENABLE_INSPECTOR=true`.
215
- - `@abide/abide/server/rpc/defineVerb` — `defineVerb(method, url, handler, opts?)`: low-level builder the GET/POST/… helpers wrap.
216
- - `@abide/abide/server/sockets/defineSocket` — `defineSocket(name, opts?)`: server-side `Socket` construction (per-subscriber queue, shared retained tail).
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).
217
161
 
218
- ## Isomorphic surface — abide/shared/\* (same callable both sides)
162
+ ## Isomorphic surface — abide/shared/*
219
163
 
220
- ### Cache — @documentation cache
164
+ ### Responses — @documentation response
221
165
 
222
- - `@abide/abide/shared/cache` — `cache(fn, opts?)`: coalesce/memoize a remote verb or producer per store (request-scoped server, tab-scoped client). `opts`: `global` (process store), `ttl`, `swr`. Methods: `cache.invalidate`, `cache.on`, `cache.patch`. Warm SSR reads resolve synchronously on hydrate.
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.
223
168
 
224
- ### Templating — @documentation templating
169
+ ### Schema projection — @documentation rpc
225
170
 
226
- - `@abide/abide/shared/html` — `html` (tagged template or `html(str)`): mark a string as trusted raw HTML so `{expr}` inserts nodes verbatim (no auto-escape).
227
- - `@abide/abide/shared/snippet` — `snippet(payload)`: brand a snippet payload (client DOM builder / server HTML string) so `{expr}` mounts it.
171
+ - `abide/shared/withJsonSchema` — attach a `toJSONSchema()` projection to a Standard Schema whose library lacks one, feeding OpenAPI / MCP / CLI / the setup form.
228
172
 
229
- ### Response — @documentation response
173
+ ### Templating — @documentation templating
230
174
 
231
- - `@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.
232
177
 
233
- ### RPC — @documentation rpc
178
+ ### Cache — @documentation cache
234
179
 
235
- - `@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.
236
181
 
237
182
  ### Page — @documentation page
238
183
 
239
- - `@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.
240
185
 
241
- ### URL — @documentation url
186
+ ### Probes — @documentation probes
242
187
 
243
- - `@abide/abide/shared/url` — `url(path, ...args)`: base-correct, typed in-app URLs for RPCs (args query), page routes (`[name]` params), and assets; external URLs pass through untouched.
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`).
244
191
 
245
- ### Probes — @documentation probes
192
+ ### URL — @documentation url
246
193
 
247
- - `@abide/abide/shared/pending` — `pending(selector?, args?)`: reactive any/specific/exact/tagged call in flight, or a stream awaiting its first frame.
248
- - `@abide/abide/shared/refreshing` — `refreshing(selector?, args?)`: reactive — a value held while fresh data is in flight (SWR refetch / reconnect with retained value).
249
- - `@abide/abide/shared/online` — `online()`: reactive connectivity (client `navigator.onLine`; server reads the caller's reported state); reports, never acts.
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.
250
195
 
251
196
  ### Observability — @documentation observability
252
197
 
253
- - `@abide/abide/shared/health` — `health()`: reactive backend health (`reachable` + app fields) polled from `/__abide/health` only while read; SSR-seeded.
254
- - `@abide/abide/shared/log` — `log(...)` / `.warn` / `.error` / `.trace(label, fn)` / `.channel(name)`: request-scoped logger; TSV or JSON (`ABIDE_LOG_FORMAT`).
255
- - `@abide/abide/shared/trace` — `trace()`: the current request's W3C `traceparent`, or `undefined` outside request scope; isomorphic.
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.
256
201
 
257
202
  ### Plumbing — @documentation plumbing
258
203
 
259
- - `@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).
260
205
 
261
- ## UI surface — abide/ui/\* (client-only)
206
+ ## UI surface — abide/ui/* (client-only)
262
207
 
263
208
  ### Reactive state — @documentation reactive-state
264
209
 
265
- - `@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.
266
211
 
267
- ### Effect — @documentation effect
212
+ ### Streaming — @documentation tail
268
213
 
269
- - `@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.
270
215
 
271
- ### Tail — @documentation tail
216
+ ### Navigation — @documentation navigate
272
217
 
273
- - `@abide/abide/ui/tail` — `tail(subscribable, opts?)`: reactive consumer of a `Socket<T>` or `fn.stream` result (latest-wins, or windowed via `{ last }`); seeds from retention; no-op on the server.
218
+ - `abide/ui/navigate` — programmatic SPA navigation typed off the page routes; `NavigateOptions` carries `replace` / `keepScroll`. Delegates path-building to `url`.
274
219
 
275
- ### Navigate — @documentation navigate
220
+ ### Outbox — @documentation ui
276
221
 
277
- - `@abide/abide/ui/navigate` — `navigate(path, { replace?, keepScroll? })`: client navigation; writes history and re-mounts the page chain.
278
-
279
- ### UI — @documentation ui
280
-
281
- - `@abide/abide/ui/outbox` — `outbox({ key, send, store, online, onDrop })`: durable FIFO mutation queue for local-first writes; drains while online, retries on reconnect, at-least-once.
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`.
282
223
 
283
224
  ### Plumbing — @documentation plumbing
284
225
 
285
- - `@abide/abide/ui/enterScope` — `enterScope()`: open a fresh lexical scope for an SSR render; returns the previous to restore.
286
- - `@abide/abide/ui/exitScope` — `exitScope(previous)`: restore the scope `enterScope` saved.
287
- - `@abide/abide/ui/router` — `router(host, loaders, layoutLoaders, probe?)`: History-API router; diffs the layout/page chain on nav. Returns a disposer.
288
- - `@abide/abide/ui/startClient` — `startClient(routes, layoutRoutes, target)`: official client entry; reads `__SSR__`, seeds cache, starts the router.
289
- - `@abide/abide/ui/renderToStream` — `renderToStream(render)`: out-of-order SSR streaming; yields the shell, then one `<abide-resolve>` fragment per streaming await.
290
- - `@abide/abide/ui/remoteProxy` — `remoteProxy(method, url)`: client substitute for a verb handler; identical `RemoteFunction` shape so `cache()` matches both sides.
291
- - `@abide/abide/ui/socketProxy` — `socketProxy(name)`: client substitute for a server `Socket`; subscribes over the multiplexed ws channel.
292
- - `@abide/abide/ui/dom/mount` — mount a top-level page/layout into a host under an ownership scope; returns a disposer.
293
- - `@abide/abide/ui/dom/mountChild` — mount a child component as a marker-bounded range (no wrapper element); records the instance for hot reload.
294
- - `@abide/abide/ui/dom/mergeProps` — compose child props from ordered layers (thunks, spreads, slot) via a Proxy; last layer wins.
295
- - `@abide/abide/ui/dom/spreadProps` — wrap a `{...source}` spread so each key resolves to a live thunk.
296
- - `@abide/abide/ui/dom/restProps` — the unconsumed rest of a component's props as a live, enumerable object.
297
- - `@abide/abide/ui/dom/spreadAttrs` — spread an object onto a native element: `on*` keys as listeners, others as reactive attributes.
298
- - `@abide/abide/ui/dom/readCall` — guarded reactive-doc method call with scope-path error messages.
299
- - `@abide/abide/ui/dom/hydrate` — adopt server-rendered DOM, attaching listeners/effects in place with a claim cursor.
300
- - `@abide/abide/ui/dom/text` — a text node whose content tracks `read()`.
301
- - `@abide/abide/ui/dom/appendText` — reactive `{expr}` interpolation (escaped text, raw `html`, or snippet).
302
- - `@abide/abide/ui/dom/appendTextAt` — reactive `{expr}` at a skeleton anchor comment, interleaved with element siblings.
303
- - `@abide/abide/ui/dom/appendSnippet` — `{snippet(args)}` interpolation in a marker-bounded range; re-reads args reactively.
304
- - `@abide/abide/ui/dom/appendStatic` — a static (non-reactive) text node, created or claimed from SSR.
305
- - `@abide/abide/ui/dom/cloneStatic` — append a fully-static subtree by template clone (create) or claim (hydrate).
306
- - `@abide/abide/ui/dom/skeleton` — realize a compiled skeleton under a parent; returns element holes (by path) and anchors (in order).
307
- - `@abide/abide/ui/dom/anchorCursor` — position a skeleton-anchored control-flow block; parks the claim cursor (hydrate) or returns a create reference.
308
- - `@abide/abide/ui/dom/mountSlot` — mount a component's `<slot>` content in a marker-bounded range (rendered once).
309
- - `@abide/abide/ui/dom/outlet` — a layout's `<slot/>` outlet (comment-bounded boundary the router fills with the next layer).
310
- - `@abide/abide/ui/dom/attr` — bind an element attribute to `read()` (boolean present/absent semantics).
311
- - `@abide/abide/ui/dom/on` — attach an event listener pinned to its ownership scope.
312
- - `@abide/abide/ui/dom/attach` — run a build-time attachment against an element with optional teardown.
313
- - `@abide/abide/ui/dom/each` — keyed list binding; reconciles by key, reorders/re-keys in place; claims SSR rows on hydrate.
314
- - `@abide/abide/ui/dom/eachAsync` — async keyed list over an `AsyncIterable`; rows append/reconcile as the iterator yields.
315
- - `@abide/abide/ui/dom/when` — conditional binding; swaps the range on truthy↔falsy flips; adopts the server branch on hydrate.
316
- - `@abide/abide/ui/dom/awaitBlock` — async binding (pending/then/catch); patches a re-settling `then` value in place rather than rebuilding.
317
- - `@abide/abide/ui/dom/tryBlock` — synchronous error boundary; builds the catch subtree if the guarded one throws (rendered once).
318
- - `@abide/abide/ui/dom/switchBlock` — multi-branch binding; picks the first matching case, else default; swaps on subject change.
319
- - `@abide/abide/ui/dom/applyResolved` — bundle-side consumer of an SSR stream chunk; routes cache-seed/resolve frames into await boundaries.
320
- - `@abide/abide/ui/runtime/escapeKey` — escape one object key into a JSON-Pointer token (`~`→`~0`, `/`→`~1`).
321
- - `@abide/abide/ui/runtime/nextBlockId` — the next block id in the current render pass (await/try blocks, document order).
322
- - `@abide/abide/ui/runtime/enterRenderPass` — mark entry into a render/mount pass; depth 0 resets the block-id counter.
323
- - `@abide/abide/ui/runtime/exitRenderPass` — mark exit from a render/mount pass, unwinding the depth.
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.
324
266
 
325
267
  ## Build / tooling
326
268
 
327
269
  ### Building — @documentation building
328
270
 
329
- - `@abide/abide/build` — `build(opts)`: build the client bundle into `dist/_app` (optional gzip); returns success.
330
- - `@abide/abide/compile` — `compile(opts)`: build a standalone server executable (runs the client build first); returns the binary path.
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.
331
273
 
332
274
  ### Plumbing — @documentation plumbing
333
275
 
334
- - `@abide/abide/preload` — the Bun plugin stack (`abideUiPlugin` + resolver) registered via `bunfig.toml` preload.
335
- - `@abide/abide/resolver-plugin` — `abideResolverPlugin({ cwd, embedAssets, target })`: resolves the `abide:*` virtual modules and convention dirs.
336
- - `@abide/abide/ui-plugin` — `abideUiPlugin`: Bun plugin that compiles `.abide` single-file components (with scoped CSS) to ES modules.
337
- - `@abide/abide/tsconfig` — `tsconfig.app.json`: the app TypeScript config (ESNext, DOM lib, Bun types, strict, no-emit).
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.
338
280
 
339
- ## Desktop bundle
281
+ ## Desktop bundle — abide/bundle/*
340
282
 
341
283
  ### Bundle — @documentation bundle
342
284
 
343
- - `@abide/abide/server/appDataDir` — `appDataDir()`: the running bundle's per-user data directory (keyed by injected program name; cwd-independent). Overridable via `ABIDE_DATA_DIR`.
344
- - `@abide/abide/bundle/BundleWindow` — type of `src/bundle/window.ts`: window config (title, size, menu, `configSchema`).
345
- - `@abide/abide/bundle/BundleMenu` — type of a top-level menu-bar menu (label + items).
346
- - `@abide/abide/bundle/BundleMenuItem` — type of one menu entry (divider, emit event, or navigate URL).
347
- - `@abide/abide/bundle/onMenu` — `onMenu(name?, handler)`: subscribe to bundle menu clicks (`abide:menu` CustomEvent).
348
- - `@abide/abide/bundle/bundled` — `bundled()`: true when running inside the abide desktop bundle.
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.
349
290
 
350
- ## MCP
291
+ ## MCP — abide/mcp/*
351
292
 
352
293
  ### MCP — @documentation mcp
353
294
 
354
- - `@abide/abide/mcp/createMcpServer` — `createMcpServer(opts)`: MCP server bound to the project registry; derives tools from verbs/sockets, handles auth.
355
-
356
- ### Prompts — @documentation plumbing
357
-
358
- - `@abide/abide/server/prompts/definePrompt` — `definePrompt(name, opts)`: build a `Prompt` from an `src/mcp/prompts/*.md` file (the bundler emits the call).
359
- - `@abide/abide/server/prompts/renderPromptTemplate` — `renderPromptTemplate(template, args)`: substitute `{{name}}` placeholders (missing → empty string).
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).
360
296
 
361
- ## Testing
297
+ ## Testing — abide/test/*
362
298
 
363
299
  ### Testing — @documentation testing
364
300
 
365
- - `@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).
366
302
 
367
303
  ### Plumbing — @documentation plumbing
368
304
 
369
- - `@abide/abide/test/createScriptedSurface` — `createScriptedSurface(tools)`: a scripted `AgentSurface` for engine tests (records calls, stubs results).
370
- - `@abide/abide/test/assertAgentFrameConformance` — `assertAgentFrameConformance(stream)`: assert engine frame-stream invariants (done frame, tool use/result pairing).
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, ).
371
307
 
372
308
  ## Generated machine surfaces
373
309
 
374
310
  Runtime routes the framework serves:
375
311
 
376
- | Route | Serves |
377
- | -------------------- | ----------------------------------------------------------------------------------------- |
378
- | `/openapi.json` | OpenAPI document for the `/rpc/*` HTTP surface. |
379
- | `/__abide/mcp` | MCP endpoint (POST) routed to `createMcpServer`. |
380
- | `/__abide/health` | Unauthenticated health payload (`reachable` + `src/app.ts` `health` fields). |
381
- | `/__abide/sockets` | WebSocket multiplex for every declared socket; `/<name>` adds the HTTP tail/publish face. |
382
- | `/__abide/cli` | CLI download (`/<platform>`): gzipped tarball with the thin binary + `.env`. |
383
- | `/__abide/hot/<id>` | Dev-only hot-module endpoint for edited `.abide` components. |
384
- | `/__abide/identity` | Server identity (app name + version) for CLI connectivity probes. |
385
- | `/__abide/inspector` | Inspector UI, gated by `ABIDE_ENABLE_INSPECTOR` and the installed package. |
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 |
386
322
 
387
323
  ## Environment variables
388
324
 
389
- | Variable | Effect |
390
- | ----------------------------- | --------------------------------------------------------------------------------------------- |
391
- | `PORT` | Port the server binds (scans upward from the default if taken). |
392
- | `APP_URL` | Request origin the CLI download writes into the downloaded `.env` (`ABIDE_APP_URL`). |
393
- | `ABIDE_APP_URL` | Server URL baked into a CLI binary at build time; runtime-overridable. |
394
- | `ABIDE_APP_TOKEN` | Bearer token written into the CLI download's `.env` when the request carries `Authorization`. |
395
- | `ABIDE_CLIENT_TIMEOUT` | Client-side RPC fetch timeout in ms (1–600000); shipped via `__SSR__`. |
396
- | `ABIDE_MAX_REQUEST_BODY_SIZE` | Server-wide request body ceiling in bytes. |
397
- | `ABIDE_IDLE_TIMEOUT` | Per-connection idle timeout in seconds (0–255, default 10); streaming opts out. |
398
- | `ABIDE_REACHABLE_TIMEOUT` | `reachable()` probe timeout in ms (100–60000, default 3000). |
399
- | `ABIDE_REACHABLE_TTL` | `reachable()` poll cadence / freshness in ms (1000–600000, default 30000). |
400
- | `ABIDE_DATA_DIR` | Override for the app data directory (used as-is, no program name appended). |
401
- | `ABIDE_LOG_FORMAT` | `json` for one JSON object per line; default is tab-separated. |
402
- | `DEBUG` | Diagnostic channel gate (e.g. `DEBUG=abide:*` or `abide:build`). |
403
- | `ABIDE_ENABLE_INSPECTOR` | `true` activates the optional inspector. |
404
- | `ABIDE_INSPECT` | Enable DevTools inspection in a desktop bundle's webview. |
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) |
405
341
 
406
342
  ---
407
343
 
408
- This map mirrors `package.json` `exports`. After adding or renaming an export,
409
- run `bun run packages/abide/scripts/readmeSurfaces.ts` and update the matching
410
- bullet here.
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.