@nimblebrain/synapse 0.12.0-rc.0 → 0.12.1
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/README.md +9 -5
- package/dist/ui/index.cjs +55 -29
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.cts +4 -4
- package/dist/ui/index.d.ts +4 -4
- package/dist/ui/index.js +55 -29
- package/dist/ui/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,6 +37,10 @@ npm install @nimblebrain/synapse
|
|
|
37
37
|
|
|
38
38
|
**Peer dependency:** `@modelcontextprotocol/ext-apps@^1.3.1`
|
|
39
39
|
|
|
40
|
+
**Building a Python MCP server?** The server half — one self-contained `ui://`
|
|
41
|
+
component rendered across ChatGPT, Claude, and NimbleBrain — ships as the
|
|
42
|
+
[`nimblebrain-synapse`](python/README.md) PyPI package (`pip install nimblebrain-synapse`).
|
|
43
|
+
|
|
40
44
|
## Package Exports
|
|
41
45
|
|
|
42
46
|
| Entry Point | Description |
|
|
@@ -113,7 +117,7 @@ app.updateModelContext(
|
|
|
113
117
|
### React
|
|
114
118
|
|
|
115
119
|
```tsx
|
|
116
|
-
import { AppProvider, useToolResult,
|
|
120
|
+
import { AppProvider, useToolResult, useResize } from "@nimblebrain/synapse/react";
|
|
117
121
|
|
|
118
122
|
function App() {
|
|
119
123
|
return (
|
|
@@ -125,7 +129,6 @@ function App() {
|
|
|
125
129
|
|
|
126
130
|
function ItemList() {
|
|
127
131
|
const result = useToolResult();
|
|
128
|
-
const { call, data, isPending } = useCallTool("list_items");
|
|
129
132
|
const resize = useResize();
|
|
130
133
|
|
|
131
134
|
useEffect(() => { if (result) resize(); }, [result, resize]);
|
|
@@ -336,7 +339,7 @@ await synapse.ready;
|
|
|
336
339
|
| `ready` | Promise that resolves after the ext-apps handshake |
|
|
337
340
|
| `isNimbleBrainHost` | Whether the host is a NimbleBrain platform |
|
|
338
341
|
| `callTool(name, args?)` | Call an MCP tool and get typed result |
|
|
339
|
-
| `callToolAsTask(name, args?, opts?)` | Call a long-running tool task-augmented; returns a `TaskHandle
|
|
342
|
+
| `callToolAsTask(name, args?, opts?)` | Call a long-running tool task-augmented; returns a `Promise<TaskHandle>`. See [Long-running tools](#long-running-tools-tasks) below. |
|
|
340
343
|
| `onDataChanged(cb)` | Subscribe to data change events |
|
|
341
344
|
| `onAction(cb)` | Subscribe to agent actions (typed, declarative) |
|
|
342
345
|
| `getTheme()` | Get current theme |
|
|
@@ -359,7 +362,7 @@ await synapse.ready;
|
|
|
359
362
|
Wrap your app with `<AppProvider>` and use these hooks. Each is a thin wrapper over `connect()`.
|
|
360
363
|
|
|
361
364
|
```tsx
|
|
362
|
-
import { AppProvider, useApp, useToolResult, useToolInput, useResize
|
|
365
|
+
import { AppProvider, useApp, useToolResult, useToolInput, useResize } from "@nimblebrain/synapse/react";
|
|
363
366
|
```
|
|
364
367
|
|
|
365
368
|
| Hook | Returns | Description |
|
|
@@ -369,7 +372,8 @@ import { AppProvider, useApp, useToolResult, useToolInput, useResize, useCallToo
|
|
|
369
372
|
| `useToolInput()` | `Record<string, unknown> \| null` | Re-renders on every `tool-input` event |
|
|
370
373
|
| `useConnectTheme()` | `Theme` | Reactive theme from `connect()` |
|
|
371
374
|
| `useResize()` | `(w?, h?) => void` | Resize helper — auto-measures body if no args |
|
|
372
|
-
|
|
375
|
+
|
|
376
|
+
To call a tool on this path, use the `App` instance: `useApp()`, then `app.callTool(name, args)`. The `useCallTool` hook (with built-in loading state) belongs to the `SynapseProvider` set below.
|
|
373
377
|
|
|
374
378
|
### `SynapseProvider`-based (Legacy)
|
|
375
379
|
|
package/dist/ui/index.cjs
CHANGED
|
@@ -307,21 +307,23 @@ function Card({
|
|
|
307
307
|
}
|
|
308
308
|
var STYLE_ID3 = "nb-synapse-drawer";
|
|
309
309
|
var RULES3 = `
|
|
310
|
+
.nb-drawer-scrim {
|
|
311
|
+
position: fixed; inset: 0; z-index: 1000;
|
|
312
|
+
display: flex; background: rgba(0, 0, 0, 0.32);
|
|
313
|
+
animation: nb-drawer-fade 200ms ease;
|
|
314
|
+
}
|
|
315
|
+
.nb-drawer-scrim--right { justify-content: flex-end; }
|
|
316
|
+
.nb-drawer-scrim--left { justify-content: flex-start; }
|
|
317
|
+
.nb-drawer-scrim--bottom { align-items: flex-end; }
|
|
310
318
|
.nb-drawer {
|
|
311
319
|
margin: 0; padding: 0; border: none; max-width: 92%;
|
|
312
320
|
height: 100%; max-height: 100%; display: flex; flex-direction: column;
|
|
321
|
+
outline: none;
|
|
313
322
|
}
|
|
314
|
-
.nb-drawer--
|
|
315
|
-
.nb-drawer--
|
|
316
|
-
.nb-drawer--
|
|
317
|
-
|
|
318
|
-
height: auto; max-height: 92%;
|
|
319
|
-
}
|
|
320
|
-
.nb-drawer--right[open] { animation: nb-drawer-in-right 240ms cubic-bezier(0.2, 0, 0, 1); }
|
|
321
|
-
.nb-drawer--left[open] { animation: nb-drawer-in-left 240ms cubic-bezier(0.2, 0, 0, 1); }
|
|
322
|
-
.nb-drawer--bottom[open] { animation: nb-drawer-in-bottom 240ms cubic-bezier(0.2, 0, 0, 1); }
|
|
323
|
-
.nb-drawer::backdrop { background: rgba(0, 0, 0, 0.32); }
|
|
324
|
-
.nb-drawer[open]::backdrop { animation: nb-drawer-fade 200ms ease; }
|
|
323
|
+
.nb-drawer--bottom { width: 100%; max-width: 100%; height: auto; max-height: 92%; }
|
|
324
|
+
.nb-drawer--right { animation: nb-drawer-in-right 240ms cubic-bezier(0.2, 0, 0, 1); }
|
|
325
|
+
.nb-drawer--left { animation: nb-drawer-in-left 240ms cubic-bezier(0.2, 0, 0, 1); }
|
|
326
|
+
.nb-drawer--bottom { animation: nb-drawer-in-bottom 240ms cubic-bezier(0.2, 0, 0, 1); }
|
|
325
327
|
@keyframes nb-drawer-in-right { from { transform: translateX(100%); } }
|
|
326
328
|
@keyframes nb-drawer-in-left { from { transform: translateX(-100%); } }
|
|
327
329
|
@keyframes nb-drawer-in-bottom { from { transform: translateY(100%); } }
|
|
@@ -343,41 +345,65 @@ function DrawerRoot({
|
|
|
343
345
|
...rest
|
|
344
346
|
}) {
|
|
345
347
|
chunkRX6XCLFF_cjs.ensureStyle(STYLE_ID3, RULES3);
|
|
346
|
-
const
|
|
348
|
+
const panelRef = react.useRef(null);
|
|
347
349
|
const labelId = react.useId();
|
|
348
350
|
const [hasTitle, setHasTitle] = react.useState(false);
|
|
349
351
|
react.useEffect(() => {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
352
|
+
if (!open) return;
|
|
353
|
+
const onKey = (e) => {
|
|
354
|
+
if (e.key === "Escape") {
|
|
355
|
+
e.preventDefault();
|
|
356
|
+
(onEscape ?? onClose)();
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
window.addEventListener("keydown", onKey);
|
|
360
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
361
|
+
}, [open, onEscape, onClose]);
|
|
362
|
+
react.useEffect(() => {
|
|
363
|
+
if (!open) return;
|
|
364
|
+
const previouslyFocused = document.activeElement;
|
|
365
|
+
panelRef.current?.focus();
|
|
366
|
+
const { body } = document;
|
|
367
|
+
const prevOverflow = body.style.overflow;
|
|
368
|
+
body.style.overflow = "hidden";
|
|
369
|
+
return () => {
|
|
370
|
+
body.style.overflow = prevOverflow;
|
|
371
|
+
previouslyFocused?.focus?.();
|
|
372
|
+
};
|
|
354
373
|
}, [open]);
|
|
374
|
+
const ctxValue = react.useMemo(() => ({ labelId, setHasTitle }), [labelId]);
|
|
375
|
+
if (!open) return null;
|
|
355
376
|
const isBottom = side === "bottom";
|
|
356
377
|
const panelStyle = {
|
|
357
378
|
...isBottom ? { width: "100%", maxWidth: "100%" } : { width },
|
|
358
379
|
background: tokens.bg,
|
|
359
380
|
color: tokens.fg,
|
|
360
381
|
boxShadow: tokens.shadowLg,
|
|
382
|
+
outline: "none",
|
|
361
383
|
...side === "right" ? { borderLeft: `${tokens.borderWidth} solid ${tokens.border}` } : side === "left" ? { borderRight: `${tokens.borderWidth} solid ${tokens.border}` } : { borderTop: `${tokens.borderWidth} solid ${tokens.border}` },
|
|
362
384
|
...style
|
|
363
385
|
};
|
|
364
|
-
const ctxValue = react.useMemo(() => ({ labelId, setHasTitle }), [labelId]);
|
|
365
386
|
return /* @__PURE__ */ jsxRuntime.jsx(DrawerContext.Provider, { value: ctxValue, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
366
|
-
"
|
|
387
|
+
"div",
|
|
367
388
|
{
|
|
368
|
-
|
|
369
|
-
className: `nb-drawer nb-drawer--${side}`,
|
|
370
|
-
style: panelStyle,
|
|
371
|
-
"aria-labelledby": hasTitle ? labelId : void 0,
|
|
372
|
-
onCancel: (e) => {
|
|
373
|
-
e.preventDefault();
|
|
374
|
-
(onEscape ?? onClose)();
|
|
375
|
-
},
|
|
389
|
+
className: `nb-drawer-scrim nb-drawer-scrim--${side}`,
|
|
376
390
|
onClick: (e) => {
|
|
377
|
-
if (e.target ===
|
|
391
|
+
if (e.target === e.currentTarget) onClose();
|
|
378
392
|
},
|
|
379
|
-
|
|
380
|
-
|
|
393
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
394
|
+
"div",
|
|
395
|
+
{
|
|
396
|
+
ref: panelRef,
|
|
397
|
+
role: "dialog",
|
|
398
|
+
"aria-modal": "true",
|
|
399
|
+
"aria-labelledby": hasTitle ? labelId : void 0,
|
|
400
|
+
tabIndex: -1,
|
|
401
|
+
className: `nb-drawer nb-drawer--${side}`,
|
|
402
|
+
style: panelStyle,
|
|
403
|
+
...rest,
|
|
404
|
+
children
|
|
405
|
+
}
|
|
406
|
+
)
|
|
381
407
|
}
|
|
382
408
|
) });
|
|
383
409
|
}
|