@abide/abide 0.50.1 → 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 +486 -490
- package/CHANGELOG.md +39 -0
- package/README.md +130 -149
- 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 +141 -25
- package/src/lib/ui/compile/lowerDocAccess.ts +2 -14
- package/src/lib/ui/compile/lowerScript.ts +23 -0
- package/src/lib/ui/compile/renameSignalRefs.ts +65 -0
- package/src/lib/ui/compile/wrapReactionCellSources.ts +117 -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,605 +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
|
|
33
|
-
|
|
34
|
-
(`
|
|
35
|
-
`
|
|
36
|
-
|
|
37
|
-
|
|
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 }`.
|
|
38
45
|
|
|
39
46
|
## File-based conventions
|
|
40
47
|
|
|
41
|
-
The bundler
|
|
42
|
-
`$server`, `$ui`, `$shared`, `$mcp`, `$cli
|
|
48
|
+
The bundler (`abideResolverPlugin`) reads these paths. Import aliases into the
|
|
49
|
+
tree: `$server`, `$ui`, `$shared`, `$mcp`, `$cli`.
|
|
43
50
|
|
|
44
51
|
| Path | Meaning |
|
|
45
52
|
|---|---|
|
|
46
|
-
| `src/server/rpc/<name>.ts` | One RPC per file;
|
|
47
|
-
| `src/server/sockets/<name>.ts` | One
|
|
48
|
-
| `src/
|
|
49
|
-
| `src/
|
|
50
|
-
| `src/
|
|
51
|
-
| `src/app.
|
|
52
|
-
| `src/
|
|
53
|
-
| `src/
|
|
54
|
-
| `src/
|
|
55
|
-
| `src/
|
|
56
|
-
| `src/bundle/window.ts` |
|
|
57
|
-
| `src
|
|
58
|
-
| `
|
|
59
|
-
| `dist/` | Build output: `dist/_app/` (prod client) or `dist/_app.gen-<id>/` (dev), `dist/app` (compiled binary), `dist/cli-*`. |
|
|
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>`). |
|
|
60
66
|
|
|
61
67
|
## CLI
|
|
62
68
|
|
|
63
|
-
`abide <command>` (the `abide` bin):
|
|
64
|
-
|
|
65
69
|
| Command | Does |
|
|
66
70
|
|---|---|
|
|
67
|
-
| `abide scaffold <name> [--no-install] [--no-dev]` | Scaffold
|
|
68
|
-
| `abide dev` |
|
|
69
|
-
| `abide build` | Single client build into `dist/_app
|
|
70
|
-
| `abide
|
|
71
|
-
| `abide
|
|
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). |
|
|
72
77
|
| `abide compile [--target=<bun-…>] [--out=<path>]` | Build a standalone server executable. |
|
|
73
|
-
| `abide cli [--target
|
|
74
|
-
| `abide bundle` | Assemble a self-contained desktop app bundle for
|
|
75
|
-
| `abide
|
|
76
|
-
| `abide
|
|
77
|
-
| `abide init-agent` | Write/refresh the abide agent-guide pointer in the project root `CLAUDE.md`. |
|
|
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). |
|
|
78
82
|
|
|
79
83
|
For tests, add `preload = ["@abide/abide/preload"]` under `[test]` in
|
|
80
84
|
`bunfig.toml` and run `bun test`.
|
|
81
85
|
|
|
82
86
|
## Authoring contracts
|
|
83
87
|
|
|
84
|
-
**RPC** (`src/server/rpc/<name>.ts`)
|
|
85
|
-
The handler receives
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
request
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
`fn.watch(handler)` / `fn.watch(args, handler)`. A streaming handler
|
|
118
|
-
(`jsonl`/`sse`) makes the bare call return a `NamedAsyncIterable<Frame>`
|
|
119
|
-
synchronously — `for await (… of fn(args))`, never `await`.
|
|
120
|
-
|
|
121
|
-
**Socket** (`src/server/sockets/<name>.ts`). `export const x = socket(opts?)`.
|
|
122
|
-
`opts` (`SocketOptions`): `tail` (retention count — kept frames for late joiners
|
|
123
|
-
/ reconnects; server default 1), `ttl` (evict retained frames older than N ms,
|
|
124
|
-
lazy), `clientPublish` (allow publishes over the wire; off by default), `schema`
|
|
125
|
-
(validate publish payloads; flips mcp/cli `clients` on), `clients`. A `Socket<T>`
|
|
126
|
-
extends `AsyncIterable<T>` (bare `for await` is the live stream, no replay) with
|
|
127
|
-
`publish(frame)`, `tail(count?)` (subscription seeded from the retained tail),
|
|
128
|
-
`peek()`, `pending()`/`refreshing()`/`done()`/`error()`, `refresh()`, and
|
|
129
|
-
`watch(handler)`. HTTP face at `/__abide/sockets/<name>`: `GET` reads the retained
|
|
130
|
-
tail, `POST` publishes (only when `clientPublish`).
|
|
131
|
-
|
|
132
|
-
**Page / layout** (`src/ui/pages/**`). `page` (isomorphic `PageSnapshot` proxy)
|
|
133
|
-
exposes `route`, `params`, `url` (browser-space `URL`, base-prefixed), and
|
|
134
|
-
`navigating`; read a field inside an effect/derived and it re-runs on navigation.
|
|
135
|
-
`url(path, params?, query?)` builds base-correct links; `navigate(path, …)`
|
|
136
|
-
performs typed in-app SPA navigation (params first for `[name]` routes, then
|
|
137
|
-
`{ replace?, keepScroll? }`). A layout renders the active page via `{children()}`.
|
|
138
|
-
|
|
139
|
-
**`app.ts` / `config.ts`.** `app.ts` default- or named-exports the `AppModule`
|
|
140
|
-
hooks (all optional; `init({ server })` may return a cleanup run on
|
|
141
|
-
SIGINT/SIGTERM; `handle(request, next)` is single middleware; `health(request)`
|
|
142
|
-
merges into `/__abide/health`, runs before `handle`, and is public). `config.ts`
|
|
143
|
-
exports `config` — `env(schema)` (validated, typed, throws on bad config at boot)
|
|
144
|
-
or the unvalidated `Bun.env` floor.
|
|
145
|
-
|
|
146
|
-
**The isomorphism move.** There is no `cache()` wrapper. A bare smart RPC call
|
|
147
|
-
read inline during SSR is captured in the per-request cache; the runtime
|
|
148
|
-
snapshots each settled entry into a wire-safe form serialized into the HTML, and
|
|
149
|
-
the client seeds its store from it on hydration — the same call hydrates warm
|
|
150
|
-
instead of re-firing. Streaming reads are snapshotted again after the stream
|
|
151
|
-
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.
|
|
152
121
|
|
|
153
122
|
## .abide template grammar
|
|
154
123
|
|
|
155
|
-
A
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
subtree). A *root* `<style>` is component-scoped. Reactive primitives are reached
|
|
161
|
-
through their own imported bindings (alias-safe) — `state` from `abide/ui/state`,
|
|
162
|
-
`watch` from `abide/ui/watch`, `html` from `abide/ui/html`, `snippet` from
|
|
163
|
-
`abide/shared/snippet` — never through `scope()` (internal plumbing). The one
|
|
164
|
-
ambient reader is `props()` (no import).
|
|
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()`.
|
|
165
129
|
|
|
166
|
-
|
|
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
|
|
167
137
|
|
|
168
138
|
| Form | Meaning |
|
|
169
139
|
|---|---|
|
|
170
|
-
| `state
|
|
171
|
-
| `state.computed(
|
|
172
|
-
| `state.linked(
|
|
173
|
-
| `watch
|
|
174
|
-
| `props
|
|
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()`). |
|
|
175
145
|
|
|
176
|
-
|
|
177
|
-
`
|
|
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(...)`.
|
|
178
149
|
|
|
179
|
-
|
|
150
|
+
### Bindings & directives
|
|
151
|
+
|
|
152
|
+
| Syntax | Meaning |
|
|
180
153
|
|---|---|
|
|
181
|
-
| `{expr}` |
|
|
182
|
-
| `
|
|
183
|
-
| `
|
|
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={…}`). |
|
|
184
158
|
| `bind:value` / `bind:checked` / `bind:group` | Two-way form binds. |
|
|
185
|
-
| `bind:value={{ get, set }}` | Derived two-way
|
|
159
|
+
| `bind:value={{ get, set }}` | Derived two-way bind. |
|
|
186
160
|
| `class:name={cond}` | Toggle a class. |
|
|
187
161
|
| `style:property={value}` | Set one style property. |
|
|
188
|
-
| `attach={fn}` | Run
|
|
189
|
-
| `{...spread}` | Spread an object's keys
|
|
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
|
|
190
166
|
|
|
191
|
-
|
|
167
|
+
Mustache `{#…}` blocks, not `<template>`. The rendered blocks are
|
|
168
|
+
`if`/`for`/`await`/`switch`/`try`; `snippet` is a declaration block.
|
|
192
169
|
|
|
193
170
|
| Block | Form |
|
|
194
171
|
|---|---|
|
|
195
|
-
|
|
|
196
|
-
|
|
|
197
|
-
|
|
|
198
|
-
|
|
|
199
|
-
|
|
|
200
|
-
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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.
|
|
209
193
|
|
|
210
194
|
## Server surface — abide/server/*
|
|
211
195
|
|
|
212
196
|
### RPC helpers — @documentation rpc
|
|
213
197
|
|
|
214
|
-
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
-
|
|
218
|
-
|
|
219
|
-
-
|
|
220
|
-
-
|
|
221
|
-
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
-
|
|
234
|
-
`
|
|
235
|
-
|
|
236
|
-
-
|
|
237
|
-
|
|
238
|
-
declares a reusable typed-error constructor driving `fn.isError(e, 'name')`.
|
|
239
|
-
- `@abide/abide/server/redirect` — `redirect(url, status=302, init?)`:
|
|
240
|
-
`TypedResponse<never>`, accepts relative URLs, `no-store`, status restricted to
|
|
241
|
-
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`.
|
|
242
222
|
|
|
243
223
|
### Request scope — @documentation request-scope
|
|
244
224
|
|
|
245
|
-
-
|
|
246
|
-
|
|
247
|
-
-
|
|
248
|
-
|
|
249
|
-
-
|
|
250
|
-
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.
|
|
251
231
|
|
|
252
|
-
###
|
|
232
|
+
### Server rendering — @documentation render
|
|
253
233
|
|
|
254
|
-
-
|
|
255
|
-
|
|
256
|
-
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`.
|
|
257
236
|
|
|
258
237
|
### Sockets — @documentation sockets
|
|
259
238
|
|
|
260
|
-
-
|
|
261
|
-
|
|
262
|
-
`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>`).
|
|
263
241
|
|
|
264
|
-
###
|
|
242
|
+
### Configuration — @documentation configuration
|
|
265
243
|
|
|
266
|
-
-
|
|
267
|
-
|
|
268
|
-
(forwarding caller auth); the handler picks transport via `jsonl`/`sse`. Exports
|
|
269
|
-
`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.
|
|
270
246
|
|
|
271
|
-
###
|
|
247
|
+
### App hooks & inspector — @documentation plumbing
|
|
272
248
|
|
|
273
|
-
-
|
|
274
|
-
(`
|
|
275
|
-
-
|
|
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
|
|
276
252
|
injects into `@abide/inspector` when `ABIDE_ENABLE_INSPECTOR=true`.
|
|
277
|
-
- `@abide/abide/server/rpc/defineRpc` — bundler-emitted RPC builder: resolves
|
|
278
|
-
`clients`, validates input/files, applies `timeout`, registers the entry.
|
|
279
|
-
- `@abide/abide/server/sockets/defineSocket` — bundler-emitted socket builder:
|
|
280
|
-
per-subscriber queue + retained tail, optional `ttl`/`schema`, Bun-native
|
|
281
|
-
fan-out.
|
|
282
|
-
- `@abide/abide/server/prompts/definePrompt` — resolver-emitted prompt builder
|
|
283
|
-
from `src/mcp/prompts/<name>.md`; registers with the MCP dispatcher.
|
|
284
|
-
- `@abide/abide/server/prompts/renderPromptTemplate` — substitutes `{{name}}`
|
|
285
|
-
placeholders in a prompt template body (missing args → empty string).
|
|
286
253
|
|
|
287
|
-
|
|
254
|
+
### RPC / socket registrars — @documentation plumbing
|
|
288
255
|
|
|
289
|
-
|
|
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.
|
|
290
262
|
|
|
291
|
-
|
|
292
|
-
attaches a `toJSONSchema()` projection to a Standard Schema whose library lacks
|
|
293
|
-
one native (feeds OpenAPI / MCP / CLI / setup form).
|
|
263
|
+
## Isomorphic surface — abide/shared/*
|
|
294
264
|
|
|
295
|
-
###
|
|
265
|
+
### RPC schema helper — @documentation rpc
|
|
296
266
|
|
|
297
|
-
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
- `@abide/abide/shared/ValidationErrorData` — type of `HttpError.data` when
|
|
301
|
-
`kind === 'validation'`: `{ issues, fields }` (raw Standard Schema issues + a
|
|
302
|
-
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.
|
|
303
270
|
|
|
304
|
-
###
|
|
271
|
+
### Response errors — @documentation response
|
|
305
272
|
|
|
306
|
-
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
-
|
|
310
|
-
|
|
311
|
-
|
|
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.
|
|
312
279
|
|
|
313
|
-
###
|
|
280
|
+
### Cache — @documentation cache
|
|
314
281
|
|
|
315
|
-
-
|
|
316
|
-
|
|
317
|
-
re-runs on navigation.
|
|
282
|
+
Isomorphic per ADR-0041: local on the client, broadcast to every connected client
|
|
283
|
+
from the server.
|
|
318
284
|
|
|
319
|
-
|
|
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.
|
|
320
293
|
|
|
321
|
-
|
|
322
|
-
"no value yet" over cached calls and tail streams.
|
|
323
|
-
- `@abide/abide/shared/peek` — `peek(source, args?)` — the currently-retained
|
|
324
|
-
value synchronously, triggering nothing; `undefined` when nothing retained.
|
|
325
|
-
- `@abide/abide/shared/refreshing` — `refreshing(source?, args?): boolean` —
|
|
326
|
-
reactive "holding a value while a fresher source is in flight".
|
|
327
|
-
- `@abide/abide/shared/done` — `done(subscribable): boolean` — reactive terminal
|
|
328
|
-
read: true once a stream closed.
|
|
329
|
-
- `@abide/abide/shared/online` — `online(): boolean` — reactive connectivity
|
|
330
|
-
probe (browser online/offline; server reflects the caller's reported state).
|
|
294
|
+
### Probes — @documentation probes
|
|
331
295
|
|
|
332
|
-
|
|
296
|
+
Reactive, report-never-act — reading opens no fetch/stream.
|
|
333
297
|
|
|
334
|
-
-
|
|
335
|
-
|
|
336
|
-
|
|
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.
|
|
337
308
|
|
|
338
|
-
###
|
|
309
|
+
### Observability — @documentation observability
|
|
339
310
|
|
|
340
|
-
-
|
|
341
|
-
`
|
|
342
|
-
|
|
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`)
|
|
343
333
|
|
|
344
|
-
###
|
|
334
|
+
### Templating — @documentation templating
|
|
345
335
|
|
|
346
|
-
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
(`log(...)`, `.warn`/`.error`/`.trace`, `.channel(name)` for a DEBUG-gated
|
|
351
|
-
channel); TSV by default, JSON under `ABIDE_LOG_FORMAT=json`.
|
|
352
|
-
- `@abide/abide/shared/reachable` — `reachable(host?): Promise<boolean>` —
|
|
353
|
-
isomorphic outbound reachability HEAD probe, cached per TTL; any response counts
|
|
354
|
-
reachable, only connection failure/timeout is not.
|
|
355
|
-
- `@abide/abide/shared/trace` — `trace(): string | undefined` — the current
|
|
356
|
-
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.)
|
|
357
340
|
|
|
358
|
-
###
|
|
341
|
+
### Subscriber — @documentation plumbing
|
|
359
342
|
|
|
360
|
-
-
|
|
361
|
-
|
|
362
|
-
|
|
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.
|
|
363
346
|
|
|
364
347
|
## UI surface — abide/ui/* (client-only)
|
|
365
348
|
|
|
366
349
|
### Reactive state — @documentation reactive-state
|
|
367
350
|
|
|
368
|
-
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
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.
|
|
377
359
|
|
|
378
360
|
### Templating — @documentation templating
|
|
379
361
|
|
|
380
|
-
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
-
the
|
|
481
|
-
-
|
|
482
|
-
|
|
483
|
-
-
|
|
484
|
-
|
|
485
|
-
-
|
|
486
|
-
|
|
487
|
-
-
|
|
488
|
-
|
|
489
|
-
-
|
|
490
|
-
|
|
491
|
-
-
|
|
492
|
-
|
|
493
|
-
-
|
|
494
|
-
|
|
495
|
-
-
|
|
496
|
-
|
|
497
|
-
-
|
|
498
|
-
|
|
499
|
-
-
|
|
500
|
-
|
|
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.
|
|
501
491
|
|
|
502
492
|
## Build / tooling
|
|
503
493
|
|
|
504
494
|
### Building — @documentation building
|
|
505
495
|
|
|
506
|
-
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
-
|
|
516
|
-
|
|
517
|
-
-
|
|
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
|
-
|
|
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).
|
|
550
549
|
|
|
551
550
|
## Testing
|
|
552
551
|
|
|
553
552
|
### Testing — @documentation testing
|
|
554
553
|
|
|
555
|
-
-
|
|
556
|
-
|
|
557
|
-
[Symbol.asyncDispose] }` (`rpc`/`sockets` typed by generated d.ts). Use
|
|
558
|
-
`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).
|
|
559
556
|
|
|
560
|
-
###
|
|
557
|
+
### Plumbing — @documentation plumbing
|
|
561
558
|
|
|
562
|
-
-
|
|
563
|
-
|
|
564
|
-
-
|
|
565
|
-
|
|
566
|
-
`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).
|
|
567
564
|
|
|
568
565
|
## Generated machine surfaces
|
|
569
566
|
|
|
570
|
-
Runtime routes the framework
|
|
567
|
+
Runtime routes served by the framework:
|
|
571
568
|
|
|
572
569
|
| Route | Serves |
|
|
573
570
|
|---|---|
|
|
574
|
-
| `/openapi.json` |
|
|
575
|
-
| `/__abide/mcp` | MCP endpoint (
|
|
576
|
-
| `/__abide/
|
|
577
|
-
| `/__abide/
|
|
578
|
-
| `/__abide/
|
|
579
|
-
| `/__abide/
|
|
580
|
-
| `/__abide/
|
|
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`. |
|
|
581
578
|
|
|
582
579
|
## Environment variables
|
|
583
580
|
|
|
584
|
-
|
|
|
581
|
+
| Var | Effect |
|
|
585
582
|
|---|---|
|
|
586
|
-
| `PORT` |
|
|
587
|
-
| `APP_URL` | Derives the server's mount base path
|
|
588
|
-
| `
|
|
589
|
-
| `
|
|
590
|
-
| `
|
|
591
|
-
| `ABIDE_DATA_DIR` | Overrides the app
|
|
592
|
-
| `ABIDE_CLIENT_TIMEOUT` |
|
|
593
|
-
| `
|
|
594
|
-
| `
|
|
595
|
-
| `ABIDE_LOG_FORMAT` | `json`
|
|
596
|
-
| `
|
|
597
|
-
| `
|
|
598
|
-
| `
|
|
599
|
-
| `DEBUG` |
|
|
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). |
|
|
600
597
|
|
|
601
598
|
---
|
|
602
599
|
|
|
603
|
-
|
|
604
|
-
`bun run packages/abide/scripts/readmeSurfaces.ts`
|
|
605
|
-
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.
|