@abide/abide 0.43.0 → 0.44.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 (160) hide show
  1. package/AGENTS.md +293 -219
  2. package/CHANGELOG.md +46 -0
  3. package/README.md +191 -67
  4. package/package.json +2 -2
  5. package/src/abideLsp.ts +8 -4
  6. package/src/abideResolverPlugin.ts +11 -1
  7. package/src/build.ts +9 -0
  8. package/src/buildDisconnected.ts +2 -0
  9. package/src/devEntry.ts +1 -0
  10. package/src/lib/bundle/probeAbideServer.ts +1 -1
  11. package/src/lib/cli/parseArgvForRpc.ts +4 -2
  12. package/src/lib/cli/types/CliManifest.ts +6 -4
  13. package/src/lib/mcp/annotationsForMethod.ts +1 -1
  14. package/src/lib/mcp/toolResultFromResponse.ts +1 -1
  15. package/src/lib/mcp/types/JsonRpcRequest.ts +3 -3
  16. package/src/lib/server/AppModule.ts +1 -1
  17. package/src/lib/server/cli/handleCliDownload.ts +7 -5
  18. package/src/lib/server/cli/installScript.ts +3 -3
  19. package/src/lib/server/prompts/promptRegistry.ts +1 -1
  20. package/src/lib/server/prompts/types/Prompt.ts +2 -1
  21. package/src/lib/server/rpc/dispatchRpcInProcess.ts +2 -2
  22. package/src/lib/server/rpc/types/RpcHelper.ts +1 -1
  23. package/src/lib/server/runtime/createPublicAssetServer.ts +2 -2
  24. package/src/lib/server/runtime/createRouteDispatcher.ts +1 -1
  25. package/src/lib/server/runtime/createServer.ts +21 -0
  26. package/src/lib/server/runtime/createUiPageRenderer.ts +7 -11
  27. package/src/lib/server/runtime/crossOriginForbidden.ts +2 -2
  28. package/src/lib/server/runtime/crossOriginGate.ts +1 -1
  29. package/src/lib/server/runtime/devHotModuleResponse.ts +2 -2
  30. package/src/lib/server/runtime/disableIdleTimeoutForStream.ts +1 -1
  31. package/src/lib/server/runtime/logExposedSurfaces.ts +3 -4
  32. package/src/lib/server/runtime/serializeCacheSnapshot.ts +3 -8
  33. package/src/lib/server/runtime/snapshotEntryFromCache.ts +10 -7
  34. package/src/lib/server/runtime/streamFromIterator.ts +2 -2
  35. package/src/lib/server/runtime/types/InspectorRpc.ts +2 -2
  36. package/src/lib/server/socket.ts +1 -1
  37. package/src/lib/server/sockets/createSocketDispatcher.ts +2 -2
  38. package/src/lib/server/sockets/socketRegistry.ts +2 -2
  39. package/src/lib/server/sockets/types/SocketClientFrame.ts +3 -3
  40. package/src/lib/shared/CACHE_WRAPPED.ts +3 -2
  41. package/src/lib/shared/REMOTE_FUNCTION.ts +1 -1
  42. package/src/lib/shared/STREAMING_CONTENT_TYPES.ts +3 -2
  43. package/src/lib/shared/bodyValueForKind.ts +35 -0
  44. package/src/lib/shared/cache.ts +1 -1
  45. package/src/lib/shared/cacheEntryFromSnapshot.ts +23 -18
  46. package/src/lib/shared/createPushIterator.ts +1 -1
  47. package/src/lib/shared/decodeResponse.ts +11 -8
  48. package/src/lib/shared/hasReplayableRequest.ts +17 -0
  49. package/src/lib/shared/html.ts +7 -26
  50. package/src/lib/shared/manifestModule.ts +2 -2
  51. package/src/lib/shared/online.ts +1 -1
  52. package/src/lib/shared/outboxProbeSlot.ts +1 -1
  53. package/src/lib/shared/pageUrlForFile.ts +1 -1
  54. package/src/lib/shared/routeParamsShape.ts +20 -0
  55. package/src/lib/shared/snapshotShippable.ts +22 -0
  56. package/src/lib/shared/types/CacheEntry.ts +1 -1
  57. package/src/lib/shared/types/CacheInvalidation.ts +3 -3
  58. package/src/lib/shared/types/ClientFlags.ts +5 -3
  59. package/src/lib/shared/types/LastConnection.ts +1 -1
  60. package/src/lib/shared/url.ts +4 -1
  61. package/src/lib/shared/withBase.ts +1 -2
  62. package/src/lib/shared/writeDts.ts +2 -2
  63. package/src/lib/shared/writeRoutesDts.ts +3 -31
  64. package/src/lib/test/createTestSocketChannel.ts +3 -3
  65. package/src/lib/ui/COMPONENT_WRAPPER_PREFIX.ts +1 -1
  66. package/src/lib/ui/compile/ABIDE_SEMANTIC_TOKENS_LEGEND.ts +40 -6
  67. package/src/lib/ui/compile/REACTIVE_CALLEES.ts +1 -1
  68. package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +49 -37
  69. package/src/lib/ui/compile/abideUiPlugin.ts +1 -1
  70. package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +3 -1
  71. package/src/lib/ui/compile/awaitPlan.ts +14 -2
  72. package/src/lib/ui/compile/catchBinding.ts +8 -0
  73. package/src/lib/ui/compile/classStyleMergePlan.ts +108 -0
  74. package/src/lib/ui/compile/collectAbideDiagnostics.ts +11 -0
  75. package/src/lib/ui/compile/compileComponent.ts +4 -4
  76. package/src/lib/ui/compile/compileModule.ts +20 -6
  77. package/src/lib/ui/compile/compileSSR.ts +12 -10
  78. package/src/lib/ui/compile/compileShadow.ts +107 -16
  79. package/src/lib/ui/compile/composeProps.ts +3 -3
  80. package/src/lib/ui/compile/createShadowLanguageService.ts +30 -2
  81. package/src/lib/ui/compile/createShadowProgram.ts +2 -1
  82. package/src/lib/ui/compile/createShadowScope.ts +42 -0
  83. package/src/lib/ui/compile/decodeHtmlEntities.ts +4 -0
  84. package/src/lib/ui/compile/desugarSignals.ts +12 -7
  85. package/src/lib/ui/compile/eachPlan.ts +61 -0
  86. package/src/lib/ui/compile/elementPlan.ts +55 -0
  87. package/src/lib/ui/compile/encodeSemanticTokens.ts +24 -1
  88. package/src/lib/ui/compile/generateBuild.ts +150 -127
  89. package/src/lib/ui/compile/generateSSR.ts +112 -73
  90. package/src/lib/ui/compile/ifPlan.ts +7 -1
  91. package/src/lib/ui/compile/interpolatedTemplateLiteral.ts +25 -0
  92. package/src/lib/ui/compile/isPlainIdentifier.ts +7 -0
  93. package/src/lib/ui/compile/isTextLeaf.ts +8 -7
  94. package/src/lib/ui/compile/lowerContext.ts +36 -46
  95. package/src/lib/ui/compile/lowerDocAccess.ts +2 -2
  96. package/src/lib/ui/compile/lowerScript.ts +1 -1
  97. package/src/lib/ui/compile/markupTokens.ts +313 -0
  98. package/src/lib/ui/compile/pagePropsType.ts +23 -0
  99. package/src/lib/ui/compile/parseTemplate.ts +131 -61
  100. package/src/lib/ui/compile/reactiveBinding.ts +47 -0
  101. package/src/lib/ui/compile/renameSignalRefs.ts +44 -10
  102. package/src/lib/ui/compile/skeletonable.ts +5 -5
  103. package/src/lib/ui/compile/snippetPlan.ts +28 -0
  104. package/src/lib/ui/compile/spreadExcludedNames.ts +4 -4
  105. package/src/lib/ui/compile/structuralBlockTokens.ts +3 -4
  106. package/src/lib/ui/compile/switchPlan.ts +7 -1
  107. package/src/lib/ui/compile/tryPlan.ts +8 -1
  108. package/src/lib/ui/compile/types/AnalyzedComponent.ts +2 -2
  109. package/src/lib/ui/compile/types/Binding.ts +24 -0
  110. package/src/lib/ui/compile/types/CompiledShadow.ts +13 -1
  111. package/src/lib/ui/compile/types/ElementPlan.ts +43 -0
  112. package/src/lib/ui/compile/types/ShadowKind.ts +9 -0
  113. package/src/lib/ui/compile/types/ShadowScope.ts +19 -0
  114. package/src/lib/ui/compile/types/TemplateAttr.ts +7 -0
  115. package/src/lib/ui/compile/types/TemplateNode.ts +8 -8
  116. package/src/lib/ui/compile/withBindings.ts +45 -0
  117. package/src/lib/ui/createScope.ts +2 -2
  118. package/src/lib/ui/dom/MATHML_NAMESPACE.ts +2 -2
  119. package/src/lib/ui/dom/SVG_NAMESPACE.ts +3 -2
  120. package/src/lib/ui/dom/appendText.ts +1 -1
  121. package/src/lib/ui/dom/applyResolved.ts +1 -1
  122. package/src/lib/ui/dom/buildDetachedRange.ts +4 -3
  123. package/src/lib/ui/dom/cloneStatic.ts +3 -2
  124. package/src/lib/ui/dom/each.ts +26 -7
  125. package/src/lib/ui/dom/foreignWrapperTag.ts +2 -2
  126. package/src/lib/ui/dom/mountChild.ts +8 -1
  127. package/src/lib/ui/dom/readCall.ts +6 -4
  128. package/src/lib/ui/dom/scopeLabel.ts +3 -3
  129. package/src/lib/ui/dom/switchBlock.ts +3 -1
  130. package/src/lib/ui/dom/types/EachRow.ts +4 -0
  131. package/src/lib/ui/dom/types/SkeletonHoles.ts +4 -2
  132. package/src/lib/ui/history.ts +1 -2
  133. package/src/lib/ui/html.ts +28 -0
  134. package/src/lib/ui/installHotBridge.ts +0 -2
  135. package/src/lib/ui/renderChain.ts +16 -3
  136. package/src/lib/ui/router.ts +66 -15
  137. package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +1 -1
  138. package/src/lib/ui/runtime/CHILD_PRESENT.ts +8 -0
  139. package/src/lib/ui/runtime/OUTLET_MARKER.ts +2 -2
  140. package/src/lib/ui/runtime/RANGE_MARKER.ts +1 -1
  141. package/src/lib/ui/runtime/REACTIVE_CONTEXT.ts +9 -3
  142. package/src/lib/ui/runtime/captureModelDoc.ts +1 -1
  143. package/src/lib/ui/runtime/createDoc.ts +5 -1
  144. package/src/lib/ui/runtime/createEffectNode.ts +5 -5
  145. package/src/lib/ui/runtime/flushEffects.ts +22 -9
  146. package/src/lib/ui/runtime/historyEntries.ts +1 -1
  147. package/src/lib/ui/runtime/track.ts +4 -2
  148. package/src/lib/ui/runtime/trigger.ts +34 -20
  149. package/src/lib/ui/runtime/types/Route.ts +2 -2
  150. package/src/lib/ui/runtime/types/UiComponent.ts +3 -2
  151. package/src/lib/ui/runtime/types/UiProps.ts +9 -12
  152. package/src/lib/ui/runtime/walkPath.ts +15 -3
  153. package/src/lib/ui/startClient.ts +41 -14
  154. package/src/lib/ui/state.ts +3 -3
  155. package/src/lib/ui/types/Scope.ts +3 -3
  156. package/src/serverBuildPlugins.ts +11 -5
  157. package/src/zodCjsPlugin.ts +36 -0
  158. package/template/.zed/settings.json +7 -1
  159. package/template/package.json +3 -0
  160. package/template/src/ui/pages/layout.abide +3 -5
package/AGENTS.md CHANGED
@@ -1,344 +1,418 @@
1
1
  # AGENTS.md — abide complete surface map
2
2
 
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.
3
+ > The exhaustive index of abide's public surface: every `exports` key appears
4
+ > once, grouped by namespace, with its import specifier and a one-line spec.
5
+ > This is the whole-API read; the README is the curated 3-primitive intro.
6
+ > CONTEXT.md is the glossary, `docs/adr/` the rationale.
7
+
8
+ **No barrels.** Every public name has its own module path
9
+ (`abide/server/GET`, `abide/shared/cache`, `abide/ui/scope`) — there is no
10
+ umbrella `index.ts`, so importing one name never drags side-effecting siblings
11
+ into the bundle. The namespace marks the side a name runs on: `abide/server/*`
12
+ is server-only, `abide/ui/*` is client-only, `abide/shared/*` is isomorphic
13
+ (same callable, same behaviour on both sides; the bundler swaps the runtime).
14
+
15
+ Package: `@abide/abide`, one runtime (Bun `>=1.3.0`), one direct dependency
16
+ (TypeScript). The bullets below name each export by its import specifier in the
17
+ `abide/…` shorthand — the published package is `@abide/abide`, so
18
+ `abide/server/GET` imports from `@abide/abide/server/GET`. These are import
19
+ specifiers, not source file paths.
8
20
 
9
21
  ## The premise
10
22
 
11
23
  One typed RPC declaration fans out to every surface:
12
24
 
13
25
  ```text
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
26
+ export const getMessages = GET(fn, { inputSchema })
27
+
28
+ ┌───────────────┬─────────────┼─────────────┬───────────────┐
29
+
30
+ SSR call browser fetch MCP tool CLI command OpenAPI op
31
+ cache(fn)() typed proxy (read-only) getMessages … /openapi.json
32
+ (in-process) (swapped fn())
20
33
  ```
21
34
 
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 }`.
35
+ A schema makes the handler safe to advertise off-browser: it turns the CLI on
36
+ for any method and auto-exposes read-only methods (`GET`/`HEAD`) as MCP tools.
37
+ A mutating method (`POST`/`PUT`/`PATCH`/`DELETE`) never auto-exposes to MCP — it
38
+ needs an explicit `clients: { mcp: true }`. Explicit `clients` always wins.
23
39
 
24
40
  ## File-based conventions
25
41
 
26
- The bundler reads these paths; the path is the identity.
42
+ The bundler reads meaning from these paths (the project's own `src/`, not the
43
+ package):
27
44
 
28
45
  | Path | Meaning |
29
46
  | --- | --- |
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`) |
47
+ | `src/server/rpc/<name>.ts` | One RPC; export name = file stem, URL = `/rpc/<path>` |
48
+ | `src/server/sockets/<name>.ts` | One socket; export name = file stem, name = path |
49
+ | `src/mcp/prompts/<name>.md` | An MCP prompt (front-matter + `{{placeholder}}` body) |
50
+ | `src/server/config.ts` | Eager-imported config module (the `env()` call site) |
51
+ | `src/app.ts` | Optional `AppModule` hooks (`init`/`handle`/`handleError`/`health`) |
52
+ | `src/bundle/window.ts` | Default-exported `BundleWindow` (desktop bundle config) |
53
+ | `src/ui/pages/**/page.abide` | A route; its URL is the folder path (`[name]` dynamic) |
54
+ | `src/ui/pages/**/layout.abide` | A layout; wraps the chain below via its outlet |
55
+ | `src/ui/public/` | Static assets, gzip-embedded and served at root |
56
+ | `src/.abide/*.d.ts` | Generated type augmentations (routes, rpc, sockets, health) |
57
+ | `dist/_app/` | Client build output |
41
58
 
42
59
  ## CLI
43
60
 
44
61
  | Command | Does |
45
62
  | --- | --- |
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`.
63
+ | `bunx abide scaffold <name>` | Scaffold a project, install, start dev (`--no-install` / `--no-dev` opt out; non-TTY never auto-starts) |
64
+ | `abide dev` | Build the client + run the server with hot reload (rebuild/restart on `src/` changes) |
65
+ | `abide build` | One client build into `dist/_app/` (no server) |
66
+ | `abide start` | Run the production server against a built `dist/` |
67
+ | `abide check` | Type-check every `.abide` template + props; non-zero exit on errors |
68
+ | `abide run <file> [args]` | Run a script under the abide preload (same runtime as the server) |
69
+ | `abide compile [--target] [--out]` | Build a standalone server executable |
70
+ | `abide cli [--target] [--out] [--platforms]` | Build the thin CLI binary (ships the server beside it) |
71
+ | `abide bundle` | Build a movable, self-contained desktop app bundle (unsigned) |
72
+ | `abide lsp` | Run the `.abide` language server over stdio (editor diagnostics) |
73
+ | `abide init-agent` | Write/refresh a root `CLAUDE.md` pointer to this surface map |
74
+
75
+ For `bun test`, add `preload = ["@abide/abide/preload"]` under `[test]` in
76
+ `bunfig.toml`.
59
77
 
60
78
  ## Authoring contracts
61
79
 
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).
80
+ **RPC** (`src/server/rpc/<name>.ts`) — `export const <name> = METHOD(handler, opts?)`.
81
+ The handler receives `InferOutput<inputSchema>` (or the bare arg type when
82
+ schemaless), reads `request()` / `cookies()` for request scope, and returns a
83
+ `json` / `jsonl` / `sse` / `error` / `redirect` helper Response or a raw
84
+ `Response`. `opts`: `inputSchema`, `outputSchema` (OpenAPI 200 + MCP
85
+ `outputSchema`), `filesSchema` (multipart File parts; send a `FormData`),
86
+ `errors` (declared error constructors handed to the handler), `clients`
87
+ (`{ browser, mcp, cli }`), `crossOrigin` (exempt a mutating RPC from the
88
+ same-origin CSRF gate), `timeout` (ms handler deadline → 504 every surface),
89
+ `maxBodySize` (received-byte ceiling → 413), and — mutating helpers only —
90
+ `outbox` (durable replay; `GET(fn, { outbox })` is a compile error). Query args
91
+ arrive as strings → use `z.coerce.*`. Consume: `cache(fn)(args)` in-process,
92
+ the swapped browser proxy `fn(args)`, `fn.raw(args)` for the `Response`,
93
+ `fn.stream(args)` for a frame `Subscribable`.
94
+
95
+ **Socket** (`src/server/sockets/<name>.ts`) — `export const <name> = socket(opts)`.
96
+ `opts`: `schema` (validates publishes, infers `T`, turns mcp/cli on), `tail`
97
+ (retained-frame count for replay), `ttl` (lazy eviction ms), `clientPublish`
98
+ (allow client `POST` publishes), `clients`. A `Socket<T>` is an
99
+ `AsyncIterable<T>` (bare `for await` = live, no replay); `.tail(count?)` opens a
100
+ replay-seeded subscription; `.publish(m)` is isomorphic.
101
+
102
+ **Page / layout** (`src/ui/pages/**`) — a `page.abide`'s URL is its folder path;
103
+ `[id]` segments arrive as `page.params.id`. A `layout.abide` wraps the layer
104
+ below at its outlet (`{children()}`). Read `page` for route/params/url, call
105
+ `url()` / `navigate()` for base-correct links.
106
+
107
+ **App** (`src/app.ts`) — optional `AppModule`: `init({ server })` (returns an
108
+ optional teardown), `handle(request, next)` (single middleware), `handleError`,
109
+ `health(request)` (fields merged into `/__abide/health`), `forwardHeaders`.
110
+ **Config** (`src/server/config.ts`) — `export const config = env(schema)` at
111
+ module top level.
112
+
113
+ **Isomorphism** — wrap an RPC in `cache(fn)(args)` so an SSR `await` bakes the
114
+ value into the initial HTML and the client hydrates warm off the serialized
115
+ snapshot; the same call in the browser dedupes and fetches.
67
116
 
68
117
  ## .abide template grammar
69
118
 
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.
119
+ A component file is a leading `<script>` (module imports + reactive setup),
120
+ markup, and an optional root `<style>`. Ambient names the compiler injects (no
121
+ import): `scope`, `props`, `effect`, `snippet`. `html` is imported
122
+ (`abide/ui/html`). A *root* `<style>` is component-scoped; a `<script>` or
123
+ `<style>` nested inside a control-flow branch is scoped to that branch (a nested
124
+ `<script>` declares branch-local `scope().state/linked/computed`, re-seeded per
125
+ mount, and may not carry module imports — those hoist to the leading `<script>`).
71
126
 
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.
127
+ **Reactive state** — reached only through `scope()`; the documented default is
128
+ the destructure-once idiom: `const { state, computed, linked, effect } = scope()`
129
+ at the top, then bare calls.
73
130
 
74
- | Form | Is |
131
+ | Form | Meaning |
75
132
  | --- | --- |
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 |
133
+ | `let x = state(v, transform?)` | Writable cell; assign `x = …` to update |
134
+ | `const d = computed(() => …)` | Read-only derived |
135
+ | `const l = linked(fn, transform?)` | Writable, re-seeds when `fn`'s deps change |
136
+ | `effect(() => { … })` | Reaction re-run on dep change; client-only (SSR strips it) |
137
+ | `const { a = d, ...rest } = props()` | Ambient prop reader (defaults + rest) |
81
138
 
82
- A two-way binding over derived state is an accessor at the bind site (`bind:value={{ get, set }}`), not a writable primitive.
139
+ Bare `state`/`computed`/`linked`/`effect`/`derived` with no `scope()` destructure
140
+ in scope is a compile error. `computed` is always read-only — express a writable
141
+ computed at the binding (`bind:value={{ get, set }}`).
83
142
 
84
- **Bindings**
143
+ **Bindings / directives**
85
144
 
86
- | Syntax | Binds |
145
+ | Syntax | Meaning |
87
146
  | --- | --- |
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) |
94
-
95
- **Control flow** — mustache blocks (`{#…}` open, `{:…}` branch, `{/…}` close):
147
+ | `{expr}` | Escaped text interpolation |
148
+ | `{html(...)}` | `html`-branded raw markup, plain call or tagged template (unescaped) |
149
+ | `name={expr}` / `name="a {b}"` | Attribute value (plain or interpolated) |
150
+ | `on<event>={fn}` | Event listener (`onclick`, `onsubmit`, …) |
151
+ | `bind:value` / `bind:checked` / `bind:group` | Two-way form binds |
152
+ | `bind:value={{ get, set }}` | Derived two-way binding |
153
+ | `class:name={cond}` | Toggle a class |
154
+ | `style:property={value}` | Set one style property |
155
+ | `attach={fn}` | Run `fn(element)` at mount (returns optional teardown) |
156
+ | `{...spread}` | Spread object keys — props on a component, attributes on an element |
157
+
158
+ **Control flow** — mustache blocks, not `<template>`:
96
159
 
97
160
  | Block | Form |
98
161
  | --- | --- |
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 })}` |
105
-
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.
162
+ | If | `{#if c}…{:else if d}…{:else}…{/if}` |
163
+ | For | `{#for item, i of list by key}…{/for}` |
164
+ | For-await | `{#for await item of source}…{/for}` (over an `AsyncIterable`) |
165
+ | Await | `{#await p}…{:then v}…{:catch e}…{:finally}…{/await}` |
166
+ | Switch | `{#switch s}{:case x}…{:default}…{/switch}` |
167
+ | Try | `{#try}…{:catch e}…{:finally}{/try}` |
168
+ | Snippet | `{#snippet name(args)}…{/snippet}`, called `{name(args)}` |
169
+
170
+ A capitalised tag is a child component; nested content renders where the child
171
+ calls `{children()}` — the single slot, with `{#if children}…{:else}…{/if}` as
172
+ the fallback form. No named slots.
173
+
174
+ > Removed forms throw a migration error: the `<slot>` element (use
175
+ > `{children()}`), `<template name>` snippets (use `{#snippet}`), and all
176
+ > `<template if>` / `<template each>` / `<template await>` / … control flow (use
177
+ > the `{#…}` blocks). A bare `<template>` is now an inert element.
107
178
 
108
179
  ## Server surface — abide/server/*
109
180
 
110
- ### RPC helpers — @documentation rpc
181
+ ### RPC — @documentation rpc
111
182
 
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.
183
+ - `abide/server/GET` — read RPC helper; `GET(fn, opts?)`, bound by the bundler from `src/server/rpc/`.
184
+ - `abide/server/POST` — create/mutate RPC helper; accepts `outbox` for durable replay.
185
+ - `abide/server/PUT` — replace RPC helper (mutating).
186
+ - `abide/server/PATCH` — partial-update RPC helper (mutating).
187
+ - `abide/server/DELETE` — delete RPC helper (mutating).
188
+ - `abide/server/HEAD` — read RPC helper, no body (auto-exposes to MCP like GET).
118
189
 
119
190
  ### Sockets — @documentation sockets
120
191
 
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.
192
+ - `abide/server/socket` — `socket(opts)` declares a broadcast topic; one export per file under `src/server/sockets/`.
122
193
 
123
- ### Responses — @documentation response
194
+ ### Response — @documentation response
124
195
 
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`.
196
+ - `abide/server/json` — JSON `Response` with `Cache-Control: no-store` default; same shape as `Response.json`.
197
+ - `abide/server/jsonl` — wraps an `AsyncIterable<Frame>` as a JSON Lines (`application/jsonl`) streaming Response.
198
+ - `abide/server/sse` — wraps an `AsyncIterable<Frame>` as Server-Sent Events (`text/event-stream`).
199
+ - `abide/server/error` — plain-text error Response; `error(status, message?)`, message defaults to the status reason phrase.
200
+ - `abide/server/redirect` — redirect Response; accepts relative URLs, defaults to 302.
130
201
 
131
202
  ### Request scope — @documentation request-scope
132
203
 
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.
204
+ - `abide/server/request` — the inbound `Request` for the current SSR/RPC pass (AsyncLocalStorage; throws outside a scope).
205
+ - `abide/server/cookies` — the request's cookie jar (Bun `CookieMap`); writes flush as `Set-Cookie` on return.
206
+ - `abide/server/server` — the active `Bun.serve` instance (a no-op in-process server under CLI/MCP/test dispatch).
136
207
 
137
208
  ### Configuration — @documentation configuration
138
209
 
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.
210
+ - `abide/server/env` — validate `Bun.env` against a Standard Schema at module top level; returns typed config or fails boot loudly.
140
211
 
141
212
  ### Observability — @documentation observability
142
213
 
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.
214
+ - `abide/server/reachable` — `await reachable(host)` outbound reachability probe with warm TTL polling (server-only).
148
215
 
149
216
  ### Agent — @documentation agent
150
217
 
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`.
218
+ - `abide/server/agent` — `agent(engine, messages)` runs a model engine against the app's own MCP surface, returning the engine's `AgentFrame` stream (wrap in `jsonl()`/`sse()`); also exports `NeutralMessage`, `AgentFrame`, `AgentSurface`, `AgentEngine`.
152
219
 
153
- ### Plumbing — @documentation plumbing
220
+ ### Server plumbing — @documentation plumbing
154
221
 
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).
222
+ - `abide/server/AppModule` — type of the optional `src/app.ts` hooks (`init`/`handle`/`handleError`/`health`/`forwardHeaders`).
223
+ - `abide/server/InspectorContext` — type the capabilities core injects into `@abide/inspector` when enabled.
224
+ - `abide/server/rpc/defineRpc` — the runtime the RPC helpers compile to (method + URL + handler `RemoteFunction`); not called directly.
225
+ - `abide/server/sockets/defineSocket` — the server-side `socket()` implementation the bundler binds the file name into.
226
+ - `abide/server/prompts/definePrompt` — the runtime each `src/mcp/prompts/<file>.md` compiles to (registers an MCP prompt).
227
+ - `abide/server/prompts/renderPromptTemplate` — substitutes `{{name}}` placeholders in a markdown prompt body (missing empty).
161
228
 
162
229
  ## Isomorphic surface — abide/shared/*
163
230
 
164
- ### Responses — @documentation response
231
+ ### Cache — @documentation cache
165
232
 
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.
233
+ - `abide/shared/cache` — `cache(fn, options?)(args)` request/tab-scoped read with coalescing, `ttl`, `swr`, `tags`, `global`; SSR snapshot for warm hydration. Carries `cache.invalidate(selector?, args?)` and `cache.on(source, handler)` (event-driven invalidation).
168
234
 
169
- ### Schema projection — @documentation rpc
235
+ ### Response — @documentation response
170
236
 
171
- - `abide/shared/withJsonSchema` — attach a `toJSONSchema()` projection to a Standard Schema whose library lacks one, feeding OpenAPI / MCP / CLI / the setup form.
237
+ - `abide/shared/HttpError` — thrown on a non-2xx remote call; carries the raw `Response` plus `kind`/`data`.
238
+ - `abide/shared/ValidationErrorData` — the `data` payload (`kind: 'validation'`, status 422) of a validation failure: raw `issues` + form-friendly `fields`.
172
239
 
173
- ### Templating — @documentation templating
240
+ ### RPC — @documentation rpc
174
241
 
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.
242
+ - `abide/shared/withJsonSchema` — attach a `toJSONSchema()` projection to a Standard Schema whose library lacks one (feeds OpenAPI/MCP/CLI).
177
243
 
178
- ### Cache — @documentation cache
244
+ ### Templating — @documentation templating
179
245
 
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.
246
+ - `abide/shared/snippet` — brands a payload so a `{expr}` interpolation mounts it (client builder / SSR string); the runtime behind `{#snippet}`. Also `Snippet<Payload>`, `snippetPayload`.
181
247
 
182
- ### Page — @documentation page
248
+ ### Probes — @documentation probes
183
249
 
184
- - `abide/shared/page` — the reactive page proxy (matched route, decoded `params`, browser-space `url`, `navigating`); isomorphic, re-runs readers on navigation.
250
+ - `abide/shared/pending` — reactive in-flight probe over cache + tail registries; `pending()`, `pending(fn)`, `pending(fn, args)`, `pending({ tags })`.
251
+ - `abide/shared/refreshing` — reactive revalidation probe (holding a value while a fresher one loads); same selector grammar as `pending`.
252
+ - `abide/shared/online` — reactive network-connectivity probe (`navigator.onLine`, offline-reliable).
185
253
 
186
- ### Probes — @documentation probes
254
+ ### Page — @documentation page
187
255
 
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`).
256
+ - `abide/shared/page` — reactive page proxy: `route`, `params`, `url` (browser-space), `navigating`; isomorphic.
191
257
 
192
258
  ### URL — @documentation url
193
259
 
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.
260
+ - `abide/shared/url` — `url(path, args?)` resolves a base-correct, typed in-app URL (RPC query / page params / asset); also `RpcRoutes`, `PageRoutes`, `PublicAssets`, `PathParams` (augmentable typing seams).
195
261
 
196
262
  ### Observability — @documentation observability
197
263
 
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.
264
+ - `abide/shared/health` — typed `health()` probe of `/__abide/health` (fields augmented from the app's `health()` hook); also `AppHealthMap`, `AppHealth`, `HealthState`.
265
+ - `abide/shared/log` — unified logger carrying request-scope context; `log`/`.warn`/`.error`/`.trace` on the app channel, `log.channel(name)` for DEBUG-gated channels.
266
+ - `abide/shared/trace` — the current request's W3C `traceparent`, or undefined outside a request scope; isomorphic.
201
267
 
202
- ### Plumbing — @documentation plumbing
268
+ ### Isomorphic plumbing — @documentation plumbing
203
269
 
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).
270
+ - `abide/shared/createSubscriber` — abide-native open-on-first-read / close-on-last-reader subscriber over the signal core (cache/tail substrate).
205
271
 
206
272
  ## UI surface — abide/ui/* (client-only)
207
273
 
208
274
  ### Reactive state — @documentation reactive-state
209
275
 
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.
211
-
212
- ### Streaming — @documentation tail
213
-
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.
276
+ - `abide/ui/scope` — `scope(address?)` resolves the current lexical scope (or `scope('/')` the tree root); the sole reactive entry, carrying `state`/`computed`/`linked`/`effect` + data/context/capability methods.
215
277
 
216
- ### Navigation — @documentation navigate
217
-
218
- - `abide/ui/navigate` — programmatic SPA navigation typed off the page routes; `NavigateOptions` carries `replace` / `keepScroll`. Delegates path-building to `url`.
219
-
220
- ### Outbox — @documentation ui
221
-
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`.
223
-
224
- ### Plumbing — @documentation plumbing
278
+ ### Templating — @documentation templating
225
279
 
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.
280
+ - `abide/ui/html` — `html(string)` / `` html`…` `` marks trusted raw HTML so a `{expr}` inserts nodes instead of escaped text.
281
+
282
+ ### Tail@documentation tail
283
+
284
+ - `abide/ui/tail` — reactive streaming consumer of a `Subscribable<T>` (socket / `fn.stream`); bare = latest frame, `{ last: n }` = window; `tail.error`, `tail.status`. No-op on the server.
285
+
286
+ ### Navigate@documentation navigate
287
+
288
+ - `abide/ui/navigate` — `navigate(path, params?, options?)` typed in-app navigation through `url()`; `replace`/`keepScroll`. Also `navigatePath` (already-resolved path) and `NavigateOptions`.
289
+
290
+ ### UI@documentation ui
291
+
292
+ - `abide/ui/outbox` — global reactive view of every durable RPC's parked writes; callable for the list, `outbox.retry()` to drain. Also `GlobalOutbox`, `GlobalOutboxEntry`.
293
+
294
+ ### UI plumbing — @documentation plumbing
295
+
296
+ - `abide/ui/effect` — the from-scratch effect primitive `scope().effect` lowers to (and the SSR strip target).
297
+ - `abide/ui/enterScope` — opens an isolated lexical scope for an SSR render; returns the previous one.
298
+ - `abide/ui/exitScope` — restores the scope `enterScope` saved (closes an SSR render's scope).
299
+ - `abide/ui/remoteProxy` — client-side substitute for a server RPC (typed fetch over HTTP); also `DurableOptions`.
300
+ - `abide/ui/socketProxy` — client-side substitute for a server `Socket` (subscribe over the multiplexed ws).
301
+ - `abide/ui/router` — the client router: outlet boundaries, chain mounting, history-driven navigation.
302
+ - `abide/ui/startClient` — boot entry that consumes the server's `__SSR__` payload and hydrates the page.
303
+ - `abide/ui/renderToStream` — out-of-order SSR streaming: shell first, then one resolved fragment per streaming await block.
304
+ - `abide/ui/dom/mount` — mount a top-level page/layout into a host under an ownership scope; returns a disposer.
305
+ - `abide/ui/dom/mountChild` — mount a child component as a marker-bounded range (no wrapper element).
306
+ - `abide/ui/dom/mergeProps` — compose a child's props from ordered explicit/spread/`$children` layers (last wins).
307
+ - `abide/ui/dom/spreadProps` — wrap a `{...source}` prop-spread layer as live value thunks.
308
+ - `abide/ui/dom/restProps` — the unconsumed props of a `const { a, ...rest } = props()` destructure.
309
+ - `abide/ui/dom/spreadAttrs` — spread an object's keys onto a native element (`<div {...rest}>`).
310
+ - `abide/ui/dom/readCall` — guarded method call on a reactive-doc read (legible throw naming the authored path).
311
+ - `abide/ui/dom/hydrate` — adopt server-rendered DOM in place instead of rebuilding; returns a disposer.
312
+ - `abide/ui/dom/text` — a text node tracking a `read()` (plain-text fast path).
313
+ - `abide/ui/dom/appendText` — a reactive `{expr}` interpolation (escaped text / `{snippet}` / `html` raw).
314
+ - `abide/ui/dom/appendTextAt` — `appendText` mounted at a skeleton anchor comment (text interleaved with elements).
315
+ - `abide/ui/dom/appendSnippet` — mount a `{snippet(args)}` builder's nodes in a marker range, reactive in its args.
316
+ - `abide/ui/dom/appendStatic` — a static (non-reactive) text node, claimed on hydrate.
317
+ - `abide/ui/dom/cloneStatic` — append a fully-static subtree, byte-identical to the SSR markup.
318
+ - `abide/ui/dom/skeleton` — clone a bound element's static skeleton with located holes/anchors.
319
+ - `abide/ui/dom/anchorCursor` — position a skeleton-anchored control-flow block or slot.
320
+ - `abide/ui/dom/mountSlot` — mount a component's `{children()}` content (or fallback) as a marker range.
321
+ - `abide/ui/dom/outlet` — a layout's outlet boundary the router fills with the next chain layer.
322
+ - `abide/ui/dom/attr` — bind one element attribute to a `read()` (present/absent boolean semantics).
323
+ - `abide/ui/dom/on` — attach an event listener, owned by the current scope (the `onclick={…}` target).
324
+ - `abide/ui/dom/attach` — run an `attach={fn}` against an element at build, owning its teardown.
325
+ - `abide/ui/dom/each` — keyed list runtime (`{#for … by key}`); each row a marker-bounded range.
326
+ - `abide/ui/dom/eachAsync` — async keyed list runtime (`{#for await … }`) over an `AsyncIterable`.
327
+ - `abide/ui/dom/when` — conditional runtime (`{#if}` + optional `{:else}`).
328
+ - `abide/ui/dom/awaitBlock` — async runtime (`{#await}` → pending / then / catch ranges).
329
+ - `abide/ui/dom/tryBlock` — synchronous error-boundary runtime (`{#try}`).
330
+ - `abide/ui/dom/switchBlock` — multi-branch runtime (`{#switch}`, strict `===`, default fallback).
331
+ - `abide/ui/dom/applyResolved` — bundle-side consumer of an SSR stream chunk (streaming nav / socket SSR).
332
+ - `abide/ui/runtime/escapeKey` — escape one object key into a JSON-Pointer reference token (RFC 6901).
333
+ - `abide/ui/runtime/nextBlockId` — the next block id in the current render pass (await/try, document order).
334
+ - `abide/ui/runtime/enterRenderPass` — mark entry into a render/mount (resets the block-id counter at depth 0).
237
335
  - `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.
266
336
 
267
337
  ## Build / tooling
268
338
 
269
339
  ### Building — @documentation building
270
340
 
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.
341
+ - `abide/build` — build the client bundle into `dist/_app` (the `.abide` loader, virtual resolver, optional Tailwind, optional gzip).
342
+ - `abide/compile` — produce a standalone Bun server executable (runs `build` first, embeds compressed assets).
273
343
 
274
- ### Plumbing — @documentation plumbing
344
+ ### Build plumbing — @documentation plumbing
275
345
 
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.
346
+ - `abide/preload` — the Bun preload that installs the `.abide` loader + resolver for the server/scripts/tests.
347
+ - `abide/resolver-plugin` — resolves `$`-aliased / extensionless / directory imports Node-style; also the build's virtual-module loaders.
348
+ - `abide/ui-plugin` — the Bun plugin that compiles `.abide` single-file components into ES modules.
349
+ - `abide/tsconfig` — the shippable base `tsconfig.app.json` projects extend.
280
350
 
281
- ## Desktop bundle — abide/bundle/*
351
+ ## Desktop bundle
282
352
 
283
353
  ### Bundle — @documentation bundle
284
354
 
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.
355
+ - `abide/server/appDataDir` — the running bundle's per-user data dir (keyed by program name; cwd-independent; server-side).
356
+ - `abide/bundle/BundleWindow` — type of the default-exported `src/bundle/window.ts` window config (title/size/menus).
357
+ - `abide/bundle/BundleMenu` — a top-level macOS menu (`label` + `items`).
358
+ - `abide/bundle/BundleMenuItem` — a single menu entry (divider or a click that dispatches an `abide:menu` event).
359
+ - `abide/bundle/onMenu` — subscribe to bundle menu clicks (catch-all or filtered); returns an unsubscribe.
360
+ - `abide/bundle/bundled` — true when running inside the desktop bundle rather than a plain browser (isomorphic).
290
361
 
291
- ## MCP — abide/mcp/*
362
+ ## MCP
292
363
 
293
364
  ### MCP — @documentation mcp
294
365
 
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).
366
+ - `abide/mcp/createMcpServer` — construct the MCP server bound to the RPC registry (tools from `clients.mcp` RPCs + prompts + resources); `handle(request)` backs `/__abide/mcp`. Framework-internal.
296
367
 
297
- ## Testing — abide/test/*
368
+ ## Testing
298
369
 
299
370
  ### Testing — @documentation testing
300
371
 
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).
372
+ - `abide/test/createTestApp` — boot an in-process app for tests: typed `app.rpc.<rpc>` / `app.sockets.<name>` off the project's real surface. Also `TestApp`, `RpcClient`, `SocketClient`.
302
373
 
303
- ### Plumbing — @documentation plumbing
374
+ ### Testing plumbing — @documentation plumbing
304
375
 
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, …).
376
+ - `abide/test/createScriptedSurface` — a scripted MCP surface for driving an `AgentEngine` in tests.
377
+ - `abide/test/assertAgentFrameConformance` — assert an engine's frame stream satisfies the neutral `AgentFrame` contract.
307
378
 
308
379
  ## Generated machine surfaces
309
380
 
310
- Runtime routes the framework serves:
381
+ Runtime routes the framework serves (the internal `/__abide/config|dev|reload|…`
382
+ routes are deliberate plumbing and not listed):
311
383
 
312
384
  | Route | Serves |
313
385
  | --- | --- |
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
+ | `/openapi.json` | OpenAPI 3 document, generated from every schema-bearing RPC |
387
+ | `/__abide/mcp` | MCP endpoint (streamable HTTP): tools, prompts, resources |
388
+ | `/__abide/health` | Health JSON (the app's `health()` hook merged in) |
389
+ | `/__abide/sockets` | Multiplexed WebSocket hub; HTTP face `/__abide/sockets/<name>` (GET tail, POST publish) |
390
+ | `/__abide/cli` | CLI binary download (per-platform thin client + server) |
391
+ | `/__abide/hot` | Dev hot-update stream the browser bridge consumes for HMR |
392
+ | `/__abide/identity` | App identity (name/version) for CLI/bundle connect handshakes |
393
+ | `/__abide/inspector` | Opt-in inspector UI (gated by `ABIDE_ENABLE_INSPECTOR`) |
322
394
 
323
395
  ## Environment variables
324
396
 
325
397
  | Variable | Effect |
326
398
  | --- | --- |
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
+ | `PORT` | Server listen port |
400
+ | `APP_URL` | Public origin; its pathname becomes the mount base (e.g. `/v2`) for `url()`/routing |
401
+ | `ABIDE_APP_URL` | Default remote server URL the CLI/bundle connects to |
402
+ | `ABIDE_APP_TOKEN` | Bearer token the CLI/bundle sends to a remote abide server |
403
+ | `ABIDE_CLIENT_TIMEOUT` | ms ceiling on a browser/CLI remote RPC call (1–600000) |
404
+ | `ABIDE_IDLE_TIMEOUT` | Bun per-connection idle timeout, seconds (default 10) |
405
+ | `ABIDE_MAX_REQUEST_BODY_SIZE` | Server-wide max request body bytes |
406
+ | `ABIDE_REACHABLE_TIMEOUT` | ms per `reachable()` probe (default 3000, 100–60000) |
407
+ | `ABIDE_REACHABLE_TTL` | ms `reachable()` freshness / poll cadence (default 30000, 1000–600000) |
408
+ | `ABIDE_DATA_DIR` | Override the per-user app data dir on every platform |
409
+ | `ABIDE_LOG_FORMAT` | `json` emits structured log records instead of human lines |
410
+ | `ABIDE_ENABLE_INSPECTOR` | `true` mounts the inspector (requires `@abide/inspector`) |
411
+ | `ABIDE_INSPECT` | Non-empty enables the desktop webview devtools |
412
+ | `DEBUG` | Enable diagnostic channels (`abide:cache`, `abide:rpc`, …); `-abide` silences all |
341
413
 
342
414
  ---
343
415
 
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.
416
+ Mirrors the `exports` map in `package.json`; run
417
+ `bun run packages/abide/scripts/readmeSurfaces.ts` after adding or renaming an
418
+ export to catch any untagged export and re-derive the slug grouping.