From f88a023fe90677cd0ab4bd91c2e6e797994aa294 Mon Sep 17 00:00:00 2001 From: "Anthony Fu (via agent)" Date: Wed, 26 Aug 2026 11:28:43 +0000 Subject: [PATCH 1/7] docs: add a canonical Terms page and terminology rules, renumber the guide One name per concept: a new guide-leading Terms page fixes the vocabulary (a devframe, built-in devframe, host framework, host page, user app, node side / browser side, client runtime, page script, in-page channel, viewer, dock entry/rail/panel, storage scopes), AGENTS.md gains the matching regulation, and the guide pages shift up one number to lead with it. Stale numbered-path comments in code follow the renumber. --- AGENTS.md | 37 +++++++--- docs/content/1.guide/1.terms.md | 71 +++++++++++++++++++ .../{9.diagnostics.md => 10.diagnostics.md} | 8 +-- ...{10.when-clauses.md => 11.when-clauses.md} | 20 +++--- ...standalone-cli.md => 12.standalone-cli.md} | 8 +-- .../1.guide/{12.client.md => 13.client.md} | 40 +++++------ .../{13.transports.md => 14.transports.md} | 20 +++--- .../{14.security.md => 15.security.md} | 12 ++-- ...{15.agent-native.md => 16.agent-native.md} | 10 +-- docs/content/1.guide/{16.hub.md => 17.hub.md} | 46 ++++++------ ...client-context.md => 18.client-context.md} | 36 +++++----- ...{18.hub-initiate.md => 19.hub-initiate.md} | 36 +++++----- ...md => 2.tutorial-server-data-inspector.md} | 36 +++++----- .../{19.services.md => 20.services.md} | 32 ++++----- ...{20.deep-linking.md => 21.deep-linking.md} | 6 +- .../1.guide/{21.events.md => 22.events.md} | 22 +++--- ...23.build-your-own-json-render-frontend.md} | 2 +- ...-hub-ui.md => 24.build-your-own-hub-ui.md} | 14 ++-- .../{24.built-with.md => 25.built-with.md} | 14 ++-- ...definition.md => 3.devframe-definition.md} | 20 +++--- docs/content/1.guide/{3.rpc.md => 4.rpc.md} | 32 ++++----- .../{4.shared-state.md => 5.shared-state.md} | 18 ++--- .../{5.streaming.md => 6.streaming.md} | 30 ++++---- ...{6.client-assets.md => 7.client-assets.md} | 2 +- ....scoped-context.md => 8.scoped-context.md} | 12 ++-- .../{8.json-render.md => 9.json-render.md} | 14 ++-- docs/content/1.guide/index.md | 28 ++++---- examples/hub-deno-minimal/src/hub.ts | 2 +- examples/hub-fastify-minimal/src/hub.ts | 2 +- examples/hub-hono-minimal/src/app.ts | 2 +- examples/hub-next-minimal/src/client/hub.ts | 2 +- .../src/client/devframe/next-devframe-hub.ts | 2 +- examples/hub-nitro-minimal/hub.ts | 2 +- .../hub-rsbuild-minimal/rsbuild.config.ts | 2 +- examples/hub-sveltekit-minimal/src/hub.ts | 2 +- examples/hub-vite-minimal/vite.config.ts | 2 +- examples/sse-basic/vite.config.ts | 2 +- packages/devframe/src/events.ts | 2 +- packages/hub/src/events.ts | 2 +- 39 files changed, 368 insertions(+), 282 deletions(-) create mode 100644 docs/content/1.guide/1.terms.md rename docs/content/1.guide/{9.diagnostics.md => 10.diagnostics.md} (91%) rename docs/content/1.guide/{10.when-clauses.md => 11.when-clauses.md} (85%) rename docs/content/1.guide/{11.standalone-cli.md => 12.standalone-cli.md} (96%) rename docs/content/1.guide/{12.client.md => 13.client.md} (81%) rename docs/content/1.guide/{13.transports.md => 14.transports.md} (56%) rename docs/content/1.guide/{14.security.md => 15.security.md} (89%) rename docs/content/1.guide/{15.agent-native.md => 16.agent-native.md} (91%) rename docs/content/1.guide/{16.hub.md => 17.hub.md} (77%) rename docs/content/1.guide/{17.client-context.md => 18.client-context.md} (64%) rename docs/content/1.guide/{18.hub-initiate.md => 19.hub-initiate.md} (65%) rename docs/content/1.guide/{1.tutorial-server-data-inspector.md => 2.tutorial-server-data-inspector.md} (79%) rename docs/content/1.guide/{19.services.md => 20.services.md} (69%) rename docs/content/1.guide/{20.deep-linking.md => 21.deep-linking.md} (78%) rename docs/content/1.guide/{21.events.md => 22.events.md} (82%) rename docs/content/1.guide/{22.build-your-own-json-render-frontend.md => 23.build-your-own-json-render-frontend.md} (97%) rename docs/content/1.guide/{23.build-your-own-hub-ui.md => 24.build-your-own-hub-ui.md} (87%) rename docs/content/1.guide/{24.built-with.md => 25.built-with.md} (86%) rename docs/content/1.guide/{2.devframe-definition.md => 3.devframe-definition.md} (91%) rename docs/content/1.guide/{3.rpc.md => 4.rpc.md} (83%) rename docs/content/1.guide/{4.shared-state.md => 5.shared-state.md} (81%) rename docs/content/1.guide/{5.streaming.md => 6.streaming.md} (83%) rename docs/content/1.guide/{6.client-assets.md => 7.client-assets.md} (98%) rename docs/content/1.guide/{7.scoped-context.md => 8.scoped-context.md} (84%) rename docs/content/1.guide/{8.json-render.md => 9.json-render.md} (91%) diff --git a/AGENTS.md b/AGENTS.md index ab7304af..dbd076be 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 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 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 - viewers (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 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/1.guide/1.terms.md`](docs/content/1.guide/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** (`createDevframeClientHost()`), **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: **viewer** (a hub UI implementation - never "shell"), **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/1.guide/22.events.md`](docs/content/1.guide/22.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 `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. - **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. +- The **full hub examples** (`examples/hub-vite`, `examples/hub-next`) consume `.../hub` on the node side but keep hand-rolling their own viewer against `@devframes/hub/client` with `ui: false` - that hand-rolled viewer 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 diff --git a/docs/content/1.guide/1.terms.md b/docs/content/1.guide/1.terms.md new file mode 100644 index 00000000..f566586c --- /dev/null +++ b/docs/content/1.guide/1.terms.md @@ -0,0 +1,71 @@ +--- +title: 'Terms' +description: 'The canonical vocabulary of these docs: one name per concept, the API or package that anchors it, and how the pieces talk to each other.' +--- + +Every concept in these docs has exactly one name. This page fixes that vocabulary — when a term below appears anywhere in the documentation, it carries the meaning defined here. + +## Core + +| Term | Meaning | Anchor | +|------|---------|--------| +| **Devframe** | The product: a framework-neutral foundation for building a devtool once and running it everywhere. | — | +| `devframe` | The npm package the foundation ships as. | `devframe` | +| **a devframe** | One tool: a definition plus its SPA, mountable anywhere. | `defineDevframe()`, `DevframeDefinition` | +| **built-in devframe** | A ready-to-run devframe shipped from this repo (data inspector, inspect, OG, a11y, git, terminals, code-server, assets). The `plugin-` npm prefix only sets these packages apart from core packages — Devframe has no plugin concept. | `@devframes/plugin-*` | +| **adapter** | A deployment entry point under `devframe/adapters/*`: cli (cac), dev, build, vite, embedded, mcp. | `devframe/adapters/*` | +| **framework kit** | Framework conventions over the standard handler, each split into a `/single` and a `/hub` scope. | `@devframes/vite`, `@devframes/nuxt`, `@devframes/next` | +| **opt-in package** | A capability shipped as its own package and added when needed. | `@devframes/json-render` | +| **hub** | The composition layer that puts many devframes behind one handler; *a hub* is one `initHub()` instance. | `@devframes/hub`, `initHub()` | +| **viewer** | A hub UI implementation: the node-side `ui` slot plus the browser-side context contract. `@devframes/hub-ui` is the reference viewer. | `initHub({ ui })` | + +## Node side + +A devframe has two halves: the **node side** registers RPC functions and owns state; the **browser side** renders and calls them. + +| Term | Meaning | Anchor | +|------|---------|--------| +| **node side** | The half of a devframe running in the Node process. | `setup(ctx)` | +| **host framework** | The environment a devframe or hub mounts into: a Vite dev server, a Next.js app, a Hono server. Named forms — *the Vite host*, *a Next.js host* — refer to a specific one. | `DevframeHost` | +| **dev server** | The standalone HTTP server the dev adapter starts. | `createDevServer()` | +| **side-car server** | The separate RPC/WebSocket process used when a host framework's handlers never see upgrade requests. | — | +| **hosted / standalone** | The two mount contexts: hosted adapters (vite, embedded) default the base path to `/__/`; standalone adapters (cli, build) default to `/`. | `resolveBasePath()` | +| **workspace scope** | Committable per-repository storage. | `DevframeStorageScope` | +| **project scope** | Per-checkout storage, gitignored. | `DevframeStorageScope` | +| **global scope** | Per-user storage. | `DevframeStorageScope` | + +## Browser side + +| Term | Meaning | Anchor | +|------|---------|--------| +| **browser side** | The half of a devframe running in a page. | `devframe/client` | +| **user app** | The application being developed and inspected. | — | +| **host page** | The browser document where the client runtime boots — in dev, usually the user app's own page. | — | +| **client runtime** | The headless runtime booted once per host page: it connects RPC, assembles the client context, and imports client scripts. | `createDevframeClientHost()` | +| **client context** | The shared object client scripts receive: panel, docks, commands, when-clauses. | `DevframeClientContext` | +| **client script** | A dock entry's script, imported into the host page by the client runtime. | `clientScript` | +| **page script** | A devframe's script running in the user app's page — loaded as a client script or standalone. The a11y page script runs axe-core. | — | +| **RPC client** | The typed connection a browser surface gets. | `connectDevframe()` | +| **SPA** | A devframe's built web interface; `clientAssets` says where it lives. | `clientAssets` | +| **panel** | A devframe's SPA as a rendered surface — in a dock panel or standalone. | — | +| **surface** | Any rendered browser view: a panel, a dock iframe, a standalone SPA. | — | +| **coding agent** | An agent consuming a devframe over MCP — the only agent in these docs. | `createMcpServer()` | + +## Hub + +| Term | Meaning | Anchor | +|------|---------|--------| +| **mounted devframe** | A devframe served inside a hub under `/`. | `initHub({ devframes })` | +| **dock entry** | A registry item: iframe, launcher, custom-render, group, or json-render. | `ctx.docks` | +| **dock rail** | The bar listing every mounted tool. | — | +| **dock panel** | The open drawer rendering the active dock entry. | — | + +## Communication paths + +Three distinct paths connect the pieces; each has its own name. + +| Path | Between | Transport | +|------|---------|-----------| +| **RPC** | browser side ↔ node side | WebSocket or static snapshot, via `connectDevframe()` | +| **client context** | client scripts ↔ client runtime | a shared object inside the host page | +| **in-page channel** | page script ↔ panel | same-origin, entirely in-browser (e.g. a `BroadcastChannel`) | diff --git a/docs/content/1.guide/9.diagnostics.md b/docs/content/1.guide/10.diagnostics.md similarity index 91% rename from docs/content/1.guide/9.diagnostics.md rename to docs/content/1.guide/10.diagnostics.md index 00f24832..8a0c185a 100644 --- a/docs/content/1.guide/9.diagnostics.md +++ b/docs/content/1.guide/10.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/10.when-clauses.md b/docs/content/1.guide/11.when-clauses.md similarity index 85% rename from docs/content/1.guide/10.when-clauses.md rename to docs/content/1.guide/11.when-clauses.md index 06dcb88c..154f3d93 100644 --- a/docs/content/1.guide/10.when-clauses.md +++ b/docs/content/1.guide/11.when-clauses.md @@ -1,9 +1,9 @@ --- title: 'When Clauses' -description: 'When clauses gate visibility and executability of docks, commands, and UI surfaces via VS Code''s when-clause contexts. The evaluator whenexpr re-exports at devframe/utils/when.' +description: 'When clauses gate visibility and executability of docks, commands, and custom UI via VS Code''s when-clause contexts. The evaluator whenexpr re-exports at devframe/utils/when.' --- -When clauses gate visibility and executability of docks, commands, and UI surfaces via [VS Code's when-clause contexts](https://code.visualstudio.com/api/references/when-clause-contexts). The evaluator [`whenexpr`](https://github.com/antfu/whenexpr) re-exports at `devframe/utils/when`. +When clauses gate visibility and executability of docks, commands, and custom UI via [VS Code's when-clause contexts](https://code.visualstudio.com/api/references/when-clause-contexts). The evaluator [`whenexpr`](https://github.com/antfu/whenexpr) re-exports at `devframe/utils/when`. ## Usage @@ -22,7 +22,7 @@ ctx.commands.register({ ### On dock entries -Gates dock-bar visibility. +Gates dock-rail visibility. ```ts ctx.docks.register({ @@ -37,7 +37,7 @@ ctx.docks.register({ ### Render-only visibility on dock entries -A dock entry also takes `visibility`, a second expression that hides only its dock-bar button while keeping the entry registered and reachable (e.g. a `subTabs` anchor). +A dock entry also takes `visibility`, a second expression that hides only its dock-rail button while keeping the entry registered and reachable (e.g. a `subTabs` anchor). ```ts ctx.docks.register({ @@ -89,21 +89,21 @@ when: 'false' // never visible when: 'clientType == embedded' // only embedded when: 'dockOpen && !paletteOpen' // dock open and palette closed when: '(clientType == embedded && dockOpen) || clientType == standalone' -when: 'my-devtool.ready' // custom plugin context +when: 'my-devtool.ready' // custom devframe context ``` ## Built-in context variables | Variable | Type | Description | |----------|------|-------------| -| `clientType` | `'embedded' \| 'standalone'` | `embedded` in the host overlay, `standalone` in a separate window. | +| `clientType` | `'embedded' \| 'standalone'` | `embedded` in the host page's overlay, `standalone` in a separate window. | | `dockOpen` | `boolean` | Dock panel open. | | `paletteOpen` | `boolean` | Command palette open. | | `dockSelectedId` | `string` | Selected dock entry ID; `''` if none. | ## Namespaced context keys -Plugins add keys with `.` or `:`: +Devframes add keys with `.` or `:`: ```ts context['my-devtool.ready'] = true @@ -144,9 +144,9 @@ defineCommand({ }) ``` -### Key validation with plugin contexts +### Key validation with devframe contexts -The default `WhenContext` leaves plugin keys open-ended (`[key: string]: unknown`). To validate names, declare a narrower context and wrapper: +The default `WhenContext` leaves devframe keys open-ended (`[key: string]: unknown`). To validate names, declare a narrower context and wrapper: ```ts import type { WhenContext, WhenExpression } from 'devframe/utils/when' @@ -214,4 +214,4 @@ Returns one (possibly namespaced) key's value. ### `WhenExpression` -The branded `whenexpr` expression type for typed `define*` helpers ([above](#key-validation-with-plugin-contexts)). +The branded `whenexpr` expression type for typed `define*` helpers ([above](#key-validation-with-devframe-contexts)). diff --git a/docs/content/1.guide/11.standalone-cli.md b/docs/content/1.guide/12.standalone-cli.md similarity index 96% rename from docs/content/1.guide/11.standalone-cli.md rename to docs/content/1.guide/12.standalone-cli.md index 70e1f32d..9be3b62a 100644 --- a/docs/content/1.guide/11.standalone-cli.md +++ b/docs/content/1.guide/12.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/13.client.md similarity index 81% rename from docs/content/1.guide/12.client.md rename to docs/content/1.guide/13.client.md index 33829b79..9ffcef11 100644 --- a/docs/content/1.guide/12.client.md +++ b/docs/content/1.guide/13.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 @@ -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' @@ -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,11 +343,11 @@ async function loadModules() { ### Recovering -The client doesn't reconnect on its own — reload or re-run your connect routine: +The RPC client doesn't reconnect on its own — reload or re-run your connect routine: ```ts async function reconnect() { - rpc = await connectDevframe() // a new client; re-subscribe your listeners + rpc = await connectDevframe() // a new RPC client; re-subscribe your listeners render() } ``` diff --git a/docs/content/1.guide/13.transports.md b/docs/content/1.guide/14.transports.md similarity index 56% rename from docs/content/1.guide/13.transports.md rename to docs/content/1.guide/14.transports.md index dbcaca94..baaebe3e 100644 --- a/docs/content/1.guide/13.transports.md +++ b/docs/content/1.guide/14.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/15.security.md similarity index 89% rename from docs/content/1.guide/14.security.md rename to docs/content/1.guide/15.security.md index 0babba2c..ace99013 100644 --- a/docs/content/1.guide/14.security.md +++ b/docs/content/1.guide/15.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/16.agent-native.md similarity index 91% rename from docs/content/1.guide/15.agent-native.md rename to docs/content/1.guide/16.agent-native.md index fcf3c122..528c9863 100644 --- a/docs/content/1.guide/15.agent-native.md +++ b/docs/content/1.guide/16.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/17.hub.md similarity index 77% rename from docs/content/1.guide/16.hub.md rename to docs/content/1.guide/17.hub.md index bfae9506..5b343e1c 100644 --- a/docs/content/1.guide/16.hub.md +++ b/docs/content/1.guide/17.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 — viewers 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 — viewers provide their own atop the hub's RPC + shared-state protocol. ![Hub screenshot](/screenshots/hub-1.png) @@ -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({ @@ -75,7 +75,7 @@ A `type: 'launcher'` dock entry is a one-click action tile. Three optional `laun | `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 viewers 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 viewer sees -A hub-aware UI imports no hub classes; it reads these shared-state keys and RPC methods: +A viewer 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](/guide/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, `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)). ## 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/18.client-context.md similarity index 64% rename from docs/content/1.guide/17.client-context.md rename to docs/content/1.guide/18.client-context.md index 81855fe4..d0536576 100644 --- a/docs/content/1.guide/17.client-context.md +++ b/docs/content/1.guide/18.client-context.md @@ -1,19 +1,19 @@ --- 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: ```ts -// main.ts — the host app / hub page's browser entry +// main.ts — the host page's browser entry import { connectDevframe, createDevframeClientHost } from '@devframes/hub/client' const rpc = await connectDevframe({ baseURL: '/__hub/' }) @@ -37,7 +37,7 @@ 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()`. | @@ -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,34 +76,34 @@ 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 viewer can override with `createDevframeClientHost({ 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 viewer 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). diff --git a/docs/content/1.guide/18.hub-initiate.md b/docs/content/1.guide/19.hub-initiate.md similarity index 65% rename from docs/content/1.guide/18.hub-initiate.md rename to docs/content/1.guide/19.hub-initiate.md index de4c8599..6e52354f 100644 --- a/docs/content/1.guide/18.hub-initiate.md +++ b/docs/content/1.guide/19.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 | +| `__index.json` | machine-readable index: mounted devframes, endpoints | | `__client-imports.js` | dock client-script import map for viewers | | `__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 @@ -60,15 +60,15 @@ 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`): - **`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 (`createDevframeClientHost({ renderers })`) overrides the manifest; an uncovered type shows the viewer'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/1.tutorial-server-data-inspector.md b/docs/content/1.guide/2.tutorial-server-data-inspector.md similarity index 79% rename from docs/content/1.guide/1.tutorial-server-data-inspector.md rename to docs/content/1.guide/2.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/2.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/19.services.md b/docs/content/1.guide/20.services.md similarity index 69% rename from docs/content/1.guide/19.services.md rename to docs/content/1.guide/20.services.md index fce664b5..7736bb64 100644 --- a/docs/content/1.guide/19.services.md +++ b/docs/content/1.guide/20.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/21.deep-linking.md similarity index 78% rename from docs/content/1.guide/20.deep-linking.md rename to docs/content/1.guide/21.deep-linking.md index f0d680ef..cd594979 100644 --- a/docs/content/1.guide/20.deep-linking.md +++ b/docs/content/1.guide/21.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/21.events.md b/docs/content/1.guide/22.events.md similarity index 82% rename from docs/content/1.guide/21.events.md rename to docs/content/1.guide/22.events.md index ffa998cd..d9cf0c4b 100644 --- a/docs/content/1.guide/21.events.md +++ b/docs/content/1.guide/22.events.md @@ -5,7 +5,7 @@ description: 'Devframe carries change notifications across channels of differing Devframe carries change notifications across channels of differing **direction and reach**: a node event bus, server RPC, and server-pushed broadcasts and shared state. -Two prefixes mark the wire surface: `hub:` for hub-layer server RPC (client → server), `devframe:` for the client-facing protocol (server → client). The internal event bus mirrors the subsystem vocabulary (`docks`, `terminals`, `messages`, `commands`) — `docks:activate` fans out to `devframe:docks:activate`. +Two prefixes mark the wire protocol: `hub:` for hub-layer server RPC (client → server), `devframe:` for the client-facing protocol (server → client). The internal event bus mirrors the subsystem vocabulary (`docks`, `terminals`, `messages`, `commands`) — `docks:activate` fans out to `devframe:docks:activate`. Each name lives in code: [`HUB_EVENTS`](https://github.com/devframes/devframe/blob/main/packages/hub/src/events.ts) (`@devframes/hub/constants`) backs the hub tables, [`DEVFRAME_EVENTS`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/events.ts) (`devframe/constants`) the core ones. @@ -13,14 +13,14 @@ Each name lives in code: [`HUB_EVENTS`](https://github.com/devframes/devframe/bl ### Internal node event bus -Each subsystem host emits on `ctx..events`, consumed **inside the same node process** by `createHubContext`, which fans them onto the wire. +Each subsystem emits on `ctx..events`, consumed **inside the same node process** by `createHubContext`, which fans them onto the wire. | Event | Emitted by | Consumed by | Payload | |---|---|---|---| | `docks:entry:updated` | `DocksHost.register` / `update` | context → `devframe:docks` shared state | `DevframeDockUserEntry` | | `docks:activate` | `DocksHost.activate()` | context → broadcast + `devframe:docks:active` | `DevframeDockActivation` | -| `terminals:session:updated` | `TerminalsHost` register / update / remove / status change | context → `devframe:terminals:updated`; terminals plugin | `DevframeTerminalSession` | -| `messages:added` / `messages:updated` / `messages:removed` / `messages:cleared` | `MessagesHost` mutations | context → `devframe:messages:updated`; messages plugin | entry / entry / id / — | +| `terminals:session:updated` | `TerminalsHost` register / update / remove / status change | context → `devframe:terminals:updated`; the terminals devframe | `DevframeTerminalSession` | +| `messages:added` / `messages:updated` / `messages:removed` / `messages:cleared` | `MessagesHost` mutations | context → `devframe:messages:updated`; the messages devframe | entry / entry / id / — | | `commands:registered` / `commands:unregistered` | `CommandsHost` register / update / unregister | context → `devframe:commands` shared state | entry / id | ### Server RPC methods — client → server @@ -41,24 +41,24 @@ Each subsystem host emits on `ctx..events`, consumed **inside the sam ### Broadcasts & shared state — server → client -A hub-aware client reads or subscribes via `rpc.client.register(...)`; the [client host](/guide/client-context) registers the `devframe:docks:activate` handler for you. +A hub-aware RPC client reads or subscribes via `rpc.client.register(...)`; the [client runtime](/guide/client-context) registers the `devframe:docks:activate` handler for you. | Name | Kind | Carries | |---|---|---| -| `devframe:docks:activate` | broadcast | Live "switch active dock" request — the client host calls its local `switchEntry`. | +| `devframe:docks:activate` | broadcast | Live "switch active dock" request — the client runtime calls its local `switchEntry`. | | `devframe:terminals:updated` | broadcast | Terminal sessions changed; re-read terminal state. | | `devframe:messages:updated` | broadcast | Message list changed; re-read message state. | | `devframe:docks` | shared state | Projected dock entry list (`DevframeDockEntry[]`). | | `devframe:docks:active` | shared state | Most recent `DevframeDockActivation`, so a dock that mounts in response still converges on it. | | `devframe:commands` | shared state | Serializable command list, handlers stripped (`DevframeServerCommandEntry[]`). | -| `devframe:user-settings` | shared state | Persisted per-workspace hub settings (`DevframeDocksUserSettings`). | +| `devframe:user-settings` | shared state | Persisted project-scope hub settings (`DevframeDocksUserSettings`). | | `devframe:terminals` | streaming channel | Live terminal output stream, keyed by session id. | ## Core devframe events This map covers notifications only; request/response RPC endpoints (`devframe:rpc:server-state:*`, `devframe:streaming:subscribe`, `anonymous:devframe:auth`, …) are typed in `types/rpc-augments.ts`, not events. -### Node host bus +### Node event bus Emitted on `ctx.agent.events`; adapters (e.g. the MCP server) re-publish their manifest. @@ -68,7 +68,7 @@ Emitted on `ctx.agent.events`; adapters (e.g. the MCP server) re-publish their m | `agent:tool:registered` / `agent:tool:unregistered` | `registerTool` / `unregisterTool` | `AgentTool` / id | | `agent:resource:registered` / `agent:resource:unregistered` | `registerResource` / `unregisterResource` | `AgentResource` / id | -### Client connection events +### RPC client connection events Emitted on the RPC client's `rpc.events` (`RpcClientEvents`) to track connection lifecycle and errors. @@ -81,11 +81,11 @@ Emitted on the RPC client's `rpc.events` (`RpcClientEvents`) to track connection ### Broadcasts — server → client -Pushed to subscribed clients, wired by the framework's own hosts. +Pushed to subscribed RPC clients, wired by the core node side. | Name | Carries | |---|---| -| `devframe:auth:revoked` | This connection's bearer token was revoked; the client drops to untrusted. | +| `devframe:auth:revoked` | This connection's bearer token was revoked; the RPC client drops to untrusted. | | `devframe:rpc:client-state:updated` | Full shared-state snapshot for a key. | | `devframe:rpc:client-state:patch` | Incremental shared-state patch for a key. | | `devframe:streaming:chunk` | A streaming chunk for a subscribed channel/id. | diff --git a/docs/content/1.guide/22.build-your-own-json-render-frontend.md b/docs/content/1.guide/23.build-your-own-json-render-frontend.md similarity index 97% rename from docs/content/1.guide/22.build-your-own-json-render-frontend.md rename to docs/content/1.guide/23.build-your-own-json-render-frontend.md index e9d404df..39ebd803 100644 --- a/docs/content/1.guide/22.build-your-own-json-render-frontend.md +++ b/docs/content/1.guide/23.build-your-own-json-render-frontend.md @@ -45,7 +45,7 @@ disabling action dispatch there. ## Plugging it in -- **Local registration** — a host bundling its client passes +- **Local registration** — a viewer bundling its own client runtime passes `createDevframeClientHost({ renderers: { 'json-render': myRenderer } })`; local registrations win over the manifest. - **A prebuilt renderer module** — bundle your renderer as one self-contained diff --git a/docs/content/1.guide/23.build-your-own-hub-ui.md b/docs/content/1.guide/24.build-your-own-hub-ui.md similarity index 87% rename from docs/content/1.guide/23.build-your-own-hub-ui.md rename to docs/content/1.guide/24.build-your-own-hub-ui.md index b5b39861..94474a23 100644 --- a/docs/content/1.guide/23.build-your-own-hub-ui.md +++ b/docs/content/1.guide/24.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 viewer 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 viewer implements two contracts — the node-side `ui` slot and the browser-side context. `@devframes/hub-ui` is the reference. ## The node seam: `DevframeHubUi` @@ -23,9 +23,9 @@ 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 @@ -40,10 +40,10 @@ Render the built-in variants of the open dock union | Type | The viewer 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 diff --git a/docs/content/1.guide/24.built-with.md b/docs/content/1.guide/25.built-with.md similarity index 86% rename from docs/content/1.guide/24.built-with.md rename to docs/content/1.guide/25.built-with.md index abad99cb..0ccf5e24 100644 --- a/docs/content/1.guide/24.built-with.md +++ b/docs/content/1.guide/25.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 viewers 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 @@ -44,7 +44,7 @@ pnpm --filter dev 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/2.devframe-definition.md b/docs/content/1.guide/3.devframe-definition.md similarity index 91% rename from docs/content/1.guide/2.devframe-definition.md rename to docs/content/1.guide/3.devframe-definition.md index 21ffda35..df091cfc 100644 --- a/docs/content/1.guide/2.devframe-definition.md +++ b/docs/content/1.guide/3.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/3.rpc.md b/docs/content/1.guide/4.rpc.md similarity index 83% rename from docs/content/1.guide/3.rpc.md rename to docs/content/1.guide/4.rpc.md index 467e05f0..823d6e60 100644 --- a/docs/content/1.guide/3.rpc.md +++ b/docs/content/1.guide/4.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/5.shared-state.md similarity index 81% rename from docs/content/1.guide/4.shared-state.md rename to docs/content/1.guide/5.shared-state.md index 397708ad..5f9e0cca 100644 --- a/docs/content/1.guide/4.shared-state.md +++ b/docs/content/1.guide/5.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/6.streaming.md similarity index 83% rename from docs/content/1.guide/5.streaming.md rename to docs/content/1.guide/6.streaming.md index 695c4263..90d559ad 100644 --- a/docs/content/1.guide/5.streaming.md +++ b/docs/content/1.guide/6.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/7.client-assets.md similarity index 98% rename from docs/content/1.guide/6.client-assets.md rename to docs/content/1.guide/7.client-assets.md index cdc0206a..887715af 100644 --- a/docs/content/1.guide/6.client-assets.md +++ b/docs/content/1.guide/7.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 diff --git a/docs/content/1.guide/7.scoped-context.md b/docs/content/1.guide/8.scoped-context.md similarity index 84% rename from docs/content/1.guide/7.scoped-context.md rename to docs/content/1.guide/8.scoped-context.md index 06ef43f0..4913a846 100644 --- a/docs/content/1.guide/7.scoped-context.md +++ b/docs/content/1.guide/8.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/9.json-render.md similarity index 91% rename from docs/content/1.guide/8.json-render.md rename to docs/content/1.guide/9.json-render.md index ab0b07e2..e65b9247 100644 --- a/docs/content/1.guide/8.json-render.md +++ b/docs/content/1.guide/9.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' @@ -148,13 +148,13 @@ const host = await createDevframeClientHost({ renderers: { 'json-render': myJsonRenderDockRenderer }, }) -// the viewer mounts the active dock into a container it owns +// the viewer 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 ``` -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/index.md b/docs/content/1.guide/index.md index 8e9ed99b..c4664ad3 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 viewer 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 viewer, 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. | | **[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/examples/hub-deno-minimal/src/hub.ts b/examples/hub-deno-minimal/src/hub.ts index 988b507a..45766743 100644 --- a/examples/hub-deno-minimal/src/hub.ts +++ b/examples/hub-deno-minimal/src/hub.ts @@ -43,7 +43,7 @@ export const hub: HubInstance = globalRef.__hubDenoMinimal ??= initHub({ ui: createUi({ branding: { primaryColor: '#70ffaf', productName: 'Devframes on Deno' } }), // Gate with devframe's interactive OTP (the default). The hub prints a // 6-digit code + magic link on startup, and the reference UI's authorization - // view exchanges it for a bearer token. See docs/content/1.guide/13.security.md. + // view exchanges it for a bearer token. See docs/content/1.guide/15.security.md. configure(ctx) { ctx.commands.register({ id: 'example:hub-deno-minimal:ping', diff --git a/examples/hub-fastify-minimal/src/hub.ts b/examples/hub-fastify-minimal/src/hub.ts index b634fe48..661ba97c 100644 --- a/examples/hub-fastify-minimal/src/hub.ts +++ b/examples/hub-fastify-minimal/src/hub.ts @@ -42,7 +42,7 @@ export const hub: HubInstance = globalRef.__hubFastifyMinimal ??= initHub({ ui: createUi({ branding: { primaryColor: '#2f2f2f', productName: 'Devframes on Fastify' } }), // Gate with devframe's interactive OTP (the default). The hub prints a // 6-digit code + magic link on startup, and the reference UI's authorization - // view exchanges it for a bearer token. See docs/content/1.guide/13.security.md. + // view exchanges it for a bearer token. See docs/content/1.guide/15.security.md. configure(ctx) { ctx.commands.register({ id: 'example:hub-fastify-minimal:ping', diff --git a/examples/hub-hono-minimal/src/app.ts b/examples/hub-hono-minimal/src/app.ts index 746cc579..ef5d2263 100644 --- a/examples/hub-hono-minimal/src/app.ts +++ b/examples/hub-hono-minimal/src/app.ts @@ -45,7 +45,7 @@ export const hub: HubInstance = globalRef.__hubHonoMinimal ??= initHub({ ui: createUi({ branding: { primaryColor: '#e36002', productName: 'Devframes on Hono' } }), // Gate with devframe's interactive OTP (the default). The hub prints a // 6-digit code + magic link on startup, and the reference UI's authorization - // view exchanges it for a bearer token. See docs/content/1.guide/13.security.md. + // view exchanges it for a bearer token. See docs/content/1.guide/15.security.md. configure(ctx) { ctx.commands.register({ id: 'example:hub-hono-minimal:ping', diff --git a/examples/hub-next-minimal/src/client/hub.ts b/examples/hub-next-minimal/src/client/hub.ts index 518e25f2..5ce4c2c3 100644 --- a/examples/hub-next-minimal/src/client/hub.ts +++ b/examples/hub-next-minimal/src/client/hub.ts @@ -90,7 +90,7 @@ async function loadHub(): Promise { // Gate with devframe's interactive OTP (the default). The hub prints a // 6-digit code + magic link on startup, and the reference UI's // authorization view exchanges it for a bearer token. See - // docs/content/1.guide/13.security.md. + // docs/content/1.guide/15.security.md. }) } 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..a5a8ba81 100644 --- a/examples/hub-next/src/client/devframe/next-devframe-hub.ts +++ b/examples/hub-next/src/client/devframe/next-devframe-hub.ts @@ -263,7 +263,7 @@ export async function nextDevframeHub( // Gate access with devframe's interactive OTP (the default): the hub // prints a 6-digit code + magic link on startup, and the client shell // (`app/page.tsx`) drives its own authorization view to exchange the code - // for a bearer token. See `docs/content/1.guide/13.security.md`. + // for a bearer token. See `docs/content/1.guide/15.security.md`. // The aggregate MCP endpoint at `/__devframes/__mcp` - the hub's agent // surface (agent-flagged commands, plugin tools, `devframe:state:read`) // over the same catch-all route as the SPAs. diff --git a/examples/hub-nitro-minimal/hub.ts b/examples/hub-nitro-minimal/hub.ts index a5b9b075..f88f4b0f 100644 --- a/examples/hub-nitro-minimal/hub.ts +++ b/examples/hub-nitro-minimal/hub.ts @@ -48,7 +48,7 @@ export const hub: HubInstance = globalRef.__hubNitroMinimal ??= initHub({ ui: createUi({ branding: { primaryColor: '#ff2056', productName: 'Devframes on Nitro' } }), // Gate with devframe's interactive OTP (the default). The hub prints a // 6-digit code + magic link on startup, and the reference UI's authorization - // view exchanges it for a bearer token. See docs/content/1.guide/13.security.md. + // view exchanges it for a bearer token. See docs/content/1.guide/15.security.md. configure(ctx) { ctx.commands.register({ id: 'example:hub-nitro-minimal:ping', diff --git a/examples/hub-rsbuild-minimal/rsbuild.config.ts b/examples/hub-rsbuild-minimal/rsbuild.config.ts index dc3e45b8..cdea8cc1 100644 --- a/examples/hub-rsbuild-minimal/rsbuild.config.ts +++ b/examples/hub-rsbuild-minimal/rsbuild.config.ts @@ -93,7 +93,7 @@ export default defineConfig({ // Gate with devframe's interactive OTP (the default). The hub prints a // 6-digit code + magic link on startup, and the reference UI's // authorization view exchanges it for a bearer token. See - // docs/content/1.guide/13.security.md. + // docs/content/1.guide/15.security.md. // Rsbuild's middleware stack never hands over WebSocket upgrades, so // the socket gets its own side-car port, advertised through // `__connection.json`. diff --git a/examples/hub-sveltekit-minimal/src/hub.ts b/examples/hub-sveltekit-minimal/src/hub.ts index 9475895c..7e8acf4c 100644 --- a/examples/hub-sveltekit-minimal/src/hub.ts +++ b/examples/hub-sveltekit-minimal/src/hub.ts @@ -44,7 +44,7 @@ export const hub: HubInstance = globalRef.__hubSvelteKitMinimal ??= initHub({ ui: createUi({ branding: { primaryColor: '#ff3e00', productName: 'Devframes on SvelteKit' } }), // Gate with devframe's interactive OTP (the default). The hub prints a // 6-digit code + magic link on startup, and the reference UI's authorization - // view exchanges it for a bearer token. See docs/content/1.guide/13.security.md. + // view exchanges it for a bearer token. See docs/content/1.guide/15.security.md. configure(ctx) { ctx.commands.register({ id: 'example:hub-sveltekit-minimal:ping', diff --git a/examples/hub-vite-minimal/vite.config.ts b/examples/hub-vite-minimal/vite.config.ts index b38b548e..84efba35 100644 --- a/examples/hub-vite-minimal/vite.config.ts +++ b/examples/hub-vite-minimal/vite.config.ts @@ -83,7 +83,7 @@ export default defineConfig({ // Gate with devframe's interactive OTP (the default): the hub prints a // 6-digit code + magic link on startup, and the reference UI's // authorization view exchanges it for a bearer token. See - // docs/content/1.guide/13.security.md. + // docs/content/1.guide/15.security.md. }), ], }) diff --git a/examples/sse-basic/vite.config.ts b/examples/sse-basic/vite.config.ts index 3eab79e0..33519fd2 100644 --- a/examples/sse-basic/vite.config.ts +++ b/examples/sse-basic/vite.config.ts @@ -58,7 +58,7 @@ export default defineConfig({ base: '/__sse-basic/', ws: false, // Single-user localhost demo; a server reachable beyond localhost - // should gate (see docs/content/1.guide/13.security.md). + // should gate (see docs/content/1.guide/15.security.md). auth: false, }) server.middlewares.use(instance.nodeMiddleware) diff --git a/packages/devframe/src/events.ts b/packages/devframe/src/events.ts index e55df7df..cfd25f4b 100644 --- a/packages/devframe/src/events.ts +++ b/packages/devframe/src/events.ts @@ -4,7 +4,7 @@ * broadcast notifications — so these names live in one place instead of * scattered string literals. * - * **Keep this in sync with [`docs/content/1.guide/20.events.md`](../../../docs/content/1.guide/20.events.md)** + * **Keep this in sync with [`docs/content/1.guide/22.events.md`](../../../docs/content/1.guide/22.events.md)** * (the "Core devframe events" section): every name here appears in that page's * tables, and every name there resolves to an entry here. Add, rename, or * remove a name in both places in the same change, and reference diff --git a/packages/hub/src/events.ts b/packages/hub/src/events.ts index 6205d39f..a571a102 100644 --- a/packages/hub/src/events.ts +++ b/packages/hub/src/events.ts @@ -3,7 +3,7 @@ * key, and channel name the hub uses — the single source of truth that keeps * these names out of scattered string literals. * - * **Keep this in sync with [`docs/content/1.guide/20.events.md`](../../../docs/content/1.guide/20.events.md)** + * **Keep this in sync with [`docs/content/1.guide/22.events.md`](../../../docs/content/1.guide/22.events.md)** * (the Hub Events Reference): every name here appears in that page's tables, and * every name there resolves to an entry here. Add, rename, or remove a name in * both places in the same change, and reference `HUB_EVENTS.*` from call sites From 7edebcaf55a1adb0a7d39e632ae8d3056bf228a2 Mon Sep 17 00:00:00 2001 From: "Anthony Fu (via agent)" Date: Wed, 26 Aug 2026 11:28:57 +0000 Subject: [PATCH 2/7] refactor(plugin-a11y): rename the injected script's internals off "agent" The injected half is now the page script, talking to the panel over the in-page channel, so "agent" stays reserved for coding agents. Internal types, the wire message (a11y:page-script-ready), and UI copy follow; the public export a11yAgentBundlePath keeps its name. --- plugins/a11y/README.md | 42 +++++++++---------- plugins/a11y/demo/index.html | 2 +- plugins/a11y/demo/server.mjs | 6 +-- plugins/a11y/package.json | 2 +- plugins/a11y/src/index.ts | 12 +++--- plugins/a11y/src/inject/index.ts | 22 +++++----- plugins/a11y/src/inject/messages.ts | 20 ++++----- plugins/a11y/src/inject/vite.config.ts | 2 +- plugins/a11y/src/rpc/functions/get-config.ts | 10 ++--- plugins/a11y/src/shared/protocol.ts | 42 +++++++++---------- plugins/a11y/src/spa/app.tsx | 18 ++++---- .../src/spa/components/EmptyState.stories.tsx | 2 +- .../src/spa/components/Header.stories.tsx | 8 ++-- plugins/a11y/src/spa/components/Header.tsx | 8 ++-- plugins/a11y/src/spa/lib/channel.ts | 38 ++++++++--------- plugins/a11y/src/spa/lib/devframe.ts | 10 ++--- plugins/a11y/src/spa/lib/impact.ts | 2 +- plugins/a11y/tests/dev-server.test.ts | 6 +-- plugins/a11y/tests/protocol.test.ts | 10 ++--- 19 files changed, 131 insertions(+), 131 deletions(-) diff --git a/plugins/a11y/README.md b/plugins/a11y/README.md index 71b35e6e..b035fbfa 100644 --- a/plugins/a11y/README.md +++ b/plugins/a11y/README.md @@ -1,15 +1,15 @@ # @devframes/plugin-a11y > [!WARNING] Experimental -> This plugin is experimental and may change without a major version bump until +> This devframe is experimental and may change without a major version bump until > it stabilizes. An accessibility inspector built on [devframe](../../packages/devframe). It runs -[axe-core](https://github.com/dequelabs/axe-core) against a host application and +[axe-core](https://github.com/dequelabs/axe-core) against a user app and surfaces the violations in a [Solid](https://www.solidjs.com/) panel: - **Route-aware tracking** — buckets violations by `location.pathname` and tracks - them as you navigate the app (History-API patched, framework-neutral), persisted + them as you navigate the user app (History-API patched, framework-neutral), persisted in `sessionStorage` so history survives reloads within a tab session. - **Dashboard + grouped violations** — a Dashboard tab (totals, severity breakdown, per-route inventory, scan controls) and a Violations tab listing @@ -28,7 +28,7 @@ surfaces the violations in a [Solid](https://www.solidjs.com/) panel: - **Console logging** — newly-appeared violations are logged (deduped) to the browser console. -The scan + highlight loop works the same whether the plugin runs as a live dev +The scan + highlight loop works the same whether the devframe runs as a live dev server or as a baked static build. ## How it works @@ -37,28 +37,28 @@ Three pieces, two of them browser-side: | Piece | Runs in | Role | |-------|---------|------| -| **Agent** (`src/inject`) | the host app's page | runs axe-core, tracks routes, broadcasts the aggregate state, draws the preview + pinned rings | +| **Page script** (`src/inject`) | the user app's page | runs axe-core, tracks routes, broadcasts the aggregate state, draws the preview + pinned rings | | **Panel** (`src/spa`) | the devtools iframe | Solid SPA: Dashboard + grouped violations, fires preview/pin/rescan | -| **Node** (`src/index.ts`, `src/node`, `src/rpc`) | the devframe backend | `get-config` RPC (impact taxonomy + runtime config) — live in dev, baked in a static build | +| **Node** (`src/index.ts`, `src/node`, `src/rpc`) | the node side | `get-config` RPC (impact taxonomy + runtime config) — live in dev, baked in a static build | -The agent and panel talk over a same-origin -[`BroadcastChannel`](src/shared/protocol.ts), not the devframe RPC backend. That +The page script and panel talk over the in-page channel (a same-origin +[`BroadcastChannel`](src/shared/protocol.ts)), not the devframe RPC backend. That is what keeps the live loop working in **both modes**: neither half needs a server to reach the other, only a shared browser origin (host page + panel -iframe). The agent owns the authoritative route → report map and broadcasts the +iframe). The page script owns the authoritative route → report map and broadcasts the whole aggregate on every change, so the panel stays a pure render of it. devframe RPC carries the data model on top — `get-config` is a `static` function, so it resolves over WebSocket in dev and from the baked dump in a static build; the -panel forwards its runtime-config slice to the agent over the channel, keeping the -agent itself free of any RPC dependency. +panel forwards its runtime-config slice to the page script over the channel, keeping the +page script itself free of any RPC dependency. -devframe deliberately provides no access to the host application's DOM, so the -agent is the author-provided bridge into the page being checked. In a hub, the -agent is the a11y dock's **client script**: attach `a11yAgentBundlePath` as the +devframe deliberately provides no access to the user app's DOM, so the +page script is the author-provided bridge into the user app's page. In a hub, the +page script is the a11y dock's **client script**: attach `a11yAgentBundlePath` as the dock's `clientScript` (resolved to an importable URL — `/@fs/…` under Vite, or a statically-served path) and the hub's client runtime (`createDevframeClientHost` from `@devframes/hub/client`) imports it into the host page and calls its -default export with the client-script context. Booted that way, the agent also +default export with the client-script context. Booted that way, the page script also mirrors the active route's scan into the hub's **messages feed** — a summary entry driven through the loading → idle lifecycle plus one entry per violated rule, carrying the impact-mapped level, WCAG tags as labels, and the first offending @@ -73,7 +73,7 @@ shows it (no hub context, so the feed mirror simply stays off). ## Configuration Pass options to `createA11yDevframe()` (surfaced through `get-config`, so they -reach both the panel and the agent): +reach both the panel and the page script): ```ts createA11yDevframe({ @@ -93,7 +93,7 @@ The demo serves an intentionally-broken host page and the panel from **one origin** so they share the channel. ```sh -pnpm -C plugins/a11y build # build the panel + the agent bundle +pnpm -C plugins/a11y build # build the panel + the page-script bundle pnpm -C plugins/a11y demo # dev: live WebSocket RPC → http://localhost:4477/ pnpm -C plugins/a11y cli:build # bake the static deploy (dist/static) @@ -104,7 +104,7 @@ Open the URL, then hover any row in the panel — the matching element in the pa gets a focus ring (and scrolls into view if it's off-screen). Both demo modes behave identically; the panel's `websocket` / `static` tag is the only tell. -Standalone, without a host app: +Standalone, without a user app: ```sh pnpx @devframes/plugin-a11y # the published package, panel only, at /__devframes_plugin_a11y/ @@ -115,13 +115,13 @@ pnpm -C plugins/a11y dev # from source: same, at /__devframes_plugin_a11 | Path | Export | Purpose | |------|--------|---------| -| `src/index.ts` | `.` | `createA11yDevframe()` (also the default export); `a11yAgentBundlePath` — the agent module a hub attaches as this dock's client script | +| `src/index.ts` | `.` | `createA11yDevframe()` (also the default export); `a11yAgentBundlePath` — the page-script module a hub attaches as this dock's client script | | `src/node/index.ts` | `/node` | `setupA11y(ctx, options?)` — registers the RPC functions with the runtime config | | `src/cli.ts` | `/cli` | `createA11yCli()` — backs the `devframes_plugin_a11y` bin | | `src/client/index.ts` | `/client` | `connectA11y()` — typed browser RPC client wrapper | | `src/rpc/` | — | `get-config` static RPC + the type-safe client registry | -| `src/shared/protocol.ts` | — | the agent ↔ panel `BroadcastChannel` contract | -| `src/inject/` | — | the host-page agent (axe scan, highlight overlay, hub messages mirror) → `dist/inject/inject.js` | +| `src/shared/protocol.ts` | — | the page script ↔ panel in-page channel (`BroadcastChannel`) contract | +| `src/inject/` | — | the page script (axe scan, highlight overlay, hub messages mirror) → `dist/inject/inject.js` | | `src/spa/` | — | the Solid panel SPA → `assets-pkg/dist` (ships in `@devframes/plugin-a11y--assets`) | | `demo/` | — | same-origin host page + server (dev + static modes) | | `tests/` | — | dev-server RPC + static-build dump | diff --git a/plugins/a11y/demo/index.html b/plugins/a11y/demo/index.html index 30c7db33..6d0a5697 100644 --- a/plugins/a11y/demo/index.html +++ b/plugins/a11y/demo/index.html @@ -160,7 +160,7 @@

This week's roasts

- + diff --git a/plugins/a11y/demo/server.mjs b/plugins/a11y/demo/server.mjs index a16165ac..3198b50e 100644 --- a/plugins/a11y/demo/server.mjs +++ b/plugins/a11y/demo/server.mjs @@ -2,11 +2,11 @@ /** * Same-origin demo host for the a11y inspector. * - * Serves three things off one origin so the injected agent (host page) and the + * Serves three things off one origin so the page script (host page) and the * panel (devtools iframe) share a BroadcastChannel: * * GET / → the demo page (intentional a11y bugs) - * GET /__df-inject/inject.js → the injected agent bundle + * GET /__df-inject/inject.js → the page script bundle * GET /__devframes_plugin_a11y/** → the Solid panel SPA * * Two modes prove the plugin works either way: @@ -76,7 +76,7 @@ async function main() { return demoHtml }) - // 2. The injected agent bundle. + // 2. The page script bundle. mountStaticHandler(app, '/__df-inject/', injectDir) if (mode === 'dev') { diff --git a/plugins/a11y/package.json b/plugins/a11y/package.json index 11f632e3..a4964b53 100644 --- a/plugins/a11y/package.json +++ b/plugins/a11y/package.json @@ -2,7 +2,7 @@ "name": "@devframes/plugin-a11y", "type": "module", "version": "0.9.5", - "description": "Devframe plugin that audits the host app with axe and highlights accessibility issues in the page.", + "description": "Built-in devframe that audits the user app with axe and highlights accessibility issues in the page.", "author": "Anthony Fu ", "license": "MIT", "homepage": "https://github.com/devframes/devframe#readme", diff --git a/plugins/a11y/src/index.ts b/plugins/a11y/src/index.ts index f68b56d3..4432ad73 100644 --- a/plugins/a11y/src/index.ts +++ b/plugins/a11y/src/index.ts @@ -12,16 +12,16 @@ const BASE_PATH = '/__devframes_plugin_a11y/' // package, served on demand through devframe's remote-assets back-proxy. The // definition's `importMetaUrl` (below) supplies the default `resolveFrom`, so a // locally installed copy (a workspace link here) is served with zero network. -// The host-page agent bundle (`dist/inject`, below) stays here. +// The page-script bundle (`dist/inject`, below) stays here. const distDir: RemoteAssets = { package: `${pkg.name}--assets`, version: pkg.version, } /** - * Absolute path to the built in-page **agent** module (`dist/inject/inject.js`) - * — the dock **client script** the hub runtime imports into the host page to - * scan it (its default export boots the agent; importing it does too). + * Absolute path to the built **page script** module (`dist/inject/inject.js`) + * — the dock **client script** the client runtime imports into the host page to + * scan it (its default export boots the page script; importing it does too). * * A hub attaches this as the a11y dock's `clientScript`, resolved to a URL the * page can import: `/@fs/${a11yAgentBundlePath}` for a Vite host, or a @@ -69,8 +69,8 @@ export interface A11yDevframeOptions { /** * Build a {@link DevframeDefinition} for the a11y inspector. The same * definition runs standalone (`/cli`, `/build`) and mounts into a host - * (`/vite`, hub). The panel talks to the in-page agent over a same-origin - * BroadcastChannel, so the scan/highlight loop works identically in dev + * (`/vite`, hub). The panel talks to the page script over the in-page channel + * (a same-origin BroadcastChannel), so the scan/highlight loop works identically in dev * (live WebSocket RPC) and in a baked static build. * * @experimental This plugin is experimental and may change without a major diff --git a/plugins/a11y/src/inject/index.ts b/plugins/a11y/src/inject/index.ts index b87bdcf6..14e53e2d 100644 --- a/plugins/a11y/src/inject/index.ts +++ b/plugins/a11y/src/inject/index.ts @@ -1,5 +1,5 @@ /** - * The a11y inspector **agent** — injected into the host application's page. + * The a11y inspector **page script** — injected into the user app's page. * * It runs axe-core against the live DOM, tracks violations per route, broadcasts * the whole {@link A11yState} aggregate to the panel, and draws transient + @@ -16,11 +16,11 @@ import type { A11yMessage, A11yState, - AgentConfig, + PageScriptConfig, PinTarget, ScanReport, } from '../shared/protocol.ts' -import type { A11yAgentContext } from './messages.ts' +import type { A11yPageScriptContext } from './messages.ts' import type { PinInfo } from './overlay.ts' import { A11Y_CHANNEL, @@ -34,7 +34,7 @@ import { resolveElement, scan } from './scanner.ts' const GLOBAL_FLAG = '__DF_A11Y_AGENT__' -function start(context?: A11yAgentContext) { +function start(context?: A11yPageScriptContext) { const w = window as unknown as Record if (w[GLOBAL_FLAG]) return @@ -46,7 +46,7 @@ function start(context?: A11yAgentContext) { // Booted as a hub dock client script — mirror the active route's scan into // the hub's messages feed. Standalone boots have no context and skip it. - const config: AgentConfig = { logIssues: true, autoScan: true } + const config: PageScriptConfig = { logIssues: true, autoScan: true } const reporter = context?.messages ? createMessagesReporter(context.messages, { @@ -251,7 +251,7 @@ function start(context?: A11yAgentContext) { const message = event.data switch (message.type) { case 'a11y:panel-ready': - post({ type: 'a11y:agent-ready', url: location.href, route: activeRoute }) + post({ type: 'a11y:page-script-ready', url: location.href, route: activeRoute }) if (routes.size > 0) broadcastState() else @@ -312,7 +312,7 @@ function start(context?: A11yAgentContext) { } }) - function applyConfig(next: AgentConfig) { + function applyConfig(next: PageScriptConfig) { config.logIssues = next.logIssues config.axeTags = next.axeTags config.axeRunOptions = next.axeRunOptions @@ -327,7 +327,7 @@ function start(context?: A11yAgentContext) { bindInteractions() // Announce ourselves and run the first scan once the page has settled. - post({ type: 'a11y:agent-ready', url: location.href, route: activeRoute }) + post({ type: 'a11y:page-script-ready', url: location.href, route: activeRoute }) if (routes.size > 0) broadcastState() if (document.readyState === 'complete') @@ -347,16 +347,16 @@ function findRule(report: ScanReport | null, nodeId: string) { * Client-script entry the hub runtime calls after importing this module, * passing its `DockClientScriptContext`. The live scan/highlight loop rides * the same-origin BroadcastChannel either way; when the context carries a - * `messages` client (duck-typed — see {@link A11yAgentContext}), the agent + * `messages` client (duck-typed — see {@link A11yPageScriptContext}), the page script * additionally mirrors each scan into the hub's messages feed. `start()` is * idempotent. */ -export default function runA11yAgent(context?: A11yAgentContext): void { +export default function runA11yPageScript(context?: A11yPageScriptContext): void { start(context) } // Also self-boot so a plain `', }, } diff --git a/plugins/a11y/src/spa/components/Header.stories.tsx b/plugins/a11y/src/spa/components/Header.stories.tsx index d54a8b40..4e497793 100644 --- a/plugins/a11y/src/spa/components/Header.stories.tsx +++ b/plugins/a11y/src/spa/components/Header.stories.tsx @@ -14,7 +14,7 @@ type Story = StoryObj function noop() {} const base = { selectedCount: 0, onGenerate: noop, onRescan: noop } -export const Connected: Story = { args: { ...base, agentReady: true, scanning: false } } -export const Scanning: Story = { args: { ...base, agentReady: true, scanning: true } } -export const WithSelection: Story = { args: { ...base, agentReady: true, scanning: false, selectedCount: 3 } } -export const Disconnected: Story = { args: { ...base, agentReady: false, scanning: false } } +export const Connected: Story = { args: { ...base, pageScriptReady: true, scanning: false } } +export const Scanning: Story = { args: { ...base, pageScriptReady: true, scanning: true } } +export const WithSelection: Story = { args: { ...base, pageScriptReady: true, scanning: false, selectedCount: 3 } } +export const Disconnected: Story = { args: { ...base, pageScriptReady: false, scanning: false } } diff --git a/plugins/a11y/src/spa/components/Header.tsx b/plugins/a11y/src/spa/components/Header.tsx index 3fd0c7ca..a2050e6a 100644 --- a/plugins/a11y/src/spa/components/Header.tsx +++ b/plugins/a11y/src/spa/components/Header.tsx @@ -2,7 +2,7 @@ import { Show } from 'solid-js' import { button, nav, navBrand } from '../design' interface HeaderProps { - agentReady: boolean + pageScriptReady: boolean scanning: boolean selectedCount: number onGenerate: () => void @@ -12,9 +12,9 @@ interface HeaderProps { /** The top nav bar: brand, connection status, generate-prompts, and rescan. */ export function Header(props: HeaderProps) { const statusLabel = () => - !props.agentReady ? 'No page connected' : props.scanning ? 'Scanning…' : 'Connected' + !props.pageScriptReady ? 'No page connected' : props.scanning ? 'Scanning…' : 'Connected' const dotClass = () => - !props.agentReady ? 'bg-neutral-400' : props.scanning ? 'bg-primary-500 animate-pulse' : 'bg-success' + !props.pageScriptReady ? 'bg-neutral-400' : props.scanning ? 'bg-primary-500 animate-pulse' : 'bg-success' return (
@@ -46,7 +46,7 @@ export function Header(props: HeaderProps) { type="button" class={button({ variant: 'secondary', size: 'sm' })} onClick={() => props.onRescan()} - disabled={!props.agentReady || props.scanning} + disabled={!props.pageScriptReady || props.scanning} > Rescan diff --git a/plugins/a11y/src/spa/lib/channel.ts b/plugins/a11y/src/spa/lib/channel.ts index 5c050ecf..4d8ea9a7 100644 --- a/plugins/a11y/src/spa/lib/channel.ts +++ b/plugins/a11y/src/spa/lib/channel.ts @@ -1,14 +1,14 @@ import type { Accessor } from 'solid-js' -import type { A11yMessage, A11yState, AgentConfig, PinTarget } from '../../shared/protocol.ts' +import type { A11yMessage, A11yState, PageScriptConfig, PinTarget } from '../../shared/protocol.ts' import { createSignal, onCleanup } from 'solid-js' import { A11Y_CHANNEL } from '../../shared/protocol.ts' export interface A11yChannel { - /** Latest full route → report aggregate, or `null` until the agent reports in. */ + /** Latest full route → report aggregate, or `null` until the page script reports in. */ state: Accessor - /** Whether an agent has announced itself on this origin. */ - agentReady: Accessor - /** Whether the agent is mid-scan. */ + /** Whether a page script has announced itself on this origin. */ + pageScriptReady: Accessor + /** Whether the page script is mid-scan. */ scanning: Accessor /** `location.pathname` currently in view in the host page. */ activeRoute: Accessor @@ -18,11 +18,11 @@ export interface A11yChannel { clearPreview: () => void /** Replace the pinned (numbered) highlight set drawn in the host page. */ setPins: (pins: PinTarget[]) => void - /** Ask the agent to re-run the scan. */ + /** Ask the page script to re-run the scan. */ rescan: () => void - /** Forward runtime configuration to the agent. */ - sendConfig: (config: AgentConfig) => void - /** Toggle the agent's interaction-driven auto-scan. */ + /** Forward runtime configuration to the page script. */ + sendConfig: (config: PageScriptConfig) => void + /** Toggle the page script's interaction-driven auto-scan. */ setAutoScan: (enabled: boolean) => void /** Drop one route's tracked history. */ clearRoute: (route: string) => void @@ -31,13 +31,13 @@ export interface A11yChannel { } /** - * Panel half of the agent↔panel BroadcastChannel. Returns reactive accessors - * that track the agent's aggregate state plus the actions the UI fires on + * Panel half of the in-page channel (a `BroadcastChannel`). Returns reactive accessors + * that track the page script's aggregate state plus the actions the UI fires on * hover/click/rescan. */ export function createA11yChannel(): A11yChannel { const [state, setState] = createSignal(null) - const [agentReady, setAgentReady] = createSignal(false) + const [pageScriptReady, setPageScriptReady] = createSignal(false) const [scanning, setScanning] = createSignal(false) const [activeRoute, setActiveRoute] = createSignal(null) @@ -47,36 +47,36 @@ export function createA11yChannel(): A11yChannel { channel.addEventListener('message', (event: MessageEvent) => { const message = event.data switch (message.type) { - case 'a11y:agent-ready': - setAgentReady(true) + case 'a11y:page-script-ready': + setPageScriptReady(true) setActiveRoute(message.route) - // Closes the startup race: if our panel-ready landed before the agent + // Closes the startup race: if our panel-ready landed before the page script // was listening, asking again now pulls down the current state. if (!state()) post({ type: 'a11y:panel-ready' }) break case 'a11y:state': - setAgentReady(true) + setPageScriptReady(true) setScanning(false) setActiveRoute(message.state.activeRoute) setState(message.state) break case 'a11y:scanning': - setAgentReady(true) + setPageScriptReady(true) setActiveRoute(message.route) setScanning(true) break } }) - // Announce the panel so a previously-loaded agent replays its current state. + // Announce the panel so a previously-loaded page script replays its current state. post({ type: 'a11y:panel-ready' }) onCleanup(() => channel.close()) return { state, - agentReady, + pageScriptReady, scanning, activeRoute, preview: node => post({ type: 'a11y:highlight', nodeId: node.id, target: node.target }), diff --git a/plugins/a11y/src/spa/lib/devframe.ts b/plugins/a11y/src/spa/lib/devframe.ts index 36fb8753..d28abccd 100644 --- a/plugins/a11y/src/spa/lib/devframe.ts +++ b/plugins/a11y/src/spa/lib/devframe.ts @@ -1,6 +1,6 @@ import type { DevframeConnectionStatus } from 'devframe/client' import type { Accessor } from 'solid-js' -import type { AgentConfig, Impact } from '../../shared/protocol.ts' +import type { Impact, PageScriptConfig } from '../../shared/protocol.ts' import { connectDevframe } from 'devframe/client' import { createSignal } from 'solid-js' import { A11Y_DOCKS_ACTIVE_KEY } from '../../shared/protocol.ts' @@ -19,8 +19,8 @@ interface A11yConfig { dockId: string /** Auto-pin all of a route's violations the first time it's scanned. */ defaultHighlight: boolean - /** Runtime configuration forwarded to the in-page agent. */ - agent: AgentConfig + /** Runtime configuration forwarded to the page script. */ + pageScript: PageScriptConfig impacts: ImpactMeta[] } @@ -47,7 +47,7 @@ export interface DevframeState { /** * Connect to the devframe backend for supplementary data: the impact legend, - * the runtime config the panel forwards to the agent, and the dock-activation + * the runtime config the panel forwards to the page script, and the dock-activation * shared state that powers deep-linking (e.g. a messages-feed entry navigating * here). Intentionally non-blocking and failure-tolerant — the panel's core * scan loop runs over BroadcastChannel, so the UI stays useful even if the @@ -95,7 +95,7 @@ export function connectDevframeState(): DevframeState { } }) .catch(() => { - // No reachable backend (e.g. agent loaded outside a devframe host). + // No reachable backend (e.g. the page script loaded outside a running devframe). setStatus('error') }) diff --git a/plugins/a11y/src/spa/lib/impact.ts b/plugins/a11y/src/spa/lib/impact.ts index 29965a34..350af913 100644 --- a/plugins/a11y/src/spa/lib/impact.ts +++ b/plugins/a11y/src/spa/lib/impact.ts @@ -1,6 +1,6 @@ import type { Impact } from '../../shared/protocol.ts' -// The severity palette is shared with the in-page agent — single source in the +// The severity palette is shared with the page script — single source in the // protocol module so the panel and the highlight ring never drift. export { IMPACT_COLOR } from '../../shared/protocol.ts' diff --git a/plugins/a11y/tests/dev-server.test.ts b/plugins/a11y/tests/dev-server.test.ts index 6a7d0eda..ea1cd4ae 100644 --- a/plugins/a11y/tests/dev-server.test.ts +++ b/plugins/a11y/tests/dev-server.test.ts @@ -47,15 +47,15 @@ describe('dev-server (CLI surface)', () => { nodeAttr: string dockId: string defaultHighlight: boolean - agent: { autoScan: boolean, logIssues: boolean, activateDockId: string } + pageScript: { autoScan: boolean, logIssues: boolean, activateDockId: string } impacts: { id: string }[] } expect(config.channel).toBe('devframes:plugin:a11y') expect(config.nodeAttr).toBe('data-df-a11y-node') expect(config.impacts.map(i => i.id)).toEqual(['critical', 'serious', 'moderate', 'minor']) - // Runtime config the panel forwards to the in-page agent. + // Runtime config the panel forwards to the page script. expect(config.dockId).toBe('devframes_plugin_a11y') expect(config.defaultHighlight).toBe(false) - expect(config.agent).toMatchObject({ autoScan: true, logIssues: true, activateDockId: 'devframes_plugin_a11y' }) + expect(config.pageScript).toMatchObject({ autoScan: true, logIssues: true, activateDockId: 'devframes_plugin_a11y' }) }) }) diff --git a/plugins/a11y/tests/protocol.test.ts b/plugins/a11y/tests/protocol.test.ts index 70681352..2d034327 100644 --- a/plugins/a11y/tests/protocol.test.ts +++ b/plugins/a11y/tests/protocol.test.ts @@ -33,15 +33,15 @@ describe('createGetConfig', () => { const cfg = createGetConfig().handler!() as any expect(cfg.dockId).toBe('devframes_plugin_a11y') expect(cfg.defaultHighlight).toBe(false) - expect(cfg.agent.autoScan).toBe(true) - expect(cfg.agent.logIssues).toBe(true) - expect(cfg.agent.activateDockId).toBe('devframes_plugin_a11y') + expect(cfg.pageScript.autoScan).toBe(true) + expect(cfg.pageScript.logIssues).toBe(true) + expect(cfg.pageScript.activateDockId).toBe('devframes_plugin_a11y') // Broadened default tag set includes WCAG 2.2 + best-practice. expect([...DEFAULT_AXE_TAGS]).toContain('best-practice') expect([...DEFAULT_AXE_TAGS]).toContain('wcag22aa') }) - it('threads author options through to the agent config and dock id', () => { + it('threads author options through to the page script config and dock id', () => { const cfg = createGetConfig({ dockId: 'my_a11y', autoScan: false, @@ -51,7 +51,7 @@ describe('createGetConfig', () => { }).handler!() as any expect(cfg.dockId).toBe('my_a11y') expect(cfg.defaultHighlight).toBe(true) - expect(cfg.agent).toMatchObject({ + expect(cfg.pageScript).toMatchObject({ autoScan: false, logIssues: false, axeTags: ['wcag2a'], From 42577e5684ea99d9f5f3d72a1fcba5964f6fead9 Mon Sep 17 00:00:00 2001 From: "Anthony Fu (via agent)" Date: Wed, 26 Aug 2026 11:29:11 +0000 Subject: [PATCH 3/7] docs: sweep docs and READMEs to the canonical vocabulary Every docs page (error pages included) and README now follows the Terms page: built-in devframes instead of a plugin concept, host framework / host page / user app, node side / browser side, client runtime, RPC client, viewer over shell, dock rail/panel, page script and in-page channel, qualified scopes and sessions. --- docs/content/2.adapters/1.initiate.md | 22 ++++++------ docs/content/2.adapters/2.cac.md | 2 +- docs/content/2.adapters/4.build.md | 2 +- docs/content/2.adapters/5.vite.md | 2 +- docs/content/2.adapters/7.mcp.md | 18 +++++----- docs/content/2.adapters/index.md | 4 +-- docs/content/3.frameworks/1.vite.md | 8 ++--- docs/content/3.frameworks/2.nuxt.md | 10 +++--- docs/content/3.frameworks/3.next.md | 6 ++-- docs/content/3.frameworks/index.md | 10 +++--- docs/content/4.helpers/1.utilities.md | 2 +- .../4.helpers/2.common-rpc-functions.md | 6 ++-- docs/content/4.helpers/3.interactive-auth.md | 8 ++--- docs/content/4.helpers/index.md | 8 ++--- docs/content/5.plugins/1.data-inspector.md | 16 ++++----- docs/content/5.plugins/2.inspect.md | 6 ++-- docs/content/5.plugins/3.og.md | 2 +- docs/content/5.plugins/4.a11y.md | 22 ++++++------ docs/content/5.plugins/5.git.md | 4 +-- docs/content/5.plugins/6.terminals.md | 8 ++--- docs/content/5.plugins/7.code-server.md | 2 +- docs/content/5.plugins/8.assets.md | 8 ++--- docs/content/5.plugins/index.md | 18 +++++----- docs/content/6.errors/DF0037.md | 4 +-- docs/content/6.errors/DF0052.md | 2 +- docs/content/6.errors/DF0066.md | 2 +- docs/content/6.errors/DF0067.md | 4 +-- docs/content/6.errors/DF0070.md | 2 +- docs/content/6.errors/DF0072.md | 4 +-- docs/content/6.errors/DF8002.md | 2 +- docs/content/6.errors/DF8104.md | 2 +- docs/content/6.errors/DF8110.md | 2 +- docs/content/7.migrations/1.migration-0.9.md | 22 ++++++------ docs/content/7.migrations/2.migration-0.8.md | 8 ++--- docs/content/7.migrations/4.migration-0.6.md | 4 +-- docs/content/7.migrations/index.md | 2 +- docs/content/index.md | 20 +++++------ examples/a11y-messages-playground/README.md | 26 +++++++------- examples/demo-dock-client/README.md | 8 ++--- examples/hub-hono-minimal/README.md | 2 +- examples/hub-next/README.md | 32 ++++++++--------- examples/hub-nitro-minimal/README.md | 4 +-- examples/hub-rsbuild-minimal/README.md | 4 +-- examples/hub-sveltekit-minimal/README.md | 2 +- examples/hub-vite-minimal/README.md | 4 +-- examples/hub-vite/README.md | 36 +++++++++---------- examples/json-render/README.md | 2 +- examples/next-runtime-snapshot/README.md | 2 +- examples/sse-basic/README.md | 2 +- packages/devframe/README.md | 2 +- plugins/assets/README.md | 4 +-- plugins/code-server/README.md | 8 ++--- plugins/data-inspector/README.md | 16 ++++----- plugins/git/README.md | 12 +++---- plugins/inspect/README.md | 12 +++---- plugins/messages/README.md | 10 +++--- starter/README.md | 6 ++-- storybook/README.md | 18 +++++----- 58 files changed, 243 insertions(+), 243 deletions(-) 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..2da43987 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' diff --git a/docs/content/3.frameworks/index.md b/docs/content/3.frameworks/index.md index 228d189b..e0d8f794 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. diff --git a/docs/content/4.helpers/1.utilities.md b/docs/content/4.helpers/1.utilities.md index 73c03661..9c47d19b 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' 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..40f518f9 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/agent.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..49b29546 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,15 +21,15 @@ 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 `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: ```ts import createA11yDevframe, { a11yAgentBundlePath } from '@devframes/plugin-a11y' @@ -39,7 +39,7 @@ await ctx.install(createA11yDevframe(), { }) ``` -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 661ba97c..00ca1114 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 e878a46a..35f2ac4d 100644 --- a/examples/hub-hono-minimal/README.md +++ b/examples/hub-hono-minimal/README.md @@ -7,7 +7,7 @@ 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 diff --git a/examples/hub-hono-minimal/src/app.ts b/examples/hub-hono-minimal/src/app.ts index ef5d2263..98655473 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 5ce4c2c3..8bf58872 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 1136f251..3c80a858 100644 --- a/examples/hub-next/README.md +++ b/examples/hub-next/README.md @@ -1,6 +1,6 @@ # 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 devframes to any non-Vite host framework. +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 node side: a single `initHub()` call from `@devframes/hub/initiate`. @@ -11,7 +11,7 @@ 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 viewer 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 rail on the left lists every mounted tool with its icon: @@ -42,11 +42,11 @@ 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 devframe, the discovery endpoints, and the built-ins. The viewer 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 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 `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 viewer shows *No renderer for "demo-unrendered" in the current environment* instead of a dead panel +- 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 devframes in a bundler diff --git a/examples/hub-next/src/client/app/page.tsx b/examples/hub-next/src/client/app/page.tsx index 616792e2..d183f7ee 100644 --- a/examples/hub-next/src/client/app/page.tsx +++ b/examples/hub-next/src/client/app/page.tsx @@ -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' })) 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 a5a8ba81..109c2ae3 100644 --- a/examples/hub-next/src/client/devframe/next-devframe-hub.ts +++ b/examples/hub-next/src/client/devframe/next-devframe-hub.ts @@ -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 f39ba4ac..d468e9d8 100644 --- a/examples/hub-nitro-minimal/README.md +++ b/examples/hub-nitro-minimal/README.md @@ -6,11 +6,11 @@ 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 devframes 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. +- [`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 f88f4b0f..25b5f9b2 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 16e1bd0f..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-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 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. +- `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 `