@lantos1618/better-ui 0.7.1 → 0.8.0

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.
@@ -179,16 +179,24 @@ function ChatProvider({ endpoint = "/api/chat", tools, toolStateStore: externalS
179
179
  const [threads, setThreads] = (0, import_react2.useState)([]);
180
180
  const [activeThreadId, setActiveThreadId] = (0, import_react2.useState)(threadId);
181
181
  (0, import_react2.useEffect)(() => {
182
- if (persistence) {
183
- persistence.listThreads().then(setThreads).catch((err) => console.warn("[better-ui] persistence error:", err));
184
- }
182
+ if (!persistence) return;
183
+ let cancelled = false;
184
+ persistence.listThreads().then((t) => {
185
+ if (!cancelled) setThreads(t);
186
+ }).catch((err) => console.warn("[better-ui] persistence error:", err));
187
+ return () => {
188
+ cancelled = true;
189
+ };
185
190
  }, [persistence]);
186
191
  (0, import_react2.useEffect)(() => {
187
- if (persistence && activeThreadId) {
188
- persistence.getMessages(activeThreadId).then((msgs) => {
189
- setMessages(msgs);
190
- }).catch((err) => console.warn("[better-ui] persistence error:", err));
191
- }
192
+ if (!persistence || !activeThreadId) return;
193
+ let cancelled = false;
194
+ persistence.getMessages(activeThreadId).then((msgs) => {
195
+ if (!cancelled) setMessages(msgs);
196
+ }).catch((err) => console.warn("[better-ui] persistence error:", err));
197
+ return () => {
198
+ cancelled = true;
199
+ };
192
200
  }, [persistence, activeThreadId, setMessages]);
193
201
  const prevStatusRef = (0, import_react2.useRef)(status);
194
202
  (0, import_react2.useEffect)(() => {
@@ -212,9 +220,10 @@ function ChatProvider({ endpoint = "/api/chat", tools, toolStateStore: externalS
212
220
  }, [persistence, setMessages, toolStateStore]);
213
221
  const switchThreadFn = (0, import_react2.useCallback)(async (id) => {
214
222
  if (!persistence) throw new Error("Persistence not configured");
215
- setActiveThreadId(id);
216
- setMessages([]);
223
+ const msgs = await persistence.getMessages(id);
217
224
  toolStateStore.clear();
225
+ setMessages(msgs);
226
+ setActiveThreadId(id);
218
227
  }, [persistence, setMessages, toolStateStore]);
219
228
  const deleteThreadFn = (0, import_react2.useCallback)(async (id) => {
220
229
  if (!persistence) throw new Error("Persistence not configured");
@@ -237,8 +246,11 @@ function ChatProvider({ endpoint = "/api/chat", tools, toolStateStore: externalS
237
246
  stateContext[entry.toolName] = entry.output;
238
247
  }
239
248
  }
240
- dirty.clear();
241
- return Object.keys(stateContext).length > 0 ? stateContext : null;
249
+ if (Object.keys(stateContext).length > 0) {
250
+ dirty.clear();
251
+ return stateContext;
252
+ }
253
+ return null;
242
254
  }, [toolStateStore]);
243
255
  const executeToolDirect = (0, import_react2.useCallback)(async (toolName, toolInput, toolCallId) => {
244
256
  const currentVersion = (versionRef.current.get(toolCallId) || 0) + 1;
@@ -420,6 +432,33 @@ var import_ai2 = require("ai");
420
432
  // src/components/ToolResult.tsx
421
433
  var import_react4 = require("react");
422
434
  var import_jsx_runtime2 = require("react/jsx-runtime");
435
+ var ToolViewErrorBoundary = class extends import_react4.Component {
436
+ constructor() {
437
+ super(...arguments);
438
+ this.state = { error: null };
439
+ }
440
+ static getDerivedStateFromError(error) {
441
+ return { error };
442
+ }
443
+ componentDidCatch(error, info) {
444
+ console.error(`[better-ui] View for tool "${this.props.toolName}" threw:`, error, info);
445
+ }
446
+ render() {
447
+ if (this.state.error) {
448
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "bg-[var(--bui-error-muted,rgba(220,38,38,0.08))] border border-[var(--bui-error-border,rgba(153,27,27,0.5))] rounded-xl p-4", children: [
449
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", children: [
450
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: "w-4 h-4 text-[var(--bui-error-fg,#f87171)] shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" }) }),
451
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-[var(--bui-error-fg,#f87171)] text-sm font-medium", children: [
452
+ this.props.toolName,
453
+ " view error"
454
+ ] })
455
+ ] }),
456
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-[var(--bui-error-fg,#f87171)]/70 text-xs mt-1", children: this.state.error.message })
457
+ ] });
458
+ }
459
+ return this.props.children;
460
+ }
461
+ };
423
462
  function useIsFollowup(toolStateStore, toolCallId) {
424
463
  const subscribe = (0, import_react4.useCallback)(
425
464
  (listener) => toolStateStore.subscribeAll(listener),
@@ -602,7 +641,7 @@ function ToolResult({
602
641
  )
603
642
  ] });
604
643
  }
605
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: className || "", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
644
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: className || "", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ToolViewErrorBoundary, { toolName, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
606
645
  toolDef.View,
607
646
  {
608
647
  data: resolvedOutput,
@@ -610,7 +649,7 @@ function ToolResult({
610
649
  onAction: getOnAction(toolCallId, toolName),
611
650
  error: storeState?.error ? new Error(storeState.error) : null
612
651
  }
613
- ) });
652
+ ) }) });
614
653
  }
615
654
 
616
655
  // src/components/Markdown.tsx
@@ -121,16 +121,24 @@ function ChatProvider({ endpoint = "/api/chat", tools, toolStateStore: externalS
121
121
  const [threads, setThreads] = useState([]);
122
122
  const [activeThreadId, setActiveThreadId] = useState(threadId);
123
123
  useEffect(() => {
124
- if (persistence) {
125
- persistence.listThreads().then(setThreads).catch((err) => console.warn("[better-ui] persistence error:", err));
126
- }
124
+ if (!persistence) return;
125
+ let cancelled = false;
126
+ persistence.listThreads().then((t) => {
127
+ if (!cancelled) setThreads(t);
128
+ }).catch((err) => console.warn("[better-ui] persistence error:", err));
129
+ return () => {
130
+ cancelled = true;
131
+ };
127
132
  }, [persistence]);
128
133
  useEffect(() => {
129
- if (persistence && activeThreadId) {
130
- persistence.getMessages(activeThreadId).then((msgs) => {
131
- setMessages(msgs);
132
- }).catch((err) => console.warn("[better-ui] persistence error:", err));
133
- }
134
+ if (!persistence || !activeThreadId) return;
135
+ let cancelled = false;
136
+ persistence.getMessages(activeThreadId).then((msgs) => {
137
+ if (!cancelled) setMessages(msgs);
138
+ }).catch((err) => console.warn("[better-ui] persistence error:", err));
139
+ return () => {
140
+ cancelled = true;
141
+ };
134
142
  }, [persistence, activeThreadId, setMessages]);
135
143
  const prevStatusRef = useRef(status);
136
144
  useEffect(() => {
@@ -154,9 +162,10 @@ function ChatProvider({ endpoint = "/api/chat", tools, toolStateStore: externalS
154
162
  }, [persistence, setMessages, toolStateStore]);
155
163
  const switchThreadFn = useCallback2(async (id) => {
156
164
  if (!persistence) throw new Error("Persistence not configured");
157
- setActiveThreadId(id);
158
- setMessages([]);
165
+ const msgs = await persistence.getMessages(id);
159
166
  toolStateStore.clear();
167
+ setMessages(msgs);
168
+ setActiveThreadId(id);
160
169
  }, [persistence, setMessages, toolStateStore]);
161
170
  const deleteThreadFn = useCallback2(async (id) => {
162
171
  if (!persistence) throw new Error("Persistence not configured");
@@ -179,8 +188,11 @@ function ChatProvider({ endpoint = "/api/chat", tools, toolStateStore: externalS
179
188
  stateContext[entry.toolName] = entry.output;
180
189
  }
181
190
  }
182
- dirty.clear();
183
- return Object.keys(stateContext).length > 0 ? stateContext : null;
191
+ if (Object.keys(stateContext).length > 0) {
192
+ dirty.clear();
193
+ return stateContext;
194
+ }
195
+ return null;
184
196
  }, [toolStateStore]);
185
197
  const executeToolDirect = useCallback2(async (toolName, toolInput, toolCallId) => {
186
198
  const currentVersion = (versionRef.current.get(toolCallId) || 0) + 1;
@@ -364,8 +376,35 @@ import {
364
376
  } from "ai";
365
377
 
366
378
  // src/components/ToolResult.tsx
367
- import { useEffect as useEffect2, useRef as useRef2, useSyncExternalStore as useSyncExternalStore2, useCallback as useCallback3 } from "react";
379
+ import { Component, useEffect as useEffect2, useRef as useRef2, useSyncExternalStore as useSyncExternalStore2, useCallback as useCallback3 } from "react";
368
380
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
381
+ var ToolViewErrorBoundary = class extends Component {
382
+ constructor() {
383
+ super(...arguments);
384
+ this.state = { error: null };
385
+ }
386
+ static getDerivedStateFromError(error) {
387
+ return { error };
388
+ }
389
+ componentDidCatch(error, info) {
390
+ console.error(`[better-ui] View for tool "${this.props.toolName}" threw:`, error, info);
391
+ }
392
+ render() {
393
+ if (this.state.error) {
394
+ return /* @__PURE__ */ jsxs("div", { className: "bg-[var(--bui-error-muted,rgba(220,38,38,0.08))] border border-[var(--bui-error-border,rgba(153,27,27,0.5))] rounded-xl p-4", children: [
395
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
396
+ /* @__PURE__ */ jsx2("svg", { className: "w-4 h-4 text-[var(--bui-error-fg,#f87171)] shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx2("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" }) }),
397
+ /* @__PURE__ */ jsxs("span", { className: "text-[var(--bui-error-fg,#f87171)] text-sm font-medium", children: [
398
+ this.props.toolName,
399
+ " view error"
400
+ ] })
401
+ ] }),
402
+ /* @__PURE__ */ jsx2("p", { className: "text-[var(--bui-error-fg,#f87171)]/70 text-xs mt-1", children: this.state.error.message })
403
+ ] });
404
+ }
405
+ return this.props.children;
406
+ }
407
+ };
369
408
  function useIsFollowup(toolStateStore, toolCallId) {
370
409
  const subscribe = useCallback3(
371
410
  (listener) => toolStateStore.subscribeAll(listener),
@@ -548,7 +587,7 @@ function ToolResult({
548
587
  )
549
588
  ] });
550
589
  }
551
- return /* @__PURE__ */ jsx2("div", { className: className || "", children: /* @__PURE__ */ jsx2(
590
+ return /* @__PURE__ */ jsx2("div", { className: className || "", children: /* @__PURE__ */ jsx2(ToolViewErrorBoundary, { toolName, children: /* @__PURE__ */ jsx2(
552
591
  toolDef.View,
553
592
  {
554
593
  data: resolvedOutput,
@@ -556,7 +595,7 @@ function ToolResult({
556
595
  onAction: getOnAction(toolCallId, toolName),
557
596
  error: storeState?.error ? new Error(storeState.error) : null
558
597
  }
559
- ) });
598
+ ) }) });
560
599
  }
561
600
 
562
601
  // src/components/Markdown.tsx
package/dist/mcp/index.js CHANGED
@@ -296,7 +296,7 @@ var MCPServer = class {
296
296
  jsonrpc: "2.0",
297
297
  id: message.id,
298
298
  result: {
299
- protocolVersion: "2024-11-05",
299
+ protocolVersion: "2025-11-25",
300
300
  capabilities: {
301
301
  tools: {}
302
302
  },
@@ -133,7 +133,7 @@ var MCPServer = class {
133
133
  jsonrpc: "2.0",
134
134
  id: message.id,
135
135
  result: {
136
- protocolVersion: "2024-11-05",
136
+ protocolVersion: "2025-11-25",
137
137
  capabilities: {
138
138
  tools: {}
139
139
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lantos1618/better-ui",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "A minimal, type-safe AI-first UI framework for building tools",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",