devframe 0.9.5 → 0.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +1 -1
  2. package/dist/{_shared-CsBsnhuO.d.mts → _shared-BJ6AmwTu.d.mts} +1 -1
  3. package/dist/adapters/build.d.mts +1 -1
  4. package/dist/adapters/build.mjs +1 -1
  5. package/dist/adapters/cac.d.mts +1 -1
  6. package/dist/adapters/cac.mjs +3 -2
  7. package/dist/adapters/dev.d.mts +4 -4
  8. package/dist/adapters/dev.mjs +1 -1
  9. package/dist/adapters/embedded.d.mts +1 -1
  10. package/dist/adapters/initiate.d.mts +3 -3
  11. package/dist/adapters/initiate.mjs +1 -1
  12. package/dist/adapters/mcp.d.mts +30 -26
  13. package/dist/adapters/mcp.mjs +559 -1
  14. package/dist/cli/main.mjs +1 -1
  15. package/dist/client/index.d.mts +5 -5
  16. package/dist/client/index.mjs +1 -64
  17. package/dist/constants.d.mts +13 -1
  18. package/dist/constants.mjs +1 -1
  19. package/dist/{context-c0OlSc12.d.mts → context-BhBzOXVa.d.mts} +3 -3
  20. package/dist/{context-DO0Qu1O8.d.mts → context-HOnG-WvG.d.mts} +1 -1
  21. package/dist/{context-Cx9V9Grq.mjs → context-KUqAfawQ.mjs} +1 -1
  22. package/dist/{context-Bc5S40Qt.mjs → context-omlma_1n.mjs} +3 -150
  23. package/dist/{dev-BRQA42Lh.mjs → dev-Czit5IP4.mjs} +3 -3
  24. package/dist/{events-DzolaBEs.mjs → events-4daw0uK9.mjs} +16 -2
  25. package/dist/in-page-channel/index.d.mts +306 -0
  26. package/dist/in-page-channel/index.mjs +816 -0
  27. package/dist/{index-DOg4S5wk.d.mts → index-BzyopqxV.d.mts} +1 -1
  28. package/dist/{index-B_DPNIT4.d.mts → index-DcKxIMoI.d.mts} +2 -2
  29. package/dist/index.d.mts +4 -4
  30. package/dist/{instance-shell-CPuYY8pw.d.mts → instance-shell-Bh4l9Vz_.d.mts} +3 -3
  31. package/dist/{instance-shell-fB5PyVtR.mjs → instance-shell-CpVW4zG0.mjs} +15 -2
  32. package/dist/internal/index.d.mts +16 -6
  33. package/dist/internal/index.mjs +3 -3
  34. package/dist/node/auth.d.mts +2 -2
  35. package/dist/node/auth.mjs +1 -1
  36. package/dist/node/hub-internals.d.mts +2 -2
  37. package/dist/node/hub-internals.mjs +1 -1
  38. package/dist/node/index.d.mts +3 -3
  39. package/dist/node/index.mjs +1 -1
  40. package/dist/recipes/common-rpc-functions.d.mts +3 -3
  41. package/dist/recipes/interactive-auth.d.mts +13 -2
  42. package/dist/recipes/interactive-auth.mjs +5 -1
  43. package/dist/{revoke-PihtvVil.mjs → revoke-COLW0cOi.mjs} +1 -1
  44. package/dist/rpc/dump.d.mts +1 -1
  45. package/dist/rpc/index.d.mts +2 -2
  46. package/dist/rpc/transports/sse-client.d.mts +1 -1
  47. package/dist/rpc/transports/sse-server.d.mts +2 -2
  48. package/dist/rpc/transports/ws-bun.d.mts +2 -2
  49. package/dist/rpc/transports/ws-client.d.mts +1 -1
  50. package/dist/rpc/transports/ws-deno.d.mts +2 -2
  51. package/dist/rpc/transports/ws-server.d.mts +1 -1
  52. package/dist/{rpc-core-Oyz-cJBC.d.mts → rpc-core-BHUsDl7s.d.mts} +3 -3
  53. package/dist/{host-agent-DbPWo0Bl.mjs → services-install-CqqQVls5.mjs} +150 -2
  54. package/dist/{sse-client-CdGD_B8_.d.mts → sse-client-DwX021ST.d.mts} +1 -1
  55. package/dist/types/index.d.mts +3 -3
  56. package/dist/{types-BBLeGfpt.d.mts → types-CMdPYhOx.d.mts} +1 -1
  57. package/dist/utils/events.d.mts +1 -1
  58. package/dist/utils/serve-static.mjs +1 -1
  59. package/dist/utils/shared-state.d.mts +1 -1
  60. package/dist/utils/streaming-channel.d.mts +1 -1
  61. package/dist/{ws-client-Bo2t6hES.d.mts → ws-client-7_lZi-B9.d.mts} +1 -1
  62. package/dist/{ws-server-DLtEoLdR.d.mts → ws-server-Di2oqr4q.d.mts} +1 -1
  63. package/package.json +2 -1
  64. package/skills/devframe/SKILL.md +86 -82
  65. package/dist/http-D7wWuy7-.mjs +0 -569
  66. package/dist/transports-vhizgqXM.mjs +0 -15
@@ -5,13 +5,13 @@ description: >
5
5
  build-tool-agnostic foundation for defining a devtool once and
6
6
  serving it anywhere. Covers the DevframeDefinition, the standard
7
7
  web handler (`initDevframe` → `handler` / `nodeMiddleware`) that
8
- mounts a tool into any host, the packaging adapters (cli / build /
9
- dev / mcp / embedded), the framework packages (`@devframes/vite`,
8
+ mounts a tool into any host framework, the packaging adapters (cli /
9
+ build / dev / mcp / embedded), the framework kits (`@devframes/vite`,
10
10
  `@devframes/next`, `@devframes/nuxt`, each split into `single` and
11
- `hub`), composing many tools into one devtools host with
11
+ `hub`), composing many tools into one hub with
12
12
  `@devframes/hub` (`initHub`, docks / commands / messages /
13
13
  terminals), designing RPC contracts, exposing an agent-native
14
- surface over MCP, and wiring the author's SPA client. Triggers on
14
+ API over MCP, and wiring the author's SPA. Triggers on
15
15
  `devframe` imports, `defineDevframe`, `initDevframe`, `initHub`,
16
16
  `createCac`, `createMcpServer`, `connectDevframe`, `@devframes/*`
17
17
  imports, and on migrations of existing inspectors
@@ -21,14 +21,14 @@ description: >
21
21
 
22
22
  # devframe skill
23
23
 
24
- **Devframe is the `unplugin` for devtools: define a tool once, mount it anywhere.** A devtool built on devframe is a single `DevframeDefinition` plus an author-provided SPA. That definition describes one tool - its RPC surface, shared state, diagnostics, web interface, and agent-facing surface - independent of how it is presented. The same definition then deploys through a standard web handler, a set of packaging adapters, thin framework integrations, or composed with other tools inside a hub.
24
+ **Devframe is the `unplugin` for devtools: define a tool once, mount it anywhere.** A devtool built on devframe is a single `DevframeDefinition` plus an author-provided SPA. That definition describes one tool - its RPC, shared state, diagnostics, web interface, and agent-facing API - independent of how it is presented. The same definition then deploys through a standard web handler, a set of packaging adapters, thin framework kits, or composed with other tools inside a hub.
25
25
 
26
26
  Two layers, one boundary:
27
27
 
28
- - **A devframe** is one portable tool. `initDevframe(def, { base })` turns it into a live instance whose `.handler` is a Web-Standard `(request: Request) => Promise<Response>` carrying the whole surface (SPA, discovery, WebSocket RPC, auth gate, optional MCP route) under one mount base. Anything that can mount a catch-all route or Connect-style middleware can host it.
28
+ - **A devframe** is one portable tool. `initDevframe(def, { base })` turns it into a live instance whose `.handler` is a Web-Standard `(request: Request) => Promise<Response>` carrying the whole devframe (SPA, discovery, WebSocket RPC, auth gate, optional MCP route) under one mount base. Anything that can mount a catch-all route or Connect-style middleware can serve it.
29
29
  - **A hub** (`@devframes/hub`) composes *many* devframes behind one namespace with a shared RPC registry, one transport, one auth gate, and the orchestration features that only make sense when tools share a UI (docks, commands, messages, terminals). `initHub()` puts the whole collection behind the same kind of standard handler.
30
30
 
31
- Devframe is framework- and build-tool-agnostic - it has zero dependency on Vite or any `@vitejs/*` package and makes no UI-framework assumption. [Vite DevTools](https://devtools.vite.dev/) is the first flagship *host* built on it; the built-in plugins deliberately span Vue, Svelte, Solid, React, and Next to prove the point.
31
+ Devframe is framework- and build-tool-agnostic - it has zero dependency on Vite or any `@vitejs/*` package and makes no UI-framework assumption. [Vite DevTools](https://devtools.vite.dev/) is the first flagship hub UI provider built on it; the built-in devframes deliberately span Vue, Svelte, Solid, React, and Next to prove the point.
32
32
 
33
33
  High-level concept: [Pluggable, Extensible, and Playful DevTools](https://antfu.me/posts/pluggable-extensible-playful-devtools). Full reference: [devfra.me](https://devfra.me/).
34
34
 
@@ -40,24 +40,24 @@ The same `DevframeDefinition` runs under every one of these. Choose based on whe
40
40
 
41
41
  | Goal | Entry | Import |
42
42
  |------|-------|--------|
43
- | Mount into any host (the portability primitive) | `initDevframe(def, { base })` → `.handler` / `.nodeMiddleware` | `devframe/initiate` |
43
+ | Mount into any host framework (the portability primitive) | `initDevframe(def, { base })` → `.handler` / `.nodeMiddleware` | `devframe/initiate` |
44
44
  | Standalone CLI (dev / build / mcp subcommands) | `createCac(def, opts?).parse()` | `devframe/adapters/cac` |
45
45
  | Programmatic dev server | `createDevServer(def, opts?)` | `devframe/adapters/dev` |
46
46
  | Self-contained static deploy with baked data | `createBuild(def, opts?)` | `devframe/adapters/build` |
47
47
  | MCP server for coding agents | `createMcpServer(def, opts?)` | `devframe/adapters/mcp` |
48
- | Runtime registration into an existing host | `createEmbedded(def, { ctx })` | `devframe/adapters/embedded` |
48
+ | Runtime registration into an existing context | `createEmbedded(def, { ctx })` | `devframe/adapters/embedded` |
49
49
  | Ride along a Vite dev server (no dock) | `devframeVitePlugin` / `devframeViteBridge` / `devframeVite` | `@devframes/vite/single` |
50
50
  | Author one devframe's SPA with Next | `withDevframe` + `createDevframeNextHandler` | `@devframes/next/single` |
51
51
  | Author one devframe's SPA with Nuxt | Nuxt module | `@devframes/nuxt/single` |
52
52
  | Mount into the Vite DevTools dock | `createPluginFromDevframe(def, opts?)` | `@vitejs/devtools-kit/node` |
53
53
 
54
- **Serve many devframes as a devtools host:**
54
+ **Serve many devframes as a hub:**
55
55
 
56
56
  | Goal | Entry | Import |
57
57
  |------|-------|--------|
58
58
  | Compose a hub behind one handler | `initHub({ base, devframes, ui })` | `@devframes/hub/initiate` |
59
59
  | Imperatively mount into a hub context | `createHubContext(...)` → `ctx.install(def)` | `@devframes/hub/node` |
60
- | Reference viewer UI for a hub | `createUi(opts?)` | `@devframes/hub-ui` |
60
+ | Reference hub UI provider | `createUi(opts?)` | `@devframes/hub-ui` |
61
61
  | Mount a hub inside Vite / Next / Nuxt | `viteDevframeHub` / `nextDevframeHub` / hub module | `@devframes/{vite,next,nuxt}/hub` |
62
62
 
63
63
  `createCac`, `createDevServer`, the `@devframes/vite` bridge, and `@devframes/next` are all assembled from `initDevframe` internally - the standard handler is the one wiring underneath every serving path.
@@ -89,21 +89,21 @@ export default defineDevframe({
89
89
  })
90
90
  ```
91
91
 
92
- Source `version` / `packageName` / `homepage` / `description` from your published `package.json` (the JSON import-attribute form resolves under both bundlers and Node's native TypeScript execution). Always pass `importMetaUrl: import.meta.url` - it is the base the host resolves the tool's own companion packages against (a `--assets` package holding the built SPA, a wire-service package), so a plugin ships them as its own dependencies and users install nothing extra.
92
+ Source `version` / `packageName` / `homepage` / `description` from your published `package.json` (the JSON import-attribute form resolves under both bundlers and Node's native TypeScript execution). Always pass `importMetaUrl: import.meta.url` - it is the base the node side resolves the tool's own companion packages against (a `--assets` package holding the built SPA, a wire-service package), so a devframe ships them as its own dependencies and users install nothing extra.
93
93
 
94
- `setup(ctx, info?)` runs in **every** runtime and does all devframe-level wiring: RPC functions, shared state, streaming channels, diagnostics, agent surface. Its optional second argument carries runtime metadata (most notably parsed CLI `flags` under `createCac`). Gate per-runtime work on `ctx.mode` (`'dev'` | `'build'`).
94
+ `setup(ctx, info?)` runs in **every** runtime and does all devframe-level wiring: RPC functions, shared state, streaming channels, diagnostics, the agent-facing API. Its optional second argument carries runtime metadata (most notably parsed CLI `flags` under `createCac`). Gate per-runtime work on `ctx.mode` (`'dev'` | `'build'`).
95
95
 
96
- **A plugin's default export is its `create<X>Devframe` factory, never a pre-built instance** - `export default createMyInspectorDevframe`, so importing the module costs nothing and each consumer calls the factory (with or without options) to get its own instance.
96
+ **A built-in devframe's default export is its `create<X>Devframe` factory, never a pre-built instance** - `export default createMyInspectorDevframe`, so importing the module costs nothing and each consumer calls the factory (with or without options) to get its own instance.
97
97
 
98
- See `templates/counter-devframe.ts` for a runnable example, `templates/hub.ts` for composing a hub, and `templates/vite-client.ts` for the author's client entry.
98
+ See `templates/counter-devframe.ts` for a runnable example, `templates/hub.ts` for composing a hub, and `templates/vite-client.ts` for the author's browser entry.
99
99
 
100
100
  ## The standard handler (`initDevframe`)
101
101
 
102
- This is the portability trick and the thing to reach for whenever a host can mount a route. `base` is required, so the mount path is explicit at the call site.
102
+ This is the portability trick and the thing to reach for whenever a host framework can mount a route. `base` is required, so the mount path is explicit at the call site.
103
103
 
104
104
  ```ts
105
105
  import { initDevframe } from 'devframe/initiate'
106
- import myDevframe from './devframe'
106
+ import myDevframe from './my-tool'
107
107
 
108
108
  const devtools = initDevframe(myDevframe, { base: '/__my-tool/' })
109
109
  // devtools.base, .handler, .nodeMiddleware, .attach, .handleUpgrade,
@@ -126,43 +126,43 @@ server.middlewares.use(initDevframe(myDevframe, {
126
126
 
127
127
  `devtools.base` is the normalized mount base - reference it in route guards instead of repeating the string.
128
128
 
129
- **The WebSocket binding** is the host's explicit call. Fetch handlers only hand over `Request`s, so the RPC socket needs its own binding, resolved in precedence order:
129
+ **The WebSocket binding** is the host framework's explicit call. Fetch handlers only hand over `Request`s, so the RPC socket needs its own binding, resolved in precedence order:
130
130
 
131
131
  1. `ws.port` — a side-car on that exact port.
132
- 2. `server` — share the host's `node:http` server; the upgrade binds at `<base>__ws`. Zero extra ports, follows the app through proxies/HTTPS.
133
- 3. `ws: { sidecar: true }` — a side-car on a free port, for hosts whose handlers never see upgrades (Next.js route handlers, Nitro, SvelteKit, Rsbuild).
134
- 4. **The host's own upgrades** — with none of the above, `devtools.attach(server)` routes a server's `upgrade` events (returns a detach fn) and `devtools.handleUpgrade(req, socket, head)` completes a single one. Built lazily — an instance nobody attaches costs nothing.
132
+ 2. `server` — share the host framework's `node:http` server; the upgrade binds at `<base>__ws`. Zero extra ports, follows the user app through proxies/HTTPS.
133
+ 3. `ws: { sidecar: true }` — a side-car on a free port, for host frameworks whose handlers never see upgrades (Next.js route handlers, Nitro, SvelteKit, Rsbuild).
134
+ 4. **The host framework's own upgrades** — with none of the above, `devtools.attach(server)` routes a `node:http` server's `upgrade` events (returns a detach fn) and `devtools.handleUpgrade(req, socket, head)` completes a single one. Built lazily — an instance nobody attaches costs nothing.
135
135
 
136
- `ws.url` instead controls the *advertisement* (the tunnel/external-transport pattern). Whichever is active, `__connection.json` describes it and the browser client follows.
136
+ `ws.url` instead controls the *advertisement* (the tunnel/external-transport pattern). Whichever is active, `__connection.json` describes it and the RPC client follows.
137
137
 
138
- Frameworks that re-evaluate modules in dev (Next, Nitro, SvelteKit) must memoize the instance on `globalThis`, or every reload leaks the previous WebSocket server. The framework packages do this for you.
138
+ Host frameworks that re-evaluate modules in dev (Next, Nitro, SvelteKit) must memoize the instance on `globalThis`, or every reload leaks the previous WebSocket server. The framework kits do this for you.
139
139
 
140
- **Auth gates by default** - a handler mounted inside an app server is reachable by anything that can open its socket. The interactive OTP handler is wired automatically and prints its code / magic-link once the public origin is known. Pass `auth: false` only for a single-user localhost setup, or a `DevframeAuthHandler` for a custom scheme.
140
+ **Auth gates by default** - a handler mounted inside the user app's server is reachable by anything that can open its socket. The interactive OTP handler is wired automatically and prints its code / magic-link once the public origin is known. Pass `auth: false` only for a single-user localhost setup, or a `DevframeAuthHandler` for a custom scheme.
141
141
 
142
142
  ## Scoped context (preferred)
143
143
 
144
- `ctx.scope(id)` (server) and `client.scope(id)` (browser) return a namespace-scoped view that auto-prefixes every RPC id, shared-state key, and streaming channel with `id:`, and adds a top-level persisted `settings` store. Prefer it over raw `ctx.rpc` / client - name the namespace once, register and call by bare name.
144
+ `ctx.scope(id)` (node side) and `client.scope(id)` (browser side) return a namespace-scoped view that auto-prefixes every RPC id, shared-state key, and streaming channel with `id:`, and adds a top-level persisted `settings` store. Prefer it over the raw `ctx.rpc` / RPC client - name the namespace once, register and call by bare name.
145
145
 
146
146
  ```ts
147
- // server — setup(ctx)
147
+ // node side — setup(ctx)
148
148
  const my = ctx.scope('my-inspector')
149
149
  my.rpc.register(getStats) // -> my-inspector:get-stats
150
150
  await my.rpc.call('get-stats') // invokeLocal, namespaced
151
151
  const state = await my.rpc.sharedState('view') // -> my-inspector:view
152
152
  await my.settings.project.set('theme', 'dark')
153
153
 
154
- // browser — connectDevframe()
154
+ // browser side — connectDevframe()
155
155
  const my = (await connectDevframe()).scope('my-inspector')
156
156
  const stats = await my.rpc.call('get-stats')
157
157
  ```
158
158
 
159
159
  - **Auto-namespacing.** Bare names get `id:` prepended; a name already containing `:` is treated as fully-qualified and passed through (so `my.rpc.call('other-tool:fn')` works). `register` only accepts bare names - a namespaced one throws `DF0034`.
160
160
  - **Typed bare calls.** Define functions with bare names and augment the registry with `RpcDefinitionsToFunctionsWithNamespace<'my-inspector', typeof serverFunctions>` so registry keys match the namespaced runtime ids; scoped `call('get-stats')` then stays typed.
161
- - **`base`.** The scoped context keeps the raw context as `my.base` (and re-exposes `views` / `diagnostics` / `agent` / `services` / `host` / `cwd` / `mode` on the server).
161
+ - **`base`.** The scoped context keeps the raw context as `my.base` (and re-exposes `views` / `diagnostics` / `agent` / `services` / `host` / `cwd` / `mode` on the node side).
162
162
 
163
163
  ### Settings
164
164
 
165
- `my.settings` is a persisted key-value store at the **top level** of the scoped context (a sibling of `my.rpc`). Two scopes: `project` (per-workspace) and `global` (per-user). Both are file-backed on the server and synced to clients over the shared-state protocol, so a `set` on either side propagates everywhere and survives restarts. All methods are async.
165
+ `my.settings` is a persisted key-value store at the **top level** of the scoped context (a sibling of `my.rpc`). Two scopes: `project` (per-checkout) and `global` (per-user). Both are file-backed on the node side and synced to RPC clients over the shared-state protocol, so a `set` on either side propagates everywhere and survives restarts. All methods are async.
166
166
 
167
167
  ```ts
168
168
  await my.settings.project.set('theme', 'dark')
@@ -183,21 +183,21 @@ declare module 'devframe' {
183
183
 
184
184
  ## DevframeNodeContext at a glance
185
185
 
186
- `setup(ctx)` receives the framework-neutral server-side surface:
186
+ `setup(ctx)` receives the framework-neutral node-side API:
187
187
 
188
- | Host | Purpose |
188
+ | Member | Purpose |
189
189
  |------|---------|
190
190
  | `ctx.scope(id)` | **Preferred** namespace-scoped view — auto-prefixed `rpc` + top-level `settings` store |
191
191
  | `ctx.rpc` | Register RPC functions, broadcast, shared state, streaming channels |
192
192
  | `ctx.views` | Serve static files via `hostStatic(base, distDir)` |
193
193
  | `ctx.diagnostics` | Structured diagnostics host (nostics) — register custom error codes |
194
194
  | `ctx.agent` | Expose tools + resources to coding agents |
195
- | `ctx.services` | Typed cross-plugin service registry (`provide` / `whenAvailable`) |
195
+ | `ctx.services` | Typed cross-devframe service registry (`provide` / `whenAvailable`) |
196
196
  | `ctx.staticConfig` | This context's own `ConnectionMeta.configs` — boot-time, read-only-from-browser data |
197
197
  | `ctx.host` | Runtime abstraction — `mountStatic`, `resolveOrigin`, `getStorageDir` |
198
198
  | `ctx.mode` | `'dev'` or `'build'` — gate setup work per runtime |
199
199
 
200
- > Hub adapters augment `ctx` with extra surfaces (`docks`, `terminals`, `messages`, `commands`) — see [The Hub](#the-hub). The Vite DevTools kit exposes the same subsystems via an optional `setup` hook.
200
+ > Hub adapters augment `ctx` with extra subsystems (`docks`, `terminals`, `messages`, `commands`) — see [The Hub](#the-hub). The Vite DevTools kit exposes the same subsystems via an optional `setup` hook.
201
201
 
202
202
  **Storage scopes** — `ctx.host.getStorageDir(scope)` places persisted state in one of three classes:
203
203
 
@@ -211,7 +211,7 @@ Scoped settings persist their `project` scope through `project` storage and thei
211
211
 
212
212
  ## Project layout
213
213
 
214
- Once a devframe grows past a couple of RPC functions, split them out - one file per function under `src/rpc/functions/`, with `src/rpc/index.ts` as the barrel that collects them into `const serverFunctions = [...] as const` and feeds the type-safe client registry via `RpcDefinitionsToFunctionsWithNamespace<'my-tool', typeof serverFunctions>`.
214
+ Once a devframe grows past a couple of RPC functions, split them out - one file per function under `src/rpc/functions/`, with `src/rpc/index.ts` as the barrel that collects them into `const serverFunctions = [...] as const` and feeds the type-safe RPC client registry via `RpcDefinitionsToFunctionsWithNamespace<'my-tool', typeof serverFunctions>`.
215
215
 
216
216
  ```ts
217
217
  // src/rpc/functions/list-files.ts
@@ -243,7 +243,7 @@ declare module 'devframe' {
243
243
  ```
244
244
 
245
245
  ```ts
246
- // src/devframe.ts
246
+ // src/my-tool.ts
247
247
  import { defineDevframe } from 'devframe'
248
248
  import pkg from '../package.json' with { type: 'json' }
249
249
  import { setMyToolContext } from './context'
@@ -267,7 +267,7 @@ export default defineDevframe({
267
267
 
268
268
  ### Sharing setup-time state via `src/context.ts`
269
269
 
270
- When per-file RPCs need runtime values `setup(ctx)` constructs once - channels, shared-state handles, watchers, loaders, caches - expose them through a `WeakMap<DevframeNodeContext, T>` in a sibling `src/context.ts`. The WeakMap keys off the existing `DevframeNodeContext` so contexts are GC'd automatically when the host tears down.
270
+ When per-file RPCs need runtime values `setup(ctx)` constructs once - channels, shared-state handles, watchers, loaders, caches - expose them through a `WeakMap<DevframeNodeContext, T>` in a sibling `src/context.ts`. The WeakMap keys off the existing `DevframeNodeContext` so contexts are GC'd automatically when the host framework tears down.
271
271
 
272
272
  ```ts
273
273
  // src/context.ts
@@ -295,10 +295,10 @@ Note `setMyToolContext(ctx, …)` keys off the raw `ctx` (the same object `setup
295
295
 
296
296
  ```ts
297
297
  'my-inspector:get-modules' // ✓
298
- 'get-modules' // ✗ — may collide with other devframes sharing the host
298
+ 'get-modules' // ✗ — may collide with other devframes sharing the hub
299
299
  ```
300
300
 
301
- A [scoped context](#scoped-context-preferred) applies this prefix for RPC / shared-state / streaming. Dock and command IDs are host-level (not part of the scoped `rpc` surface) - prefix those by hand.
301
+ A [scoped context](#scoped-context-preferred) applies this prefix for RPC / shared-state / streaming. Dock and command IDs are hub-level (not part of the scoped `rpc` API) - prefix those by hand.
302
302
 
303
303
  ## RPC contracts
304
304
 
@@ -324,7 +324,7 @@ const getModules = defineRpcFunction({
324
324
  |------|----------|--------|-------------|
325
325
  | `'static'` | Data constant for a given input — dump at build time | Indefinitely | Automatic |
326
326
  | `'query'` | Read that may change; optional `dump` for build adapters | Opt-in via `cacheable` | Manual |
327
- | `'action'` | Server-state mutation | Never | Never |
327
+ | `'action'` | Node-side state mutation | Never | Never |
328
328
  | `'event'` | Fire-and-forget; no response | Never | Never |
329
329
 
330
330
  Declared `args` / `returns` schemas are **enforced at runtime** - a failing call is rejected with `DF0043` / `DF0044`. Prefer a **single object arg** (`args: [v.object({ ... })]`) over positional args - property names self-document and agents rely on them.
@@ -338,7 +338,7 @@ Declared `args` / `returns` schemas are **enforced at runtime** - a failing call
338
338
 
339
339
  Set `jsonSerializable: true` when your handler returns plain JSON - the strict serializer **throws `DF0020`** synchronously on the offending call when a value can't round-trip through JSON, surfacing next to the call in dev. `agent: {...}` requires `jsonSerializable: true` (registration throws `DF0019` otherwise) - MCP tools speak JSON.
340
340
 
341
- Through the scope, `my.rpc.broadcast({ method, args, optional?, event?, filter? })` pushes to every connected client (method name namespaced), and `my.rpc.call(name, ...args)` invokes a server function locally without transport (the scoped form of `ctx.rpc.invokeLocal`, for cross-function composition).
341
+ Through the scope, `my.rpc.broadcast({ method, args, optional?, event?, filter? })` pushes to every connected RPC client (method name namespaced), and `my.rpc.call(name, ...args)` invokes a node-side function locally without transport (the scoped form of `ctx.rpc.invokeLocal`, for cross-function composition).
342
342
 
343
343
  ## Shared state
344
344
 
@@ -355,7 +355,7 @@ state.mutate((draft) => {
355
355
  ```
356
356
 
357
357
  - Values must be serializable — no functions, no circular refs.
358
- - Mutations round-trip to all clients; the host tracks `syncIds` to avoid replay loops.
358
+ - Mutations round-trip to every RPC client; the node side tracks `syncIds` to avoid replay loops.
359
359
  - Prefer shared state over ad-hoc RPC events for UI that must reappear after reconnect.
360
360
 
361
361
  ## Streaming channels
@@ -365,28 +365,28 @@ For chunk-style data in either direction - LLM deltas, log tails, build progress
365
365
  ```ts
366
366
  const my = ctx.scope('my-inspector')
367
367
  const channel = my.rpc.streaming.create<string>('tokens', { // -> my-inspector:tokens
368
- replayWindow: 256, // server keeps last N chunks per stream id
368
+ replayWindow: 256, // node side keeps last N chunks per stream id
369
369
  closedStreamRetention: 30_000, // ms to hold finished streams for late subscribers
370
370
  })
371
371
 
372
- // Server — typically inside an action handler that returns the stream id
372
+ // Node side — typically inside an action handler that returns the stream id
373
373
  const stream = channel.start({ id: 'optional-stream-id' })
374
374
  stream.write(token) // imperative
375
375
  stream.close() // terminal success; stream.error(err) for terminal failure
376
376
  stream.signal // AbortSignal — flips when consumers cancel or all subscribers drop
377
377
  await channel.pipeFrom(sourceReadable) // start + pipe in one call
378
378
 
379
- // Client — my = (await connectDevframe()).scope('my-inspector')
379
+ // Browser side — my = (await connectDevframe()).scope('my-inspector')
380
380
  const reader = my.rpc.streaming.subscribe<string>('tokens', streamId)
381
381
  for await (const token of reader) renderToken(token)
382
- reader.cancel() // server `stream.signal` aborts
382
+ reader.cancel() // node-side `stream.signal` aborts
383
383
  ```
384
384
 
385
- The same channel exposes `openInbound()` for the server side of a client→server upload; pair it with an action that returns the id, and the client drives `my.rpc.streaming.upload<T>('files', uploadId)`. Web Streams are the canonical surface (Node 17+ ships `Readable.fromWeb` / `Writable.fromWeb` converters). Producers should poll `stream.signal.aborted` and exit cooperatively.
385
+ The same channel exposes `openInbound()` — the node-side half of an upload from the browser side; pair it with an action that returns the id, and the RPC client drives `my.rpc.streaming.upload<T>('files', uploadId)`. Web Streams are the canonical API (Node 17+ ships `Readable.fromWeb` / `Writable.fromWeb` converters). Producers should poll `stream.signal.aborted` and exit cooperatively.
386
386
 
387
387
  **Streaming vs events vs shared state:** streaming for token/chunk feeds, uploads, per-call lifecycles with cancellation, and replay-on-reconnect; `event`-typed RPC for payload-free notifications and fire-and-forget signals; shared state for long-lived UI that survives reconnect. For chat UIs, keep the conversation log in shared state and stream active responses - working example: [`examples/streaming-chat`](https://github.com/devframes/devframe/tree/main/examples/streaming-chat).
388
388
 
389
- ## Agent-native surface
389
+ ## Agent-native API
390
390
 
391
391
  Once a tool has a structured boundary, its visual panel is no longer the only interface: the same internal state and capabilities are consumable programmatically by coding agents, sharing one source of truth. RPC functions stay **private by default** and explicitly opt into agent exposure with an `agent` field. Agent-exposed functions must declare `jsonSerializable: true`.
392
392
 
@@ -405,19 +405,19 @@ defineRpcFunction({
405
405
  })
406
406
  ```
407
407
 
408
- Or register tools / resources directly on `ctx.agent.registerTool({ id, description, safety, handler })` and `ctx.agent.registerResource({ id, name, mimeType, read })`. Expose the surface over MCP:
408
+ Or register tools / resources directly on `ctx.agent.registerTool({ id, description, safety, handler })` and `ctx.agent.registerResource({ id, name, mimeType, read })`. Expose the API over MCP:
409
409
 
410
410
  ```ts
411
411
  import { createMcpServer } from 'devframe/adapters/mcp'
412
412
 
413
- await createMcpServer(devframe, { transport: 'stdio' })
413
+ await createMcpServer(myDevframe, { transport: 'stdio' })
414
414
  ```
415
415
 
416
- `@modelcontextprotocol/server` is a peer dependency. The CLI adapter also exposes `my-devframe mcp` (route host logs to stderr - stdout is the transport). Safety classifications (`'read' | 'action' | 'destructive'`) drive MCP hint annotations that agent clients use to prompt for confirmation. In a hub, `ctx.commands` entries opt into the same agent surface with an `agent` field and reach MCP through the aggregate endpoint.
416
+ `@modelcontextprotocol/server` is a peer dependency. The CLI adapter also exposes `my-tool mcp` (route node-side logs to stderr - stdout is the transport). Safety classifications (`'read' | 'action' | 'destructive'`) drive MCP hint annotations that coding agents use to prompt for confirmation. In a hub, `ctx.commands` entries opt into the same agent-facing API with an `agent` field and reach MCP through the aggregate endpoint.
417
417
 
418
418
  ## Author SPA
419
419
 
420
- Authors bring their own SPA (any framework or plain HTML). The client code is **byte-identical** whether the tool runs standalone, embedded, or inside a hub - that is the portability promise.
420
+ Authors bring their own SPA (any framework or plain HTML). The browser-side code is **byte-identical** whether the tool runs standalone, embedded, or inside a hub - that is the portability promise.
421
421
 
422
422
  ```ts
423
423
  import { connectDevframe } from 'devframe/client'
@@ -429,16 +429,20 @@ const data = await my.rpc.call('get-stats', { limit: 10 })
429
429
 
430
430
  `connectDevframe` auto-detects the backend via `./__connection.json`, resolved relative to the executing script's runtime base (so the SPA never hardcodes its mount path - build with `vite.base: './'`):
431
431
 
432
- - **websocket** (dev mode) — full read/write, requires the auth handshake. `await client.ensureTrusted()` blocks until the server accepts; listen for token updates on the `devframe-auth` BroadcastChannel.
432
+ - **websocket** (dev mode) — full read/write, requires the auth handshake. `await client.ensureTrusted()` blocks until the node side accepts; listen for token updates on the `devframe-auth` BroadcastChannel.
433
433
  - **static** (build output) — read-only, resolves calls from the baked RPC dump.
434
434
 
435
- Use `my.rpc.sharedState(key)` for observable state, `my.rpc.register(...)` to receive server broadcasts, `my.rpc.callOptional(...)` when a missing handler should resolve to `undefined`, and `my.settings.{project,global}` for persisted settings synced from the server.
435
+ Use `my.rpc.sharedState(key)` for observable state, `my.rpc.register(...)` to receive broadcasts from the node side, `my.rpc.callOptional(...)` when a missing handler should resolve to `undefined`, and `my.settings.{project,global}` for persisted settings synced from the node side.
436
+
437
+ ### In-page channel (page script ↔ panel, server-free)
438
+
439
+ For a live inspect-the-page loop, `devframe/in-page-channel` connects a devframe's **page script** (in the user app's page) to its **panels** entirely in the browser — no server, so it works identically in dev and static builds. Declare one shared protocol type; the page script is `createPageScriptChannel<P>({ name, functions })`, each panel `connectPanelChannel<P>({ name })`. Functions use `defineChannelFunction` (the `defineRpcFunction` shape: `type: 'event'` fans out to every panel, `query`/`action` are request/response); `channel.sharedState.get(key)` mirrors `rpc.sharedState` with the page script as authority (panels get automatic replay). The handshake is panel-initiated with retry — boot order and reloads don't matter — and panels expose `status`/`whenConnected(ms)` for "page script not loaded" fallbacks. Channel names follow `devframes:plugin:<slug>`. The a11y inspector's scan/highlight loop is the reference use.
436
440
 
437
441
  ## The Hub
438
442
 
439
- A single devframe is one portable tool; a hub is where many tools **meet and collaborate**. `@devframes/hub` is the framework-neutral composition layer. It adds the orchestration subsystems that only make sense when tools share a UI, and it ships no UI of its own - a viewer product fills the `ui` slot.
443
+ A single devframe is one portable tool; a hub is where many tools **meet and collaborate**. `@devframes/hub` is the framework-neutral composition layer. It adds the orchestration subsystems that only make sense when tools share a UI, and it ships no UI of its own - a hub UI provider fills the `ui` slot.
440
444
 
441
- `initHub()` puts the whole collection behind one standard handler with the same surface and mount snippets as `initDevframe`:
445
+ `initHub()` puts the whole collection behind one standard handler with the same API and mount snippets as `initDevframe`:
442
446
 
443
447
  ```ts
444
448
  import { createUi } from '@devframes/hub-ui'
@@ -449,7 +453,7 @@ import createTerminalsDevframe from '@devframes/plugin-terminals'
449
453
  export const hub = initHub({
450
454
  base: DEVFRAMES_HUB_BASE, // required — the conventional `/__devframes/`
451
455
  devframes: [createInspectDevframe(), createTerminalsDevframe()],
452
- ui: createUi(), // reference viewer + floating dock; `ui: false` for headless
456
+ ui: createUi(), // reference hub UI provider + floating dock; `ui: false` for headless
453
457
  configure(ctx) {
454
458
  ctx.commands.register({ id: 'app:hello', title: 'Hello', handler: () => 'hi' })
455
459
  },
@@ -458,20 +462,20 @@ export const hub = initHub({
458
462
  hub.handler // the whole devtools ecosystem as Request -> Response
459
463
  ```
460
464
 
461
- Every mounted devframe runs its `setup()` against **one shared hub context**: a merged RPC registry (frames can call each other's functions), one shared-state store, one WebSocket transport, and one auth gate. Frame ids become URL segments (`<base><id>/`) and are validated (reserved → `DF8000`, non-route-safe → `DF8004`).
465
+ Every mounted devframe runs its `setup()` against **one shared hub context**: a merged RPC registry (mounted devframes can call each other's functions), one shared-state store, one WebSocket transport, and one auth gate. Devframe ids become URL segments (`<base><id>/`) and are validated (reserved → `DF8000`, non-route-safe → `DF8004`).
462
466
 
463
467
  ### Hub subsystems
464
468
 
465
469
  A hub-aware `DevframeHubContext` extends `DevframeNodeContext` with four subsystems:
466
470
 
467
- | Subsystem | Surface | Purpose |
471
+ | Subsystem | API | Purpose |
468
472
  |-----------|---------|---------|
469
473
  | `ctx.docks` | `register / update / values / activate` | Dock entries (iframe, launcher, custom-render, group, and opt-in types) and cross-iframe activation. |
470
474
  | `ctx.terminals` | `register / startChildProcess` | Aggregate terminal sessions, stream output over a well-known channel. |
471
- | `ctx.messages` | `add / update / remove / clear` | Server-side toast/notification queue (FIFO, capped 1000). |
475
+ | `ctx.messages` | `add / update / remove / clear` | Node-side toast/notification queue (FIFO, capped 1000). |
472
476
  | `ctx.commands` | `register / execute / list` | Hierarchical command palette with keybindings and `when` clauses. |
473
477
 
474
- The dock union is **open** - opt-in integrations contribute their own entry types (e.g. JSON-Render adds a `json-render` dock type with no JSON-render dependency in the hub). `ctx.docks.activate(dockId, params?)` steers which dock the viewer shows; from a mounted iframe, `rpc.call('hub:docks:activate', { dockId, params })` does the same cross-iframe. A `type: 'launcher'` dock binds a command, streams a `digest` line, and jumps to its terminal session - the pattern that lets an analyzer spawn `vite build` and navigate the user to its output.
478
+ The dock union is **open** - opt-in packages contribute their own entry types (e.g. JSON-Render adds a `json-render` dock type with no JSON-render dependency in the hub). `ctx.docks.activate(dockId, params?)` steers which dock entry the hub UI provider shows; from a mounted iframe, `rpc.call('hub:docks:activate', { dockId, params })` does the same cross-iframe. A `type: 'launcher'` dock binds a command, streams a `digest` line, and jumps to its terminal session - the pattern that lets an analyzer spawn `vite build` and navigate the user to its output.
475
479
 
476
480
  ### Mounting into a hub
477
481
 
@@ -486,27 +490,27 @@ await ctx.install(myDevframe)
486
490
 
487
491
  When a devframe sharing an already-mounted `id` is installed, its `duplicationStrategy` (`'warn'` default / `'silent'` / `'throw'` / `'duplicate'`) decides the outcome.
488
492
 
489
- ### The protocol — what a viewer sees
493
+ ### The protocol — what a hub UI provider sees
490
494
 
491
- A hub-aware UI imports no hub classes; it reads shared-state keys and one RPC method:
495
+ A hub UI provider imports no hub classes; it reads shared-state keys and one RPC method:
492
496
 
493
497
  | Channel | Type | Carries |
494
498
  |---------|------|---------|
495
499
  | `devframe:docks` (shared state) | `DevframeDockEntry[]` | Every registered dock entry. |
496
500
  | `devframe:commands` (shared state) | `DevframeServerCommandEntry[]` | Serializable command list (handlers stripped). |
497
501
  | `devframe:docks:active` (shared state) | `DevframeDocksActiveState` | Most recent dock-activation request. |
498
- | `hub:commands:execute` (RPC) | `(id, ...args) => unknown` | Server-side command dispatch. |
499
- | `hub:docks:activate` (RPC) | `({ dockId, params? }) => void` | Switch the active dock from any client. |
502
+ | `hub:commands:execute` (RPC) | `(id, ...args) => unknown` | Node-side command dispatch. |
503
+ | `hub:docks:activate` (RPC) | `({ dockId, params? }) => void` | Switch the active dock from any RPC client. |
500
504
 
501
- Plus broadcasts (`devframe:docks:activate`, `devframe:terminals:updated`, `devframe:messages:updated`). The hub also ships a headless browser runtime, `createDevframeClientHost()` from `@devframes/hub/client`: booted in the host page, it assembles the shared client context from this protocol and imports each dock entry's client script into that page - how a plugin like the a11y inspector runs code inside the page being inspected.
505
+ Plus broadcasts (`devframe:docks:activate`, `devframe:terminals:updated`, `devframe:messages:updated`). The hub also ships a headless client runtime, `createDevframeClientRuntime()` from `@devframes/hub/client`: booted in the host page, it assembles the shared client context from this protocol and imports each dock entry's client script into that page - how a built-in devframe like the a11y inspector runs its page script inside the user app's page.
502
506
 
503
507
  ### The `ui` slot
504
508
 
505
- The hub is headless; `DevframeHubUi` is pure data (`viewer` / `embedded` / `assets` / `setup`). `@devframes/hub-ui`'s `createUi()` is the reference implementation - a standalone viewer plus a floating dock injected via one `<script type="module" src="/__devframes/embedded.js">` tag. It takes `branding`, `dockPreferences`, and `embeddedVisibility` (`'normal'` / `'passive'` / `'hidden'`). A viewer product supplies a different object to the same slot and reuses all the infrastructure. Renderer modules for opt-in dock types compose at the hub via `initHub({ renderers })`.
509
+ The hub is headless; `DevframeHubUi` is pure data (`viewer` / `embedded` / `assets` / `setup`). `@devframes/hub-ui`'s `createUi()` is the reference hub UI provider - a standalone devtools page plus a floating dock injected via one `<script type="module" src="/__devframes/embedded.js">` tag. It takes `branding`, `dockPreferences`, and `embeddedVisibility` (`'normal'` / `'passive'` / `'hidden'`). Another hub UI provider supplies a different object to the same slot and reuses all the infrastructure. Renderer modules for opt-in dock types compose at the hub via `initHub({ renderers })`.
506
510
 
507
- Two copyable reference hubs mount every built-in plugin behind an icon dock - the shape Vite DevTools wears, shrunk to the smallest thing you can build your own viewer from: [`examples/hub-vite/`](https://github.com/devframes/devframe/tree/main/examples/hub-vite) (~120-line Vite plugin host, vanilla DOM UI) and [`examples/hub-next/`](https://github.com/devframes/devframe/tree/main/examples/hub-next) (Next.js App Router). The `hub-*-minimal` family shows the default `createUi()` mount across Vite, Next, Nitro, Hono, Fastify, SvelteKit, Deno, and Rsbuild.
511
+ Two copyable reference hubs mount every built-in devframe behind an icon dock rail - the shape Vite DevTools wears, shrunk to the smallest thing you can build your own hub UI provider from: [`examples/hub-vite/`](https://github.com/devframes/devframe/tree/main/examples/hub-vite) (~120-line Vite host, vanilla DOM UI) and [`examples/hub-next/`](https://github.com/devframes/devframe/tree/main/examples/hub-next) (Next.js App Router). The `hub-*-minimal` family shows the default `createUi()` mount across Vite, Next, Nitro, Hono, Fastify, SvelteKit, Deno, and Rsbuild.
508
512
 
509
- ## Framework packages: two scopes
513
+ ## Framework kits: two scopes
510
514
 
511
515
  `@devframes/vite`, `@devframes/next`, and `@devframes/nuxt` each split into two clearly-scoped subpaths; the bare root import throws with a pointer to both.
512
516
 
@@ -518,13 +522,13 @@ Two copyable reference hubs mount every built-in plugin behind an icon dock - th
518
522
  - `viteDevframeHub()` (shares Vite's server, injects the dock), `nextDevframeHub()` (side-car socket, App Router route), the Nuxt hub module.
519
523
  - `@devframes/hub` and `@devframes/hub-ui` are optional peers of these packages.
520
524
 
521
- Vite and Nuxt already have native hub viewers ([Vite DevTools](https://devtools.vite.dev/), [Nuxt DevTools](https://devtools.nuxt.com/)), so `@devframes/vite/hub` and `@devframes/nuxt/hub` print a one-time recommendation to prefer those (silence with `{ quiet: true }`); `@devframes/next/hub` has no native counterpart and stays quiet.
525
+ Vite and Nuxt already have native hub UI providers ([Vite DevTools](https://devtools.vite.dev/), [Nuxt DevTools](https://devtools.nuxt.com/)), so `@devframes/vite/hub` and `@devframes/nuxt/hub` print a one-time recommendation to prefer those (silence with `{ quiet: true }`); `@devframes/next/hub` has no native counterpart and stays quiet.
522
526
 
523
- For Vite DevTools specifically, `createPluginFromDevframe(def, opts?)` from `@vitejs/devtools-kit/node` adapts a definition into the kit's plugin interface (`{ name, devtools: { setup, capabilities } }`), auto-deriving an iframe dock entry from `id` / `name` / `icon` / `basePath`. Pass `options.setup` for richer host-side behaviour (custom-render docks, terminals, palette commands) on the kit-augmented context. The factory lives in the kit, not devframe, so devframe stays free of any `@vitejs/*` dependency.
527
+ For Vite DevTools specifically, `createPluginFromDevframe(def, opts?)` from `@vitejs/devtools-kit/node` adapts a definition into the kit's plugin interface (`{ name, devtools: { setup, capabilities } }`), auto-deriving an iframe dock entry from `id` / `name` / `icon` / `basePath`. Pass `options.setup` for richer node-side behaviour (custom-render docks, terminals, palette commands) on the kit-augmented context. The factory lives in the kit, not devframe, so devframe stays free of any `@vitejs/*` dependency.
524
528
 
525
529
  ## When clauses
526
530
 
527
- Gate host-side dock / command visibility with VS Code-style expressions. The runtime + types ship bundled from `devframe/utils/when` - no separate install.
531
+ Gate dock / command visibility with VS Code-style expressions. The runtime + types ship bundled from `devframe/utils/when` - no separate install.
528
532
 
529
533
  ```ts
530
534
  when: 'clientType == embedded'
@@ -532,11 +536,11 @@ when: 'dockOpen && !paletteOpen'
532
536
  when: 'my-inspector.ready && count >= 10'
533
537
  ```
534
538
 
535
- Built-in context: `clientType` (`'embedded' | 'standalone'`), `dockOpen`, `paletteOpen`, `dockSelectedId`. Plugins add namespaced keys (`.` or `:` separators). `when` clauses evaluate **client-side only** and are not enforced for agent calls - only gate a command with `when` if running it outside its UI context is safe.
539
+ Built-in context: `clientType` (`'embedded' | 'standalone'`), `dockOpen`, `paletteOpen`, `dockSelectedId`. Devframes add namespaced keys (`.` or `:` separators). `when` clauses evaluate **browser-side only** and are not enforced for agent calls - only gate a command with `when` if running it outside its UI context is safe.
536
540
 
537
541
  ## CLI adapter subcommands
538
542
 
539
- `createCac(devframe).parse()` gives three subcommands out of the box:
543
+ `createCac(myDevframe).parse()` gives three subcommands out of the box:
540
544
 
541
545
  | Subcommand | Action |
542
546
  |------------|--------|
@@ -564,7 +568,7 @@ defineRpcFunction({
564
568
  })
565
569
  ```
566
570
 
567
- At runtime, static clients look up the argument hash in the dump; misses resolve to `fallback` (or throw if absent). To bake an RPC this devframe doesn't own (e.g. a wire service's), declare it under the definition's `rpc.snapshot`.
571
+ At runtime, static RPC clients look up the argument hash in the dump; misses resolve to `fallback` (or throw if absent). To bake an RPC this devframe doesn't own (e.g. a wire service's), declare it under the definition's `rpc.snapshot`.
568
572
 
569
573
  ## Bundled utilities
570
574
 
@@ -584,25 +588,25 @@ Devframe re-exports a curated set of helpers under `devframe/utils/*`. They are
584
588
  | `s` from `devframe/utils/simple-schema` | — | Zero-dep Standard Schema builder (`s.object`, `s.string`, …) for validator-neutral first-party code |
585
589
  | `evaluateWhen` / `WhenExpression` from `devframe/utils/when` | `whenexpr` | When-clause expressions |
586
590
 
587
- For "open file in editor" + "reveal in finder", prefer the `@devframes/service-open` wire service (declare `services: [{ package: '@devframes/service-open' }]` on the definition, gate client UI on `rpc.services.has(...)`) - one host-level installation shared by every plugin.
591
+ For "open file in editor" + "reveal in finder", prefer the `@devframes/service-open` wire service (declare `services: [{ package: '@devframes/service-open' }]` on the definition, gate browser-side UI on `rpc.services.has(...)`) - one installation at the host framework shared by every devframe.
588
592
 
589
593
  ## Security (secure by default)
590
594
 
591
- RPC handlers run with the full privileges of the host process, so the boundary that matters is who may connect.
595
+ RPC handlers run with the full privileges of the host framework's process, so the boundary that matters is who may connect.
592
596
 
593
- - **`auth` defaults to `true`** — dev-mode connections must authenticate before calls are accepted. In a **hub**, one gate at the one shared transport covers every frame, the hub built-ins, and the MCP route; mounted frames have no gates of their own.
597
+ - **`auth` defaults to `true`** — dev-mode connections must authenticate before calls are accepted. In a **hub**, one gate at the one shared transport covers every mounted devframe, the hub built-ins, and the MCP route; mounted devframes have no gates of their own.
594
598
  - **`auth: false` trusts every reachable connection.** Only for single-user `localhost` tools - never with a non-loopback bind host, a tunnel, or a shared/CI environment.
595
599
  - **Authentication** exchanges a 6-digit one-time code (shown in the developer's terminal) for a node-issued bearer token. Single-use, expires in 5 min, constant-time compared, rotates after repeated failures.
596
- - **Magic-link (optional):** the code rides the URL **fragment** (`#devframe_otp=<code>`), never sent to the server; `connectDevframe` reads, exchanges, and strips it. Only the single-use code ever rides a URL, never the bearer.
600
+ - **Magic-link (optional):** the code rides the URL **fragment** (`#devframe_otp=<code>`), never sent to the node side; `connectDevframe` reads, exchanges, and strips it. Only the single-use code ever rides a URL, never the bearer.
597
601
  - **Tokens are secrets.** Serve over `wss://` / `https://` beyond loopback. Never log or bake them into build output. Revoke via `revokeAuthToken(...)`.
598
- - **Authorize handlers.** Any trusted client can call any registered function - validate inputs, and mark state-changing functions `type: 'destructive'` so MCP/agent clients prompt first. `when` clauses are UI-only and not enforced for agent calls.
602
+ - **Authorize handlers.** Any trusted RPC client can call any registered function - validate inputs, and mark state-changing functions `type: 'destructive'` so coding agents prompt first. `when` clauses are UI-only and not enforced for agent calls.
599
603
  - **The MCP route requires an Origin** — the route-based MCP server rejects `Origin`-less requests, so it isn't reachable by an arbitrary local process.
600
604
 
601
605
  See [Security](https://devfra.me/security) for the full reference.
602
606
 
603
607
  ## Testing
604
608
 
605
- - Unit-test host classes with fake contexts.
609
+ - Unit-test node-side classes with fake contexts.
606
610
  - Run `templates/counter-devframe.ts` under each adapter for integration coverage.
607
611
  - Snapshot the build-static RPC dump (`<outDir>/.devframe/.rpc-dump/index.json`) to catch drift in `static` function outputs.
608
612
 
@@ -615,7 +619,7 @@ Devframe-level (one portable tool):
615
619
  - [Scoped Context](https://devfra.me/guide/scoped-context) — `ctx.scope(id)`, auto-namespacing, `settings`
616
620
  - [Adapters](https://devfra.me/adapters/) — cli / dev / build / mcp / embedded
617
621
  - [RPC](https://devfra.me/guide/rpc) — types, Standard Schema, broadcasts, dumps
618
- - [Shared State](https://devfra.me/guide/shared-state) · [Streaming](https://devfra.me/guide/streaming) · [When Clauses](https://devfra.me/guide/when-clauses)
622
+ - [Shared State](https://devfra.me/guide/shared-state) · [Streaming](https://devfra.me/guide/streaming) · [When Clauses](https://devfra.me/references/when-clauses)
619
623
  - [Diagnostics](https://devfra.me/guide/diagnostics) · [Services](https://devfra.me/guide/services) · [Client](https://devfra.me/guide/client)
620
624
  - [Security](https://devfra.me/guide/security) · [Agent-Native](https://devfra.me/guide/agent-native)
621
625
 
@@ -623,6 +627,6 @@ Hub & frameworks (composing many tools):
623
627
 
624
628
  - [Hub](https://devfra.me/guide/hub) — subsystems, docks, protocol, `ctx.install`
625
629
  - [Serve a Hub Anywhere](https://devfra.me/guide/hub-initiate) — `initHub`, the `ui` slot, one auth
626
- - [Client Scripts & Client Context](https://devfra.me/guide/client-context) — `createDevframeClientHost`, dock-script contract
630
+ - [Client Scripts & Client Context](https://devfra.me/guide/client-context) — `createDevframeClientRuntime`, client-script contract
627
631
  - [Frameworks](https://devfra.me/frameworks/) — [Vite](https://devfra.me/frameworks/vite) · [Next](https://devfra.me/frameworks/next) · [Nuxt](https://devfra.me/frameworks/nuxt)
628
632
  - [Vite DevTools adapter](https://devfra.me/adapters/vite) — `createPluginFromDevframe`