@abide/abide 0.53.0 → 0.55.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 (129) hide show
  1. package/AGENTS.md +330 -494
  2. package/CHANGELOG.md +106 -0
  3. package/README.md +166 -146
  4. package/package.json +3 -4
  5. package/src/checkAbide.ts +1 -1
  6. package/src/lib/server/rpc/defineRpc.ts +2 -1
  7. package/src/lib/server/rpc/types/RpcHelper.ts +29 -22
  8. package/src/lib/server/runtime/SSR_SWAP_SCRIPT.ts +3 -2
  9. package/src/lib/server/runtime/amendBroadcaster.ts +53 -0
  10. package/src/lib/server/runtime/createUiPageRenderer.ts +54 -20
  11. package/src/lib/server/runtime/runWithRequestScope.ts +1 -0
  12. package/src/lib/server/runtime/types/RequestStore.ts +9 -0
  13. package/src/lib/server/sockets/amendFamilyEntry.ts +39 -0
  14. package/src/lib/server/sockets/createSocketDispatcher.ts +9 -0
  15. package/src/lib/server/sockets/defineSocket.ts +11 -1
  16. package/src/lib/shared/AMEND_TOPIC_PREFIX.ts +9 -0
  17. package/src/lib/shared/SEEDS.ts +33 -0
  18. package/src/lib/shared/SOCKET_SEED.ts +12 -0
  19. package/src/lib/shared/amend.ts +48 -0
  20. package/src/lib/shared/amendBroadcastSlot.ts +19 -0
  21. package/src/lib/shared/applyAmendLocally.ts +20 -0
  22. package/src/lib/shared/attachRpcSelectorMethods.ts +5 -5
  23. package/src/lib/shared/buildSocketOverChannel.ts +9 -2
  24. package/src/lib/shared/cache.ts +203 -84
  25. package/src/lib/shared/cacheReaderSocketSlot.ts +11 -0
  26. package/src/lib/shared/createCacheStore.ts +8 -0
  27. package/src/lib/shared/createRemoteFunction.ts +4 -1
  28. package/src/lib/shared/encodeSeedValue.ts +24 -0
  29. package/src/lib/shared/jsonSchemaForType.ts +3 -16
  30. package/src/lib/shared/peek.ts +4 -7
  31. package/src/lib/shared/socketTailsSlot.ts +13 -0
  32. package/src/lib/shared/types/AmendApply.ts +15 -0
  33. package/src/lib/shared/types/CacheReaderHook.ts +11 -0
  34. package/src/lib/shared/types/RemoteFunction.ts +30 -19
  35. package/src/lib/shared/types/SocketTails.ts +13 -0
  36. package/src/lib/shared/types/SsrPayload.ts +6 -0
  37. package/src/lib/test/createTestApp.ts +6 -0
  38. package/src/lib/ui/amendReaderHook.ts +125 -0
  39. package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +1 -6
  40. package/src/lib/ui/compile/analyzeComponent.ts +0 -2
  41. package/src/lib/ui/compile/awaitSubjectExpr.ts +23 -0
  42. package/src/lib/ui/compile/cachedShadowProgram.ts +39 -0
  43. package/src/lib/ui/compile/compileComponent.ts +1 -3
  44. package/src/lib/ui/compile/componentOrdinals.ts +39 -0
  45. package/src/lib/ui/compile/desugarSignals.ts +65 -36
  46. package/src/lib/ui/compile/generateBuild.ts +25 -24
  47. package/src/lib/ui/compile/generateSSR.ts +37 -24
  48. package/src/lib/ui/compile/hoistableAwaits.ts +1 -11
  49. package/src/lib/ui/compile/interpolationClassifierForRoot.ts +8 -13
  50. package/src/lib/ui/compile/isSpuriousAsyncReadDiagnostic.ts +46 -5
  51. package/src/lib/ui/compile/lowerCompoundAssignment.ts +49 -0
  52. package/src/lib/ui/compile/lowerContext.ts +26 -6
  53. package/src/lib/ui/compile/lowerDocAccess.ts +19 -31
  54. package/src/lib/ui/compile/lowerScript.ts +1 -11
  55. package/src/lib/ui/compile/lowerUpdateExpression.ts +32 -0
  56. package/src/lib/ui/compile/renameSignalRefs.ts +25 -45
  57. package/src/lib/ui/compile/seedTypeClassifierForRoot.ts +10 -15
  58. package/src/lib/ui/compile/shadowInterpolationClassifier.ts +1 -1
  59. package/src/lib/ui/compile/types/AnalyzedComponent.ts +3 -5
  60. package/src/lib/ui/compile/wrapReactionCellSources.ts +10 -6
  61. package/src/lib/ui/compile/wrapReactiveSeeds.ts +44 -0
  62. package/src/lib/ui/compile/wrapSeed.ts +28 -0
  63. package/src/lib/ui/computed.ts +16 -0
  64. package/src/lib/ui/createScope.ts +4 -3
  65. package/src/lib/ui/dom/anchorCursor.ts +2 -1
  66. package/src/lib/ui/dom/anchoredBranch.ts +14 -3
  67. package/src/lib/ui/dom/appendStatic.ts +11 -20
  68. package/src/lib/ui/dom/appendText.ts +41 -43
  69. package/src/lib/ui/dom/appendTextAt.ts +10 -57
  70. package/src/lib/ui/dom/awaitBlock.ts +31 -45
  71. package/src/lib/ui/dom/awaitSubject.ts +118 -0
  72. package/src/lib/ui/dom/claimText.ts +38 -0
  73. package/src/lib/ui/dom/cloneStatic.ts +2 -8
  74. package/src/lib/ui/dom/discardAndRebuild.ts +23 -0
  75. package/src/lib/ui/dom/discardBoundary.ts +5 -4
  76. package/src/lib/ui/dom/each.ts +3 -5
  77. package/src/lib/ui/dom/fillBoundary.ts +2 -1
  78. package/src/lib/ui/dom/hydrate.ts +5 -12
  79. package/src/lib/ui/dom/mountChild.ts +73 -5
  80. package/src/lib/ui/dom/mountSwappableRange.ts +2 -1
  81. package/src/lib/ui/dom/openMarker.ts +2 -4
  82. package/src/lib/ui/dom/outlet.ts +4 -3
  83. package/src/lib/ui/dom/readCell.ts +26 -11
  84. package/src/lib/ui/dom/skeleton.ts +2 -7
  85. package/src/lib/ui/dom/switchBlock.ts +2 -1
  86. package/src/lib/ui/dom/tryBlock.ts +27 -39
  87. package/src/lib/ui/dom/when.ts +6 -2
  88. package/src/lib/ui/linked.ts +14 -0
  89. package/src/lib/ui/resumeSeedScript.ts +1 -1
  90. package/src/lib/ui/router.ts +12 -16
  91. package/src/lib/ui/runtime/AsyncCellError.ts +2 -1
  92. package/src/lib/ui/runtime/BRANCH_SEGMENT.ts +24 -0
  93. package/src/lib/ui/runtime/CELL_SEED.ts +4 -6
  94. package/src/lib/ui/runtime/DOC_SEED.ts +5 -6
  95. package/src/lib/ui/runtime/RENDER.ts +7 -5
  96. package/src/lib/ui/runtime/RESUME.ts +5 -7
  97. package/src/lib/ui/runtime/SEED_MARKS.ts +14 -0
  98. package/src/lib/ui/runtime/SuspenseSignal.ts +2 -2
  99. package/src/lib/ui/runtime/claimChild.ts +5 -7
  100. package/src/lib/ui/runtime/claimExpected.ts +2 -6
  101. package/src/lib/ui/runtime/claimMarker.ts +13 -0
  102. package/src/lib/ui/runtime/claimRun.ts +22 -0
  103. package/src/lib/ui/runtime/consumeSeed.ts +38 -0
  104. package/src/lib/ui/runtime/createAsyncCell.ts +56 -8
  105. package/src/lib/ui/runtime/createComputedNode.ts +1 -0
  106. package/src/lib/ui/runtime/createDoc.ts +5 -1
  107. package/src/lib/ui/runtime/createEffectNode.ts +1 -0
  108. package/src/lib/ui/runtime/createSignalNode.ts +1 -0
  109. package/src/lib/ui/runtime/parkCursor.ts +10 -0
  110. package/src/lib/ui/runtime/readNode.ts +21 -1
  111. package/src/lib/ui/runtime/renderPath.ts +2 -2
  112. package/src/lib/ui/runtime/routeBareSeed.ts +28 -0
  113. package/src/lib/ui/runtime/runHydrationPass.ts +69 -0
  114. package/src/lib/ui/runtime/runNode.ts +14 -0
  115. package/src/lib/ui/runtime/types/Cell.ts +4 -2
  116. package/src/lib/ui/runtime/types/Doc.ts +3 -1
  117. package/src/lib/ui/runtime/types/HydrationCursor.ts +8 -0
  118. package/src/lib/ui/runtime/types/ReactiveNode.ts +5 -0
  119. package/src/lib/ui/settleAsyncCells.ts +30 -3
  120. package/src/lib/ui/socketProxy.ts +17 -1
  121. package/src/lib/ui/startClient.ts +23 -0
  122. package/src/lib/ui/tryEncodeResume.ts +6 -15
  123. package/src/lib/ui/types/Scope.ts +1 -1
  124. package/src/lib/ui/watch.ts +21 -16
  125. package/src/serverEntry.ts +15 -0
  126. package/src/lib/shared/patch.ts +0 -41
  127. package/src/lib/ui/compile/COMPOUND_ASSIGNMENT_OPERATORS.ts +0 -19
  128. package/src/lib/ui/dom/mountStreamedChild.ts +0 -84
  129. package/src/lib/ui/dom/readCellBlocking.ts +0 -29
package/AGENTS.md CHANGED
@@ -1,610 +1,446 @@
1
1
  # AGENTS.md — abide complete surface map
2
2
 
3
- > This file is the exhaustive map of abide's public surface: every `exports` key,
4
- > grouped by namespace, with its import specifier and a one-line spec. Where the
5
- > `README.md` teaches the three foundational primitives (RPC, socket, component),
6
- > this maps the whole API in one read. `CONTEXT.md` is the domain glossary and
7
- > `docs/adr/` carries the rationale behind each decision.
3
+ > This file is the exhaustive map of abide's public surface: every `exports`
4
+ > key, grouped by namespace, with its import specifier and a one-line spec, plus
5
+ > the CLI, generated routes, env vars, and the `.abide` grammar. The README is
6
+ > the opposite — a curated intro to the three foundational primitives (RPCs,
7
+ > sockets, components). CONTEXT.md is the domain glossary; `docs/adr/` holds the
8
+ > rationale.
8
9
  >
9
- > Ground rule: **no barrels.** Every public name has its own module path
10
- > (`abide/server/GET`, `abide/shared/cache`, `abide/ui/state`, …) — there is no
11
- > umbrella `index.ts`, so importing one name never drags side-effecting siblings
12
- > into the bundle. The namespace marks the side a name runs on: `abide/server/*`
13
- > is server-side, `abide/ui/*` is client-side, `abide/shared/*` is isomorphic
14
- > (same callable, same intent both sides — usually identical behaviour, e.g.
15
- > `HttpError`; sometimes a side-swapped runtime behind one intent, e.g. `cache`,
16
- > `invalidate`, `refresh`).
10
+ > **No barrels.** Every public name has its own module path — there is no
11
+ > umbrella `index.ts`, so importing one name never drags in side-effecting
12
+ > siblings. The namespace marks the side a name runs on: `abide/server/*` is
13
+ > server-only, `abide/ui/*` is client-only, `abide/shared/*` is isomorphic (same
14
+ > callable, same intent on both sides — usually identical behaviour, e.g.
15
+ > `HttpError`; sometimes a side-swapped runtime behind one intent, e.g.
16
+ > `invalidate`, `refresh`, `amend`).
17
17
  >
18
- > Package `@abide/abide` runs on **Bun ≥ 1.3.0** with **one direct dependency**
19
- > (TypeScript). The surface bullets below write the short `abide/…` specifier for
20
- > readability — the installable path prefixes the package name, so `abide/server/GET`
21
- > imports from `@abide/abide/server/GET`. These are the public `exports` keys, not
22
- > the on-disk file paths.
18
+ > Package `@abide/abide`, one runtime (Bun ≥ 1.3.0), one direct dependency
19
+ > (TypeScript). Below, a leading `@abide/abide/...` is the **import specifier**
20
+ > (an `exports` key); a `src/...` path is a **file-based convention** the bundler
21
+ > reads.
23
22
 
24
23
  ## The premise
25
24
 
26
- One typed RPC declaration branches to every surface:
25
+ One typed RPC declaration fans out to every surface:
27
26
 
28
27
  ```text
29
- export const getMessages = GET(handler, { schemas })
28
+ getMessages (src/server/rpc/getMessages.ts)
30
29
  │
31
- ┌──────────┬──────────────┼──────────────┬──────────────┐
32
- ▼ ▼ ▼ ▼ ▼
33
- SSR call browser MCP tool CLI OpenAPI op
34
- the bare fetch (read-only, subcommand in
35
- call, typed proxy auto-exposed) abide cli /openapi.json
36
- in-process fn(args) getMessages
30
+ ┌──────────────┬──────────┼───────────────┬───────────────────┐
31
+ ▼ ▼ ▼ ▼ ▼
32
+ SSR call browser MCP tool CLI OpenAPI op
33
+ (in-proc) fetch get_messages app get-messages GET /rpc/
34
+ smart read typed proxy (read → auto) --limit 20 getMessages
37
35
  ```
38
36
 
39
- A **typed handler input** flips those surfaces on: the input type is projected to
40
- JSON Schema at build (ADR-0030), so a plainly-typed handler auto-exposes to the
41
- CLI and — for read-only methods (`GET`/`HEAD`) — MCP with no hand-written
42
- `schemas.input`. A declared `schemas.input` adds runtime validation (422) on top.
43
- A mutating method (`POST`/`PUT`/`PATCH`/`DELETE`) never auto-exposes to MCP —
44
- opt it in with `clients: { mcp: true }`.
37
+ A **typed input** unlocks the CLI, and for a read-only method (GET/HEAD) the MCP
38
+ tool: the handler's input-parameter type is projected to JSON Schema at build
39
+ (ADR-0030), so a plainly-typed handler auto-exposes with no hand-written
40
+ `schemas.input`. A declared `schemas.input` (a Standard Schema) adds runtime
41
+ validation on top; it is not what flips the surfaces on. A mutating method
42
+ (POST/PUT/PATCH/DELETE) never auto-exposes to MCP — it needs explicit
43
+ `clients: { mcp: true }`.
45
44
 
46
45
  ## File-based conventions
47
46
 
48
- The bundler (`abideResolverPlugin`) reads these paths. Import aliases into the
49
- tree: `$server`, `$ui`, `$shared`, `$mcp`, `$cli`.
47
+ The bundler reads these paths by convention (resolved by `abideResolverPlugin`):
50
48
 
51
49
  | Path | Meaning |
52
- |---|---|
53
- | `src/server/rpc/<name>.ts` | One RPC endpoint per file; URL is `/rpc/<name>`. Export name = file stem. Flat — dynamic `[seg]` segments are rejected here (pass ids as args). |
54
- | `src/server/sockets/<name>.ts` | One `socket(...)` per file; socket name = path under `sockets/` (nested kept, e.g. `orders/new`). Multiplexed over `/__abide/sockets`. |
55
- | `src/ui/pages/<route>/page.abide` | Folder-based routed page; URL = directory path. Dynamic segments keep `[name]` / `[[name]]` / `[...rest]` shape → `page.params`. |
56
- | `src/ui/pages/<route>/layout.abide` | Nested layout wrapping the route chain below; renders it at `{children()}`. |
57
- | `src/ui/public/` | Static assets, served at site root; embedded for standalone compile. |
58
- | `src/ui/app.html` | Custom document shell (optional; a bundled default is used otherwise). |
59
- | `src/mcp/prompts/<name>.md` | Markdown MCP prompts; name = path minus `.md`, `/`→`-` (`code/review.md` → `code-review`). |
60
- | `src/mcp/resources/` | MCP resources, embedded for standalone builds. |
61
- | `src/server/config.ts` | Re-exported as `abide:config` for boot-time `env(schema)` validation (optional). |
62
- | `src/app.ts` | Re-exported as `abide:app` — the `AppModule` request hooks (optional). |
63
- | `src/bundle/window.ts` | Default-export `BundleWindow` config baked into the desktop launcher (optional). |
64
- | `src/.abide/*.d.ts` | Editor `.d.ts` artifacts written by `build()` (not at boot). |
65
- | `dist/` | Build output; client bundle → `dist/_app/` (dev: per-generation `dist/_app.gen-<id>`). |
50
+ | --- | --- |
51
+ | `src/server/rpc/<name>.ts` | One RPC per file; the path is the URL (`/rpc/<name>`), the export name matches the file stem. |
52
+ | `src/server/sockets/<name>.ts` | One `socket(...)` per file; the socket name is the file stem, loaded lazily on first sub/pub frame. |
53
+ | `src/mcp/prompts/<name>.md` | A Markdown MCP prompt; `{{placeholder}}` args are substituted at render. |
54
+ | `src/mcp/resources/<name>` | A static MCP resource, gzip-embedded into the build. |
55
+ | `src/server/config.ts` | Boot-time `env(...)` schema, eager-imported so env validation runs at startup. |
56
+ | `src/app.ts` | Optional app hooks (`init`, `handle`, `handleError`, `health`, `forwardHeaders`) — the `AppModule` shape. |
57
+ | `src/ui/pages/**/page.abide` | A page route; `[name]` path segments become `page.params`. |
58
+ | `src/ui/pages/**/layout.abide` | A layout that wraps nested pages via the router outlet; persists across same-chain navigation. |
59
+ | `src/ui/public/` | Static assets, gzip-embedded and served from the app root. |
60
+ | `src/bundle/window.ts` | Optional desktop-bundle window config (default export, the `BundleWindow` shape). |
61
+ | `src/.abide/*.d.ts` | Generated editor/type artifacts (rpc route maps, prop shapes) written by `build`. |
62
+ | `dist/` | Build output; `dist/_app` is the server-loaded app dir (override with `ABIDE_APP_DIR`). |
66
63
 
67
64
  ## CLI
68
65
 
66
+ Every `abide <command>` (the `abide` bin):
67
+
69
68
  | Command | Does |
70
- |---|---|
71
- | `abide scaffold <name> [--no-install] [--no-dev]` | Scaffold the bundled template, install, and (TTY only) start dev. |
72
- | `abide dev` | Build the client + run the server with file-watch rebuild/restart and browser live-reload. |
73
- | `abide build` | Single client build into `dist/_app/` (no server). |
74
- | `abide check` | Type-check every `.abide` template + props via the shadow program; non-zero on errors. |
75
- | `abide start` | Run the production server against a built `dist/`. |
76
- | `abide run <file> [args…]` | Run a script under the abide preload (same runtime as the server). |
77
- | `abide compile [--target=<bun-…>] [--out=<path>]` | Build a standalone server executable. |
78
- | `abide cli [--target=] [--out=] [--platforms=a,b,c]` | Build the thin CLI binary (ships the server beside it); `--platforms` cross-compiles. |
79
- | `abide bundle` | Assemble a movable, self-contained (unsigned) desktop app bundle for this platform. |
80
- | `abide lsp` | Run the `.abide` language server over stdio (JSON-RPC) for editors. |
81
- | `abide init-agent` | Write/refresh a `CLAUDE.md` pointer to this surface map (non-scaffolded projects). |
82
-
83
- For tests, add `preload = ["@abide/abide/preload"]` under `[test]` in
84
- `bunfig.toml` and run `bun test`.
69
+ | --- | --- |
70
+ | `abide scaffold <name>` | Scaffold a project from the bundled template, install deps, and (on a TTY) start dev. `--no-install` / `--no-dev` opt out. |
71
+ | `abide dev` | Dev orchestrator: build the client, spawn the server, watch `src/`, rebuild + restart with browser live-reload. |
72
+ | `abide build` | One client build into `dist/_app/`, no server (CI / static deploys). |
73
+ | `abide start` | Run the production server against an already-built `dist/`. |
74
+ | `abide run <file> [args...]` | Run a script under the abide preload (same runtime as the server); argv after the file is forwarded. |
75
+ | `abide compile [--target] [--out]` | Build a standalone server executable (client assets embedded) for a platform. |
76
+ | `abide cli [--target] [--out] [--platforms]` | Build the thin remote-client CLI binary; `--platforms` cross-compiles per platform. |
77
+ | `abide bundle` | Assemble a self-contained (unsigned) desktop app bundle for the host platform. |
78
+ | `abide check` | Type-check every `.abide` component's template + props through its shadow program. |
79
+ | `abide lsp` | Run the `.abide` language server over stdio (JSON-RPC) for editor diagnostics. |
80
+ | `abide init-agent` | Write/refresh a `CLAUDE.md` pointer to this surface map for a non-scaffolded project. |
81
+
82
+ For tests, don't use `abide run` — add `preload = ["@abide/abide/preload"]` under
83
+ `[test]` in `bunfig.toml` and use `bun test`, so tests run in the server runtime.
85
84
 
86
85
  ## Authoring contracts
87
86
 
88
- - **RPC** (`src/server/rpc/<name>.ts`) — `export const x = GET(handler, opts?)`.
89
- The handler receives `InferOutput<schemas.input>` (intersected with
90
- `InferOutput<schemas.files>` for multipart), or reads its arg shape structurally
91
- off the function when schemaless. Inside it, `request()` / `cookies()` reach the
92
- request scope; it returns `json` / `jsonl` / `sse` / `error` / `redirect` /
93
- `error.typed(...)` / a raw `Response`. `opts` (ADR-0020): `schemas: { input?,
94
- output?, files? }` (nested, **not** flat `inputSchema`/`filesSchema`; there is
95
- **no** `outbox` field), `clients: { browser, mcp, cli }`, `crossOrigin`,
96
- `timeout` (ms → 504), `maxBodySize`; read helpers add `cache` and `stream`
97
- (a compile error on mutating helpers). Query/path/form args auto-coerce from the
98
- endpoint's typed shape at build (ADR-0028) — no `z.coerce`; an unparseable value
99
- stays a string so the schema raises an honest 422.
100
- - **Consume forms** — the **bare call `fn(args)` is the smart read** (cached,
101
- coalesced, reactive; in-process during SSR and baked into the HTML for warm
102
- hydration — there is no `cache()` wrapper). Around it: `fn.raw(args, opts?)`
103
- (raw `Response`), `fn.refresh` / `fn.invalidate` / `fn.patch` / `fn.peek` /
104
- `fn.pending` / `fn.refreshing` / `fn.error` / `fn.watch` / `fn.isError`. A
105
- streaming handler (`jsonl`/`sse`) makes the bare call return a
106
- `NamedAsyncIterable<Frame>` (the iterable IS the value; `await`-ing it is a
107
- compile error; `patch` is dropped).
108
- - **Socket** (`src/server/sockets/<name>.ts`) — `export const x =
109
- socket<T>(opts?)`. `opts`: `tail` (retention depth; defaults to keeping the last
110
- frame), `ttl` (per-frame eviction ms), `clientPublish` (accept `pub` frames from
111
- the wire; off by default), `schema` (validates publishes, projects MCP/CLI),
112
- `clients`. `Socket<T>` is an `AsyncIterable<T>` — iterate for the live stream,
113
- `.tail(count?)` to seed from retention first.
114
- - **Page / layout** (`.abide`) — dynamic `[id]` segments arrive on `page.params`;
115
- a `layout.abide` renders the route chain below at `{children()}`. Reactive
116
- navigation reads `page` (`route`/`params`/`url`/`navigating`) and writes via
117
- `navigate`.
118
- - **`src/app.ts` / `src/server/config.ts`** — the `AppModule` hooks
119
- (`init`/`handle`/`handleError`/`health`/`forwardHeaders`) and the boot-time
120
- `env(schema)` config, respectively.
87
+ - **RPC** (`src/server/rpc/<name>.ts`). Declare one `export const <name> =
88
+ GET|POST|PUT|PATCH|DELETE|HEAD(handler, opts?)`. The handler receives the typed
89
+ input (`InferOutput<schemas.input>` when a schema is declared, else its own
90
+ parameter type), reads request context via `request()` / `cookies()` /
91
+ `server()`, and returns `json` / `jsonl` / `sse` / `error` / `redirect` / a raw
92
+ `Response`. `opts` is `{ schemas: { input?, output?, files? }, clients: {
93
+ browser?, mcp?, cli? }, crossOrigin?, maxBodySize?, timeout?, cache? }` — plus
94
+ `stream?` on the read helpers only (a write has no replayable stream). There is
95
+ no flat `inputSchema`/`filesSchema` and no `outbox`. Query / path / form args
96
+ auto-coerce from the endpoint's typed shape (ADR-0028) — no `z.coerce`.
97
+ - **Consuming an RPC.** The bare call `fn(args)` is the smart read — cached,
98
+ coalesced, reactive, in-process during SSR (its value is baked into the HTML
99
+ for warm hydration) and swapped to `fetch` in the browser (there is no
100
+ `cache()` wrapper). Also: `fn.raw(args, init?)` → the raw `Response`;
101
+ `fn.refresh` / `fn.invalidate` / `fn.amend` / `fn.peek` / `fn.pending` /
102
+ `fn.refreshing` / `fn.error` / `fn.watch`; a `jsonl`/`sse` handler makes the
103
+ bare call return a `Subscribable`. Typed errors come from returning an
104
+ `error.typed(name, status, schema?)` branch and narrow via `fn.isError`.
105
+ - **Socket** (`src/server/sockets/<name>.ts`). `export const <name> =
106
+ socket<T>(opts?)` — a `Socket<T>` (isomorphic `AsyncIterable<T>`). Opts (server
107
+ only): `tail` (retain the last N frames), `ttl` (evict retained frames older
108
+ than N ms), `clientPublish` (allow client publishes), `schema` (validate
109
+ payloads + type `T`), `clients` (expose to mcp/cli). HTTP face at
110
+ `/__abide/sockets/<name>`; all sockets multiplex onto one ws.
111
+ - **Page / layout** (`.abide`). A `[id]` path segment lands in `page.params.id`;
112
+ a layout renders its nested route at the router outlet. Read the active route
113
+ via `page` (`page.url`, `page.params`, `page.navigating`), move with `navigate`
114
+ and build hrefs with `url`.
115
+ - **`app.ts` / `config.ts`.** `app.ts` default-exports the `AppModule` hooks;
116
+ `config.ts` runs `env(schema)` at module top level so boot fails fast on bad
117
+ env.
121
118
 
122
119
  ## .abide template grammar
123
120
 
124
- A component is valid HTML: a leading `<script>` (module scope — imports hoist
125
- here for the whole template), markup with `{expr}` interpolation and directive
126
- attributes, `{#…}` control-flow blocks, and an optional root `<style>`. Reactive
127
- primitives are **imported by their own module paths** and resolved by import
128
- binding (alias-safe); the one ambient reader is `props()`.
129
-
130
- A `<script>` and a `<style>` are **not** component-root-only: either may sit
131
- inside a control-flow branch. A nested `<script>` declares branch-local
132
- `state` / `state.computed` / `state.linked` (re-seeded per mount, no module
133
- imports); a nested `<style>` scopes to its sibling subtree, not the whole
134
- component. So a *root* `<style>` is component-scoped, a nested one is branch-scoped.
121
+ A `.abide` component is a leading `<script>` (module imports + setup), then
122
+ HTML-with-mustaches, then optional `<style>`. The reactive primitives are
123
+ imported by their own module paths and resolved by import binding (alias-safe):
124
+ `state` (`abide/ui/state`, with `.computed` / `.linked`), `watch`
125
+ (`abide/ui/watch`), `html` (`abide/ui/html`), `snippet` (`abide/shared/snippet`).
126
+ The one ambient reader is `props()` (`abide/ui/props`) — a required import, not
127
+ `scope()` (which is internal plumbing now, never authored). In `.abide` source a
128
+ `state` cell is read and reassigned as a **plain variable** (`count`,
129
+ `count += 1`); the compiler desugars that to the cell — `.value` is not the
130
+ authoring surface.
135
131
 
136
- ### Reactive state
132
+ **Reactive state**
137
133
 
138
134
  | Form | Meaning |
139
- |---|---|
140
- | `import { state } from '@abide/abide/ui/state'` | The reactive-cell primitive; called bare (`let count = state(0)`). Read and reassigned as a plain variable — the compiler desugars to the cell. `state(v, transform?)` gates writes through `transform(next, previous)`. |
135
+ | --- | --- |
136
+ | `let x = state(v, transform?)` | Writable cell; `transform` gates every write. Read/assign as a plain variable. |
141
137
  | `state.computed(() => …)` | Read-only derived cell (lazy, never serialized). |
142
138
  | `state.linked(() => src, transform?)` | Writable cell reseeded when the thunk's deps change. |
143
- | `import { watch } from '@abide/abide/ui/watch'` | The single reaction primitive — `watch(source, handler)` over a cell, a cell array, a socket/stream, or an rpc; bare `watch(thunk)` is an auto-tracked effect. Client-only, stripped from SSR. |
144
- | `import { props } from '@abide/abide/ui/props'` | The prop reader — `const { name = fallback, ...rest } = props()`. A required import (not ambient, not `scope()`). |
145
-
146
- A writable computed is expressed at the binding: `bind:value={{ get, set }}`.
147
- `scope()` is internal plumbing now, never authored; `effect` is compiler-emitted —
148
- authors write `watch`, not a bare `effect(...)`.
139
+ | `watch(source, handler)` | The single reaction primitive (client-only) over a cell, a cell array, a socket/stream, or an rpc; bare `watch(thunk)` is an auto-tracked effect. |
140
+ | `const { name = fallback, ...rest } = props()` | The prop reader (ambient; required import). |
149
141
 
150
- ### Bindings & directives
142
+ **Bindings / directives**
151
143
 
152
- | Syntax | Meaning |
153
- |---|---|
154
- | `{expr}` | Escaped text interpolation. |
155
- | `{html\`…\`}` / `{html(str)}` | `html`-branded raw markup, inserted unescaped. |
156
- | `name={expr}` | Attribute (or component prop) bound to an expression. |
157
- | `on<event>={fn}` | Event listener (`onclick={…}`). |
144
+ | Form | Meaning |
145
+ | --- | --- |
146
+ | `{expr}` | Reactive text interpolation (escaped). |
147
+ | `{html(...)}` / `{html\`…\`}` | Insert trusted raw HTML unescaped. |
148
+ | `name={expr}` | Reactive attribute (present/absent for booleans). |
149
+ | `on<event>={fn}` | Event listener (`onclick={fn}`). |
158
150
  | `bind:value` / `bind:checked` / `bind:group` | Two-way form binds. |
159
151
  | `bind:value={{ get, set }}` | Derived two-way bind. |
160
152
  | `class:name={cond}` | Toggle a class. |
161
153
  | `style:property={value}` | Set one style property. |
162
- | `attach={fn}` | Run a node-lifetime attachment against the element. |
163
- | `{...spread}` | Spread an object's keys — props on a component, attributes on an element. |
164
-
165
- ### Control flow
166
-
167
- Mustache `{#…}` blocks, not `<template>`. The rendered blocks are
168
- `if`/`for`/`await`/`switch`/`try`; `snippet` is a declaration block.
169
-
170
- | Block | Form |
171
- |---|---|
172
- | `{#if}` / `{:else if}` / `{:else}` / `{/if}` | Conditional chain (`{:else if}` has a space). |
173
- | `{#for item, i of list by key}` / `{/for}` | Keyed list; `{#for await x of source}` iterates an AsyncIterable. |
174
- | `{#await p}` / `{:then v}` / `{:catch e}` / `{:finally}` / `{/await}` | The explicit async opt-in (pending branch + `{:then}` narrowing). |
175
- | `{#switch}` / `{:case}` / `{:default}` / `{/switch}` | Value switch. |
176
- | `{#try}` / `{:catch}` / `{:finally}` / `{/try}` | Render error boundary. |
177
- | `{#snippet name(args)}…{/snippet}` | Reusable builder; called `{name(args)}`. |
178
-
179
- Async reads have no ceremony, and `await` is the marker that picks the mode
180
- (ADR-0042). A **bare call in an interpolation** (`{getMessages({ limit })}`)
181
- *streams*: a peek that reads `undefined` while pending (typed `T | undefined`,
182
- auto-streaming on SSR), composing with `?? fallback`, `?.`, `{#if}`, and
183
- attributes; pair it with `.pending(...)` / `.error(...)` probes. `{await fn()}`
184
- means *resolved*: the server blocks the flush AND the client **suspends** the
185
- reading region (renders nothing) until it settles, so the value is never pending
186
- at the read — `{(await fn()).field}` needs no `?.` and never derefs a pending
187
- `undefined`. A refetch never re-suspends (stale-while-revalidate); warm-seed keeps
188
- a hydrating read `refreshing()`, so no flash. (Consequently a no-`await` `async`
189
- thunk — `computed(async () => getFoo())` — is a *streaming* cell, not blocking;
190
- `await` is the sole blocking marker.) `{#await}` is the opt-in for branch structure
191
- and `{:then}` narrowing — not the default.
192
-
193
- Components are capitalised tags; the content nested in a component renders where
194
- it calls `{children()}` (`{#if children}{children()}{:else}…{/if}` is the
195
- fallback — no `<slot>` element, no named slots). The `<slot>` element, the
196
- `<template name>` snippet form, and `<template if>`/`<template each>`/… control
197
- flow were **removed** — a bare `<template>` is now an inert element, and using a
198
- removed form throws a migration error.
154
+ | `attach={fn}` | Run `fn(node)` on mount; its return is the teardown. |
155
+ | `{...expr}` | Spread an object's keys as props (component) or attributes (element). |
156
+
157
+ **Control flow** — mustache blocks, not `<template>`:
158
+
159
+ | Block | Branches |
160
+ | --- | --- |
161
+ | `{#if}` … `{/if}` | `{:else if cond}`, `{:else}` |
162
+ | `{#for item, i of list by key}` … `{/for}` | `{#for await item of source}` over an `AsyncIterable`; `{:catch e}` on the async form |
163
+ | `{#await p}` … `{/await}` | `{:then v}`, `{:catch e}`, `{:finally}` |
164
+ | `{#switch subj}` … `{/switch}` | `{:case value}`, `{:default}` |
165
+ | `{#try}` … `{/try}` | `{:catch e}`, `{:finally}` |
166
+ | `{#snippet name(args)}` … `{/snippet}` | A reusable builder, called `{name(args)}` |
167
+
168
+ Async reads have no ceremony: the bare call `{fn(args)}` is the documented way —
169
+ a peek reading `undefined` while pending (auto-streams on SSR), composing with
170
+ `?.` / `??` / `{#if}` / attributes, paired with the `.pending()` / `.error()`
171
+ probes. `{await fn()}` inline blocks SSR until the value is in the HTML;
172
+ `{#await}` is the explicit opt-in for a distinct pending branch, a local
173
+ `{:catch}`, or `{:then}` narrowing.
174
+
175
+ Components are capitalised tags; the content nested in them renders where the
176
+ component calls `{children()}` — the single slot (the `<slot>` element was
177
+ removed; `{#if children}{children()}{:else}…{/if}` is the fallback form, no named
178
+ slots). Snippets are a `{#…}` block, not `<template>`: the `<template name>`
179
+ snippet form and all `<template if>`/`<template each>`/… control flow were
180
+ removed (a bare `<template>` is now an inert element; a removed form throws a
181
+ migration error).
182
+
183
+ `<script>` and `<style>` are **not** component-root-only. Either may sit inside a
184
+ control-flow branch, scoped to that branch's lexical scope: a nested `<script>`
185
+ declares branch-local `state` / `state.computed` / `state.linked` (re-seeded per
186
+ mount, no module imports — it reuses the leading script's imports), and a nested
187
+ `<style>` scopes to its sibling subtree. So a *root* `<style>` is
188
+ component-scoped; a nested one is branch-scoped.
199
189
 
200
190
  ## Server surface — abide/server/*
201
191
 
202
- ### RPC helpers — @documentation rpc
203
-
204
- - `abide/server/GET` — read helper; `export const x = GET(fn, opts?)`. Adds
205
- `cache`/`stream` opts. Read-only, so a typed input auto-exposes it to MCP + CLI.
206
- - `abide/server/HEAD` — read helper, same shape as `GET` (headers only, no body).
207
- - `abide/server/POST` — mutating helper; `RpcSharedOpts` only (`cache`/`stream`
208
- are a compile error). Never auto-exposes to MCP.
209
- - `abide/server/PUT` — mutating helper (idempotent replace); as `POST`.
210
- - `abide/server/PATCH` — mutating helper (partial update); as `POST`.
211
- - `abide/server/DELETE` — mutating helper (removal); as `POST`.
212
-
213
- ### Response helpers — @documentation response
214
-
215
- - `abide/server/json` — `json<T>(data, init?)` → `TypedResponse<T>`; default
216
- `no-store`, honest-JSON encodes Set/Map/bigint/Date (ADR-0029); `undefined` body
217
- → 204.
218
- - `abide/server/jsonl` — `jsonl<Frame>(iterable, init?)`; JSON Lines stream
219
- (`application/jsonl`), cancellation calls `iter.return()`, a generator error
220
- emits a final `{"$error":…}` line.
221
- - `abide/server/sse` — `sse<Frame>(iterable, init?)`; `text/event-stream` with a
222
- 15 s keepalive comment; an error emits an `event: error` frame.
223
- - `abide/server/error` — `error(status, message?, init?)` → `TypedResponse<never>`
224
- (positional status wins). `error.typed(name, status, schema?)` returns a typed
225
- error constructor driving `rpc.isError`; `schema` types `.data`, never validates.
226
- - `abide/server/redirect` — `redirect(url, status = 302, init?)`; accepts relative
227
- URLs, sets `Location` + `no-store`.
228
-
229
- ### Request scope — @documentation request-scope
192
+ ### RPC — @documentation rpc
230
193
 
231
- - `abide/server/request` — `request(): Request`; the inbound request from the ALS
232
- scope, throws outside one.
233
- - `abide/server/cookies` — `cookies(): Bun.CookieMap`; live read + `.set` / `.delete`,
234
- flushed as `Set-Cookie` on return.
235
- - `abide/server/server` — `server(): Server`; the active `Bun.serve`, an in-process
236
- no-op server under CLI/MCP/test, else throws.
194
+ - `@abide/abide/server/GET` — read-only RPC helper `GET(fn, opts?)`; infers args from the handler parameter and Return/Errors from its `TypedResponse`/`error.typed` returns; opts `{ schemas, clients, crossOrigin, maxBodySize, timeout, cache, stream }`. Bundler-rewritten; throws if called unprocessed.
195
+ - `@abide/abide/server/HEAD` — read-only RPC helper, identical to `GET`.
196
+ - `@abide/abide/server/POST` — mutating RPC helper; same inference as `GET` but opts omit `stream` (writes aren't replayable), still accept `cache` (a no-ttl write coalesces only).
197
+ - `@abide/abide/server/PUT` — mutating RPC helper, identical to `POST`.
198
+ - `@abide/abide/server/PATCH` — mutating RPC helper, identical to `POST`.
199
+ - `@abide/abide/server/DELETE` — mutating RPC helper, identical to `POST`.
200
+ - `@abide/abide/shared/withJsonSchema` — attaches a `toJSONSchema()` projection to a Standard Schema lacking a native one, feeding OpenAPI / MCP / CLI / bundle-form generation; mutates and returns the same schema. (Isomorphic — lives in `shared/`.)
237
201
 
238
- ### Server rendering — @documentation render
202
+ ### Response — @documentation response
239
203
 
240
- - `abide/server/render` — `render(path, params?, query?)`: SSR a page route to an
241
- HTML string from within a request scope; args mirror `url()`/`navigate`.
204
+ - `@abide/abide/server/json` — `json(data, init?)` → `TypedResponse<T>` with honest-JSON wire encoding (Set/Map/bigint) and default `Cache-Control: no-store`; caller `content-type` / `cache-control` override.
205
+ - `@abide/abide/server/jsonl` — `jsonl(iterable, init?)` streams an `AsyncIterable<Frame>` as `application/jsonl` (one JSON line/frame); consumer cancel flows to `iter.return()`, a throw emits a final `{"$error":…}` line.
206
+ - `@abide/abide/server/sse` — `sse(iterable, init?)` streams frames as `text/event-stream` (`data: <json>\n\n`) with a 15s keepalive comment; a throw emits an `event: error` frame carrying only the message.
207
+ - `@abide/abide/server/error` — `error(status, message?, init?)` → a `text/plain` `TypedResponse<never>` (message defaults to the status reason phrase, positional status wins); member `error.typed(name, status, schema?)` returns a constructor for a named typed error (`{$abideError,data}`) that `fn.isError(e, name)` narrows.
208
+ - `@abide/abide/server/redirect` — `redirect(url, status=302, init?)` → a `TypedResponse<never>` redirect accepting relative URLs, status restricted to 301/302/303/307/308, positional status wins over `init.status`.
209
+ - `@abide/abide/shared/HttpError` — error thrown by remote-function calls on non-2xx, carrying `status`, `statusText`, the raw `response`, and optional `kind`/`data` for typed / validation (422) errors. (Isomorphic.)
210
+ - `@abide/abide/shared/ValidationErrorData` — type of the `data` on a `kind: 'validation'` (422) `HttpError`: `{ issues, fields }` (raw Standard Schema issues plus a field→first-message map). (Isomorphic.)
242
211
 
243
212
  ### Sockets — @documentation sockets
244
213
 
245
- - `abide/server/socket` — `socket<T>(opts?)`: declare one broadcast topic per file
246
- under `src/server/sockets/`. Returns `Socket<T>` (isomorphic `AsyncIterable<T>`).
214
+ - `@abide/abide/server/socket` — declares one named `Socket<T>` per `src/server/sockets/` file (`socket<T>(opts?)` or a `{ schema }` overload inferring `T`); server-only opts `tail`/`ttl`/`clientPublish`/`schema`/`clients`. Throws if called outside a sockets module.
247
215
 
248
- ### Configuration — @documentation configuration
216
+ ### Request scope — @documentation request-scope
249
217
 
250
- - `abide/server/env` — `env(schema)`: validate `Bun.env` against a Standard Schema
251
- at module top-level, returning typed config; fails boot loudly, sync-only.
218
+ - `@abide/abide/server/request` — `request()` returns the inbound `Request` for the current SSR/RPC pass (AsyncLocalStorage); throws outside a request scope.
219
+ - `@abide/abide/server/cookies` — `cookies()` returns the request's lazily-materialized `Bun.CookieMap` jar (reads parse inbound Cookie; `set`/`delete` flush as `Set-Cookie` on return); throws outside a request scope.
220
+ - `@abide/abide/server/server` — `server()` returns the active `Bun.serve` instance (or a no-op in-process server for in-scope dispatch without a booted server); throws before init outside any request scope.
252
221
 
253
- ### App hooks & inspector — @documentation plumbing
222
+ ### Render — @documentation render
254
223
 
255
- - `abide/server/AppModule` — `type AppModule`: the optional hooks exported from
256
- `src/app.ts` (`init`/`handle`/`handleError`/`health`/`forwardHeaders`).
257
- - `abide/server/InspectorContext` — `type InspectorContext`: the capabilities core
258
- injects into `@abide/inspector` when `ABIDE_ENABLE_INSPECTOR=true`.
224
+ - `@abide/abide/server/render` — `render(path, params?, query?)` renders a page route to its HTML string in a fresh nested request scope; inline / blocking-await pages return complete HTML, streaming `{#await}` pages return the shell plus `<abide-resolve>` fragments. For a no-JS surface (email) use blocking awaits.
259
225
 
260
- ### RPC / socket registrars — @documentation plumbing
226
+ ### Configuration — @documentation configuration
261
227
 
262
- - `abide/server/rpc/defineRpc` — `defineRpc(method, url, handler, opts?)`: the real
263
- server-side RPC builder the bundler emits; runs validation → 422, timeout → 504,
264
- and registers the registry entry.
265
- - `abide/server/sockets/defineSocket` — `defineSocket<T>(name, opts?)`: the server
266
- runtime `socket()` binds to; retention defaults to `tail ?? 1`, `ttl` evicts
267
- lazily, schema validates synchronously on publish.
228
+ - `@abide/abide/server/env` — `env(schema)` validates `Bun.env` against a Standard Schema at module top level, returning the typed config and reporting all issues at once on failure; registers the schema for the launcher setup form.
268
229
 
269
- ## Isomorphic surface — abide/shared/*
230
+ ### Beyond the browser — @documentation agent · bundle
270
231
 
271
- ### RPC schema helper — @documentation rpc
232
+ - `@abide/abide/server/agent` — `agent(engine, messages)` runs an `AgentEngine` against the current request's gated MCP surface (forwarding caller auth) and returns its `AgentFrame` stream for a handler to frame via `jsonl()`/`sse()`; must run inside an RPC request scope. Exports the `NeutralMessage`/`AgentFrame`/`AgentSurface`/`AgentEngine` types.
233
+ - `@abide/abide/server/appDataDir` — `appDataDir()` returns the desktop bundle's per-user data dir, keyed by the injected program name; cwd-independent, filesystem-pure.
272
234
 
273
- - `abide/shared/withJsonSchema` — `withJsonSchema(schema, toJsonSchema)`: attach a
274
- `toJSONSchema()` projection to a Standard Schema whose library lacks one; feeds
275
- OpenAPI / MCP / CLI.
235
+ ### Plumbing — @documentation plumbing
276
236
 
277
- ### Response errors — @documentation response
237
+ - `@abide/abide/server/rpc/defineRpc` — `defineRpc(method, url, handler, opts?)` builds the runtime `RemoteFunction` the bundler targets on the server; the bundler stamps the JSON-schema projections, `coerce`, and `streaming` flags. (Authored code uses `GET`/`POST`/…, not this.)
238
+ - `@abide/abide/server/sockets/defineSocket` — `defineSocket(name, opts?)` is the bundler-targeted server `Socket` implementation: per-subscriber queues, a shared retained tail (default 1, lazy `ttl` eviction), schema-validated `publish` fanning out via `server.publish`, and `tail`/`peek`.
239
+ - `@abide/abide/server/prompts/definePrompt` — `definePrompt(name, opts)` builds a `Prompt` (name/description/render) from a resolver-generated `src/mcp/prompts/<file>.md` module and registers it for MCP.
240
+ - `@abide/abide/server/prompts/renderPromptTemplate` — `renderPromptTemplate(template, args)` substitutes `{{name}}` placeholders (missing args → empty string); used by the generated `.md` prompt render closure.
241
+ - `@abide/abide/server/AppModule` — type of the optional `src/app.ts` hooks (`forwardHeaders`, `init` with cleanup, `handle` middleware, `handleError`, `health`), all optional with framework defaults.
242
+ - `@abide/abide/server/InspectorContext` — type of the capability bundle core injects into `@abide/inspector` when `ABIDE_ENABLE_INSPECTOR=true`: `app`, `loadSurface`, `cacheSnapshot`, `inFlightSnapshot`, `onRecord`.
278
243
 
279
- - `abide/shared/HttpError` — `class HttpError extends Error`; thrown by remote
280
- calls on non-2xx. Carries `status`, `statusText`, `response`, optional `kind`
281
- (declared error name or `'validation'`) and `data`.
282
- - `abide/shared/ValidationErrorData` — `type ValidationErrorData`: the
283
- `HttpError.data` shape when `kind === 'validation'` (422) — `{ issues, fields }`,
284
- `fields` mapping top-level field → first message.
244
+ ## Isomorphic surface — abide/shared/*
285
245
 
286
246
  ### Cache — @documentation cache
287
247
 
288
- Isomorphic per ADR-0041: local on the client, broadcast to every connected client
289
- from the server.
290
-
291
- - `abide/shared/invalidate` — `invalidate(selector?, args?)`: the drop verb — drop
292
- matching cached reads so the next read reloads lazily. Selector grammar:
293
- `(fn, args)` / `(fn)` / `({ tags })` / `()`.
294
- - `abide/shared/patch` — `patch(fn, args?, updater)` / `patch({ tags }, updater)`:
295
- mutate the retained value of matching reads in place, reactive, no network (the
296
- optimistic-update / socket-frame primitive).
297
- - `abide/shared/refresh` — `refresh(selector?, args?)`: refetch matching reads now,
298
- keeping the stale value visible (`refreshing()` true) until the fresh one swaps in.
248
+ - `@abide/abide/shared/invalidate` — the drop verb: discards cached reads matching the selector so the next read reloads lazily (retained readers revalidate stale-in-place); `invalidate(asyncCell)` aliases `cell.refresh()`. Side-swapped (ADR-0041): local on the client, broadcast to all clients from the server.
249
+ - `@abide/abide/shared/refresh` — the refetch verb: refetches matching reads while keeping the stale value visible until fresh data swaps in (pair with `refreshing()`). Side-swapped (ADR-0041): local on the client, broadcast eagerly to all clients from the server.
250
+ - `@abide/abide/shared/amend` — mutate a retained cached value in place, reactively and without network (optimistic / real-time), via a replacement value or `(current) => Return` updater. Side-swapped (ADR-0043): local on the client; the value form broadcasts from the server, the updater form stays local.
299
251
 
300
252
  ### Probes — @documentation probes
301
253
 
302
- Reactive, report-never-act — reading opens no fetch/stream.
254
+ - `@abide/abide/shared/pending` — reactive "no value yet" probe over cache calls and stream tails (`pending()` / `pending(fn)` / `pending(fn, args)` / `pending({ tags })` / `pending(subscribable)`); reports, never fetches.
255
+ - `@abide/abide/shared/refreshing` — reactive "holding an old value while a fresher one is in flight" probe over cache and streams; distinguishes revalidation from `pending()`'s "no value yet".
256
+ - `@abide/abide/shared/peek` — the value member: synchronously returns the retained cache value or a stream's latest frame (`T | undefined`), reactive in a tracking scope, triggering nothing; null-tolerant.
257
+ - `@abide/abide/shared/done` — reactive stream-only terminal-state reader: true once a subscribable's tail has closed; not-done when no prober is registered (server render).
258
+ - `@abide/abide/shared/online` — reactive connectivity probe: on the client re-runs on `online`/`offline` and returns `navigator.onLine`; on the server reflects the calling client's reported connectivity, defaulting true during SSR and outside any request scope.
259
+
260
+ ### Templating — @documentation templating
303
261
 
304
- - `abide/shared/pending` — `pending(selector?/subscribable?, args?): boolean` — "no
305
- value yet"; `()` = anything in flight.
306
- - `abide/shared/peek` — `peek(fn, args?)` / `peek(socket)` / `peek(cell)`: the
307
- retained value synchronously, no trigger; null-tolerant.
308
- - `abide/shared/refreshing` — `refreshing(selector?, args?): boolean` — holding a
309
- prior value while a fresher source is in flight (distinct from `pending`).
310
- - `abide/shared/done` — `done(subscribable): boolean` — stream-only, true once the
311
- source has closed.
312
- - `abide/shared/online` — `online(): boolean` — reactive connectivity; browser
313
- reads `navigator.onLine`, server reflects the calling client's reported state.
262
+ - `@abide/abide/shared/snippet` — brands a payload as a mountable snippet value (client DOM builder / server HTML string) so a `{expr}` interpolation mounts it instead of escaping; the compiler wraps snippet bodies with it.
314
263
 
315
264
  ### Observability — @documentation observability
316
265
 
317
- - `abide/shared/health` — `health(): HealthState` — reactive backend-health read;
318
- polls `/__abide/health` only while a tracking scope reads it. Also exports
319
- `AppHealth` / `AppHealthMap` / `HealthState` types.
320
- - `abide/shared/log` — `log: Log`, callable (`log(message, data?)` is the info-level
321
- speaker) with members `log.warn` / `log.error` / `log.trace(name, work)` /
322
- `log.channel(name)` (DEBUG-gated); records carry request-scope context, tsv or
323
- JSON under `ABIDE_LOG_FORMAT=json`. (No `log.info` — the base call is info.)
324
- - `abide/shared/reachable` — `reachable(host?): Promise<boolean>`: isomorphic
325
- outbound reachability, HEADs the origin, caches per TTL; no host = the app's own
326
- backend (constant `true` on server).
327
- - `abide/shared/trace` — `trace(): string | undefined`: the current request's W3C
328
- `traceparent` (server from the ALS scope, browser from `__SSR__`).
329
-
330
- ### Page & URL — @documentation page / url
331
-
332
- - `abide/shared/page` — `page: PageSnapshot`: reactive proxy with `route` /
333
- `params` / `url` / `navigating` for the active page; reading a field in a tracked
334
- scope re-runs on navigation. (`@documentation page`)
335
- - `abide/shared/url` — `url(path, ...args): string`: resolve any in-app URL to its
336
- base-correct form (RPC → query, page route → typed params, asset → bare path);
337
- throws on a missing required param. Also exports `RpcRoutes` / `PageRoutes` /
338
- `PublicAssets` / `PathParams`. (`@documentation url`)
266
+ - `@abide/abide/shared/health` — reactive backend-health read returning `{ reachable }` plus the app `health()` hook's fields, polled from `/__abide/health` only while a tracking scope reads it; constant `{ reachable: true }` on the server.
267
+ - `@abide/abide/shared/log` — the request-scope-aware logger, `Object.assign`-composed: callable `log(...)` on the always-on channel, plus `log.info` / `log.warn` / `log.error` / `log.trace` and `log.channel(name)` for a `DEBUG`-gated channel; tsv or JSON per `ABIDE_LOG_FORMAT`.
268
+ - `@abide/abide/shared/reachable` — isomorphic outbound reachability: `await reachable(host)` HEADs the host origin and caches the verdict for one TTL; constant true for no host (own backend) and loopback. Answers "can I connect," not "is my endpoint healthy."
269
+ - `@abide/abide/shared/trace` — returns the current request's W3C `traceparent` string, or undefined outside a request scope; isomorphic (server ALS / client `__SSR__`-stamped).
339
270
 
340
- ### Templating — @documentation templating
271
+ ### Page — @documentation page
272
+
273
+ - `@abide/abide/shared/page` — reactive isomorphic page proxy exposing `route`, `params`, `url` (browser-space both sides), and `navigating`; getters resolve per-side and re-run readers on navigation.
274
+
275
+ ### URL — @documentation url
341
276
 
342
- - `abide/shared/snippet` — `snippet<Payload>(payload): SnippetValue`: brand a
343
- payload so a `{expr}` interpolation mounts it instead of escaping (client builder
344
- / server string). Also exports `snippetPayload` and the `Snippet` / `SnippetValue`
345
- types. (The `html` primitive lives under the UI surface.)
277
+ - `@abide/abide/shared/url` — base-correct, typed in-app URL resolver keyed off path kind (rpc args → query, page-route params, or bare asset/raw path); prefixes the mount base only for rooted internal paths, leaves external URLs untouched.
346
278
 
347
- ### Subscriber — @documentation plumbing
279
+ ### Plumbing — @documentation plumbing
348
280
 
349
- - `abide/shared/createSubscriber` — `createSubscriber(start)`: an
350
- open-on-first-read / close-on-last-reader primitive over abide's signal core, for
351
- building custom reactive sources.
281
+ - `@abide/abide/shared/createSubscriber` — abide-ui-native open-on-first-read / close-on-last-reader subscriber built on abide's signal core: `start(update)` opens the resource and returns cleanup; `subscribe()` registers a tracked reader that re-runs when `update` fires.
352
282
 
353
283
  ## UI surface — abide/ui/* (client-only)
354
284
 
355
285
  ### Reactive state — @documentation reactive-state
356
286
 
357
- - `abide/ui/state` — the `state(v, transform?)` writable cell plus members
358
- `state.computed` (read-only derived), `state.linked` (writable, reseeded from a
359
- thunk), and the context seam `state.share` / `state.shared`.
360
- - `abide/ui/watch` — the single reaction primitive `watch(source, handler)` (and
361
- bare `watch(thunk)` auto-tracked effect); unifies effect / socket.on / cache.on /
362
- rpc-selector; client-only, returns a scope-tied disposer.
363
- - `abide/ui/props` — `props()` prop reader; compiler-lowered inside a `.abide`
364
- component, throws if called directly.
287
+ - `@abide/abide/ui/state` — abide's from-scratch writable reactive cell `state(initial, transform?)`, plus `.computed` (read-only derived) / `.linked` (writable, reseeded) and `.share` / `.shared` ambient-scope context. Read and reassigned as a plain variable inside `.abide`.
288
+ - `@abide/abide/ui/watch` — the single reaction primitive: `watch(source, handler)` names a trigger (cell / cell array / socket / rpc) and runs the handler on change; bare `watch(thunk)` is the compiler's auto-tracked binding form. Client-only, SSR-stripped.
289
+ - `@abide/abide/ui/props` — the prop reader `props<T>()`; compiler-lowered inside a `.abide` component, throws when called directly.
365
290
 
366
291
  ### Templating — @documentation templating
367
292
 
368
- - `abide/ui/html` — `html` marks a string as trusted raw HTML (`html(str)` or
369
- tagged `` html`…` ``) so a `{expr}` inserts nodes unescaped; nullish → empty.
370
-
371
- ### Navigation — @documentation navigate
372
-
373
- - `abide/ui/navigate` — `navigate(path, params?, options?)`: typed client
374
- navigation through `url()`, writing history + the reactive route
375
- (`replace`/`keepScroll`). Also exports `navigatePath` and `NavigateOptions`.
376
-
377
- ### Effect — @documentation plumbing
378
-
379
- - `abide/ui/effect` — the internal effect primitive the compiler emits (runs now,
380
- re-runs on change, supports teardown/async); authors use `watch`.
381
-
382
- ### Client entry, router & proxies — @documentation plumbing
383
-
384
- - `abide/ui/startClient` — the official client entry: reads `window.__SSR__`, seeds
385
- cache/cell/doc state, installs slots, starts the router; returns a disposer.
386
- - `abide/ui/router` — the History-API router: matches the path, imports page +
387
- layout chunks, mounts them as an outlet chain, diffs on navigation, restores scroll.
388
- - `abide/ui/renderToStream` — server async generator for out-of-order SSR streaming:
389
- yields the shell, then one `<abide-resolve>` fragment per streaming await block.
390
- - `abide/ui/remoteProxy` — the client-side RPC substitute: a `RemoteFunction` that
391
- fetches over the network (input pre-flight validation, timeout/abort merge, output
392
- decode). Also exports `RemoteProxyOptions`.
393
- - `abide/ui/socketProxy` — the client-side `Socket` substitute over the multiplexed
394
- ws channel (iterate / `.tail` / `.publish` / real `.watch`).
395
-
396
- ### Scope & render-pass plumbing — @documentation plumbing
397
-
398
- - `abide/ui/currentScope` — resolve the current lexical reactive scope (mints a
399
- detached root outside any scope).
400
- - `abide/ui/enterRenderScope` — establish a fresh per-render SSR scope, returning the
401
- previous to restore.
402
- - `abide/ui/exitRenderScope` — restore the scope `enterRenderScope` saved.
403
- - `abide/ui/settleAsyncCells` — SSR await-barrier draining the request-scoped
404
- pending-cell list; no-op on client.
405
- - `abide/ui/flight` — start a hoisted SSR flight from a thunk with a synchronous
406
- `settled`/`value`/`error` snapshot so flights overlap. Also exports `FlightPromise`.
407
- - `abide/ui/isolateCellBarrier` — run a hoisted child render under its own async-cell
408
- barrier list; inert passthrough on client.
409
- - `abide/ui/finalizeStreamedChildren` — the ADR-0039 when-to-stream decision after a
410
- body walk (inline a settled flight, rethrow a rejected one, or stream). Also
411
- exports `StagedChild`.
412
- - `abide/ui/runtime/withPath` — push one escaped render-path segment for a
413
- synchronous `build`, restoring after.
414
- - `abide/ui/runtime/renderPath` — compose a child's ordinal segment onto the ambient
415
- path, returning the render-path string (the streamed-child boundary id).
416
- - `abide/ui/runtime/escapeKey` — escape one path key to an RFC-6901 JSON-Pointer token.
417
- - `abide/ui/runtime/nextBlockId` — allocate the next await/try block id in the current
418
- render pass, namespaced by the ambient path.
419
- - `abide/ui/runtime/blockId` — allocate a render-path-namespaced block id
420
- (`${path}:${n}`) from a counters map.
421
- - `abide/ui/runtime/enterRenderPass` — mark entry into a render/mount, clearing the
422
- per-path block-id counters at the outermost depth.
423
- - `abide/ui/runtime/exitRenderPass` — mark exit from a render/mount, unwinding the depth.
424
-
425
- ### DOM runtime helpers — @documentation plumbing
426
-
427
- Compiler-emitted; an author never imports these directly.
428
-
429
- - `abide/ui/dom/mount` — mount a top-level page/layout into a host under an ownership
430
- scope + render pass; returns a disposer.
431
- - `abide/ui/dom/mountChild` — mount a child component as a marker-bounded range (no
432
- wrapper), filing its dispose with the owner.
433
- - `abide/ui/dom/mountStreamedChild` — dual-mode adopter for a hoistable child: adopt an
434
- inlined range, claim a streamed boundary, or create-mount fresh.
435
- - `abide/ui/dom/mountSlot` — mount a component's slot content as a marker-bounded range
436
- (create fills, hydrate adopts); runs once.
437
- - `abide/ui/dom/mergeProps` — compose a child's props from ordered layers (runs,
438
- spreads, slot) into one last-layer-wins Proxy bag.
439
- - `abide/ui/dom/spreadProps` — wrap a `{...source}` spread as a live Proxy of value
440
- thunks (keys/membership re-evaluated live; `children` hidden).
441
- - `abide/ui/dom/restProps` — live Proxy of the unconsumed props for `{ ...rest }`,
442
- excluding destructured keys and `children`.
443
- - `abide/ui/dom/bindProp` — the parent half of a component `bind:prop`: annotate the
444
- value thunk with a `.set` write-back channel.
445
- - `abide/ui/dom/bindableProp` — the child half of a two-way prop: pass-through when the
446
- parent bound it, else a local reseeding `linked` cell.
447
- - `abide/ui/dom/spreadAttrs` — spread a thunk's keys onto a native element (`on*` →
448
- listeners, others → reactive attributes); `exclude` skips explicit attrs.
449
- - `abide/ui/dom/readCall` — guarded non-optional method call on a reactive-document
450
- read, with legible authored-scope errors.
451
- - `abide/ui/dom/readCell` — unified read of a `computed`/`linked`/derive reference
452
- (async → throwing peek, function → call, sync → `.value`).
453
- - `abide/ui/dom/readCellBlocking` — the blocking (`await`) variant of `readCell`: on a
454
- pending cell it throws a suspense signal so the reading region withholds until the
455
- value resolves, rather than reading `undefined` (client template lowering; ADR-0042).
456
- - `abide/ui/dom/writeCell` — unified write of a `linked` reference from an author
457
- assignment (async → `.set`, sync → `.value =`).
458
- - `abide/ui/dom/cellPending` — whether a control-flow subject is a still-loading async
459
- cell, so a block renders no branch while loading.
460
- - `abide/ui/dom/mutateDocContainer` — apply an in-place container mutation
461
- (`splice`/`sort`/`add`/…) on a reactive-document value by clone-mutate-`replace`.
462
- - `abide/ui/dom/hydrate` — adopt existing server-rendered DOM in place with a claim
463
- cursor active (no re-render); returns a disposer.
464
- - `abide/ui/dom/appendText` — a reactive `{expr}` interpolation: escaped text,
465
- `html`-branded raw, or a snippet call; create-appends or claims the SSR text node.
466
- - `abide/ui/dom/appendTextAt` — a reactive `{expr}` mounted just after a skeleton
467
- anchor comment (text interleaved with element siblings).
468
- - `abide/ui/dom/appendSnippet` — mount a `{snippet(args)}` builder in a marker-bounded
469
- range, reactive in its args.
470
- - `abide/ui/dom/appendStatic` — a static (non-reactive) text node; create-appends or
471
- claims the merged SSR text node.
472
- - `abide/ui/dom/cloneStatic` — append a fully-static binding-free subtree by cloning a
473
- cached template (create) / advancing the claim cursor (hydrate).
474
- - `abide/ui/dom/skeleton` — realize a compiled skeleton (static subtree with located
475
- holes) and return its element + anchor holes.
476
- - `abide/ui/dom/anchorCursor` — position a skeleton-anchored block/slot: the create
477
- insertion reference after the anchor and the hydrate claim cursor.
478
- - `abide/ui/dom/outlet` — a layout outlet: insert/claim an empty `abide:outlet` comment
479
- boundary; returns `{ open, close }`.
480
- - `abide/ui/dom/attr` — bind an element attribute to `read()` via one effect
481
- (boolean-present/absent + stringify semantics).
482
- - `abide/ui/dom/on` — attach an event listener (`onclick={…}` target), scope-pinned and
483
- batched, removal registered on the owner.
484
- - `abide/ui/dom/attach` — run an `attach={…}` attachment against an element at build
485
- time and register its teardown (the dual of `on`).
486
- - `abide/ui/dom/bindSelectValue` — two-way `bind:value` for `<select>` (effect drives
487
- selection re-applied via MutationObserver; `change` writes back; `multiple` → array).
488
- - `abide/ui/dom/each` — keyed list runtime (`{#for … by key}`): each row a
489
- marker-bounded range, reconciled by key with minimal DOM moves.
490
- - `abide/ui/dom/eachAsync` — async keyed list runtime (`{#for await … of …}`):
491
- append/reconcile as the iterator yields; mid-stream rejection routes to the catch.
492
- - `abide/ui/dom/when` — conditional runtime (`{#if}`/`{:else}`): a swappable range
493
- tracking `condition()`, with an optional pending state for a bare async subject.
494
- - `abide/ui/dom/awaitBlock` — `{#await}` runtime: render pending then swap to
495
- resolved/error; reactive re-runs on cache invalidation; hydration resumes or rebuilds.
496
- - `abide/ui/dom/tryBlock` — `{#try}` reactive error-boundary runtime: catch build /
497
- read / re-run throws, swap to the catch branch, re-arm on recovery.
498
- - `abide/ui/dom/switchBlock` — `{#switch}` / `{:else if}` runtime: a swappable range
499
- selecting the first matching (`===`) case (or default), with per-case async gating.
293
+ - `@abide/abide/ui/html` — marks a string as trusted raw HTML (plain call or tagged template) so a `{expr}` interpolation inserts its nodes verbatim instead of escaped text.
294
+
295
+ ### Navigate — @documentation navigate
296
+
297
+ - `@abide/abide/ui/navigate` — navigates to a typed in-app path (interpolating `[name]` params through `url()`) via the History API, with `replace` / `keepScroll` options.
298
+
299
+ ### Reaction plumbing — @documentation plumbing
300
+
301
+ - `@abide/abide/ui/effect` — abide's effect primitive (runs `fn` capturing reads, re-runs on change, teardown-returning); internal — authors use `watch`, and the compiler emits `effect`.
302
+ - `@abide/abide/ui/currentScope` — resolves the current lexical scope (minting a detached root outside any); the lowering host the compiler targets for `state`/`effect`/`share`.
303
+ - `@abide/abide/ui/enterRenderScope` — establishes a fresh nested lexical scope for an SSR render and returns the previous one to restore.
304
+ - `@abide/abide/ui/exitRenderScope` — restores the scope `enterRenderScope` saved, closing an SSR render's scope.
305
+
306
+ ### Client entry / router plumbing — @documentation plumbing
307
+
308
+ - `@abide/abide/ui/startClient` — the abide-ui client entry: reads `window.__SSR__`, seeds the tab cache / doc / cell / socket warm partitions, then starts the router; returns a disposer.
309
+ - `@abide/abide/ui/router` — a minimal History-API client router that matches the path, code-splits and mounts the page + its layout chain, diffs chains across navigation, and restores scroll.
310
+ - `@abide/abide/ui/renderToStream` — out-of-order SSR streaming generator: yields the shell first, then one resolved fragment per streaming `await` block in completion order.
311
+ - `@abide/abide/ui/remoteProxy` — client-side substitute for an RPC handler: builds a `RemoteFunction` that fetches over the network (pre-flight validation, cache policy, abort/timeout).
312
+ - `@abide/abide/ui/socketProxy` — client-side substitute for a server `Socket`: subscribes over the multiplexed ws channel, warm-seeded from the SSR retained frame, producing the identical `Socket` surface.
313
+
314
+ ### DOM binding plumbing — @documentation plumbing
315
+
316
+ Everything the compiler emits from the template; authors never call these.
317
+
318
+ - `@abide/abide/ui/dom/mount` — mounts a top-level page/layout into a host under an ownership scope and render pass, returning a disposer.
319
+ - `@abide/abide/ui/dom/mountChild` — the compiler's only child-component mount: a marker-bounded range adopter probing the hydration cursor for inlined vs streamed content, else create-mode.
320
+ - `@abide/abide/ui/dom/hydrate` — adopts existing server-rendered DOM in place by running `build` with a claim cursor active, attaching listeners/effects without re-rendering; returns a disposer.
321
+ - `@abide/abide/ui/dom/mergeProps` — composes a child's props from ordered explicit/spread/slot layers into one live proxy bag, last-layer-wins.
322
+ - `@abide/abide/ui/dom/spreadProps` — wraps a `{...source}` spread layer so every key resolves to a live value thunk.
323
+ - `@abide/abide/ui/dom/restProps` — exposes the unconsumed props of `const { foo, ...rest } = props()` as a live object, excluding consumed keys and `children`.
324
+ - `@abide/abide/ui/dom/bindProp` — the parent half of a component `bind:prop`: annotates the prop's value thunk with a `set` write-back channel.
325
+ - `@abide/abide/ui/dom/bindableProp` — the child half of a two-way prop: a writable pass-through to the parent's target, or a local reseeding `linked` cell when unbound.
326
+ - `@abide/abide/ui/dom/spreadAttrs` — spreads an object's keys onto a native element (`{...rest}`): `on*` keys attach as listeners, others bind as reactive attributes.
327
+ - `@abide/abide/ui/dom/attr` — binds one element attribute to `read()` via an effect, with present/absent boolean semantics, suspense handling, and a hydration divergence check.
328
+ - `@abide/abide/ui/dom/on` — attaches an event listener (the `onclick={…}` target) pinned to the current scope with batched writes; skips non-function handlers.
329
+ - `@abide/abide/ui/dom/attach` — runs an `attach={…}` against an element at build time and registers its (possibly async) teardown with the ownership scope.
330
+ - `@abide/abide/ui/dom/bindSelectValue` — two-way `bind:value` for a `<select>` (single or `multiple`), re-applying on value / option-set changes and writing back on `change`.
331
+ - `@abide/abide/ui/dom/readCall` — guarded non-optional method call on a reactive-document read, throwing a TypeError naming the authored scope path instead of the engine's opaque error.
332
+ - `@abide/abide/ui/dom/readCell` — the unified read for a `state`/`computed`/`linked` reference: returns the retained value, throwing a `SuspenseSignal` for a pending blocking cell or an `AsyncCellError` for a settled error.
333
+ - `@abide/abide/ui/dom/writeCell` — the unified write for a `linked` reference: `.value` for a sync cell or `.set(value)` for an async one.
334
+ - `@abide/abide/ui/dom/cellPending` — reports whether a control-flow subject is a still-loading async cell, so `{#if}`/`{#switch}` render no branch instead of flashing the falsy one.
335
+ - `@abide/abide/ui/dom/awaitSubject` — normalizes an `{#await}` block's bare cell subject so it awaits the async cell's resolution instead of peeking its pending `undefined`.
336
+ - `@abide/abide/ui/dom/mutateDocContainer` — lowers an in-place-mutating container method (splice/sort/…) on a reactive-document value to clone-mutate-`replace`, emitting a patch so readers wake.
337
+ - `@abide/abide/ui/dom/appendText` — a reactive `{expr}` interpolation under a parent, rendering escaped text, a snippet builder, or `html`-branded raw markup (create or hydrate-claim).
338
+ - `@abide/abide/ui/dom/appendTextAt` — a reactive `{expr}` interpolation mounted at a kept skeleton anchor (text interleaved with element siblings), delegating to `appendText`.
339
+ - `@abide/abide/ui/dom/appendSnippet` — mounts a `{snippet(args)}` builder's nodes in a marker-bounded range, reactive in its arguments.
340
+ - `@abide/abide/ui/dom/appendStatic` — appends a static text node under a parent (create), or claims the merged server text node (hydrate).
341
+ - `@abide/abide/ui/dom/cloneStatic` — appends a fully-static subtree by cloning a cached template once (create), or advances the claim cursor past the run (hydrate).
342
+ - `@abide/abide/ui/dom/skeleton` — realizes a compiled skeleton (a bound element subtree) under a parent and returns its element + anchor holes.
343
+ - `@abide/abide/ui/dom/anchorCursor` — positions a skeleton-anchored control-flow block/slot, returning the create insertion reference and parking the hydrate claim cursor.
344
+ - `@abide/abide/ui/dom/mountSlot` — mounts a component's `{children()}` content as a marker-bounded range positioned like a control-flow block, running once.
345
+ - `@abide/abide/ui/dom/outlet` — a layout's outlet as an empty `<!--abide:outlet-->` boundary the router fills; hydrate claims and skips the server child content.
346
+ - `@abide/abide/ui/dom/each` — keyed list binding (the compiler target for `{#for … by key}`): reconciles marker-bounded row ranges by key with minimal DOM moves and hydrate-in-place adoption.
347
+ - `@abide/abide/ui/dom/eachAsync` — async keyed list (the target for `{#for await … by key}`): appends/reconciles row ranges as an `AsyncIterable` yields, with generation-guarded drains and catch-branch handling.
348
+ - `@abide/abide/ui/dom/when` — conditional binding (the target for `{#if}`/`{:else}`): a swappable range tracking `condition()`, with an optional third pending state so a loading async subject flashes no branch.
349
+ - `@abide/abide/ui/dom/awaitBlock` — async binding (the target for `{#await}`): renders pending then swaps to the resolved value cell or catch branch on settle; adopts streamed-resume / warm-sync values on hydrate.
350
+ - `@abide/abide/ui/dom/tryBlock` — reactive error boundary (the target for `{#try}`): catches build / initial-read / re-run throws, swaps to the catch branch, and rebuilds on recovery.
351
+ - `@abide/abide/ui/dom/switchBlock` — multi-branch binding (the target for `{#switch}` / `{:else if}` chains): a swappable range selecting the first matching (or default) case, with per-case pending gating.
352
+
353
+ ### Render runtime plumbing — @documentation plumbing
354
+
355
+ - `@abide/abide/ui/settleAsyncCells` — the SSR await-barrier: drains this render's pending async-cell list to a fixpoint so blocking `await` cells resolve before render; a client no-op.
356
+ - `@abide/abide/ui/flight` — server-only flight-starter that hoists an await's promise into the synchronous render prefix, normalizing sync throws and keeping pre-consumer rejections non-fatal.
357
+ - `@abide/abide/ui/isolateCellBarrier` — runs a hoisted child render under its own async-cell barrier list (server ALS), isolating its cell registration/drain from siblings; a client passthrough.
358
+ - `@abide/abide/ui/finalizeStreamedChildren` — the when-to-stream decision (ADR-0039): fills each hoisted child's reserved slot inline if its flight settled, else emits a streaming boundary.
359
+ - `@abide/abide/ui/runtime/renderPath` — computes the render-path (streamed-boundary id) a child mounts under by composing its ordinal onto the ambient path; server-emit-only.
360
+ - `@abide/abide/ui/runtime/withPath` — pushes one `escapeKey`-escaped render-path segment for the duration of a synchronous `build`, restoring after.
361
+ - `@abide/abide/ui/runtime/escapeKey` — escapes one object key into a JSON-Pointer token (`~`→`~0`, `/`→`~1`) so a key with `/` survives a `/`-joined render path.
362
+ - `@abide/abide/ui/runtime/blockId` — allocates an await/try block id namespaced by the ambient render-path (`${path}:${n}`) from a per-render-pass counter map.
363
+ - `@abide/abide/ui/runtime/nextBlockId` — allocates the next await/try block id in the current client render pass, over the shared `blockId`.
364
+ - `@abide/abide/ui/runtime/enterRenderPass` — marks entry into a render/mount, clearing per-path block-id counters at the outermost depth so each pass restarts ids at 0.
365
+ - `@abide/abide/ui/runtime/exitRenderPass` — marks exit from a render/mount, unwinding the depth `enterRenderPass` raised.
500
366
 
501
367
  ## Build / tooling
502
368
 
503
369
  ### Building — @documentation building
504
370
 
505
- - `abide/build` — `build({ cwd, … })`: build the client bundle into `dist/_app`
506
- (optional gzip/Tailwind), and write `src/.abide/*.d.ts`.
507
- - `abide/compile` — `compile({ cwd, target?, outfile? })`: build the client then a
508
- standalone Bun server executable; returns the binary path.
371
+ - `@abide/abide/build` — `build()` runs `Bun.build` to emit the client bundle into `dist` (`_app` in prod via atomic staging-swap, per-generation dirs in dev) with the `.abide` loader, virtual-module resolver, optional Tailwind and gzip; returns `{ appDir }` or `false`.
372
+ - `@abide/abide/compile` — `compile()` produces a standalone Bun server executable (client assets embedded), defaulting the target to the host platform; returns the emitted binary path.
509
373
 
510
374
  ### Plumbing — @documentation plumbing
511
375
 
512
- - `abide/preload` — the Bun preload registering `abideUiPlugin` +
513
- `abideResolverPlugin` + a css-noop loader; `ABIDE_TARGET` selects server/client mode.
514
- - `abide/resolver-plugin` — `abideResolverPlugin({ cwd, embedAssets, target })`: the
515
- BunPlugin wiring the `abide:*` virtuals and rewriting rpc/socket/prompt modules.
516
- - `abide/ui-plugin` — `abideUiPlugin`: the BunPlugin that loads `.abide` components
517
- (compiles to an ES module; a `layout.abide` lowers its outlet to the router).
518
- - `abide/tsconfig` — the consumer-extendable `tsconfig.app.json`.
376
+ - `@abide/abide/preload` — side-effect Bun preload registering the `.abide` UI plugin, the virtual-module resolver (mode from `ABIDE_TARGET`), and a `.css` no-op loader; add it under `[test]` `preload` in `bunfig.toml`.
377
+ - `@abide/abide/resolver-plugin` — `abideResolverPlugin()` is the Bun plugin supplying abide's `abide:*` virtual modules and rewriting `src/server/rpc` + `src/server/sockets` modules to server impls or client proxies.
378
+ - `@abide/abide/ui-plugin` — `abideUiPlugin` is the Bun plugin that loads `.abide` single-file components, compiling each (via `compileModule`) to an ES module with scoped `<style>` bundled to a virtual CSS module on the browser build.
379
+ - `@abide/abide/tsconfig` — shareable TS config (`tsconfig.app.json`): ESNext target/module, bundler resolution, strict + `isolatedModules` + `erasableSyntaxOnly`, `allowImportingTsExtensions`, `noEmit`, Bun types.
519
380
 
520
381
  ## Desktop bundle
521
382
 
522
- ### Desktop bundle — @documentation bundle
523
-
524
- - `abide/bundle/BundleWindow` — `type BundleWindow`: the default-export window config
525
- (title/size/menu) from `src/bundle/window.ts`, baked into the launcher.
526
- - `abide/bundle/BundleMenu` — `type BundleMenu`: a `{ label, items }` top-level menu
527
- inserted between the Edit and Window menus.
528
- - `abide/bundle/BundleMenuItem` — `type BundleMenuItem`: a divider or clickable item
529
- (`emit` dispatches an `abide:menu` event, `navigate` repoints the window, `shortcut`
530
- = Cmd-key).
531
- - `abide/bundle/onMenu` — `onMenu(handler)` / `onMenu(name, handler)`: subscribe to
532
- `abide:menu` clicks, returns unsubscribe; inert in SSR / a plain browser tab.
533
- - `abide/bundle/bundled` — `bundled(): boolean`: true when running inside the abide
534
- desktop bundle (isomorphic).
535
- - `abide/server/appDataDir` — `appDataDir()`: the running bundle's per-user data dir
536
- keyed by program name (pure, cwd-independent). (`@documentation bundle`)
537
-
538
- ## MCP
539
-
540
- ### MCP server — @documentation mcp
541
-
542
- - `abide/mcp/createMcpServer` — `createMcpServer(opts?)`: construct the framework MCP
543
- server bound to the RPC registry; `handle(request)` is what `/__abide/mcp` invokes.
544
- Tools come from rpcs/sockets with `clients.mcp: true`; optional `authorize`.
383
+ ### Bundle — @documentation bundle
545
384
 
546
- ### Agent — @documentation agent
385
+ - `@abide/abide/bundle/BundleWindow` — type for the default-exported `src/bundle/window.ts` config (title/width/height, custom `menu`, optional `config` Standard Schema overriding the setup form), baked into the launcher.
386
+ - `@abide/abide/bundle/BundleMenu` — type for a top-level bundle menu (`label` + `items`) inserted into the macOS menu bar between Edit and Window.
387
+ - `@abide/abide/bundle/BundleMenuItem` — serializable union type for a menu entry: a separator, an `emit` item dispatching an `abide:menu` event into the page, or a `navigate` item, with optional Cmd-`shortcut`.
388
+ - `@abide/abide/bundle/onMenu` — subscribes to bundle-menu clicks (catch-all `onMenu(handler)` or filtered `onMenu(name, handler)`), returning an unsubscribe; inert during SSR and in a plain browser tab.
389
+ - `@abide/abide/bundle/bundled` — `bundled()` returns true when running inside the abide desktop bundle (client: webview flag; server: `ABIDE_PARENT_PID`); isomorphic, false in a plain browser tab or remote server.
547
390
 
548
- - `abide/server/agent` — `agent(engine, messages)`: run a model engine against the
549
- app's own MCP surface, returning the neutral `AgentFrame` stream (no transport — wrap
550
- in `jsonl()` / `sse()`).
391
+ ## MCP
551
392
 
552
- ### Prompts — @documentation plumbing
393
+ ### MCP — @documentation mcp
553
394
 
554
- - `abide/server/prompts/definePrompt` — `definePrompt(name, opts)`: build + register a
555
- `Prompt`; called by the resolver-generated module for each `src/mcp/prompts/*.md`.
556
- - `abide/server/prompts/renderPromptTemplate` — `renderPromptTemplate(template, args)`:
557
- substitute `{{name}}` placeholders (missing → empty string).
395
+ - `@abide/abide/mcp/createMcpServer` — `createMcpServer(opts)` builds the framework-internal MCP server bound to the project's rpc registry, returning `{ handle(request) }` for the `/__abide/mcp` route; tools derive from rpcs/sockets with `clients.mcp`, auth inherits from the inbound request, with an optional `authorize` hook.
558
396
 
559
397
  ## Testing
560
398
 
561
399
  ### Testing — @documentation testing
562
400
 
563
- - `abide/test/createTestApp` — the augmentable typed test app exposing
564
- `app.rpc.<rpc>` / `app.sockets.<name>` (types arrive from `src/.abide/` after a build).
401
+ - `@abide/abide/test/createTestApp` — `createTestApp()` boots the real app on an ephemeral port (same wiring as `serverEntry`) from the project's virtual manifests, returning a disposable `TestApp` with `origin`, `fetch`, typed `rpc`/`sockets`, `health`, and `stop`.
565
402
 
566
403
  ### Plumbing — @documentation plumbing
567
404
 
568
- - `abide/test/createScriptedSurface` — `createScriptedSurface(...)`: a scripted
569
- `AgentSurface` with declarative tool stubs for engine tests; records every `call`.
570
- - `abide/test/assertAgentFrameConformance` — `assertAgentFrameConformance(...)`: assert
571
- the neutral `AgentFrame` contract (single terminal `done`, matched
572
- `tool_use`/`tool_result`, string deltas).
405
+ - `@abide/abide/test/createScriptedSurface` — `createScriptedSurface(tools)` returns a scripted `AgentSurface` (declarative tool stubs) that records every `call` so engine tests can assert dispatched tools and args.
406
+ - `@abide/abide/test/assertAgentFrameConformance` — `assertAgentFrameConformance(stream)` collects an engine's `AgentFrame` stream and asserts the neutral contract (one trailing `done`, matched `tool_use`/`tool_result` pairs, string text deltas).
573
407
 
574
408
  ## Generated machine surfaces
575
409
 
576
- Runtime routes served by the framework:
410
+ Runtime routes the framework serves (`DOCUMENT` set; internal `/__abide/*`
411
+ routes are omitted):
577
412
 
578
413
  | Route | Serves |
579
- |---|---|
580
- | `/openapi.json` | The generated OpenAPI document. |
581
- | `/__abide/mcp` | The generated MCP server endpoint (`createMcpServer().handle`). |
582
- | `/__abide/sockets` | The sockets hub (ws multiplex); `/__abide/sockets/<name>` is the per-socket HTTP/SSE face (GET tail, POST publish). |
583
- | `/__abide/health` | Health JSON, aggregating the app's optional `health(request)` hook. |
584
- | `/__abide/identity` | Server identity (name / version). |
585
- | `/__abide/cli` | CLI binary download; `/__abide/cli/<platform>` streams the thin cli + server. |
586
- | `/__abide/inspector` | Inspector surface, mounted only when `ABIDE_ENABLE_INSPECTOR=true`. |
414
+ | --- | --- |
415
+ | `/openapi.json` | The generated OpenAPI 3.1 document for the RPC surface. |
416
+ | `/__abide/mcp` | The MCP server endpoint (tools from mcp-exposed rpcs/sockets). |
417
+ | `/__abide/sockets` | The multiplexed WebSocket; `/__abide/sockets/<name>` is the per-socket HTTP face (tail over SSE/JSON, publish). |
418
+ | `/__abide/health` | Health probe: `{ reachable }` plus the app `health()` hook's fields, ahead of app middleware. |
419
+ | `/__abide/identity` | The health payload under the legacy `{ abide: true }` shape, for already-shipped probers. |
420
+ | `/__abide/cli` | The platform-detecting install script; `/__abide/cli/<platform>` streams the thin-CLI tarball. |
421
+ | `/__abide/inspector` | The operator inspector surface, gated by `ABIDE_ENABLE_INSPECTOR`. |
587
422
 
588
423
  ## Environment variables
589
424
 
590
425
  | Var | Effect |
591
- |---|---|
426
+ | --- | --- |
592
427
  | `PORT` | Server listen port. |
593
- | `APP_URL` | Derives the server's mount base path / public URL. |
594
- | `ABIDE_APP_DIR` | Overrides the client-asset dir (default `dist/_app`). |
595
- | `ABIDE_APP_URL` | Default remote server URL the CLI targets when no saved connection. |
596
- | `ABIDE_APP_TOKEN` | Bearer token the CLI sends to the target server. |
597
- | `ABIDE_DATA_DIR` | Overrides the app's per-user data dir. |
598
- | `ABIDE_CLIENT_TIMEOUT` | Client transport timeout in ms (bounded 1–600000). |
599
- | `ABIDE_IDLE_TIMEOUT` | Server socket idle timeout in seconds (default 10). |
600
- | `ABIDE_MAX_REQUEST_BODY_SIZE` | Caps the max request body size. |
601
- | `ABIDE_LOG_FORMAT` | `json` emits structured JSON log records (else tsv). |
602
- | `ABIDE_DEV_SURFACE` | `1` enables request logging even under dev. |
603
- | `ABIDE_ENABLE_INSPECTOR` | `true` mounts the inspector at `/__abide/inspector`. |
604
- | `ABIDE_INSPECT` | Truthy opens the bundle webview with devtools enabled. |
605
- | `DEBUG` | Enables debug logging; the value is the namespace filter (supports negation). |
428
+ | `APP_URL` | Public app URL; its path derives the mount base for all in-app URLs. |
429
+ | `ABIDE_APP_DIR` | Override the built app dir the server loads (default `dist/_app`). |
430
+ | `ABIDE_DATA_DIR` | Override the per-user app data dir (`appDataDir`). |
431
+ | `ABIDE_CLIENT_TIMEOUT` | Client-side RPC fetch timeout in ms (bounded 1–600000). |
432
+ | `ABIDE_MAX_REQUEST_BODY_SIZE` | Server-wide max request body bytes (per-RPC `maxBodySize` overrides). |
433
+ | `ABIDE_IDLE_TIMEOUT` | WebSocket idle timeout in seconds (default 10). |
434
+ | `ABIDE_LOG_FORMAT` | `json` switches `log` output to JSON lines (else tsv). |
435
+ | `DEBUG` | Enable diagnostic log channels (e.g. `abide:rpc`). |
436
+ | `ABIDE_DEV_SURFACE` | `1` logs requests even under `abide dev`. |
437
+ | `ABIDE_ENABLE_INSPECTOR` | `true` enables the `@abide/inspector` capability injection + `/__abide/inspector`. |
438
+ | `ABIDE_INSPECT` | Enable inspector debug instrumentation. |
439
+ | `ABIDE_APP_URL` | The app URL the remote CLI / bundle client targets. |
440
+ | `ABIDE_APP_TOKEN` | Bearer token the remote CLI / bundle client sends to reach its app. |
606
441
 
607
442
  ---
608
443
 
609
- This map mirrors `package.json`'s `exports`. After adding, renaming, or removing an
610
- export, run `bun run packages/abide/scripts/readmeSurfaces.ts` and update this file.
444
+ This map mirrors the `exports` map in `package.json`. After adding or renaming an
445
+ export, run `bun run packages/abide/scripts/readmeSurfaces.ts` and update this
446
+ file so every export, route, and env var stays accounted for.