@devframes/hub 0.9.7 → 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-DrF61GFx.mjs";
2
- import { a as resolveClientModuleSpecifier } from "../remote-url-Bgc7gtsP.mjs";
3
- import { m as prepareDevframe, t as createHubContext, y as diagnostics } from "../context-C3hUUCC1.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
@@ -27,9 +27,14 @@ interface DevframeDocksHost {
27
27
  */
28
28
  activate: (dockId: string, params?: Record<string, unknown>) => void;
29
29
  }
30
+ /** Current state of one dock panel in a host page. */
31
+ interface DevframeDockPanelState {
32
+ state: 'open' | 'closed' | 'hidden';
33
+ selectedDockId?: string;
34
+ }
30
35
  /**
31
36
  * A request to switch the active dock. `params` is an opaque, serializable
32
- * bag the target dock interprets — the terminals dock reads `params.sessionId`
37
+ * bag the target dock interprets; the terminals dock reads `params.sessionId`
33
38
  * to focus a specific session.
34
39
  */
35
40
  interface DevframeDockActivation {
@@ -37,7 +42,7 @@ interface DevframeDockActivation {
37
42
  params?: Record<string, unknown>;
38
43
  }
39
44
  /**
40
- * 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
41
46
  * {@link DevframeDockActivation}, or `null` before any activation. Mirrored
42
47
  * so a dock that mounts in response to an activation can still converge on the
43
48
  * request instead of missing the live broadcast.
@@ -45,12 +50,19 @@ interface DevframeDockActivation {
45
50
  interface DevframeDocksActiveState {
46
51
  activation: DevframeDockActivation | null;
47
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
+ */
48
60
  type DevframeDockEntryCategory = 'framework' | 'app' | 'ui' | 'data' | 'web' | 'performance' | 'advanced' | 'docs' | 'default' | '~builtin' | (string & {});
49
61
  type DevframeDockEntryIcon = string | {
50
62
  light: string;
51
63
  dark: string;
52
64
  };
53
- /** 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`. */
54
66
  type DevframeDockBadgeVariant = 'default' | 'info' | 'success' | 'warning' | 'danger';
55
67
  interface DevframeDockEntryBase {
56
68
  id: string;
@@ -63,17 +75,17 @@ interface DevframeDockEntryBase {
63
75
  */
64
76
  defaultOrder?: number;
65
77
  /**
66
- * 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
67
79
  * whether {@link groupId} resolves to a registered {@link DevframeViewGroup}:
68
80
  *
69
- * - **Ungrouped (or orphan) entry** — `category` is the entry's OUTER bucket
81
+ * - **Ungrouped (or orphan) entry**: `category` is the entry's OUTER bucket
70
82
  * on the dock bar, ordered by {@link import('../constants').DEFAULT_CATEGORIES_ORDER}.
71
- * - **Grouped entry** (a `groupId` that resolves to a registered group) —
83
+ * - **Grouped entry** (a `groupId` that resolves to a registered group):
72
84
  * the OUTER bucket is instead the group's own `category`, and this field is
73
85
  * reinterpreted as the entry's IN-GROUP sub-category, used to sub-divide and
74
86
  * sort members inside the group's popover / sub-navigation.
75
87
  *
76
- * 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
77
89
  * grouped member, as its in-group sub-bucket.
78
90
  *
79
91
  * @default 'default'
@@ -93,13 +105,13 @@ interface DevframeDockEntryBase {
93
105
  /**
94
106
  * Render-only conditional visibility expression, same syntax as {@link when}.
95
107
  * When it evaluates to `false`, a viewer omits the entry from the rendered
96
- * dock bar / list, but the entry stays registered and fully reachable —
108
+ * dock bar / list, but the entry stays registered and fully reachable:
97
109
  * `docks.activate()`/`switchEntry()` by id, RPC lookups, and anything else
98
110
  * that walks the raw entry list (e.g. the {@link DevframeViewIframe.subTabs}
99
111
  * frame-nav adapter) keep working exactly as if it were visible.
100
112
  *
101
113
  * Use this instead of {@link when} when an entry must remain part of the
102
- * 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
103
115
  * shared-frame {@link DevframeViewIframe.subTabs anchor}: set
104
116
  * `visibility: 'false'` on the anchor so only its synthesized member tabs
105
117
  * render, while the anchor itself keeps driving the postMessage nav loop.
@@ -126,7 +138,7 @@ interface DevframeDockEntryBase {
126
138
  * under the matching group's button instead of showing it directly on the
127
139
  * dock bar.
128
140
  *
129
- * 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
130
142
  * independently-registered, top-level entry; only its rendering is grouped
131
143
  * downstream.
132
144
  *
@@ -143,7 +155,7 @@ interface DevframeDockEntryBase {
143
155
  }
144
156
  interface ClientScriptEntry {
145
157
  /**
146
- * 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
147
159
  * module, e.g. `/@fs/<abs path>` under Vite or a statically-mounted bundle
148
160
  * path), or a **bare npm specifier** (e.g.
149
161
  * `'vite-plugin-vue-tracer/client/vite-devtools'`), a host-runtime
@@ -166,7 +178,7 @@ declare module 'devframe/types' {
166
178
  dock: {
167
179
  /**
168
180
  * URL template resolving a **bare-specifier** client script to an
169
- * importable URL on this host — `{specifier}` replaced with the bare
181
+ * importable URL on this host, with `{specifier}` replaced with the bare
170
182
  * specifier (a token-less template is used as a prefix). Declaring it
171
183
  * advertises the host runtime can serve npm modules to the browser:
172
184
  * Vite hosts declare `'/@id/{specifier}'`, routing the import (and its
@@ -180,6 +192,15 @@ declare module 'devframe/types' {
180
192
  interface DevframeViewIframe extends DevframeDockEntryBase {
181
193
  type: 'iframe';
182
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
+ };
183
204
  /**
184
205
  * The id of the iframe, if multiple tabs is assigned with the same id, the iframe will be shared.
185
206
  *
@@ -223,10 +244,10 @@ interface DevframeViewIframe extends DevframeDockEntryBase {
223
244
  * Enable remote-UI mode: the hub injects a connection descriptor
224
245
  * (WS URL + pre-approved auth token) into the iframe URL so a hosted
225
246
  * page can connect back via `connectRemoteDevframe()` from
226
- * `@devframes/hub/client` — without needing to ship a dist with the
247
+ * `@devframes/hub/client`, without needing to ship a dist with the
227
248
  * plugin.
228
249
  *
229
- * 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).
230
251
  * When enabled, the dock is automatically hidden in build mode unless
231
252
  * the author provides an explicit `when` clause.
232
253
  */
@@ -234,16 +255,21 @@ interface DevframeViewIframe extends DevframeDockEntryBase {
234
255
  }
235
256
  /**
236
257
  * A structured, soft-navigation target within a shared frame. `path` is opaque
237
- * 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.
238
259
  *
239
260
  * Kept to `path` + `query` so the shape survives shared-state's `Immutable`
240
261
  * projection cleanly (a `DevframeViewIframe` must still narrow back from its
241
262
  * immutable form). An `unknown`/recursive history-`state` field breaks that
242
263
  * round-trip, so richer per-navigation state is intentionally out of scope for
243
- * now — carry it in `query` or the app's own store.
264
+ * now; carry it in `query` or the app's own store.
244
265
  */
245
266
  interface NavTarget {
246
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
+ */
247
273
  query?: Record<string, string | readonly string[]>;
248
274
  }
249
275
  /**
@@ -266,7 +292,7 @@ interface RemoteDockOptions {
266
292
  * How to pass the connection descriptor to the hosted page.
267
293
  *
268
294
  * - `'fragment'` (default): appended as a URL fragment.
269
- * 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.
270
296
  * - `'query'`: appended as a URL query parameter. Use when your hosting
271
297
  * platform rewrites fragments or your SPA router repurposes the fragment
272
298
  * for navigation. The token will appear in server access logs and
@@ -305,7 +331,7 @@ interface DevframeViewLauncher extends DevframeDockEntryBase {
305
331
  /**
306
332
  * Bound command id: the launch button, command palette entry, and any
307
333
  * keybinding all resolve to this one handler. A viewer running out of
308
- * process dispatches it over the `hub:commands:execute` RPC — the
334
+ * process dispatches it over the `hub:commands:execute` RPC, the
309
335
  * serializable path {@link onLaunch} can't cross, since a function is
310
336
  * dropped when the entry is projected into the `devframe:docks` shared
311
337
  * state. Register the command (with its handler) via `ctx.commands`.
@@ -343,7 +369,7 @@ interface DevframeViewCustomRender extends DevframeDockEntryBase {
343
369
  renderer: ClientScriptEntry;
344
370
  }
345
371
  /**
346
- * 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
347
373
  * settings panel, the terminals feed, the messages feed, etc. A high-level
348
374
  * integration registers the built-in views it wants; the viewer recognizes the
349
375
  * reserved `id` and renders its own UI for it.
@@ -362,12 +388,12 @@ interface DevframeViewBuiltin extends DevframeDockEntryBase {
362
388
  *
363
389
  * A group carries its own `title`/`icon`/`category`/`defaultOrder`/`when`
364
390
  * (inherited from {@link DevframeDockEntryBase}) and has no view payload of its
365
- * 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
366
392
  * through the same `register`/`update`/`values` machinery as every other entry,
367
393
  * keyed by `id`.
368
394
  *
369
395
  * The group's `category` is the OUTER bucket for the group button itself AND
370
- * 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
371
397
  * its outer bucket, but is reinterpreted as an in-group sub-category that
372
398
  * sub-divides and sorts members inside this group. A group with no `category`
373
399
  * buckets itself and its members under `'default'`.
@@ -383,7 +409,7 @@ interface DevframeViewGroup extends DevframeDockEntryBase {
383
409
  */
384
410
  defaultChildId?: string;
385
411
  /**
386
- * 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
387
413
  * sub-category id → ordering weight (lower sorts earlier), mirroring the
388
414
  * shape of {@link import('../constants').DEFAULT_CATEGORIES_ORDER}.
389
415
  *
@@ -415,7 +441,7 @@ interface DevframeViewGroup extends DevframeDockEntryBase {
415
441
  /**
416
442
  * The **open** registry of dock entry variants, keyed by their `type`
417
443
  * discriminator. The hub ships the framework-neutral built-ins; opt-in
418
- * integrations contribute their own variants through declaration merging —
444
+ * integrations contribute their own variants through declaration merging,
419
445
  * e.g. `@devframes/json-render/hub` adds a `'json-render'` entry. The hub
420
446
  * itself stays agnostic: it hard-codes no integration-specific variant.
421
447
  *
@@ -459,16 +485,16 @@ interface DevframeCommandBase {
459
485
  description?: string;
460
486
  /**
461
487
  * Icon for the command. Either an Iconify icon string (e.g. "ph:pencil-duotone")
462
- * 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.
463
489
  */
464
490
  icon?: DevframeDockEntryIcon;
465
491
  category?: string;
466
492
  /**
467
493
  * Whether to show in command palette. Default: true
468
494
  *
469
- * - `true` — show the command and flatten its children into search results
470
- * - `false` — hide the command entirely from the palette
471
- * - `'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)
472
498
  */
473
499
  showInPalette?: boolean | 'without-children';
474
500
  /**
@@ -483,13 +509,13 @@ interface DevframeCommandBase {
483
509
  keybindings?: DevframeCommandKeybinding[];
484
510
  }
485
511
  /**
486
- * 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
487
513
  * `defineRpcFunction`. A command carrying this field (and a `handler`) is
488
514
  * projected into `ctx.agent` as a callable tool, reaching MCP clients through
489
515
  * the devframe MCP adapter.
490
516
  *
491
517
  * `when` clauses are evaluated client-side only and are **not** enforced for
492
- * 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
493
519
  * UI context is safe.
494
520
  */
495
521
  interface DevframeCommandAgentOptions {
@@ -501,7 +527,7 @@ interface DevframeCommandAgentOptions {
501
527
  /** Display title (falls back to the command's `title`). */
502
528
  title?: string;
503
529
  /**
504
- * Safety classification — drives MCP hint annotations.
530
+ * Safety classification that drives MCP hint annotations.
505
531
  * @default 'action'
506
532
  */
507
533
  safety?: 'read' | 'action' | 'destructive';
@@ -509,7 +535,7 @@ interface DevframeCommandAgentOptions {
509
535
  tags?: readonly string[];
510
536
  /**
511
537
  * Positional [Standard Schema](https://standardschema.dev/) validators for
512
- * the handler's arguments — the same shape RPC definitions carry (valibot,
538
+ * the handler's arguments, the same shape RPC definitions carry (valibot,
513
539
  * zod, arktype, devframe's built-in `s` builder, …). Each is advertised
514
540
  * under `arg0` / `arg1` / … on the tool's JSON-Schema input. Omitted: the
515
541
  * tool takes no arguments.
@@ -517,7 +543,7 @@ interface DevframeCommandAgentOptions {
517
543
  args?: readonly StandardSchemaV1[];
518
544
  }
519
545
  /**
520
- * Server command input — what plugins pass to `ctx.commands.register()`.
546
+ * Server command input: what plugins pass to `ctx.commands.register()`.
521
547
  */
522
548
  interface DevframeServerCommandInput extends DevframeCommandBase {
523
549
  /**
@@ -530,20 +556,20 @@ interface DevframeServerCommandInput extends DevframeCommandBase {
530
556
  */
531
557
  agent?: DevframeCommandAgentOptions;
532
558
  /**
533
- * Static sub-commands. Two levels max (parent → children).
559
+ * Static sub-commands, nested arbitrarily deep.
534
560
  * Each child must have a globally unique `id`.
535
561
  */
536
562
  children?: DevframeServerCommandInput[];
537
563
  }
538
564
  /**
539
- * Serializable server command entry — sent over RPC (no handler).
565
+ * Serializable server command entry, sent over RPC (no handler).
540
566
  */
541
567
  interface DevframeServerCommandEntry extends DevframeCommandBase {
542
568
  source: 'server';
543
569
  children?: DevframeServerCommandEntry[];
544
570
  }
545
571
  /**
546
- * Client command — registered in the webcomponent context.
572
+ * Client command, registered in the webcomponent context.
547
573
  */
548
574
  interface DevframeClientCommand extends DevframeCommandBase {
549
575
  source: 'client';
@@ -553,7 +579,7 @@ interface DevframeClientCommand extends DevframeCommandBase {
553
579
  */
554
580
  action?: (...args: any[]) => void | DevframeClientCommand[] | Promise<void | DevframeClientCommand[]>;
555
581
  /**
556
- * Static sub-commands. Two levels max (parent → children).
582
+ * Static sub-commands, nested arbitrarily deep.
557
583
  */
558
584
  children?: DevframeClientCommand[];
559
585
  }
@@ -614,7 +640,7 @@ interface DevframeCommandShortcutOverrides {
614
640
  * ```
615
641
  *
616
642
  * Augmented fields are optional so the hub's `DEFAULT_STATE_USER_SETTINGS()`
617
- * (which knows nothing about them) stays assignable — an absent value reads
643
+ * (which knows nothing about them) stays assignable, so an absent value reads
618
644
  * as the viewer's documented default.
619
645
  */
620
646
  interface DevframeDocksUserSettings {
@@ -630,4 +656,4 @@ interface DevframeDocksUserSettings {
630
656
  commandShortcuts: DevframeCommandShortcutOverrides;
631
657
  }
632
658
  //#endregion
633
- export { DevframeViewLauncher as A, DevframeDocksActiveState as C, DevframeViewCustomRender as D, DevframeViewBuiltin as E, RemoteDockOptions as F, FrameSubTabsConfig as M, NavTarget as N, DevframeViewGroup as O, RemoteConnectionInfo as P, DevframeDockUserEntry as S, DevframeViewAction as T, DevframeDockEntry as _, DevframeCommandEntry as a, DevframeDockEntryIcon as b, DevframeCommandShortcutOverrides as c, DevframeServerCommandEntry as d, DevframeServerCommandInput as f, DevframeDockEntriesGrouped as g, DevframeDockBadgeVariant as h, DevframeCommandBase as i, DevframeViewLauncherStatus as j, DevframeViewIframe as k, DevframeCommandsHost as l, DevframeDockActivation as m, DevframeClientCommand as n, DevframeCommandHandle as o, ClientScriptEntry as p, DevframeCommandAgentOptions as r, DevframeCommandKeybinding as s, DevframeDocksUserSettings as t, DevframeCommandsHostEvents as u, DevframeDockEntryBase as v, DevframeDocksHost as w, DevframeDockEntryRegistry as x, DevframeDockEntryCategory as y };
659
+ export { DevframeViewIframe as A, DevframeDockUserEntry as C, DevframeViewBuiltin as D, DevframeViewAction as E, RemoteConnectionInfo as F, RemoteDockOptions as I, DevframeViewLauncherStatus as M, FrameSubTabsConfig as N, DevframeViewCustomRender as O, NavTarget as P, DevframeDockPanelState as S, DevframeDocksHost as T, DevframeDockEntry as _, DevframeCommandEntry as a, DevframeDockEntryIcon as b, DevframeCommandShortcutOverrides as c, DevframeServerCommandEntry as d, DevframeServerCommandInput as f, DevframeDockEntriesGrouped as g, DevframeDockBadgeVariant as h, DevframeCommandBase as i, DevframeViewLauncher as j, DevframeViewGroup as k, DevframeCommandsHost as l, DevframeDockActivation as m, DevframeClientCommand as n, DevframeCommandHandle as o, ClientScriptEntry as p, DevframeCommandAgentOptions as r, DevframeCommandKeybinding as s, DevframeDocksUserSettings as t, DevframeCommandsHostEvents as u, DevframeDockEntryBase as v, DevframeDocksActiveState as w, DevframeDockEntryRegistry as x, DevframeDockEntryCategory as y };