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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ The `pnpm test` script intentionally runs `build` first so `tsnapi` snapshots co
- 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:<name>`, don't pin versions in `package.json`.

### Framework adapter packages: 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:

- **`.../dev-spa`** - **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 `.../dev-spa/client`. Nuxt: the Nuxt module (registered as `modules: ['@devframes/nuxt/dev-spa']`).
- **`.../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.
- **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.

### Design system

All five built-in plugins - and every example under `examples/` - share one design system, [`@antfu/design`](https://github.com/antfu/design), so they look and feel like one product across frameworks (Git is React/Next, terminals is Svelte, code-server is Vue, inspect is Vue, a11y is Solid, the examples are Preact/Next/vanilla). It's a dev dependency consumed at build time: its UnoCSS preset and shipped styles drive every surface, and its Vue components are the canonical reference every framework matches. There is no shared internal design package - each app wires the preset itself and owns its own component ports.
Expand Down
13 changes: 11 additions & 2 deletions alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export const alias = {
'devframe/adapters/cac': r('devframe/src/adapters/cac.ts'),
'devframe/adapters/dev': r('devframe/src/adapters/dev.ts'),
'devframe/adapters/build': r('devframe/src/adapters/build.ts'),
'devframe/helpers/vite': r('devframe/src/helpers/vite.ts'),
'devframe/adapters/embedded': r('devframe/src/adapters/embedded.ts'),
'devframe/initiate': r('devframe/src/adapters/initiate.ts'),
'devframe/adapters/mcp': r('devframe/src/adapters/mcp/index.ts'),
Expand All @@ -51,9 +50,19 @@ export const alias = {
'@devframes/hub': r('hub/src/index.ts'),
'@devframes/hub-ui': r('hub-ui/src/index.ts'),
'@devframes/nuxt/runtime/plugin.client': r('nuxt/src/runtime/plugin.client.ts'),
'@devframes/nuxt/dev-spa': r('nuxt/src/dev-spa.ts'),
'@devframes/nuxt/hub/client': r('nuxt/src/hub-client.ts'),
'@devframes/nuxt/hub': r('nuxt/src/hub.ts'),
'@devframes/nuxt': r('nuxt/src/index.ts'),
'@devframes/next/client': r('next/src/client.tsx'),
'@devframes/next/dev-spa/client': r('next/src/client.tsx'),
'@devframes/next/dev-spa': r('next/src/dev-spa.ts'),
'@devframes/next/hub/client': r('next/src/hub-client.tsx'),
'@devframes/next/hub': r('next/src/hub.ts'),
'@devframes/next': r('next/src/index.ts'),
'@devframes/vite/dev-spa': r('vite/src/dev-spa.ts'),
'@devframes/vite/hub/client': r('vite/src/hub-client.ts'),
'@devframes/vite/hub': r('vite/src/hub.ts'),
'@devframes/vite': r('vite/src/index.ts'),
'@devframes/json-render/core': r('json-render/src/core.ts'),
'@devframes/json-render/hub': r('json-render/src/hub.ts'),
'@devframes/json-render/node': r('json-render/src/node/index.ts'),
Expand Down
98 changes: 70 additions & 28 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,60 @@ function listErrorCodes(prefix: string): string[] {
.sort()
}

function guideItems(prefix: string) {
function guideGroups(prefix: string) {
return [
{ text: 'Introduction', link: `${prefix}/guide/` },
{ text: 'Devframe Definition', link: `${prefix}/guide/devframe-definition` },
{ text: 'Scoped Context', link: `${prefix}/guide/scoped-context` },
{ text: 'Cross-Plugin Services', link: `${prefix}/guide/services` },
{ text: 'RPC', link: `${prefix}/guide/rpc` },
{ text: 'Shared State', link: `${prefix}/guide/shared-state` },
{ text: 'JSON-Render', link: `${prefix}/guide/json-render` },
{ text: 'Streaming', link: `${prefix}/guide/streaming` },
{ text: 'When Clauses', link: `${prefix}/guide/when-clauses` },
{ text: 'Structured Diagnostics', link: `${prefix}/guide/diagnostics` },
{ text: 'Client', link: `${prefix}/guide/client` },
{ text: 'Transports', link: `${prefix}/guide/transports` },
{ text: 'Security', link: `${prefix}/guide/security` },
{ text: 'Standalone CLI', link: `${prefix}/guide/standalone-cli` },
{ text: 'Hub', link: `${prefix}/guide/hub` },
{ text: 'Serve a Hub Anywhere', link: `${prefix}/guide/hub-initiate` },
{ text: 'Deep Linking', link: `${prefix}/guide/deep-linking` },
{ text: 'Client Scripts & Context', link: `${prefix}/guide/client-context` },
{ text: 'Build Your Own Hub UI', link: `${prefix}/guide/build-your-own-hub-ui` },
{ text: 'Build Your Own JSON-Render Frontend', link: `${prefix}/guide/build-your-own-json-render-frontend` },
{ text: 'Agent-Native (experimental)', link: `${prefix}/guide/agent-native` },
] satisfies DefaultTheme.NavItemWithLink[]
{
text: 'Fundamentals',
items: [
{ text: 'Introduction', link: `${prefix}/guide/` },
{ text: 'Devframe Definition', link: `${prefix}/guide/devframe-definition` },
{ text: 'Scoped Context', link: `${prefix}/guide/scoped-context` },
{ text: 'Cross-Plugin Services', link: `${prefix}/guide/services` },
{ text: 'RPC', link: `${prefix}/guide/rpc` },
{ text: 'Shared State', link: `${prefix}/guide/shared-state` },
{ text: 'Streaming', link: `${prefix}/guide/streaming` },
{ text: 'When Clauses', link: `${prefix}/guide/when-clauses` },
{ text: 'Structured Diagnostics', link: `${prefix}/guide/diagnostics` },
],
},
{
text: 'Client & Security',
items: [
{ text: 'Client', link: `${prefix}/guide/client` },
{ text: 'Transports', link: `${prefix}/guide/transports` },
{ text: 'Security', link: `${prefix}/guide/security` },
{ text: 'Deep Linking', link: `${prefix}/guide/deep-linking` },
],
},
{
text: 'JSON-Render',
items: [
{ text: 'JSON-Render', link: `${prefix}/guide/json-render` },
{ text: 'Build Your Own JSON-Render Frontend', link: `${prefix}/guide/build-your-own-json-render-frontend` },
],
},
{
text: 'Hub',
items: [
{ text: 'Hub', link: `${prefix}/guide/hub` },
{ text: 'Serve a Hub Anywhere', link: `${prefix}/guide/hub-initiate` },
{ text: 'Client Scripts & Context', link: `${prefix}/guide/client-context` },
{ text: 'Build Your Own Hub UI', link: `${prefix}/guide/build-your-own-hub-ui` },
],
},
{
text: 'Recipes & Advanced',
items: [
{ text: 'Standalone CLI', link: `${prefix}/guide/standalone-cli` },
{ text: 'Agent-Native (experimental)', link: `${prefix}/guide/agent-native` },
],
},
] satisfies { text: string, items: DefaultTheme.NavItemWithLink[] }[]
}

/** Flattened guide list — used by the top nav dropdown, which renders one level. */
function guideItems(prefix: string) {
return guideGroups(prefix).flatMap(group => group.items) satisfies DefaultTheme.NavItemWithLink[]
}

function adaptersItems(prefix: string) {
Expand All @@ -49,19 +79,25 @@ function adaptersItems(prefix: string) {
{ text: 'Dev', link: `${prefix}/adapters/dev` },
{ text: 'Initiate (middleware)', link: `${prefix}/adapters/initiate` },
{ text: 'Build', link: `${prefix}/adapters/build` },
{ text: 'Vite', link: `${prefix}/adapters/vite` },
{ text: 'Vite DevTools', link: `${prefix}/adapters/vite` },
{ text: 'Embedded', link: `${prefix}/adapters/embedded` },
{ text: 'MCP', link: `${prefix}/adapters/mcp` },
] satisfies DefaultTheme.NavItemWithLink[]
}

function frameworksItems(prefix: string) {
return [
{ text: 'Overview', link: `${prefix}/frameworks/` },
{ text: 'Vite', link: `${prefix}/frameworks/vite` },
{ text: 'Nuxt', link: `${prefix}/frameworks/nuxt` },
{ text: 'Next', link: `${prefix}/frameworks/next` },
] satisfies DefaultTheme.NavItemWithLink[]
}

function helpersItems(prefix: string) {
return [
{ text: 'Overview', link: `${prefix}/helpers/` },
{ text: 'Utilities', link: `${prefix}/helpers/utilities` },
{ text: 'Vite Bridge', link: `${prefix}/helpers/vite-bridge` },
{ text: 'Nuxt Module', link: `${prefix}/helpers/nuxt` },
{ text: 'Next Helper', link: `${prefix}/helpers/next` },
{ text: 'Common RPC Functions', link: `${prefix}/helpers/common-rpc-functions` },
{ text: 'Interactive Auth', link: `${prefix}/helpers/interactive-auth` },
] satisfies DefaultTheme.NavItemWithLink[]
Expand Down Expand Up @@ -103,12 +139,17 @@ export function devframeSidebar(prefix = ''): DefaultTheme.SidebarItem[] {
return [
{
text: 'Guide',
items: guideItems(prefix),
// Labelled, collapsible subsections instead of one long flat list.
items: guideGroups(prefix).map(group => ({ ...group, collapsed: false })),
},
{
text: 'Adapters',
items: adaptersItems(prefix),
},
{
text: 'Frameworks',
items: frameworksItems(prefix),
},
{
text: 'Helpers',
items: helpersItems(prefix),
Expand Down Expand Up @@ -137,6 +178,7 @@ export function devframeNav(prefix = ''): DefaultTheme.NavItem[] {
text: 'Adapters',
items: [
...adaptersItems(prefix),
{ text: 'Frameworks', items: frameworksItems(prefix) },
{ text: 'Helpers', items: helpersItems(prefix) },
],
},
Expand Down
2 changes: 1 addition & 1 deletion docs/adapters/initiate.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,4 @@ The instance **gates by default** — a handler mounted inside an app server is

## Relation to the other adapters

`createDevServer`, `viteDevBridge`, and `@devframes/next` are assembled from this instance internally — the handler is the one wiring underneath every serving path. To host **many** devframes behind one namespace with shared transport and docks, use the hub's counterpart: [`initHub`](../guide/hub-initiate).
`createDevServer`, `devframeViteBridge` (`@devframes/vite`), and `@devframes/next` are assembled from this instance internally — the handler is the one wiring underneath every serving path. To host **many** devframes behind one namespace with shared transport and docks, use the hub's counterpart: [`initHub`](../guide/hub-initiate).
6 changes: 3 additions & 3 deletions docs/adapters/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ defineDevframe({
Both hosted bridges forward the same option to their side-car dev server and advertise the endpoint (with its port) in the `__connection.json` they serve:

```ts
// Vite
viteDevBridge(devframe, { devMiddleware: true, mcp: true })
// Vite (@devframes/vite)
devframeViteBridge(devframe, { mcp: true })

// Next.js (@devframes/next)
createDevframeNextHandler(devframe, { mcp: true })
Expand Down Expand Up @@ -90,6 +90,6 @@ It exposes two gateway tools (the wire names of the `devframe:connect:*` ids —
- **`devframe_connect_list-instances`** — discover running devframe dev servers and list each one's MCP tools. Instances running without an MCP route are listed with a hint to restart with `--mcp`.
- **`devframe_connect_call-tool`** — invoke one tool on one instance (`{ port, tool, args }`) over its Streamable-HTTP endpoint.

Discovery reads the **instance registry**: every `createDevServer` (CLI `dev`, `viteDevBridge`, `@devframes/next`'s handler) writes a record to `~/.devframe/instances/<pid>-<port>.json` on boot and removes it on close; readers prune records whose liveness probe fails. The connector dials each instance's endpoint with the instance's own loopback origin, so it clears the route's origin gate without any configuration. In-process hosts register explicitly with `registerDevframeInstance` from `devframe/node` — see `createDevframeNextHost().mountMcp` for serving MCP on a Next app's own origin. `--port <n>` probes an explicit port besides the registry; `DEVFRAME_INSTANCES_DIR` relocates the registry and `DEVFRAME_DISABLE_INSTANCE_REGISTRY=1` opts a server out.
Discovery reads the **instance registry**: every `createDevServer` (CLI `dev`, `devframeViteBridge`, `@devframes/next`'s handler) writes a record to `~/.devframe/instances/<pid>-<port>.json` on boot and removes it on close; readers prune records whose liveness probe fails. The connector dials each instance's endpoint with the instance's own loopback origin, so it clears the route's origin gate without any configuration. In-process hosts register explicitly with `registerDevframeInstance` from `devframe/node` — see `createDevframeNextHost().mountMcp` for serving MCP on a Next app's own origin. `--port <n>` probes an explicit port besides the registry; `DEVFRAME_INSTANCES_DIR` relocates the registry and `DEVFRAME_DISABLE_INSTANCE_REGISTRY=1` opts a server out.

See the [Agent-Native](/guide/agent-native) page for the full API, safety model, and Claude Desktop integration example.
6 changes: 3 additions & 3 deletions docs/errors/DF0033.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ outline: deep

## Cause

`viteDevBridge({ devMiddleware })` could not bring up the bridge dev server that pairs a host-served SPA (Vite, Nuxt, Astro, etc.) with devframe's RPC backend. Common reasons:
`devframeViteBridge()` (from `@devframes/vite`) could not bring up the bridge dev server that pairs a host-served SPA (Vite, Nuxt, Astro, etc.) with devframe's RPC backend. Common reasons:

- The preferred port is in use and no fallback range was configured.
- Calling `def.setup(ctx)` threw — the devframe's own setup logic surfaced an error.
Expand All @@ -20,10 +20,10 @@ This is a soft warning — the surrounding Vite dev server keeps running, but th

## Fix

- Pin a port via `cli.port` / `cli.portRange` on the devframe definition, or via `devMiddleware.port` on `viteDevBridge`.
- Pin a port via `cli.port` / `cli.portRange` on the devframe definition, or via `port` on `devframeViteBridge`.
- Inspect the `reason` (or the attached `cause`) for the underlying error — fix the setup function or free the port.
- For Nuxt: pass `devMiddleware: { port: <free-port> }` to the `@devframes/nuxt` module.

## Source

- [`packages/devframe/src/helpers/vite.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/helpers/vite.ts) — `viteDevBridge({ devMiddleware })` logs `DF0033` when port resolution or `createDevServer` throws during `configureServer`.
- [`packages/vite/src/index.ts`](https://github.com/devframes/devframe/blob/main/packages/vite/src/index.ts) — `devframeViteBridge()` logs `DF0033` when port resolution or `createDevServer` throws during `configureServer`.
2 changes: 1 addition & 1 deletion docs/errors/DF0052.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The instance's side-car / shared-server transport binding tried to bind the HTTP

## Fix

- Free the port, or pick another via `--port`, `cli.port` / `cli.portRange` on the definition, or `devMiddleware.port` on `viteDevBridge`.
- Free the port, or pick another via `--port`, `cli.port` / `cli.portRange` on the definition, or `port` on `devframeViteBridge` (`@devframes/vite`).
- The original node error is available as `error.cause` — check `error.cause.code` (e.g. `'EADDRINUSE'`) to branch on the failure kind programmatically.

## Source
Expand Down
34 changes: 34 additions & 0 deletions docs/frameworks/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
outline: deep
---

# Frameworks

The framework packages — [`@devframes/vite`](./vite), [`@devframes/nuxt`](./nuxt), and [`@devframes/next`](./next) — integrate devframe with a specific meta-framework's dev server. Each one splits into **two clearly-scoped subpaths**, because you're always doing one of two distinct jobs:

| Scope | Subpath | You are… |
|-------|---------|----------|
| **dev-spa** | `.../dev-spa` | 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 |

The bare package root (`@devframes/vite`, `@devframes/nuxt`, `@devframes/next`) has no export — it throws with a pointer to the two subpaths, so an accidental bare import fails loudly instead of resolving to nothing.

| Package | dev-spa | hub |
|---------|---------|-----|
| [`@devframes/vite`](./vite) | `devframeVitePlugin` / `devframeViteBridge` / `devframeVite` | `viteDevframeHub` (+ `/hub/client`) |
| [`@devframes/nuxt`](./nuxt) | the Nuxt module (`modules: ['@devframes/nuxt/dev-spa']`) | the hub Nuxt module (+ `/hub/client`) |
| [`@devframes/next`](./next) | `withDevframe` + `createDevframeNextHandler` (+ `/dev-spa/client`) | `nextDevframeHub` (+ `/hub/client`) |

## dev-spa: author one devframe

The `dev-spa` scope is for when the thing you're building **is** a devframe — you author its UI with Vite/Nuxt/Next and want its RPC backend running during development. See each package's page for the details; for the framework-neutral CLI/build/embedded outputs, reach for the [adapters](/adapters/) instead.

## hub: mount a devframes-hub

The `hub` scope mounts an [`@devframes/hub`](/guide/hub) — many integrations under one namespace, one merged RPC registry — inside the tool's dev server. Each `hub` entry wraps [`initHub`](/guide/hub-initiate), defaults the UI slot to [`@devframes/hub-ui`](/guide/build-your-own-hub-ui)'s `createUi()` (override with `ui`, or `ui: false` for a headless hub you drive with the matching `/hub/client` helper), and mounts everything behind one catch-all.

- **[Vite](./vite#mounting-a-hub)** — `viteDevframeHub()` shares Vite's dev server and injects the floating dock.
- **[Nuxt](./nuxt#mounting-a-hub)** — the hub Nuxt module wires the Vite hub plugin into `nuxt dev`.
- **[Next](./next#mounting-a-hub)** — `nextDevframeHub()` serves the hub from one App Router route on a side-car socket.

Vite and Nuxt already have native hub viewers ([Vite DevTools](https://devtools.vite.dev), [Nuxt DevTools](https://devtools.nuxt.com)) that integrate the same hub protocol, so `@devframes/vite/hub` and `@devframes/nuxt/hub` print a one-time recommendation to prefer those (silence with `{ quiet: true }`). Next has no native counterpart, so `@devframes/next/hub` stays quiet.
Loading
Loading