You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(hub): cross-iframe dock activation with terminal session focus
Let any connected client — including a mounted devframe running in its own
iframe on its own RPC client — steer the host shell's active dock, which is
otherwise client-local and unreachable from a mounted iframe. The terminals
dock reads the activation params to focus a specific session, so a tool can
spawn a build and navigate the user straight to its terminal output.
Co-authored-by: opencode agent
> Dock activation requested for unknown dock id "`{id}`"
10
+
11
+
## Cause
12
+
13
+
`ctx.docks.activate(dockId)` — reached via the `hub:docks:activate` RPC, which lets any connected client (e.g. a mounted devframe in its own iframe) steer the host shell's active dock — was called with a `dockId` that no registered dock entry owns.
14
+
15
+
This is a warning, not a thrown error: the activation is still broadcast so a dock that registers momentarily later can pick it up, and both the client host and each dock ignore ids they don't recognize. A mis-addressed activation is inert rather than fatal — the warning exists so a typo doesn't fail silently.
16
+
17
+
## Fix
18
+
19
+
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`:
20
+
21
+
```ts
22
+
awaitrpc.call('hub:docks:activate', {
23
+
dockId: 'devframes-plugin-terminals',
24
+
params: { sessionId },
25
+
})
26
+
```
27
+
28
+
## Source
29
+
30
+
-[`packages/hub/src/node/host-docks.ts`](https://github.com/devframes/devframe/blob/main/packages/hub/src/node/host-docks.ts) — `DevframeDocksHost.activate()` reports this when the requested dock id isn't in `views`.
Copy file name to clipboardExpand all lines: docs/guide/hub.md
+24-3Lines changed: 24 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,7 +12,7 @@ A hub-aware node context (`DevframeHubContext`) extends `DevframeNodeContext` wi
12
12
13
13
| Subsystem | Surface | Purpose |
14
14
|---|---|---|
15
-
|`ctx.docks`|`register / update / values`| Multi-tool dock entries (iframes, launchers, json-render, custom-render) and groups that collapse them under one button. |
15
+
|`ctx.docks`|`register / update / values / activate`| Multi-tool dock entries (iframes, launchers, json-render, custom-render) and groups that collapse them under one button. `activate(dockId, params?)` steers which dock the viewer shows — see [Cross-iframe dock activation](#cross-iframe-dock-activation). |
16
16
|`ctx.terminals`|`register / startChildProcess`| Aggregate terminal sessions, stream output over a well-known channel. The single source of truth for "what sessions exist" — see [Terminals](/plugins/terminals#hub-aggregation) for how the terminals plugin renders and mirrors into it. |
|`ctx.commands`|`register / execute / list`| Hierarchical command palette with keybindings and `when` clauses. |
@@ -21,12 +21,31 @@ Plus a `createJsonRenderer(spec)` factory for building remote-UI panels via the
21
21
22
22
## Built-in RPC
23
23
24
-
Every hub context auto-registers this RPC function so framework kits don't reimplement it:
24
+
Every hub context auto-registers these RPC functions so framework kits don't reimplement them:
25
25
26
26
-`hub:commands:execute` — invoke a registered server command by id. `await rpc.call('hub:commands:execute', 'my-tool:do-thing', ...args)`.
27
+
-`hub:docks:activate` — switch the viewer's active dock. `await rpc.call('hub:docks:activate', { dockId, params })` — see [Cross-iframe dock activation](#cross-iframe-dock-activation).
27
28
28
29
Host-specific capabilities (open in editor, reveal in finder, …) ship as kit-registered RPC functions rather than as part of the hub surface.
29
30
31
+
## Cross-iframe dock activation
32
+
33
+
The viewer's active dock is client-local state — which dock is on screen lives in the shell page, not in shared state. A mounted devframe runs in its own iframe on its own RPC client, so it can't reach that selection directly. `hub:docks:activate` bridges the gap: any connected client asks the hub to switch the active dock, and the hub relays the request to the shell.
34
+
35
+
```ts
36
+
// From inside a mounted devframe's iframe (its own RPC client):
37
+
awaitrpc.call('hub:docks:activate', {
38
+
dockId: 'devframes-plugin-terminals',
39
+
params: { sessionId }, // opaque bag the target dock interprets
40
+
})
41
+
```
42
+
43
+
The hub broadcasts the request live over `devframe:docks:activate` (the client host calls its local `switchEntry(dockId)`) and mirrors it into the `devframe:docks:active` shared-state slot, so a dock that mounts *because* of the switch still converges on the request instead of missing the broadcast. `params` is an opaque, serializable bag the target dock reads — the [terminals dock](/plugins/terminals#focusing-a-session) reads `params.sessionId` to focus a specific session. Unknown dock ids degrade to a no-op (warned server-side as [DF8107](/errors/DF8107)); the target dock ignores `params` it doesn't recognize.
44
+
45
+
This is what lets Vite DevTools' Rolldown analyzer spawn a `vite build` via `ctx.terminals.startChildProcess` and then navigate the user straight to that build's terminal session.
46
+
47
+
Server-side, the same switch is available as `ctx.docks.activate(dockId, params?)`.
48
+
30
49
## Mounting a devframe into a hub
31
50
32
51
`mountDevframe(ctx, def)` is the framework-neutral primitive that registers any `DevframeDefinition` as a dock and runs its `setup(ctx)`:
@@ -136,9 +155,11 @@ A hub-aware UI doesn't import any hub classes; it reads three shared-state keys
136
155
|`devframe:docks` shared state |`DevframeDockEntry[]`| Every dock entry the mounted integrations registered. |
137
156
|`devframe:commands` shared state |`DevframeServerCommandEntry[]`| Serializable command list (handlers stripped). |
138
157
|`devframe:user-settings` shared state |`DevframeDocksUserSettings`| Persisted per-workspace hub settings. |
158
+
|`devframe:docks:active` shared state |`DevframeDocksActiveState`| The most recent [dock activation](#cross-iframe-dock-activation) request, so a dock that mounts in response converges on it. |
|`hub:docks:activate` RPC |`({ dockId, params? }) => void`| Switch the active dock from any client. |
140
161
141
-
Plus broadcast notifications (`devframe:terminals:updated`, `devframe:messages:updated`) that a UI can subscribe to via `rpc.client.register(...)`.
162
+
Plus broadcast notifications (`devframe:docks:activate`, `devframe:terminals:updated`, `devframe:messages:updated`) that a UI can subscribe to via `rpc.client.register(...)`. The client host registers the `devframe:docks:activate` handler for you.
Copy file name to clipboardExpand all lines: docs/plugins/terminals.md
+14Lines changed: 14 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -58,6 +58,20 @@ Mounted into a hub, the plugin owns PTY/child-process spawning and its own strea
58
58
59
59
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.
60
60
61
+
## Focusing a session
62
+
63
+
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:
64
+
65
+
```ts
66
+
// e.g. right after ctx.terminals.startChildProcess(..., { id: sessionId, ... })
67
+
awaitrpc.call('hub:docks:activate', {
68
+
dockId: 'devframes-plugin-terminals',
69
+
params: { sessionId },
70
+
})
71
+
```
72
+
73
+
It works whether the panel is already open (it reacts to the `devframe:docks:active` shared-state slot) or mounts in response to the switch (it reads the slot on start and converges). Focus is one-shot: an unknown or not-yet-arrived session id waits for that session to appear, and the user's own tab clicks are always honored afterward. A session id that never appears is a no-op — the default selection (most-recent session) stands.
0 commit comments