@abide/abide 0.51.0 → 0.52.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.
- package/AGENTS.md +509 -525
- package/CHANGELOG.md +29 -0
- package/README.md +128 -157
- package/package.json +4 -1
- package/src/abideResolverPlugin.ts +12 -0
- package/src/checkAbide.ts +60 -7
- package/src/lib/cli/completeCli.ts +38 -0
- package/src/lib/cli/printTopLevelHelp.ts +1 -0
- package/src/lib/cli/renderCliCompletions.ts +56 -0
- package/src/lib/cli/runCli.ts +27 -0
- package/src/lib/server/render.ts +70 -0
- package/src/lib/server/runtime/cacheStalenessBroadcaster.ts +33 -0
- package/src/lib/server/runtime/createServer.ts +43 -0
- package/src/lib/server/runtime/createUiPageRenderer.ts +37 -0
- package/src/lib/server/runtime/pageRenderSlot.ts +15 -0
- package/src/lib/server/runtime/runWithRequestScope.ts +1 -0
- package/src/lib/server/runtime/types/RequestStore.ts +9 -0
- package/src/lib/server/sockets/createSocketDispatcher.ts +9 -0
- package/src/lib/server/sockets/registerSocket.ts +12 -0
- package/src/lib/shared/CACHE_STALENESS_SOCKET.ts +8 -0
- package/src/lib/shared/RESERVED_SOCKET_PREFIX.ts +8 -0
- package/src/lib/shared/applyCacheStalenessLocally.ts +18 -0
- package/src/lib/shared/attachRpcSelectorMethods.ts +3 -1
- package/src/lib/shared/cache.ts +32 -4
- package/src/lib/shared/cacheStalenessSlot.ts +21 -0
- package/src/lib/shared/createRpcServerProgram.ts +187 -0
- package/src/lib/shared/docSnapshotsSlot.ts +13 -0
- package/src/lib/shared/invalidate.ts +39 -0
- package/src/lib/shared/matcherFromEnvelope.ts +31 -0
- package/src/lib/shared/prepareRpcModule.ts +22 -3
- package/src/lib/shared/refresh.ts +9 -2
- package/src/lib/shared/selectorMatcher.ts +2 -15
- package/src/lib/shared/serializeSelector.ts +69 -0
- package/src/lib/shared/setsIntersect.ts +15 -0
- package/src/lib/shared/types/CacheStalenessApply.ts +13 -0
- package/src/lib/shared/types/CacheStalenessFrame.ts +24 -0
- package/src/lib/shared/types/DocSnapshots.ts +12 -0
- package/src/lib/shared/types/RemoteFunction.ts +11 -8
- package/src/lib/shared/types/RpcBuildStamps.ts +9 -0
- package/src/lib/shared/types/SsrPayload.ts +5 -0
- package/src/lib/test/createTestApp.ts +15 -0
- package/src/lib/ui/compile/COMPOUND_ASSIGNMENT_OPERATORS.ts +19 -0
- package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +1 -0
- package/src/lib/ui/compile/compileShadow.ts +92 -14
- package/src/lib/ui/compile/lowerDocAccess.ts +2 -14
- package/src/lib/ui/compile/lowerScript.ts +6 -1
- package/src/lib/ui/compile/renameSignalRefs.ts +65 -0
- package/src/lib/ui/compile/wrapReactionCellSources.ts +32 -0
- package/src/lib/ui/createScope.ts +42 -1
- package/src/lib/ui/dom/assertClaimedText.ts +14 -4
- package/src/lib/ui/dom/attr.ts +25 -2
- package/src/lib/ui/dom/writeCell.ts +22 -0
- package/src/lib/ui/runtime/DOC_SEED.ts +11 -0
- package/src/lib/ui/runtime/claimExpected.ts +6 -1
- package/src/lib/ui/runtime/reportHydrationDivergence.ts +30 -0
- package/src/lib/ui/startClient.ts +22 -1
- package/src/lib/ui/subscribeCacheStaleness.ts +85 -0
- package/src/serverEntry.ts +12 -0
package/AGENTS.md
CHANGED
|
@@ -1,617 +1,601 @@
|
|
|
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
|
|
5
|
-
>
|
|
6
|
-
>
|
|
7
|
-
> rationale behind
|
|
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.
|
|
8
8
|
>
|
|
9
|
-
> Ground rule
|
|
10
|
-
>
|
|
11
|
-
>
|
|
12
|
-
>
|
|
13
|
-
>
|
|
14
|
-
>
|
|
15
|
-
>
|
|
16
|
-
>
|
|
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`).
|
|
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.
|
|
17
23
|
|
|
18
24
|
## The premise
|
|
19
25
|
|
|
20
|
-
One
|
|
26
|
+
One typed RPC declaration branches to every surface:
|
|
21
27
|
|
|
22
28
|
```text
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
SSR call browser
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
export const getMessages = GET(handler, { schemas })
|
|
30
|
+
│
|
|
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
37
|
```
|
|
31
38
|
|
|
32
|
-
A typed input
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
`schemas.input
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
`clients: { mcp: true }`. Explicit `clients` values always win; `browser`
|
|
39
|
-
defaults on. A socket with a `schema` auto-exposes to MCP and CLI regardless of
|
|
40
|
-
direction.
|
|
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 }`.
|
|
41
45
|
|
|
42
46
|
## File-based conventions
|
|
43
47
|
|
|
44
|
-
The bundler
|
|
45
|
-
`$server`, `$ui`, `$shared`, `$mcp`, `$cli
|
|
46
|
-
|
|
47
|
-
| Path
|
|
48
|
-
|
|
49
|
-
| `src/server/rpc/<name>.ts`
|
|
50
|
-
| `src/server/sockets/<name>.ts`
|
|
51
|
-
| `src/
|
|
52
|
-
| `src/
|
|
53
|
-
| `src/
|
|
54
|
-
| `src/app.
|
|
55
|
-
| `src/
|
|
56
|
-
| `src/
|
|
57
|
-
| `src/
|
|
58
|
-
| `src/
|
|
59
|
-
| `src/bundle/window.ts`
|
|
60
|
-
| `src
|
|
61
|
-
| `
|
|
62
|
-
| `dist/` | Build output: `dist/_app/` (prod client) or `dist/_app.gen-<id>/` (dev), `dist/app` (compiled binary), `dist/cli-*`. |
|
|
48
|
+
The bundler (`abideResolverPlugin`) reads these paths. Import aliases into the
|
|
49
|
+
tree: `$server`, `$ui`, `$shared`, `$mcp`, `$cli`.
|
|
50
|
+
|
|
51
|
+
| 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>`). |
|
|
63
66
|
|
|
64
67
|
## CLI
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
|
69
|
-
|
|
|
70
|
-
| `abide
|
|
71
|
-
| `abide
|
|
72
|
-
| `abide
|
|
73
|
-
| `abide
|
|
74
|
-
| `abide
|
|
75
|
-
| `abide
|
|
76
|
-
| `abide
|
|
77
|
-
| `abide
|
|
78
|
-
| `abide
|
|
79
|
-
| `abide lsp` | Run the `.abide` language server over stdio (JSON-RPC) for editor diagnostics. |
|
|
80
|
-
| `abide init-agent` | Write/refresh the abide agent-guide pointer in the project root `CLAUDE.md`. |
|
|
69
|
+
| 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). |
|
|
81
82
|
|
|
82
83
|
For tests, add `preload = ["@abide/abide/preload"]` under `[test]` in
|
|
83
84
|
`bunfig.toml` and run `bun test`.
|
|
84
85
|
|
|
85
86
|
## Authoring contracts
|
|
86
87
|
|
|
87
|
-
**RPC** (`src/server/rpc/<name>.ts`)
|
|
88
|
-
The handler receives
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
request
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
`
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
`
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
`fn.watch(handler)` / `fn.watch(args, handler)`. A streaming handler
|
|
121
|
-
(`jsonl`/`sse`) makes the bare call return a `NamedAsyncIterable<Frame>`
|
|
122
|
-
synchronously — `for await (… of fn(args))`, never `await`.
|
|
123
|
-
|
|
124
|
-
**Socket** (`src/server/sockets/<name>.ts`). `export const x = socket(opts?)`.
|
|
125
|
-
`opts` (`SocketOptions`): `tail` (retention count — kept frames for late joiners
|
|
126
|
-
/ reconnects; server default 1), `ttl` (evict retained frames older than N ms,
|
|
127
|
-
lazy), `clientPublish` (allow publishes over the wire; off by default), `schema`
|
|
128
|
-
(validate publish payloads; flips mcp/cli `clients` on), `clients`. A `Socket<T>`
|
|
129
|
-
extends `AsyncIterable<T>` (bare `for await` is the live stream, no replay) with
|
|
130
|
-
`publish(frame)`, `tail(count?)` (subscription seeded from the retained tail),
|
|
131
|
-
`peek()`, `pending()`/`refreshing()`/`done()`/`error()`, `refresh()`, and
|
|
132
|
-
`watch(handler)`. HTTP face at `/__abide/sockets/<name>`: `GET` reads the retained
|
|
133
|
-
tail, `POST` publishes (only when `clientPublish`).
|
|
134
|
-
|
|
135
|
-
**Page / layout** (`src/ui/pages/**`). `page` (isomorphic `PageSnapshot` proxy)
|
|
136
|
-
exposes `route`, `params`, `url` (browser-space `URL`, base-prefixed), and
|
|
137
|
-
`navigating`; read a field inside an effect/derived and it re-runs on navigation.
|
|
138
|
-
`url(path, params?, query?)` builds base-correct links; `navigate(path, …)`
|
|
139
|
-
performs typed in-app SPA navigation (params first for `[name]` routes, then
|
|
140
|
-
`{ replace?, keepScroll? }`). A layout renders the active page via `{children()}`.
|
|
141
|
-
|
|
142
|
-
**`app.ts` / `config.ts`.** `app.ts` default- or named-exports the `AppModule`
|
|
143
|
-
hooks (all optional; `init({ server })` may return a cleanup run on
|
|
144
|
-
SIGINT/SIGTERM; `handle(request, next)` is single middleware; `health(request)`
|
|
145
|
-
merges into `/__abide/health`, runs before `handle`, and is public). `config.ts`
|
|
146
|
-
exports `config` — `env(schema)` (validated, typed, throws on bad config at boot)
|
|
147
|
-
or the unvalidated `Bun.env` floor.
|
|
148
|
-
|
|
149
|
-
**The isomorphism move.** There is no `cache()` wrapper. A bare smart RPC call
|
|
150
|
-
read inline during SSR is captured in the per-request cache; the runtime
|
|
151
|
-
snapshots each settled entry into a wire-safe form serialized into the HTML, and
|
|
152
|
-
the client seeds its store from it on hydration — the same call hydrates warm
|
|
153
|
-
instead of re-firing. Streaming reads are snapshotted again after the stream
|
|
154
|
-
drains and seeded over the wire.
|
|
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.
|
|
155
121
|
|
|
156
122
|
## .abide template grammar
|
|
157
123
|
|
|
158
|
-
A
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
`
|
|
167
|
-
|
|
168
|
-
a
|
|
169
|
-
|
|
170
|
-
Reactive state
|
|
171
|
-
|
|
172
|
-
| Form
|
|
173
|
-
|
|
174
|
-
| `state
|
|
175
|
-
| `state.computed(
|
|
176
|
-
| `state.linked(
|
|
177
|
-
| `watch
|
|
178
|
-
| `props
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
`
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
|
187
|
-
|
|
188
|
-
| `
|
|
189
|
-
| `
|
|
190
|
-
| `
|
|
191
|
-
| `
|
|
192
|
-
| `
|
|
193
|
-
| `{
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
`{
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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.
|
|
135
|
+
|
|
136
|
+
### Reactive state
|
|
137
|
+
|
|
138
|
+
| 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)`. |
|
|
141
|
+
| `state.computed(() => …)` | Read-only derived cell (lazy, never serialized). |
|
|
142
|
+
| `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(...)`.
|
|
149
|
+
|
|
150
|
+
### Bindings & directives
|
|
151
|
+
|
|
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={…}`). |
|
|
158
|
+
| `bind:value` / `bind:checked` / `bind:group` | Two-way form binds. |
|
|
159
|
+
| `bind:value={{ get, set }}` | Derived two-way bind. |
|
|
160
|
+
| `class:name={cond}` | Toggle a class. |
|
|
161
|
+
| `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: the **bare call in an interpolation**
|
|
180
|
+
(`{getMessages({ limit })}`) is the documented default — a peek that reads
|
|
181
|
+
`undefined` while pending (auto-streaming on SSR), composing with `?? fallback`,
|
|
182
|
+
`?.`, `{#if}`, and attributes; pair it with `.pending(...)` / `.error(...)`
|
|
183
|
+
probes. `{await fn()}` is an inline read that blocks SSR until the value is in the
|
|
184
|
+
HTML. `{#await}` is the opt-in for branch structure and `{:then}` narrowing — not
|
|
185
|
+
the default.
|
|
186
|
+
|
|
187
|
+
Components are capitalised tags; the content nested in a component renders where
|
|
188
|
+
it calls `{children()}` (`{#if children}{children()}{:else}…{/if}` is the
|
|
189
|
+
fallback — no `<slot>` element, no named slots). The `<slot>` element, the
|
|
190
|
+
`<template name>` snippet form, and `<template if>`/`<template each>`/… control
|
|
191
|
+
flow were **removed** — a bare `<template>` is now an inert element, and using a
|
|
192
|
+
removed form throws a migration error.
|
|
221
193
|
|
|
222
194
|
## Server surface — abide/server/*
|
|
223
195
|
|
|
224
196
|
### RPC helpers — @documentation rpc
|
|
225
197
|
|
|
226
|
-
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
-
|
|
230
|
-
|
|
231
|
-
-
|
|
232
|
-
-
|
|
233
|
-
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
-
|
|
246
|
-
`
|
|
247
|
-
|
|
248
|
-
-
|
|
249
|
-
|
|
250
|
-
declares a reusable typed-error constructor driving `fn.isError(e, 'name')`.
|
|
251
|
-
- `@abide/abide/server/redirect` — `redirect(url, status=302, init?)`:
|
|
252
|
-
`TypedResponse<never>`, accepts relative URLs, `no-store`, status restricted to
|
|
253
|
-
301/302/303/307/308.
|
|
198
|
+
- `abide/server/GET` — read helper; `export const x = GET(fn, opts?)`. Adds
|
|
199
|
+
`cache`/`stream` opts. Read-only, so a typed input auto-exposes it to MCP + CLI.
|
|
200
|
+
- `abide/server/HEAD` — read helper, same shape as `GET` (headers only, no body).
|
|
201
|
+
- `abide/server/POST` — mutating helper; `RpcSharedOpts` only (`cache`/`stream`
|
|
202
|
+
are a compile error). Never auto-exposes to MCP.
|
|
203
|
+
- `abide/server/PUT` — mutating helper (idempotent replace); as `POST`.
|
|
204
|
+
- `abide/server/PATCH` — mutating helper (partial update); as `POST`.
|
|
205
|
+
- `abide/server/DELETE` — mutating helper (removal); as `POST`.
|
|
206
|
+
|
|
207
|
+
### Response helpers — @documentation response
|
|
208
|
+
|
|
209
|
+
- `abide/server/json` — `json<T>(data, init?)` → `TypedResponse<T>`; default
|
|
210
|
+
`no-store`, honest-JSON encodes Set/Map/bigint/Date (ADR-0029); `undefined` body
|
|
211
|
+
→ 204.
|
|
212
|
+
- `abide/server/jsonl` — `jsonl<Frame>(iterable, init?)`; JSON Lines stream
|
|
213
|
+
(`application/jsonl`), cancellation calls `iter.return()`, a generator error
|
|
214
|
+
emits a final `{"$error":…}` line.
|
|
215
|
+
- `abide/server/sse` — `sse<Frame>(iterable, init?)`; `text/event-stream` with a
|
|
216
|
+
15 s keepalive comment; an error emits an `event: error` frame.
|
|
217
|
+
- `abide/server/error` — `error(status, message?, init?)` → `TypedResponse<never>`
|
|
218
|
+
(positional status wins). `error.typed(name, status, schema?)` returns a typed
|
|
219
|
+
error constructor driving `rpc.isError`; `schema` types `.data`, never validates.
|
|
220
|
+
- `abide/server/redirect` — `redirect(url, status = 302, init?)`; accepts relative
|
|
221
|
+
URLs, sets `Location` + `no-store`.
|
|
254
222
|
|
|
255
223
|
### Request scope — @documentation request-scope
|
|
256
224
|
|
|
257
|
-
-
|
|
258
|
-
|
|
259
|
-
-
|
|
260
|
-
|
|
261
|
-
-
|
|
262
|
-
no-op
|
|
225
|
+
- `abide/server/request` — `request(): Request`; the inbound request from the ALS
|
|
226
|
+
scope, throws outside one.
|
|
227
|
+
- `abide/server/cookies` — `cookies(): Bun.CookieMap`; live read + `.set` / `.delete`,
|
|
228
|
+
flushed as `Set-Cookie` on return.
|
|
229
|
+
- `abide/server/server` — `server(): Server`; the active `Bun.serve`, an in-process
|
|
230
|
+
no-op server under CLI/MCP/test, else throws.
|
|
263
231
|
|
|
264
|
-
###
|
|
232
|
+
### Server rendering — @documentation render
|
|
265
233
|
|
|
266
|
-
-
|
|
267
|
-
|
|
268
|
-
typed config; also registers the schema for the launcher setup form.
|
|
234
|
+
- `abide/server/render` — `render(path, params?, query?)`: SSR a page route to an
|
|
235
|
+
HTML string from within a request scope; args mirror `url()`/`navigate`.
|
|
269
236
|
|
|
270
237
|
### Sockets — @documentation sockets
|
|
271
238
|
|
|
272
|
-
-
|
|
273
|
-
|
|
274
|
-
`schema`/`clients` (server-only; the client stub discards them).
|
|
239
|
+
- `abide/server/socket` — `socket<T>(opts?)`: declare one broadcast topic per file
|
|
240
|
+
under `src/server/sockets/`. Returns `Socket<T>` (isomorphic `AsyncIterable<T>`).
|
|
275
241
|
|
|
276
|
-
###
|
|
242
|
+
### Configuration — @documentation configuration
|
|
277
243
|
|
|
278
|
-
-
|
|
279
|
-
|
|
280
|
-
(forwarding caller auth); the handler picks transport via `jsonl`/`sse`. Exports
|
|
281
|
-
`NeutralMessage`, `AgentFrame`, `AgentSurface`, `AgentEngine` types.
|
|
244
|
+
- `abide/server/env` — `env(schema)`: validate `Bun.env` against a Standard Schema
|
|
245
|
+
at module top-level, returning typed config; fails boot loudly, sync-only.
|
|
282
246
|
|
|
283
|
-
###
|
|
247
|
+
### App hooks & inspector — @documentation plumbing
|
|
284
248
|
|
|
285
|
-
-
|
|
286
|
-
(`
|
|
287
|
-
-
|
|
249
|
+
- `abide/server/AppModule` — `type AppModule`: the optional hooks exported from
|
|
250
|
+
`src/app.ts` (`init`/`handle`/`handleError`/`health`/`forwardHeaders`).
|
|
251
|
+
- `abide/server/InspectorContext` — `type InspectorContext`: the capabilities core
|
|
288
252
|
injects into `@abide/inspector` when `ABIDE_ENABLE_INSPECTOR=true`.
|
|
289
|
-
- `@abide/abide/server/rpc/defineRpc` — bundler-emitted RPC builder: resolves
|
|
290
|
-
`clients`, validates input/files, applies `timeout`, registers the entry.
|
|
291
|
-
- `@abide/abide/server/sockets/defineSocket` — bundler-emitted socket builder:
|
|
292
|
-
per-subscriber queue + retained tail, optional `ttl`/`schema`, Bun-native
|
|
293
|
-
fan-out.
|
|
294
|
-
- `@abide/abide/server/prompts/definePrompt` — resolver-emitted prompt builder
|
|
295
|
-
from `src/mcp/prompts/<name>.md`; registers with the MCP dispatcher.
|
|
296
|
-
- `@abide/abide/server/prompts/renderPromptTemplate` — substitutes `{{name}}`
|
|
297
|
-
placeholders in a prompt template body (missing args → empty string).
|
|
298
253
|
|
|
299
|
-
|
|
254
|
+
### RPC / socket registrars — @documentation plumbing
|
|
300
255
|
|
|
301
|
-
|
|
256
|
+
- `abide/server/rpc/defineRpc` — `defineRpc(method, url, handler, opts?)`: the real
|
|
257
|
+
server-side RPC builder the bundler emits; runs validation → 422, timeout → 504,
|
|
258
|
+
and registers the registry entry.
|
|
259
|
+
- `abide/server/sockets/defineSocket` — `defineSocket<T>(name, opts?)`: the server
|
|
260
|
+
runtime `socket()` binds to; retention defaults to `tail ?? 1`, `ttl` evicts
|
|
261
|
+
lazily, schema validates synchronously on publish.
|
|
302
262
|
|
|
303
|
-
|
|
304
|
-
attaches a `toJSONSchema()` projection to a Standard Schema whose library lacks
|
|
305
|
-
one native (feeds OpenAPI / MCP / CLI / setup form).
|
|
263
|
+
## Isomorphic surface — abide/shared/*
|
|
306
264
|
|
|
307
|
-
###
|
|
265
|
+
### RPC schema helper — @documentation rpc
|
|
308
266
|
|
|
309
|
-
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
- `@abide/abide/shared/ValidationErrorData` — type of `HttpError.data` when
|
|
313
|
-
`kind === 'validation'`: `{ issues, fields }` (raw Standard Schema issues + a
|
|
314
|
-
field→first-message map).
|
|
267
|
+
- `abide/shared/withJsonSchema` — `withJsonSchema(schema, toJsonSchema)`: attach a
|
|
268
|
+
`toJSONSchema()` projection to a Standard Schema whose library lacks one; feeds
|
|
269
|
+
OpenAPI / MCP / CLI.
|
|
315
270
|
|
|
316
|
-
###
|
|
271
|
+
### Response errors — @documentation response
|
|
317
272
|
|
|
318
|
-
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
-
|
|
322
|
-
|
|
323
|
-
|
|
273
|
+
- `abide/shared/HttpError` — `class HttpError extends Error`; thrown by remote
|
|
274
|
+
calls on non-2xx. Carries `status`, `statusText`, `response`, optional `kind`
|
|
275
|
+
(declared error name or `'validation'`) and `data`.
|
|
276
|
+
- `abide/shared/ValidationErrorData` — `type ValidationErrorData`: the
|
|
277
|
+
`HttpError.data` shape when `kind === 'validation'` (422) — `{ issues, fields }`,
|
|
278
|
+
`fields` mapping top-level field → first message.
|
|
324
279
|
|
|
325
|
-
###
|
|
280
|
+
### Cache — @documentation cache
|
|
326
281
|
|
|
327
|
-
-
|
|
328
|
-
|
|
329
|
-
re-runs on navigation.
|
|
282
|
+
Isomorphic per ADR-0041: local on the client, broadcast to every connected client
|
|
283
|
+
from the server.
|
|
330
284
|
|
|
331
|
-
|
|
285
|
+
- `abide/shared/invalidate` — `invalidate(selector?, args?)`: the drop verb — drop
|
|
286
|
+
matching cached reads so the next read reloads lazily. Selector grammar:
|
|
287
|
+
`(fn, args)` / `(fn)` / `({ tags })` / `()`.
|
|
288
|
+
- `abide/shared/patch` — `patch(fn, args?, updater)` / `patch({ tags }, updater)`:
|
|
289
|
+
mutate the retained value of matching reads in place, reactive, no network (the
|
|
290
|
+
optimistic-update / socket-frame primitive).
|
|
291
|
+
- `abide/shared/refresh` — `refresh(selector?, args?)`: refetch matching reads now,
|
|
292
|
+
keeping the stale value visible (`refreshing()` true) until the fresh one swaps in.
|
|
332
293
|
|
|
333
|
-
|
|
334
|
-
"no value yet" over cached calls and tail streams.
|
|
335
|
-
- `@abide/abide/shared/peek` — `peek(source, args?)` — the currently-retained
|
|
336
|
-
value synchronously, triggering nothing; `undefined` when nothing retained.
|
|
337
|
-
- `@abide/abide/shared/refreshing` — `refreshing(source?, args?): boolean` —
|
|
338
|
-
reactive "holding a value while a fresher source is in flight".
|
|
339
|
-
- `@abide/abide/shared/done` — `done(subscribable): boolean` — reactive terminal
|
|
340
|
-
read: true once a stream closed.
|
|
341
|
-
- `@abide/abide/shared/online` — `online(): boolean` — reactive connectivity
|
|
342
|
-
probe (browser online/offline; server reflects the caller's reported state).
|
|
294
|
+
### Probes — @documentation probes
|
|
343
295
|
|
|
344
|
-
|
|
296
|
+
Reactive, report-never-act — reading opens no fetch/stream.
|
|
345
297
|
|
|
346
|
-
-
|
|
347
|
-
|
|
348
|
-
|
|
298
|
+
- `abide/shared/pending` — `pending(selector?/subscribable?, args?): boolean` — "no
|
|
299
|
+
value yet"; `()` = anything in flight.
|
|
300
|
+
- `abide/shared/peek` — `peek(fn, args?)` / `peek(socket)` / `peek(cell)`: the
|
|
301
|
+
retained value synchronously, no trigger; null-tolerant.
|
|
302
|
+
- `abide/shared/refreshing` — `refreshing(selector?, args?): boolean` — holding a
|
|
303
|
+
prior value while a fresher source is in flight (distinct from `pending`).
|
|
304
|
+
- `abide/shared/done` — `done(subscribable): boolean` — stream-only, true once the
|
|
305
|
+
source has closed.
|
|
306
|
+
- `abide/shared/online` — `online(): boolean` — reactive connectivity; browser
|
|
307
|
+
reads `navigator.onLine`, server reflects the calling client's reported state.
|
|
349
308
|
|
|
350
|
-
###
|
|
309
|
+
### Observability — @documentation observability
|
|
351
310
|
|
|
352
|
-
-
|
|
353
|
-
`
|
|
354
|
-
|
|
311
|
+
- `abide/shared/health` — `health(): HealthState` — reactive backend-health read;
|
|
312
|
+
polls `/__abide/health` only while a tracking scope reads it. Also exports
|
|
313
|
+
`AppHealth` / `AppHealthMap` / `HealthState` types.
|
|
314
|
+
- `abide/shared/log` — `log: Log`, callable (`log(message, data?)` is the info-level
|
|
315
|
+
speaker) with members `log.warn` / `log.error` / `log.trace(name, work)` /
|
|
316
|
+
`log.channel(name)` (DEBUG-gated); records carry request-scope context, tsv or
|
|
317
|
+
JSON under `ABIDE_LOG_FORMAT=json`. (No `log.info` — the base call is info.)
|
|
318
|
+
- `abide/shared/reachable` — `reachable(host?): Promise<boolean>`: isomorphic
|
|
319
|
+
outbound reachability, HEADs the origin, caches per TTL; no host = the app's own
|
|
320
|
+
backend (constant `true` on server).
|
|
321
|
+
- `abide/shared/trace` — `trace(): string | undefined`: the current request's W3C
|
|
322
|
+
`traceparent` (server from the ALS scope, browser from `__SSR__`).
|
|
323
|
+
|
|
324
|
+
### Page & URL — @documentation page / url
|
|
325
|
+
|
|
326
|
+
- `abide/shared/page` — `page: PageSnapshot`: reactive proxy with `route` /
|
|
327
|
+
`params` / `url` / `navigating` for the active page; reading a field in a tracked
|
|
328
|
+
scope re-runs on navigation. (`@documentation page`)
|
|
329
|
+
- `abide/shared/url` — `url(path, ...args): string`: resolve any in-app URL to its
|
|
330
|
+
base-correct form (RPC → query, page route → typed params, asset → bare path);
|
|
331
|
+
throws on a missing required param. Also exports `RpcRoutes` / `PageRoutes` /
|
|
332
|
+
`PublicAssets` / `PathParams`. (`@documentation url`)
|
|
355
333
|
|
|
356
|
-
###
|
|
334
|
+
### Templating — @documentation templating
|
|
357
335
|
|
|
358
|
-
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
(`log(...)`, `.warn`/`.error`/`.trace`, `.channel(name)` for a DEBUG-gated
|
|
363
|
-
channel); TSV by default, JSON under `ABIDE_LOG_FORMAT=json`.
|
|
364
|
-
- `@abide/abide/shared/reachable` — `reachable(host?): Promise<boolean>` —
|
|
365
|
-
isomorphic outbound reachability HEAD probe, cached per TTL; any response counts
|
|
366
|
-
reachable, only connection failure/timeout is not.
|
|
367
|
-
- `@abide/abide/shared/trace` — `trace(): string | undefined` — the current
|
|
368
|
-
request's W3C `traceparent` (server from ALS, browser from `__SSR__`).
|
|
336
|
+
- `abide/shared/snippet` — `snippet<Payload>(payload): SnippetValue`: brand a
|
|
337
|
+
payload so a `{expr}` interpolation mounts it instead of escaping (client builder
|
|
338
|
+
/ server string). Also exports `snippetPayload` and the `Snippet` / `SnippetValue`
|
|
339
|
+
types. (The `html` primitive lives under the UI surface.)
|
|
369
340
|
|
|
370
|
-
###
|
|
341
|
+
### Subscriber — @documentation plumbing
|
|
371
342
|
|
|
372
|
-
-
|
|
373
|
-
|
|
374
|
-
|
|
343
|
+
- `abide/shared/createSubscriber` — `createSubscriber(start)`: an
|
|
344
|
+
open-on-first-read / close-on-last-reader primitive over abide's signal core, for
|
|
345
|
+
building custom reactive sources.
|
|
375
346
|
|
|
376
347
|
## UI surface — abide/ui/* (client-only)
|
|
377
348
|
|
|
378
349
|
### Reactive state — @documentation reactive-state
|
|
379
350
|
|
|
380
|
-
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
reactive names); compiler-lowered inside a component; throws if called directly.
|
|
351
|
+
- `abide/ui/state` — the `state(v, transform?)` writable cell plus members
|
|
352
|
+
`state.computed` (read-only derived), `state.linked` (writable, reseeded from a
|
|
353
|
+
thunk), and the context seam `state.share` / `state.shared`.
|
|
354
|
+
- `abide/ui/watch` — the single reaction primitive `watch(source, handler)` (and
|
|
355
|
+
bare `watch(thunk)` auto-tracked effect); unifies effect / socket.on / cache.on /
|
|
356
|
+
rpc-selector; client-only, returns a scope-tied disposer.
|
|
357
|
+
- `abide/ui/props` — `props()` prop reader; compiler-lowered inside a `.abide`
|
|
358
|
+
component, throws if called directly.
|
|
389
359
|
|
|
390
360
|
### Templating — @documentation templating
|
|
391
361
|
|
|
392
|
-
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
-
|
|
409
|
-
|
|
410
|
-
-
|
|
411
|
-
|
|
412
|
-
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
-
|
|
425
|
-
|
|
426
|
-
-
|
|
427
|
-
|
|
428
|
-
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
-
|
|
437
|
-
|
|
438
|
-
-
|
|
439
|
-
|
|
440
|
-
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
-
|
|
460
|
-
(
|
|
461
|
-
-
|
|
462
|
-
|
|
463
|
-
-
|
|
464
|
-
|
|
465
|
-
-
|
|
466
|
-
|
|
467
|
-
-
|
|
468
|
-
|
|
469
|
-
-
|
|
470
|
-
|
|
471
|
-
-
|
|
472
|
-
|
|
473
|
-
-
|
|
474
|
-
|
|
475
|
-
-
|
|
476
|
-
|
|
477
|
-
-
|
|
478
|
-
|
|
479
|
-
-
|
|
480
|
-
|
|
481
|
-
-
|
|
482
|
-
|
|
483
|
-
-
|
|
484
|
-
|
|
485
|
-
-
|
|
486
|
-
|
|
487
|
-
-
|
|
488
|
-
|
|
489
|
-
-
|
|
490
|
-
|
|
491
|
-
-
|
|
492
|
-
the
|
|
493
|
-
-
|
|
494
|
-
|
|
495
|
-
-
|
|
496
|
-
|
|
497
|
-
-
|
|
498
|
-
|
|
499
|
-
-
|
|
500
|
-
|
|
501
|
-
-
|
|
502
|
-
|
|
503
|
-
-
|
|
504
|
-
|
|
505
|
-
-
|
|
506
|
-
|
|
507
|
-
-
|
|
508
|
-
|
|
509
|
-
-
|
|
510
|
-
|
|
511
|
-
-
|
|
512
|
-
|
|
362
|
+
- `abide/ui/html` — `html` marks a string as trusted raw HTML (`html(str)` or
|
|
363
|
+
tagged `` html`…` ``) so a `{expr}` inserts nodes unescaped; nullish → empty.
|
|
364
|
+
|
|
365
|
+
### Navigation — @documentation navigate
|
|
366
|
+
|
|
367
|
+
- `abide/ui/navigate` — `navigate(path, params?, options?)`: typed client
|
|
368
|
+
navigation through `url()`, writing history + the reactive route
|
|
369
|
+
(`replace`/`keepScroll`). Also exports `navigatePath` and `NavigateOptions`.
|
|
370
|
+
|
|
371
|
+
### Effect — @documentation plumbing
|
|
372
|
+
|
|
373
|
+
- `abide/ui/effect` — the internal effect primitive the compiler emits (runs now,
|
|
374
|
+
re-runs on change, supports teardown/async); authors use `watch`.
|
|
375
|
+
|
|
376
|
+
### Client entry, router & proxies — @documentation plumbing
|
|
377
|
+
|
|
378
|
+
- `abide/ui/startClient` — the official client entry: reads `window.__SSR__`, seeds
|
|
379
|
+
cache/cell/doc state, installs slots, starts the router; returns a disposer.
|
|
380
|
+
- `abide/ui/router` — the History-API router: matches the path, imports page +
|
|
381
|
+
layout chunks, mounts them as an outlet chain, diffs on navigation, restores scroll.
|
|
382
|
+
- `abide/ui/renderToStream` — server async generator for out-of-order SSR streaming:
|
|
383
|
+
yields the shell, then one `<abide-resolve>` fragment per streaming await block.
|
|
384
|
+
- `abide/ui/remoteProxy` — the client-side RPC substitute: a `RemoteFunction` that
|
|
385
|
+
fetches over the network (input pre-flight validation, timeout/abort merge, output
|
|
386
|
+
decode). Also exports `RemoteProxyOptions`.
|
|
387
|
+
- `abide/ui/socketProxy` — the client-side `Socket` substitute over the multiplexed
|
|
388
|
+
ws channel (iterate / `.tail` / `.publish` / real `.watch`).
|
|
389
|
+
|
|
390
|
+
### Scope & render-pass plumbing — @documentation plumbing
|
|
391
|
+
|
|
392
|
+
- `abide/ui/currentScope` — resolve the current lexical reactive scope (mints a
|
|
393
|
+
detached root outside any scope).
|
|
394
|
+
- `abide/ui/enterRenderScope` — establish a fresh per-render SSR scope, returning the
|
|
395
|
+
previous to restore.
|
|
396
|
+
- `abide/ui/exitRenderScope` — restore the scope `enterRenderScope` saved.
|
|
397
|
+
- `abide/ui/settleAsyncCells` — SSR await-barrier draining the request-scoped
|
|
398
|
+
pending-cell list; no-op on client.
|
|
399
|
+
- `abide/ui/flight` — start a hoisted SSR flight from a thunk with a synchronous
|
|
400
|
+
`settled`/`value`/`error` snapshot so flights overlap. Also exports `FlightPromise`.
|
|
401
|
+
- `abide/ui/isolateCellBarrier` — run a hoisted child render under its own async-cell
|
|
402
|
+
barrier list; inert passthrough on client.
|
|
403
|
+
- `abide/ui/finalizeStreamedChildren` — the ADR-0039 when-to-stream decision after a
|
|
404
|
+
body walk (inline a settled flight, rethrow a rejected one, or stream). Also
|
|
405
|
+
exports `StagedChild`.
|
|
406
|
+
- `abide/ui/runtime/withPath` — push one escaped render-path segment for a
|
|
407
|
+
synchronous `build`, restoring after.
|
|
408
|
+
- `abide/ui/runtime/renderPath` — compose a child's ordinal segment onto the ambient
|
|
409
|
+
path, returning the render-path string (the streamed-child boundary id).
|
|
410
|
+
- `abide/ui/runtime/escapeKey` — escape one path key to an RFC-6901 JSON-Pointer token.
|
|
411
|
+
- `abide/ui/runtime/nextBlockId` — allocate the next await/try block id in the current
|
|
412
|
+
render pass, namespaced by the ambient path.
|
|
413
|
+
- `abide/ui/runtime/blockId` — allocate a render-path-namespaced block id
|
|
414
|
+
(`${path}:${n}`) from a counters map.
|
|
415
|
+
- `abide/ui/runtime/enterRenderPass` — mark entry into a render/mount, clearing the
|
|
416
|
+
per-path block-id counters at the outermost depth.
|
|
417
|
+
- `abide/ui/runtime/exitRenderPass` — mark exit from a render/mount, unwinding the depth.
|
|
418
|
+
|
|
419
|
+
### DOM runtime helpers — @documentation plumbing
|
|
420
|
+
|
|
421
|
+
Compiler-emitted; an author never imports these directly.
|
|
422
|
+
|
|
423
|
+
- `abide/ui/dom/mount` — mount a top-level page/layout into a host under an ownership
|
|
424
|
+
scope + render pass; returns a disposer.
|
|
425
|
+
- `abide/ui/dom/mountChild` — mount a child component as a marker-bounded range (no
|
|
426
|
+
wrapper), filing its dispose with the owner.
|
|
427
|
+
- `abide/ui/dom/mountStreamedChild` — dual-mode adopter for a hoistable child: adopt an
|
|
428
|
+
inlined range, claim a streamed boundary, or create-mount fresh.
|
|
429
|
+
- `abide/ui/dom/mountSlot` — mount a component's slot content as a marker-bounded range
|
|
430
|
+
(create fills, hydrate adopts); runs once.
|
|
431
|
+
- `abide/ui/dom/mergeProps` — compose a child's props from ordered layers (runs,
|
|
432
|
+
spreads, slot) into one last-layer-wins Proxy bag.
|
|
433
|
+
- `abide/ui/dom/spreadProps` — wrap a `{...source}` spread as a live Proxy of value
|
|
434
|
+
thunks (keys/membership re-evaluated live; `children` hidden).
|
|
435
|
+
- `abide/ui/dom/restProps` — live Proxy of the unconsumed props for `{ ...rest }`,
|
|
436
|
+
excluding destructured keys and `children`.
|
|
437
|
+
- `abide/ui/dom/bindProp` — the parent half of a component `bind:prop`: annotate the
|
|
438
|
+
value thunk with a `.set` write-back channel.
|
|
439
|
+
- `abide/ui/dom/bindableProp` — the child half of a two-way prop: pass-through when the
|
|
440
|
+
parent bound it, else a local reseeding `linked` cell.
|
|
441
|
+
- `abide/ui/dom/spreadAttrs` — spread a thunk's keys onto a native element (`on*` →
|
|
442
|
+
listeners, others → reactive attributes); `exclude` skips explicit attrs.
|
|
443
|
+
- `abide/ui/dom/readCall` — guarded non-optional method call on a reactive-document
|
|
444
|
+
read, with legible authored-scope errors.
|
|
445
|
+
- `abide/ui/dom/readCell` — unified read of a `computed`/`linked`/derive reference
|
|
446
|
+
(async → throwing peek, function → call, sync → `.value`).
|
|
447
|
+
- `abide/ui/dom/writeCell` — unified write of a `linked` reference from an author
|
|
448
|
+
assignment (async → `.set`, sync → `.value =`).
|
|
449
|
+
- `abide/ui/dom/cellPending` — whether a control-flow subject is a still-loading async
|
|
450
|
+
cell, so a block renders no branch while loading.
|
|
451
|
+
- `abide/ui/dom/mutateDocContainer` — apply an in-place container mutation
|
|
452
|
+
(`splice`/`sort`/`add`/…) on a reactive-document value by clone-mutate-`replace`.
|
|
453
|
+
- `abide/ui/dom/hydrate` — adopt existing server-rendered DOM in place with a claim
|
|
454
|
+
cursor active (no re-render); returns a disposer.
|
|
455
|
+
- `abide/ui/dom/appendText` — a reactive `{expr}` interpolation: escaped text,
|
|
456
|
+
`html`-branded raw, or a snippet call; create-appends or claims the SSR text node.
|
|
457
|
+
- `abide/ui/dom/appendTextAt` — a reactive `{expr}` mounted just after a skeleton
|
|
458
|
+
anchor comment (text interleaved with element siblings).
|
|
459
|
+
- `abide/ui/dom/appendSnippet` — mount a `{snippet(args)}` builder in a marker-bounded
|
|
460
|
+
range, reactive in its args.
|
|
461
|
+
- `abide/ui/dom/appendStatic` — a static (non-reactive) text node; create-appends or
|
|
462
|
+
claims the merged SSR text node.
|
|
463
|
+
- `abide/ui/dom/cloneStatic` — append a fully-static binding-free subtree by cloning a
|
|
464
|
+
cached template (create) / advancing the claim cursor (hydrate).
|
|
465
|
+
- `abide/ui/dom/skeleton` — realize a compiled skeleton (static subtree with located
|
|
466
|
+
holes) and return its element + anchor holes.
|
|
467
|
+
- `abide/ui/dom/anchorCursor` — position a skeleton-anchored block/slot: the create
|
|
468
|
+
insertion reference after the anchor and the hydrate claim cursor.
|
|
469
|
+
- `abide/ui/dom/outlet` — a layout outlet: insert/claim an empty `abide:outlet` comment
|
|
470
|
+
boundary; returns `{ open, close }`.
|
|
471
|
+
- `abide/ui/dom/attr` — bind an element attribute to `read()` via one effect
|
|
472
|
+
(boolean-present/absent + stringify semantics).
|
|
473
|
+
- `abide/ui/dom/on` — attach an event listener (`onclick={…}` target), scope-pinned and
|
|
474
|
+
batched, removal registered on the owner.
|
|
475
|
+
- `abide/ui/dom/attach` — run an `attach={…}` attachment against an element at build
|
|
476
|
+
time and register its teardown (the dual of `on`).
|
|
477
|
+
- `abide/ui/dom/bindSelectValue` — two-way `bind:value` for `<select>` (effect drives
|
|
478
|
+
selection re-applied via MutationObserver; `change` writes back; `multiple` → array).
|
|
479
|
+
- `abide/ui/dom/each` — keyed list runtime (`{#for … by key}`): each row a
|
|
480
|
+
marker-bounded range, reconciled by key with minimal DOM moves.
|
|
481
|
+
- `abide/ui/dom/eachAsync` — async keyed list runtime (`{#for await … of …}`):
|
|
482
|
+
append/reconcile as the iterator yields; mid-stream rejection routes to the catch.
|
|
483
|
+
- `abide/ui/dom/when` — conditional runtime (`{#if}`/`{:else}`): a swappable range
|
|
484
|
+
tracking `condition()`, with an optional pending state for a bare async subject.
|
|
485
|
+
- `abide/ui/dom/awaitBlock` — `{#await}` runtime: render pending then swap to
|
|
486
|
+
resolved/error; reactive re-runs on cache invalidation; hydration resumes or rebuilds.
|
|
487
|
+
- `abide/ui/dom/tryBlock` — `{#try}` reactive error-boundary runtime: catch build /
|
|
488
|
+
read / re-run throws, swap to the catch branch, re-arm on recovery.
|
|
489
|
+
- `abide/ui/dom/switchBlock` — `{#switch}` / `{:else if}` runtime: a swappable range
|
|
490
|
+
selecting the first matching (`===`) case (or default), with per-case async gating.
|
|
513
491
|
|
|
514
492
|
## Build / tooling
|
|
515
493
|
|
|
516
494
|
### Building — @documentation building
|
|
517
495
|
|
|
518
|
-
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
-
|
|
528
|
-
|
|
529
|
-
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
-
|
|
542
|
-
|
|
543
|
-
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
496
|
+
- `abide/build` — `build({ cwd, … })`: build the client bundle into `dist/_app`
|
|
497
|
+
(optional gzip/Tailwind), and write `src/.abide/*.d.ts`.
|
|
498
|
+
- `abide/compile` — `compile({ cwd, target?, outfile? })`: build the client then a
|
|
499
|
+
standalone Bun server executable; returns the binary path.
|
|
500
|
+
|
|
501
|
+
### Plumbing — @documentation plumbing
|
|
502
|
+
|
|
503
|
+
- `abide/preload` — the Bun preload registering `abideUiPlugin` +
|
|
504
|
+
`abideResolverPlugin` + a css-noop loader; `ABIDE_TARGET` selects server/client mode.
|
|
505
|
+
- `abide/resolver-plugin` — `abideResolverPlugin({ cwd, embedAssets, target })`: the
|
|
506
|
+
BunPlugin wiring the `abide:*` virtuals and rewriting rpc/socket/prompt modules.
|
|
507
|
+
- `abide/ui-plugin` — `abideUiPlugin`: the BunPlugin that loads `.abide` components
|
|
508
|
+
(compiles to an ES module; a `layout.abide` lowers its outlet to the router).
|
|
509
|
+
- `abide/tsconfig` — the consumer-extendable `tsconfig.app.json`.
|
|
510
|
+
|
|
511
|
+
## Desktop bundle
|
|
512
|
+
|
|
513
|
+
### Desktop bundle — @documentation bundle
|
|
514
|
+
|
|
515
|
+
- `abide/bundle/BundleWindow` — `type BundleWindow`: the default-export window config
|
|
516
|
+
(title/size/menu) from `src/bundle/window.ts`, baked into the launcher.
|
|
517
|
+
- `abide/bundle/BundleMenu` — `type BundleMenu`: a `{ label, items }` top-level menu
|
|
518
|
+
inserted between the Edit and Window menus.
|
|
519
|
+
- `abide/bundle/BundleMenuItem` — `type BundleMenuItem`: a divider or clickable item
|
|
520
|
+
(`emit` dispatches an `abide:menu` event, `navigate` repoints the window, `shortcut`
|
|
521
|
+
= Cmd-key).
|
|
522
|
+
- `abide/bundle/onMenu` — `onMenu(handler)` / `onMenu(name, handler)`: subscribe to
|
|
523
|
+
`abide:menu` clicks, returns unsubscribe; inert in SSR / a plain browser tab.
|
|
524
|
+
- `abide/bundle/bundled` — `bundled(): boolean`: true when running inside the abide
|
|
525
|
+
desktop bundle (isomorphic).
|
|
526
|
+
- `abide/server/appDataDir` — `appDataDir()`: the running bundle's per-user data dir
|
|
527
|
+
keyed by program name (pure, cwd-independent). (`@documentation bundle`)
|
|
528
|
+
|
|
529
|
+
## MCP
|
|
530
|
+
|
|
531
|
+
### MCP server — @documentation mcp
|
|
532
|
+
|
|
533
|
+
- `abide/mcp/createMcpServer` — `createMcpServer(opts?)`: construct the framework MCP
|
|
534
|
+
server bound to the RPC registry; `handle(request)` is what `/__abide/mcp` invokes.
|
|
535
|
+
Tools come from rpcs/sockets with `clients.mcp: true`; optional `authorize`.
|
|
536
|
+
|
|
537
|
+
### Agent — @documentation agent
|
|
538
|
+
|
|
539
|
+
- `abide/server/agent` — `agent(engine, messages)`: run a model engine against the
|
|
540
|
+
app's own MCP surface, returning the neutral `AgentFrame` stream (no transport — wrap
|
|
541
|
+
in `jsonl()` / `sse()`).
|
|
542
|
+
|
|
543
|
+
### Prompts — @documentation plumbing
|
|
544
|
+
|
|
545
|
+
- `abide/server/prompts/definePrompt` — `definePrompt(name, opts)`: build + register a
|
|
546
|
+
`Prompt`; called by the resolver-generated module for each `src/mcp/prompts/*.md`.
|
|
547
|
+
- `abide/server/prompts/renderPromptTemplate` — `renderPromptTemplate(template, args)`:
|
|
548
|
+
substitute `{{name}}` placeholders (missing → empty string).
|
|
562
549
|
|
|
563
550
|
## Testing
|
|
564
551
|
|
|
565
552
|
### Testing — @documentation testing
|
|
566
553
|
|
|
567
|
-
-
|
|
568
|
-
|
|
569
|
-
[Symbol.asyncDispose] }` (`rpc`/`sockets` typed by generated d.ts). Use
|
|
570
|
-
`await using`.
|
|
554
|
+
- `abide/test/createTestApp` — the augmentable typed test app exposing
|
|
555
|
+
`app.rpc.<rpc>` / `app.sockets.<name>` (types arrive from `src/.abide/` after a build).
|
|
571
556
|
|
|
572
|
-
###
|
|
557
|
+
### Plumbing — @documentation plumbing
|
|
573
558
|
|
|
574
|
-
-
|
|
575
|
-
|
|
576
|
-
-
|
|
577
|
-
|
|
578
|
-
`tool_use
|
|
559
|
+
- `abide/test/createScriptedSurface` — `createScriptedSurface(...)`: a scripted
|
|
560
|
+
`AgentSurface` with declarative tool stubs for engine tests; records every `call`.
|
|
561
|
+
- `abide/test/assertAgentFrameConformance` — `assertAgentFrameConformance(...)`: assert
|
|
562
|
+
the neutral `AgentFrame` contract (single terminal `done`, matched
|
|
563
|
+
`tool_use`/`tool_result`, string deltas).
|
|
579
564
|
|
|
580
565
|
## Generated machine surfaces
|
|
581
566
|
|
|
582
|
-
Runtime routes the framework
|
|
567
|
+
Runtime routes served by the framework:
|
|
583
568
|
|
|
584
|
-
| Route
|
|
585
|
-
|
|
586
|
-
| `/openapi.json`
|
|
587
|
-
| `/__abide/mcp`
|
|
588
|
-
| `/__abide/
|
|
589
|
-
| `/__abide/
|
|
590
|
-
| `/__abide/
|
|
591
|
-
| `/__abide/
|
|
592
|
-
| `/__abide/
|
|
569
|
+
| Route | Serves |
|
|
570
|
+
|---|---|
|
|
571
|
+
| `/openapi.json` | The generated OpenAPI document. |
|
|
572
|
+
| `/__abide/mcp` | The generated MCP server endpoint (`createMcpServer().handle`). |
|
|
573
|
+
| `/__abide/sockets` | The sockets hub (ws multiplex); `/__abide/sockets/<name>` is the per-socket HTTP/SSE face (GET tail, POST publish). |
|
|
574
|
+
| `/__abide/health` | Health JSON, aggregating the app's optional `health(request)` hook. |
|
|
575
|
+
| `/__abide/identity` | Server identity (name / version). |
|
|
576
|
+
| `/__abide/cli` | CLI binary download; `/__abide/cli/<platform>` streams the thin cli + server. |
|
|
577
|
+
| `/__abide/inspector` | Inspector surface, mounted only when `ABIDE_ENABLE_INSPECTOR=true`. |
|
|
593
578
|
|
|
594
579
|
## Environment variables
|
|
595
580
|
|
|
596
|
-
|
|
|
597
|
-
|
|
598
|
-
| `PORT`
|
|
599
|
-
| `APP_URL`
|
|
600
|
-
| `
|
|
601
|
-
| `
|
|
602
|
-
| `
|
|
603
|
-
| `ABIDE_DATA_DIR`
|
|
604
|
-
| `ABIDE_CLIENT_TIMEOUT`
|
|
605
|
-
| `
|
|
606
|
-
| `
|
|
607
|
-
| `ABIDE_LOG_FORMAT`
|
|
608
|
-
| `
|
|
609
|
-
| `
|
|
610
|
-
| `
|
|
611
|
-
| `DEBUG`
|
|
581
|
+
| Var | Effect |
|
|
582
|
+
|---|---|
|
|
583
|
+
| `PORT` | Server listen port. |
|
|
584
|
+
| `APP_URL` | Derives the server's mount base path / public URL. |
|
|
585
|
+
| `ABIDE_APP_DIR` | Overrides the client-asset dir (default `dist/_app`). |
|
|
586
|
+
| `ABIDE_APP_URL` | Default remote server URL the CLI targets when no saved connection. |
|
|
587
|
+
| `ABIDE_APP_TOKEN` | Bearer token the CLI sends to the target server. |
|
|
588
|
+
| `ABIDE_DATA_DIR` | Overrides the app's per-user data dir. |
|
|
589
|
+
| `ABIDE_CLIENT_TIMEOUT` | Client transport timeout in ms (bounded 1–600000). |
|
|
590
|
+
| `ABIDE_IDLE_TIMEOUT` | Server socket idle timeout in seconds (default 10). |
|
|
591
|
+
| `ABIDE_MAX_REQUEST_BODY_SIZE` | Caps the max request body size. |
|
|
592
|
+
| `ABIDE_LOG_FORMAT` | `json` emits structured JSON log records (else tsv). |
|
|
593
|
+
| `ABIDE_DEV_SURFACE` | `1` enables request logging even under dev. |
|
|
594
|
+
| `ABIDE_ENABLE_INSPECTOR` | `true` mounts the inspector at `/__abide/inspector`. |
|
|
595
|
+
| `ABIDE_INSPECT` | Truthy opens the bundle webview with devtools enabled. |
|
|
596
|
+
| `DEBUG` | Enables debug logging; the value is the namespace filter (supports negation). |
|
|
612
597
|
|
|
613
598
|
---
|
|
614
599
|
|
|
615
|
-
|
|
616
|
-
`bun run packages/abide/scripts/readmeSurfaces.ts`
|
|
617
|
-
export to keep this map honest.
|
|
600
|
+
This map mirrors `package.json`'s `exports`. After adding, renaming, or removing an
|
|
601
|
+
export, run `bun run packages/abide/scripts/readmeSurfaces.ts` and update this file.
|