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
9 changes: 9 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,18 @@ Callouts (`> [!NOTE]`, `> [!TIP]`, `> [!INFO]`, `::: tip`, etc.) interrupt the r

Trim filler intros, redundant cross-links (one link per page is enough - sidebars handle navigation), and code samples that demonstrate more than the point being made. Lead each page with one sentence that says what the reader can build with this. Strip out promises about future work, marketing language ("powerful", "seamless"), and exposition that the surrounding code already conveys.

### 4. Guides teach, references list

The docs separate learning material from lookup material, following the [Divio documentation system](https://docs.divio.com/documentation-system/):

- **Guide pages (`docs/content/1.guide/`) are learning-oriented** - prose, code examples, and explanation. A lookup table (definition fields, options, enums, statuses, event names, route tables) belongs on a references page, with the guide keeping a one-or-two-sentence prose summary of the essentials plus a link to the reference section. Comparison and decision tables ("X vs Y", trade-off matrices) are explanation and stay in the guides; navigational link tables stay on `index.md` pages.
- **The references section (`docs/content/8.references/`) holds the lookup tables**, grouped: [Node-Side API](docs/content/8.references/4.node-api.md), [Browser-Side API](docs/content/8.references/5.browser-api.md), and [Hub API](docs/content/8.references/6.hub-api.md), alongside the terms, when-clauses, and events pages. Each reference section opens with one line naming what the table lists and linking the guide page that teaches it. A new lookup table goes into the matching reference page (and the references `index.md`), not into a guide.
- **The adapters, frameworks, helpers, and plugins sections are per-package reference pages** - each page is the reference for its own adapter/kit/package, so its options and RPC tables stay in place.

### What goes where

- Critical security / data-loss hazard → `[!WARNING]` callout.
- Experimental API / stability caveat → `[!WARNING]` callout at the top of the page.
- Bad-practice contrast → inline `// ✗ Bad` / `// ✓ Good` comments inside code blocks.
- Lookup table for a guide topic → the matching `docs/content/8.references/` page; the guide keeps a prose summary + link.
- Anything else worth saying → prose.
8 changes: 1 addition & 7 deletions docs/content/1.guide/10.standalone-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,13 +279,7 @@ version.on('updated', () => fetchPayload().then(setData))

## Use your own CLI framework

Own a CLI framework (commander, yargs, oclif)? Use the three factories `createCac` wraps, against one `DevframeDefinition`:

| Building block | Entry |
|----------------|-------|
| `createDevServer(def, opts?)` | `devframe/adapters/dev` |
| `createBuild(def, opts?)` | `devframe/adapters/build` |
| `createMcpServer(def, opts?)` | `devframe/adapters/mcp` |
Own a CLI framework (commander, yargs, oclif)? Use the three factories `createCac` wraps against one `DevframeDefinition`: `createDevServer` (`devframe/adapters/dev`), `createBuild` (`devframe/adapters/build`), and `createMcpServer` (`devframe/adapters/mcp`) — see the [CLI adapter](/adapters/cac#use-your-own-cli-framework).

```ts [src/cli.ts]
import process from 'node:process'
Expand Down
30 changes: 3 additions & 27 deletions docs/content/1.guide/11.client.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,7 @@ await registerDevframeViewerOrigin(connection)

### Options

| Option | Description |
|--------|-------------|
| `connection` | Connection prepared by `setupDevframeConnection()`. |
| `baseURL` | Mount path to probe for `__connection.json` (array = fallback). Default `'./'` (relative to `document.baseURI`); use an absolute path (`'/__devframe/'`) from outside the SPA. |
| `authToken` | Override the auth token (default: a locally-persisted id). |
| `cacheOptions` | `true` for default caching, or an options object. |
| `callTimeout` | Ms before a pending `rpc.call` rejects with a `'timeout'` `DevframeConnectionError`; `0`/omit = wait forever. |
| `wsOptions` | Transport overrides — `onConnected` / `onError` / `onDisconnected` hooks, socket URL. |
| `rpcOptions` | Forwarded to `birpc`. |
| `connectionMeta` | Descriptor that skips the `__connection.json` fetch. |
`baseURL` points at the mount path to probe for `__connection.json` (default `'./'`, relative to `document.baseURI`); `connection` adopts one prepared by `setupDevframeConnection()`. The rest cover auth (`authToken`), [caching](#caching) (`cacheOptions`), timeouts (`callTimeout`), transport hooks (`wsOptions`), `birpc` passthrough (`rpcOptions`), and discovery override (`connectionMeta`) — every option is in the [Browser-Side API reference](/references/browser-api#connectdevframe-options).

## Modes

Expand Down Expand Up @@ -256,14 +247,7 @@ const displayUrl = stripRemoteConnectionFromUrl(viewerUrl)

## Events

Emitted over `rpc.events`:

| Event | Fires when |
|-------|------------|
| `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 node side or a down connection. Carries `(error, method)`. |
Four events arrive over `rpc.events`: `rpc:is-trusted:updated` when trust is granted, denied, or revoked; `connection:status` when the [connection status](#handling-connection-and-auth-errors) changes; `connection:error` on a connection-level failure; and `rpc:error` when an `rpc.call` rejects. Payloads are in the [Browser-Side API reference](/references/browser-api#rpc-client-events).

```ts
rpc.events.on('rpc:is-trusted:updated', (isTrusted) => {
Expand All @@ -280,15 +264,7 @@ rpc.events.on('rpc:is-trusted:updated', (isTrusted) => {

### Connection status

`rpc.status` collapses transport and trust into one value; `rpc.connectionError` holds the last connection-level `Error` (`null` when healthy):

| Status | Meaning |
|--------|---------|
| `connecting` | Establishing socket / handshake. Calls queue until open. |
| `connected` | Socket open and trusted; calls are served. |
| `unauthorized` | Socket open, trust refused. Prompt for [authentication](#authenticating-with-a-one-time-code). |
| `disconnected` | Socket closed (dropped mid-session or never opened). |
| `error` | Fatal — the socket errored or connection meta couldn't load. |
`rpc.status` collapses transport and trust into one value; `rpc.connectionError` holds the last connection-level `Error` (`null` when healthy). It moves through `connecting` (calls queue until open), `connected` (calls are served), `unauthorized` (socket open, trust refused — prompt for [authentication](#authenticating-with-a-one-time-code)), `disconnected`, and `error`; each value's meaning is in the [Browser-Side API reference](/references/browser-api#connection-statuses).

A `static` backend has no live socket, so `rpc.status` stays `connected`.

Expand Down
11 changes: 1 addition & 10 deletions docs/content/1.guide/12.in-page-channel.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,7 @@ Without an `initialValue`, a panel's `get` resolves once the first replay arrive

## Errors and fallbacks

Every failure mode is a coded `InPageChannelError` (`error.code`) with a message that explains itself:

| Code | When | What to do |
|------|------|------------|
| `timeout` | A call outlived `callTimeoutMs` (default 15s), or `whenConnected(ms)` expired | The message carries the endpoint status — `connecting` usually means the page script isn't loaded in this context |
| `closed` | The endpoint was closed with calls pending | Expected during teardown |
| `not-serializable` | A `jsonSerializable: true` payload contained a non-JSON value | The message names the offending path (e.g. `its arguments[0].nodes[2]` is a Map) |
| `not-cloneable` | The port refused to clone a payload (`DataCloneError`) | Strip functions/DOM nodes/reactivity proxies — or declare `jsonSerializable: true` for the precise error above |
| `invalid-args` | Incoming arguments failed their Standard-Schema validation | The message lists the schema issues |
| `state-uninitialized` | The page script read a shared state before providing its `initialValue` | Initialize on first access |
Every failure mode is a coded `InPageChannelError` (`error.code`) with a message that explains itself: `timeout` (a call or `whenConnected(ms)` outlived its deadline), `closed` (endpoint torn down with calls pending), `not-serializable` / `not-cloneable` (a payload the port can't carry — the message names the offending path), `invalid-args` (Standard-Schema validation failed), and `state-uninitialized` (a shared state read before its `initialValue`). Causes and fixes per code are in the [Browser-Side API reference](/references/browser-api#in-page-channel-error-codes).

The panel endpoint's connection lifecycle is explicit, so a panel renders a useful fallback instead of hanging:

Expand Down
19 changes: 3 additions & 16 deletions docs/content/1.guide/14.security.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,9 @@ Pass `clientAuthTokens` for CI/shared machines to skip the prompt, or a custom `

### Auth methods

| RPC method | Direction | Shape |
|------------|-----------|-------|
| `anonymous:devframe:auth` | client → server | `{ authToken, ua, origin }` → `{ isTrusted }` — re-authenticate a stored token |
| `anonymous:devframe:auth:exchange` | client → server | `{ code, ua, origin }` → `{ authToken \| null }` — exchange a code for a token |
| `devframe:auth:revoke` | client → server | self-revoke the caller's own token |
| `devframe:auth:revoked` | server → client | event — token revoked |

Node primitives (`devframe/node/auth`):

| Function | Role |
|----------|------|
| `getTempAuthCode()` / `refreshTempAuthCode()` | read / rotate the one-time code |
| `exchangeTempAuthCode(code, session, { ua, origin }, storage)` | verify a code, mint + store the token, trust the session, return it (or `null`) |
| `verifyAuthToken(token, session, storage)` | trust a session presenting a known token |
| `buildOtpAuthUrl(origin, code?)` | build a magic-link URL embedding the code |
| `revokeAuthToken(context, storage, token)` | delete a token and disconnect sessions using it |
The two `anonymous:`-prefixed handshake methods re-authenticate a stored token (`anonymous:devframe:auth`) and exchange a one-time code for a token (`anonymous:devframe:auth:exchange`); `devframe:auth:revoke` self-revokes, and the `devframe:auth:revoked` event drops affected RPC clients to untrusted. Wire shapes are in the [Node-Side API reference](/references/node-api#auth-methods).

Node primitives in `devframe/node/auth` — `getTempAuthCode` / `refreshTempAuthCode`, `exchangeTempAuthCode`, `verifyAuthToken`, `buildOtpAuthUrl`, and `revokeAuthToken` — implement the same flow for a host framework wiring its own gate; signatures are in the [reference](/references/node-api#node-auth-primitives).

RPC client methods (`devframe/client`): `requestTrustWithCode(code)`, `requestTrustWithToken(token)`, and `ensureTrusted(timeout?)` / `isTrusted` (the trust gate).

Expand Down
6 changes: 1 addition & 5 deletions docs/content/1.guide/15.agent-native.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,4 @@ Prefer coded diagnostics anywhere agent-reachable: agents act on `fix` and follo

## CLI

| Command | Description |
|---------|-------------|
| `<your-app> mcp` | Start the MCP server on `stdio`. |
| `<your-app> 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). |
`<your-app> mcp` starts the MCP server on `stdio`; `<your-app> dev --mcp` serves the agent-consumable API on `/__mcp`; `devframe connect` discovers running devframes and proxies their tools ([MCP adapter](/adapters/mcp#discovery-devframe-connect)). The command table is in the [Node-Side API reference](/references/node-api#mcp-cli-commands).
54 changes: 6 additions & 48 deletions docs/content/1.guide/16.hub.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,7 @@ _Orchestrating multiple devtools (from [A Playground](https://github.com/devfram

## What the hub adds

`DevframeHubContext` adds four subsystems to `DevframeNodeContext`:

| 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. |
`DevframeHubContext` adds four subsystems to `DevframeNodeContext`: `ctx.docks` registers dock entries and groups and [activates docks](#cross-iframe-dock-activation); `ctx.terminals` aggregates terminal sessions with streaming output ([Terminals](/plugins/terminals#hub-aggregation)); `ctx.messages` is the server-side toast/notification queue; `ctx.commands` is the hierarchical command palette with keybindings and `when` clauses. Each subsystem's API is in the [Hub API reference](/references/hub-api#hub-subsystems).

Data-driven UI panels are an opt-in [JSON-Render](/guide/json-render) package (a `json-render` dock type).

Expand Down Expand Up @@ -69,13 +62,7 @@ It mirrors into the `devframe:docks:active` shared-state slot; the [terminals do

## Process-control launchers

A `type: 'launcher'` dock entry is a one-click action tile. Three optional `launcher` fields make it a live process controller:

| Field | Purpose |
|---|---|
| `command` | Bound command id; out-of-process hub UI providers dispatch via `hub:commands:execute` (register a handler via `ctx.commands`). |
| `terminalSessionId` | Tracked session id; a "view in terminal" action calls `hub:docks:activate` with the terminals dock id and `{ sessionId }`. |
| `digest` | Latest progress line, shown inline; patch via `docks.update()`. |
A `type: 'launcher'` dock entry is a one-click action tile. Three optional `launcher` fields make it a live process controller: `command` binds a command id dispatched via `hub:commands:execute`, `terminalSessionId` links a tracked session for a "view in terminal" action, and `digest` shows the latest progress line inline ([Hub API reference](/references/hub-api#launcher-fields)).

`onLaunch` lets a same-process host framework invoke directly; provide `command`, `onLaunch`, or both.

Expand Down Expand Up @@ -173,14 +160,7 @@ export default { skipTrailingSlashRedirect: true }

### Duplicate devframes

When a devframe shares an already-mounted `id`, `duplicationStrategy` decides:

| Strategy | Behavior |
|---|---|
| `'warn'` (default) | Keep the first, drop the later, emit `DF8105`. |
| `'silent'` | Drop the later one without warning. |
| `'throw'` | Throw `DF8105`. |
| `'duplicate'` | Every instance coexists under a disambiguated dock id (`my-tool`, `my-tool-2`, …). |
When a devframe shares an already-mounted `id`, `duplicationStrategy` decides: `'warn'` (the default) keeps the first and drops the later with [`DF8105`](/errors/DF8105), `'silent'` drops it quietly, `'throw'` raises, and `'duplicate'` lets every instance coexist under disambiguated dock ids ([Hub API reference](/references/hub-api#duplication-strategies)).

```ts
defineDevframe({
Expand Down Expand Up @@ -222,35 +202,13 @@ Group and members stay independent top-level entries in `devframe:docks`. Activa

#### Known categories

`DEFAULT_CATEGORIES_ORDER` (from `@devframes/hub`, `/node`, `/client`, `/constants`) names the default buckets:

| Category | Weight | Typical use |
|---|---|---|
| `framework` | `-100` | Framework internals. |
| `default` | `0` | Uncategorized. |
| `app` | `100` | App tools. |
| `ui` | `150` | Components, styling. |
| `data` | `250` | State, storage, queries. |
| `web` | `300` | Network, platform, a11y. |
| `performance` | `350` | Profiling, metrics. |
| `advanced` | `400` | Power-user tools. |
| `docs` | `500` | Documentation. |
| `~builtin` | `1000` | Built-in views; always last. |
`DEFAULT_CATEGORIES_ORDER` (from `@devframes/hub`, `/node`, `/client`, `/constants`) names the default buckets, running from `framework` (weight `-100`) through `default`, `app`, `ui`, `data`, `web`, `performance`, `advanced`, and `docs` to `~builtin` (always last). The weight table is in the [Hub API reference](/references/hub-api#dock-categories).

Framework kits can interleave category ids or override weights; an unknown category sorts as `0`.

## The protocol — what the hub UI provider sees

A hub UI provider imports no hub classes; it reads these shared-state keys and RPC methods:
## The hub UI protocol

| 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 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. |
A hub UI provider imports no hub classes; it renders from four shared-state slots — `devframe:docks` (every registered dock entry), `devframe:commands` (the serializable command list), `devframe:user-settings` (persisted hub settings), and `devframe:docks:active` (the most recent [dock activation](#cross-iframe-dock-activation) request) — and dispatches through two RPC methods, `hub:commands:execute` and `hub:docks:activate`. Types and payloads are in the [Hub API reference](/references/hub-api#hub-ui-protocol).

Broadcast notifications (`devframe:docks:activate`, `devframe:terminals:updated`, `devframe:messages:updated`) arrive via `rpc.client.register(...)`; the client runtime registers `devframe:docks:activate` for you ([Events Reference](/references/events)).

Expand Down
Loading
Loading