diff --git a/AGENTS.md b/AGENTS.md
index ab7304af..dc98f9b3 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -2,9 +2,24 @@
## Positioning
-**`devframe`** is the framework-neutral container for one devtool integration, portable across viewers. Build a single tool (its RPC, its SPA, its diagnostics, its CLI/build/embedded outputs) without caring how it'll be displayed. A devframe app runs standalone (CLI, static deploy, embedded SPA) just as well as it mounts inside a hub.
+**`devframe`** is the framework-neutral container for one devtool integration, portable across hub UI providers. Build a single tool (its RPC, its SPA, its diagnostics, its CLI/build/embedded outputs) without caring how it'll be displayed. A devframe runs standalone (CLI, static deploy, embedded SPA) just as well as it mounts inside a hub.
-**`@devframes/hub`** is the framework-neutral hub layer that sits on top of devframe and provides the multi-integration orchestration (docks, terminals, messages, commands). It does not ship UI - implementers (e.g. `@vitejs/devtools-kit`) provide their own UI on top of the hub's RPC + shared-state protocol. It does ship a **headless client runtime** (`createDevframeClientHost()` from `@devframes/hub/client`): booted in the host page, it assembles the shared `DevframeClientContext` (panel, docks, commands, when) and imports each dock entry's client script (`action` / `custom-render` / iframe `clientScript`) into that page - how a plugin like the a11y inspector runs code inside the page being inspected. See `examples/hub-vite/` for a working ~120-line Vite host demonstrating the protocol end to end.
+**`@devframes/hub`** is the framework-neutral hub layer that sits on top of devframe and provides the multi-devframe orchestration (docks, terminals, messages, commands). It does not ship UI - hub UI providers (e.g. `@vitejs/devtools-kit`) provide their own UI on top of the hub's RPC + shared-state protocol. It does ship a **headless client runtime** (`createDevframeClientRuntime()` from `@devframes/hub/client`): booted in the host page, it assembles the shared `DevframeClientContext` (panel, docks, commands, when) and imports each dock entry's client script (`action` / `custom-render` / iframe `clientScript`) into that page - how a built-in devframe like the a11y inspector runs its page script inside the user app's page. See `examples/hub-vite/` for a working ~120-line Vite host demonstrating the protocol end to end.
+
+## Terminology
+
+The docs' canonical vocabulary lives in [`docs/content/8.references/1.terms.md`](docs/content/8.references/1.terms.md) - one name per concept. Every docs, README, and comment edit follows it:
+
+- **Never use bare `client`, `host`, `server`, `agent`, `plugin`, `embedded`, or `standalone` in prose.** Use a fixed compound from the terms page or a code-formatted API/package name. One exception: the directional `client → server` arrows in the RPC/events reference tables.
+- **The tool is "a devframe"** - never "integration", "frame", or "app". The ready-to-run `@devframes/plugin-*` packages are **built-in devframes**: Devframe has no plugin concept; the `plugin-` npm prefix only sets those packages apart from core packages. "Vite plugin" stays for the bundler mechanism, and RPC ids keep the literal `devframes:plugin::` namespace because it mirrors the package names on the wire.
+- **host framework** is the environment a devframe or hub mounts into (a Vite dev server, a Next.js app, a Hono server); named forms like "the Vite host" are fine. **host page** is the browser document where the client runtime boots; **user app** is the application being developed and inspected.
+- A devframe's two halves are the **node side** and the **browser side**.
+- Browser-side terms: **client runtime** (`createDevframeClientRuntime()`), **client context**, **client script**, **page script** (a devframe's script in the user app's page - never "agent"; **coding agent** is the only agent), **RPC client** (`connectDevframe()`), **SPA**, **panel** (a devframe's SPA as a rendered surface), **surface** (any rendered browser view - say "API", not "API surface").
+- Hub terms: **hub UI provider** (a hub UI implementation - never "shell" or bare "viewer"; "external viewer" stays for cross-origin surfaces in the security docs), **dock entry** / **dock rail** / **dock panel**, **mounted devframe** (never "frame").
+- The three communication paths: **RPC** (browser side ↔ node side), the **client context** (client scripts ↔ client runtime), and the **in-page channel** (page script ↔ panel, same-origin in-browser).
+- Storage scopes: **workspace scope** (committable, per-repo), **project scope** (per-checkout), **global scope** (per-user) - never describe the project scope as "per-workspace".
+- **framework kits** are `@devframes/vite` / `@devframes/nuxt` / `@devframes/next`; refer to external products by their full names (`@vitejs/devtools-kit`, `@nuxt/devtools`).
+- Qualify the rest: embedded/standalone only as attached adjectives (embedded adapter, standalone SPA), sessions (terminal / MCP / trust session), entries (dock entry / entry point / browser entry), bridges (RPC bridge).
## Stack & Structure
@@ -36,29 +51,29 @@ The `pnpm test` script intentionally runs `build` first so `tsnapi` snapshots co
Ahead-of-time build artifacts that live under `src/` - the shadow-root stylesheets in `packages/hub-ui/src/client/.generated/` and `packages/json-render-ui/src/.generated/` - are **generated, not committed** (`.generated` is gitignored). Each owning package builds its own with `pnpm run build:css`, and three things guarantee the file is on disk before anything imports it: the root `postinstall` runs `turbo run build:css`, the Turbo `typecheck` task depends on both `build:css` tasks, and each package's `build` script chains `build:css` first. A new generated-under-`src` artifact follows the same shape - its own build script, declared `outputs` in `turbo.json`, and a `typecheck` dependency - rather than being checked in, since a minified single-line blob conflicts on every concurrent edit.
-**`starter/`** is the top-level, self-contained template for creating a new devframe integration (Vanilla TS, Vite client, playgrounds, tests). It uses real versions in its `package.json` (no catalogs, no `workspace:*`) so it's copy-paste ready for users. Pnpm links its `devframe`/`@devframes/*` dependencies to the local workspace copies during development. When `bumpp -r` bumps the repo versions, `bumpp.config.ts` runs `scripts/sync-starter-version.ts` to update the starter's dependencies to match.
+**`starter/`** is the top-level, self-contained template for creating a new devframe (Vanilla TS, Vite SPA, playgrounds, tests). It uses real versions in its `package.json` (no catalogs, no `workspace:*`) so it's copy-paste ready for users. Pnpm links its `devframe`/`@devframes/*` dependencies to the local workspace copies during development. When `bumpp -r` bumps the repo versions, `bumpp.config.ts` runs `scripts/sync-starter-version.ts` to update the starter's dependencies to match.
-`pnpm knip` finds unused files, dependencies, and exports across every workspace (config in `knip.jsonc`). It runs against source directly - no prior build needed. Most workspaces need no configuration; `knip.jsonc` only carries per-workspace overrides for cases knip's defaults can't infer on their own: a package's non-`index.ts` `exports` subpaths (knip's package.json→`dist`→`src` source mapping needs a workspace `tsconfig.json` `outDir`, which conflicts with this repo's cross-workspace `src/*.ts` imports, so multi-entry packages list their `exports`-mapped entry files explicitly instead - keep that list in sync with each `tsdown.config.ts`), config files knip's plugins don't discover in a nested location (a Next.js app rooted below the workspace root, `storybook-solidjs-vite` not matching the Storybook plugin trigger), and dependencies referenced dynamically outside its static import graph (icon collections consumed by UnoCSS at build time, plugin packages loaded via a runtime `import()` string). Prefer fixing the underlying gap or a scoped `ignoreDependencies`/`entry` override over a blanket `ignore`.
+`pnpm knip` finds unused files, dependencies, and exports across every workspace (config in `knip.jsonc`). It runs against source directly - no prior build needed. Most workspaces need no configuration; `knip.jsonc` only carries per-workspace overrides for cases knip's defaults can't infer on their own: a package's non-`index.ts` `exports` subpaths (knip's package.json→`dist`→`src` source mapping needs a workspace `tsconfig.json` `outDir`, which conflicts with this repo's cross-workspace `src/*.ts` imports, so multi-entry packages list their `exports`-mapped entry files explicitly instead - keep that list in sync with each `tsdown.config.ts`), config files knip's plugins don't discover in a nested location (a Next.js app rooted below the workspace root, `storybook-solidjs-vite` not matching the Storybook plugin trigger), and dependencies referenced dynamically outside its static import graph (icon collections consumed by UnoCSS at build time, built-in devframe packages loaded via a runtime `import()` string). Prefer fixing the underlying gap or a scoped `ignoreDependencies`/`entry` override over a blanket `ignore`.
## Conventions
-- RPC functions must use `defineRpcFunction`; always namespace IDs `devframes:plugin::` (matching the plugin's `@devframes/plugin-` package name).
-- **No magic event names — use the centralized event maps.** Every event, broadcast, shared-state key, and channel name lives in one of two source-of-truth maps: `DEVFRAME_EVENTS` (`packages/devframe/src/events.ts`, re-exported from `devframe/constants`) for the core runtime, and `HUB_EVENTS` (`packages/hub/src/events.ts`, re-exported from `@devframes/hub/constants`) for the hub. Reference `DEVFRAME_EVENTS.*` / `HUB_EVENTS.*` at call sites (`.events.emit`/`.on`, `rpc.broadcast({ method })`, `sharedState.get(key)`, `defineHubRpcFunction({ name })`, `rpc.call`) instead of re-typing a string literal. The two maps and the [`docs/content/1.guide/20.events.md`](docs/content/1.guide/20.events.md) Events Reference are kept in lockstep: adding, renaming, or removing a name means editing the map **and** that page in the same change — every name in the maps appears in the tables, and vice versa. The only literals left are unavoidable type-position keys (the `EventEmitter<…>` maps in `types/*` and the `DevframeRpcClientFunctions`/`DevframeRpcServerFunctions` augmentations), which mirror the maps; a package that deliberately avoids a hub dependency (e.g. `@devframes/plugin-terminals`, which models the hub bridge structurally) keeps a local literal rather than importing `HUB_EVENTS`.
+- RPC functions must use `defineRpcFunction`; always namespace IDs `devframes:plugin::` for built-in devframes (the literal `plugin:` token mirrors the `@devframes/plugin-` package name on the wire - it is npm namespacing, not a concept).
+- **No magic event names — use the centralized event maps.** Every event, broadcast, shared-state key, and channel name lives in one of two source-of-truth maps: `DEVFRAME_EVENTS` (`packages/devframe/src/events.ts`, re-exported from `devframe/constants`) for the core runtime, and `HUB_EVENTS` (`packages/hub/src/events.ts`, re-exported from `@devframes/hub/constants`) for the hub. Reference `DEVFRAME_EVENTS.*` / `HUB_EVENTS.*` at call sites (`.events.emit`/`.on`, `rpc.broadcast({ method })`, `sharedState.get(key)`, `defineHubRpcFunction({ name })`, `rpc.call`) instead of re-typing a string literal. The two maps and the [`docs/content/8.references/3.events.md`](docs/content/8.references/3.events.md) Events Reference are kept in lockstep: adding, renaming, or removing a name means editing the map **and** that page in the same change — every name in the maps appears in the tables, and vice versa. The only literals left are unavoidable type-position keys (the `EventEmitter<…>` maps in `types/*` and the `DevframeRpcClientFunctions`/`DevframeRpcServerFunctions` augmentations), which mirror the maps; a package that deliberately avoids a hub dependency (e.g. `@devframes/plugin-terminals`, which models the hub bridge structurally) keeps a local literal rather than importing `HUB_EVENTS`.
- **Stay validator-neutral.** `devframe` and every `@devframes/*` package must not introduce a preferred schema validator dependency - no `valibot`, `zod`, `arktype`, etc. in their runtime `dependencies`. `args`/`returns`/flag schemas are typed against [Standard Schema](https://standardschema.dev/) (`@standard-schema/spec`, types-only); first-party code that needs to author a schema uses the built-in zero-dep `devframe/utils/simple-schema` builder (deliberately minimal - not a general validator). JSON-schema conversion uses each schema's own Standard JSON Schema converter (`~standard.jsonSchema`, implemented by e.g. zod 4) when present and degrades to a permissive object otherwise - no converter library and no vendor dependency is required. Docs, by contrast, should point *users* at a real validator for their own integrations - recommend **valibot** (lightest) or **zod** (worth reusing if they already pull it via the JSON-render or MCP integrations).
- Shared state via `devframe/utils/shared-state`; keep values serializable.
- Utility imports use the package-path form `devframe/utils/*`, never relative `../utils/*`.
- Dependencies go through the pnpm catalogs in `pnpm-workspace.yaml` (`cli`, `inlined`, `testing`, `types`) - add to a catalog and reference as `catalog:`, don't pin versions in `package.json`.
-- **A plugin's default export is its `createDevframe` factory, never a pre-built instance.** Don't write `const xDevframe = createXDevframe(); export default xDevframe` (or the inline `export default createXDevframe()` equivalent) - that eagerly constructs a `DevframeDefinition` the moment the module loads, at import time, whether or not any consumer wants that exact zero-config shape; a host that needs its own options (an id override, a data directory, …) ends up paying for a second, discarded instance alongside the one it actually uses. Alias the factory itself as the default export instead - `export default createXDevframe` - so importing the module costs nothing beyond defining the function, and every consumer calls it (with or without options) to get their own instance: `import createA11yDevframe from '@devframes/plugin-a11y'` then `createA11yDevframe(options)`.
+- **A built-in devframe's default export is its `createDevframe` factory, never a pre-built instance.** Don't write `const xDevframe = createXDevframe(); export default xDevframe` (or the inline `export default createXDevframe()` equivalent) - that eagerly constructs a `DevframeDefinition` the moment the module loads, at import time, whether or not any consumer wants that exact zero-config shape; a consumer that needs its own options (an id override, a data directory, …) ends up paying for a second, discarded instance alongside the one it actually uses. Alias the factory itself as the default export instead - `export default createXDevframe` - so importing the module costs nothing beyond defining the function, and every consumer calls it (with or without options) to get their own instance: `import createA11yDevframe from '@devframes/plugin-a11y'` then `createA11yDevframe(options)`.
-### Framework adapter packages: two scopes, one shape
+### Framework kits: two scopes, one shape
-The framework adapter packages - `@devframes/vite`, `@devframes/nuxt`, `@devframes/next` - each split their surface into **two clearly-scoped subpaths**, because a consumer is always doing one of two distinct jobs. Keep all three parallel:
+The framework kits - `@devframes/vite`, `@devframes/nuxt`, `@devframes/next` - each split their surface into **two clearly-scoped subpaths**, because a consumer is always doing one of two distinct jobs. Keep all three parallel:
- **`.../single`** - **build & dev-serve a single devframe's SPA** with that tool (the "I'm authoring one devframe" scope). Vite: the `devframeVitePlugin` / `devframeViteBridge` / `devframeVite` plugins. Next: `withDevframe` + `createDevframeNextHandler`, with its React client at `.../single/client`. Nuxt: the Nuxt module (registered as `modules: ['@devframes/nuxt/single']`).
-- **`.../hub`** - **mount a whole `@devframes/hub` (many integrations) inside that tool** (the "I'm standing up devtools" scope). Wraps `initHub`, defaults the UI slot to `@devframes/hub-ui`'s `createUi()` (overridable via `ui`, or `ui: false` for headless), and ships a browser client helper at `.../hub/client` (a thin, lifecycle-managing wrapper over `@devframes/hub/client`'s `createDevframeClientHost`). `@devframes/hub` and `@devframes/hub-ui` are **optional peers** of these packages; `hub-ui` is loaded lazily (a bundler-ignored dynamic `import()` in the Next hub) so it stays optional and its `import.meta.url` asset lookups resolve at request time.
+- **`.../hub`** - **mount a whole `@devframes/hub` (many devframes) inside that tool** (the "I'm standing up devtools" scope). Wraps `initHub`, defaults the UI slot to `@devframes/hub-ui`'s `createUi()` (overridable via `ui`, or `ui: false` for headless), and ships a browser client helper at `.../hub/client` (a thin, lifecycle-managing wrapper over `@devframes/hub/client`'s `createDevframeClientRuntime`). `@devframes/hub` and `@devframes/hub-ui` are **optional peers** of these packages; `hub-ui` is loaded lazily (a bundler-ignored dynamic `import()` in the Next hub) so it stays optional and its `import.meta.url` asset lookups resolve at request time.
- **The bare root (`.`) throws** a helpful error pointing at the two subpaths - never put real code on it.
-- **Vite and Nuxt already have native hub viewers** (`@vitejs/devtools-kit`, `@nuxt/devtools`), so `@devframes/vite/hub` and `@devframes/nuxt/hub` still work but emit a one-time `console.warn` recommending those (silence with `{ quiet: true }`). `@devframes/next/hub` has no native counterpart, so it warns nothing.
-- The **full hub examples** (`examples/hub-vite`, `examples/hub-next`) consume `.../hub` for the server but keep hand-rolling their own client UI against `@devframes/hub/client` with `ui: false` - that hand-rolled client is the whole point of those reference hosts. The **minimal** ones (`examples/hub-*-minimal`) consume `.../hub` with the default `@devframes/hub-ui` and inject its `embedded.js`, needing no client code.
+- **Vite and Nuxt already have native hub UI providers** (`@vitejs/devtools-kit`, `@nuxt/devtools`), so `@devframes/vite/hub` and `@devframes/nuxt/hub` still work but emit a one-time `console.warn` recommending those (silence with `{ quiet: true }`). `@devframes/next/hub` has no native counterpart, so it warns nothing.
+- The **full hub examples** (`examples/hub-vite`, `examples/hub-next`) consume `.../hub` on the node side but keep hand-rolling their own hub UI provider against `@devframes/hub/client` with `ui: false` - that hand-rolled hub UI provider is the whole point of those reference hosts. The **minimal** ones (`examples/hub-*-minimal`) consume `.../hub` with the default `@devframes/hub-ui` and inject its `embedded.js`, needing no browser-side code.
### Design system
@@ -80,7 +95,7 @@ All five built-in plugins - and every example under `examples/` - share one desi
### Devframe design principles
-These reinforce devframe's positioning as "the container for one devtool integration, portable to multiple viewers". When in doubt, err on the side of "devframe provides primitives, the hub provides UX".
+These reinforce devframe's positioning as "the container for one devtool integration, portable to multiple hub UI providers". When in doubt, err on the side of "devframe provides primitives, the hub provides UX".
- **Single-integration scope.** Devframe describes one tool. If a feature only makes sense when multiple tools share a UI - docking, a unified command palette, cross-tool toasts, terminal aggregation - it belongs in a hub package, not here.
- **Headless by default.** No default startup banners, no opinionated logging to stdout, no default styling. Provide hooks (`onReady`, `cli.configure`, etc.); let the application print its own branding. Structured diagnostics via `nostics` are fine - ad-hoc `console.log`s baked into adapters are not.
diff --git a/docs/app/app.config.ts b/docs/app/app.config.ts
index 74e154a0..62a33626 100644
--- a/docs/app/app.config.ts
+++ b/docs/app/app.config.ts
@@ -22,6 +22,7 @@ export default defineAppConfig({
sections: ['adapters', 'frameworks', 'helpers'],
},
{ label: 'Plugins', sections: ['plugins'], link: 'section' as const },
+ { label: 'Reference', sections: ['references'], link: 'section' as const },
{ label: 'Errors', sections: ['errors'], link: 'section' as const },
{
label: `v${devframePkg.version}`,
@@ -87,7 +88,6 @@ export default defineAppConfig({
'/guide/scoped-context',
'/guide/json-render',
'/guide/diagnostics',
- '/guide/when-clauses',
],
},
{
@@ -110,7 +110,6 @@ export default defineAppConfig({
'/guide/hub-initiate',
'/guide/services',
'/guide/deep-linking',
- '/guide/events',
],
},
{
@@ -171,7 +170,7 @@ export default defineAppConfig({
{
category: 'Hub',
items: [
- 'How do I compose multiple integrations into a hub?',
+ 'How do I compose multiple devframes into a hub?',
'How do I build my own hub UI on top of the hub protocol?',
],
},
diff --git a/docs/content/1.guide/1.tutorial-server-data-inspector.md b/docs/content/1.guide/1.tutorial-server-data-inspector.md
index 307c46da..4492df3e 100644
--- a/docs/content/1.guide/1.tutorial-server-data-inspector.md
+++ b/docs/content/1.guide/1.tutorial-server-data-inspector.md
@@ -1,15 +1,15 @@
---
title: 'Tutorial: Build a Server Data Inspector'
-description: 'Build a devtool that displays and queries live server-side data, then ship it as a hub dock, a static build, a standalone server, and a CLI.'
+description: 'Build a devtool that displays and queries live server-side data, then ship it as a hub dock entry, a static build, a standalone dev server, and a CLI.'
---
-Let's build a real devtool from scratch: a **Data Inspector** that shows the shape of your server's live state and lets you read any value out of it. We'll get it working first, then teach it new tricks one at a time: a dock in a hub, a static build, a standalone server, and a CLI.
+Let's build a real devtool from scratch: a **Data Inspector** that shows the shape of live server-side state and lets you read any value out of it. We'll get it working first, then teach it new tricks one at a time: a dock entry in a hub, a static build, a standalone dev server, and a CLI.
You'll need [Node 24+](https://nodejs.org/) and a terminal. Every code block is complete, so you can copy them as you go.
-## The shape of a devframe app
+## The shape of a devframe
-A devframe app is two halves talking over a typed connection: a **server** in your Node process that exposes functions, and a **browser** client that calls them and renders the results. Devframe is everything in between: the wire, the UI hosting, auth, builds, and a CLI.
+A devframe is two halves talking over a typed connection: the **node side** exposes functions, and the **browser side** calls them and renders the results. Devframe is everything in between: the wire, the UI hosting, auth, builds, and a CLI.
## Step 1 — Define the tool
@@ -24,8 +24,8 @@ npm install devframe && npm install -D typescript
```ts [src/data-inspector.ts]
import { defineDevframe } from 'devframe'
-// Some example server-side data — whatever you want to peek at while your app
-// runs: config, a cache, a DB handle.
+// Some example server-side data — whatever you want to peek at while your
+// user app runs: config, a cache, a DB handle.
const serverState = {
config: { name: 'Acme', port: 3000, debug: false },
users: [
@@ -82,11 +82,11 @@ const dataInspectorFrame = defineDevframe({
export default dataInspectorFrame
```
-`ctx.rpc.register` publishes a function the browser can call: a namespaced `name`, a `type` (`query` is read-only), and a `handler` that takes the call's arguments and returns JSON. That's the whole server. ([RPC](/guide/rpc) has the other types; [Devframe Definition](/guide/devframe-definition) has every field.)
+`ctx.rpc.register` publishes a function the browser side can call: a namespaced `name`, a `type` (`query` is read-only), and a `handler` that takes the call's arguments and returns JSON. That's the whole node side. ([RPC](/guide/rpc) has the other types; [Devframe Definition](/guide/devframe-definition) has every field.)
## Step 2 — Add a UI
-Now the browser half. We'll use React here, but any framework works — the only devframe-specific line is `connectDevframe`, which opens the connection back to the server.
+Now the browser side. We'll use React here, but any framework works — the only devframe-specific line is `connectDevframe`, which opens the connection back to the node side.
```sh
npm install react react-dom @devframes/vite
@@ -128,8 +128,8 @@ export function App() {
const [result, setResult] = useState()
useEffect(() => {
- // No argument: the client finds the server from the page's own URL, so
- // this line never changes no matter how the tool is hosted.
+ // No argument: the RPC client finds the node side from the page's own
+ // URL, so this line never changes no matter how the tool is hosted.
connectDevframe().then(async (client) => {
setRpc(client)
const call = client.call as (name: string, ...args: unknown[]) => Promise
@@ -194,7 +194,7 @@ export default defineConfig({
npx vite --config vite.client.config.ts
```
-Open the printed URL. The three keys and their types show up, and typing `config.port` or `users.0.name` and hitting **Query** prints the value. Button → `call` → your `handler` → back to the page: that's the whole app working.
+Open the printed URL. The three keys and their types show up, and typing `config.port` or `users.0.name` and hitting **Query** prints the value. Button → `call` → your `handler` → back to the page: that's the whole devframe working.
> [!WARNING]
> `auth: false` trusts anything that can reach the port. It's off here to keep the tutorial simple — turn it on for anything you publish or expose beyond localhost. See [Security](/guide/security).
@@ -203,7 +203,7 @@ From here on we reuse this same `src/data-inspector.ts` and `client/` unchanged;
## Step 4 — Dock it in a hub
-A [hub](/guide/hub) puts many devframes behind one interface, each a **dock** you switch between — the tool's own UI in an iframe. Since our client uses a bare `connectDevframe()`, it already works anywhere; the hub just needs the built UI, so point the definition at it:
+A [hub](/guide/hub) puts many devframes behind one interface, each a **dock entry** you switch between — the tool's own UI in an iframe. Since our SPA uses a bare `connectDevframe()`, it already works anywhere; the hub just needs the built UI, so point the definition at it:
```ts [src/data-inspector.ts]
import { fileURLToPath } from 'node:url'
@@ -243,11 +243,11 @@ export default defineConfig({
npx vite --config vite.hub.config.ts
```
-Your inspector now sits in the hub's rail as a dock. Add more to `devframes: [...]` — your own or the [built-in plugins](/plugins) — and each gets its own. (The hub prints a code to authorize on first connect.)
+Your inspector now sits in the hub's dock rail as a dock entry. Add more to `devframes: [...]` — your own or the [built-in devframes](/plugins) — and each gets its own. (The hub prints a code to authorize on first connect.)
## Step 5 — Build a static version
-Some tools should work with no server at all — a report you can drop on any static host. `createBuild` renders the UI and **bakes in** the results of read-only calls. Opt one in with `snapshot: true`:
+Some tools should work with no node side at all — a report you can drop on any static hosting. `createBuild` renders the UI and **bakes in** the results of read-only calls. Opt one in with `snapshot: true`:
```ts
ctx.rpc.register({
@@ -273,7 +273,7 @@ npx vite build # refresh dist/client
node scripts/build.mjs # → dist-static/
```
-Serve `dist-static/` anywhere and the meta list renders from the baked snapshot, no Node in sight. `query` takes an argument, so it still needs the live server (next) — or you can bake specific inputs ([Client Assets](/guide/client-assets)).
+Serve `dist-static/` anywhere and the meta list renders from the baked snapshot, no Node in sight. `query` takes an argument, so it still needs the live node side (next) — or you can bake specific inputs ([Client Assets](/guide/client-assets)).
## Step 6 — Run it standalone
@@ -295,7 +295,7 @@ Same UI, same live calls, no bundler in the loop — this is what you'd drop int
## Step 7 — Give it a CLI
-Finally, wrap that server in a command shell. `devframe/adapters/cac` turns a devframe into a CLI with `dev`, `build`, and `mcp` commands:
+Finally, wrap that dev server in a CLI. `devframe/adapters/cac` turns a devframe into a CLI with `dev`, `build`, and `mcp` commands:
```js [bin.mjs]
#!/usr/bin/env node
@@ -308,14 +308,14 @@ createCac(dataInspectorFrame).parse()
```sh
npm pkg set bin.data-inspector=bin.mjs
-node bin.mjs dev # the standalone server from Step 6
+node bin.mjs dev # the standalone dev server from Step 6
node bin.mjs build # the static build from Step 5
node bin.mjs mcp # expose the tool to a coding agent over MCP
```
You can also assemble your own CLI from the adapter functions used above.
-That's it for this tutorial. For a full-featured version, there's a ready-to-use [Data Inspector plugin](/plugins/data-inspector) to use or read for reference.
+That's it for this tutorial. For a full-featured version, there's a ready-to-use [Data Inspector built-in devframe](/plugins/data-inspector) to use or read for reference.
## What's next
diff --git a/docs/content/1.guide/11.standalone-cli.md b/docs/content/1.guide/10.standalone-cli.md
similarity index 96%
rename from docs/content/1.guide/11.standalone-cli.md
rename to docs/content/1.guide/10.standalone-cli.md
index 70e1f32d..9be3b62a 100644
--- a/docs/content/1.guide/11.standalone-cli.md
+++ b/docs/content/1.guide/10.standalone-cli.md
@@ -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`:
@@ -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//` to rotate with `pnpm install`.
+Persistence is your tool's job ([`unstorage`](https://unstorage.unjs.io/) recommended); keep cache paths under `node_modules/.cache//` to rotate with `pnpm install`.
```ts
import { resolve } from 'pathe'
@@ -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({
@@ -267,7 +267,7 @@ defineDevframe({
})
```
-On the client:
+On the browser side:
```ts
const my = (await connectDevframe()).scope('my-tool')
diff --git a/docs/content/1.guide/12.client.md b/docs/content/1.guide/11.client.md
similarity index 78%
rename from docs/content/1.guide/12.client.md
rename to docs/content/1.guide/11.client.md
index 33829b79..19b1cb03 100644
--- a/docs/content/1.guide/12.client.md
+++ b/docs/content/1.guide/11.client.md
@@ -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
@@ -25,7 +25,7 @@ One SPA artifact serves at `/`, `/__/`, or any subpath, no rebuild. Build wi
### Sharing a connection with an external viewer
-`setupDevframeConnection()` prepares a serializable connection for a cross-origin viewer:
+`setupDevframeConnection()` prepares a serializable connection for an external viewer:
```ts
import { setupDevframeConnection } from 'devframe/client'
@@ -35,7 +35,7 @@ const connection = await setupDevframeConnection({
})
```
-In the viewer:
+In the external viewer:
```ts
import { connectDevframe } from 'devframe/client'
@@ -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'
@@ -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) {
@@ -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
@@ -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
@@ -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'
@@ -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')
@@ -213,7 +213,7 @@ Devframe writes a JSON descriptor at `/__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:
@@ -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'
@@ -240,7 +240,7 @@ const rpc = await connectDevframe()
// Already wired to the local dev server via the injected descriptor.
```
-The descriptor's session-only, pre-approved token makes `ensureTrusted()` resolve immediately. An external hub builds a viewer URL from a trusted connection with `buildRemoteDevframeUrl()`, keeping the token in the URL fragment:
+The descriptor's session-only, pre-approved token makes `ensureTrusted()` resolve immediately. An external hub builds an external-viewer URL from a trusted connection with `buildRemoteDevframeUrl()`, keeping the token in the URL fragment:
```ts
import {
@@ -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) => {
@@ -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 })
@@ -343,13 +343,13 @@ 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()
}
```
-In a hub, a viewer reads this status from [`context.connection`](/guide/client-context#the-client-context).
+In a hub, a hub UI provider reads this status from [`context.connection`](/guide/client-context#the-client-context).
diff --git a/docs/content/1.guide/13.transports.md b/docs/content/1.guide/12.transports.md
similarity index 56%
rename from docs/content/1.guide/13.transports.md
rename to docs/content/1.guide/12.transports.md
index dbcaca94..baaebe3e 100644
--- a/docs/content/1.guide/13.transports.md
+++ b/docs/content/1.guide/12.transports.md
@@ -1,16 +1,16 @@
---
title: 'Transports'
-description: 'Devframe serves live RPC over two interchangeable transports — WebSocket and SSE — so a client connects even where the WebSocket upgrade is unavailable (serverless, buffering proxies). Both speak the identical birpc wire protocol, transparent to your RPC code.'
+description: 'Devframe serves live RPC over two interchangeable transports — WebSocket and SSE — so an RPC client connects even where the WebSocket upgrade is unavailable (serverless, buffering proxies). Both speak the identical birpc wire protocol, transparent to your RPC code.'
---
-Devframe serves live RPC over two interchangeable transports — WebSocket and SSE — so a client connects even where the WebSocket upgrade is unavailable (serverless, buffering proxies). Both speak the identical birpc wire protocol, transparent to your RPC code.
+Devframe serves live RPC over two interchangeable transports — WebSocket and SSE — so an RPC client connects even where the WebSocket upgrade is unavailable (serverless, buffering proxies). Both speak the identical birpc wire protocol, transparent to your RPC code.
-## What the server binds
+## What the node side binds
A live instance binds both by default:
- **WebSocket** at `__ws` — primary, one full-duplex socket.
-- **SSE** at `__sse` — one method-dispatched route: `GET` opens the server→client stream, `POST` carries client→server frames. It rides the same HTTP surface as `__connection.json`, so wherever discovery works SSE works — including middleware-only hosts (the Vite bridge, `initDevframe`'s `handler` / `nodeMiddleware`).
+- **SSE** at `__sse` — one method-dispatched route: `GET` opens the server→client stream, `POST` carries client→server frames. It rides the same HTTP routes as `__connection.json`, so wherever discovery works SSE works — including middleware-only host frameworks (the Vite bridge, `initDevframe`'s `handler` / `nodeMiddleware`).
`__connection.json` advertises what's bound; `backend` is the primary:
@@ -27,8 +27,8 @@ The SSE stream sends a keep-alive comment every 30 seconds. Both endpoints share
### Configuring
```ts
-// SSE-only — hosts/proxies where the upgrade can't happen. Clients
-// connect over SSE automatically (backend: 'sse').
+// SSE-only — host frameworks/proxies where the upgrade can't happen.
+// RPC clients connect over SSE automatically (backend: 'sse').
initDevframe(def, { base: '/__my-tool/', ws: false })
// WebSocket-only — opt out of the SSE endpoint.
@@ -38,11 +38,11 @@ initDevframe(def, { base: '/__my-tool/', server, sse: false })
initDevframe(def, { base: '/__my-tool/', server, sse: { route: '__events' } })
```
-`ws: false` + `sse: false` runs an RPC-less shell (`backend: 'none'`); the SPA, discovery, and MCP routes still serve. The same options apply to `createDevServer`, `initHub`, and `cli.ws` / `cli.sse` defaults.
+`ws: false` + `sse: false` runs RPC-less (`backend: 'none'`); the SPA, discovery, and MCP routes still serve. The same options apply to `createDevServer`, `initHub`, and `cli.ws` / `cli.sse` defaults.
-## What the client picks
+## What the RPC client picks
-`connectDevframe` connects over the declared primary, preferring WebSocket when both are present; a socket-less server advertises SSE as primary, so the client lands there directly.
+`connectDevframe` connects over the declared primary, preferring WebSocket when both are present; a socket-less server advertises SSE as primary, so the RPC client lands there directly.
Pin a transport when you know better — e.g. an intermediary that silently strips WS upgrades:
@@ -54,4 +54,4 @@ client.transport // 'websocket' | 'sse' | 'static' — what actually connected
Pinning an unadvertised transport rejects. SSE follows the same proxy-safe rules as WebSocket: relative paths against `__connection.json`'s URL, explicit `host`/`port` only for a cross-origin endpoint.
-A dropped SSE stream ends the client like a closed socket: pending calls reject, status moves to `disconnected`, reconnect via `connectDevframe`.
+A dropped SSE stream ends the RPC client like a closed socket: pending calls reject, status moves to `disconnected`, reconnect via `connectDevframe`.
diff --git a/docs/content/1.guide/14.security.md b/docs/content/1.guide/13.security.md
similarity index 89%
rename from docs/content/1.guide/14.security.md
rename to docs/content/1.guide/13.security.md
index 0babba2c..ace99013 100644
--- a/docs/content/1.guide/14.security.md
+++ b/docs/content/1.guide/13.security.md
@@ -7,13 +7,13 @@ Devframe tools are secure by default: connections bind to `localhost`, and dev-m
## Trust model
-An RPC handler runs with the full privileges of its host process — filesystem, child processes, network — and a trusted connection can call any registered function. The boundary that matters is *who may connect*:
+An RPC handler runs with the full privileges of its Node process — filesystem, child processes, network — and a trusted connection can call any registered function. The boundary that matters is *who may connect*:
- **Authenticated (default).** `auth` defaults to `true`; the browser authenticates before calls are accepted, then reconnects with a node-issued bearer token. `createInteractiveAuth` (`devframe/recipes/interactive-auth`) packages the protocol into one `DevframeAuthHandler` the adapters wire for you (pass it to `initDevframe` / `initHub` via `auth`).
- **Unauthenticated opt-out.** `auth: false` starts the server with an auto-trust handshake, for single-user tools on their own `localhost`.
> [!WARNING]
-> `auth: false` trusts every connection that can reach the port. Only use it when the surface is reachable solely by the local developer. Never combine it with a non-loopback bind host, a tunnelled port, or a shared/CI environment.
+> `auth: false` trusts every connection that can reach the port. Only use it when the endpoint is reachable solely by the local developer. Never combine it with a non-loopback bind host, a tunnelled port, or a shared/CI environment.
## The pre-trust gate
@@ -23,7 +23,7 @@ The RPC server binding enforces this: pass `auth: authHandler` (its `.authorize`
## Authentication flow
-1. A fresh client calls `anonymous:devframe:auth` with its stored token (empty on first run); the server returns `{ isTrusted: false }` and the UI prompts for a code.
+1. A fresh RPC client calls `anonymous:devframe:auth` with its stored token (empty on first run); the server returns `{ isTrusted: false }` and the UI prompts for a code.
2. The dev server shows a 6-digit code in the terminal — `auth.printBanner()` once listening.
3. The developer enters it; the browser calls `requestTrustWithCode(code)`.
4. The server verifies the code, mints a high-entropy bearer token, trusts the session, and returns it.
@@ -31,7 +31,7 @@ The RPC server binding enforces this: pass `auth: authHandler` (its `.authorize`
The 6-digit code is single-use, expires after five minutes, is compared in constant time, and rotates after repeated wrong attempts. Show it only in a trusted channel (the terminal), never over the network.
-The bearer token is a secret. It travels to the server on the WebSocket URL (`?devframe_auth_token=…`), so serve over `wss://`/`https://` whenever the surface is reachable beyond loopback. A client self-revokes (`devframe:auth:revoke`) or a host revokes it (`revokeAuthToken`); affected clients drop to untrusted via `devframe:auth:revoked`.
+The bearer token is a secret. It travels to the server on the WebSocket URL (`?devframe_auth_token=…`), so serve over `wss://`/`https://` whenever the endpoint is reachable beyond loopback. An RPC client self-revokes (`devframe:auth:revoke`) or the node side revokes it (`revokeAuthToken`); affected RPC clients drop to untrusted via `devframe:auth:revoked`.
### The ready-made layer
@@ -66,7 +66,7 @@ Node primitives (`devframe/node/auth`):
| `buildOtpAuthUrl(origin, code?)` | build a magic-link URL embedding the code |
| `revokeAuthToken(context, storage, token)` | delete a token and disconnect sessions using it |
-Client methods (`devframe/client`): `requestTrustWithCode(code)`, `requestTrustWithToken(token)`, and `ensureTrusted(timeout?)` / `isTrusted` (the trust gate).
+RPC client methods (`devframe/client`): `requestTrustWithCode(code)`, `requestTrustWithToken(token)`, and `ensureTrusted(timeout?)` / `isTrusted` (the trust gate).
### Magic-link authentication
@@ -83,7 +83,7 @@ For your own auth UI, disable built-in handling with `otpParam: false`, then cal
## Practices for tools built on devframe
- **Stay on loopback.** Bind to a routable address only intentionally, and require authentication when you do.
-- **Keep `auth: false` local.** The hosted bridges (`devframeViteBridge`, `@devframes/next`'s handler) gate their side-car by default; opt out with an explicit `auth: false` only when the host owns the trust boundary another way.
+- **Keep `auth: false` local.** The hosted bridges (`devframeViteBridge`, `@devframes/next`'s handler) gate their side-car by default; opt out with an explicit `auth: false` only when the host framework owns the trust boundary another way.
- **The MCP route requires an origin.** The route-based MCP server rejects requests without a loopback or allow-listed `Origin`, so an arbitrary local process can't reach it — see [MCP](/adapters/mcp).
- **Treat tokens as secrets.** Never log the bearer token or the one-time code, or bake either into build output.
- **Authorize every handler.** Validate inputs, and mark state-changing functions `type: 'destructive'` so MCP and agent clients prompt before invoking them.
diff --git a/docs/content/1.guide/15.agent-native.md b/docs/content/1.guide/14.agent-native.md
similarity index 91%
rename from docs/content/1.guide/15.agent-native.md
rename to docs/content/1.guide/14.agent-native.md
index fcf3c122..528c9863 100644
--- a/docs/content/1.guide/15.agent-native.md
+++ b/docs/content/1.guide/14.agent-native.md
@@ -1,9 +1,9 @@
---
title: 'Agent-Native Devframe'
-description: 'Devframe exposes its browser-UI surface — RPC functions, resources, shared state — to agents over MCP, opt-in per function.'
+description: 'Devframe exposes its browser-side API — RPC functions, resources, shared state — to coding agents over MCP, opt-in per function.'
---
-Devframe exposes its browser-UI surface — RPC functions, resources, shared state — to agents over MCP, opt-in per function.
+Devframe exposes its browser-side API — RPC functions, resources, shared state — to coding agents over MCP, opt-in per function.
## How it works
@@ -34,7 +34,7 @@ export const getSessionSummary = defineRpcFunction({
## Tool ids and wire names
-- **The id** — registers/invokes in devframe, colon-namespaced: `devframes:plugin::` (plugin RPCs), `devframe::` (built-ins), command ids.
+- **The id** — registers/invokes in devframe, colon-namespaced: `devframes:plugin::` (built-in devframe RPCs), `devframe::` (built-ins), command ids.
- **The wire name** — what MCP clients call, constrained to `^[a-zA-Z0-9_-]{1,128}$`; runs outside that set collapse to `_`, truncated to 128.
```
@@ -45,7 +45,7 @@ my-plugin:summarize → my-plugin_summarize
`toAgentToolName` (`devframe/utils/agent-tool-name`, client-safe) predicts a wire name; two ids sanitizing alike keep the first, the later hidden with `DF0047`.
-## Registering a plugin tool
+## Registering a devframe tool
Tools without a matching RPC register directly.
@@ -184,5 +184,5 @@ Prefer coded diagnostics anywhere agent-reachable: agents act on `fix` and follo
| Command | Description |
|---------|-------------|
| ` mcp` | Start the MCP server on `stdio`. |
-| ` dev --mcp` | Serve the agent surface on `/__mcp`. |
+| ` dev --mcp` | Serve the agent-consumable API on `/__mcp`. |
| `devframe connect` | Discover running devframes and proxy their tools — see [MCP adapter](/adapters/mcp#discovery-devframe-connect). |
diff --git a/docs/content/1.guide/16.hub.md b/docs/content/1.guide/15.hub.md
similarity index 76%
rename from docs/content/1.guide/16.hub.md
rename to docs/content/1.guide/15.hub.md
index bfae9506..76ac6892 100644
--- a/docs/content/1.guide/16.hub.md
+++ b/docs/content/1.guide/15.hub.md
@@ -1,9 +1,9 @@
---
title: 'Hub'
-description: '@devframes/hub orchestrates many devtools sharing a UI: a dock registry, terminal aggregation, message/toast queue, and command palette. It ships no UI — each framework kit provides its own atop the hub''s RPC + shared-state protocol.'
+description: '@devframes/hub orchestrates many devtools sharing a UI: a dock registry, terminal aggregation, message/toast queue, and command palette. It ships no UI — hub UI providers provide their own atop the hub''s RPC + shared-state protocol.'
---
-`@devframes/hub` orchestrates many devtools sharing a UI: a dock registry, terminal aggregation, message/toast queue, and command palette. It ships no UI — each framework kit provides its own atop the hub's RPC + shared-state protocol.
+`@devframes/hub` orchestrates many devtools sharing a UI: a dock registry, terminal aggregation, message/toast queue, and command palette. It ships no UI — hub UI providers provide their own atop the hub's RPC + shared-state protocol.

@@ -13,29 +13,29 @@ _Orchestrating multiple devtools (from [A Playground](https://github.com/devfram
`DevframeHubContext` adds four subsystems to `DevframeNodeContext`:
-| Subsystem | Surface | Purpose |
+| Subsystem | API | Purpose |
|---|---|---|
| `ctx.docks` | `register / update / values / activate` | Dock entries (iframes, launchers, custom-render) and groups; `activate(dockId, params?)` sets the active dock ([Cross-iframe dock activation](#cross-iframe-dock-activation)). |
| `ctx.terminals` | `register / startChildProcess` | Aggregate terminal sessions, streaming output ([Terminals](/plugins/terminals#hub-aggregation)). |
| `ctx.messages` | `add / update / remove / clear` | Server-side toast/notification queue (FIFO, capped at 1000). |
| `ctx.commands` | `register / execute / list` | Hierarchical command palette with keybindings and `when` clauses. |
-Data-driven UI panels are an opt-in [JSON-Render](/guide/json-render) integration (a `json-render` dock type).
+Data-driven UI panels are an opt-in [JSON-Render](/guide/json-render) package (a `json-render` dock type).
## Built-in RPC
-Every hub context auto-registers these client-callable functions:
+Every hub context auto-registers these functions, callable from any RPC client:
- `hub:commands:execute` — invoke a server command by id.
- `hub:docks:activate` — switch the active dock ([Cross-iframe dock activation](#cross-iframe-dock-activation)).
- `hub:messages:add` / `update` / `remove` / `clear` — write the messages feed.
- `hub:terminals:write` / `resize` — drive a PTY session by id.
-Host-specific capabilities (open in editor, reveal in finder) ship as kit-registered functions.
+Host-framework-specific capabilities (open in editor, reveal in finder) ship as kit-registered functions.
## Commands as agent tools
-A server command with an `agent` field ([agent surface](/guide/agent-native)) becomes a `ctx.agent` MCP tool:
+A server command with an `agent` field ([agent-consumable API](/guide/agent-native)) becomes a `ctx.agent` MCP tool:
```ts
ctx.commands.register({
@@ -71,11 +71,11 @@ A `type: 'launcher'` dock entry is a one-click action tile. Three optional `laun
| Field | Purpose |
|---|---|
-| `command` | Bound command id; out-of-process viewers dispatch via `hub:commands:execute` (register a handler via `ctx.commands`). |
+| `command` | Bound command id; out-of-process hub UI providers dispatch via `hub:commands:execute` (register a handler via `ctx.commands`). |
| `terminalSessionId` | Tracked session id; a "view in terminal" action calls `hub:docks:activate` with the terminals dock id and `{ sessionId }`. |
| `digest` | Latest progress line, shown inline; patch via `docks.update()`. |
-`onLaunch` lets a same-process host invoke directly; provide `command`, `onLaunch`, or both.
+`onLaunch` lets a same-process host framework invoke directly; provide `command`, `onLaunch`, or both.
```ts
ctx.commands.register({ id: 'app:build', title: 'Run build', handler: runBuild })
@@ -120,11 +120,11 @@ const ctx = await createHubContext({ cwd, host, mode: 'dev' })
await ctx.install(myDevframe)
```
-Framework kits wrap this in a plugin shell (e.g. `@vitejs/devtools-kit`'s `createPluginFromDevframe`).
+Framework kits and hub UI providers wrap this (e.g. `@vitejs/devtools-kit`'s `createPluginFromDevframe`).
### Connecting embedded SPAs
-A mounted SPA loads at `/__/` and calls `connectDevframe()`, which fetches `./__connection.json` — served by the host's `mountConnectionMeta(base)`:
+A mounted SPA loads at `/__/` and calls `connectDevframe()`, which fetches `./__connection.json` — served by the host framework's `mountConnectionMeta(base)`:
```ts
const host: DevframeHost = {
@@ -146,9 +146,9 @@ const host: DevframeHost = {
Omitting `mountConnectionMeta` (with servable `clientAssets`) triggers [`DF8106`](https://devfra.me/errors/DF8106) and falls back to same-origin inheritance.
-### Bundled hosts (Next.js)
+### Bundled host frameworks (Next.js)
-Load node-side plugin packages via dynamic `import()` with `webpackIgnore`/`turbopackIgnore` comments:
+Load node-side built-in devframe packages via dynamic `import()` with `webpackIgnore`/`turbopackIgnore` comments:
```ts
const pkgs = ['@devframes/plugin-git', '@devframes/plugin-terminals']
@@ -190,7 +190,7 @@ defineDevframe({
## Grouping dock entries
-Related dock entries collapse under one dock-bar button (a `type: 'group'` entry); an entry whose `groupId` matches the group's `id` joins it.
+Related dock entries collapse under one dock-rail button (a `type: 'group'` entry); an entry whose `groupId` matches the group's `id` joins it.
```ts
ctx.docks.register({
@@ -212,11 +212,11 @@ ctx.docks.register({
})
```
-Group and members stay independent top-level entries in `devframe:docks`; `defaultChildId` opens on activation. Grouping affects the dock bar, not iframes — to share **one** soft-navigated iframe, give docks a shared `frameId` and mark the anchor with `subTabs` ([Shared-iframe soft navigation](/guide/client-context#shared-iframe-soft-navigation)).
+Group and members stay independent top-level entries in `devframe:docks`; `defaultChildId` opens on activation. Grouping affects the dock rail, not iframes — to share **one** soft-navigated iframe, give docks a shared `frameId` and mark the anchor with `subTabs` ([Shared-iframe soft navigation](/guide/client-context#shared-iframe-soft-navigation)).
### The dual role of `category`
-`category` (ordered by `DEFAULT_CATEGORIES_ORDER`, default `'default'`) sets an **ungrouped** entry's outer dock-bar bucket. A **grouped** entry takes its outer bucket from the **group's** `category`, and its own `category` becomes an **in-group sub-category**; a member whose `groupId` never resolves renders top-level under its own `category`.
+`category` (ordered by `DEFAULT_CATEGORIES_ORDER`, default `'default'`) sets an **ungrouped** entry's outer dock-rail bucket. A **grouped** entry takes its outer bucket from the **group's** `category`, and its own `category` becomes an **in-group sub-category**; a member whose `groupId` never resolves renders top-level under its own `category`.
#### Known categories
@@ -235,30 +235,30 @@ Group and members stay independent top-level entries in `devframe:docks`; `defau
| `docs` | `500` | Documentation. |
| `~builtin` | `1000` | Built-in views; always last. |
-Kits can interleave category ids or override weights; an unknown category sorts as `0`.
+Framework kits can interleave category ids or override weights; an unknown category sorts as `0`.
-## The protocol — what the UI sees
+## The protocol — what the hub UI provider sees
-A hub-aware UI imports no hub classes; it reads these shared-state keys and RPC methods:
+A hub UI provider imports no hub classes; it reads these shared-state keys and RPC methods:
| Channel | Type | What it carries |
|---|---|---|
| `devframe:docks` shared state | `DevframeDockEntry[]` | Every registered dock entry. |
| `devframe:commands` shared state | `DevframeServerCommandEntry[]` | Serializable command list (handlers stripped). |
-| `devframe:user-settings` shared state | `DevframeDocksUserSettings` | Persisted per-workspace hub settings. |
+| `devframe:user-settings` shared state | `DevframeDocksUserSettings` | Persisted project-scope hub settings. |
| `devframe:docks:active` shared state | `DevframeDocksActiveState` | Most recent [dock activation](#cross-iframe-dock-activation) request. |
| `hub:commands:execute` RPC | `(id, ...args) => unknown` | Server-side command dispatch. |
| `hub:docks:activate` RPC | `({ dockId, params? }) => void` | Switch the active dock. |
-Broadcast notifications (`devframe:docks:activate`, `devframe:terminals:updated`, `devframe:messages:updated`) arrive via `rpc.client.register(...)`; the client host registers `devframe:docks:activate` for you ([Events Reference](/guide/events)).
+Broadcast notifications (`devframe:docks:activate`, `devframe:terminals:updated`, `devframe:messages:updated`) arrive via `rpc.client.register(...)`; the client runtime registers `devframe:docks:activate` for you ([Events Reference](/references/events)).
-## Running plugin code in the host page
+## Running a devframe's code in the host page
-The hub ships a headless browser runtime, `createDevframeClientHost()` (`@devframes/hub/client`): booted in the host page, it assembles the client context and imports each dock entry's client script ([Client Scripts & Client Context](/guide/client-context)).
+The hub ships a headless client runtime, `createDevframeClientRuntime()` (`@devframes/hub/client`): booted in the host page, it assembles the client context and imports each dock entry's client script ([Client Scripts & Client Context](/guide/client-context)).
## Example
-Two minimal hubs mount every built-in plugin behind an icon dock, plus a "Tabbed Tool" demonstrating [shared-iframe soft navigation](/guide/client-context#shared-iframe-soft-navigation):
+Two minimal hubs mount every built-in devframe behind an icon dock, plus a "Tabbed Tool" demonstrating [shared-iframe soft navigation](/guide/client-context#shared-iframe-soft-navigation):
- [`examples/hub-vite/`](https://github.com/devframes/devframe/tree/main/examples/hub-vite) — a ~120-line Vite host with a vanilla DOM UI.
- [`examples/hub-next/`](https://github.com/devframes/devframe/tree/main/examples/hub-next) — the same, from a Next.js App Router app.
diff --git a/docs/content/1.guide/17.client-context.md b/docs/content/1.guide/16.client-context.md
similarity index 53%
rename from docs/content/1.guide/17.client-context.md
rename to docs/content/1.guide/16.client-context.md
index 81855fe4..87eb4375 100644
--- a/docs/content/1.guide/17.client-context.md
+++ b/docs/content/1.guide/16.client-context.md
@@ -1,23 +1,23 @@
---
title: 'Client Scripts & Client Context'
-description: 'A dock client script runs a plugin''s code inside the host page; the client context is how client surfaces reach the hub.'
+description: 'A dock client script runs a devframe''s code inside the host page; the client context is how client scripts reach the hub.'
---
-A dock **client script** runs a plugin's code inside the **host page**; the **client context** is how client surfaces reach the hub.
+A dock **client script** runs a devframe's code inside the **host page**; the **client context** is how client scripts reach the hub.
> [!WARNING] Experimental
-> The hub API surface is still being refined. Names may change before 1.0.
+> The hub API is still being refined. Names may change before 1.0.
-## The client host runtime
+## The client runtime
-`createDevframeClientHost()` (`@devframes/hub/client`) boots the host page: it connects (or adopts) an RPC client, publishes the `DevframeClientContext`, and imports each dock's client script:
+`createDevframeClientRuntime()` (`@devframes/hub/client`) boots the host page: it connects (or adopts) an RPC client, publishes the `DevframeClientContext`, and imports each dock's client script:
```ts
-// main.ts — the host app / hub page's browser entry
-import { connectDevframe, createDevframeClientHost } from '@devframes/hub/client'
+// main.ts — the host page's browser entry
+import { connectDevframe, createDevframeClientRuntime } from '@devframes/hub/client'
const rpc = await connectDevframe({ baseURL: '/__hub/' })
-const { context, dispose } = await createDevframeClientHost({ rpc })
+const { context, dispose } = await createDevframeClientRuntime({ rpc })
```
### Options
@@ -37,12 +37,12 @@ A second boot replaces the context and warns; `dispose()` tears down listeners a
| Property | Description |
|----------|-------------|
| `rpc` | The [RPC client](/guide/client) — server/client functions, shared state. |
-| `clientType` | `'embedded'` (inside your app) or `'standalone'` (independent hub page). |
+| `clientType` | `'embedded'` (inside the user app) or `'standalone'` (independent hub page). |
| `docks` | `entries`, `selected`, `groupedEntries`, `switchEntry()`, `toggleEntry()`, `getStateById()`, `register()` / `update()` for [client-only docks](#client-only-docks). |
| `panel` | Dock panel state: position, size, drag/resize. |
| `commands` | Command palette: `register()`, `execute()`, `getKeybindings()`. |
| `renderers` | Dock-renderer registry — `register()`, `get()`, `has()`, `mount(entry, container)`. Routes a dock `type` to a renderer (local boot or the hub's [manifest](/guide/hub-initiate#renderer-modules); local wins). `mount()` resolves a `status`: `mounted` (with `dispose`), `missing-renderer`, or `load-error` (with `error`). |
-| `when` | The [when-clause](/guide/when-clauses) context. |
+| `when` | The [when-clause](/references/when-clauses) context. |
| `connection` | Live [connection status](/guide/client#handling-connection-and-auth-errors) — `status`, `error`, `events`. |
### Accessing the context
@@ -51,7 +51,7 @@ A second boot replaces the context and warns; `dispose()` tears down listeners a
### Client-only docks
-A client host can register a dock local to this page (unlike [node hub context](/guide/hub) docks synced via `devframe:docks`). `ctx.docks.register(entry)` — e.g. `type: 'custom-render'` with `renderer: { importFrom }` — returns a handle whose `update({ badge })` patches in place (id immutable) and `dispose()` removes it. One sharing a server dock's id overrides it locally; re-registering an owned id throws unless you pass `register(entry, true)`.
+A client runtime can register a dock local to the host page (unlike [node hub context](/guide/hub) docks synced via `devframe:docks`). `ctx.docks.register(entry)` — e.g. `type: 'custom-render'` with `renderer: { importFrom }` — returns a handle whose `update({ badge })` patches in place (id immutable) and `dispose()` removes it. One sharing a server dock's id overrides it locally; re-registering an owned id throws unless you pass `register(entry, true)`.
A client-only dock can also carry `type: 'json-render'` with an inline [JSON-render](/guide/json-render) `view: { spec }` (a `DevframeJsonRenderSpec` built in-browser), rendered when a `json-render` renderer is registered at boot. `view` also accepts `{ stateKey }` for live shared state (from `createJsonRenderView`).
@@ -76,37 +76,37 @@ A failed import retries on the next dock update.
`importFrom` accepts two shapes:
-- **A host-served URL** — a self-contained ES module; works on every host.
-- **A bare npm specifier** (`'vite-plugin-vue-tracer/client/vite-devtools'`) — resolved through the host runtime.
+- **A URL served by the host framework** — a self-contained ES module; works on every host framework.
+- **A bare npm specifier** (`'vite-plugin-vue-tracer/client/vite-devtools'`) — resolved through the host framework.
-For a URL, attach it via `ctx.install(myDevframe, { dock: { clientScript: { importFrom } } })`. Under Vite `/@fs/` serves it; other hosts mount the directory statically.
+For a URL, attach it via `ctx.install(myDevframe, { dock: { clientScript: { importFrom } } })`. Under Vite `/@fs/` serves it; other host frameworks mount the directory statically.
### Bare npm specifiers
-Bare specifiers are a **host-runtime capability**: a host advertises a resolution template at `ConnectionMeta.configs.dock.clientModuleResolution` (loaders replace `{specifier}` before import). A Vite host declares this by default (`@devframes/vite/hub`) as `initHub({ clientModuleResolution: '/@id/{specifier}' })`; then use the specifier alone as `importFrom`. A host with no template (Next.js) supports the URL shape only, warning [`DF8111`](/errors/DF8111) on a specifier. A viewer can override with `createDevframeClientHost({ resolveClientModule })`.
+Resolving a bare specifier is the **host framework's capability**: a host framework advertises a resolution template at `ConnectionMeta.configs.dock.clientModuleResolution` (loaders replace `{specifier}` before import). A Vite host declares this by default (`@devframes/vite/hub`) as `initHub({ clientModuleResolution: '/@id/{specifier}' })`; then use the specifier alone as `importFrom`. A host framework with no template (Next.js) supports the URL shape only, warning [`DF8111`](/errors/DF8111) on a specifier. A hub UI provider can override with `createDevframeClientRuntime({ resolveClientModule })`.
-Client scripts execute in the inspected page's realm (`window`); anchor shared state on `globalThis`.
+Client scripts execute in the user app's page realm (`window`); anchor shared state on `globalThis`.
### Dual boots
-One bundle can serve as both a client script (default export) and, via a globally-guarded self-boot, a standalone in-page agent ([a11y inspector](/plugins/a11y)).
+One bundle can serve as both a client script (default export) and, via a globally-guarded self-boot, a standalone page script ([a11y inspector](/plugins/a11y)).
## Iframe panels
-Dock iframes are their own documents: the panel SPA calls `connectDevframe()`, discovering `./__connection.json` from its base. Host script and iframe share the server via RPC and shared state, or a same-origin `BroadcastChannel` for static builds.
+Dock iframes are their own documents: the panel calls `connectDevframe()`, discovering `./__connection.json` from its base. A client script and an iframe panel share the node side via RPC and shared state, or a same-origin `BroadcastChannel` for static builds.
## Shared-iframe soft navigation
-A tool with many internal views (Nuxt DevTools' tabs) can surface each as a hub dock sharing **one** live iframe — the **anchor** owns a `frameId` and opts in via `ctx.install(…, { dock: { frameId, subTabs: { protocol: 'postmessage' } } })`. On mount, the host attaches a **frame-nav adapter** speaking an origin-locked `postMessage` protocol on `devframe:frame-nav`.
+A tool with many internal views (Nuxt DevTools' tabs) can surface each as a hub dock sharing **one** live iframe — the **anchor** owns a `frameId` and opts in via `ctx.install(…, { dock: { frameId, subTabs: { protocol: 'postmessage' } } })`. On mount, the client runtime attaches a **frame-nav adapter** speaking an origin-locked `postMessage` protocol on `devframe:frame-nav`.
| Message | Direction | Meaning |
|---|---|---|
-| `ready` / `manifest` | frame → host | tab list (`{ tabs, current }`), on load and change |
-| `navigate` | host → frame | show a view (`{ tabId, navTarget }`); app routes client-side |
-| `navigated` | frame → host | app navigated internally; host highlights the dock |
+| `ready` / `manifest` | iframe → host page | tab list (`{ tabs, current }`), on load and change |
+| `navigate` | host page → iframe | show a view (`{ tabId, navTarget }`); the SPA routes client-side |
+| `navigated` | iframe → host page | the SPA navigated internally; the hub UI provider highlights the dock |
It materializes a [client-only dock](#client-only-docks) per tab (id `:`) sharing the anchor's `frameId` and a `navTarget`, independent of [`groupId`](/guide/hub#grouping-dock-entries).
-### The viewer's part
+### The hub UI provider's part
-A viewer keeps one iframe alive per `frameId` (shown/hidden); on mount, it sets the element on the anchor's `docks.getStateById(anchorId)` state (`domElements.iframe`) and emits `dom:iframe:mounted`. See the "Tabbed Tool" in [`examples/hub-vite`](https://github.com/devframes/devframe/tree/main/examples/hub-vite) / [`hub-next`](https://github.com/devframes/devframe/tree/main/examples/hub-next).
+A hub UI provider keeps one iframe alive per `frameId` (shown/hidden); on mount, it sets the element on the anchor's `docks.getStateById(anchorId)` state (`domElements.iframe`) and emits `dom:iframe:mounted`. See the "Tabbed Tool" in [`examples/hub-vite`](https://github.com/devframes/devframe/tree/main/examples/hub-vite) / [`hub-next`](https://github.com/devframes/devframe/tree/main/examples/hub-next).
diff --git a/docs/content/1.guide/18.hub-initiate.md b/docs/content/1.guide/17.hub-initiate.md
similarity index 61%
rename from docs/content/1.guide/18.hub-initiate.md
rename to docs/content/1.guide/17.hub-initiate.md
index de4c8599..48adb444 100644
--- a/docs/content/1.guide/18.hub-initiate.md
+++ b/docs/content/1.guide/17.hub-initiate.md
@@ -21,13 +21,13 @@ export const hub = initHub({
})
```
-`base` is required (echoed as `hub.base`); each mounted devframe runs `setup()` against the **shared hub context**. The instance mirrors `initDevframe`'s surface — see [The Standard Handler](/adapters/initiate#mount-the-handler).
+`base` is required (echoed as `hub.base`); each mounted devframe runs `setup()` against the **shared hub context**. The instance mirrors `initDevframe`'s API — see [The Standard Handler](/adapters/initiate#mount-the-handler).
## The shared socket
-One transport serves the namespace, chosen in precedence: `ws.port` pins a side-car; `server` shares the host's `node:http` upgrade at `__ws`; `ws: { sidecar: true }` takes a free port; none leaves the socket to the host — Node uses `hub.attach(server)`, Bun/Deno `attachBunWsTransport` / `attachDenoWsTransport`.
+One transport serves the namespace, chosen in precedence: `ws.port` pins a side-car; `server` shares the host framework's `node:http` upgrade at `__ws`; `ws: { sidecar: true }` takes a free port; none leaves the socket to the host framework — Node uses `hub.attach(server)`, Bun/Deno `attachBunWsTransport` / `attachDenoWsTransport`.
-The advertised path is hub-base-absolute (`/__devframes/__ws`). Dev-reevaluated hosts (Next, Nitro) memoize it on `globalThis`.
+The advertised path is hub-base-absolute (`/__devframes/__ws`). Dev-reevaluated host frameworks (Next, Nitro) memoize it on `globalThis`.
## The namespace
@@ -38,11 +38,11 @@ The advertised path is hub-base-absolute (`/__devframes/__ws`). Dev-reevaluated
| `embedded.js` | the `ui.embedded` bootstrap (`404` if none) |
| `__connection.json` | meta for the shared RPC socket |
| `__ws` | WebSocket upgrade route |
-| `__index.json` | machine-readable index: frames, endpoints |
-| `__client-imports.js` | dock client-script import map for viewers |
+| `__index.json` | machine-readable index: mounted devframes, endpoints |
+| `__client-imports.js` | dock client-script import map for hub UI providers |
| `__mcp` | aggregate MCP endpoint over the tool registry (opt-in `mcp`) |
-Frame ids become URL segments, validated: reserved names throw `DF8000`, non-route-safe `DF8004`.
+Devframe ids become URL segments, validated: reserved names throw `DF8000`, non-route-safe `DF8004`.
## The `ui` slot
@@ -57,18 +57,18 @@ interface DevframeHubUi {
}
```
-`@devframes/hub-ui`'s `createUi()` is the reference (viewer + floating dock); its `setup(ctx)` publishes config to `ctx.staticConfig.ui` (`ConnectionMeta.configs.ui`):
+`@devframes/hub-ui`'s `createUi()` is the reference (standalone `viewer` SPA + floating dock); its `setup(ctx)` publishes config to `ctx.staticConfig.ui` (`ConnectionMeta.configs.ui`):
- **`branding`** — rebrand the UI (logo, name, primary color).
-- **`dockPreferences`** — dock-bar: `categoryOrder`, floating-dock `maxVisibleItems`, first-run `defaultMode` (`'float'`/`'edge'`) and `defaultPosition`.
+- **`dockPreferences`** — dock-rail: `categoryOrder`, floating-dock `maxVisibleItems`, first-run `defaultMode` (`'float'`/`'edge'`) and `defaultPosition`.
- **`embeddedVisibility`** — the floating dock's reveal policy:
- `'normal'` (default) — shows immediately.
- - `'passive'` — hidden until `Shift+Alt+D`, then persisted per-origin (later sessions start shown).
- - `'hidden'` — hidden until `Shift+Alt+D`, that session only.
+ - `'passive'` — hidden until `Shift+Alt+D`, then persisted per-origin (later browser sessions start shown).
+ - `'hidden'` — hidden until `Shift+Alt+D`, that browser session only.
## Renderer modules
-A dock type's renderer (e.g. [JSON-Render](/guide/json-render)) composes via `initHub({ renderers })`. Each registration `{ type, file, importName? }` (`file` = a prebuilt ES module exporting a `DockRenderer`) is served at `__renderers/.mjs` and published into the `devframe:dock-renderers` manifest; clients import it lazily on first mount:
+A dock type's renderer (e.g. [JSON-Render](/guide/json-render)) composes via `initHub({ renderers })`. Each registration `{ type, file, importName? }` (`file` = a prebuilt ES module exporting a `DockRenderer`) is served at `__renderers/.mjs` and published into the `devframe:dock-renderers` manifest; client runtimes import it lazily on first mount:
```ts
import { createUi } from '@devframes/hub-ui'
@@ -80,13 +80,13 @@ initHub({
})
```
-A client-registered renderer (`createDevframeClientHost({ renderers })`) overrides the manifest; an uncovered type shows the viewer's missing-renderer fallback.
+A renderer registered at boot (`createDevframeClientRuntime({ renderers })`) overrides the manifest; an uncovered type shows the hub UI provider's missing-renderer fallback.
Registrations are validated fail-fast: one module per type (`DF8108`), an existing bundle (`DF8109`), a route-safe type name (`DF8110`).
## One Auth for the hub
-The hub's **single Auth** is one gate at the shared transport for every frame, built-ins, and the MCP route; one handshake (OTP, magic link, or pre-shared token) unlocks the namespace; `auth: false` disables it for localhost.
+The hub's **single Auth** is one gate at the shared transport for every mounted devframe, built-ins, and the MCP route; one handshake (OTP, magic link, or pre-shared token) unlocks the namespace; `auth: false` disables it for localhost.
## Singular vs hub mounting
@@ -95,20 +95,20 @@ A devframe's SPA and RPC client are byte-identical in both cases; only the envir
| What the SPA / RPC client sees | Singular (`/__git/`) | Hub (`/__devframes/git/`) |
| --- | --- | --- |
| Runtime base | `/__git/` | `/__devframes/git/` (transparent) |
-| `__connection.json` | own meta, own socket | per-frame meta → shared hub socket |
-| RPC registry | this frame's functions | merged: all frames + hub built-ins, cross-frame |
-| Shared state | own context's slots | all frames' slots + hub slots |
+| `__connection.json` | own meta, own socket | per-devframe meta → shared hub socket |
+| RPC registry | this devframe's functions | merged: all mounted devframes + hub built-ins, cross-devframe |
+| Shared state | own context's slots | all mounted devframes' slots + hub slots |
| Auth | own gate, own token | the single hub Auth |
-| Hub subsystems | — | docks, terminals, messages, commands; frame is also an iframe dock |
-| MCP | `__mcp`, this frame's tools | the hub-level aggregate |
+| Hub subsystems | — | docks, terminals, messages, commands; the devframe is also an iframe dock |
+| MCP | `__mcp`, this devframe's tools | the hub-level aggregate |
| Isolation | hard (own context, own transport) | cooperative (shared context) |
## Bring your own context
-Hosts that assemble `createHubContext` + `ctx.install` themselves pass the context instead of a `devframes` list:
+Host frameworks that assemble `createHubContext` + `ctx.install` themselves pass the context instead of a `devframes` list:
```ts
const hub = initHub({ base: DEVFRAMES_HUB_BASE, context: ctx })
```
-It then serves only hub-level endpoints and transport; serve each frame's meta from `hub.connectionMeta()` yourself.
+It then serves only hub-level endpoints and transport; serve each mounted devframe's meta from `hub.connectionMeta()` yourself.
diff --git a/docs/content/1.guide/19.services.md b/docs/content/1.guide/18.services.md
similarity index 69%
rename from docs/content/1.guide/19.services.md
rename to docs/content/1.guide/18.services.md
index fce664b5..7736bb64 100644
--- a/docs/content/1.guide/19.services.md
+++ b/docs/content/1.guide/18.services.md
@@ -1,9 +1,9 @@
---
-title: 'Cross-Plugin Services'
-description: 'ctx.services lets one integration expose a typed, namespaced capability visible to every devframe. Two tiers: in-process services (provide/get) share live objects between plugins; wire services also register RPC and advertise to clients.'
+title: 'Cross-Devframe Services'
+description: 'ctx.services lets one devframe expose a typed, namespaced capability visible to every devframe. Two tiers: in-process services (provide/get) share live objects between devframes; wire services also register RPC and advertise to RPC clients.'
---
-`ctx.services` lets one integration expose a typed, namespaced capability visible to every devframe. Two tiers: in-process services (`provide`/`get`) share live objects between plugins; [wire services](#wire-services) also register RPC and advertise to clients.
+`ctx.services` lets one devframe expose a typed, namespaced capability visible to every devframe. Two tiers: in-process services (`provide`/`get`) share live objects between devframes; [wire services](#wire-services) also register RPC and advertise to RPC clients.
## Providing a service
@@ -25,7 +25,7 @@ export function setup(ctx: DevframeNodeContext) {
}
```
-Service ids prefix the provider's id (`:`), unique per context — a second `provide()` under a taken id throws [`DF0037`](https://devfra.me/errors/DF0037). `provide()` returns a revoke; guard idempotent setup with `has(id)`.
+Service ids prefix the provider's id (`:`), unique per context — a second `provide()` under a taken id throws [`DF0037`](https://devfra.me/errors/DF0037). `provide()` returns a revoke; guard idempotent setup with `has(id)`.
## Consuming a service
@@ -39,13 +39,13 @@ ctx.services.whenAvailable('my-plugin:sources', (sources) => {
Prefer `whenAvailable` over `get`: it fires immediately if provided, else on `provide`, and re-fires on revoke/re-provide. `get(id)` returns the implementation or `undefined` (ids without an augmentation as `unknown`).
-## The host surface
+## The `DevframeServicesHost` API
`DevframeServicesHost` exposes `provide(id, service) => revoke`, `get(id)`, `has(id)`, `whenAvailable(id, cb) => unsubscribe`, and `keys()`; plus a wire tier `install(input, options?) => Promise` and `ready() => Promise`.
## Wire services
-A **wire service** is a shared server-side capability, an npm module.
+A **wire service** is a shared node-side capability, an npm module.
### Shipping one
@@ -71,10 +71,10 @@ Two declaration merges type it: RPC ids into `DevframeRpcServerFunctions`, packa
### Declaring
-Services are **declarative**: a plugin lists what it consumes; a host, shared ones on `initHub`. The adapter resolves each package — for a plugin, **against its own dependencies** via [`importMetaUrl`](/guide/devframe-definition#resolving-against-the-plugins-own-dependencies).
+Services are **declarative**: a devframe lists what it consumes; a hub, shared ones on `initHub`. The adapter resolves each package — for a devframe, **against its own dependencies** via [`importMetaUrl`](/guide/devframe-definition#resolving-against-the-devframes-own-dependencies).
```ts
-// plugin side — on the definition
+// devframe side — on the definition
defineDevframe({
importMetaUrl: import.meta.url, // resolution base for the declared packages
services: [
@@ -84,7 +84,7 @@ defineDevframe({
})
```
-A host lists shared, constructed services on `initHub({ services: [createShikiService(opts)] })`.
+A hub lists shared, constructed services on `initHub({ services: [createShikiService(opts)] })`.
Entries are optional; uninstalled packages are skipped (`has() === false`). Mark one `required: true` to fail hard: [`DF0067`](https://devfra.me/errors/DF0067) on a missing package, [`DF0068`](https://devfra.me/errors/DF0068) on an unsatisfied `version` range (otherwise a range mismatch only warns, [`DF0069`](https://devfra.me/errors/DF0069)).
@@ -94,9 +94,9 @@ The hub constructs every declared service (all devframes plus `initHub`) **once*
For a runtime-only service, `ctx.services.install(input)` builds immediately; re-installing a constructed package returns the existing API, warning [`DF0066`](https://devfra.me/errors/DF0066) if options can't merge.
-### Feature-detecting on the client
+### Feature-detecting on the RPC client
-Installed services are advertised via `devframe:services` [shared state](/guide/shared-state), mirrored on the client's `rpc.services`:
+Installed services are advertised via `devframe:services` [shared state](/guide/shared-state), mirrored on the RPC client's `rpc.services`:
```ts
const rpc = await connectDevframe()
@@ -113,14 +113,14 @@ A reactive UI subscribes via `rpc.services.state()`. `has()`/`get()`/`keys()` ar
**`@devframes/service-open`** (`devframes:service:open`) opens files in an editor (`open-in-editor`, optional `line`/`column`) or OS explorer (`open-in-finder`), refusing paths outside the workspace root plus extra `roots` (`DS_OPEN_0002`), gating editors to `KNOWN_EDITORS`. Options `{ editor?, roots? }` (later wins; dirs union-merged).
-**`@devframes/service-git`** (`devframes:service:git`) runs typed git ops — `status`, `log`, `show`, `readFile`, `diff`, `branches`, `tags`, `stage`, `unstage`, `commit` — on one repo fixed at install (`{ cwd? }`). Write ops are exposed; authorization is the host's boundary.
+**`@devframes/service-git`** (`devframes:service:git`) runs typed git ops — `status`, `log`, `show`, `readFile`, `diff`, `branches`, `tags`, `stage`, `unstage`, `commit` — on one repo fixed at install (`{ cwd? }`). Write ops are exposed; authorization is the host framework's boundary.
-**`@devframes/service-shiki`** (`devframes:service:shiki`) renders [Shiki](https://shiki.style) highlighting server-side via three RPC queries — `highlight` (dual-theme HTML), `code-to-hast`, `code-to-tokens` — all client-`cacheable`, LRU-cached per `(code, lang, themes)`. Options `{ themes?, langs? }` — light/dark pair (defaults `vitesse-light`/`vitesse-dark`; later wins) and preloaded languages (union-merged).
+**`@devframes/service-shiki`** (`devframes:service:shiki`) renders [Shiki](https://shiki.style) highlighting on the node side via three RPC queries — `highlight` (dual-theme HTML), `code-to-hast`, `code-to-tokens` — all client-`cacheable`, LRU-cached per `(code, lang, themes)`. Options `{ themes?, langs? }` — light/dark pair (defaults `vitesse-light`/`vitesse-dark`; later wins) and preloaded languages (union-merged).
## Services, RPC, or shared state?
- **Services** — node-to-node in-process live references, never crossing a wire.
-- **[RPC](/guide/rpc)** — browser-to-node: a client calls a named function.
-- **[Shared state](/guide/shared-state)** — serializable data synced node↔clients.
+- **[RPC](/guide/rpc)** — browser-to-node: an RPC client calls a named function.
+- **[Shared state](/guide/shared-state)** — serializable data synced node side ↔ RPC clients.
-A service serves *other plugins*, RPC *UIs or agents*, a [wire service](#wire-services) both.
+A service serves *other devframes*, RPC *surfaces or coding agents*, a [wire service](#wire-services) both.
diff --git a/docs/content/1.guide/20.deep-linking.md b/docs/content/1.guide/19.deep-linking.md
similarity index 78%
rename from docs/content/1.guide/20.deep-linking.md
rename to docs/content/1.guide/19.deep-linking.md
index f0d680ef..cd594979 100644
--- a/docs/content/1.guide/20.deep-linking.md
+++ b/docs/content/1.guide/19.deep-linking.md
@@ -1,13 +1,13 @@
---
title: 'Deep Linking'
-description: 'Send a user to a view inside a devframe — from another dock, agent, or copied URL — two ways: the hub relays a dock activation to focus a dock in place; a standalone SPA reads its URL hash to restore the view.'
+description: 'Send a user to a view inside a devframe — from another dock, a coding agent, or a copied URL — two ways: the hub relays a dock activation to focus a dock in place; a standalone SPA reads its URL hash to restore the view.'
---
-Send a user to a view inside a devframe — from another dock, agent, or copied URL — two ways: the hub relays a **dock activation** to focus a dock in place; a standalone SPA reads its **URL hash** to restore the view.
+Send a user to a view inside a devframe — from another dock, a coding agent, or a copied URL — two ways: the hub relays a **dock activation** to focus a dock in place; a standalone SPA reads its **URL hash** to restore the view.
## Focusing a dock inside a hub
-The active dock is client-local state in the shell page. A mounted devframe, in its own iframe and RPC client, reaches it via the hub: `hub:docks:activate` switches the active dock, carrying an opaque `params` bag the target reads:
+The active dock is state local to the host page. A mounted devframe, in its own iframe and RPC client, reaches it via the hub: `hub:docks:activate` switches the active dock, carrying an opaque `params` bag the target reads:
```ts
await rpc.call('hub:docks:activate', {
diff --git a/docs/content/1.guide/2.devframe-definition.md b/docs/content/1.guide/2.devframe-definition.md
index 21ffda35..df091cfc 100644
--- a/docs/content/1.guide/2.devframe-definition.md
+++ b/docs/content/1.guide/2.devframe-definition.md
@@ -50,7 +50,7 @@ export default defineDevframe({
| `basePath` | `string` | Optional mount-path override. Default `/` standalone (`cli`/`build`), `/__/` hosted (`vite`/`embedded`). |
| `duplicationStrategy` | `'warn' \| 'silent' \| 'throw' \| 'duplicate'` | Hub reaction when another devframe shares this `id`. Default `'warn'`. See [Hub](/guide/hub); standalone adapters ignore it. |
| `capabilities` | `{ dev?, build? }` | Per-runtime feature flags. `boolean` = whole runtime; object = individual features. |
-| `services` | `DevframeServiceInput[]` | Wire services consumed — descriptors (`{ package, version?, required?, options? }`) imported against the plugin's own deps, or ready definitions. See [Cross-Plugin Services](/guide/services#wire-services). |
+| `services` | `DevframeServiceInput[]` | Wire services consumed — descriptors (`{ package, version?, required?, options? }`) imported against the devframe's own deps, or ready definitions. See [Cross-Devframe Services](/guide/services#wire-services). |
| `clientAssets` | `string \| RemoteAssets` | Built SPA served as the UI — local dist dir or [remote assets](/guide/client-assets). Read by every UI-serving adapter (`dev`, `build`, `vite`, `next`, hub). |
| `rpc` | `{ snapshot?: (string \| { method, inputs })[] }` | RPC config. `rpc.snapshot` opts an RPC this devframe doesn't own into the static dump. Bare method id bakes the no-arg call; `{ method, inputs }` bakes one record per argument-tuple (`inputs` = tuples or async `(ctx) => tuples`). First tuple = fallback. |
| `setup` | `(ctx, info?) => void \| Promise` | **Required.** Server-side entry point, run in every runtime. Optional 2nd arg carries runtime metadata — notably parsed CLI `flags` under `createCac`. |
@@ -75,9 +75,9 @@ export default defineDevframe({
})
```
-### Resolving against the plugin's own dependencies
+### Resolving against the devframe's own dependencies
-`importMetaUrl` resolves companion packages against the plugin's **own** dependencies:
+`importMetaUrl` resolves companion packages against the devframe's **own** dependencies:
```ts
export default defineDevframe({
@@ -147,16 +147,16 @@ interface DevframeNodeContext {
views: DevframeViewHost // static file hosting (`hostStatic`)
diagnostics: DevframeDiagnosticsHost
agent: DevframeAgentHost // expose tools + resources to coding agents
- services: DevframeServicesHost // typed cross-plugin service registry
+ services: DevframeServicesHost // typed cross-devframe service registry
staticConfig: Partial // this context's own ConnectionMeta.configs
scope: (id) => DevframeScopedNodeContext // namespaced view (preferred)
}
```
-### Cross-plugin services
+### Cross-devframe services
-`ctx.services` is a typed, namespaced registry — one integration exposes a capability, others consume it ([Cross-Plugin Services](/guide/services)).
+`ctx.services` is a typed, namespaced registry — one devframe exposes a capability, others consume it ([Cross-Devframe Services](/guide/services)).
```ts
ctx.services.provide('my-plugin:sources', sources)
@@ -192,7 +192,7 @@ ctx.staticConfig['my-plugin'] = { featureFlag: true }
`ctx.scope(id)` returns a namespace-scoped view ([Scoped Context](/guide/scoped-context)) auto-prefixing every RPC id, shared-state key, and streaming channel, plus a persisted `settings` store (`project`/`global` scopes use the matching storage classes).
-Host adapters can augment `ctx` — e.g. the [`vite` adapter](/adapters/vite)'s dock, command, message, and terminal hosts.
+Hosted adapters can augment `ctx` — e.g. the [`vite` adapter](/adapters/vite)'s dock, command, message, and terminal hosts.
## CLI options
@@ -251,12 +251,12 @@ await createCac(devframe).parse()
// 2. Offline snapshot:
await createBuild(devframe, { outDir: 'dist-static' })
-// 3. Mount into a host (Vite DevTools shown — other hosts can implement equivalents):
+// 3. Mount into a host framework (Vite DevTools shown — others can implement equivalents):
export const myPlugin = () => createPluginFromDevframe(devframe)
```
## What's next
- [Adapters](/adapters) — deployment targets
-- [RPC](/guide/rpc) — register server functions
-- [`vite` adapter](/adapters/vite) — mount into a host
+- [RPC](/guide/rpc) — register node-side functions
+- [`vite` adapter](/adapters/vite) — mount into a host framework
diff --git a/docs/content/1.guide/22.build-your-own-json-render-frontend.md b/docs/content/1.guide/20.build-your-own-json-render-frontend.md
similarity index 92%
rename from docs/content/1.guide/22.build-your-own-json-render-frontend.md
rename to docs/content/1.guide/20.build-your-own-json-render-frontend.md
index e9d404df..0a307fae 100644
--- a/docs/content/1.guide/22.build-your-own-json-render-frontend.md
+++ b/docs/content/1.guide/20.build-your-own-json-render-frontend.md
@@ -45,8 +45,8 @@ disabling action dispatch there.
## Plugging it in
-- **Local registration** — a host bundling its client passes
- `createDevframeClientHost({ renderers: { 'json-render': myRenderer } })`;
+- **Local registration** — a hub UI provider bundling its own client runtime passes
+ `createDevframeClientRuntime({ renderers: { 'json-render': myRenderer } })`;
local registrations win over the manifest.
- **A prebuilt renderer module** — bundle your renderer as one self-contained
browser ES module (framework and styles included), default-exporting the
@@ -61,7 +61,7 @@ disabling action dispatch there.
```
Compose it with `initHub({ renderers: [myRenderer()] })`; the hub serves
- the module and viewers import it lazily (see [renderer
+ the module and hub UI providers import it lazily (see [renderer
modules](/guide/hub-initiate#renderer-modules)).
A prebuilt module must be **self-styling and shadow-root-safe**: deliver your
diff --git a/docs/content/1.guide/23.build-your-own-hub-ui.md b/docs/content/1.guide/21.build-your-own-hub-ui.md
similarity index 76%
rename from docs/content/1.guide/23.build-your-own-hub-ui.md
rename to docs/content/1.guide/21.build-your-own-hub-ui.md
index b5b39861..f9de8103 100644
--- a/docs/content/1.guide/23.build-your-own-hub-ui.md
+++ b/docs/content/1.guide/21.build-your-own-hub-ui.md
@@ -1,9 +1,9 @@
---
title: 'Build Your Own Hub UI'
-description: 'A hub viewer implements two contracts — the node-side ui slot and the client-side context. @devframes/hub-ui is the reference.'
+description: 'A hub UI provider implements two contracts — the node-side ui slot and the browser-side context. @devframes/hub-ui is the reference.'
---
-A hub viewer implements two contracts — the node-side `ui` slot and the client-side context. `@devframes/hub-ui` is the reference.
+A hub UI provider implements two contracts — the node-side `ui` slot and the browser-side context. `@devframes/hub-ui` is the reference.
## The node seam: `DevframeHubUi`
@@ -23,12 +23,12 @@ Ship a function returning this object (`createUi()`) with prebuilt assets using
relative paths.
`setup(ctx)` runs once at hub init: config in `ctx.staticConfig` is serialized
-into `ConnectionMeta.configs` and read by the client at handshake.
+into `ConnectionMeta.configs` and read by the RPC client at handshake.
-## The client contracts
+## The browser-side contracts
-A viewer renders from the hub's shared state via
-[`createDevframeClientHost()`](/guide/client-context), which assembles the
+A hub UI provider renders from the hub's shared state via
+[`createDevframeClientRuntime()`](/guide/client-context), which assembles the
`DevframeClientContext` (docks, commands, renderers, when-clauses, connection)
and loads dock client scripts. Honor:
@@ -37,13 +37,13 @@ and loads dock client scripts. Honor:
Render the built-in variants of the open dock union
(`DevframeDockEntryRegistry`, `@devframes/hub/types`):
-| Type | The viewer renders |
+| Type | The hub UI provider renders |
|---|---|
| `iframe` | the entry's `url` in a kept-alive iframe (per `frameId` when shared); honor `subTabs` soft nav |
-| `action` | a bar button; activating runs its client script |
+| `action` | a dock-rail button; activating runs its client script |
| `custom-render` | a container its client script mounts into |
| `launcher` | a launch call-to-action reflecting `launcher.status` |
-| `group` | one bar button collapsing its member entries |
+| `group` | one dock-rail button collapsing its member entries |
| `~builtin` | your native views (settings, feeds) for reserved ids |
Honor `when` / `visibility`, `category` grouping (order from
@@ -53,7 +53,7 @@ Honor `when` / `visibility`, `category` grouping (order from
An `iframe` entry serving a [remote assets package](/guide/client-assets) can
report it unreachable: its fallback page posts a `RemoteAssetsErrorMessage`
(`DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE`, `@devframes/hub/constants`) to
-`window.parent`. Match it against the frame's `contentWindow` to offer install + retry.
+`window.parent`. Match it against the iframe's `contentWindow` to offer install + retry.
### The renderer registry and its fallback
@@ -89,7 +89,7 @@ Renderer modules self-style (sometimes via a shadow root). Keep a live
## Reference points
-- `packages/hub-ui` — the full reference viewer (Vue, `@antfu/design`).
+- `packages/hub-ui` — the full reference hub UI provider (Vue, `@antfu/design`).
- [`examples/hub-vite`](https://github.com/devframes/devframe/tree/main/examples/hub-vite) and
[`examples/hub-next`](https://github.com/devframes/devframe/tree/main/examples/hub-next) — hand-rolled
- viewers in vanilla DOM and React.
+ hub UI providers in vanilla DOM and React.
diff --git a/docs/content/1.guide/24.built-with.md b/docs/content/1.guide/22.built-with.md
similarity index 85%
rename from docs/content/1.guide/24.built-with.md
rename to docs/content/1.guide/22.built-with.md
index abad99cb..ec927a7b 100644
--- a/docs/content/1.guide/24.built-with.md
+++ b/docs/content/1.guide/22.built-with.md
@@ -1,6 +1,6 @@
---
title: 'Built with Devframe'
-description: 'Real-world devtools and hosts built on devframe — from Vite DevTools to ESLint Config Inspector.'
+description: 'Real-world devtools and hub UI providers built on devframe — from Vite DevTools to ESLint Config Inspector.'
---
## Real-world DevTools
@@ -9,14 +9,14 @@ description: 'Real-world devtools and hosts built on devframe — from Vite DevT
- [**ESLint Config Inspector**](https://github.com/eslint/config-inspector) — inspects flat configs.
- [**node-modules-inspector**](https://github.com/antfu/node-modules-inspector) — visualizes your `node_modules` dependency graph.
-## Builtin Plugins
+## Built-in Devframes
-The [built-in plugins](/plugins) are real tools built on Devframe, each in a different UI framework:
+The [built-in devframes](/plugins) are real tools built on Devframe, each in a different UI framework:
-| Plugin | UI framework | What it does |
+| Devframe | UI framework | What it does |
|--------|--------------|--------------|
| [Data Inspector](/plugins/data-inspector) | Vue | Query live server-side objects with jora. |
-| [Devframe Inspector](/plugins/inspect) | Vue | Browse RPC, shared state, and agent surface. |
+| [Devframe Inspector](/plugins/inspect) | Vue | Browse RPC, shared state, and the agent-consumable API. |
| [Open Graph Viewer](/plugins/og) | Vue | Inspect Open Graph / Twitter metadata and card previews. |
| [Accessibility Inspector](/plugins/a11y) | Solid | Run axe-core; list WCAG violations. |
| [Git](/plugins/git) | React (Next.js) | Repository dashboard: status, graph, branches, diffs. |
@@ -26,7 +26,7 @@ The [built-in plugins](/plugins) are real tools built on Devframe, each in a dif
## Playable Examples
-Runnable apps in the [repository](https://github.com/devframes/devframe/tree/main/examples), each a single node-side definition behind a different UI. Run one with:
+Runnable examples in the [repository](https://github.com/devframes/devframe/tree/main/examples), each a single node-side definition behind a different UI. Run one with:
```sh
pnpm install
@@ -39,12 +39,12 @@ pnpm --filter dev
| [json-render](https://github.com/devframes/devframe/tree/main/examples/json-render) | Vue | Server-authored view via `@devframes/json-render-ui`; live state + action bridge. |
| [streaming-chat](https://github.com/devframes/devframe/tree/main/examples/streaming-chat) | Preact | Streams tokens; history in shared state. |
| [next-runtime-snapshot](https://github.com/devframes/devframe/tree/main/examples/next-runtime-snapshot) | React (Next.js) | App Router SPA surfacing the Node runtime. |
-| [hub-vite](https://github.com/devframes/devframe/tree/main/examples/hub-vite) | Vanilla TS (Vite) | ~120-line Vite host wiring `@devframes/hub`; hand-built viewer. |
+| [hub-vite](https://github.com/devframes/devframe/tree/main/examples/hub-vite) | Vanilla TS (Vite) | ~120-line Vite host wiring `@devframes/hub`; hand-built hub UI provider. |
| [hub-next](https://github.com/devframes/devframe/tree/main/examples/hub-next) | React (Next.js) | Same protocol, Next.js route. |
The **minimal** family mounts `initHub({ ui: createUi() })` with `@devframes/hub-ui`:
-| Example | Host | Shows |
+| Example | Host framework | Shows |
|---------|------|-------|
| [hub-vite-minimal](https://github.com/devframes/devframe/tree/main/examples/hub-vite-minimal) | Vite | Dev middleware. |
| [hub-next-minimal](https://github.com/devframes/devframe/tree/main/examples/hub-next-minimal) | Next.js | App Router route. |
diff --git a/docs/content/1.guide/3.rpc.md b/docs/content/1.guide/3.rpc.md
index 467e05f0..823d6e60 100644
--- a/docs/content/1.guide/3.rpc.md
+++ b/docs/content/1.guide/3.rpc.md
@@ -64,7 +64,7 @@ Use `setup(ctx)` (returns `{ handler, dump? }`) when the handler needs `Devframe
## Broadcasting
-`rpc.broadcast` sends to every connected client; a scoped context namespaces the method:
+`rpc.broadcast` sends to every connected RPC client; a scoped context namespaces the method:
```ts
defineDevframe({
@@ -84,15 +84,15 @@ defineDevframe({
| Option | Type | Description |
|--------|------|-------------|
-| `method` | client RPC name | Client-side function to call. |
-| `args` | any[] | Arguments for the client function. |
-| `optional` | `boolean` | Don't throw if no client is listening. |
+| `method` | browser-side RPC name | Browser-side function to call. |
+| `args` | any[] | Arguments for the browser-side function. |
+| `optional` | `boolean` | Don't throw if no RPC client is listening. |
| `event` | `boolean` | Fire-and-forget. |
-| `filter` | `(client) => boolean` | Skip specific clients. |
+| `filter` | `(client) => boolean` | Skip specific RPC clients. |
## Streaming
-For server→client chunk feeds, use [streaming channels](/guide/streaming):
+For node-side→browser-side chunk feeds, use [streaming channels](/guide/streaming):
```ts
const channel = ctx.rpc.streaming.create('my-devframe:chat', {
@@ -104,7 +104,7 @@ sourceReadable.pipeTo(stream.writable)
## Local invocation
-A scoped `rpc.call` invokes a server function directly, skipping the transport:
+A scoped `rpc.call` invokes a node-side function directly, skipping the transport:
```ts
const my = ctx.scope('my-devframe')
@@ -113,9 +113,9 @@ const modules = await my.rpc.call('get-modules', { limit: 10 })
It wraps `ctx.rpc.invokeLocal(...)`; a fully-qualified name (with `:`) calls another tool's function.
-## Client-side calls
+## Browser-side calls
-From the browser, [`connectDevframe`](/guide/client) (or `getDevframeRpcClient`) returns a client:
+From the browser, [`connectDevframe`](/guide/client) (or `getDevframeRpcClient`) returns an RPC client:
```ts
@@ -127,11 +127,11 @@ const my = client.scope('my-devframe')
const modules = await my.rpc.call('get-modules', { limit: 10 })
```
-Client-side registration (server→client) uses `my.rpc.register()`.
+Browser-side registration (node side → browser side) uses `my.rpc.register()`.
-## Type-safe client registry
+## Type-safe RPC-client registry
-Two augmentable interfaces — `DevframeRpcServerFunctions` (client→server) and `DevframeRpcClientFunctions` (server→client) — type each registered name on the client via `declare module 'devframe'`. Feed a const array through `RpcDefinitionsToFunctionsWithNamespace`, which prefixes each bare name with your id:
+Two augmentable interfaces — `DevframeRpcServerFunctions` (client→server) and `DevframeRpcClientFunctions` (server→client) — type each registered name on the RPC client via `declare module 'devframe'`. Feed a const array through `RpcDefinitionsToFunctionsWithNamespace`, which prefixes each bare name with your id:
```ts
import type { RpcDefinitionsToFunctionsWithNamespace } from 'devframe/rpc'
@@ -181,7 +181,7 @@ defineRpcFunction({
})
```
-Static clients resolve from the baked dump; unmatched arguments hit `dump.fallback` (or throw).
+Static RPC clients resolve from the baked dump; unmatched arguments hit `dump.fallback` (or throw).
## JSON-serializable declaration
@@ -196,7 +196,7 @@ When every function is JSON-flagged, the wire stays plain JSON. A `jsonSerializa
## Agent exposure
-Add an `agent` field to surface the function to agents over MCP:
+Add an `agent` field to expose the function to coding agents over MCP:
```ts
defineRpcFunction({
@@ -220,6 +220,6 @@ Exposing a function over MCP requires `jsonSerializable: true`.
## What's next
-- [Shared State](/guide/shared-state) — state synced across clients
+- [Shared State](/guide/shared-state) — state synced across RPC clients
- [Client](/guide/client) — connecting from the browser
-- [Agent-Native](/guide/agent-native) — exposing RPCs to agents
+- [Agent-Native](/guide/agent-native) — exposing RPCs to coding agents
diff --git a/docs/content/1.guide/4.shared-state.md b/docs/content/1.guide/4.shared-state.md
index 397708ad..5f9e0cca 100644
--- a/docs/content/1.guide/4.shared-state.md
+++ b/docs/content/1.guide/4.shared-state.md
@@ -1,21 +1,21 @@
---
title: 'Shared State'
-description: 'Shared state is observable, immutable-by-default state synced between server and every client, surviving reconnects — a new client gets the snapshot.'
+description: 'Shared state is observable, immutable-by-default state synced between the node side and every RPC client, surviving reconnects — a new RPC client gets the snapshot.'
---
-Shared state is observable, immutable-by-default state synced between server and every client, surviving reconnects — a new client gets the snapshot.
+Shared state is observable, immutable-by-default state synced between the node side and every RPC client, surviving reconnects — a new RPC client gets the snapshot.
## Overview
```mermaid
flowchart LR
- subgraph ClientA["Client A"]
+ subgraph ClientA["RPC client A"]
A["state.value()"]
end
- subgraph Server["Server"]
+ subgraph Server["Node side"]
S["state.mutate(fn)"]
end
- subgraph ClientB["Client B"]
+ subgraph ClientB["RPC client B"]
B["state.value()"]
end
S <-->|RPC sync| A
@@ -70,7 +70,7 @@ state.mutate((draft) => {
})
```
-Devframe applies the recipe to a draft, emits `updated` (with `SharedStatePatch[]` if enabled), and broadcasts to clients; a `syncIds` set keeps mutations idempotent on replay.
+Devframe applies the recipe to a draft, emits `updated` (with `SharedStatePatch[]` if enabled), and broadcasts to RPC clients; a `syncIds` set keeps mutations idempotent on replay.
## Patches (advanced)
@@ -94,7 +94,7 @@ state.on('updated', (fullState, patches, syncId) => {
## Client-side access
-The same key is on the browser RPC client, scoped identically; client mutations round-trip the server, keeping `state.value()` authoritative.
+The same key is on the browser RPC client, scoped identically; browser-side mutations round-trip the node side, keeping `state.value()` authoritative.
```ts
import { connectDevframe } from 'devframe/client'
@@ -112,7 +112,7 @@ state.mutate((draft) => {
## Enumerating keys
-Both hosts expose `keys()` and `onKeyAdded`:
+Both sides expose `keys()` and `onKeyAdded`:
```ts
for (const key of ctx.rpc.sharedState.keys()) {
@@ -146,7 +146,7 @@ declare module 'devframe' {
| Use shared state for | Use RPC for |
|----------------------|-------------|
| Long-lived UI state (selections, filters, expanded nodes) | One-shot queries (`get-modules`, `read-file`) |
-| Cross-client coordination | Commands / actions with side effects |
+| Coordination across RPC clients | Commands / actions with side effects |
| Data that should reappear after reconnect | Event streams (prefer `broadcast` / `callEvent`) |
For actions and events, use `ctx.rpc.register` + `broadcast` ([RPC](/guide/rpc)).
diff --git a/docs/content/1.guide/5.streaming.md b/docs/content/1.guide/5.streaming.md
index 695c4263..90d559ad 100644
--- a/docs/content/1.guide/5.streaming.md
+++ b/docs/content/1.guide/5.streaming.md
@@ -1,15 +1,15 @@
---
title: 'Streaming'
-description: 'Streaming channels push chunk-style data server→client over the RPC socket.'
+description: 'Streaming channels push chunk-style data from the node side to the browser side over the RPC socket.'
---
-Streaming channels push chunk-style data server→client over the RPC socket.
+Streaming channels push chunk-style data from the node side to the browser side over the RPC socket.
## Overview
```mermaid
sequenceDiagram
- participant Producer as Producer (server)
+ participant Producer as Producer (node side)
participant Channel as ctx.rpc.streaming
channel
participant Browser as Subscriber (browser)
@@ -59,7 +59,7 @@ export default defineDevframe({
})
```
-## Producing — three surfaces, one stream
+## Producing — three APIs, one stream
```ts
const stream = channel.start({ id: 'optional-explicit-id' })
@@ -69,7 +69,7 @@ stream.write(chunk)
stream.error(err) // terminal failure
stream.close() // terminal success
stream.signal // AbortSignal — flips when consumers cancel
-stream.id // string — what clients subscribe to
+stream.id // string — what RPC clients subscribe to
// Web Streams — pipe any ReadableStream in:
sourceReadable.pipeTo(stream.writable, { signal: stream.signal })
@@ -113,24 +113,24 @@ for await (const token of reader)
// Or pipe to a DOM-side WritableStream
await reader.readable.pipeTo(downloadWritable)
-reader.cancel() // sends cancel upstream; server stream.signal flips
+reader.cancel() // sends cancel upstream; the node-side stream.signal flips
```
## Lifecycle and cancellation
-| Event | Server | Client |
+| Event | Node side | Browser side |
|-------|--------|--------|
| `stream.close()` / `stream.error(err)` | broadcasts `end` | `for await` resolves or throws |
| `reader.cancel()` | aborts `stream.signal` on **last**-subscriber cancel | `for await` ends |
| WS disconnects | aborts `stream.signal` on **last**-subscriber drop | reader survives, resubscribes on re-trust |
| `chat` panel closes | cancels upstream | — |
-## Client-to-server uploads
+## Browser-to-node uploads
In reverse: an RPC call allocates the id; events carry chunks.
```ts
-// Server — typically inside an action handler
+// Node side — typically inside an action handler
ctx.rpc.register(defineRpcFunction({
name: 'my-devframe:upload-file',
type: 'action',
@@ -140,7 +140,7 @@ ctx.rpc.register(defineRpcFunction({
const reader = channel.openInbound()
// Process chunks asynchronously — the action returns immediately
- // so the client can start uploading.
+ // so the browser side can start uploading.
;(async () => {
const file = createWriteStream(name)
for await (const chunk of reader)
@@ -154,7 +154,7 @@ ctx.rpc.register(defineRpcFunction({
```
```ts
-// Client
+// Browser side
const { uploadId } = await my.rpc.call('upload-file', {
name: 'capture.bin',
})
@@ -169,11 +169,11 @@ upload.close()
fileReadable.pipeTo(upload.writable, { signal: upload.signal })
```
-Lifecycle mirrors outbound: `upload.signal` aborts on `reader.cancel()` (broadcasting `upload-cancel`), `upload.error(err)` throws inside its `for await`, and a client disconnect exits with `UploadDisconnected`. Each `openInbound()` id is point-to-point — one producer, no fan-in or replay.
+Lifecycle mirrors outbound: `upload.signal` aborts on `reader.cancel()` (broadcasting `upload-cancel`), `upload.error(err)` throws inside its `for await`, and an RPC-client disconnect exits with `UploadDisconnected`. Each `openInbound()` id is point-to-point — one producer, no fan-in or replay.
## Replay on reconnect
-With `replayWindow: N`, the server keeps the last `N` chunks; a resubscribing client sends its highest seen sequence and the server replays newer ones.
+With `replayWindow: N`, the node side keeps the last `N` chunks; a resubscribing RPC client sends its highest seen sequence and the node side replays newer ones.
```ts
my.rpc.streaming.create('chat', { // -> my-devframe:chat
@@ -186,7 +186,7 @@ my.rpc.streaming.create('chat', { // -> my-devframe:chat
## Backpressure
-The client keeps a bounded queue per subscription (`highWaterMark`, default 256); when the consumer falls behind, the oldest chunk drops, logging [`DF0029`](/errors/DF0029).
+The RPC client keeps a bounded queue per subscription (`highWaterMark`, default 256); when the consumer falls behind, the oldest chunk drops, logging [`DF0029`](/errors/DF0029).
```ts
const reader = my.rpc.streaming.subscribe('chat', id, { // -> my-devframe:chat
@@ -201,7 +201,7 @@ const reader = my.rpc.streaming.subscribe('chat', id, { // -> my-devframe:chat
| Token/chunk feeds (LLM deltas, logs) | Payload-less notifications (`refresh`, `clear`) | Long-lived UI state |
| Per-call lifecycles, cancellation | Cross-cutting signals | Snapshots surviving reconnect |
| Replay on reconnect | Fire-and-forget | Diff-based sync |
-| Client→server uploads (files, mic) | | |
+| Browser→node uploads (files, mic) | | |
## Reference
diff --git a/docs/content/1.guide/6.client-assets.md b/docs/content/1.guide/6.client-assets.md
index cdc0206a..4cf2fd1e 100644
--- a/docs/content/1.guide/6.client-assets.md
+++ b/docs/content/1.guide/6.client-assets.md
@@ -84,7 +84,7 @@ export default defineDevframe({
})
```
-The definition's [`importMetaUrl`](/guide/devframe-definition#resolving-against-the-plugins-own-dependencies) is the resolution base.
+The definition's [`importMetaUrl`](/guide/devframe-definition#resolving-against-the-devframes-own-dependencies) is the resolution base.
### How assets resolve
@@ -119,7 +119,7 @@ npm install @acme/my-tool-assets
A file absent from local install and cache, with the provider unreachable, raises [`DF0060`](/errors/DF0060); an HTML navigation gets a self-contained error page.
-It also posts the failure to `window.parent` (`DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE` from `devframe/constants`, payload `RemoteAssetsErrorMessage`) for an embedding viewer like [`@devframes/hub-ui`](/guide/build-your-own-hub-ui).
+It also posts the failure to `window.parent` (`DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE` from `devframe/constants`, payload `RemoteAssetsErrorMessage`) for an embedding hub UI provider like [`@devframes/hub-ui`](/guide/build-your-own-hub-ui).
### Custom provider
diff --git a/docs/content/1.guide/7.scoped-context.md b/docs/content/1.guide/7.scoped-context.md
index 06ef43f0..4913a846 100644
--- a/docs/content/1.guide/7.scoped-context.md
+++ b/docs/content/1.guide/7.scoped-context.md
@@ -5,7 +5,7 @@ description: 'A scoped context is a namespaced view of the context: it auto-pref
A scoped context is a namespaced view of the context: it auto-prefixes every RPC id, shared-state key, and streaming channel with your tool's id, and adds a typed, persisted `settings` store, used from a single tool's code.
-## Server side
+## Node side
`setup(ctx)` receives the full `DevframeNodeContext`; scope it with `ctx.scope(id)`, conventionally your devframe `id`:
@@ -29,9 +29,9 @@ export default defineDevframe({
declare function loadModules(): { id: string }[]
```
-`ctx.scope(id)` is stable per id, re-exposing unscoped surfaces (`views`, `diagnostics`, `agent`, `host`, `cwd`, `mode`), swapping in the auto-namespaced `rpc`, and keeping the original as `my.base`.
+`ctx.scope(id)` is stable per id, re-exposing unscoped APIs (`views`, `diagnostics`, `agent`, `host`, `cwd`, `mode`), swapping in the auto-namespaced `rpc`, and keeping the original as `my.base`.
-## Client side
+## Browser side
`(await connectDevframe()).scope(id)` gives the matching view: `my.rpc` carries `call` / `callEvent` / `callOptional`, `register`, `sharedState`, and `streaming`, plus `my.settings`.
@@ -41,14 +41,14 @@ Bare names are prefixed `:` (`call('get-modules')` → `my-plugin:get
`register` accepts only bare names; an already-namespaced one throws [`DF0034`](/errors/DF0034) — use `ctx.base.rpc.register`.
-Bare names stay typed: `call('get-modules')` resolves to your [RPC registry](/guide/rpc#type-safe-client-registry) entry, `sharedState('selection')` to the matching [`DevframeRpcSharedStates`](/guide/shared-state#type-safe-keys) key.
+Bare names stay typed: `call('get-modules')` resolves to your [RPC registry](/guide/rpc#type-safe-rpc-client-registry) entry, `sharedState('selection')` to the matching [`DevframeRpcSharedStates`](/guide/shared-state#type-safe-keys) key.
## Settings
`my.settings` is a persisted key-value store (alongside `my.rpc`), with two scopes:
-- **`project`** — per-workspace values, under the host's `workspace` dir.
-- **`global`** — per-user values, under the host's `global` dir.
+- **`project`** — per-checkout values, under the `workspace` storage dir.
+- **`global`** — per-user values, under the `global` storage dir.
Both are file-backed and synced to the browser over the shared-state protocol; a `set` propagates to peers, surviving restarts.
diff --git a/docs/content/1.guide/8.json-render.md b/docs/content/1.guide/8.json-render.md
index ab0b07e2..ab8b6fb0 100644
--- a/docs/content/1.guide/8.json-render.md
+++ b/docs/content/1.guide/8.json-render.md
@@ -1,10 +1,10 @@
---
title: 'JSON-Render'
-description: 'JSON-render describes a UI as data — a serializable component spec any frontend renders. Opt-in: a plain app pulls zero JSON-render dependencies. Two packages:'
+description: 'JSON-render describes a UI as data — a serializable component spec any frontend renders. Opt-in: a plain devframe pulls zero JSON-render dependencies. Two packages:'
---
JSON-render describes a UI as **data** — a serializable component spec any
-frontend renders. **Opt-in**: a plain app pulls zero JSON-render
+frontend renders. **Opt-in**: a plain devframe pulls zero JSON-render
dependencies. Two packages:
- **`@devframes/json-render`** — framework-neutral protocol layer (spec/catalog
@@ -58,8 +58,8 @@ Catalog v1 ships fourteen components — `Stack`, `Card`, `Text`, `Badge`,
`Button`, `Icon`, `Divider`, `TextInput`, `Switch`, `KeyValueTable`,
`DataTable`, `CodeBlock`, `Progress`, `Tree`. A spec **is** an `@json-render/core`
`Spec` plus a per-component Zod prop schema (`basePropSchemas`), validated at
-ingress (server) and render time (client); `$state` / `$bindState` bindings work
-on scalar props.
+ingress (node side) and render time (browser side); `$state` / `$bindState`
+bindings work on scalar props.
## Actions and state
@@ -116,7 +116,7 @@ state and bindings still work.
`@devframes/json-render/hub` adds a `json-render` dock type:
```ts
-// server — register a dock carrying the view's serializable reference,
+// node side — register a dock entry carrying the view's serializable reference,
// and compose the frontend as a prebuilt renderer module
import { jsonRenderUiRenderer } from '@devframes/json-render-ui/hub'
import { toJsonRenderDockEntry } from '@devframes/json-render/hub'
@@ -141,20 +141,20 @@ missing-renderer fallback). A host page can register a
```ts
// host page — a locally-bundled frontend wins over the manifest module
-import { createDevframeClientHost } from '@devframes/hub/client'
+import { createDevframeClientRuntime } from '@devframes/hub/client'
import { myJsonRenderDockRenderer } from './my-renderer'
-const host = await createDevframeClientHost({
+const host = await createDevframeClientRuntime({
renderers: { 'json-render': myJsonRenderDockRenderer },
})
-// the viewer mounts the active dock into a container it owns
+// the hub UI provider mounts the active dock entry into a container it owns
const result = await host.context.renderers.mount(entry, container)
if (result.status === 'mounted')
- result.dispose // tear down when the viewer decides; deactivation disposes too
+ result.dispose // tear down when the hub UI provider decides; deactivation disposes too
```
-The dock carries a serializable `JsonRenderViewRef` in two shapes: `{ stateKey }`
+The dock entry carries a serializable `JsonRenderViewRef` in two shapes: `{ stateKey }`
points at live shared state (`createJsonRenderView`); `{ spec }` embeds it inline
for a browser-synthesized [client-only dock](/guide/client-context#client-only-docks).
diff --git a/docs/content/1.guide/9.diagnostics.md b/docs/content/1.guide/9.diagnostics.md
index 00f24832..8a0c185a 100644
--- a/docs/content/1.guide/9.diagnostics.md
+++ b/docs/content/1.guide/9.diagnostics.md
@@ -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
@@ -20,12 +20,12 @@ interface DevframeDiagnosticsHost {
/** Register additional diagnostic definitions. */
register: (definitions: Record) => 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
@@ -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()
},
},
diff --git a/docs/content/1.guide/index.md b/docs/content/1.guide/index.md
index 8e9ed99b..18c9db10 100644
--- a/docs/content/1.guide/index.md
+++ b/docs/content/1.guide/index.md
@@ -1,19 +1,19 @@
---
title: 'Introduction'
-description: 'Devframe is a framework-neutral foundation for building a devtool once and running it everywhere — inside any host, as a standalone app, or through a coding agent. A devtool here is anything that makes a program''s implicit state visible and interactive: an inspector, a build or bundle analyzer,…'
+description: 'Devframe is a framework-neutral foundation for building a devtool once and running it everywhere — inside any host framework, as a standalone tool, or through a coding agent. A devtool here is anything that makes a program''s implicit state visible and interactive: an inspector, a build or bundle analyzer,…'
---
-**Devframe is a framework-neutral foundation for building a devtool once and running it everywhere — inside any host, as a standalone app, or through a coding agent.** A devtool here is anything that makes a program's implicit state visible and interactive: an inspector, a build or bundle analyzer, an asset viewer, a state or data explorer, a terminal. You describe such a tool one time, and the same definition mounts almost anywhere. Think of it as [`unplugin`](https://unplugin.unjs.io/) for devtools.
+**Devframe is a framework-neutral foundation for building a devtool once and running it everywhere — inside any host framework, as a standalone tool, or through a coding agent.** A devtool here is anything that makes a program's implicit state visible and interactive: an inspector, a build or bundle analyzer, an asset viewer, a state or data explorer, a terminal. You describe such a tool one time, and the same definition mounts almost anywhere. Think of it as [`unplugin`](https://unplugin.unjs.io/) for devtools.
## Why it exists
-Most devtools rebuild the same plumbing — server–client communication, state synchronization, serialization, static-asset hosting, a web interface — and wire it to one framework's dev server. The same idea then gets rebuilt, slightly differently, for the next framework, so effort fragments across the ecosystem instead of compounding.
+Most devtools rebuild the same plumbing — node–browser communication, state synchronization, serialization, static-asset hosting, a web interface — and wire it to one framework's dev server. The same idea then gets rebuilt, slightly differently, for the next framework, so effort fragments across the ecosystem instead of compounding.
-Devframe moves that boundary. A capability is defined once against a stable interface and runs on every supported host, so a good tool can be built once, travel further, and improve through the work of more communities.
+Devframe moves that boundary. A capability is defined once against a stable interface and runs on every supported host framework, so a good tool can be built once, travel further, and improve through the work of more communities.
## Who it's for
-- **Devtool authors** who want one tool to run standalone, embed in a host, ship as a CLI or static report, and answer to an agent — without maintaining a separate version per environment.
+- **Devtool authors** who want one tool to run standalone, embed in a host framework, ship as a CLI or static report, and answer to a coding agent — without maintaining a separate version per environment.
- **Framework and build-tool teams** who want to offer devtools without rebuilding shared infrastructure, and to inherit capabilities other communities already built.
- **Anyone** who wants a tool's state and actions available to both a human UI and a coding agent from one source of truth.
@@ -52,7 +52,7 @@ devtools.nodeMiddleware
// (req, res, next) => void — for Connect-style servers (Vite, Rsbuild)
```
-The handler serves the web interface, connection metadata, live RPC, authentication, and optional MCP endpoint under one namespace. Hono and Nitro take Web Standard requests directly; Next.js and SvelteKit expose route handlers; Vite and Rsbuild accept its `nodeMiddleware`. The live RPC connection attaches via a shared HTTP server, upgrade events, or side-car, advertised through `__connection.json`. See [The Standard Handler](/adapters/initiate).
+The handler serves the web interface, connection metadata, live RPC, authentication, and optional MCP endpoint under one namespace. Hono and Nitro take Web Standard requests directly; Next.js and SvelteKit expose route handlers; Vite and Rsbuild accept its `nodeMiddleware`. The live RPC connection attaches via a shared HTTP server, upgrade events, or a side-car server, advertised through `__connection.json`. See [The Standard Handler](/adapters/initiate).
## Adapters as conveniences
@@ -76,7 +76,7 @@ export const buildReport = () => createBuild(devframe, { outDir: 'dist-static' }
## Visual and agentic
-One source of truth feeds a visual panel and programmatic consumers. RPC functions stay private by default and opt into agent exposure explicitly: the [MCP adapter](/adapters/mcp) translates functions, readable resources, and selected shared state into an agent-consumable surface. See [Agent-Native](/guide/agent-native).
+One source of truth feeds a visual panel and programmatic consumers. RPC functions stay private by default and opt into agent exposure explicitly: the [MCP adapter](/adapters/mcp) translates functions, readable resources, and selected shared state into an agent-consumable interface. See [Agent-Native](/guide/agent-native).
## From one devframe to a hub
@@ -98,11 +98,11 @@ hub.handler
// the whole devtools collection as Request → Response
```
-The mounted devframes share one RPC registry, state store, connection, auth gate, and optional aggregate MCP endpoint. The hub is headless: [`@devframes/hub-ui`](/guide/build-your-own-hub-ui) is a reference interface a product can replace.
+The mounted devframes share one RPC registry, state store, connection, auth gate, and optional aggregate MCP endpoint. The hub is headless: [`@devframes/hub-ui`](/guide/build-your-own-hub-ui) is a reference hub UI provider a product can replace.
## Inheriting the ecosystem
-[Vite DevTools](https://devtools.vite.dev/) is the first flagship host, using `initHub()` alongside its own Vite, Rolldown, Vitest, and Oxc tooling. The [framework packages](/frameworks) — [`@devframes/vite`](/frameworks/vite), [`@devframes/nuxt`](/frameworks/nuxt), [`@devframes/next`](/frameworks/next) — add conventions over the same handler. See [Built with Devframe](/guide/built-with).
+[Vite DevTools](https://devtools.vite.dev/) is the first flagship hub UI provider, using `initHub()` alongside its own Vite, Rolldown, Vitest, and Oxc tooling. The [framework kits](/frameworks) — [`@devframes/vite`](/frameworks/vite), [`@devframes/nuxt`](/frameworks/nuxt), [`@devframes/next`](/frameworks/next) — add conventions over the same handler. See [Built with Devframe](/guide/built-with).
## Install
@@ -150,7 +150,7 @@ node ./my-devframe.js build # self-contained static deploy in dist-static/
node ./my-devframe.js mcp # stdio MCP server
```
-The CLI adapter serves the SPA at `/`; embedded in a host (`vite`, `embedded`) the default becomes `/__my-devframe/`. Override via `defineDevframe({ basePath })`.
+The CLI adapter serves the SPA at `/`; embedded in a host framework (`vite`, `embedded`) the default becomes `/__my-devframe/`. Override via `defineDevframe({ basePath })`.
## What Devframe provides
@@ -158,19 +158,19 @@ The CLI adapter serves the SPA at `/`; embedded in a host (`vite`, `embedded`) t
|-----------|--------------|
| **[Devframe Definition](/guide/devframe-definition)** | One `defineDevframe` call describes your tool; adapters deploy it anywhere. |
| **[RPC](/guide/rpc)** | Type-safe bidirectional calls on birpc, validated against any Standard Schema validator. `query`, `static`, `action`, `event` types. |
-| **[Shared State](/guide/shared-state)** | Observable, patch-synced state surviving reconnects, server ↔ browser. |
+| **[Shared State](/guide/shared-state)** | Observable, patch-synced state surviving reconnects, node side ↔ browser side. |
| **[JSON-Render](/guide/json-render)** | Opt-in data-driven UI — a serializable view spec, rendered standalone or in a hub dock. |
-| **[Diagnostics](/guide/diagnostics)** | Coded warnings/errors via `nostics`, in the host's shared lookup. |
+| **[Diagnostics](/guide/diagnostics)** | Coded warnings/errors via `nostics`, in the host framework's shared lookup. |
| **[Streaming](/guide/streaming)** | One-way (RPC streaming) and two-way (uploads) channel primitives. |
-| **[When Clauses](/guide/when-clauses)** | VS Code-style conditional expressions for docks, commands, and custom UI. |
+| **[When Clauses](/references/when-clauses)** | VS Code-style conditional expressions for docks, commands, and custom UI. |
| **[The Standard Handler](/adapters/initiate)** | `initDevframe()` — the Web Standard `Request → Response` boundary. |
| **[Client](/guide/client)** | Browser RPC client (`connectDevframe`), auto-auth, WebSocket / static modes. |
-| **[Agent-Native](/guide/agent-native)** | Opt-in exposure of your tool's surface to coding agents over MCP. |
+| **[Agent-Native](/guide/agent-native)** | Opt-in exposure of your tool's capabilities to coding agents over MCP. |
## What's next
- [Tutorial: Build a Server Data Inspector](/guide/tutorial-server-data-inspector) — go from an empty folder to a shippable devtool, one capability at a time
- [Devframe Definition](/guide/devframe-definition) — `defineDevframe` and `DevframeNodeContext`
-- [The Standard Handler](/adapters/initiate) — mount into any host
+- [The Standard Handler](/adapters/initiate) — mount into any host framework
- [Adapters](/adapters) — convenience entry points
- [Hub](/guide/hub) — compose many devframes
diff --git a/docs/content/2.adapters/1.initiate.md b/docs/content/2.adapters/1.initiate.md
index 50c0c554..a6d6a729 100644
--- a/docs/content/2.adapters/1.initiate.md
+++ b/docs/content/2.adapters/1.initiate.md
@@ -1,9 +1,9 @@
---
title: 'The Standard Handler'
-description: 'initDevframe() turns a DevframeDefinition into a live instance whose .handler — a Web Standard (request: Request) => Promise — carries the entire surface (SPA, __connection.json discovery, RPC socket, auth gate, MCP route) under one mount base. Every other serving path — adapters,…'
+description: 'initDevframe() turns a DevframeDefinition into a running devframe whose .handler — a Web Standard (request: Request) => Promise — carries everything a devframe serves (SPA, __connection.json discovery, RPC socket, auth gate, MCP route) under one mount base. Every other serving path — adapters,…'
---
-`initDevframe()` turns a `DevframeDefinition` into a live instance whose `.handler` — a Web Standard `(request: Request) => Promise` — carries the entire surface (SPA, `__connection.json` discovery, RPC socket, auth gate, MCP route) under one mount base. Every other serving path — [adapters](/adapters), [framework packages](/frameworks), [hub](/guide/hub-initiate) — is assembled from it. Mount it with a catch-all route.
+`initDevframe()` turns a `DevframeDefinition` into a running devframe whose `.handler` — a Web Standard `(request: Request) => Promise` — carries everything a devframe serves (SPA, `__connection.json` discovery, RPC socket, auth gate, MCP route) under one mount base. Every other serving path — [adapters](/adapters), [framework kits](/frameworks), [hub](/guide/hub-initiate) — is assembled from it. Mount it with a catch-all route.
```ts
import { initDevframe } from 'devframe/initiate'
@@ -15,7 +15,7 @@ const devtools = initDevframe(myDevframe, { base: '/__my-tool/' })
// devtools.connectionMeta(), devtools.close()
```
-`base` is required — pass `resolveBasePath(def, 'hosted')` (`def.basePath ?? /__/`) to default it; the instance echoes it back as `devtools.base`. `handler`/`nodeMiddleware` await readiness internally. The instance binds no port — [the WebSocket binding](#the-websocket-binding) is the host's call.
+`base` is required — pass `resolveBasePath(def, 'hosted')` (`def.basePath ?? /__/`) to default it; the running devframe echoes it back as `devtools.base`. `handler`/`nodeMiddleware` await readiness internally. The running devframe binds no port — [the WebSocket binding](#the-websocket-binding) is the host framework's call.
## Mount the handler
@@ -110,24 +110,24 @@ export const GET = ({ request }) => devtools.handler(request)
```
::
-Frameworks with dev-time module reloading (Next, Nitro, SvelteKit) re-evaluate the calling module, so memoize the instance on `globalThis` to avoid leaking a socket per reload. `@devframes/next`'s `createDevframeNextHandler` handles this.
+Host frameworks with dev-time module reloading (Next, Nitro, SvelteKit) re-evaluate the calling module, so memoize the running devframe on `globalThis` to avoid leaking a socket per reload. `@devframes/next`'s `createDevframeNextHandler` handles this.
## The WebSocket binding
-Fetch handlers only hand over `Request`s, so the host binds the RPC socket. The **local binding** resolves in this order:
+Fetch handlers only hand over `Request`s, so the host framework binds the RPC socket. The **local binding** resolves in this order:
1. **`ws.port`** — a side-car server on that exact port.
-2. **`server`** — share the host's `node:http` server; the upgrade binds at `__ws`. No extra ports.
-3. **`ws: { sidecar: true }`** — a side-car server on a free port, for hosts whose handlers never see upgrades (Next.js route handlers, Nitro, Rsbuild).
-4. **The host's own upgrades** — with none set, the socket waits: `devtools.attach(server)` routes a server's `upgrade` events (returning a detach fn); `devtools.handleUpgrade(req, socket, head)` completes a single one from a listener you own.
+2. **`server`** — share the host framework's `node:http` server; the upgrade binds at `__ws`. No extra ports.
+3. **`ws: { sidecar: true }`** — a side-car server on a free port, for host frameworks whose handlers never see upgrades (Next.js route handlers, Nitro, Rsbuild).
+4. **The host framework's own upgrades** — with none set, the socket waits: `devtools.attach(server)` routes a server's `upgrade` events (returning a detach fn); `devtools.handleUpgrade(req, socket, head)` completes a single one from a listener you own.
-`ws.url` controls the *advertisement* instead — the browser dials it verbatim. Alone, an external server owns the transport and its auth (wire the instance's `context` via `createContextRpcServer` + a WS transport); alongside a local binding it overrides only the advertisement (the tunnel pattern).
+`ws.url` controls the *advertisement* instead — the browser dials it verbatim. Alone, an external WebSocket server owns the transport and its auth (wire the running devframe's `context` via `createContextRpcServer` + a WS transport); alongside a local binding it overrides only the advertisement (the tunnel pattern).
-`__connection.json` describes the active combination. Asking a configured instance to take over host upgrades reports `DF0055` (a local binding owns the socket) or `DF0056` (`ws.url` handed it off).
+`__connection.json` describes the active combination. Asking a configured running devframe to take over the host framework's upgrades reports `DF0055` (a local binding owns the socket) or `DF0056` (`ws.url` handed it off).
## Auth
-The instance **gates by default**. The interactive OTP handler wires automatically, printing its code/magic-link banner once the public origin is known (the first request, or the `origin` option). Pass `auth: false` for single-user localhost, or a `DevframeAuthHandler` for a custom scheme.
+The running devframe **gates by default**. The interactive OTP handler wires automatically, printing its code/magic-link banner once the public origin is known (the first request, or the `origin` option). Pass `auth: false` for single-user localhost, or a `DevframeAuthHandler` for a custom scheme.
## Relation to the other adapters
diff --git a/docs/content/2.adapters/2.cac.md b/docs/content/2.adapters/2.cac.md
index 4c9d15c9..a6d69c09 100644
--- a/docs/content/2.adapters/2.cac.md
+++ b/docs/content/2.adapters/2.cac.md
@@ -65,7 +65,7 @@ defineDevframe({
port: 7777, // preferred port
portRange: [7777, 9000], // passed through to get-port-please
random: false, // passed through to get-port-please
- host: '127.0.0.1', // default host; --host overrides
+ host: '127.0.0.1', // default bind host; --host overrides
open: true, // auto-open the browser on dev start; embeds the current OTP so the tab lands authenticated
configure(cli) { // contribute capability flags/commands
cli.option('--config ', 'Custom config file')
diff --git a/docs/content/2.adapters/4.build.md b/docs/content/2.adapters/4.build.md
index e3bc2f32..1ea67c56 100644
--- a/docs/content/2.adapters/4.build.md
+++ b/docs/content/2.adapters/4.build.md
@@ -25,4 +25,4 @@ await createBuild(devframe, {
| `distDir` | `def.clientAssets` | SPA dist override (or [remote assets](/guide/client-assets)). |
| `pretty` | `false` | Pretty-print dump JSON. |
-The client runs read-only. For a custom URL base, build with relative asset paths (`vite.base: './'`).
+The RPC client runs read-only. For a custom URL base, build with relative asset paths (`vite.base: './'`).
diff --git a/docs/content/2.adapters/5.vite.md b/docs/content/2.adapters/5.vite.md
index f45a5458..f4891ec0 100644
--- a/docs/content/2.adapters/5.vite.md
+++ b/docs/content/2.adapters/5.vite.md
@@ -21,4 +21,4 @@ The returned object has the shape `{ name, devtools: { setup, capabilities } }`.
| `name` | `devframe:` | Plugin name. |
| `base` | `def.basePath ?? /.${id}/` | Mount path override. |
| `dock` | `{}` | Overrides for the iframe dock entry (category, icon, when). |
-| `setup` | — | Host-only setup hook; receives the kit-augmented context. |
+| `setup` | — | Setup hook run only in the Vite host; receives the kit-augmented context. |
diff --git a/docs/content/2.adapters/7.mcp.md b/docs/content/2.adapters/7.mcp.md
index 26765680..b2f56ffa 100644
--- a/docs/content/2.adapters/7.mcp.md
+++ b/docs/content/2.adapters/7.mcp.md
@@ -1,9 +1,9 @@
---
title: 'MCP'
-description: 'Exposes a devframe''s agent host as a Model Context Protocol server: agents call flagged RPCs and read resources.'
+description: 'Exposes a devframe''s agent-facing API as a Model Context Protocol server: coding agents call flagged RPCs and read resources.'
---
-Exposes a devframe's agent host as a [Model Context Protocol](https://modelcontextprotocol.io) server: agents call flagged RPCs and read resources.
+Exposes a devframe's agent-facing API as a [Model Context Protocol](https://modelcontextprotocol.io) server: coding agents call flagged RPCs and read resources.
```ts
import { createMcpServer } from 'devframe/adapters/mcp'
@@ -12,11 +12,11 @@ import devframe from './devframe'
await createMcpServer(devframe, { transport: 'stdio' })
```
-`@modelcontextprotocol/server` is a peer dependency; `createMcpServer` speaks `stdio`, spawned per session.
+`@modelcontextprotocol/server` is a peer dependency; `createMcpServer` speaks `stdio`, spawned per MCP session.
## Route-based server
-The dev server exposes the same surface over HTTP, live. Enable with `cli.mcp`:
+The dev server exposes the same MCP API over HTTP, live. Enable with `cli.mcp`:
```ts
import { defineDevframe } from 'devframe'
@@ -29,9 +29,9 @@ export default defineDevframe({
})
```
-The endpoint speaks Streamable-HTTP at `/__mcp` (`/__/__mcp` under a host), sharing its origin/port. `--mcp` / `--no-mcp` override; `__connection.json` advertises it.
+The endpoint speaks Streamable-HTTP at `/__mcp` (`/__/__mcp` under a host framework), sharing its origin/port. `--mcp` / `--no-mcp` override; `__connection.json` advertises it.
-Each session gets its own MCP server, keyed by `Mcp-Session-Id`. An origin gate requires `Origin` be loopback (or allow-listed) and rejects `Origin`-less requests. Widen for a tunnel/LAN origin with `cli: { mcp: { allowedOrigins: ['https://tunnel.example.com'] } }`.
+Each MCP session gets its own MCP server, keyed by `Mcp-Session-Id`. An origin gate requires `Origin` be loopback (or allow-listed) and rejects `Origin`-less requests. Widen for a tunnel/LAN origin with `cli: { mcp: { allowedOrigins: ['https://tunnel.example.com'] } }`.
### Hosted bridges
@@ -45,7 +45,7 @@ devframeViteBridge(devframe, { mcp: true })
createDevframeNextHandler(devframe, { mcp: true })
```
-## Custom hosts
+## Custom host frameworks
`createMcpFetchHandler(ctx, options)` returns the endpoint as a `Request → Response` handler plus a `dispose()` — mount on any fetch server.
@@ -75,8 +75,8 @@ The `devframe` bin ships an MCP **connector** ([next-devtools-mcp](https://githu
Two gateway tools (`devframe:connect:*` ids — see [tool ids and wire names](/guide/agent-native#tool-ids-and-wire-names)):
- **`devframe_connect_list-instances`** — list running dev servers and their MCP tools.
-- **`devframe_connect_call-tool`** — invoke one tool on an instance (`{ port, tool, args }`) over Streamable-HTTP.
+- **`devframe_connect_call-tool`** — invoke one tool on a running devframe (`{ port, tool, args }`) over Streamable-HTTP.
-Discovery reads the **instance registry**: every `createDevServer` writes `~/.devframe/instances/-.json`, dialed with a loopback origin. In-process hosts register via `registerDevframeInstance` (`devframe/node`). `--port ` probes a port; `DEVFRAME_INSTANCES_DIR` relocates the registry, `DEVFRAME_DISABLE_INSTANCE_REGISTRY=1` opts out.
+Discovery reads the **instance registry**: every `createDevServer` writes `~/.devframe/instances/-.json`, dialed with a loopback origin. In-process host frameworks register via `registerDevframeInstance` (`devframe/node`). `--port ` probes a port; `DEVFRAME_INSTANCES_DIR` relocates the registry, `DEVFRAME_DISABLE_INSTANCE_REGISTRY=1` opts out.
See [Agent-Native](/guide/agent-native) for the API and safety model.
diff --git a/docs/content/2.adapters/index.md b/docs/content/2.adapters/index.md
index c754b3fc..bfc0b49c 100644
--- a/docs/content/2.adapters/index.md
+++ b/docs/content/2.adapters/index.md
@@ -26,7 +26,7 @@ SPA basePath depends on the adapter:
| Adapter kind | Default basePath | Reason |
|--------------|------------------|--------|
| `cli`, `build` (standalone) | `/` | Owns the origin. |
-| `vite`, `embedded` (hosted) | `/__/` | Shares a host's origin. |
+| `vite`, `embedded` (hosted) | `/__/` | Shares a host framework's origin. |
Override with `DevframeDefinition.basePath`:
@@ -38,4 +38,4 @@ defineDevframe({
})
```
-The client discovers its SPA base at runtime — see [Client](/guide/client#runtime-basepath-discovery).
+The SPA discovers its base at runtime — see [Client](/guide/client#runtime-basepath-discovery).
diff --git a/docs/content/3.frameworks/1.vite.md b/docs/content/3.frameworks/1.vite.md
index 6f711088..ba439a94 100644
--- a/docs/content/3.frameworks/1.vite.md
+++ b/docs/content/3.frameworks/1.vite.md
@@ -1,9 +1,9 @@
---
title: 'Vite'
-description: '@devframes/vite splits into @devframes/vite/single (dev-serve one devframe''s SPA) and @devframes/vite/hub (mount a devframes-hub); the bare import throws.'
+description: '@devframes/vite splits into @devframes/vite/single (dev-serve one devframe''s SPA) and @devframes/vite/hub (mount a hub); the bare import throws.'
---
-`@devframes/vite` splits into **`@devframes/vite/single`** (dev-serve one devframe's SPA) and [**`@devframes/vite/hub`**](#mounting-a-hub) (mount a devframes-hub); the bare import throws.
+`@devframes/vite` splits into **`@devframes/vite/single`** (dev-serve one devframe's SPA) and [**`@devframes/vite/hub`**](#mounting-a-hub) (mount a hub); the bare import throws.
`single` exports `devframeVitePlugin`, `devframeViteBridge`, and `devframeVite`; also used by [`@devframes/nuxt`](/frameworks/nuxt).
@@ -16,7 +16,7 @@ export default defineConfig({
// Statically mounts the built SPA at `/__/` — no RPC server:
plugins: [devframeVitePlugin(devframe)],
// Or bridge the RPC/WS backend into this dev server instead — the
- // host app owns the SPA:
+ // user app owns the SPA:
// plugins: [devframeViteBridge(devframe)],
})
```
@@ -48,7 +48,7 @@ Devframe spawns a separate RPC + WS server and registers Vite middleware at `/` behind one `handler` (memoize on `globalThis`; see `examples/hub-next`):
+[`@devframes/hub`](/guide/hub)'s `initHub` mounts every devframe under `/` behind one `handler` (memoize on `globalThis`; see `examples/hub-next`):
```ts [devframe/host.ts]
import { DEVFRAMES_HUB_BASE, initHub } from '@devframes/hub/initiate'
@@ -76,7 +76,7 @@ export async function GET(request: Request): Promise {
}
```
-## React client
+## React RPC client
```tsx [app/providers.tsx]
'use client'
@@ -122,7 +122,7 @@ export const POST = (req: Request) => hub.handler(req)
export const DELETE = (req: Request) => hub.handler(req)
```
-No native hub viewer here, so this scope stays quiet; `createDevframeNextHost()` is the low-level `DevframeHost`.
+No native hub UI provider here, so this scope stays quiet; `createDevframeNextHost()` is the low-level `DevframeHost`.
## See also
diff --git a/docs/content/3.frameworks/index.md b/docs/content/3.frameworks/index.md
index 228d189b..7c476685 100644
--- a/docs/content/3.frameworks/index.md
+++ b/docs/content/3.frameworks/index.md
@@ -1,14 +1,14 @@
---
title: 'Frameworks'
-description: 'The framework packages — @devframes/vite, @devframes/nuxt, @devframes/next — integrate devframe with a meta-framework''s dev server. Two subpaths:'
+description: 'The framework kits — @devframes/vite, @devframes/nuxt, @devframes/next — integrate devframe with a meta-framework''s dev server. Two subpaths:'
---
-The framework packages — [`@devframes/vite`](/frameworks/vite), [`@devframes/nuxt`](/frameworks/nuxt), [`@devframes/next`](/frameworks/next) — integrate devframe with a meta-framework's dev server. Two **subpaths**:
+The framework kits — [`@devframes/vite`](/frameworks/vite), [`@devframes/nuxt`](/frameworks/nuxt), [`@devframes/next`](/frameworks/next) — integrate devframe with a meta-framework's dev server. Two **subpaths**:
| Scope | Subpath | You are… |
|-------|---------|----------|
| **single** | `.../single` | building & dev-serving a **single devframe's SPA** with that tool |
-| **hub** | `.../hub` | mounting a whole **[devframes-hub](/guide/hub)** (many integrations) inside that tool |
+| **hub** | `.../hub` | mounting a whole **[hub](/guide/hub)** (many devframes) inside that tool |
The bare package root throws, pointing to the two subpaths.
@@ -22,8 +22,8 @@ The bare package root throws, pointing to the two subpaths.
For framework-neutral CLI/build/embedded outputs, use the [adapters](/adapters) instead.
-## hub: mount a devframes-hub
+## hub: mount a hub
-Each `hub` entry wraps [`initHub`](/guide/hub-initiate) and defaults the UI to [`@devframes/hub-ui`](/guide/build-your-own-hub-ui)'s `createUi()` (`ui` to override, `ui: false` for headless). Per tool: **[Vite](/frameworks/vite#mounting-a-hub)**, **[Nuxt](/frameworks/nuxt#mounting-a-hub)**, **[Next](/frameworks/next#mounting-a-hub)**.
+Each `hub` scope wraps [`initHub`](/guide/hub-initiate) and defaults the UI to [`@devframes/hub-ui`](/guide/build-your-own-hub-ui)'s `createUi()` (`ui` to override, `ui: false` for headless). Per tool: **[Vite](/frameworks/vite#mounting-a-hub)**, **[Nuxt](/frameworks/nuxt#mounting-a-hub)**, **[Next](/frameworks/next#mounting-a-hub)**.
-`@devframes/vite/hub` and `@devframes/nuxt/hub` recommend the native viewers ([Vite DevTools](https://devtools.vite.dev), [Nuxt DevTools](https://devtools.nuxt.com)) once (silence with `{ quiet: true }`). Next has none, so `@devframes/next/hub` stays quiet.
+`@devframes/vite/hub` and `@devframes/nuxt/hub` recommend the native hub UI providers ([Vite DevTools](https://devtools.vite.dev), [Nuxt DevTools](https://devtools.nuxt.com)) once (silence with `{ quiet: true }`). Next has none, so `@devframes/next/hub` stays quiet.
diff --git a/docs/content/4.helpers/1.utilities.md b/docs/content/4.helpers/1.utilities.md
index 73c03661..88c08ae8 100644
--- a/docs/content/4.helpers/1.utilities.md
+++ b/docs/content/4.helpers/1.utilities.md
@@ -109,7 +109,7 @@ off()
### `devframe/utils/shared-state`
-The immutable state container behind `ctx.rpc.sharedState` ([Shared State](/guide/shared-state)); usable outside the host.
+The immutable state container behind `ctx.rpc.sharedState` ([Shared State](/guide/shared-state)); usable outside a running devframe.
```ts
import { createSharedState } from 'devframe/utils/shared-state'
@@ -127,7 +127,7 @@ Sink/reader primitives for streamed RPC payloads, via `ctx.rpc.streaming` — se
### `devframe/utils/when`
-Statically-validated when-clause expressions for conditional visibility; runtime + types ship here, consumer `when` fields are kit-side (see [When Clauses](/guide/when-clauses)).
+Statically-validated when-clause expressions for conditional visibility; runtime + types ship here, consumer `when` fields are kit-side (see [When Clauses](/references/when-clauses)).
## Why a `utils/*` subpath
diff --git a/docs/content/4.helpers/2.common-rpc-functions.md b/docs/content/4.helpers/2.common-rpc-functions.md
index ee55a0ec..71a37a65 100644
--- a/docs/content/4.helpers/2.common-rpc-functions.md
+++ b/docs/content/4.helpers/2.common-rpc-functions.md
@@ -4,7 +4,7 @@ description: 'Prebuilt RPC actions: open a file in an editor, reveal a path in t
---
> [!WARNING]
-> Deprecated for the [`@devframes/service-open` wire service](/guide/services#built-in-services) — one host-level install shared by every plugin, feature-detectable from clients, with workspace-root path containment atop the editor gating. The recipe keeps working; removal in a future major.
+> Deprecated for the [`@devframes/service-open` wire service](/guide/services#built-in-services) — one node-side install shared by every devframe, feature-detectable from RPC clients, with workspace-root path containment atop the editor gating. The recipe keeps working; removal in a future major.
Prebuilt RPC actions: open a file in an editor, reveal a path in the OS.
@@ -45,7 +45,7 @@ defineDevframe({
})
```
-## On the client
+## On the browser side
```ts
const rpc = await connectDevframe()
@@ -54,4 +54,4 @@ await rpc.call('devframe:open-in-editor', 'src/main.ts:42:7', 'code')
await rpc.call('devframe:open-in-finder', '/abs/path/to/dir')
```
-Auto-detection reads server-side `LAUNCH_EDITOR` when no `editor` is passed.
+Auto-detection reads the node-side `LAUNCH_EDITOR` when no `editor` is passed.
diff --git a/docs/content/4.helpers/3.interactive-auth.md b/docs/content/4.helpers/3.interactive-auth.md
index 7db3505c..456d4ef3 100644
--- a/docs/content/4.helpers/3.interactive-auth.md
+++ b/docs/content/4.helpers/3.interactive-auth.md
@@ -1,9 +1,9 @@
---
title: 'Interactive Auth'
-description: 'An OTP auth layer over devframe''s node-side primitives (exchangeTempAuthCode / verifyAuthToken / revokeAuthToken), so a host needn''t re-implement the protocol.'
+description: 'An OTP auth layer over devframe''s node-side primitives (exchangeTempAuthCode / verifyAuthToken / revokeAuthToken), so a host framework needn''t re-implement the protocol.'
---
-An OTP auth layer over devframe's node-side primitives (`exchangeTempAuthCode` / `verifyAuthToken` / `revokeAuthToken`), so a host needn't re-implement the protocol.
+An OTP auth layer over devframe's node-side primitives (`exchangeTempAuthCode` / `verifyAuthToken` / `revokeAuthToken`), so a host framework needn't re-implement the protocol.
Adapters gate with this layer by default via `createDevServer(def)` / `initDevframe` / `initHub` (`auth: true`). Use `createInteractiveAuth` only for a custom transport:
@@ -29,7 +29,7 @@ As `auth` it wires `rpcFunctions`, `authorize`, and `onConnect` — see [Securit
|--------|---------|---------|
| `clientAuthTokens` | `undefined` | Pre-shared bearer tokens, always trusted. |
| `banner` | a small boxed console message | Called with `{ code, url }`; prints via `printBanner()`. |
-| `onTrusted` | `undefined` | Called with `{ session, authToken }` once a code exchange succeeds, so a host rendering its own banner can retract it. |
+| `onTrusted` | `undefined` | Called with `{ session, authToken }` (the trust session and its token) once a code exchange succeeds, so a host framework rendering its own banner can retract it. |
| `serverUrl` | `context.host.resolveOrigin()` | Magic-link base URL. |
Returns a `DevframeAuthHandler`:
@@ -58,6 +58,6 @@ if (!auth.authorize(methodName, session))
auth.onConnect(peer, session)
```
-An exchange rotates the code and prints the new one, and `onTrusted` fires after that, so a host retracting a sticky notice drops that follow-up too and calls `auth.printBanner()` when it next wants a code on screen.
+An exchange rotates the code and prints the new one, and `onTrusted` fires after that, so a host framework retracting a sticky notice drops that follow-up too and calls `auth.printBanner()` when it next wants a code on screen.
Auth storage is internal, not `devframe/node/hub-internals`.
diff --git a/docs/content/4.helpers/index.md b/docs/content/4.helpers/index.md
index a76051fb..f932c891 100644
--- a/docs/content/4.helpers/index.md
+++ b/docs/content/4.helpers/index.md
@@ -1,9 +1,9 @@
---
title: 'Helpers'
-description: 'Helpers are the optional surface around defineDevframe: prebuilt RPC recipes and low-level utilities from the devframe package.'
+description: 'Helpers are the optional layer around defineDevframe: prebuilt RPC recipes and low-level utilities from the devframe package.'
---
-Helpers are the optional surface around `defineDevframe`: prebuilt RPC recipes and low-level utilities from the `devframe` package.
+Helpers are the optional layer around `defineDevframe`: prebuilt RPC recipes and low-level utilities from the `devframe` package.
| Helper | Entry | What it does |
|--------|-------|--------------|
@@ -11,6 +11,6 @@ Helpers are the optional surface around `defineDevframe`: prebuilt RPC recipes a
| [Common RPC Functions](/helpers/common-rpc-functions) | `devframe/recipes/common-rpc-functions` | "Open in editor" and "reveal in Finder" actions. |
| [Interactive Auth](/helpers/interactive-auth) | `devframe/recipes/interactive-auth` | OTP auth layer: handshake, resolver gate, connect-time trust, banner. |
-Unlike [adapters](/adapters), which deploy a `DevframeDefinition` as a runnable surface (CLI, dev server, build, MCP), a helper is a recipe or utility composed with one.
+Unlike [adapters](/adapters), which deploy a `DevframeDefinition` as a runnable deployment (CLI, dev server, build, MCP), a helper is a recipe or utility composed with one.
-To integrate a devframe or hub with a meta-framework, see the [`@devframes/*` packages](/frameworks).
+To integrate a devframe or hub with a meta-framework, see the [framework kits](/frameworks).
diff --git a/docs/content/5.plugins/1.data-inspector.md b/docs/content/5.plugins/1.data-inspector.md
index 116701bd..98dbb025 100644
--- a/docs/content/5.plugins/1.data-inspector.md
+++ b/docs/content/5.plugins/1.data-inspector.md
@@ -21,9 +21,9 @@ _Query data with advanced Jora syntax_
- **Auto rerun** — optional poller (`auto rerun every N seconds`).
- **Result viewer** — normalizes to strict JSON (circulars → `$ref`; Maps, Sets, class instances, functions, Dates get type badges) plus per-query stats.
- **Expansion, shape & filters** — deep nodes fetch lazily via `load deeper`; a shape panel shows a one-level skeleton; filters drop functions and `_`/`$` properties.
-- **Saved queries** — recipes (`query` + title/description + filters) in **workspace** (committable) and **project** (per-checkout) scopes.
+- **Saved queries** — recipes (`query` + title/description + filters) in the **workspace scope** (committable) and the **project scope** (per-checkout).
-A built-in **example source** registers by default; opt out with `exampleSource: false` (`--no-example`; agent `DEVFRAME_DATA_INSPECTOR_EXAMPLE=0`).
+A built-in **example source** registers by default; opt out with `exampleSource: false` (`--no-example`; `DEVFRAME_DATA_INSPECTOR_EXAMPLE=0` when injected).
## Providing data sources
@@ -33,7 +33,7 @@ The registry is **process-global** — register anywhere, before or after mount.
import { registerDataSource } from '@devframes/plugin-data-inspector/registry'
registerDataSource({
- id: 'my-plugin:store', // namespace with your plugin id
+ id: 'my-plugin:store', // namespace with your devframe id
title: 'My plugin store',
description: 'The live state store',
icon: 'i-ph:database-duotone',
@@ -45,7 +45,7 @@ registerDataSource({
})
```
-`data` is a plain value or sync/async factory; `static: true` resolves it once; `queries` show read-only beside saved ones; `registerDataSource` returns an unregister callback. Zero-dependency integrations `register` through the typed [context service](/guide/devframe-definition#cross-plugin-services) `ctx.services.whenAvailable('devframes:plugin:data-inspector:sources', …)`.
+`data` is a plain value or sync/async factory; `static: true` resolves it once; `queries` show read-only beside saved ones; `registerDataSource` returns an unregister callback. Zero-dependency devframes `register` through the typed [context service](/guide/devframe-definition#cross-devframe-services) `ctx.services.whenAvailable('devframes:plugin:data-inspector:sources', …)`.
> [!WARNING]
> Queries are eval-grade: jora invokes any function reachable as an own property and fires own getters. Register live objects accordingly, and keep endpoints on loopback.
@@ -62,7 +62,7 @@ In a hub, another dock jumps to a source via [dock activation](/guide/deep-linki
pnpx @devframes/plugin-data-inspector # the example source
pnpx @devframes/plugin-data-inspector stats.json log.jsonl # one static source per data file
pnpx @devframes/plugin-data-inspector build stats.json # self-contained static export
-pnpx @devframes/plugin-data-inspector attach # attach to a process running the agent
+pnpx @devframes/plugin-data-inspector attach # attach to a process running the inject endpoint
```
`.json` parses whole; `.jsonl` / `.ndjson` as a record array. `build` embeds the dataset in a static site.
@@ -98,7 +98,7 @@ export default createDataInspectorDevframe({
## Attach to another Node process
-The target starts the agent:
+The target starts the inject endpoint:
```ts
import { exposeDataInspector } from '@devframes/plugin-data-inspector/inject'
@@ -124,9 +124,9 @@ globalThis.cache = cache
It reads `globalThis` at query time, so later assignments appear next run (opt out: `DEVFRAME_DATA_INSPECTOR_GLOBAL=0`).
-The agent binds `127.0.0.1`, requires the trust handshake with a per-run token, and writes its endpoint to `node_modules/.data-inspector/agent.json`, which `attach` reads (or pass `ws://…` + `--token`).
+The inject endpoint binds `127.0.0.1`, requires the trust handshake with a per-run token, and advertises itself in `node_modules/.data-inspector/discovery.json`, which `attach` reads (or pass `ws://…` + `--token`).
-## RPC surface
+## RPC
All `devframes:plugin:data-inspector:*`:
diff --git a/docs/content/5.plugins/2.inspect.md b/docs/content/5.plugins/2.inspect.md
index d858c824..37f59d35 100644
--- a/docs/content/5.plugins/2.inspect.md
+++ b/docs/content/5.plugins/2.inspect.md
@@ -1,9 +1,9 @@
---
title: 'Devframe Inspector'
-description: 'A self-inspector for any devframe connection, including the host''s — a Vue SPA.'
+description: 'A self-inspector for any devframe connection, including the host framework''s — a Vue SPA.'
---
-A self-inspector for any devframe connection, including the host's — a **Vue** SPA.
+A self-inspector for any devframe connection, including the host framework's — a **Vue** SPA.
Package: `@devframes/plugin-inspect` · framework: **Vue + Vite**
@@ -75,7 +75,7 @@ import { createCac } from 'devframe/adapters/cac'
await createCac(createInspectDevframe({ port: 9100 })).parse()
```
-## RPC surface
+## RPC
All functions are namespaced `devframes:plugin:inspect:*`:
diff --git a/docs/content/5.plugins/3.og.md b/docs/content/5.plugins/3.og.md
index dde488c3..ef0e53cb 100644
--- a/docs/content/5.plugins/3.og.md
+++ b/docs/content/5.plugins/3.og.md
@@ -52,7 +52,7 @@ export default createOgDevframe({
`defaultUrl` sets the initial target and bakes it into the static build.
-## RPC surface
+## RPC
Namespaced `devframes:plugin:og:*`:
diff --git a/docs/content/5.plugins/4.a11y.md b/docs/content/5.plugins/4.a11y.md
index 360348e6..dfa59b3e 100644
--- a/docs/content/5.plugins/4.a11y.md
+++ b/docs/content/5.plugins/4.a11y.md
@@ -1,9 +1,9 @@
---
title: 'Accessibility Inspector'
-description: 'Runs axe-core against a host app, lists WCAG A/AA violations in a Solid panel, and highlights the element on hover.'
+description: 'Runs axe-core against the user app, lists WCAG A/AA violations in a Solid panel, and highlights the element on hover.'
---
-Runs [axe-core](https://github.com/dequelabs/axe-core) against a host app, lists WCAG A/AA violations in a **Solid** panel, and highlights the element on hover.
+Runs [axe-core](https://github.com/dequelabs/axe-core) against the user app, lists WCAG A/AA violations in a **Solid** panel, and highlights the element on hover.
Package: `@devframes/plugin-a11y` · framework: **Solid + Vite**
@@ -21,25 +21,25 @@ Three pieces, two browser-side:
| Piece | Runs in | Role |
|-------|---------|------|
-| **Agent** | the host app's page | runs axe-core, broadcasts the report, draws the highlight ring |
+| **Page script** | the user app's page | runs axe-core, broadcasts the report, draws the highlight ring |
| **Panel** | the devtools iframe | Solid SPA: lists violations, highlights on hover |
-| **Node** | the devframe backend | the `get-config` RPC (impact taxonomy), baked in static builds |
+| **Node side** | the Node process | the `get-config` RPC (impact taxonomy), baked in static builds |
-Agent and panel talk over a same-origin `BroadcastChannel`, so the loop works live or static. The agent is the author-provided bridge (no host DOM access) — one module script scans, reports, and highlights.
+The page script and the panel talk over the in-page channel (a same-origin `BroadcastChannel`), so the loop works live or static. The page script is the author-provided bridge (the panel has no reach into the user app's DOM) — one module script scans, reports, and highlights.
## In a hub
-The agent is the a11y dock's [client script](/guide/client-context): attach `a11yAgentBundlePath` as the dock's `clientScript` and the hub imports it into the page. It also mirrors each scan into the hub's messages feed — a summary plus one per rule:
+The page script is the a11y dock's [client script](/guide/client-context): attach `a11yPageScriptBundlePath` as the dock's `clientScript` and the hub imports it into the page. It also mirrors each scan into the hub's messages feed — a summary plus one per rule:
```ts
-import createA11yDevframe, { a11yAgentBundlePath } from '@devframes/plugin-a11y'
+import createA11yDevframe, { a11yPageScriptBundlePath } from '@devframes/plugin-a11y'
await ctx.install(createA11yDevframe(), {
- dock: { clientScript: { importFrom: `/@fs/${a11yAgentBundlePath}` } },
+ dock: { clientScript: { importFrom: `/@fs/${a11yPageScriptBundlePath}` } },
})
```
-Outside a hub, a `
diff --git a/examples/hub-fastify-minimal/README.md b/examples/hub-fastify-minimal/README.md
index 369f9076..3dc0505c 100644
--- a/examples/hub-fastify-minimal/README.md
+++ b/examples/hub-fastify-minimal/README.md
@@ -6,7 +6,7 @@ The minimal [Fastify](https://fastify.dev) host for `@devframes/hub` — one `in
pnpm --filter hub-fastify-minimal dev
```
-Open — the host page carries the floating dock via one script tag — or for the standalone viewer.
+Open — the host page carries the floating dock via one script tag — or for the standalone hub UI.
## How it works
diff --git a/examples/hub-fastify-minimal/src/hub.ts b/examples/hub-fastify-minimal/src/hub.ts
index b634fe48..2012e0eb 100644
--- a/examples/hub-fastify-minimal/src/hub.ts
+++ b/examples/hub-fastify-minimal/src/hub.ts
@@ -67,7 +67,7 @@ export const hostPage = `
This page is the host app. The devtools ride along:
diff --git a/examples/hub-hono-minimal/README.md b/examples/hub-hono-minimal/README.md
index 438df3d4..35f2ac4d 100644
--- a/examples/hub-hono-minimal/README.md
+++ b/examples/hub-hono-minimal/README.md
@@ -7,11 +7,11 @@ pnpm --filter hub-hono-minimal dev # Node (tsx)
pnpm --filter hub-hono-minimal dev:bun # Bun
```
-Open — the host page carries the floating dock via one script tag — or for the standalone viewer.
+Open — the host page carries the floating dock via one script tag — or for the standalone hub UI.
## How it works
-- [`src/app.ts`](./src/app.ts) — runtime-agnostic: `initHub({ devframes, ui: createUi({ branding }) })` (rebranded to Hono's own orange, `#e36002`) plus `app.all('/__devframes/*', c => hub.handler(c.req.raw))`. Everything — frame SPAs, `__connection.json`, `__index.json`, `embedded.js`, `__client-imports.js` — flows through that one route. The instance is memoized on `globalThis` so a dev-time reload reuses the live hub. It configures no WebSocket transport, so each entry below wires the socket its runtime's way; both end up serving `/__devframes/__ws` on the app's own origin, which is what the hub advertises either way.
+- [`src/app.ts`](./src/app.ts) — runtime-agnostic: `initHub({ devframes, ui: createUi({ branding }) })` (rebranded to Hono's own orange, `#e36002`) plus `app.all('/__devframes/*', c => hub.handler(c.req.raw))`. Everything — the mounted devframes' SPAs, `__connection.json`, `__index.json`, `embedded.js`, `__client-imports.js` — flows through that one route. The instance is memoized on `globalThis` so a dev-time reload reuses the live hub. It configures no WebSocket transport, so each entry below wires the socket its runtime's way; both end up serving `/__devframes/__ws` on the app's own origin, which is what the hub advertises either way.
- [`src/server.ts`](./src/server.ts) — Node: `@hono/node-server`'s `serve()` returns the `node:http` server, and `hub.attach(server)` routes its upgrade events to the shared RPC socket.
- [`src/bun.ts`](./src/bun.ts) — Bun: upgrades arrive as fetch requests, so this entry binds Bun's own transport to the hub context with `createContextRpcServer` + `attachBunWsTransport` and answers the upgrade route inside `Bun.serve({ fetch, websocket })`.
diff --git a/examples/hub-hono-minimal/src/app.ts b/examples/hub-hono-minimal/src/app.ts
index 746cc579..ae199246 100644
--- a/examples/hub-hono-minimal/src/app.ts
+++ b/examples/hub-hono-minimal/src/app.ts
@@ -84,7 +84,7 @@ app.get('/', c => c.html(
This page is the host app. The devtools ride along:
diff --git a/examples/hub-next-minimal/src/client/app/%5F_devframes/[[...path]]/route.ts b/examples/hub-next-minimal/src/client/app/%5F_devframes/[[...path]]/route.ts
index 65a9f268..37a27cbc 100644
--- a/examples/hub-next-minimal/src/client/app/%5F_devframes/[[...path]]/route.ts
+++ b/examples/hub-next-minimal/src/client/app/%5F_devframes/[[...path]]/route.ts
@@ -5,7 +5,7 @@ export const dynamic = 'force-dynamic'
// The whole hub namespace behind one catch-all route: `initHub`'s
// web-standard `handler` serves every frame SPA, the discovery endpoints,
-// and the embedded/viewer UI. Next reserves `_`-prefixed segment folders,
+// and the embedded/standalone UI. Next reserves `_`-prefixed segment folders,
// so `__devframes` is URL-encoded as `%5F_devframes` in the app directory.
// MCP would speak Streamable-HTTP over GET/POST/DELETE - the same handler.
async function handler(request: Request): Promise {
diff --git a/examples/hub-next-minimal/src/client/app/page.tsx b/examples/hub-next-minimal/src/client/app/page.tsx
index 0777907b..791b269a 100644
--- a/examples/hub-next-minimal/src/client/app/page.tsx
+++ b/examples/hub-next-minimal/src/client/app/page.tsx
@@ -12,7 +12,7 @@ export default function Page() {
/__devframes/embedded.js
- the standalone viewer lives at
+ the standalone hub UI lives at
{' '}
/__devframes/
diff --git a/examples/hub-next-minimal/src/client/hub.ts b/examples/hub-next-minimal/src/client/hub.ts
index 518e25f2..3de37ee7 100644
--- a/examples/hub-next-minimal/src/client/hub.ts
+++ b/examples/hub-next-minimal/src/client/hub.ts
@@ -9,7 +9,7 @@ import { createNextDevframeHub } from '@devframes/next/hub'
// A server-authored JSON-render dock: the whole view is this serializable
// spec — no client build. It renders through whatever `'json-render'`
// renderer the hub composes (below, the reference `@devframes/json-render-ui`
-// module); without one, the viewer shows its missing-renderer fallback.
+// module); without one, the hub UI provider shows its missing-renderer fallback.
const jsonRenderSpec: DevframeJsonRenderSpec = {
root: 'root',
elements: {
@@ -29,7 +29,7 @@ const jsonRenderDock: DevframeJsonRenderDockEntry = {
}
// The plugin packages and `@devframes/hub-ui` resolve their prebuilt `dist`
-// (SPA assets, the embedded/viewer bundles) via `new URL('../dist/...',
+// (SPA assets, the embedded/standalone bundles) via `new URL('../dist/...',
// import.meta.url)`. Loaded with a runtime dynamic `import()` carrying
// `webpackIgnore` / `turbopackIgnore` so Next's bundler leaves them alone and
// Node resolves the published `dist` at request time - a static import would
@@ -72,7 +72,7 @@ async function loadHub(): Promise {
]
// `@devframes/next/hub` runs the socket on a side-car (Next routes can't
// accept WS upgrades). This host overrides the default UI slot to rebrand
- // the reference viewer to Next.js/Vercel's monochrome black — one field, no
+ // the reference hub UI to Next.js/Vercel's monochrome black — one field, no
// CSS: `createUi`'s `branding` option publishes
// `ConnectionMeta.configs.ui.branding`, which the dock reads at connect
// time and feeds into `--devframe-primary` (see `@devframes/hub-ui`'s
@@ -82,7 +82,7 @@ async function loadHub(): Promise {
ui: (hubUi.createUi as typeof CreateUi)({ branding: { primaryColor: '#3f8ba9', productName: 'Devframes on Next.js' } }),
// Serve the reference json-render frontend as a prebuilt renderer module
// — the one-liner that makes `'json-render'` docks render in the prebuilt
- // viewer. Swap it for any community implementation of the same contract.
+ // hub UI provider. Swap it for any community implementation of the same contract.
renderers: [(jsonRenderUi.jsonRenderUiRenderer as typeof JsonRenderUiRenderer)()],
configure(ctx) {
ctx.docks.register(jsonRenderDock)
diff --git a/examples/hub-next/README.md b/examples/hub-next/README.md
index 941338d6..ddef149f 100644
--- a/examples/hub-next/README.md
+++ b/examples/hub-next/README.md
@@ -1,8 +1,8 @@
# Next Devframe Hub
-A tiny, copyable **vite-devtools-style hub on Next.js**. [vite-devtools](https://github.com/vitejs/devtools) is the full Vite viewer built on `@devframes/hub`; this example wears the same shape - an icon dock, an iframe stage, a subsystem drawer - but hosts it from a Next.js App Router app with one `initHub()` instance behind one catch-all route. It's the reference for bringing the same integrations to any non-Vite host.
+A tiny, copyable **vite-devtools-style hub on Next.js**. [vite-devtools](https://github.com/vitejs/devtools) is the full Vite hub UI provider built on `@devframes/hub`; this example wears the same shape - an icon dock, an iframe stage, a subsystem drawer - but hosts it from a Next.js App Router app with one `initHub()` instance behind one catch-all route. It's the reference for bringing the same devframes to any non-Vite host framework.
-`src/client/devframe/next-devframe-hub.ts` is the entire host: a single `initHub()` call from `@devframes/hub/initiate`.
+`src/client/devframe/next-devframe-hub.ts` is the entire node side: a single `initHub()` call from `@devframes/hub/initiate`.
## Run it
@@ -11,18 +11,18 @@ pnpm install
pnpm --filter hub-next dev
```
-On first load the hub asks you to authorize. `initHub()` gates every connection by default (devframe's interactive OTP), so it prints a 6-digit code and a magic link in the terminal, and the page shows an authorization view that exchanges the code for a bearer token stored in the browser. The client shell opts out of devframe's native `prompt()` (`simpleAuth: false`) to render that view; open the magic link instead to authorize without typing. Each embedded SPA then inherits the token the host page stored.
+On first load the hub asks you to authorize. `initHub()` gates every connection by default (devframe's interactive OTP), so it prints a 6-digit code and a magic link in the terminal, and the page shows an authorization view that exchanges the code for a bearer token stored in the browser. The hub UI provider opts out of devframe's native `prompt()` (`simpleAuth: false`) to render that view; open the magic link instead to authorize without typing. Each embedded SPA then inherits the token the host page stored.
-Open the printed URL. The dock on the left lists every mounted tool with its icon:
+Open the printed URL. The dock rail on the left lists every mounted tool with its icon:
-- **Git**, **Terminals**, **Code Server**, **RPC & State Inspector**, **A11y Inspector** - the built-in plugins, each an entry in `initHub`'s `devframes` list
+- **Git**, **Terminals**, **Code Server**, **RPC & State Inspector**, **A11y Inspector** - the built-in devframes, each an entry in `initHub`'s `devframes` list
- **Next Demo Tool** - a trivial static SPA that shows the bare mount path
-Selecting a tool loads its SPA in the stage. The bottom drawer mirrors the hub's **Commands**, **Messages**, and **Terminals** subsystems, plus a button that dispatches a command through `hub:commands:execute`, and a **Transport** section showing which RPC transport the connection runs on (`websocket` or `sse`) with a segmented Auto / WS / SSE toggle - the choice rides a `?transport=` URL param and reconnects the whole client host on the pinned transport.
+Selecting a tool loads its SPA in the stage. The bottom drawer mirrors the hub's **Commands**, **Messages**, and **Terminals** subsystems, plus a button that dispatches a command through `hub:commands:execute`, and a **Transport** section showing which RPC transport the connection runs on (`websocket` or `sse`) with a segmented Auto / WS / SSE toggle - the choice rides a `?transport=` URL param and reconnects the whole client runtime on the pinned transport.
-The A11y Inspector shows a live axe-core report of this hub's own page: the host serves the plugin's in-page agent module (`a11yAgentBundlePath`) same-origin inside the hub namespace and attaches it as the a11y dock's `clientScript` (the `{ devframe, dock }` entry form); the hub client runtime - `createDevframeClientHost()` booted in `app/page.tsx` - imports it into the page, so the docked panel and the agent share the origin their BroadcastChannel rides.
+The A11y Inspector shows a live axe-core report of this hub's own page: the hub serves the devframe's page-script module (`a11yPageScriptBundlePath`) same-origin inside the hub namespace and attaches it as the a11y dock's `clientScript` (the `{ devframe, dock }` entry form); the hub client runtime - `createDevframeClientRuntime()` booted in `app/page.tsx` - imports it into the page, so the docked panel and the page script share the origin their in-page channel (a `BroadcastChannel`) rides.
-The **RPC & State Inspector** carries an **Instances** tab that lists every devframe dev server running on your machine. The host registers itself in the shared registry (`~/.devframe/instances/`) on startup via `registerDevframeInstance()`, so it shows up as "this instance"; start another example (e.g. `pnpm --filter hub-vite dev`, or any `node bin.mjs` CLI example) in a second terminal and it appears there too, each linking to its own SPA.
+The **RPC & State Inspector** carries an **Instances** tab that lists every devframe dev server running on your machine. The hub registers itself in the shared registry (`~/.devframe/instances/`) on startup via `registerDevframeInstance()`, so it shows up as "this instance"; start another example (e.g. `pnpm --filter hub-vite dev`, or any `node bin.mjs` CLI example) in a second terminal and it appears there too, each linking to its own SPA.
## One namespace, one route
@@ -30,7 +30,7 @@ The **RPC & State Inspector** carries an **Instances** tab that lists every devf
- `/__devframes//` - each mounted devframe's SPA and its `__connection.json`
- `/__devframes/__connection.json` - hub discovery; advertises the side-car WebSocket (Next route handlers can't accept upgrades, so the hub asks for one with `ws: { sidecar: true }` and the meta carries its port)
-- `/__devframes/__index.json` - the frame index and endpoint map
+- `/__devframes/__index.json` - the mounted-devframe index and endpoint map
- `/__devframes/__client-imports.js` - the dock client-script import map
- `/__devframes/__mcp` - the aggregate MCP endpoint (Streamable-HTTP) over the whole hub tool registry
@@ -42,22 +42,22 @@ The instance is memoized on `globalThis`, so Next's dev-time module re-evaluatio
- `initHub()` boots a whole hub with no Vite-specific code path - devframes, shared RPC registry, WS transport, MCP, and discovery behind one framework-agnostic handler
- Every `devframes` entry is mounted as a dock and served at `/__devframes//` with its own `__connection.json`, so the embedded SPA connects straight back to the hub
-- One authorization covers the whole hub: `initHub()` gates the shared transport by default, so a single OTP handshake trusts every mounted frame, the discovery endpoints, and the built-ins. The shell drives its own authorization view (`simpleAuth: false`) and each embedded SPA inherits the stored token
+- One authorization covers the whole hub: `initHub()` gates the shared transport by default, so a single OTP handshake trusts every mounted devframe, the discovery endpoints, and the built-ins. The hub UI provider drives its own authorization view (`simpleAuth: false`) and each embedded SPA inherits the stored token
- The browser reads `devframe:docks` / `devframe:commands` shared state and dispatches commands over RPC - byte-for-byte the same protocol the Vite host speaks
-- `createDevframeClientHost()` boots the hub's framework-level client runtime in the host page: it publishes the shared client context and imports each dock's `clientScript` (here, the a11y agent) so plugins run code in the page being inspected
-- The **JSON Render** dock renders through a **local React renderer** (`src/client/json-render/react-renderer.tsx` - a compact React port of the base catalog) registered at `createDevframeClientHost({ renderers })`. The hub *also* publishes the reference Vue frontend through its renderer manifest (`renderers: [jsonRenderUiRenderer()]` on `initHub`), but local registration takes precedence - witnessing that any frontend implementing the `JsonRenderDockRenderer` contract can replace the reference one. Delete the local `renderers` option and the same dock renders via the manifest-served module instead. (The sibling `hub-vite` witness ships no local renderer and consumes the manifest directly - the other side of the swap seam.)
-- The **No Renderer** dock witnesses the missing-renderer path: its type is covered by nothing, so `renderers.mount()` resolves `{ status: 'missing-renderer' }` and the shell shows *No renderer for "demo-unrendered" in the current environment* instead of a dead panel
-- The **Client Script Demo** dock witnesses the **URL shape of client scripts**: this host declares no `clientModuleResolution` (Next's bundler exposes no browser-reachable on-demand module URL, so bare-specifier client scripts are unsupported here), so it mounts `demo-dock-client`'s prebuilt self-contained bundle statically and passes the served URL as `action.importFrom`. The sibling `hub-vite` host consumes the **same package** as a bare npm specifier through its `/@id/{specifier}` template - the two shapes of `importFrom` side by side
+- `createDevframeClientRuntime()` boots the hub's framework-level client runtime in the host page: it publishes the shared client context and imports each dock's `clientScript` (here, the a11y page script) so devframes run page scripts in the user app's page
+- The **JSON Render** dock renders through a **local React renderer** (`src/client/json-render/react-renderer.tsx` - a compact React port of the base catalog) registered at `createDevframeClientRuntime({ renderers })`. The hub *also* publishes the reference Vue frontend through its renderer manifest (`renderers: [jsonRenderUiRenderer()]` on `initHub`), but local registration takes precedence - witnessing that any frontend implementing the `JsonRenderDockRenderer` contract can replace the reference one. Delete the local `renderers` option and the same dock renders via the manifest-served module instead. (The sibling `hub-vite` witness ships no local renderer and consumes the manifest directly - the other side of the swap seam.)
+- The **No Renderer** dock witnesses the missing-renderer path: its type is covered by nothing, so `renderers.mount()` resolves `{ status: 'missing-renderer' }` and the hub UI provider shows *No renderer for "demo-unrendered" in the current environment* instead of a dead panel
+- The **Client Script Demo** dock witnesses the **URL shape of client scripts**: the Next host declares no `clientModuleResolution` (Next's bundler exposes no browser-reachable on-demand module URL, so bare-specifier client scripts are unsupported here), so it mounts `demo-dock-client`'s prebuilt self-contained bundle statically and passes the served URL as `action.importFrom`. The sibling `hub-vite` host consumes the **same package** as a bare npm specifier through its `/@id/{specifier}` template - the two shapes of `importFrom` side by side
-## Hosting built-in plugins in a bundler
+## Hosting built-in devframes in a bundler
-The plugins run node-side (child processes, the native `zigpty` PTY backend) and resolve their SPA dist via `new URL(..., import.meta.url)`. Next's bundler would try to inline that, so the host loads them through a bundler-ignored dynamic `import()` and sets `skipTrailingSlashRedirect` (see `next.config.mjs`) so each SPA's relative assets resolve under `/__devframes//`. This is the recipe for any bundled (webpack/Turbopack) host.
+The built-in devframes run node-side (child processes, the native `zigpty` PTY backend) and resolve their SPA dist via `new URL(..., import.meta.url)`. Next's bundler would try to inline that, so the node side loads them through a bundler-ignored dynamic `import()` and sets `skipTrailingSlashRedirect` (see `next.config.mjs`) so each SPA's relative assets resolve under `/__devframes//`. This is the recipe for any bundled (webpack/Turbopack) host framework.
## Files
| File | Role |
|---|---|
-| `src/client/devframe/next-devframe-hub.ts` | The Next host - one `initHub()` call: devframes (incl. the a11y agent's dock `clientScript`), hub RPCs, commands, the json-render dock + renderer manifest, instance-registry registration |
+| `src/client/devframe/next-devframe-hub.ts` | The Next host - one `initHub()` call: devframes (incl. the a11y page script as its dock's `clientScript`), hub RPCs, commands, the json-render dock + renderer manifest, instance-registry registration |
| `src/client/devframe/unrendered-dock.ts` | A dock type registered with no renderer on purpose - the missing-renderer fallback witness |
| `../demo-dock-client/` | The shared demo client script, consumed here as a statically-mounted self-contained bundle |
| `src/client/app/%5F_devframes/[[...path]]/route.ts` | The one catch-all - delegates every `/__devframes/*` request to the instance's `handler` |
diff --git a/examples/hub-next/src/client/app/page.tsx b/examples/hub-next/src/client/app/page.tsx
index 616792e2..28202003 100644
--- a/examples/hub-next/src/client/app/page.tsx
+++ b/examples/hub-next/src/client/app/page.tsx
@@ -11,7 +11,7 @@ import type {
import type { DevframeJsonRenderSpec } from '@devframes/json-render'
import type { DevframeJsonRenderDockEntry } from '@devframes/json-render/hub'
import type { FormEvent } from 'react'
-import { connectDevframe, createDevframeClientHost, FRAME_NAV_CHANNEL } from '@devframes/hub/client'
+import { connectDevframe, createDevframeClientRuntime, FRAME_NAV_CHANNEL } from '@devframes/hub/client'
import { useEffect, useMemo, useRef, useState } from 'react'
import { createReactJsonRenderDockRenderer } from '../json-render/react-renderer'
import { dockIconSvg } from './icons'
@@ -49,7 +49,7 @@ interface Status {
type IframeDock = DevframeDockEntry & { type: 'iframe', url: string }
type TerminalSummary = Pick
-type ClientHost = Awaited>
+type ClientHost = Awaited>
function isIframeDock(d: DevframeDockEntry): d is IframeDock {
return d.type === 'iframe' && typeof (d as { url?: unknown }).url === 'string'
@@ -87,7 +87,7 @@ function createClientNotesUrl(): string {
This dock was registered in the browser with
host.context.docks.register(). It lives only in this page - it
never enters the devframe:docks shared state, so it is not synced
- to the hub server or to any other connected viewer.
+ to the hub node side or to any other connected surface.
Patch it live through the returned handle with update() (its
badge was set that way), or remove it with dispose().
`
return URL.createObjectURL(new Blob([html], { type: 'text/html' }))
@@ -418,7 +418,7 @@ export default function Page() {
// implementing the `JsonRenderDockRenderer` contract can replace the
// reference one. Delete this `renderers` option and the same dock
// renders through the manifest-served Vue module instead.
- const clientHost = await createDevframeClientHost({
+ const clientHost = await createDevframeClientRuntime({
rpc,
renderers: { 'json-render': createReactJsonRenderDockRenderer() },
})
diff --git a/examples/hub-next/src/client/devframe/next-devframe-hub.ts b/examples/hub-next/src/client/devframe/next-devframe-hub.ts
index 415039a7..d7eb9283 100644
--- a/examples/hub-next/src/client/devframe/next-devframe-hub.ts
+++ b/examples/hub-next/src/client/devframe/next-devframe-hub.ts
@@ -114,7 +114,7 @@ interface A11yAgentMount {
async function loadA11yAgentMount(): Promise {
try {
const mod = await import(/* webpackIgnore: true */ /* turbopackIgnore: true */ '@devframes/plugin-a11y')
- const bundle = mod.a11yAgentBundlePath as string
+ const bundle = mod.a11yPageScriptBundlePath as string
return {
dockId: (mod.default as () => DevframeDefinition)().id,
dir: dirname(bundle),
@@ -269,7 +269,7 @@ export async function nextDevframeHub(
// over the same catch-all route as the SPAs.
mcp: true,
// This host renders its own React UI in `app/page.tsx`, so skip the
- // default `@devframes/hub-ui` viewer/embedded slot.
+ // default `@devframes/hub-ui` standalone/embedded slot.
ui: false,
// Next route handlers can't accept WS upgrades — `createNextDevframeHub`
// runs the socket on a side-car (a free port near 9777, or the pinned one).
@@ -317,7 +317,7 @@ export async function nextDevframeHub(
})
// The hub synthesizes no built-in docks - a high-level integration
- // registers the viewer's native views it wants, declaring the `~builtin`
+ // registers the hub UI provider's native views it wants, declaring the `~builtin`
// category itself so this Settings tab groups and sorts last.
ctx.docks.register({
type: '~builtin',
diff --git a/examples/hub-next/src/client/devframe/unrendered-dock.ts b/examples/hub-next/src/client/devframe/unrendered-dock.ts
index 9ae9f1b7..dee03510 100644
--- a/examples/hub-next/src/client/devframe/unrendered-dock.ts
+++ b/examples/hub-next/src/client/devframe/unrendered-dock.ts
@@ -3,7 +3,7 @@ import type { DevframeDockEntryBase } from '@devframes/hub/types'
/**
* A dock variant registered through the hub's **open** dock union — exactly
* how an opt-in integration contributes its own type — that no renderer
- * covers on purpose. It witnesses the missing-renderer path: the viewer
+ * covers on purpose. It witnesses the missing-renderer path: the hub UI provider
* resolves the type against its renderer registry (local registrations, then
* the hub's renderer manifest), finds nothing, and renders its fallback view
* — `No renderer for "demo-unrendered" in the current environment` — instead
diff --git a/examples/hub-nitro-minimal/README.md b/examples/hub-nitro-minimal/README.md
index 64584c96..d468e9d8 100644
--- a/examples/hub-nitro-minimal/README.md
+++ b/examples/hub-nitro-minimal/README.md
@@ -6,12 +6,12 @@ The minimal [Nitro](https://nitro.build) host for `@devframes/hub`: one `initHub
pnpm --filter hub-nitro-minimal dev
```
-Open - the host page carries the floating dock via one script tag - or for the standalone viewer.
+Open - the host page carries the floating dock via one script tag - or for the standalone hub UI.
## How it works
-- [`hub.ts`](./hub.ts) - `initHub({ devframes, ui: createUi({ branding }) })`: mounts the Inspect and Messages plugins against one shared hub context, fills the hub's `ui` slot with `@devframes/hub-ui`'s prebuilt viewer + floating-dock bootstrap (rebranded to Nitro's own pink/red, `#ff2056`), and memoizes the instance across Nitro's dev-time module reloads.
-- [`routes/__devframes/[...path].ts`](./routes/__devframes/%5B...path%5D.ts) (and its `index.ts` sibling for the namespace root) - the delegation: every request under `/__devframes/` becomes `hub.handler(event.req)`, web-standard Request in, Response out. Everything - frame SPAs, `__connection.json`, `__index.json`, `embedded.js`, `__client-imports.js` - flows through it.
+- [`hub.ts`](./hub.ts) - `initHub({ devframes, ui: createUi({ branding }) })`: mounts the Inspect and Messages devframes against one shared hub context, fills the hub's `ui` slot with `@devframes/hub-ui`'s prebuilt standalone UI + floating-dock bootstrap (rebranded to Nitro's own pink/red, `#ff2056`), and memoizes the instance across Nitro's dev-time module reloads.
+- [`routes/__devframes/[...path].ts`](./routes/__devframes/%5B...path%5D.ts) (and its `index.ts` sibling for the namespace root) - the delegation: every request under `/__devframes/` becomes `hub.handler(event.req)`, web-standard Request in, Response out. Everything - the mounted devframes' SPAs, `__connection.json`, `__index.json`, `embedded.js`, `__client-imports.js` - flows through it.
- [`nitro.config.ts`](./nitro.config.ts) - keeps the devframe packages external so their prebuilt client assets resolve from the packages themselves rather than Nitro's build output.
- The RPC WebSocket runs on a side-car port - Nitro handlers hand over `Request`s, so `ws: { sidecar: true }` asks for one - advertised through `__connection.json`; the browser client discovers it automatically.
diff --git a/examples/hub-nitro-minimal/hub.ts b/examples/hub-nitro-minimal/hub.ts
index a5b9b075..dfc77ff6 100644
--- a/examples/hub-nitro-minimal/hub.ts
+++ b/examples/hub-nitro-minimal/hub.ts
@@ -13,7 +13,7 @@ import { createTerminalsDevframe } from '@devframes/plugin-terminals'
// The whole devtools installation in one call: two plugins mounted under
// /__devframes/, the reference UI filling the hub's ui slot (the standalone
-// viewer at the namespace root + the floating dock at embedded.js), and the
+// hub UI at the namespace root + the floating dock at embedded.js), and the
// RPC socket on a side-car port advertised via __connection.json - Nitro's
// route handlers never deal with WebSocket upgrades, so `ws.sidecar` asks
// for a socket of its own.
diff --git a/examples/hub-nitro-minimal/routes/__devframes/index.ts b/examples/hub-nitro-minimal/routes/__devframes/index.ts
index 30368d27..4edd2c9a 100644
--- a/examples/hub-nitro-minimal/routes/__devframes/index.ts
+++ b/examples/hub-nitro-minimal/routes/__devframes/index.ts
@@ -1,7 +1,7 @@
import { defineHandler } from 'nitro'
import { hub } from '../../hub'
-// The namespace root itself (`/__devframes/`) - the standalone viewer from
+// The namespace root itself (`/__devframes/`) - the standalone hub UI from
// `@devframes/hub-ui`. The `[...path]` sibling covers every path beneath it,
// but a catch-all doesn't match its own empty subpath, so the root needs its
// own route.
diff --git a/examples/hub-nitro-minimal/routes/index.ts b/examples/hub-nitro-minimal/routes/index.ts
index 76f2a134..6c64e825 100644
--- a/examples/hub-nitro-minimal/routes/index.ts
+++ b/examples/hub-nitro-minimal/routes/index.ts
@@ -16,7 +16,7 @@ export default defineHandler(() => new Response(
This page is the host app. The devtools ride along:
diff --git a/examples/hub-rsbuild-minimal/README.md b/examples/hub-rsbuild-minimal/README.md
index d9913fc9..0541bbed 100644
--- a/examples/hub-rsbuild-minimal/README.md
+++ b/examples/hub-rsbuild-minimal/README.md
@@ -6,13 +6,13 @@ The minimal [Rsbuild](https://rsbuild.dev) host for `@devframes/hub`: one `initH
pnpm --filter hub-rsbuild-minimal dev
```
-Open the printed URL - the host page carries the floating dock via one injected script tag - or `/__devframes/` for the standalone viewer.
+Open the printed URL - the host page carries the floating dock via one injected script tag - or `/__devframes/` for the standalone hub UI.
## How it works
-[`rsbuild.config.ts`](./rsbuild.config.ts) is the entire host:
+[`rsbuild.config.ts`](./rsbuild.config.ts) is the entire host-framework integration:
-- `initHub({ devframes: [inspect, messages], ui: createUi({ branding }) })` runs in Rsbuild's Node config process (never bundled into the browser), so `createUi()`'s prebuilt viewer/dock and the plugins' node code work unchanged. `branding.primaryColor` is Rsbuild's own orange (`#ff5e00`) — a rebrand reaches every `primary`-based color in the dock, no CSS required.
+- `initHub({ devframes: [inspect, messages], ui: createUi({ branding }) })` runs in Rsbuild's Node config process (never bundled into the browser), so `createUi()`'s prebuilt standalone UI + floating dock and the devframes' node code work unchanged. `branding.primaryColor` is Rsbuild's own orange (`#ff5e00`) — a rebrand reaches every `primary`-based color in the dock, no CSS required.
- `dev.setupMiddlewares` unshifts `hub.nodeMiddleware`, which owns the whole `/__devframes/` namespace and hands everything else back to Rsbuild.
- The RPC WebSocket runs on a side-car port (`ws: { sidecar: true }`, since Rsbuild's middleware stack never hands over upgrades), advertised through `__connection.json`; the browser client discovers it automatically.
- `html.tags` injects `