@devframes/hub 0.9.8 → 0.9.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
- import { a as normalizeHubBase, i as DOCK_RENDERERS_STATE_KEY, r as DEVFRAMES_HUB_BASE } from "../constants-D1zicoJ4.mjs";
2
- import { a as resolveClientModuleSpecifier } from "../remote-url-Bgc7gtsP.mjs";
3
- import { m as prepareDevframe, t as createHubContext, y as diagnostics } from "../context-DYUkd3il.mjs";
1
+ import { a as normalizeHubBase, i as DOCK_RENDERERS_STATE_KEY, r as DEVFRAMES_HUB_BASE } from "../constants-BUd6oMci.mjs";
2
+ import { a as resolveClientModuleSpecifier } from "../remote-url-UClKLQ5_.mjs";
3
+ import { m as prepareDevframe, t as createHubContext, y as diagnostics } from "../context-D4L966cN.mjs";
4
4
  import { joinURL, withTrailingSlash, withoutLeadingSlash } from "ufo";
5
5
  import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_MCP_ROUTE, DEVFRAME_WS_ROUTE } from "devframe/constants";
6
- import { createH3DevframeHost, createInstanceShell, importRuntimeModule, resolveInstanceRegister } from "devframe/internal";
6
+ import { createH3DevframeHost, createInstanceShell, importRuntimeModule, resolveInstanceRegister, resolveMcpConfig } from "devframe/internal";
7
7
  import { resolve } from "pathe";
8
8
  import process from "node:process";
9
9
  import { existsSync } from "node:fs";
@@ -23,7 +23,7 @@ function assetContentType(key) {
23
23
  if (key.endsWith(".html")) return "text/html; charset=utf-8";
24
24
  return "application/octet-stream";
25
25
  }
26
- /** Reserved filenames directly under the hub base — a frame id can't shadow them. */
26
+ /** Reserved filenames directly under the hub base; a frame id can't shadow them. */
27
27
  const RESERVED_HUB_PATHS = [
28
28
  DEVFRAME_CONNECTION_META_FILENAME,
29
29
  DEVFRAME_DOCK_IMPORTS_FILENAME,
@@ -35,7 +35,7 @@ const RESERVED_HUB_PATHS = [
35
35
  ];
36
36
  /**
37
37
  * Flatten the `devframes` input: await every thenable, call every factory,
38
- * spread the arrays, and drop the empty slots — so a host can build the list
38
+ * spread the arrays, and drop the empty slots, so a host can build the list
39
39
  * conditionally (`isDev && loadInspect()`) without filtering it first.
40
40
  */
41
41
  function resolveDevframesInput(input) {
@@ -67,7 +67,7 @@ function resolveRendererRegistrations(registrations) {
67
67
  });
68
68
  }
69
69
  /**
70
- * Render the dock client-script import map as an ES module — one dynamic
70
+ * Render the dock client-script import map as an ES module: one dynamic
71
71
  * import thunk per dock that carries a client script (`clientScript` on
72
72
  * iframe docks, `action`, `renderer`). External viewers import this module
73
73
  * from `<base>__client-imports.js` to load per-dock client code into the
@@ -87,17 +87,78 @@ function renderClientImportsModule(ctx) {
87
87
  const thunks = scripts.map((script) => `() => import(${JSON.stringify(resolveClientModuleSpecifier(script.importFrom, { template }))})`);
88
88
  entries.push(` ${JSON.stringify(id)}: [${thunks.join(", ")}],`);
89
89
  }
90
- return `// Generated by @devframes/hub — dock client-script import map.\nexport const clientImports = {\n${entries.join("\n")}\n}\nexport default clientImports\n`;
90
+ return `// Generated by @devframes/hub: dock client-script import map.\nexport const clientImports = {\n${entries.join("\n")}\n}\nexport default clientImports\n`;
91
91
  }
92
92
  /**
93
- * Initiate a hub instance — the whole multi-devframe devtools surface
93
+ * Resolve the shared hub context: reuse a caller-supplied one, or build an
94
+ * h3-backed context that serves the hub's connection meta under every frame.
95
+ */
96
+ async function resolveHubContext(options, app, cwd, api) {
97
+ if (options.context && options.devframes?.length) throw diagnostics.DF8002();
98
+ if (options.context) return options.context;
99
+ const host = {
100
+ ...createH3DevframeHost({
101
+ origin: () => api.origin() ?? "http://localhost",
102
+ appName: "devframes",
103
+ workspaceRoot: cwd,
104
+ mount: (mountBase, dir) => {
105
+ mountStaticHandler(app, mountBase, dir);
106
+ }
107
+ }),
108
+ ...options.getStorageDir ? { getStorageDir: options.getStorageDir } : {},
109
+ /**
110
+ * Serve the hub's own connection meta under every mounted frame's
111
+ * base, so each SPA discovers the shared RPC endpoint via its
112
+ * relative `./__connection.json` fetch; the meta's WS path is
113
+ * hub-base-absolute, so it resolves to the one shared socket no
114
+ * matter how deep the frame base is.
115
+ */
116
+ mountConnectionMeta: (frameBase) => {
117
+ app.use(joinURL(frameBase, DEVFRAME_CONNECTION_META_FILENAME), () => api.connectionMeta());
118
+ }
119
+ };
120
+ return createHubContext({
121
+ cwd,
122
+ workspaceRoot: cwd,
123
+ mode: "dev",
124
+ host,
125
+ ...options.rpcDeclarations ? { builtinRpcDeclarations: options.rpcDeclarations } : {}
126
+ });
127
+ }
128
+ /**
129
+ * Pass 1: mount each devframe under `<base><id>/` (SPA, meta, iframe dock)
130
+ * and queue its declared services, guarding the id against reserved hub
131
+ * filenames and route-pattern characters. Returns the deferred setup thunks.
132
+ */
133
+ async function mountDevframes(ctx, devframes, base, frames, hubMcpEnabled) {
134
+ const setups = [];
135
+ for (const { devframe: def, dock } of devframes) {
136
+ if (RESERVED_HUB_PATHS.includes(def.id)) throw diagnostics.DF8000({ id: def.id });
137
+ if (!/^[\w.-]+$/.test(def.id)) throw diagnostics.DF8004({ id: def.id });
138
+ if (!hubMcpEnabled && def.cli?.mcp) diagnostics.DF8005({ id: def.id });
139
+ const frameBase = withTrailingSlash(joinURL(base, def.id));
140
+ const run = await prepareDevframe(ctx, def, {
141
+ base: frameBase,
142
+ ...dock ? { dock } : {}
143
+ });
144
+ if (run) setups.push(run);
145
+ frames.push({
146
+ id: def.id,
147
+ base: frameBase,
148
+ title: def.name
149
+ });
150
+ }
151
+ return setups;
152
+ }
153
+ /**
154
+ * Initiate a hub instance: the whole multi-devframe devtools surface
94
155
  * behind one framework-agnostic, web-standard handler. Every mounted
95
156
  * devframe shares one context (merged RPC registry, shared state, docks /
96
157
  * terminals / messages / commands), one WebSocket transport, and one Auth;
97
158
  * the instance serves each frame's SPA at `<base><id>/`, the discovery
98
159
  * endpoints (`__connection.json`, `__index.json`, `__client-imports.js`),
99
160
  * the aggregate MCP route, and whatever the {@link DevframeHubUi} slot
100
- * provides — the hub itself stays headless.
161
+ * provides, while the hub itself stays headless.
101
162
  *
102
163
  * The factory is synchronous and initializes eagerly, and binds no port of
103
164
  * its own: the WebSocket follows `server` / `ws` (see
@@ -127,6 +188,11 @@ function initHub(options) {
127
188
  ...options.name !== void 0 ? { name: options.name } : {},
128
189
  rootDir: cwd
129
190
  }),
191
+ /**
192
+ * One meta document is served from the hub base *and* from every frame
193
+ * base, so the advertised WS path has to be base-absolute to resolve to
194
+ * the same socket from any depth.
195
+ */
130
196
  absoluteWsPath: true,
131
197
  resolveSidecarPort: async (sidecarHost) => {
132
198
  const { getPort } = await import("../dist-R0T2g9Vw.mjs");
@@ -140,54 +206,14 @@ function initHub(options) {
140
206
  throw diagnostics.DF8003();
141
207
  },
142
208
  async init(api) {
143
- if (options.context && options.devframes?.length) throw diagnostics.DF8002();
144
- let ctx;
145
- if (options.context) ctx = options.context;
146
- else {
147
- const host = {
148
- ...createH3DevframeHost({
149
- origin: () => api.origin() ?? "http://localhost",
150
- appName: "devframes",
151
- workspaceRoot: cwd,
152
- mount: (mountBase, dir) => {
153
- mountStaticHandler(app, mountBase, dir);
154
- }
155
- }),
156
- ...options.getStorageDir ? { getStorageDir: options.getStorageDir } : {},
157
- mountConnectionMeta: (frameBase) => {
158
- app.use(joinURL(frameBase, DEVFRAME_CONNECTION_META_FILENAME), () => api.connectionMeta());
159
- }
160
- };
161
- ctx = await createHubContext({
162
- cwd,
163
- workspaceRoot: cwd,
164
- mode: "dev",
165
- host,
166
- ...options.rpcDeclarations ? { builtinRpcDeclarations: options.rpcDeclarations } : {}
167
- });
168
- }
209
+ const ctx = await resolveHubContext(options, app, cwd, api);
169
210
  if (options.clientModuleResolution) ctx.staticConfig.dock = {
170
211
  ...ctx.staticConfig.dock,
171
212
  clientModuleResolution: options.clientModuleResolution
172
213
  };
173
214
  const devframes = await resolveDevframesInput(options.devframes ?? []);
174
215
  for (const input of options.services ?? []) ctx.services.install(input);
175
- const setups = [];
176
- for (const { devframe: def, dock } of devframes) {
177
- if (RESERVED_HUB_PATHS.includes(def.id)) throw diagnostics.DF8000({ id: def.id });
178
- if (!/^[\w.-]+$/.test(def.id)) throw diagnostics.DF8004({ id: def.id });
179
- const frameBase = withTrailingSlash(joinURL(base, def.id));
180
- const run = await prepareDevframe(ctx, def, {
181
- base: frameBase,
182
- ...dock ? { dock } : {}
183
- });
184
- if (run) setups.push(run);
185
- frames.push({
186
- id: def.id,
187
- base: frameBase,
188
- title: def.name
189
- });
190
- }
216
+ const setups = await mountDevframes(ctx, devframes, base, frames, !!options.mcp);
191
217
  await ctx.services.ready();
192
218
  for (const run of setups) await run();
193
219
  await options.configure?.(ctx);
@@ -198,7 +224,7 @@ function initHub(options) {
198
224
  ...registration.importName ? { importName: registration.importName } : {}
199
225
  };
200
226
  (await ctx.rpc.sharedState.get(DOCK_RENDERERS_STATE_KEY, { initialValue: {} })).mutate(() => manifest);
201
- const mcpConfig = options.mcp === true ? {} : options.mcp;
227
+ const mcpConfig = resolveMcpConfig(options.mcp);
202
228
  if (!mcpConfig) return { context: ctx };
203
229
  const mcpRoute = withoutLeadingSlash(mcpConfig.path ?? DEVFRAME_MCP_ROUTE);
204
230
  const { mountMcpHttp } = await importRuntimeModule("devframe/adapters/mcp");
@@ -206,6 +232,7 @@ function initHub(options) {
206
232
  serverName: options.name ?? "devframes-hub",
207
233
  serverVersion: options.version ?? "0.0.0",
208
234
  exposeSharedState: true,
235
+ authorization: mcpConfig.authorization,
209
236
  allowedOrigins: mcpConfig.allowedOrigins
210
237
  });
211
238
  return {
@@ -2,7 +2,7 @@ import { REMOTE_CONNECTION_KEY } from "devframe/constants";
2
2
  //#region src/client-modules.ts
3
3
  const URL_SCHEME_RE = /^[a-z][a-z\d+.-]*:/i;
4
4
  /**
5
- * Whether a client-script `importFrom` is a **bare module specifier** — an
5
+ * Whether a client-script `importFrom` is a **bare module specifier**: an
6
6
  * npm-style name (`vite-plugin-vue-tracer/client/vite-devtools`) rather than
7
7
  * a URL the browser can resolve natively.
8
8
  */
@@ -15,10 +15,10 @@ function isBareModuleSpecifier(specifier) {
15
15
  * specifiers pass through untouched; a **bare** specifier resolves through
16
16
  * the explicit `resolveClientModule` callback (a viewer's own policy), then
17
17
  * the host-advertised `template` (`ConnectionMeta.configs.dock.clientModuleResolution`,
18
- * its `{specifier}` token replaced — e.g. Vite's `'/@id/{specifier}'`),
18
+ * its `{specifier}` token replaced, e.g. Vite's `'/@id/{specifier}'`),
19
19
  * resolved against `metaBaseUrl` so it targets the host's origin from any
20
- * viewer. With neither, it stays unchanged — a page import map may still
21
- * cover it.
20
+ * viewer. With neither, it stays unchanged, since a page import map may
21
+ * still cover it.
22
22
  */
23
23
  function resolveClientModuleSpecifier(specifier, options = {}) {
24
24
  if (!isBareModuleSpecifier(specifier)) return specifier;
@@ -40,8 +40,8 @@ function resolveClientModuleSpecifier(specifier, options = {}) {
40
40
  */
41
41
  function clientScriptFailureHint(importFrom, specifier) {
42
42
  if (!isBareModuleSpecifier(importFrom)) return "";
43
- if (specifier === importFrom) return " — the specifier is a bare npm specifier and this host advertises no client-module resolution (`ConnectionMeta.configs.dock.clientModuleResolution`). Serve the script as a self-contained bundle by URL, pass `resolveClientModule` to this client host, or run under a host that resolves bare specifiers (e.g. Vite: `/@id/{specifier}`).";
44
- return ` — the host resolved the bare specifier "${importFrom}" to this URL but could not serve the module. Check the package is installed and resolvable from the host project's root (and built, if its exports point at build output).`;
43
+ if (specifier === importFrom) return ". The specifier is a bare npm specifier and this host advertises no client-module resolution (`ConnectionMeta.configs.dock.clientModuleResolution`). Serve the script as a self-contained bundle by URL, pass `resolveClientModule` to this client host, or run under a host that resolves bare specifiers (e.g. Vite: `/@id/{specifier}`).";
44
+ return `. The host resolved the bare specifier "${importFrom}" to this URL but could not serve the module. Check the package is installed and resolvable from the host project's root (and built, if its exports point at build output).`;
45
45
  }
46
46
  //#endregion
47
47
  //#region src/remote-url.ts
@@ -77,42 +77,37 @@ function buildRemoteConnectionUrl(baseUrl, payload, transport = "fragment") {
77
77
  const beforeHash = hashIdx === -1 ? baseUrl : baseUrl.slice(0, hashIdx);
78
78
  return `${beforeHash}${beforeHash.includes("?") ? "&" : "?"}${param}${hash}`;
79
79
  }
80
+ function stripParamList(value) {
81
+ const parts = value.split("&");
82
+ const filtered = parts.filter((part) => part.split("=")[0] !== REMOTE_CONNECTION_KEY);
83
+ return [filtered.join("&"), filtered.length !== parts.length];
84
+ }
85
+ /** Strip the descriptor from a URL fragment, preserving its route/query split. */
86
+ function stripHashParams(hash) {
87
+ const routeQueryIdx = hash.indexOf("?");
88
+ if (routeQueryIdx === -1) return stripParamList(hash);
89
+ const route = hash.slice(0, routeQueryIdx);
90
+ const [query, changed] = stripParamList(hash.slice(routeQueryIdx + 1));
91
+ if (!changed) return [hash, false];
92
+ return [query ? `${route}?${query}` : route, true];
93
+ }
94
+ /** Strip the descriptor from a URL query string (the part before any fragment). */
95
+ function stripQueryParams(beforeHash) {
96
+ const queryIdx = beforeHash.indexOf("?");
97
+ if (queryIdx === -1) return [beforeHash, false];
98
+ const path = beforeHash.slice(0, queryIdx);
99
+ const [query, changed] = stripParamList(beforeHash.slice(queryIdx + 1));
100
+ if (!changed) return [beforeHash, false];
101
+ return [query ? `${path}?${query}` : path, true];
102
+ }
80
103
  /** Remove the remote connection descriptor from a URL before displaying or copying it. */
81
104
  function stripRemoteConnectionFromUrl(input) {
82
- function stripParamList(value) {
83
- const parts = value.split("&");
84
- const filtered = parts.filter((part) => part.split("=")[0] !== REMOTE_CONNECTION_KEY);
85
- return [filtered.join("&"), filtered.length !== parts.length];
86
- }
87
105
  const hashIdx = input.indexOf("#");
88
- let beforeHash = hashIdx === -1 ? input : input.slice(0, hashIdx);
89
- let hash = hashIdx === -1 ? void 0 : input.slice(hashIdx + 1);
90
- let changed = false;
91
- if (hash !== void 0) {
92
- const routeQueryIdx = hash.indexOf("?");
93
- if (routeQueryIdx === -1) {
94
- const [nextHash, didChange] = stripParamList(hash);
95
- hash = nextHash;
96
- changed ||= didChange;
97
- } else {
98
- const route = hash.slice(0, routeQueryIdx);
99
- const [query, didChange] = stripParamList(hash.slice(routeQueryIdx + 1));
100
- if (didChange) {
101
- hash = query ? `${route}?${query}` : route;
102
- changed = true;
103
- }
104
- }
105
- }
106
- const queryIdx = beforeHash.indexOf("?");
107
- if (queryIdx !== -1) {
108
- const path = beforeHash.slice(0, queryIdx);
109
- const [query, didChange] = stripParamList(beforeHash.slice(queryIdx + 1));
110
- if (didChange) {
111
- beforeHash = query ? `${path}?${query}` : path;
112
- changed = true;
113
- }
114
- }
115
- if (!changed) return input;
106
+ const rawHash = hashIdx === -1 ? void 0 : input.slice(hashIdx + 1);
107
+ const rawBeforeHash = hashIdx === -1 ? input : input.slice(0, hashIdx);
108
+ const [hash, hashChanged] = rawHash === void 0 ? [void 0, false] : stripHashParams(rawHash);
109
+ const [beforeHash, queryChanged] = stripQueryParams(rawBeforeHash);
110
+ if (!hashChanged && !queryChanged) return input;
116
111
  return hash ? `${beforeHash}#${hash}` : beforeHash;
117
112
  }
118
113
  //#endregion
@@ -17,7 +17,7 @@ interface DevframeDocksHost {
17
17
  * carrying `params` for the target dock to interpret (e.g. a terminals
18
18
  * session id).
19
19
  *
20
- * Any connected client may drive this via the `hub:docks:activate` RPC — a
20
+ * Any connected client may drive this via the `hub:docks:activate` RPC, so a
21
21
  * mounted devframe running in its own iframe can steer the host shell's dock
22
22
  * selection, which is otherwise client-local. The request is delivered live
23
23
  * to connected clients (broadcast) and mirrored into the
@@ -34,7 +34,7 @@ interface DevframeDockPanelState {
34
34
  }
35
35
  /**
36
36
  * A request to switch the active dock. `params` is an opaque, serializable
37
- * bag the target dock interprets — the terminals dock reads `params.sessionId`
37
+ * bag the target dock interprets; the terminals dock reads `params.sessionId`
38
38
  * to focus a specific session.
39
39
  */
40
40
  interface DevframeDockActivation {
@@ -42,7 +42,7 @@ interface DevframeDockActivation {
42
42
  params?: Record<string, unknown>;
43
43
  }
44
44
  /**
45
- * Shape of the `devframe:docks:active` shared-state slot — the most recent
45
+ * Shape of the `devframe:docks:active` shared-state slot: the most recent
46
46
  * {@link DevframeDockActivation}, or `null` before any activation. Mirrored
47
47
  * so a dock that mounts in response to an activation can still converge on the
48
48
  * request instead of missing the live broadcast.
@@ -50,12 +50,19 @@ interface DevframeDockActivation {
50
50
  interface DevframeDocksActiveState {
51
51
  activation: DevframeDockActivation | null;
52
52
  }
53
+ /**
54
+ * Known categories the hub orders by default (see
55
+ * {@link import('../constants').DEFAULT_CATEGORIES_ORDER}). Kits may pass their
56
+ * own category ids; `(string & {})` keeps autocomplete on the known set while
57
+ * allowing arbitrary string values. `~builtin` is reserved for the viewer's
58
+ * own built-in views (see {@link DevframeViewBuiltin}) and always sorts last.
59
+ */
53
60
  type DevframeDockEntryCategory = 'framework' | 'app' | 'ui' | 'data' | 'web' | 'performance' | 'advanced' | 'docs' | 'default' | '~builtin' | (string & {});
54
61
  type DevframeDockEntryIcon = string | {
55
62
  light: string;
56
63
  dark: string;
57
64
  };
58
- /** Color for a dock-bar entry's {@link DevframeDockEntryBase.badge} — mirrors json-render's `TabDescriptor.badgeVariant`. */
65
+ /** Color for a dock-bar entry's {@link DevframeDockEntryBase.badge}, mirroring json-render's `TabDescriptor.badgeVariant`. */
59
66
  type DevframeDockBadgeVariant = 'default' | 'info' | 'success' | 'warning' | 'danger';
60
67
  interface DevframeDockEntryBase {
61
68
  id: string;
@@ -68,17 +75,17 @@ interface DevframeDockEntryBase {
68
75
  */
69
76
  defaultOrder?: number;
70
77
  /**
71
- * The category of the entry — a field with a dual role that depends on
78
+ * The category of the entry, a field with a dual role that depends on
72
79
  * whether {@link groupId} resolves to a registered {@link DevframeViewGroup}:
73
80
  *
74
- * - **Ungrouped (or orphan) entry** — `category` is the entry's OUTER bucket
81
+ * - **Ungrouped (or orphan) entry**: `category` is the entry's OUTER bucket
75
82
  * on the dock bar, ordered by {@link import('../constants').DEFAULT_CATEGORIES_ORDER}.
76
- * - **Grouped entry** (a `groupId` that resolves to a registered group) —
83
+ * - **Grouped entry** (a `groupId` that resolves to a registered group):
77
84
  * the OUTER bucket is instead the group's own `category`, and this field is
78
85
  * reinterpreted as the entry's IN-GROUP sub-category, used to sub-divide and
79
86
  * sort members inside the group's popover / sub-navigation.
80
87
  *
81
- * Falls back to `'default'` when omitted — both as an outer bucket and, for a
88
+ * Falls back to `'default'` when omitted, both as an outer bucket and, for a
82
89
  * grouped member, as its in-group sub-bucket.
83
90
  *
84
91
  * @default 'default'
@@ -98,13 +105,13 @@ interface DevframeDockEntryBase {
98
105
  /**
99
106
  * Render-only conditional visibility expression, same syntax as {@link when}.
100
107
  * When it evaluates to `false`, a viewer omits the entry from the rendered
101
- * dock bar / list, but the entry stays registered and fully reachable —
108
+ * dock bar / list, but the entry stays registered and fully reachable:
102
109
  * `docks.activate()`/`switchEntry()` by id, RPC lookups, and anything else
103
110
  * that walks the raw entry list (e.g. the {@link DevframeViewIframe.subTabs}
104
111
  * frame-nav adapter) keep working exactly as if it were visible.
105
112
  *
106
113
  * Use this instead of {@link when} when an entry must remain part of the
107
- * model without a dock-bar button of its own — the canonical case is a
114
+ * model without a dock-bar button of its own; the canonical case is a
108
115
  * shared-frame {@link DevframeViewIframe.subTabs anchor}: set
109
116
  * `visibility: 'false'` on the anchor so only its synthesized member tabs
110
117
  * render, while the anchor itself keeps driving the postMessage nav loop.
@@ -131,7 +138,7 @@ interface DevframeDockEntryBase {
131
138
  * under the matching group's button instead of showing it directly on the
132
139
  * dock bar.
133
140
  *
134
- * This is a flat pointer — membership, not containment. The entry stays an
141
+ * This is a flat pointer: membership, not containment. The entry stays an
135
142
  * independently-registered, top-level entry; only its rendering is grouped
136
143
  * downstream.
137
144
  *
@@ -148,7 +155,7 @@ interface DevframeDockEntryBase {
148
155
  }
149
156
  interface ClientScriptEntry {
150
157
  /**
151
- * What to import — either a **URL the host serves** (a self-contained ES
158
+ * What to import: either a **URL the host serves** (a self-contained ES
152
159
  * module, e.g. `/@fs/<abs path>` under Vite or a statically-mounted bundle
153
160
  * path), or a **bare npm specifier** (e.g.
154
161
  * `'vite-plugin-vue-tracer/client/vite-devtools'`), a host-runtime
@@ -171,7 +178,7 @@ declare module 'devframe/types' {
171
178
  dock: {
172
179
  /**
173
180
  * URL template resolving a **bare-specifier** client script to an
174
- * importable URL on this host — `{specifier}` replaced with the bare
181
+ * importable URL on this host, with `{specifier}` replaced with the bare
175
182
  * specifier (a token-less template is used as a prefix). Declaring it
176
183
  * advertises the host runtime can serve npm modules to the browser:
177
184
  * Vite hosts declare `'/@id/{specifier}'`, routing the import (and its
@@ -185,6 +192,15 @@ declare module 'devframe/types' {
185
192
  interface DevframeViewIframe extends DevframeDockEntryBase {
186
193
  type: 'iframe';
187
194
  url: string;
195
+ /** Request the address bar for this iframe dock. Pass an object to configure its controls. The user's global always-show setting forces it on for every iframe. */
196
+ addressBar?: boolean | {
197
+ /** Override Back visibility. Cross-origin history access may still be blocked by the browser. */
198
+ back?: boolean;
199
+ /** Override Reload visibility. */
200
+ reload?: boolean;
201
+ /** Override Open externally visibility. */
202
+ openExternal?: boolean;
203
+ };
188
204
  /**
189
205
  * The id of the iframe, if multiple tabs is assigned with the same id, the iframe will be shared.
190
206
  *
@@ -228,10 +244,10 @@ interface DevframeViewIframe extends DevframeDockEntryBase {
228
244
  * Enable remote-UI mode: the hub injects a connection descriptor
229
245
  * (WS URL + pre-approved auth token) into the iframe URL so a hosted
230
246
  * page can connect back via `connectRemoteDevframe()` from
231
- * `@devframes/hub/client` — without needing to ship a dist with the
247
+ * `@devframes/hub/client`, without needing to ship a dist with the
232
248
  * plugin.
233
249
  *
234
- * Requires dev mode (no effect in build mode — no WS server exists).
250
+ * Requires dev mode (no effect in build mode, where no WS server exists).
235
251
  * When enabled, the dock is automatically hidden in build mode unless
236
252
  * the author provides an explicit `when` clause.
237
253
  */
@@ -239,16 +255,21 @@ interface DevframeViewIframe extends DevframeDockEntryBase {
239
255
  }
240
256
  /**
241
257
  * A structured, soft-navigation target within a shared frame. `path` is opaque
242
- * to the hub — the embedded app maps it onto its own router.
258
+ * to the hub, which the embedded app maps onto its own router.
243
259
  *
244
260
  * Kept to `path` + `query` so the shape survives shared-state's `Immutable`
245
261
  * projection cleanly (a `DevframeViewIframe` must still narrow back from its
246
262
  * immutable form). An `unknown`/recursive history-`state` field breaks that
247
263
  * round-trip, so richer per-navigation state is intentionally out of scope for
248
- * now — carry it in `query` or the app's own store.
264
+ * now; carry it in `query` or the app's own store.
249
265
  */
250
266
  interface NavTarget {
251
267
  path: string;
268
+ /**
269
+ * `readonly` arrays keep this shape stable under the shared-state `Immutable`
270
+ * projection, so a `Immutable<DevframeViewIframe>` still narrows back to
271
+ * `DevframeViewIframe` (a mutable `string[]` would not).
272
+ */
252
273
  query?: Record<string, string | readonly string[]>;
253
274
  }
254
275
  /**
@@ -271,7 +292,7 @@ interface RemoteDockOptions {
271
292
  * How to pass the connection descriptor to the hosted page.
272
293
  *
273
294
  * - `'fragment'` (default): appended as a URL fragment.
274
- * Not sent in HTTP requests or Referer headers — safest for auth tokens.
295
+ * Not sent in HTTP requests or Referer headers, so safest for auth tokens.
275
296
  * - `'query'`: appended as a URL query parameter. Use when your hosting
276
297
  * platform rewrites fragments or your SPA router repurposes the fragment
277
298
  * for navigation. The token will appear in server access logs and
@@ -310,7 +331,7 @@ interface DevframeViewLauncher extends DevframeDockEntryBase {
310
331
  /**
311
332
  * Bound command id: the launch button, command palette entry, and any
312
333
  * keybinding all resolve to this one handler. A viewer running out of
313
- * process dispatches it over the `hub:commands:execute` RPC — the
334
+ * process dispatches it over the `hub:commands:execute` RPC, the
314
335
  * serializable path {@link onLaunch} can't cross, since a function is
315
336
  * dropped when the entry is projected into the `devframe:docks` shared
316
337
  * state. Register the command (with its handler) via `ctx.commands`.
@@ -348,7 +369,7 @@ interface DevframeViewCustomRender extends DevframeDockEntryBase {
348
369
  renderer: ClientScriptEntry;
349
370
  }
350
371
  /**
351
- * A view rendered natively by the viewer rather than by a plugin — the
372
+ * A view rendered natively by the viewer rather than by a plugin: the
352
373
  * settings panel, the terminals feed, the messages feed, etc. A high-level
353
374
  * integration registers the built-in views it wants; the viewer recognizes the
354
375
  * reserved `id` and renders its own UI for it.
@@ -367,12 +388,12 @@ interface DevframeViewBuiltin extends DevframeDockEntryBase {
367
388
  *
368
389
  * A group carries its own `title`/`icon`/`category`/`defaultOrder`/`when`
369
390
  * (inherited from {@link DevframeDockEntryBase}) and has no view payload of its
370
- * own — hosts render its members in a popover / sub-navigation. It flows
391
+ * own; hosts render its members in a popover / sub-navigation. It flows
371
392
  * through the same `register`/`update`/`values` machinery as every other entry,
372
393
  * keyed by `id`.
373
394
  *
374
395
  * The group's `category` is the OUTER bucket for the group button itself AND
375
- * for every one of its members — a member's own `category` no longer decides
396
+ * for every one of its members; a member's own `category` no longer decides
376
397
  * its outer bucket, but is reinterpreted as an in-group sub-category that
377
398
  * sub-divides and sorts members inside this group. A group with no `category`
378
399
  * buckets itself and its members under `'default'`.
@@ -388,7 +409,7 @@ interface DevframeViewGroup extends DevframeDockEntryBase {
388
409
  */
389
410
  defaultChildId?: string;
390
411
  /**
391
- * Per-group override of the in-group sub-category ordering — a map of
412
+ * Per-group override of the in-group sub-category ordering: a map of
392
413
  * sub-category id → ordering weight (lower sorts earlier), mirroring the
393
414
  * shape of {@link import('../constants').DEFAULT_CATEGORIES_ORDER}.
394
415
  *
@@ -420,7 +441,7 @@ interface DevframeViewGroup extends DevframeDockEntryBase {
420
441
  /**
421
442
  * The **open** registry of dock entry variants, keyed by their `type`
422
443
  * discriminator. The hub ships the framework-neutral built-ins; opt-in
423
- * integrations contribute their own variants through declaration merging —
444
+ * integrations contribute their own variants through declaration merging,
424
445
  * e.g. `@devframes/json-render/hub` adds a `'json-render'` entry. The hub
425
446
  * itself stays agnostic: it hard-codes no integration-specific variant.
426
447
  *
@@ -464,16 +485,16 @@ interface DevframeCommandBase {
464
485
  description?: string;
465
486
  /**
466
487
  * Icon for the command. Either an Iconify icon string (e.g. "ph:pencil-duotone")
467
- * or a theme-specific pair `{ light, dark }` — the same shape as dock icons.
488
+ * or a theme-specific pair `{ light, dark }`, the same shape as dock icons.
468
489
  */
469
490
  icon?: DevframeDockEntryIcon;
470
491
  category?: string;
471
492
  /**
472
493
  * Whether to show in command palette. Default: true
473
494
  *
474
- * - `true` — show the command and flatten its children into search results
475
- * - `false` — hide the command entirely from the palette
476
- * - `'without-children'` — show the command but don't flatten children into top-level search (children are still accessible via drill-down)
495
+ * - `true`: show the command and flatten its children into search results
496
+ * - `false`: hide the command entirely from the palette
497
+ * - `'without-children'`: show the command but don't flatten children into top-level search (children are still accessible via drill-down)
477
498
  */
478
499
  showInPalette?: boolean | 'without-children';
479
500
  /**
@@ -488,13 +509,13 @@ interface DevframeCommandBase {
488
509
  keybindings?: DevframeCommandKeybinding[];
489
510
  }
490
511
  /**
491
- * Opt-in agent exposure for a server command — mirrors the `agent` field on
512
+ * Opt-in agent exposure for a server command, mirroring the `agent` field on
492
513
  * `defineRpcFunction`. A command carrying this field (and a `handler`) is
493
514
  * projected into `ctx.agent` as a callable tool, reaching MCP clients through
494
515
  * the devframe MCP adapter.
495
516
  *
496
517
  * `when` clauses are evaluated client-side only and are **not** enforced for
497
- * agent calls — opt in a `when`-gated command only if running it outside its
518
+ * agent calls; opt in a `when`-gated command only if running it outside its
498
519
  * UI context is safe.
499
520
  */
500
521
  interface DevframeCommandAgentOptions {
@@ -506,7 +527,7 @@ interface DevframeCommandAgentOptions {
506
527
  /** Display title (falls back to the command's `title`). */
507
528
  title?: string;
508
529
  /**
509
- * Safety classification — drives MCP hint annotations.
530
+ * Safety classification that drives MCP hint annotations.
510
531
  * @default 'action'
511
532
  */
512
533
  safety?: 'read' | 'action' | 'destructive';
@@ -514,7 +535,7 @@ interface DevframeCommandAgentOptions {
514
535
  tags?: readonly string[];
515
536
  /**
516
537
  * Positional [Standard Schema](https://standardschema.dev/) validators for
517
- * the handler's arguments — the same shape RPC definitions carry (valibot,
538
+ * the handler's arguments, the same shape RPC definitions carry (valibot,
518
539
  * zod, arktype, devframe's built-in `s` builder, …). Each is advertised
519
540
  * under `arg0` / `arg1` / … on the tool's JSON-Schema input. Omitted: the
520
541
  * tool takes no arguments.
@@ -522,7 +543,7 @@ interface DevframeCommandAgentOptions {
522
543
  args?: readonly StandardSchemaV1[];
523
544
  }
524
545
  /**
525
- * Server command input — what plugins pass to `ctx.commands.register()`.
546
+ * Server command input: what plugins pass to `ctx.commands.register()`.
526
547
  */
527
548
  interface DevframeServerCommandInput extends DevframeCommandBase {
528
549
  /**
@@ -535,20 +556,20 @@ interface DevframeServerCommandInput extends DevframeCommandBase {
535
556
  */
536
557
  agent?: DevframeCommandAgentOptions;
537
558
  /**
538
- * Static sub-commands. Two levels max (parent → children).
559
+ * Static sub-commands, nested arbitrarily deep.
539
560
  * Each child must have a globally unique `id`.
540
561
  */
541
562
  children?: DevframeServerCommandInput[];
542
563
  }
543
564
  /**
544
- * Serializable server command entry — sent over RPC (no handler).
565
+ * Serializable server command entry, sent over RPC (no handler).
545
566
  */
546
567
  interface DevframeServerCommandEntry extends DevframeCommandBase {
547
568
  source: 'server';
548
569
  children?: DevframeServerCommandEntry[];
549
570
  }
550
571
  /**
551
- * Client command — registered in the webcomponent context.
572
+ * Client command, registered in the webcomponent context.
552
573
  */
553
574
  interface DevframeClientCommand extends DevframeCommandBase {
554
575
  source: 'client';
@@ -558,7 +579,7 @@ interface DevframeClientCommand extends DevframeCommandBase {
558
579
  */
559
580
  action?: (...args: any[]) => void | DevframeClientCommand[] | Promise<void | DevframeClientCommand[]>;
560
581
  /**
561
- * Static sub-commands. Two levels max (parent → children).
582
+ * Static sub-commands, nested arbitrarily deep.
562
583
  */
563
584
  children?: DevframeClientCommand[];
564
585
  }
@@ -619,7 +640,7 @@ interface DevframeCommandShortcutOverrides {
619
640
  * ```
620
641
  *
621
642
  * Augmented fields are optional so the hub's `DEFAULT_STATE_USER_SETTINGS()`
622
- * (which knows nothing about them) stays assignable — an absent value reads
643
+ * (which knows nothing about them) stays assignable, so an absent value reads
623
644
  * as the viewer's documented default.
624
645
  */
625
646
  interface DevframeDocksUserSettings {
@@ -1,4 +1,4 @@
1
- import { A as DevframeMessagesLevelShortcuts, C as DevframeMessageEntryInput, D as DevframeMessageShortcutInput, E as DevframeMessageLevel, O as DevframeMessagesClient, S as DevframeMessageEntryFrom, T as DevframeMessageHandle, _ as DevframeMessageAction, a as DevframeChildProcessExecuteOptions, b as DevframeMessageElementPosition, c as DevframeChildProcessTerminalSession, d as DevframePtyResult, f as DevframePtyTerminalSession, g as DevframeTerminalsHost, h as DevframeTerminalStatus, j as DevframeMessagesListDelta, k as DevframeMessagesHost, l as DevframePtyExecuteOptions, m as DevframeTerminalSessionBase, n as DevframeHubContext, o as DevframeChildProcessOutput, p as DevframeTerminalSession, s as DevframeChildProcessResult, t as CreateHubContextOptions, u as DevframePtyOutput, v as DevframeMessageActivateAction, w as DevframeMessageFilePosition, x as DevframeMessageEntry, y as DevframeMessageCommandAction } from "../context-BJFGlcf8.mjs";
2
- import { A as DevframeViewIframe, C as DevframeDockUserEntry, D as DevframeViewBuiltin, E as DevframeViewAction, F as RemoteConnectionInfo, I as RemoteDockOptions, M as DevframeViewLauncherStatus, N as FrameSubTabsConfig, O as DevframeViewCustomRender, P as NavTarget, S as DevframeDockPanelState, T as DevframeDocksHost, _ as DevframeDockEntry, a as DevframeCommandEntry, b as DevframeDockEntryIcon, c as DevframeCommandShortcutOverrides, d as DevframeServerCommandEntry, f as DevframeServerCommandInput, g as DevframeDockEntriesGrouped, h as DevframeDockBadgeVariant, i as DevframeCommandBase, j as DevframeViewLauncher, k as DevframeViewGroup, l as DevframeCommandsHost, m as DevframeDockActivation, n as DevframeClientCommand, o as DevframeCommandHandle, p as ClientScriptEntry, r as DevframeCommandAgentOptions, s as DevframeCommandKeybinding, t as DevframeDocksUserSettings, u as DevframeCommandsHostEvents, v as DevframeDockEntryBase, w as DevframeDocksActiveState, x as DevframeDockEntryRegistry, y as DevframeDockEntryCategory } from "../settings-DwxCyeI6.mjs";
3
- import { S as RpcStreamingHost, _ as RpcFunctionsHost, a as DevframeHost, b as RpcStreamingChannel, c as DevframeRpcServerFunctions, d as EventEmitter, f as EventUnsubscribe, g as RpcDefinitionsToFunctions, h as RpcDefinitionsFilter, i as DevframeDiagnosticsLogger, l as DevframeRpcSharedStates, m as RpcBroadcastOptions, n as DevframeCapabilities, o as DevframeNodeRpcSession, p as EventsMap, r as DevframeDiagnosticsHost, s as DevframeRpcClientFunctions, t as ConnectionMeta, u as DevframeViewHost, v as RpcSharedStateGetOptions, x as RpcStreamingChannelOptions, y as RpcSharedStateHost } from "../index-OpSX0dK5.mjs";
1
+ import { A as DevframeMessagesLevelShortcuts, C as DevframeMessageEntryInput, D as DevframeMessageShortcutInput, E as DevframeMessageLevel, O as DevframeMessagesClient, S as DevframeMessageEntryFrom, T as DevframeMessageHandle, _ as DevframeMessageAction, a as DevframeChildProcessExecuteOptions, b as DevframeMessageElementPosition, c as DevframeChildProcessTerminalSession, d as DevframePtyResult, f as DevframePtyTerminalSession, g as DevframeTerminalsHost, h as DevframeTerminalStatus, j as DevframeMessagesListDelta, k as DevframeMessagesHost, l as DevframePtyExecuteOptions, m as DevframeTerminalSessionBase, n as DevframeHubContext, o as DevframeChildProcessOutput, p as DevframeTerminalSession, s as DevframeChildProcessResult, t as CreateHubContextOptions, u as DevframePtyOutput, v as DevframeMessageActivateAction, w as DevframeMessageFilePosition, x as DevframeMessageEntry, y as DevframeMessageCommandAction } from "../context-Di-fhMKP.mjs";
2
+ import { A as DevframeViewIframe, C as DevframeDockUserEntry, D as DevframeViewBuiltin, E as DevframeViewAction, F as RemoteConnectionInfo, I as RemoteDockOptions, M as DevframeViewLauncherStatus, N as FrameSubTabsConfig, O as DevframeViewCustomRender, P as NavTarget, S as DevframeDockPanelState, T as DevframeDocksHost, _ as DevframeDockEntry, a as DevframeCommandEntry, b as DevframeDockEntryIcon, c as DevframeCommandShortcutOverrides, d as DevframeServerCommandEntry, f as DevframeServerCommandInput, g as DevframeDockEntriesGrouped, h as DevframeDockBadgeVariant, i as DevframeCommandBase, j as DevframeViewLauncher, k as DevframeViewGroup, l as DevframeCommandsHost, m as DevframeDockActivation, n as DevframeClientCommand, o as DevframeCommandHandle, p as ClientScriptEntry, r as DevframeCommandAgentOptions, s as DevframeCommandKeybinding, t as DevframeDocksUserSettings, u as DevframeCommandsHostEvents, v as DevframeDockEntryBase, w as DevframeDocksActiveState, x as DevframeDockEntryRegistry, y as DevframeDockEntryCategory } from "../settings-CWHPzCWS.mjs";
3
+ import { S as RpcStreamingHost, _ as RpcFunctionsHost, a as DevframeHost, b as RpcStreamingChannel, c as DevframeRpcServerFunctions, d as EventEmitter, f as EventUnsubscribe, g as RpcDefinitionsToFunctions, h as RpcDefinitionsFilter, i as DevframeDiagnosticsLogger, l as DevframeRpcSharedStates, m as RpcBroadcastOptions, n as DevframeCapabilities, o as DevframeNodeRpcSession, p as EventsMap, r as DevframeDiagnosticsHost, s as DevframeRpcClientFunctions, t as ConnectionMeta, u as DevframeViewHost, v as RpcSharedStateGetOptions, x as RpcStreamingChannelOptions, y as RpcSharedStateHost } from "../index-faD2d5iX.mjs";
4
4
  export { ClientScriptEntry, type ConnectionMeta, type CreateHubContextOptions, type DevframeCapabilities, DevframeChildProcessExecuteOptions, DevframeChildProcessOutput, DevframeChildProcessResult, DevframeChildProcessTerminalSession, DevframeClientCommand, DevframeCommandAgentOptions, DevframeCommandBase, DevframeCommandEntry, DevframeCommandHandle, DevframeCommandKeybinding, DevframeCommandShortcutOverrides, DevframeCommandsHost, DevframeCommandsHostEvents, type DevframeDiagnosticsHost, type DevframeDiagnosticsLogger, DevframeDockActivation, DevframeDockBadgeVariant, DevframeDockEntriesGrouped, DevframeDockEntry, DevframeDockEntryBase, DevframeDockEntryCategory, DevframeDockEntryIcon, DevframeDockEntryRegistry, DevframeDockPanelState, DevframeDockUserEntry, DevframeDocksActiveState, DevframeDocksHost, DevframeDocksUserSettings, type DevframeHost, type DevframeHubContext, DevframeMessageAction, DevframeMessageActivateAction, DevframeMessageCommandAction, DevframeMessageElementPosition, DevframeMessageEntry, DevframeMessageEntryFrom, DevframeMessageEntryInput, DevframeMessageFilePosition, DevframeMessageHandle, DevframeMessageLevel, DevframeMessageShortcutInput, DevframeMessagesClient, DevframeMessagesHost, DevframeMessagesLevelShortcuts, DevframeMessagesListDelta, type DevframeNodeRpcSession, DevframePtyExecuteOptions, DevframePtyOutput, DevframePtyResult, DevframePtyTerminalSession, type DevframeRpcClientFunctions, type DevframeRpcServerFunctions, type DevframeRpcSharedStates, DevframeServerCommandEntry, DevframeServerCommandInput, DevframeTerminalSession, DevframeTerminalSessionBase, DevframeTerminalStatus, DevframeTerminalsHost, DevframeViewAction, DevframeViewBuiltin, DevframeViewCustomRender, DevframeViewGroup, type DevframeViewHost, DevframeViewIframe, DevframeViewLauncher, DevframeViewLauncherStatus, type EventEmitter, type EventUnsubscribe, type EventsMap, FrameSubTabsConfig, NavTarget, RemoteConnectionInfo, RemoteDockOptions, type RpcBroadcastOptions, type RpcDefinitionsFilter, type RpcDefinitionsToFunctions, type RpcFunctionsHost, type RpcSharedStateGetOptions, type RpcSharedStateHost, type RpcStreamingChannel, type RpcStreamingChannelOptions, type RpcStreamingHost };