@abide/abide 0.28.0 → 0.30.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 +242 -0
- package/CHANGELOG.md +22 -0
- package/bin/abide.ts +13 -1
- package/package.json +2 -1
- package/src/initAgent.ts +50 -0
- package/src/lib/ui/compile/compileShadow.ts +25 -6
- package/src/lib/ui/router.ts +22 -5
- package/template/CLAUDE.md +22 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# AGENTS.md — abide complete surface map
|
|
2
|
+
|
|
3
|
+
> Generated from the source as the single index of abide's public featureset, so an
|
|
4
|
+
> agent can grasp the whole API in one read and know which file to open for depth.
|
|
5
|
+
> The README is the curated human intro (3 primitives); this is the exhaustive map.
|
|
6
|
+
> CONTEXT.md is the domain glossary; docs/adr/ holds design rationale.
|
|
7
|
+
>
|
|
8
|
+
> **Ground rule:** every public name has its own module path — no barrels. The
|
|
9
|
+
> namespace marks the side: `abide/server/*` server-only, `abide/ui/*` client-only,
|
|
10
|
+
> `abide/shared/*` isomorphic (same callable, same behaviour on both sides; the
|
|
11
|
+
> bundler swaps the runtime). Package: `@abide/abide`. Runtime: Bun ≥ 1.3, web
|
|
12
|
+
> standards only, zero runtime deps.
|
|
13
|
+
>
|
|
14
|
+
> **Two kinds of path below.** Import specifiers (`abide/server/GET`) are the stable
|
|
15
|
+
> identity — that's what you import. File paths like `src/lib/...` are *inside the
|
|
16
|
+
> abide package* (relative to this file: `packages/abide/` in this repo,
|
|
17
|
+
> `node_modules/@abide/abide/` when abide is a dependency); the published package
|
|
18
|
+
> ships `src`, so those files are readable in both. Paths like `src/server/rpc/...`
|
|
19
|
+
> and `src/.abide/...` in the conventions table refer to **your own app**, not the
|
|
20
|
+
> package.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## The premise
|
|
25
|
+
|
|
26
|
+
One declared verb fans out to every surface, with no extra work:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
export const getMessages = GET(fn, { inputSchema })
|
|
30
|
+
│
|
|
31
|
+
┌───────────────┬────────────┼──────────────┬────────────────┐
|
|
32
|
+
SSR call browser fetch MCP tool CLI subcommand OpenAPI op
|
|
33
|
+
cache(fn)() fetch /rpc/... (read-only + app get-messages /openapi.json
|
|
34
|
+
(in-process) (typed proxy) schema → tool) (schema → flags) (described)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
A Standard Schema (zod / valibot / arktype, unadapted) is the contract: it
|
|
38
|
+
validates args and projects the CLI flags, the MCP tool, and the OpenAPI operation.
|
|
39
|
+
The schema gates the machine surfaces — it unlocks CLI and (for read-only/GET verbs)
|
|
40
|
+
MCP; a mutating verb never auto-exposes to MCP, it needs explicit `clients: { mcp: true }`.
|
|
41
|
+
|
|
42
|
+
## File-based conventions (the bundler reads these paths)
|
|
43
|
+
|
|
44
|
+
| Path | Meaning |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `src/server/rpc/<name>.ts` | one RPC verb per file; file path = URL (`/rpc/<name>`), one export named after the file |
|
|
47
|
+
| `src/server/sockets/<name>.ts` | one broadcast socket per file; multiplexes onto `/__abide/sockets` |
|
|
48
|
+
| `src/mcp/prompts/<name>.md` | MCP prompt template (`{{arg}}` placeholders) → `definePrompt` |
|
|
49
|
+
| `src/server/config.ts` | `export const config = env(schema)` — boot-validated env, also drives the bundle setup form |
|
|
50
|
+
| `src/app.ts` | optional app module: `handleError`, `health()` hook, lifecycle (see `AppModule`) |
|
|
51
|
+
| `src/bundle/window.ts` | optional desktop bundle window config (`BundleWindow`, default export) |
|
|
52
|
+
| `**/page.abide` | a page; directory path → route in bracket form (`/post/[id]`, `/docs/[...rest]`) |
|
|
53
|
+
| `**/layout.abide` | nearest-only layout wrapping a page via `<slot/>` (layouts never stack) |
|
|
54
|
+
| `src/.abide/*.d.ts` | generated: route/param/rpc/health types (do not hand-edit) |
|
|
55
|
+
| `public/<file>` | static asset, served at `/<file>` |
|
|
56
|
+
| `dist/_app/` | `abide build` output; `dist/` is what `abide start` serves |
|
|
57
|
+
|
|
58
|
+
## CLI (`bunx abide <cmd>` / `abide <cmd>`)
|
|
59
|
+
|
|
60
|
+
| Command | Does |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `scaffold <name>` | scaffold a project, install, start dev (`--no-install` / `--no-dev`) |
|
|
63
|
+
| `dev` | build + run with hot reload |
|
|
64
|
+
| `build` | build the client into `dist/_app/` |
|
|
65
|
+
| `check` | type-check `.abide` templates + props |
|
|
66
|
+
| `start` | run the production server against `dist/` |
|
|
67
|
+
| `run <file> [args]` | run a script under the abide preload (same runtime as the server) |
|
|
68
|
+
| `compile [--target] [--out]` | build a standalone server executable |
|
|
69
|
+
| `cli [--target] [--out] [--platforms]` | build the cli client binary (ships the server beside it; cross-compiles) |
|
|
70
|
+
| `bundle` | build a movable self-contained desktop app bundle (unsigned) |
|
|
71
|
+
| `lsp` | language server for `.abide` files |
|
|
72
|
+
|
|
73
|
+
For `bun test`, add `preload = ["@abide/abide/preload"]` under `[test]` in `bunfig.toml`.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Server surface — `abide/server/*` (and authored helpers)
|
|
78
|
+
|
|
79
|
+
### RPC verbs — `@readme rpc`
|
|
80
|
+
Each is `Verb(fn, opts?)` with three overloads (see `src/lib/server/rpc/types/VerbHelper.ts`):
|
|
81
|
+
`Verb(fn, { inputSchema, outputSchema?, filesSchema?, clients?, timeout?, maxBodySize?, crossOrigin? })`,
|
|
82
|
+
`Verb(fn, { clients })`, and bare `Verb(fn)`. Return type usually infers from the
|
|
83
|
+
`TypedResponse<T>` brand on `json`/`error`/`redirect`/`jsonl`/`sse`.
|
|
84
|
+
|
|
85
|
+
- `abide/server/GET` · `abide/server/POST` · `abide/server/PUT` · `abide/server/PATCH` · `abide/server/DELETE` · `abide/server/HEAD`
|
|
86
|
+
- **Query args travel as strings** — use `z.coerce.*` in the schema for numbers/booleans.
|
|
87
|
+
- `timeout` (ms) bounds the handler on *every* surface → 504; on the network path it also aborts `request().signal`.
|
|
88
|
+
- `crossOrigin: true` exempts a mutating verb from the same-origin CSRF gate (non-GET/HEAD cross-origin browser requests are 403 by default).
|
|
89
|
+
- `filesSchema` enables multipart upload: handler gets text fields ∩ validated `File` parts; call with a `FormData`.
|
|
90
|
+
- `abide/shared/withJsonSchema(schema, toJsonSchema)` — attach `toJSONSchema()` to a schema whose library lacks one (feeds OpenAPI / MCP / CLI help).
|
|
91
|
+
|
|
92
|
+
The reference each verb returns is a `RemoteFunction<Args, Return>` (see `src/lib/shared/types/RemoteFunction.ts`):
|
|
93
|
+
plain call decodes the body + throws `HttpError` on non-2xx; `.raw` resolves to the
|
|
94
|
+
`Response` undecoded; `.stream(args)` returns a `Subscribable` (SSE/JSONL frames, or
|
|
95
|
+
the decoded body once) for `tail()`; `.fetch(req)` is framework-internal dispatch.
|
|
96
|
+
|
|
97
|
+
### Responses — `@readme response`
|
|
98
|
+
- `abide/server/json(data, init?)` — JSON with `Cache-Control: no-store`; `json(undefined)` → 204 that round-trips to `undefined`.
|
|
99
|
+
- `abide/server/error(status, message?, init?)` — text/plain error; message defaults to the status reason phrase.
|
|
100
|
+
- `abide/server/redirect(url, status=302, init?)` — accepts relative URLs (platform `Response.redirect` doesn't); 301/302/303/307/308.
|
|
101
|
+
- `abide/server/jsonl(iterable, init?)` — wrap an `AsyncIterable` as JSON Lines stream; errors → final `{"$error":…}` line.
|
|
102
|
+
- `abide/server/sse(iterable, init?)` — wrap an `AsyncIterable` as Server-Sent Events; 15s keepalive; errors → `event: error`.
|
|
103
|
+
- `abide/shared/HttpError` — thrown by remote calls on non-2xx; carries `status`, `statusText`, raw `response`.
|
|
104
|
+
|
|
105
|
+
### Sockets — `@readme sockets`
|
|
106
|
+
- `abide/server/socket(opts?)` — declare a `Socket<T>` (an isomorphic `AsyncIterable<T>`) inside `src/server/sockets/<name>.ts`. Opts: `{ schema?, tail?, ttl?, clientPublish?, clients? }`. `tail: n` retains last n frames; `ttl` evicts older. HTTP face at `/__abide/sockets/<name>`: GET = retained tail, POST = publish (gated by `clientPublish`).
|
|
107
|
+
|
|
108
|
+
### Agents — `@readme agent`
|
|
109
|
+
- `abide/server/agent(engine, messages)` — run a model engine against the app's own (already-gated) MCP surface; returns the engine's `AgentFrame` stream. Wrap in `jsonl()`/`sse()` to pick the transport. Types exported: `NeutralMessage`, `AgentFrame` (`text`/`tool_use`/`tool_result`/`done`), `AgentSurface`, `AgentEngine`. Engines live in `@abide/<provider>` packages, never in core.
|
|
110
|
+
|
|
111
|
+
### Request scope — `@readme request-scope`
|
|
112
|
+
Resolve only during an in-flight SSR render or RPC handler (throw outside one):
|
|
113
|
+
- `abide/server/request()` → the inbound `Request`.
|
|
114
|
+
- `abide/server/cookies()` → Bun `CookieMap`; writes flush as `Set-Cookie` on return.
|
|
115
|
+
- `abide/server/server()` → active `Bun.serve` (or a no-op in-process server under CLI/MCP/test).
|
|
116
|
+
|
|
117
|
+
### Config / data — `@readme configuration` / `reference`
|
|
118
|
+
- `abide/server/env(schema, opts?)` — validate `Bun.env` against a Standard Schema at module top level; fails boot loudly. Registered so the bundle launcher derives its setup form.
|
|
119
|
+
- `abide/server/appDataDir()` → the running bundle's per-user data dir (cwd-independent, pure).
|
|
120
|
+
|
|
121
|
+
### Observability — `@readme observability`
|
|
122
|
+
- `abide/server/reachable(host)` — server-only outbound reachability; first call probes (HEAD), then warm-polls every TTL. Tuned by `ABIDE_REACHABLE_TTL` / `ABIDE_REACHABLE_TIMEOUT`.
|
|
123
|
+
- `abide/shared/health()` → `HealthState` (framework + app `health()` hook payload, typed via generated `health.d.ts`); also at `/__abide/health`.
|
|
124
|
+
- `abide/shared/log` — unified logger carrying request-scope context; `log()/warn/error/trace` on the app channel, `log.channel(name)` on a `DEBUG`-gated channel. `ABIDE_LOG_FORMAT=json` for JSON lines.
|
|
125
|
+
- `abide/shared/trace()` → current W3C `traceparent` (isomorphic; client reads the SSR-stamped trace).
|
|
126
|
+
|
|
127
|
+
### App / inspector types — `@readme plumbing`
|
|
128
|
+
- `abide/server/AppModule` — shape of `src/app.ts` (error handler, health hook, lifecycle).
|
|
129
|
+
- `abide/server/InspectorContext` — inspector wiring type.
|
|
130
|
+
- `abide/server/rpc/defineVerb`, `abide/server/sockets/defineSocket`, `abide/server/prompts/definePrompt`, `abide/server/prompts/renderPromptTemplate` — bundler-emitted runtime bindings; you don't call these by hand (the `GET`/`socket`/`.md` files compile down to them).
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Isomorphic surface — `abide/shared/*`
|
|
135
|
+
|
|
136
|
+
### Cache — `@readme cache`
|
|
137
|
+
- `abide/shared/cache(fnOrProducer)` → a memoised callable. Key auto-derives (method+url+args for a remote fn; producer-reference+args for a producer). Options `{ ttl?, scope?, global?, invalidate? }`: `ttl` ms-past-resolve (omit = forever, `0` = dedupe-only / the **mutation idiom**); `scope` tags for group invalidation; `global` = process-level store (server); `invalidate: { throttle | debounce }` = stale-while-revalidate refetch policy (GET-only, throws on a write).
|
|
138
|
+
- `cache.invalidate(selector?, args?)` — end retention early (by fn, fn+args, `{ scope }`, or all).
|
|
139
|
+
- `cache.patch(selector, updater, args?)` — optimistic local fold over matching entries.
|
|
140
|
+
- `cache.on(subscribable, (frame, ctx) => …)` — event-driven cache maintenance: run a handler per socket/stream frame; `ctx.invalidate` / `ctx.patch` are coverage-tracked and resync on reconnect. Client-only (no-op on server).
|
|
141
|
+
|
|
142
|
+
### Probes — `@readme probes` (read-only; reading never triggers work)
|
|
143
|
+
- `abide/shared/pending(selector?, args?)` — "no value yet" (in-flight call, or stream awaiting first frame).
|
|
144
|
+
- `abide/shared/refreshing(selector?, args?)` — "holding a value while a fresher source is in flight".
|
|
145
|
+
- `abide/shared/online()` — reactive connectivity probe (server: the calling client's reported state via offline header).
|
|
146
|
+
|
|
147
|
+
Same selector grammar as `cache.invalidate`; also accept a `Subscribable`. See CONTEXT.md → Probe.
|
|
148
|
+
|
|
149
|
+
### Routing / page — `@readme page` / `url`
|
|
150
|
+
- `abide/shared/page` — reactive proxy: `route`, `params`, `url` (browser-space), `navigating`. Isomorphic; re-runs reactive readers on navigation.
|
|
151
|
+
- `abide/shared/url(path, query?)` — type-safe URL builder; augmented by generated `RpcRoutes`/`PageRoutes`/`PublicAssets` for autocomplete. Applies mount base.
|
|
152
|
+
|
|
153
|
+
### Templating values — `@readme plumbing`
|
|
154
|
+
- `abide/shared/html` — `html\`...\`` tagged template → `RawHtml` (trusted markup); `rawHtmlString(v)`.
|
|
155
|
+
- `abide/shared/snippet(payload)` → `Snippet` — a reusable template builder (the `<template name=…>` form).
|
|
156
|
+
- `abide/shared/createSubscriber(start)` — the subscription primitive behind sockets/streams/probes.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## UI surface — `abide/ui/*` (client-only)
|
|
161
|
+
|
|
162
|
+
### Reactive primitives — `@readme plumbing` (in scope inside `.abide`, no import)
|
|
163
|
+
- `abide/ui/state(initial)` → writable `State<T>` (`.value` getter/setter).
|
|
164
|
+
- `abide/ui/derived(compute)` → lazy read-only computed (`.value`); re-derived on resume, never serialized.
|
|
165
|
+
- `abide/ui/effect(fn)` → run now, re-run on dependency change; `fn` may return teardown / be async; returns dispose.
|
|
166
|
+
- `abide/ui/doc(initial?)` → reactive document: immutable tree addressed by path, every change a patch (the substrate under all reactivity / resumability / sync).
|
|
167
|
+
|
|
168
|
+
### Streams
|
|
169
|
+
- `abide/ui/tail(subscribable)` → latest frame (`T | undefined`); `tail(x, { last: n })` → window `T[]`. Reactive latest-wins read of a socket/stream. `tail.status` / `tail.error` expose richer stream state. See CONTEXT.md → Tail.
|
|
170
|
+
|
|
171
|
+
### Navigation / lifecycle — `@readme plumbing`
|
|
172
|
+
- `abide/ui/navigate(path, replace?)` — client navigation.
|
|
173
|
+
- `abide/ui/router(...)`, `abide/ui/startClient(...)`, `abide/ui/renderToStream(render)` — bootstrap/render runtime (compiler/launcher uses these).
|
|
174
|
+
|
|
175
|
+
### DOM + render runtime — `@readme plumbing` (compiler-emitted; you don't hand-write these)
|
|
176
|
+
`abide/ui/dom/{mount,mountChild,hydrate,text,openChild,openRoot,appendText,appendSnippet,appendStatic,cloneStatic,attr,on,attach,each,eachAsync,when,awaitBlock,tryBlock,switchBlock,applyResolved}` and `abide/ui/runtime/{nextBlockId,enterRenderPass,exitRenderPass}`. These are what `analyzeComponent → generateBuild/generateSSR` lower a `.abide` file into. Read them only to understand compiler output.
|
|
177
|
+
- `abide/ui/remoteProxy`, `abide/ui/socketProxy` — the browser-side implementations the bundler swaps in for `GET(...)` / `socket(...)`.
|
|
178
|
+
|
|
179
|
+
### `.abide` component format (see `src/lib/ui/README.md`)
|
|
180
|
+
Valid HTML with `<script>` + native `<template>` control flow + scoped `<style>`.
|
|
181
|
+
- **Bindings:** `{expr}` text, `name={expr}` attr, `onclick={fn}`, `bind:value={…}` / `bind:checked` / `bind:group`, `attach={fn}` (node-lifetime attachment — the dual of `on`; the `use:`-action / `{@attach}` equivalent, lowered to `ui/dom/attach`).
|
|
182
|
+
- **Control flow (native `<template>`):** `if`/`else`, `each={list} as="x" key="x.id"`, `await={p}`/`then`/`catch`, `switch`/`case`/`default`.
|
|
183
|
+
- **Components:** capitalised tags (`<Layout title=…>`); children fill `<slot/>`; props are reactive (passed as thunks). `prop('name')` reads a typed page param.
|
|
184
|
+
- **Snippets / named slots:** `<template name="x" args={…}>` declares a reusable named builder (the `snippet()` form), rendered like a function — covers named slots / `{@render}`.
|
|
185
|
+
- **Reactivity:** write plain assignment (`count += 1`, `items.push(x)`); the compiler lowers it to patches. Deep-field edits wake only that field.
|
|
186
|
+
- **SSR:** byte-identical HTML string; `renderToStream` ships the shell then streams `<template await>` fragments out of order; `hydrate` adopts static structure in place (control-flow blocks + child components fall back to `mount`/re-render — known gap).
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Build / tooling — `@readme plumbing`
|
|
191
|
+
- `abide/ui-plugin` — Bun loader plugin for `.abide` files.
|
|
192
|
+
- `abide/resolver-plugin` — resolves the `$server`/`$ui`/virtual modules and swaps server↔client runtime per target.
|
|
193
|
+
- `abide/preload` — Bun preload registering both plugins (use in `bunfig.toml` `[test]`).
|
|
194
|
+
- `abide/build`, `abide/compile` — programmatic build / standalone-executable compile.
|
|
195
|
+
- `abide/tsconfig` — base tsconfig for consumer apps.
|
|
196
|
+
|
|
197
|
+
## Desktop bundle — `@readme bundle`
|
|
198
|
+
- `abide/bundle/BundleWindow` — `src/bundle/window.ts` default-export shape (`title`, `width`, `height`, `menu`, `config` schema for the first-run setup form).
|
|
199
|
+
- `abide/bundle/BundleMenu`, `abide/bundle/BundleMenuItem` — custom menu types.
|
|
200
|
+
- `abide/bundle/onMenu(handler)` / `onMenu(name, handler)` — subscribe to native menu clicks (returns unsubscribe; drop into an `effect`). Inert outside the bundle.
|
|
201
|
+
- `abide/bundle/bundled()` — true inside the desktop webview (isomorphic detection).
|
|
202
|
+
|
|
203
|
+
## MCP — `@readme plumbing`
|
|
204
|
+
- `abide/mcp/createMcpServer(opts)` — framework-internal; the `abide:mcp` virtual constructs it. Tools derive from verbs/sockets flagged `clients.mcp: true`; auth inherits from the inbound request; optional `authorize` hook. Served at `/__abide/mcp`.
|
|
205
|
+
|
|
206
|
+
## Testing — `@readme testing`
|
|
207
|
+
- `abide/test/createTestApp()` → `TestApp` with typed `RpcClient` / `SocketClient` — in-process dispatch, no network.
|
|
208
|
+
- `abide/test/createScriptedSurface(...)` — records tool dispatches for engine tests.
|
|
209
|
+
- `abide/test/assertAgentFrameConformance(...)` — the frame contract every engine must satisfy (exactly one `done`, last; every `tool_use` answered by a same-id/name `tool_result`).
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Generated machine surfaces (runtime, from the user's app)
|
|
214
|
+
- `/openapi.json` — full OpenAPI for every exposed verb.
|
|
215
|
+
- `/__abide/mcp` — MCP endpoint (tools/sockets/prompts).
|
|
216
|
+
- `/__abide/health` — health payload. `/__abide/identity` — app identity.
|
|
217
|
+
- `/__abide/sockets` — ws multiplex (per-socket HTTP face at `/__abide/sockets/<name>`).
|
|
218
|
+
- `/__abide/cli` — CLI dispatch endpoint. `/__abide/hot` — dev hot-reload. `/__abide/inspector` — inspector stream (gated by `ABIDE_ENABLE_INSPECTOR=true`).
|
|
219
|
+
|
|
220
|
+
## Environment variables (consumer-facing)
|
|
221
|
+
|
|
222
|
+
| Var | Effect |
|
|
223
|
+
|---|---|
|
|
224
|
+
| `PORT` | TCP port the server binds |
|
|
225
|
+
| `APP_URL` | public URL; its pathname becomes the mount base (e.g. `…/v2` → `/v2`) |
|
|
226
|
+
| `ABIDE_APP_URL` / `ABIDE_APP_TOKEN` | remote server URL + bearer token for the CLI client / bundle |
|
|
227
|
+
| `ABIDE_DATA_DIR` | override the per-user data dir |
|
|
228
|
+
| `ABIDE_CLIENT_TIMEOUT` | client-side RPC timeout (ms); distinct from per-verb `timeout` |
|
|
229
|
+
| `ABIDE_IDLE_TIMEOUT` | Bun per-connection idle timeout (s) |
|
|
230
|
+
| `ABIDE_MAX_REQUEST_BODY_SIZE` | server-wide request body ceiling |
|
|
231
|
+
| `ABIDE_REACHABLE_TTL` / `ABIDE_REACHABLE_TIMEOUT` | `reachable()` poll cadence / per-HEAD bound (ms) |
|
|
232
|
+
| `ABIDE_LOG_FORMAT` | `json` for one JSON object per log line (default: tsv) |
|
|
233
|
+
| `ABIDE_ENABLE_INSPECTOR` | `true` to enable the inspector endpoint |
|
|
234
|
+
| `ABIDE_INSPECT` | enable Bun inspector on the build |
|
|
235
|
+
| `DEBUG` | enable diagnostic log channels (e.g. `abide:cache`, `abide:build`); browser uses the `abide-debug` localStorage key |
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
*Maintenance (abide repo only): this file mirrors `package.json` `exports`. After
|
|
240
|
+
adding/renaming an export, run `bun run scripts/readmeSurfaces.ts` from
|
|
241
|
+
`packages/abide/` (it lists every export by `@readme` slug and fails on any untagged
|
|
242
|
+
one) and reflect the change here.*
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# abide
|
|
2
2
|
|
|
3
|
+
## 0.30.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`311f3e7`](https://github.com/briancray/abide/commit/311f3e76732eb2b98d2739f2f7eff0fbbb667645) - cross-fade navigations via the View Transition API ([`d3ea6c0`](https://github.com/briancray/abide/commit/d3ea6c001c47748dd2be3ae47dfc3624ae7c65ee))
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- [`311f3e7`](https://github.com/briancray/abide/commit/311f3e76732eb2b98d2739f2f7eff0fbbb667645) - surface attach= binding and named-template snippets in .abide grammar ([`51a37ee`](https://github.com/briancray/abide/commit/51a37ee0b0cef708d0571de272ace3584d45a8c0))
|
|
12
|
+
|
|
13
|
+
- [`311f3e7`](https://github.com/briancray/abide/commit/311f3e76732eb2b98d2739f2f7eff0fbbb667645) - hoist component-local types above \_\_Props so prop annotations resolve ([`8d674c5`](https://github.com/briancray/abide/commit/8d674c543be53ae5775124f392ee44fd8731204f))
|
|
14
|
+
|
|
15
|
+
## 0.29.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- [`2d2c141`](https://github.com/briancray/abide/commit/2d2c141a59d3643f997ef4bb16773d29e4149c95) - add 'abide init-agent' command ([`b5e9158`](https://github.com/briancray/abide/commit/b5e915862a74febffb8df28c68f444a73bd371da))
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- [`2d2c141`](https://github.com/briancray/abide/commit/2d2c141a59d3643f997ef4bb16773d29e4149c95) - add AGENTS.md complete public-surface map for agents ([`b0539ec`](https://github.com/briancray/abide/commit/b0539ec69c880106dc1b0cb8573203e7113964ed))
|
|
24
|
+
|
|
3
25
|
## 0.28.0
|
|
4
26
|
|
|
5
27
|
### Minor Changes
|
package/bin/abide.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { buildCli } from '../src/buildCli.ts'
|
|
|
5
5
|
import { bundleApp } from '../src/bundleApp.ts'
|
|
6
6
|
import { checkAbide } from '../src/checkAbide.ts'
|
|
7
7
|
import { compile } from '../src/compile.ts'
|
|
8
|
+
import { initAgent } from '../src/initAgent.ts'
|
|
8
9
|
import { normalizeTarget } from '../src/lib/shared/normalizeTarget.ts'
|
|
9
10
|
import { scaffold } from '../src/scaffold.ts'
|
|
10
11
|
|
|
@@ -156,6 +157,13 @@ async function scaffoldCmd(): Promise<void> {
|
|
|
156
157
|
await scaffold({ cwd, name, install, dev })
|
|
157
158
|
}
|
|
158
159
|
|
|
160
|
+
// Writes/refreshes the abide agent-guide pointer in the project's root CLAUDE.md so
|
|
161
|
+
// Claude (which never reads node_modules) is told where abide's surface map lives.
|
|
162
|
+
// For projects that added abide as a dependency without scaffolding.
|
|
163
|
+
async function initAgentCmd(): Promise<void> {
|
|
164
|
+
await initAgent({ cwd })
|
|
165
|
+
}
|
|
166
|
+
|
|
159
167
|
// Prints the CLI synopsis to stderr and exits non-zero. Marked `never` because the process is gone.
|
|
160
168
|
function usage(): never {
|
|
161
169
|
console.error(
|
|
@@ -182,7 +190,9 @@ function usage(): never {
|
|
|
182
190
|
' abide bundle build a movable, self-contained app\n' +
|
|
183
191
|
' bundle for this platform (unsigned). Boots\n' +
|
|
184
192
|
' into a connect screen — start the embedded\n' +
|
|
185
|
-
' server or connect to a remote one'
|
|
193
|
+
' server or connect to a remote one\n' +
|
|
194
|
+
" abide init-agent write/refresh a CLAUDE.md pointer to abide's\n" +
|
|
195
|
+
' surface map (for non-scaffolded projects)',
|
|
186
196
|
)
|
|
187
197
|
process.exit(1)
|
|
188
198
|
}
|
|
@@ -207,6 +217,8 @@ if (command === 'scaffold') {
|
|
|
207
217
|
await checkCmd()
|
|
208
218
|
} else if (command === 'lsp') {
|
|
209
219
|
await lspCmd()
|
|
220
|
+
} else if (command === 'init-agent') {
|
|
221
|
+
await initAgentCmd()
|
|
210
222
|
} else {
|
|
211
223
|
usage()
|
|
212
224
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abide/abide",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "Isomorphic multimodal HTTP framework built for humans and machines in a single Bun runtime",
|
|
@@ -130,6 +130,7 @@
|
|
|
130
130
|
"bin",
|
|
131
131
|
"template",
|
|
132
132
|
"tsconfig.app.json",
|
|
133
|
+
"AGENTS.md",
|
|
133
134
|
"CHANGELOG.md"
|
|
134
135
|
],
|
|
135
136
|
"dependencies": {
|
package/src/initAgent.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* Marker pair fencing the abide block, so a re-run replaces it in place rather
|
|
2
|
+
than duplicating, and the rest of an existing CLAUDE.md stays untouched. */
|
|
3
|
+
const GUIDE_START = '<!-- abide:agent-guide -->'
|
|
4
|
+
const GUIDE_END = '<!-- /abide:agent-guide -->'
|
|
5
|
+
|
|
6
|
+
/* The pointer injected into the consumer's root CLAUDE.md. Claude Code auto-loads
|
|
7
|
+
root CLAUDE.md but never reads node_modules, so this is the only reliable hook
|
|
8
|
+
telling it where abide's full surface map lives. */
|
|
9
|
+
const GUIDE_BLOCK = `${GUIDE_START}
|
|
10
|
+
## Working with abide
|
|
11
|
+
|
|
12
|
+
This project uses **abide**. Before using any abide API, read the complete surface
|
|
13
|
+
map: \`node_modules/@abide/abide/AGENTS.md\` — every export (import path + signature),
|
|
14
|
+
the file-based conventions, the CLI, env vars, and the \`.abide\` component grammar.
|
|
15
|
+
Open the source under \`node_modules/@abide/abide/src/lib/\` for depth.
|
|
16
|
+
${GUIDE_END}`
|
|
17
|
+
|
|
18
|
+
/* Folds the guide block into existing CLAUDE.md content: replaces a fenced block
|
|
19
|
+
when both markers are present, otherwise appends it; titles a fresh file when
|
|
20
|
+
there is no content. Pure — the side effect (write) stays in initAgent. */
|
|
21
|
+
function mergeGuide(existing: string | undefined): string {
|
|
22
|
+
if (existing === undefined) {
|
|
23
|
+
return `# Project guide for Claude\n\n${GUIDE_BLOCK}\n`
|
|
24
|
+
}
|
|
25
|
+
const start = existing.indexOf(GUIDE_START)
|
|
26
|
+
const end = existing.indexOf(GUIDE_END)
|
|
27
|
+
if (start !== -1 && end > start) {
|
|
28
|
+
return existing.slice(0, start) + GUIDE_BLOCK + existing.slice(end + GUIDE_END.length)
|
|
29
|
+
}
|
|
30
|
+
return `${existing.replace(/\s*$/, '')}\n\n${GUIDE_BLOCK}\n`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
Writes (or refreshes) the abide agent-guide block in the project's root CLAUDE.md
|
|
35
|
+
so Claude is pointed at node_modules/@abide/abide/AGENTS.md. Idempotent: the marker
|
|
36
|
+
pair fences the block, so re-running updates it in place instead of duplicating, and
|
|
37
|
+
any surrounding CLAUDE.md content is preserved. For projects that added abide as a
|
|
38
|
+
dependency without scaffolding (the scaffold ships this pointer already).
|
|
39
|
+
*/
|
|
40
|
+
export async function initAgent({ cwd }: { cwd: string }): Promise<void> {
|
|
41
|
+
const path = `${cwd}/CLAUDE.md`
|
|
42
|
+
const file = Bun.file(path)
|
|
43
|
+
const existed = await file.exists()
|
|
44
|
+
await Bun.write(path, mergeGuide(existed ? await file.text() : undefined))
|
|
45
|
+
console.log(
|
|
46
|
+
existed
|
|
47
|
+
? `[abide] refreshed the abide agent-guide in ${path}`
|
|
48
|
+
: `[abide] wrote ${path} pointing Claude at node_modules/@abide/abide/AGENTS.md`,
|
|
49
|
+
)
|
|
50
|
+
}
|
|
@@ -46,11 +46,18 @@ export function compileShadow(source: string): CompiledShadow {
|
|
|
46
46
|
const scriptStart = leadingScript ? source.indexOf('>', leadingScript.index) + 1 : 0
|
|
47
47
|
const templateStart = leadingScript ? (leadingScript.index ?? 0) + leadingScript[0].length : 0
|
|
48
48
|
|
|
49
|
-
const { imports, scope, props } = analyzeScript(scriptBody, scriptStart)
|
|
49
|
+
const { imports, types, scope, props } = analyzeScript(scriptBody, scriptStart)
|
|
50
50
|
builder.raw(SHADOW_PREAMBLE)
|
|
51
51
|
for (const line of imports) {
|
|
52
52
|
builder.flush(line)
|
|
53
53
|
}
|
|
54
|
+
/* Component-local `type`/`interface` declarations are hoisted to module scope —
|
|
55
|
+
above `__Props` so prop annotations referencing them resolve, and still visible
|
|
56
|
+
inside the function body where the rest of the scope and template expressions use
|
|
57
|
+
them. (Emitting them as in-function scope lines would hide them from `__Props`.) */
|
|
58
|
+
for (const line of types) {
|
|
59
|
+
builder.flush(line)
|
|
60
|
+
}
|
|
54
61
|
builder.raw(`interface __Props {\n${props.join('\n')}\n}\n`)
|
|
55
62
|
/* async so `await` blocks are legal; never executed, so the return is void. */
|
|
56
63
|
builder.raw('export default async function (props: __Props): Promise<void> {\n')
|
|
@@ -117,17 +124,24 @@ function createBuilder(): Builder {
|
|
|
117
124
|
return builder
|
|
118
125
|
}
|
|
119
126
|
|
|
120
|
-
type ScriptAnalysis = {
|
|
127
|
+
type ScriptAnalysis = {
|
|
128
|
+
imports: ScopeLine[]
|
|
129
|
+
types: ScopeLine[]
|
|
130
|
+
scope: ScopeLine[]
|
|
131
|
+
props: string[]
|
|
132
|
+
}
|
|
121
133
|
|
|
122
134
|
/* Walks the leading `<script>` and produces the shadow's module imports, the
|
|
123
|
-
value-typed scope lines, and the Props
|
|
124
|
-
body's absolute offset in the source, so
|
|
135
|
+
module-scope type declarations, the value-typed scope lines, and the Props
|
|
136
|
+
interface fields. `scriptStart` is the body's absolute offset in the source, so
|
|
137
|
+
verbatim spans map back exactly. */
|
|
125
138
|
function analyzeScript(scriptBody: string, scriptStart: number): ScriptAnalysis {
|
|
126
139
|
const imports: ScopeLine[] = []
|
|
140
|
+
const types: ScopeLine[] = []
|
|
127
141
|
const scope: ScopeLine[] = []
|
|
128
142
|
const props: string[] = []
|
|
129
143
|
if (scriptBody.trim() === '') {
|
|
130
|
-
return { imports, scope, props }
|
|
144
|
+
return { imports, types, scope, props }
|
|
131
145
|
}
|
|
132
146
|
const file = ts.createSourceFile('script.ts', scriptBody, ts.ScriptTarget.Latest, true)
|
|
133
147
|
/* A verbatim span: original text + the segment mapping it back, relative to the
|
|
@@ -145,6 +159,11 @@ function analyzeScript(scriptBody: string, scriptStart: number): ScriptAnalysis
|
|
|
145
159
|
imports.push({ text: verbatim(statement), segments: [span(statement, 0)] })
|
|
146
160
|
continue
|
|
147
161
|
}
|
|
162
|
+
if (ts.isTypeAliasDeclaration(statement) || ts.isInterfaceDeclaration(statement)) {
|
|
163
|
+
/* Hoist to module scope (verbatim, mapped) so prop annotations resolve them. */
|
|
164
|
+
types.push({ text: verbatim(statement), segments: [span(statement, 0)] })
|
|
165
|
+
continue
|
|
166
|
+
}
|
|
148
167
|
const reactive = reactiveDeclarations(statement)
|
|
149
168
|
if (reactive === undefined) {
|
|
150
169
|
/* Plain statement (function, const, expression) — emit verbatim, mapped. */
|
|
@@ -155,7 +174,7 @@ function analyzeScript(scriptBody: string, scriptStart: number): ScriptAnalysis
|
|
|
155
174
|
scope.push(scopeLineFor(declaration, props, verbatim, span))
|
|
156
175
|
}
|
|
157
176
|
}
|
|
158
|
-
return { imports, scope, props }
|
|
177
|
+
return { imports, types, scope, props }
|
|
159
178
|
}
|
|
160
179
|
|
|
161
180
|
/* The `state`/`derived`/`prop` declarations in a variable statement, or undefined
|
package/src/lib/ui/router.ts
CHANGED
|
@@ -264,12 +264,29 @@ export function router(
|
|
|
264
264
|
const hydrating =
|
|
265
265
|
first && pageView?.hydratable === true && pageView.hydrate !== undefined
|
|
266
266
|
first = false
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
base
|
|
267
|
+
/* The DOM mutation a navigation makes: tear the divergent chain down,
|
|
268
|
+
clear its DOM (a fresh mount; hydration adopts in place), rebuild. */
|
|
269
|
+
const swap = (): void => {
|
|
270
|
+
const base = disposeFrom(divergence)
|
|
271
|
+
if (!hydrating) {
|
|
272
|
+
base.textContent = ''
|
|
273
|
+
}
|
|
274
|
+
buildFrom(base, divergence, chainKeys, layoutViews, pageView, params, hydrating)
|
|
275
|
+
}
|
|
276
|
+
/* Wrap the swap in a View Transition where the browser supports it, so
|
|
277
|
+
the page change cross-fades (and shared `view-transition-name` elements
|
|
278
|
+
morph) — the synchronous swap is exactly the mutation the API snapshots
|
|
279
|
+
around. Skipped while hydrating: the first paint adopts SSR DOM in place,
|
|
280
|
+
not animate. CSS owns opting out (e.g. prefers-reduced-motion). */
|
|
281
|
+
if (
|
|
282
|
+
!hydrating &&
|
|
283
|
+
typeof document !== 'undefined' &&
|
|
284
|
+
'startViewTransition' in document
|
|
285
|
+
) {
|
|
286
|
+
document.startViewTransition(swap)
|
|
287
|
+
} else {
|
|
288
|
+
swap()
|
|
271
289
|
}
|
|
272
|
-
buildFrom(base, divergence, chainKeys, layoutViews, pageView, params, hydrating)
|
|
273
290
|
})
|
|
274
291
|
})
|
|
275
292
|
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Project guide for Claude
|
|
2
|
+
|
|
3
|
+
This app is built with **abide** — a type-safe isomorphic framework on Bun.
|
|
4
|
+
|
|
5
|
+
**Before working with any abide API, read the complete surface map:**
|
|
6
|
+
`node_modules/@abide/abide/AGENTS.md` — every export (with import path + signature),
|
|
7
|
+
the file-based conventions, the CLI, env vars, and the `.abide` component grammar.
|
|
8
|
+
Open the source under `node_modules/@abide/abide/src/lib/` for depth; the README is
|
|
9
|
+
at `node_modules/@abide/abide/README.md`.
|
|
10
|
+
|
|
11
|
+
> Tip: to keep that map permanently in context instead of reading it on demand,
|
|
12
|
+
> replace the line above with an import: `@node_modules/@abide/abide/AGENTS.md`
|
|
13
|
+
|
|
14
|
+
## Conventions (see AGENTS.md for the full list)
|
|
15
|
+
|
|
16
|
+
- One export per file, named after the file. No barrels — import each name by its
|
|
17
|
+
own path (`@abide/abide/server/GET`, `@abide/abide/shared/cache`, …).
|
|
18
|
+
- RPC verbs live in `src/server/rpc/<name>.ts`; sockets in `src/server/sockets/`;
|
|
19
|
+
pages are `**/page.abide`, layouts `**/layout.abide`.
|
|
20
|
+
- Generated types land in `src/.abide/` — do not hand-edit them; run `abide dev`/
|
|
21
|
+
`abide check` to regenerate.
|
|
22
|
+
- Prefer Bun and web-standard APIs over Node APIs.
|