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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ The `pnpm test` script intentionally runs `build` first so `tsnapi` snapshots co

## Conventions

- RPC functions must use `defineRpcFunction`; always namespace IDs (`my-plugin:fn-name`).
- RPC functions must use `defineRpcFunction`; always namespace IDs `devframes:plugin:<slug>:<fn-name>` (matching the plugin's `@devframes/plugin-<slug>` package name).
- 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:<name>`, don't pin versions in `package.json`.
Expand Down
4 changes: 2 additions & 2 deletions docs/errors/DF8107.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ This is a warning, not a thrown error: the activation is still broadcast so a do

## Fix

Pass a `dockId` that matches a registered dock entry. Ids are case-sensitive, so check for typos, and make sure the target dock is registered before activating it. For the terminals dock the id is `devframes-plugin-terminals`:
Pass a `dockId` that matches a registered dock entry. Ids are case-sensitive, so check for typos, and make sure the target dock is registered before activating it. For the terminals dock the id is `devframes_plugin_terminals`:

```ts
await rpc.call('hub:docks:activate', {
dockId: 'devframes-plugin-terminals',
dockId: 'devframes_plugin_terminals',
params: { sessionId },
})
```
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/hub.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ The viewer's active dock is client-local state — which dock is on screen lives
```ts
// From inside a mounted devframe's iframe (its own RPC client):
await rpc.call('hub:docks:activate', {
dockId: 'devframes-plugin-terminals',
dockId: 'devframes_plugin_terminals',
params: { sessionId }, // opaque bag the target dock interprets
})
```
Expand Down
2 changes: 1 addition & 1 deletion docs/plugins/code-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default createCodeServerDevframe({

## RPC surface

All functions are namespaced `devframes-plugin-code-server:*`:
All functions are namespaced `devframes:plugin:code-server:*`:

| Function | Type | Purpose |
|----------|------|---------|
Expand Down
10 changes: 5 additions & 5 deletions docs/plugins/git.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ await createCac(createGitDevframe({ repoRoot: process.cwd() })).parse()

The read functions are each a `query` with `snapshot: true` — resolved live over WebSocket in dev, and served from a snapshot baked at build time for static deploys. Each degrades to an empty, `isRepo: false` result outside a git repository.

- `git:status` — branch, upstream tracking, and staged / unstaged / untracked files.
- `git:log` — paginated commit history including parent hashes, which drive the commit graph.
- `git:branches` — local branches with SHA, upstream, ahead / behind, and tip subject.
- `git:diff` — per-file added / deleted counts plus a unified patch for a selected file.
- `devframes:plugin:git:status` — branch, upstream tracking, and staged / unstaged / untracked files.
- `devframes:plugin:git:log` — paginated commit history including parent hashes, which drive the commit graph.
- `devframes:plugin:git:branches` — local branches with SHA, upstream, ahead / behind, and tip subject.
- `devframes:plugin:git:diff` — per-file added / deleted counts plus a unified patch for a selected file.

Write actions (`git:stage`, `git:unstage`, `git:commit`) are `action` functions, registered only when write mode is enabled.
Write actions (`devframes:plugin:git:stage`, `devframes:plugin:git:unstage`, `devframes:plugin:git:commit`) are `action` functions, registered only when write mode is enabled.

## Source

Expand Down
2 changes: 1 addition & 1 deletion docs/plugins/inspect.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ await createCac(createInspectDevframe({ port: 9100 })).parse()

## RPC surface

All functions are namespaced `devframes-plugin-inspect:*`:
All functions are namespaced `devframes:plugin:inspect:*`:

| Function | Type | Returns |
|----------|------|---------|
Expand Down
6 changes: 3 additions & 3 deletions docs/plugins/terminals.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,20 @@ export default createTerminalsDevframe({

## Hub aggregation

Mounted into a hub, the plugin owns PTY/child-process spawning and its own streaming channel (`devframes-plugin-terminals:output`) — that's what the panel renders. It also mirrors every session it spawns into `ctx.terminals` (the hub's aggregate registry, streaming on `devframe:terminals`) so other tools — a launcher dock, a custom panel — see the same session list without depending on the plugin's own types. A session started the other way, via `ctx.terminals.startChildProcess` / `startPtySession` directly (e.g. from a launcher dock), shows up in the terminals panel too — the plugin reads foreign hub sessions read-only and renders them alongside its own, subscribing to the hub's channel for their output.
Mounted into a hub, the plugin owns PTY/child-process spawning and its own streaming channel (`devframes:plugin:terminals:output`) — that's what the panel renders. It also mirrors every session it spawns into `ctx.terminals` (the hub's aggregate registry, streaming on `devframe:terminals`) so other tools — a launcher dock, a custom panel — see the same session list without depending on the plugin's own types. A session started the other way, via `ctx.terminals.startChildProcess` / `startPtySession` directly (e.g. from a launcher dock), shows up in the terminals panel too — the plugin reads foreign hub sessions read-only and renders them alongside its own, subscribing to the hub's channel for their output.

`ctx.terminals` is the source of truth for "what sessions exist"; the plugin is the panel that renders them and the one PTY-capable provider among possibly several session sources. The plugin never imports `@devframes/hub`'s types to stay mountable without a hub — it duck-types the minimal `register` / `update` / `events` shape it needs.

A session from `ctx.terminals.startChildProcess()` carries a `getResult()` accessor shaped like `tinyexec`'s `Result` — `await`able to `{ stdout, stderr, exitCode }` (captured separately from the merged display stream), with live `pid` / `exitCode` / `killed` getters and `kill()` in the meantime. That's the seam for migrating an existing `tinyexec`/`execa`-based "run a subprocess and get its result" API onto the hub's terminals: keep the same calling code, swap the runner for `startChildProcess()`, and the session's output shows up in every hub-aware terminal panel for free.

## Focusing a session

The panel reacts to the hub's [cross-iframe dock activation](/guide/hub#cross-iframe-dock-activation): when an activation targets this dock (`dockId: 'devframes-plugin-terminals'`) and carries a `sessionId`, the panel selects that session. This lets another tool spawn a build and jump the user straight to its output:
The panel reacts to the hub's [cross-iframe dock activation](/guide/hub#cross-iframe-dock-activation): when an activation targets this dock (`dockId: 'devframes_plugin_terminals'`) and carries a `sessionId`, the panel selects that session. This lets another tool spawn a build and jump the user straight to its output:

```ts
// e.g. right after ctx.terminals.startChildProcess(..., { id: sessionId, ... })
await rpc.call('hub:docks:activate', {
dockId: 'devframes-plugin-terminals',
dockId: 'devframes_plugin_terminals',
params: { sessionId },
})
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ describe('minimal-next-devframe-hub (example)', () => {
expect(dockIds).toContain('~settings')
expect(docks.find(d => d.id === '~settings')?.category).toBe('~builtin')
// The dogfooded built-in plugin packages mount their own docks.
expect(dockIds).toContain('devframes-plugin-terminals')
expect(dockIds).toContain('devframes-plugin-messages')
expect(dockIds).toContain('devframes_plugin_terminals')
expect(dockIds).toContain('devframes_plugin_messages')
})

it('lists startup and demo messages through the kit-local RPC', async () => {
Expand Down
8 changes: 4 additions & 4 deletions packages/hub/src/client/__tests__/host.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,17 +125,17 @@ describe('createDevframeClientHost', () => {
it('switches the active dock when the hub broadcasts devframe:docks:activate', async () => {
const { rpc, states, definitions } = createStubRpc()
const host = await createDevframeClientHost({ rpc })
states.get('devframe:docks')!.push([iframeEntry('one'), iframeEntry('devframes-plugin-terminals')])
states.get('devframe:docks')!.push([iframeEntry('one'), iframeEntry('devframes_plugin_terminals')])

// Simulate the hub's server→client broadcast.
const handler = definitions.get('devframe:docks:activate')!.handler!
handler({ dockId: 'devframes-plugin-terminals', params: { sessionId: 'sess-1' } })
await vi.waitFor(() => expect(host.context.docks.selectedId).toBe('devframes-plugin-terminals'))
handler({ dockId: 'devframes_plugin_terminals', params: { sessionId: 'sess-1' } })
await vi.waitFor(() => expect(host.context.docks.selectedId).toBe('devframes_plugin_terminals'))

// Unknown dock ids degrade to a no-op (the previous selection stands).
handler({ dockId: 'ghost' })
await new Promise(r => setTimeout(r, 0))
expect(host.context.docks.selectedId).toBe('devframes-plugin-terminals')
expect(host.context.docks.selectedId).toBe('devframes_plugin_terminals')
host.dispose()
})

Expand Down
10 changes: 5 additions & 5 deletions packages/hub/src/node/__tests__/context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,23 +37,23 @@ describe('createHubContext dock activation', () => {
})
context.docks.register({
type: 'iframe',
id: 'devframes-plugin-terminals',
id: 'devframes_plugin_terminals',
title: 'Terminals',
icon: 'ph:terminal-window-duotone',
url: '/__devframes-plugin-terminals/',
url: '/__devframes_plugin_terminals/',
})

const broadcast = vi.spyOn(context.rpc, 'broadcast').mockResolvedValue()
context.docks.activate('devframes-plugin-terminals', { sessionId: 'sess-1' })
context.docks.activate('devframes_plugin_terminals', { sessionId: 'sess-1' })

const active = await context.rpc.sharedState.get<{ activation: unknown }>('devframe:docks:active')
expect(active.value().activation).toEqual({
dockId: 'devframes-plugin-terminals',
dockId: 'devframes_plugin_terminals',
params: { sessionId: 'sess-1' },
})
expect(broadcast).toHaveBeenCalledWith({
method: 'devframe:docks:activate',
args: [{ dockId: 'devframes-plugin-terminals', params: { sessionId: 'sess-1' } }],
args: [{ dockId: 'devframes_plugin_terminals', params: { sessionId: 'sess-1' } }],
})
broadcast.mockRestore()
})
Expand Down
8 changes: 4 additions & 4 deletions packages/hub/src/node/__tests__/rpc-builtins.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,16 @@ describe('hub docks activate RPC', () => {
const ctx = { docks: { activate } } as unknown as DevframeHubContext

const fn = await hubDocksActivate.setup!(ctx)
await fn.handler!({ dockId: 'devframes-plugin-terminals', params: { sessionId: 'sess-1' } })
expect(activate).toHaveBeenCalledWith('devframes-plugin-terminals', { sessionId: 'sess-1' })
await fn.handler!({ dockId: 'devframes_plugin_terminals', params: { sessionId: 'sess-1' } })
expect(activate).toHaveBeenCalledWith('devframes_plugin_terminals', { sessionId: 'sess-1' })
})

it('forwards a bare dockId without params', async () => {
const activate = vi.fn()
const ctx = { docks: { activate } } as unknown as DevframeHubContext

const fn = await hubDocksActivate.setup!(ctx)
await fn.handler!({ dockId: 'devframes-plugin-messages' })
expect(activate).toHaveBeenCalledWith('devframes-plugin-messages', undefined)
await fn.handler!({ dockId: 'devframes_plugin_messages' })
expect(activate).toHaveBeenCalledWith('devframes_plugin_messages', undefined)
})
})
4 changes: 2 additions & 2 deletions plans/014-git-show-dump-parallel.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ it('bakes one show record per commit in the snapshot', async () => {
try {
const ctx = await createDashboardContext(repo.dir, 'build')
const dump = await collectStaticRpcDump(ctx.rpc.definitions.values(), ctx)
const entry = dump.manifest['git:show']
const entry = dump.manifest['devframes:plugin:git:show']
expect(entry).toBeDefined()
// The temp repo has 2 commits; both should be baked, each with a hash + output.
const files = Object.values(dump.files).map(f => (f.data as any).output)
Expand All @@ -117,7 +117,7 @@ it('bakes one show record per commit in the snapshot', async () => {
```

Adjust the assertion to how the git dump surfaces records in the manifest/files
(inspect `dump.manifest['git:show']` shape while writing). The key assertion:
(inspect `dump.manifest['devframes:plugin:git:show']` shape while writing). The key assertion:
every commit in the window produced a record, and order/content match the serial
version.

Expand Down
12 changes: 6 additions & 6 deletions plans/019-inspect-rpc-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ by plans 004 and 014, so this plan focuses on inspect.)
## Current state

`plugins/inspect/src/rpc/functions/` defines (names verified):
- `devframes-plugin-inspect:list-functions`
- `devframes-plugin-inspect:invoke`
- `devframes-plugin-inspect:list-state-keys`
- `devframes-plugin-inspect:describe-agent`
- `devframes-plugin-inspect:read-agent-resource`
- `devframes-plugin-inspect:invoke-agent-tool`
- `devframes:plugin:inspect:list-functions`
- `devframes:plugin:inspect:invoke`
- `devframes:plugin:inspect:list-state-keys`
- `devframes:plugin:inspect:describe-agent`
- `devframes:plugin:inspect:read-agent-resource`
- `devframes:plugin:inspect:invoke-agent-tool`

Existing tests: `plugins/inspect/test/{dev-server,static-build,function-name}.test.ts`.
`dev-server.test.ts` already boots the inspect devframe over a real server —
Expand Down
4 changes: 2 additions & 2 deletions plugins/a11y/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ behave identically; the panel's `websocket` / `static` tag is the only tell.
Standalone, without a host app:

```sh
pnpm -C plugins/a11y dev # panel only, at /__devframe-a11y-inspector/
pnpm -C plugins/a11y dev # panel only, at /__devframes_plugin_a11y/
```

## File map
Expand All @@ -74,7 +74,7 @@ pnpm -C plugins/a11y dev # panel only, at /__devframe-a11y-inspector/
|------|--------|---------|
| `src/index.ts` | `.` | `createA11yDevframe()` + the default `DevframeDefinition`; `a11yAgentBundlePath` — the agent module a hub attaches as this dock's client script |
| `src/node/index.ts` | `/node` | `setupA11y(ctx)` — registers the RPC functions |
| `src/cli.ts` | `/cli` | `createA11yCli()` — backs the `devframe-a11y-inspector` bin |
| `src/cli.ts` | `/cli` | `createA11yCli()` — backs the `devframes_plugin_a11y` bin |
| `src/vite.ts` | `/vite` | `a11yVitePlugin()` — mounts the panel into a Vite host |
| `src/client/index.ts` | `/client` | `connectA11y()` — typed browser RPC client wrapper |
| `src/rpc/` | — | `get-config` static RPC + the type-safe client registry |
Expand Down
2 changes: 1 addition & 1 deletion plugins/a11y/demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ <h2>This week's roasts</h2>

<!-- Docked A11y Inspector panel (same origin → shares the BroadcastChannel). -->
<aside class="df-dock" aria-label="A11y Inspector">
<iframe class="df-dock__frame" title="A11y Inspector panel" src="/__devframe-a11y-inspector/"></iframe>
<iframe class="df-dock__frame" title="A11y Inspector panel" src="/__devframes_plugin_a11y/"></iframe>
</aside>

<!-- The injected a11y agent: scans this page and answers the panel. -->
Expand Down
2 changes: 1 addition & 1 deletion plugins/a11y/demo/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*
* GET / → the demo page (intentional a11y bugs)
* GET /__df-inject/inject.js → the injected agent bundle
* GET /__devframe-a11y-inspector/** → the Solid panel SPA
* GET /__devframes_plugin_a11y/** → the Solid panel SPA
*
* Two modes prove the plugin works either way:
*
Expand Down
2 changes: 1 addition & 1 deletion plugins/a11y/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
},
"types": "./dist/index.d.mts",
"bin": {
"devframe-a11y-inspector": "./bin.mjs"
"devframes_plugin_a11y": "./bin.mjs"
},
"files": [
"bin.mjs",
Expand Down
2 changes: 1 addition & 1 deletion plugins/a11y/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import a11yDevframe from './index.ts'

/**
* Build the standalone CLI for the a11y inspector — backs the package `bin`
* (`devframe-a11y-inspector`) and `npx @devframes/plugin-a11y`. Wraps the
* (`devframes_plugin_a11y`) and `npx @devframes/plugin-a11y`. Wraps the
* default {@link createA11yDevframe} definition with devframe's
* `dev` / `build` / `spa` command shell.
*/
Expand Down
6 changes: 3 additions & 3 deletions plugins/a11y/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import pkg from '../package.json' with { type: 'json' }
import { setupA11y } from './node/index.ts'

/** Default devframe id — drives the standalone CLI command and the hosted mount path `/__<id>/`. */
const DEFAULT_ID = 'devframe-a11y-inspector'
const BASE_PATH = '/__devframe-a11y-inspector/'
const DEFAULT_ID = 'devframes_plugin_a11y'
const BASE_PATH = '/__devframes_plugin_a11y/'

// The Solid panel SPA is built (by Vite) into `dist/spa`. From both the
// source entry (`src/index.ts`, via the workspace alias) and the published
Expand Down Expand Up @@ -34,7 +34,7 @@ export interface A11yDevframeOptions {
/** Override the dock icon. */
icon?: string
/**
* Override the mount path. Defaults to `/__devframe-a11y-inspector/` so the
* Override the mount path. Defaults to `/__devframes_plugin_a11y/` so the
* panel iframe shares an origin with the host page it scans.
*/
basePath?: string
Expand Down
4 changes: 2 additions & 2 deletions plugins/a11y/src/inject/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ export interface A11yAgentContext {
}

/** One deduplicated summary entry tracks the scan lifecycle. */
const SCAN_MESSAGE_ID = 'devframe-a11y-inspector:scan'
const SCAN_MESSAGE_ID = 'devframes:plugin:a11y:scan'
/** One deduplicated entry per violated rule; removed when the rule clears. */
const RULE_MESSAGE_PREFIX = 'devframe-a11y-inspector:rule:'
const RULE_MESSAGE_PREFIX = 'devframes:plugin:a11y:rule:'

const IMPACT_LEVEL: Record<Impact, HubMessageInput['level']> = {
critical: 'error',
Expand Down
2 changes: 1 addition & 1 deletion plugins/a11y/src/rpc/functions/get-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const IMPACT_COPY = {
* same in both modes.
*/
export const getConfig = defineRpcFunction({
name: 'devframe-a11y-inspector:get-config',
name: 'devframes:plugin:a11y:get-config',
type: 'static',
jsonSerializable: true,
handler: () => ({
Expand Down
2 changes: 1 addition & 1 deletion plugins/a11y/src/shared/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/

/** BroadcastChannel name. Namespaced with the devframe id, per convention. */
export const A11Y_CHANNEL = 'devframe-a11y-inspector'
export const A11Y_CHANNEL = 'devframes:plugin:a11y'

/**
* Attribute the agent stamps on each violating element so the panel can ask
Expand Down
2 changes: 1 addition & 1 deletion plugins/a11y/src/spa/lib/devframe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function connectDevframeState(): DevframeState {
// The server-fn augmentation lives in `src/rpc` (node side); the client
// bundle doesn't import it, so call by name with a local return type.
const callConfig = rpc.callOptional as (name: string) => Promise<A11yConfig | undefined>
const cfg = await callConfig('devframe-a11y-inspector:get-config')
const cfg = await callConfig('devframes:plugin:a11y:get-config')
if (cfg)
setConfig(cfg)
})
Expand Down
2 changes: 1 addition & 1 deletion plugins/a11y/src/spa/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { alias } from '../../../../alias'

// `base: './'` + `<base href="./" />` keeps the bundle mount-path portable:
// the same `dist/spa` works whether devframe serves it at `/` (standalone)
// or `/__devframe-a11y-inspector/` (mounted in a hub). `connectDevframe`
// or `/__devframes_plugin_a11y/` (mounted in a hub). `connectDevframe`
// resolves its connection meta relative to `document.baseURI` to match.
export default defineConfig({
base: './',
Expand Down
2 changes: 1 addition & 1 deletion plugins/a11y/src/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export type { ViteDevBridgeOptions }
/**
* Mount the a11y inspector panel into an existing Vite dev server. In the
* default static-mount mode it serves the built panel at
* `/__devframe-a11y-inspector/`; pass `{ devMiddleware: true }` for the
* `/__devframes_plugin_a11y/`; pass `{ devMiddleware: true }` for the
* bridge mode where the host owns the SPA and devframe runs a side-car
* RPC + WS server.
*
Expand Down
2 changes: 1 addition & 1 deletion plugins/a11y/tests/_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const SPA_DIST = resolve(HERE, '../dist/spa')
export function assertClientBuilt(): void {
if (!existsSync(path.join(SPA_DIST, 'index.html'))) {
throw new Error(
'[devframe-a11y-inspector] dist/spa missing — run `pnpm -C plugins/a11y run build` first.',
'[devframes_plugin_a11y] dist/spa missing — run `pnpm -C plugins/a11y run build` first.',
)
}
}
Expand Down
Loading
Loading