@webmcp-auto-ui/ui 2.5.29 → 2.5.31

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webmcp-auto-ui/ui",
3
- "version": "2.5.29",
3
+ "version": "2.5.31",
4
4
  "description": "Svelte 5 UI components — primitives, widgets, window manager",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -11,7 +11,7 @@ import {
11
11
  addImportedCells, registerExecutor, collectDataServers,
12
12
  autosize, openShareModal, registerHistoryObserver,
13
13
  renderCellLogs,
14
- createPublishControls, autoConnectFrontmatterServers,
14
+ createPublishControls, autoConnectFrontmatterServers, bootstrapMcpBridge,
15
15
  createRuntimeOverlay, effectiveResult, cellRuntimeStatus,
16
16
  lastRefreshedAt, bootstrapLiveRefresh, fmtRelTime, preserveScrollAround,
17
17
  type NotebookState, type NotebookCell, type CellResult, type CellExecContext,
@@ -289,6 +289,10 @@ export async function render(container: HTMLElement, data: Record<string, unknow
289
289
  // Auto-connect data servers declared in the recipe frontmatter (data.servers)
290
290
  autoConnectFrontmatterServers(data, () => pane.setServers(collectDataServers(data)));
291
291
 
292
+ // Start a persistent MCP bridge so the sql executor can find tools in edit mode
293
+ // too (not just when live-refresh is running in view mode).
294
+ const mcpBridgeCleanup = bootstrapMcpBridge({ data, MultiMcpBridgeCtor: MultiMcpBridge as any });
295
+
292
296
  // Keep pane servers in sync with canvas changes
293
297
  let canvasUnsub: (() => void) | null = null;
294
298
  try {
@@ -315,6 +319,7 @@ export async function render(container: HTMLElement, data: Record<string, unknow
315
319
  pane.destroy();
316
320
  publishCleanup();
317
321
  liveCleanup?.();
322
+ mcpBridgeCleanup();
318
323
  };
319
324
  }
320
325
 
@@ -207,12 +207,15 @@ function walk(node: Node): void {
207
207
  // via turndown on input (debounced) and at blur.
208
208
  // ---------------------------------------------------------------------------
209
209
 
210
- // @ts-ignore — turndown ships its own types but we stay ts-nocheck here
211
- import TurndownService from 'turndown';
212
-
210
+ // turndown is loaded lazily (browser-only). Top-level import breaks SSR because
211
+ // turndown's CJS internals use require() which throws in ESM scope.
213
212
  let _td: any = null;
214
- function td(): any {
213
+ async function ensureTd(): Promise<any> {
215
214
  if (_td) return _td;
215
+ if (typeof window === 'undefined') return null;
216
+ // @ts-ignore — turndown ships its own types but we stay ts-nocheck here
217
+ const mod = await import('turndown');
218
+ const TurndownService = mod.default || mod;
216
219
  _td = new TurndownService({
217
220
  headingStyle: 'atx',
218
221
  hr: '---',
@@ -230,8 +233,11 @@ function td(): any {
230
233
  return _td;
231
234
  }
232
235
 
233
- function htmlToMd(html: string): string {
234
- try { return td().turndown(html || ''); } catch { return ''; }
236
+ async function htmlToMd(html: string): Promise<string> {
237
+ try {
238
+ const t = await ensureTd();
239
+ return t ? t.turndown(html || '') : '';
240
+ } catch { return ''; }
235
241
  }
236
242
 
237
243
  function ensureToolbarStyles(): void {
@@ -442,9 +448,9 @@ export function mountEditableProse(opts: {
442
448
  flushToMd();
443
449
  }, 400);
444
450
  };
445
- const flushToMd = () => {
451
+ const flushToMd = async () => {
446
452
  const html = host.innerHTML;
447
- const md = htmlToMd(html);
453
+ const md = await htmlToMd(html);
448
454
  opts.setContent(md);
449
455
  opts.onChange?.();
450
456
  updateEmptyState(host);
@@ -491,10 +497,11 @@ export function mountEditableProse(opts: {
491
497
  if (html) {
492
498
  e.preventDefault();
493
499
  // Strip inline styles by routing via turndown → re-render via our MD pipeline
494
- const md = htmlToMd(html);
495
- const cleanHtml = renderProse(md);
496
- document.execCommand('insertHTML', false, cleanHtml);
497
- scheduleSync();
500
+ htmlToMd(html).then((md) => {
501
+ const cleanHtml = renderProse(md);
502
+ document.execCommand('insertHTML', false, cleanHtml);
503
+ scheduleSync();
504
+ });
498
505
  } else if (text) {
499
506
  // Plain text paste — default behaviour fine, but still trigger sync
500
507
  scheduleSync();
@@ -128,8 +128,19 @@ export function preserveScrollAround(anchor: HTMLElement): () => void {
128
128
  }
129
129
  const winY = window.scrollY;
130
130
  const saved = scrollParents.map((el) => el.scrollTop);
131
+ // Only capture the active cell if the user was actually editing
132
+ // (textarea or contentEditable). Clicking a button or label should
133
+ // NOT trigger a post-rerender refocus, which would scroll the page
134
+ // to that cell's input.
131
135
  const activeEl = document.activeElement as HTMLElement | null;
132
- const activeCellId = activeEl?.closest<HTMLElement>('[data-cell-id]')?.dataset.cellId ?? null;
136
+ const isEditing = !!activeEl && (
137
+ activeEl.tagName === 'TEXTAREA' ||
138
+ activeEl.tagName === 'INPUT' ||
139
+ activeEl.isContentEditable
140
+ );
141
+ const activeCellId = isEditing
142
+ ? activeEl?.closest<HTMLElement>('[data-cell-id]')?.dataset.cellId ?? null
143
+ : null;
133
144
 
134
145
  return () => {
135
146
  requestAnimationFrame(() => {
@@ -844,6 +855,35 @@ export function createPublishControls(state: NotebookState, opts: PublishControl
844
855
  };
845
856
  }
846
857
 
858
+ /**
859
+ * Start a persistent MCP bridge that connects declared data servers and keeps
860
+ * their tool/recipe metadata populated on the canvas store. Independent of
861
+ * live-refresh — this runs even in edit mode, so the sql executor can discover
862
+ * `*_query_sql` tools as soon as the bridge is connected.
863
+ *
864
+ * Returns a cleanup function. Caller is expected to start this once at mount
865
+ * and call cleanup on unmount.
866
+ */
867
+ export function bootstrapMcpBridge(opts: {
868
+ data: Record<string, unknown>;
869
+ MultiMcpBridgeCtor: new (opts: { getCanvas: () => unknown }) => {
870
+ start(): void;
871
+ stop(): void;
872
+ };
873
+ }): () => void {
874
+ try {
875
+ autoConnectFrontmatterServers(opts.data);
876
+ const canvas: unknown = (globalThis as { __canvasVanilla?: unknown; canvasVanilla?: unknown })
877
+ .__canvasVanilla ?? (globalThis as { canvasVanilla?: unknown }).canvasVanilla;
878
+ if (!canvas) return () => { /* no-op */ };
879
+ const bridge = new opts.MultiMcpBridgeCtor({ getCanvas: () => canvas });
880
+ bridge.start();
881
+ return () => { try { bridge.stop(); } catch { /* ignore */ } };
882
+ } catch {
883
+ return () => { /* no-op */ };
884
+ }
885
+ }
886
+
847
887
  /**
848
888
  * Auto-connect any data servers declared in recipe frontmatter (`data.servers`)
849
889
  * to the shared canvas store. No-op / no-throw if the canvas store is absent.