Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 26 additions & 11 deletions AGENTS.md

Large diffs are not rendered by default.

71 changes: 71 additions & 0 deletions docs/content/1.guide/1.terms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
title: 'Terms'
description: 'The canonical vocabulary of these docs: one name per concept, the API or package that anchors it, and how the pieces talk to each other.'
---

Every concept in these docs has exactly one name. This page fixes that vocabulary — when a term below appears anywhere in the documentation, it carries the meaning defined here.

## Core

| Term | Meaning | Anchor |
|------|---------|--------|
| **Devframe** | The product: a framework-neutral foundation for building a devtool once and running it everywhere. | — |
| `devframe` | The npm package the foundation ships as. | `devframe` |
| **a devframe** | One tool: a definition plus its SPA, mountable anywhere. | `defineDevframe()`, `DevframeDefinition` |
| **built-in devframe** | A ready-to-run devframe shipped from this repo (data inspector, inspect, OG, a11y, git, terminals, code-server, assets). The `plugin-` npm prefix only sets these packages apart from core packages — Devframe has no plugin concept. | `@devframes/plugin-*` |
| **adapter** | A deployment entry point under `devframe/adapters/*`: cli (cac), dev, build, vite, embedded, mcp. | `devframe/adapters/*` |
| **framework kit** | Framework conventions over the standard handler, each split into a `/single` and a `/hub` scope. | `@devframes/vite`, `@devframes/nuxt`, `@devframes/next` |
| **opt-in package** | A capability shipped as its own package and added when needed. | `@devframes/json-render` |
| **hub** | The composition layer that puts many devframes behind one handler; *a hub* is one `initHub()` instance. | `@devframes/hub`, `initHub()` |
| **viewer** | A hub UI implementation: the node-side `ui` slot plus the browser-side context contract. `@devframes/hub-ui` is the reference viewer. | `initHub({ ui })` |
Comment thread
antfu marked this conversation as resolved.
Outdated

## Node side

A devframe has two halves: the **node side** registers RPC functions and owns state; the **browser side** renders and calls them.

| Term | Meaning | Anchor |
|------|---------|--------|
| **node side** | The half of a devframe running in the Node process. | `setup(ctx)` |
| **host framework** | The environment a devframe or hub mounts into: a Vite dev server, a Next.js app, a Hono server. Named forms — *the Vite host*, *a Next.js host* — refer to a specific one. | `DevframeHost` |
| **dev server** | The standalone HTTP server the dev adapter starts. | `createDevServer()` |
| **side-car server** | The separate RPC/WebSocket process used when a host framework's handlers never see upgrade requests. | — |
| **hosted / standalone** | The two mount contexts: hosted adapters (vite, embedded) default the base path to `/__<id>/`; standalone adapters (cli, build) default to `/`. | `resolveBasePath()` |
Comment thread
antfu marked this conversation as resolved.
Outdated
| **workspace scope** | Committable per-repository storage. | `DevframeStorageScope` |
| **project scope** | Per-checkout storage, gitignored. | `DevframeStorageScope` |
| **global scope** | Per-user storage. | `DevframeStorageScope` |

## Browser side

| Term | Meaning | Anchor |
|------|---------|--------|
| **browser side** | The half of a devframe running in a page. | `devframe/client` |
| **user app** | The application being developed and inspected. | — |
| **host page** | The browser document where the client runtime boots — in dev, usually the user app's own page. | — |
| **client runtime** | The headless runtime booted once per host page: it connects RPC, assembles the client context, and imports client scripts. | `createDevframeClientHost()` |
| **client context** | The shared object client scripts receive: panel, docks, commands, when-clauses. | `DevframeClientContext` |
| **client script** | A dock entry's script, imported into the host page by the client runtime. | `clientScript` |
| **page script** | A devframe's script running in the user app's page — loaded as a client script or standalone. The a11y page script runs axe-core. | — |
| **RPC client** | The typed connection a browser surface gets. | `connectDevframe()` |
| **SPA** | A devframe's built web interface; `clientAssets` says where it lives. | `clientAssets` |
| **panel** | A devframe's SPA as a rendered surface — in a dock panel or standalone. | — |
| **surface** | Any rendered browser view: a panel, a dock iframe, a standalone SPA. | — |
| **coding agent** | An agent consuming a devframe over MCP — the only agent in these docs. | `createMcpServer()` |

## Hub

| Term | Meaning | Anchor |
|------|---------|--------|
| **mounted devframe** | A devframe served inside a hub under `<base><id>/`. | `initHub({ devframes })` |
| **dock entry** | A registry item: iframe, launcher, custom-render, group, or json-render. | `ctx.docks` |
| **dock rail** | The bar listing every mounted tool. | — |
| **dock panel** | The open drawer rendering the active dock entry. | — |

## Communication paths

Three distinct paths connect the pieces; each has its own name.

| Path | Between | Transport |
|------|---------|-----------|
| **RPC** | browser side ↔ node side | WebSocket or static snapshot, via `connectDevframe()` |
| **client context** | client scripts ↔ client runtime | a shared object inside the host page |
| **in-page channel** | page script ↔ panel | same-origin, entirely in-browser (e.g. a `BroadcastChannel`) |
Comment thread
antfu marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: 'ctx.diagnostics is a thin layer over nostics for author-defined co

| Surface | Purpose | Example |
|---------|---------|---------|
| `ctx.diagnostics` | Coded errors and warnings emitted from node-side plugin code | `MYP0001: Plugin foo not configured` |
| `ctx.diagnostics` | Coded errors and warnings emitted from node-side code | `MYP0001: Plugin foo not configured` |
| [`ctx.messages`](https://devtools.vite.dev/kit/messages) | Free-form, user-facing notifications shown in the Messages panel | `'Audit complete — 3 issues found'` |

## Shape
Expand All @@ -20,12 +20,12 @@ interface DevframeDiagnosticsHost {
/** Register additional diagnostic definitions. */
register: (definitions: Record<string, unknown>) => void

/** Build a typed diagnostics object with the host's ANSI reporter pre-wired. */
/** Build a typed diagnostics object with devframe's ANSI reporter pre-wired. */
defineDiagnostics: typeof defineDiagnostics
}
```

The host ships pre-seeded with devframe's `DF*` codes plus the host package's (`DTK*`, etc.); call `register()` to add your own.
The diagnostics host ships pre-seeded with devframe's `DF*` codes plus the host framework's own (`DTK*`, etc.); call `register()` to add your own.

## Register your own codes

Expand All @@ -50,7 +50,7 @@ export function MyPlugin(): PluginWithDevTools {

ctx.diagnostics.register(myDiagnostics)

// Emit through the host's shared reporter:
// Emit through the host framework's shared reporter:
myDiagnostics.MYP0002()
},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
title: 'When Clauses'
description: 'When clauses gate visibility and executability of docks, commands, and UI surfaces via VS Code''s when-clause contexts. The evaluator whenexpr re-exports at devframe/utils/when.'
description: 'When clauses gate visibility and executability of docks, commands, and custom UI via VS Code''s when-clause contexts. The evaluator whenexpr re-exports at devframe/utils/when.'
---

When clauses gate visibility and executability of docks, commands, and UI surfaces via [VS Code's when-clause contexts](https://code.visualstudio.com/api/references/when-clause-contexts). The evaluator [`whenexpr`](https://github.com/antfu/whenexpr) re-exports at `devframe/utils/when`.
When clauses gate visibility and executability of docks, commands, and custom UI via [VS Code's when-clause contexts](https://code.visualstudio.com/api/references/when-clause-contexts). The evaluator [`whenexpr`](https://github.com/antfu/whenexpr) re-exports at `devframe/utils/when`.

## Usage

Expand All @@ -22,7 +22,7 @@ ctx.commands.register({

### On dock entries

Gates dock-bar visibility.
Gates dock-rail visibility.

```ts
ctx.docks.register({
Expand All @@ -37,7 +37,7 @@ ctx.docks.register({

### Render-only visibility on dock entries

A dock entry also takes `visibility`, a second expression that hides only its dock-bar button while keeping the entry registered and reachable (e.g. a `subTabs` anchor).
A dock entry also takes `visibility`, a second expression that hides only its dock-rail button while keeping the entry registered and reachable (e.g. a `subTabs` anchor).

```ts
ctx.docks.register({
Expand Down Expand Up @@ -89,21 +89,21 @@ when: 'false' // never visible
when: 'clientType == embedded' // only embedded
when: 'dockOpen && !paletteOpen' // dock open and palette closed
when: '(clientType == embedded && dockOpen) || clientType == standalone'
when: 'my-devtool.ready' // custom plugin context
when: 'my-devtool.ready' // custom devframe context
```

## Built-in context variables

| Variable | Type | Description |
|----------|------|-------------|
| `clientType` | `'embedded' \| 'standalone'` | `embedded` in the host overlay, `standalone` in a separate window. |
| `clientType` | `'embedded' \| 'standalone'` | `embedded` in the host page's overlay, `standalone` in a separate window. |
| `dockOpen` | `boolean` | Dock panel open. |
| `paletteOpen` | `boolean` | Command palette open. |
| `dockSelectedId` | `string` | Selected dock entry ID; `''` if none. |

## Namespaced context keys

Plugins add keys with `.` or `:`:
Devframes add keys with `.` or `:`:

```ts
context['my-devtool.ready'] = true
Expand Down Expand Up @@ -144,9 +144,9 @@ defineCommand({
})
```

### Key validation with plugin contexts
### Key validation with devframe contexts

The default `WhenContext` leaves plugin keys open-ended (`[key: string]: unknown`). To validate names, declare a narrower context and wrapper:
The default `WhenContext` leaves devframe keys open-ended (`[key: string]: unknown`). To validate names, declare a narrower context and wrapper:

```ts
import type { WhenContext, WhenExpression } from 'devframe/utils/when'
Expand Down Expand Up @@ -214,4 +214,4 @@ Returns one (possibly namespaced) key's value.

### `WhenExpression<Ctx, S>`

The branded `whenexpr` expression type for typed `define*` helpers ([above](#key-validation-with-plugin-contexts)).
The branded `whenexpr` expression type for typed `define*` helpers ([above](#key-validation-with-devframe-contexts)).
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ defineDevframe({

Call `connectDevframe()` in a Client Component — see [Client](/guide/client) and [`examples/next-runtime-snapshot`](https://github.com/devframes/devframe/tree/main/examples/next-runtime-snapshot).

## Connecting from the client
## Connecting from the browser side

With the Nuxt helper, use `$rpc`:

Expand Down Expand Up @@ -207,7 +207,7 @@ It's the no-args fallback for any deployed `rpc.call('my-tool:get-payload', …)

## On-disk caching

Persistence is the app's job ([`unstorage`](https://unstorage.unjs.io/) recommended); keep cache paths under `node_modules/.cache/<your-devtool-id>/` to rotate with `pnpm install`.
Persistence is your tool's job ([`unstorage`](https://unstorage.unjs.io/) recommended); keep cache paths under `node_modules/.cache/<your-devtool-id>/` to rotate with `pnpm install`.

```ts
import { resolve } from 'pathe'
Expand Down Expand Up @@ -238,7 +238,7 @@ defineDevframe({

## Live-reload on config changes

Filesystem watching is the app's job — wire chokidar, signal the client via shared state.
Filesystem watching is your tool's job — wire chokidar, signal the browser side via shared state.

```ts [src/cli.ts]
defineDevframe({
Expand Down Expand Up @@ -267,7 +267,7 @@ defineDevframe({
})
```

On the client:
On the browser side:

```ts
const my = (await connectDevframe()).scope('my-tool')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
title: 'Client'
description: 'The browser client connects any surface — dock iframe, remote page, standalone SPA — to the Devframe server with type-safe RPC, shared state, and a trust handshake.'
description: 'The RPC client connects any surface — dock iframe, remote page, standalone SPA — to a devframe''s node side with type-safe RPC, shared state, and a trust handshake.'
---

The browser client connects any surface — dock iframe, remote page, standalone SPA — to the Devframe server with type-safe RPC, shared state, and a trust handshake.
The RPC client connects any surface — dock iframe, remote page, standalone SPA — to a devframe's node side with type-safe RPC, shared state, and a trust handshake.

## Connecting

Expand Down Expand Up @@ -43,9 +43,9 @@ import { connectDevframe } from 'devframe/client'
const rpc = await connectDevframe({ connection })
```

The client retains it as `rpc.connection`; cross-realm viewers read it via `getDevframeConnection()` or `DEVFRAME_CONNECTION_KEY` (`devframe/constants`).
The RPC client retains it as `rpc.connection`; cross-realm viewers read it via `getDevframeConnection()` or `DEVFRAME_CONNECTION_KEY` (`devframe/constants`).

An external viewer registers its origin before the WebSocket opens (needs `viewerOriginToken` in the host's connection metadata; see [External viewer origins](/guide/security#external-viewer-origins)):
An external viewer registers its origin before the WebSocket opens (needs `viewerOriginToken` in the host framework's connection metadata; see [External viewer origins](/guide/security#external-viewer-origins)):

```ts
import { registerDevframeViewerOrigin } from 'devframe/client'
Expand Down Expand Up @@ -77,12 +77,12 @@ Per the `__devframe/__connection.json` backend:

## Trust & auth (WebSocket mode)

`ensureTrusted()` resolves once the server trusts the client's stored token:
`ensureTrusted()` resolves once the node side trusts the RPC client's stored token:

```ts
const rpc = await connectDevframe()

// Blocks until the server trusts this client (default timeout 60s)
// Blocks until the node side trusts this RPC client (default timeout 60s)
const trusted = await rpc.ensureTrusted()

if (!trusted) {
Expand All @@ -100,7 +100,7 @@ The dev server prints a single-use 6-digit code (expires in five minutes, rotate
const ok = await rpc.requestTrustWithCode('047204')
```

A host can embed the code in a link (`buildOtpAuthUrl(origin)`); `connectDevframe` reads the `devframe_otp` fragment, exchanges it, and strips the URL. Rename it with `otpParam`, or set `otpParam: false` to drive it yourself via `authenticateWithUrlOtp(rpc)` / `consumeOtpFromUrl()`.
A host framework can embed the code in a link (`buildOtpAuthUrl(origin)`); `connectDevframe` reads the `devframe_otp` fragment, exchanges it, and strips the URL. Rename it with `otpParam`, or set `otpParam: false` to drive it yourself via `authenticateWithUrlOtp(rpc)` / `consumeOtpFromUrl()`.

### Re-using an existing token

Expand All @@ -112,7 +112,7 @@ const ok = await rpc.requestTrustWithToken('a1b2c3…')

### Broadcast-channel sync

`connectDevframe` listens on a shared `BroadcastChannel` (`devframe-auth`) for `auth-update` messages; one tab authenticating trusts every open client.
`connectDevframe` listens on a shared `BroadcastChannel` (`devframe-auth`) for `auth-update` messages; one tab authenticating trusts every open RPC client.


## Calling functions
Expand All @@ -132,11 +132,11 @@ const maybe = await my.rpc.callOptional('get-modules', { limit: 10 })
my.rpc.callEvent('notify', { message: 'hello' })
```

Types flow from the server's `defineRpcFunction` definitions.
Types flow from the node side's `defineRpcFunction` definitions.

## Registering client functions

Register functions the server calls via `rpc.broadcast`:
Register functions the node side calls via `rpc.broadcast`:

```ts
import { defineRpcFunction } from 'devframe'
Expand Down Expand Up @@ -172,18 +172,18 @@ See [Shared State](/guide/shared-state).

## Services

`rpc.services` mirrors the server's wire-service advertisements:
`rpc.services` mirrors the node side's wire-service advertisements:

```ts
if (rpc.services.has('@devframes/service-open'))
await rpc.services.get('@devframes/service-open')!.rpc.call('open-in-editor', { path })
```

See [Cross-Plugin Services](/guide/services#wire-services).
See [Cross-Devframe Services](/guide/services#wire-services).

## Settings

A scoped client exposes a persisted `settings` store, per-user (`global`) or per-workspace (`project`):
A scoped client exposes a persisted `settings` store, per-user (`global`) or per-checkout (`project`):

```ts
await my.settings.project.set('theme', 'dark')
Expand Down Expand Up @@ -213,7 +213,7 @@ Devframe writes a JSON descriptor at `<base>/__connection.json`. The socket shar
}
```

The client resolves it against its origin (`http`→`ws` / `https`→`wss`). The field also accepts a `number` (port on the page's host), a full `ws://`/`wss://` URL, or `{ port }` / `{ host }` for a cross-origin side-car.
The RPC client resolves it against its origin (`http`→`ws` / `https`→`wss`). The field also accepts a `number` (port on the page's host), a full `ws://`/`wss://` URL, or `{ port }` / `{ host }` for a cross-origin side-car server.

For static mode:

Expand All @@ -231,7 +231,7 @@ await connectDevframe({

## Remote docks

Supporting hosts (Vite DevTools; see [its remote-client docs](https://devtools.vite.dev/kit/remote-client)) inject a connection descriptor into the iframe URL that `connectDevframe` auto-detects:
Supporting host frameworks (Vite DevTools; see [its remote-client docs](https://devtools.vite.dev/kit/remote-client)) inject a connection descriptor into the iframe URL that `connectDevframe` auto-detects:

```ts
import { connectDevframe } from 'devframe/client'
Expand Down Expand Up @@ -261,7 +261,7 @@ Emitted over `rpc.events`:
| `rpc:is-trusted:updated` | Trust granted, denied, or revoked. Carries the new `isTrusted` boolean. |
| `connection:status` | The [connection status](#handling-connection-and-auth-errors) changes. Carries `(status, previous)`. |
| `connection:error` | A connection-level failure — socket error or trust refused. Carries the `Error`. |
| `rpc:error` | An `rpc.call` rejects, from the server or a down connection. Carries `(error, method)`. |
| `rpc:error` | An `rpc.call` rejects, from the node side or a down connection. Carries `(error, method)`. |

```ts
rpc.events.on('rpc:is-trusted:updated', (isTrusted) => {
Expand Down Expand Up @@ -295,10 +295,10 @@ A `static` backend has no live socket, so `rpc.status` stays `connected`.
When the socket closes or trust is refused, in-flight and new `rpc.call` promises reject with a `DevframeConnectionError`, its `kind`:

- `'connection'` — the transport is down (`disconnected` / `error`).
- `'auth'` — the client is `unauthorized`.
- `'auth'` — the RPC client is `unauthorized`.
- `'timeout'` — the call outlived `callTimeout`.

Set `callTimeout` to cap an unresponsive server:
Set `callTimeout` to cap an unresponsive node side:

```ts
const rpc = await connectDevframe({ callTimeout: 10_000 })
Expand Down Expand Up @@ -343,11 +343,11 @@ async function loadModules() {

### Recovering

The client doesn't reconnect on its own — reload or re-run your connect routine:
The RPC client doesn't reconnect on its own — reload or re-run your connect routine:

```ts
async function reconnect() {
rpc = await connectDevframe() // a new client; re-subscribe your listeners
rpc = await connectDevframe() // a new RPC client; re-subscribe your listeners
render()
}
```
Expand Down
Loading
Loading