@devframes/hub 0.9.8 → 0.9.10

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,5 +1,5 @@
1
- import { i as DOCK_RENDERERS_STATE_KEY, n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS, t as DEFAULT_CATEGORIES_ORDER } from "../constants-D1zicoJ4.mjs";
2
- import { a as resolveClientModuleSpecifier, n as stripRemoteConnectionFromUrl, r as clientScriptFailureHint, t as buildRemoteConnectionUrl } from "../remote-url-Bgc7gtsP.mjs";
1
+ import { i as DOCK_RENDERERS_STATE_KEY, n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS, t as DEFAULT_CATEGORIES_ORDER } from "../constants-Da23JxCZ.mjs";
2
+ import { a as resolveClientModuleSpecifier, n as stripRemoteConnectionFromUrl, r as clientScriptFailureHint, t as buildRemoteConnectionUrl } from "../remote-url-UClKLQ5_.mjs";
3
3
  import { REMOTE_CONNECTION_KEY } from "devframe/constants";
4
4
  import { connectDevframe, getDevframeRpcClient, resolveWsUrl } from "devframe/client";
5
5
  import { createEventEmitter } from "devframe/utils/events";
@@ -82,7 +82,7 @@ function watchFrameLocation(options) {
82
82
  let disposed = false;
83
83
  /**
84
84
  * The frame's window, or `null` when its location can't be read. Touching
85
- * `location.href` is the cross-origin probe — it throws for a foreign
85
+ * `location.href` is the cross-origin probe, since it throws for a foreign
86
86
  * document, where there is nothing to observe.
87
87
  */
88
88
  function readableWindow() {
@@ -153,7 +153,7 @@ function watchFrameLocation(options) {
153
153
  //#endregion
154
154
  //#region src/client/frame-nav.ts
155
155
  /**
156
- * Shared-iframe soft navigation — the hub-UI-provider-side half of a host-page↔iframe
156
+ * Shared-iframe soft navigation: the hub-UI-provider-side half of a host-page↔iframe
157
157
  * `postMessage` protocol.
158
158
  *
159
159
  * An {@link DevframeViewIframe.subTabs anchor} iframe dock owns one live iframe
@@ -169,7 +169,7 @@ function watchFrameLocation(options) {
169
169
  * highlight to match.
170
170
  *
171
171
  * The protocol is server-free (works cross-origin and in static builds) and
172
- * decoupled — the embedded app takes no hub/RPC dependency, only the shim.
172
+ * decoupled, so the embedded app takes no hub/RPC dependency, only the shim.
173
173
  */
174
174
  /** `postMessage` channel tag shared by both halves of the protocol. */
175
175
  const FRAME_NAV_CHANNEL = HUB_EVENTS.postMessage.frameNav;
@@ -341,17 +341,18 @@ function deriveFallbackUrl(base, navTarget) {
341
341
  }
342
342
  const path = navTarget.path.startsWith("#") ? navTarget.path.slice(1) : navTarget.path;
343
343
  let hash = `#${path}`;
344
- const query = navTarget.query;
345
- if (query) {
346
- const params = new URLSearchParams();
347
- for (const [key, value] of Object.entries(query)) if (typeof value === "string") params.set(key, value);
348
- else for (const v of value) params.append(key, v);
349
- const qs = params.toString();
350
- if (qs) hash += (path.includes("?") ? "&" : "?") + qs;
351
- }
344
+ const qs = navTarget.query ? serializeNavQuery(navTarget.query) : "";
345
+ if (qs) hash += (path.includes("?") ? "&" : "?") + qs;
352
346
  url.hash = hash;
353
347
  return url.toString();
354
348
  }
349
+ /** Serialize a {@link NavTarget.query} into a URL query string. */
350
+ function serializeNavQuery(query) {
351
+ const params = new URLSearchParams();
352
+ for (const [key, value] of Object.entries(query)) if (typeof value === "string") params.set(key, value);
353
+ else for (const v of value) params.append(key, v);
354
+ return params.toString();
355
+ }
355
356
  //#endregion
356
357
  //#region src/client/messages.ts
357
358
  /**
@@ -361,6 +362,7 @@ function deriveFallbackUrl(base, navTarget) {
361
362
  * dock client script reports into the very feed the server writes to.
362
363
  */
363
364
  function createMessagesClient(rpc, options = {}) {
365
+ if (rpc.connectionMeta?.backend === "static") return createLocalMessagesClient(options);
364
366
  const { call } = rpc;
365
367
  function makeHandle(entry) {
366
368
  let current = entry;
@@ -403,10 +405,62 @@ function createMessagesClient(rpc, options = {}) {
403
405
  debug: levelShortcut("debug")
404
406
  };
405
407
  }
408
+ /** In-page-only writes for a static backend: same surface, no wire. */
409
+ function createLocalMessagesClient(options) {
410
+ let nextId = 0;
411
+ function makeEntry(input) {
412
+ return {
413
+ timestamp: Date.now(),
414
+ ...options.defaults,
415
+ ...input,
416
+ id: input.id ?? `local-${++nextId}`,
417
+ from: "browser"
418
+ };
419
+ }
420
+ function makeHandle(entry) {
421
+ let current = entry;
422
+ return {
423
+ get entry() {
424
+ return current;
425
+ },
426
+ get id() {
427
+ return current.id;
428
+ },
429
+ async update(patch) {
430
+ current = {
431
+ ...current,
432
+ ...patch,
433
+ id: current.id,
434
+ from: current.from
435
+ };
436
+ return current;
437
+ },
438
+ dismiss: async () => {}
439
+ };
440
+ }
441
+ const add = async (input) => makeHandle(makeEntry(input));
442
+ function levelShortcut(level) {
443
+ return (message, extra) => add({
444
+ ...extra,
445
+ message,
446
+ level
447
+ });
448
+ }
449
+ return {
450
+ add,
451
+ remove: async () => {},
452
+ clear: async () => {},
453
+ info: levelShortcut("info"),
454
+ warn: levelShortcut("warn"),
455
+ error: levelShortcut("error"),
456
+ success: levelShortcut("success"),
457
+ debug: levelShortcut("debug")
458
+ };
459
+ }
406
460
  //#endregion
407
461
  //#region src/client/renderers.ts
408
462
  /**
409
- * Build the {@link DockRenderersContext} shared by every hub-aware client —
463
+ * Build the {@link DockRenderersContext} shared by every hub-aware client.
410
464
  * `createDevframeClientRuntime` and hub UI providers that assemble their own context
411
465
  * (`@devframes/hub-ui`) both delegate here so local-first resolution, lazy
412
466
  * manifest imports, and the typed mount result behave identically everywhere.
@@ -504,7 +558,7 @@ const DOCKS_ACTIVATE_EVENT = HUB_EVENTS.broadcast.docksActivate;
504
558
  * Boot the client runtime: connect RPC, assemble the full
505
559
  * {@link DevframeClientContext} (panel, docks, commands, when) from the hub's
506
560
  * shared state, publish it at `__DEVFRAME_HUB_CLIENT_CONTEXT__`, and load every
507
- * dock entry's client script into this page — the devframe equivalent of the
561
+ * dock entry's client script into this page, the devframe equivalent of the
508
562
  * runtime `@vitejs/devtools-kit` injects into a host app.
509
563
  *
510
564
  * A hub UI provider keeps rendering its own dock UI (reading the same shared state); this
@@ -575,7 +629,12 @@ async function createDevframeClientRuntime(options = {}) {
575
629
  type: "action",
576
630
  handler: (activation) => activateHandler(activation)
577
631
  });
578
- if (getDevframeClientContext()) console.warn("[@devframes/hub] A client context is already published on this page — replacing it. Boot createDevframeClientRuntime() once per page (e.g. HTML injection combined with a manual import boots it twice).");
632
+ if (rpc.connectionMeta?.backend === "static" && typeof BroadcastChannel !== "undefined") {
633
+ const activateChannel = new BroadcastChannel(HUB_EVENTS.broadcastChannel.docksActivate);
634
+ activateChannel.onmessage = (event) => activateHandler(event.data);
635
+ disposers.push(() => activateChannel.close());
636
+ }
637
+ if (getDevframeClientContext()) console.warn("[@devframes/hub] A client context is already published on this page; replacing it. Boot createDevframeClientRuntime() once per page (e.g. HTML injection combined with a manual import boots it twice).");
579
638
  setDevframeClientContext(context);
580
639
  const loadedScripts = /* @__PURE__ */ new Set();
581
640
  if (loadScriptsEnabled) {
@@ -678,6 +737,10 @@ async function createDevframeClientRuntime(options = {}) {
678
737
  set selectedId(id) {
679
738
  switchEntry(id);
680
739
  },
740
+ /**
741
+ * A mirror of the session field, so a persisting host reads and writes the
742
+ * selected iframe's route through the same context every hub UI provider uses.
743
+ */
681
744
  get selectedRoute() {
682
745
  return panel.session.selectedDockRoute;
683
746
  },
@@ -696,14 +759,14 @@ async function createDevframeClientRuntime(options = {}) {
696
759
  switchEntry,
697
760
  toggleEntry: (id) => selectedId === id ? switchEntry(null) : switchEntry(id),
698
761
  register(entry, force) {
699
- if (clientDocks.has(entry.id) && !force) throw new Error(`[@devframes/hub] a client dock "${entry.id}" is already registered — pass force to overwrite`);
762
+ if (clientDocks.has(entry.id) && !force) throw new Error(`[@devframes/hub] a client dock "${entry.id}" is already registered; pass force to overwrite`);
700
763
  clientDocks.set(entry.id, entry);
701
764
  refreshEntries();
702
765
  return {
703
766
  update: (patch) => {
704
767
  if (patch.id && patch.id !== entry.id) throw new Error(`[@devframes/hub] cannot change a dock id ("${entry.id}" → "${patch.id}")`);
705
768
  const existing = clientDocks.get(entry.id);
706
- if (!existing) throw new Error(`[@devframes/hub] client dock "${entry.id}" was removed — register it again to update`);
769
+ if (!existing) throw new Error(`[@devframes/hub] client dock "${entry.id}" was removed; register it again to update`);
707
770
  clientDocks.set(entry.id, {
708
771
  ...existing,
709
772
  ...patch
@@ -716,7 +779,7 @@ async function createDevframeClientRuntime(options = {}) {
716
779
  };
717
780
  },
718
781
  update(entry) {
719
- if (!clientDocks.has(entry.id)) throw new Error(`[@devframes/hub] no client dock "${entry.id}" to update — register it first`);
782
+ if (!clientDocks.has(entry.id)) throw new Error(`[@devframes/hub] no client dock "${entry.id}" to update; register it first`);
720
783
  clientDocks.set(entry.id, entry);
721
784
  refreshEntries();
722
785
  }
@@ -739,7 +802,14 @@ async function createDevframeClientRuntime(options = {}) {
739
802
  function allCommands() {
740
803
  return [...commandsState.value(), ...clientCommands.values()];
741
804
  }
742
- return {
805
+ function findCommand(commands, id) {
806
+ for (const command of commands) {
807
+ if (command.id === id) return command;
808
+ const child = findCommand(command.children ?? [], id);
809
+ if (child) return child;
810
+ }
811
+ }
812
+ const ctx = {
743
813
  get commands() {
744
814
  return allCommands();
745
815
  },
@@ -754,18 +824,24 @@ async function createDevframeClientRuntime(options = {}) {
754
824
  };
755
825
  },
756
826
  async execute(id, ...args) {
757
- const client = clientCommands.get(id);
758
- if (client?.action) return client.action(...args);
827
+ const client = findCommand(clientCommands.values(), id);
828
+ if (client?.source === "client" && client.action) return client.action(...args);
759
829
  return rpc.call("hub:commands:execute", id, ...args);
760
830
  },
761
831
  getKeybindings(id) {
762
832
  const override = settings.value().commandShortcuts?.[id];
763
833
  if (override) return override;
764
- return allCommands().find((c) => c.id === id)?.keybindings ?? [];
834
+ return findCommand(allCommands(), id)?.keybindings ?? [];
765
835
  },
766
836
  settings,
767
- paletteOpen: false
837
+ paletteOpen: false,
838
+ paletteScopeId: null,
839
+ openPalette(atCommandId) {
840
+ ctx.paletteScopeId = atCommandId ?? null;
841
+ ctx.paletteOpen = true;
842
+ }
768
843
  };
844
+ return ctx;
769
845
  }
770
846
  function createRenderersContext() {
771
847
  const mountedDisposers = /* @__PURE__ */ new Set();
@@ -902,7 +978,7 @@ function groupByCategory(entries, categoryOrder) {
902
978
  }
903
979
  return [...groups.entries()].sort(([a], [b]) => (categoryOrder[a] ?? 0) - (categoryOrder[b] ?? 0));
904
980
  }
905
- /** @deprecated Renamed — use {@link createDevframeClientRuntime}. */
981
+ /** @deprecated Renamed; use {@link createDevframeClientRuntime}. */
906
982
  const createDevframeClientHost = createDevframeClientRuntime;
907
983
  //#endregion
908
984
  //#region src/client/remote.ts
@@ -962,22 +1038,9 @@ function extractKeyFromQuery(search) {
962
1038
  * Throws if the descriptor is malformed or its schema version is unsupported.
963
1039
  */
964
1040
  function parseRemoteConnection(input) {
965
- let hash = "";
966
- let search = "";
967
- if (input === void 0) {
968
- if (typeof location === "undefined") return null;
969
- hash = location.hash;
970
- search = location.search;
971
- } else try {
972
- const parsed = new URL(input, "http://_");
973
- hash = parsed.hash;
974
- search = parsed.search;
975
- } catch {
976
- if (input.startsWith("#")) hash = input;
977
- else if (input.startsWith("?")) search = input;
978
- else return null;
979
- }
980
- const encoded = extractKeyFromFragment(hash) ?? extractKeyFromQuery(search);
1041
+ const parts = resolveUrlParts(input);
1042
+ if (!parts) return null;
1043
+ const encoded = extractKeyFromFragment(parts.hash) ?? extractKeyFromQuery(parts.search);
981
1044
  if (!encoded) return null;
982
1045
  let payload;
983
1046
  try {
@@ -985,6 +1048,37 @@ function parseRemoteConnection(input) {
985
1048
  } catch (cause) {
986
1049
  throw new Error("[@devframes/hub] Failed to decode remote connection descriptor.", { cause });
987
1050
  }
1051
+ return validateConnectionDescriptor(payload);
1052
+ }
1053
+ /** Resolve the URL fragment/query to inspect, from an explicit input or `location`. */
1054
+ function resolveUrlParts(input) {
1055
+ if (input === void 0) {
1056
+ if (typeof location === "undefined") return null;
1057
+ return {
1058
+ hash: location.hash,
1059
+ search: location.search
1060
+ };
1061
+ }
1062
+ try {
1063
+ const parsed = new URL(input, "http://_");
1064
+ return {
1065
+ hash: parsed.hash,
1066
+ search: parsed.search
1067
+ };
1068
+ } catch {
1069
+ if (input.startsWith("#")) return {
1070
+ hash: input,
1071
+ search: ""
1072
+ };
1073
+ if (input.startsWith("?")) return {
1074
+ hash: "",
1075
+ search: input
1076
+ };
1077
+ return null;
1078
+ }
1079
+ }
1080
+ /** Validate a decoded payload as a {@link RemoteConnectionInfo}, throwing on any mismatch. */
1081
+ function validateConnectionDescriptor(payload) {
988
1082
  if (!payload || typeof payload !== "object") throw new Error("[@devframes/hub] Remote connection descriptor must be an object.");
989
1083
  const info = payload;
990
1084
  if (info.v !== 1) throw new Error(`[@devframes/hub] Unsupported remote connection descriptor version: ${String(info.v)}`);
@@ -998,7 +1092,7 @@ function parseRemoteConnection(input) {
998
1092
  * the current URL and returns a connected {@link DevframeRpcClient}.
999
1093
  *
1000
1094
  * Pairs with `remote: true` on a `DevframeViewIframe` registered on the node
1001
- * side — the hub injects the descriptor into the iframe URL.
1095
+ * side, where the hub injects the descriptor into the iframe URL.
1002
1096
  *
1003
1097
  * @throws if no descriptor is present in the URL.
1004
1098
  */
@@ -1,8 +1,8 @@
1
- import { t as DevframeDocksUserSettings } from "./settings-DwxCyeI6.mjs";
1
+ import { t as DevframeDocksUserSettings } from "./settings-CWHPzCWS.mjs";
2
2
  //#region src/events.d.ts
3
3
  /**
4
4
  * Centralized registry of every event, broadcast, RPC method, shared-state
5
- * key, and channel name the hub uses — the single source of truth that keeps
5
+ * key, and channel name the hub uses: the single source of truth that keeps
6
6
  * these names out of scattered string literals.
7
7
  *
8
8
  * **Keep this in sync with [`docs/content/8.references/3.events.md`](../../../docs/content/8.references/3.events.md)**
@@ -72,10 +72,19 @@ declare const HUB_EVENTS: {
72
72
  readonly postMessage: {
73
73
  readonly frameNav: "devframe:frame-nav";
74
74
  };
75
+ /**
76
+ * Same-origin `BroadcastChannel` names, `devframe:` prefix. Used on a
77
+ * `static` backend, where no live server can relay a client's request to
78
+ * its sibling browsing contexts (e.g. a panel iframe steering the host
79
+ * page's dock selection).
80
+ */
81
+ readonly broadcastChannel: {
82
+ readonly docksActivate: "devframe:docks:activate";
83
+ };
75
84
  };
76
85
  //#endregion
77
86
  //#region src/constants.d.ts
78
- /** Default mount base for a hub instance — one namespace, one catch-all. */
87
+ /** Default mount base for a hub instance: one namespace, one catch-all. */
79
88
  declare const DEVFRAMES_HUB_BASE = "/__devframes/";
80
89
  /**
81
90
  * Normalize a hub mount base to an absolute path with leading and trailing
@@ -86,7 +95,7 @@ declare const DEVFRAMES_HUB_BASE = "/__devframes/";
86
95
  */
87
96
  declare function normalizeHubBase(base: string): string;
88
97
  /**
89
- * The default ordering weight for each known dock category — lower sorts
98
+ * The default ordering weight for each known dock category, where lower sorts
90
99
  * earlier. Downstream viewers (e.g. `@vitejs/devtools-kit`) import this as the
91
100
  * single source of truth so the hub and its viewers agree on category order.
92
101
  * `framework` sorts first; `~builtin` (the viewer's own built-in views) last.
@@ -97,7 +106,7 @@ declare function normalizeHubBase(base: string): string;
97
106
  */
98
107
  declare const DEFAULT_CATEGORIES_ORDER: Record<string, number>;
99
108
  /**
100
- * Shared-state slot carrying the hub's renderer manifest — one
109
+ * Shared-state slot carrying the hub's renderer manifest: one
101
110
  * {@link import('./client/renderers').DockRendererManifest} entry per dock
102
111
  * `type`, published by `initHub({ renderers })` and consumed by every
103
112
  * hub-aware client (the headless client host and viewers alike).
@@ -2,7 +2,7 @@ import { cleanDoubleSlashes, withLeadingSlash, withTrailingSlash } from "ufo";
2
2
  //#region src/events.ts
3
3
  /**
4
4
  * Centralized registry of every event, broadcast, RPC method, shared-state
5
- * key, and channel name the hub uses — the single source of truth that keeps
5
+ * key, and channel name the hub uses: the single source of truth that keeps
6
6
  * these names out of scattered string literals.
7
7
  *
8
8
  * **Keep this in sync with [`docs/content/8.references/3.events.md`](../../../docs/content/8.references/3.events.md)**
@@ -65,11 +65,18 @@ const HUB_EVENTS = {
65
65
  /** Streaming channel ids (server → client), `devframe:` prefix. */
66
66
  stream: { terminals: "devframe:terminals" },
67
67
  /** `postMessage` channels for host ↔ iframe protocols, `devframe:` prefix. */
68
- postMessage: { frameNav: "devframe:frame-nav" }
68
+ postMessage: { frameNav: "devframe:frame-nav" },
69
+ /**
70
+ * Same-origin `BroadcastChannel` names, `devframe:` prefix. Used on a
71
+ * `static` backend, where no live server can relay a client's request to
72
+ * its sibling browsing contexts (e.g. a panel iframe steering the host
73
+ * page's dock selection).
74
+ */
75
+ broadcastChannel: { docksActivate: "devframe:docks:activate" }
69
76
  };
70
77
  //#endregion
71
78
  //#region src/constants.ts
72
- /** Default mount base for a hub instance — one namespace, one catch-all. */
79
+ /** Default mount base for a hub instance: one namespace, one catch-all. */
73
80
  const DEVFRAMES_HUB_BASE = "/__devframes/";
74
81
  /**
75
82
  * Normalize a hub mount base to an absolute path with leading and trailing
@@ -82,7 +89,7 @@ function normalizeHubBase(base) {
82
89
  return cleanDoubleSlashes(withTrailingSlash(withLeadingSlash(base)));
83
90
  }
84
91
  /**
85
- * The default ordering weight for each known dock category — lower sorts
92
+ * The default ordering weight for each known dock category, where lower sorts
86
93
  * earlier. Downstream viewers (e.g. `@vitejs/devtools-kit`) import this as the
87
94
  * single source of truth so the hub and its viewers agree on category order.
88
95
  * `framework` sorts first; `~builtin` (the viewer's own built-in views) last.
@@ -104,7 +111,7 @@ const DEFAULT_CATEGORIES_ORDER = {
104
111
  "~builtin": 1e3
105
112
  };
106
113
  /**
107
- * Shared-state slot carrying the hub's renderer manifest — one
114
+ * Shared-state slot carrying the hub's renderer manifest: one
108
115
  * {@link import('./client/renderers').DockRendererManifest} entry per dock
109
116
  * `type`, published by `initHub({ renderers })` and consumed by every
110
117
  * hub-aware client (the headless client host and viewers alike).
@@ -1,3 +1,3 @@
1
- import { a as normalizeHubBase, i as DOCK_RENDERERS_STATE_KEY, n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS, r as DEVFRAMES_HUB_BASE, t as DEFAULT_CATEGORIES_ORDER } from "./constants-DJrPlLi0.mjs";
1
+ import { a as normalizeHubBase, i as DOCK_RENDERERS_STATE_KEY, n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS, r as DEVFRAMES_HUB_BASE, t as DEFAULT_CATEGORIES_ORDER } from "./constants-Bz4yclHo.mjs";
2
2
  export * from "devframe/constants";
3
3
  export { DEFAULT_CATEGORIES_ORDER, DEFAULT_STATE_USER_SETTINGS, DEVFRAMES_HUB_BASE, DOCK_RENDERERS_STATE_KEY, HUB_EVENTS, normalizeHubBase };
@@ -1,3 +1,3 @@
1
- import { a as normalizeHubBase, i as DOCK_RENDERERS_STATE_KEY, n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS, r as DEVFRAMES_HUB_BASE, t as DEFAULT_CATEGORIES_ORDER } from "./constants-D1zicoJ4.mjs";
1
+ import { a as normalizeHubBase, i as DOCK_RENDERERS_STATE_KEY, n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS, r as DEVFRAMES_HUB_BASE, t as DEFAULT_CATEGORIES_ORDER } from "./constants-Da23JxCZ.mjs";
2
2
  export * from "devframe/constants";
3
3
  export { DEFAULT_CATEGORIES_ORDER, DEFAULT_STATE_USER_SETTINGS, DEVFRAMES_HUB_BASE, DOCK_RENDERERS_STATE_KEY, HUB_EVENTS, normalizeHubBase };