@seed-ship/mcp-ui-solid 5.0.0 → 5.2.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +160 -6
  3. package/dist/components/ChatPrompt.cjs +71 -53
  4. package/dist/components/ChatPrompt.cjs.map +1 -1
  5. package/dist/components/ChatPrompt.d.ts +37 -2
  6. package/dist/components/ChatPrompt.d.ts.map +1 -1
  7. package/dist/components/ChatPrompt.js +72 -54
  8. package/dist/components/ChatPrompt.js.map +1 -1
  9. package/dist/components/FeedbackInline.cjs +57 -0
  10. package/dist/components/FeedbackInline.cjs.map +1 -0
  11. package/dist/components/FeedbackInline.d.ts +71 -0
  12. package/dist/components/FeedbackInline.d.ts.map +1 -0
  13. package/dist/components/FeedbackInline.js +57 -0
  14. package/dist/components/FeedbackInline.js.map +1 -0
  15. package/dist/index.cjs +9 -0
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +8 -2
  18. package/dist/index.d.ts +8 -2
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +11 -2
  21. package/dist/index.js.map +1 -1
  22. package/dist/services/chat-bus.cjs +71 -0
  23. package/dist/services/chat-bus.cjs.map +1 -1
  24. package/dist/services/chat-bus.d.ts +31 -1
  25. package/dist/services/chat-bus.d.ts.map +1 -1
  26. package/dist/services/chat-bus.js +71 -0
  27. package/dist/services/chat-bus.js.map +1 -1
  28. package/dist/services/chat-prompt-controller.cjs +83 -0
  29. package/dist/services/chat-prompt-controller.cjs.map +1 -0
  30. package/dist/services/chat-prompt-controller.d.ts +93 -0
  31. package/dist/services/chat-prompt-controller.d.ts.map +1 -0
  32. package/dist/services/chat-prompt-controller.js +83 -0
  33. package/dist/services/chat-prompt-controller.js.map +1 -0
  34. package/dist/stores/scratchpad-store.cjs +105 -77
  35. package/dist/stores/scratchpad-store.cjs.map +1 -1
  36. package/dist/stores/scratchpad-store.d.ts +88 -19
  37. package/dist/stores/scratchpad-store.d.ts.map +1 -1
  38. package/dist/stores/scratchpad-store.js +105 -77
  39. package/dist/stores/scratchpad-store.js.map +1 -1
  40. package/dist/types/chat-bus.d.ts +164 -22
  41. package/dist/types/chat-bus.d.ts.map +1 -1
  42. package/package.json +1 -1
  43. package/src/components/ChatPrompt.test.tsx +122 -0
  44. package/src/components/ChatPrompt.tsx +70 -15
  45. package/src/components/FeedbackInline.test.tsx +117 -0
  46. package/src/components/FeedbackInline.tsx +143 -0
  47. package/src/index.ts +24 -1
  48. package/src/services/chat-bus.test.ts +154 -2
  49. package/src/services/chat-bus.ts +115 -0
  50. package/src/services/chat-prompt-controller.test.ts +144 -0
  51. package/src/services/chat-prompt-controller.ts +214 -0
  52. package/src/stores/scratchpad-store.test.tsx +140 -0
  53. package/src/stores/scratchpad-store.tsx +244 -0
  54. package/src/types/chat-bus.ts +166 -22
  55. package/tsconfig.tsbuildinfo +1 -1
  56. package/src/stores/scratchpad-store.ts +0 -126
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
6
+ const solidJs = require("solid-js");
7
+ class PromptReplacedError extends Error {
8
+ constructor(message = "Prompt replaced by a newer one") {
9
+ super(message);
10
+ __publicField(this, "name", "PromptReplacedError");
11
+ }
12
+ }
13
+ function createChatPromptController() {
14
+ const [activePrompt, setActivePrompt] = solidJs.createSignal(null);
15
+ let pending = null;
16
+ function cleanupAbort(entry) {
17
+ if (entry.signal && entry.onAbort) {
18
+ entry.signal.removeEventListener("abort", entry.onAbort);
19
+ }
20
+ }
21
+ function handle(config, signal) {
22
+ if (pending) {
23
+ const previous = pending;
24
+ pending = null;
25
+ cleanupAbort(previous);
26
+ previous.reject(new PromptReplacedError());
27
+ }
28
+ if (signal == null ? void 0 : signal.aborted) {
29
+ setActivePrompt(null);
30
+ return Promise.reject(new DOMException("Prompt aborted", "AbortError"));
31
+ }
32
+ return new Promise((resolve, reject) => {
33
+ const entry = { type: config.type, resolve, reject, signal };
34
+ if (signal) {
35
+ entry.onAbort = () => {
36
+ if (pending === entry) {
37
+ pending = null;
38
+ cleanupAbort(entry);
39
+ setActivePrompt(null);
40
+ reject(new DOMException("Prompt aborted", "AbortError"));
41
+ }
42
+ };
43
+ signal.addEventListener("abort", entry.onAbort, { once: true });
44
+ }
45
+ pending = entry;
46
+ setActivePrompt(config);
47
+ });
48
+ }
49
+ function resolveActive(response) {
50
+ if (!pending) return;
51
+ const entry = pending;
52
+ pending = null;
53
+ cleanupAbort(entry);
54
+ setActivePrompt(null);
55
+ entry.resolve(response);
56
+ }
57
+ function dismissActive() {
58
+ if (!pending) return;
59
+ const entry = pending;
60
+ pending = null;
61
+ cleanupAbort(entry);
62
+ setActivePrompt(null);
63
+ entry.resolve({ type: entry.type, value: "", label: "", dismissed: true });
64
+ }
65
+ function abort(reason = "Prompt aborted") {
66
+ if (!pending) return;
67
+ const entry = pending;
68
+ pending = null;
69
+ cleanupAbort(entry);
70
+ setActivePrompt(null);
71
+ entry.reject(new DOMException(reason, "AbortError"));
72
+ }
73
+ return {
74
+ handle,
75
+ activePrompt,
76
+ resolveActive,
77
+ dismissActive,
78
+ abort
79
+ };
80
+ }
81
+ exports.PromptReplacedError = PromptReplacedError;
82
+ exports.createChatPromptController = createChatPromptController;
83
+ //# sourceMappingURL=chat-prompt-controller.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-prompt-controller.cjs","sources":["../../src/services/chat-prompt-controller.ts"],"sourcesContent":["/**\n * createChatPromptController — centralised lifecycle for `showChatPrompt`\n *\n * @experimental\n * @since v5.2.0\n *\n * The controller owns the resolver closure, AbortSignal wiring, and\n * re-entrance policy in one primitive. Consumers go from ~20 LOC of manual\n * wiring per app to :\n *\n * ```ts\n * const ctrl = createChatPromptController()\n * bus.commands.handle('showChatPrompt', ctrl.handle)\n * // ...\n * <Show when={ctrl.activePrompt()}>\n * {(cfg) => (\n * <ChatPrompt\n * config={cfg()}\n * onSubmit={ctrl.resolveActive}\n * onDismiss={ctrl.dismissActive}\n * />\n * )}\n * </Show>\n * ```\n *\n * ## Re-entrance policy\n *\n * If a new `showChatPrompt` arrives while a previous Promise is still\n * pending, the previous Promise rejects **synchronously** with a\n * `PromptReplacedError` before the new prompt is installed. Callers that\n * care can branch on `err instanceof PromptReplacedError` or `err.name ===\n * 'PromptReplacedError'`.\n *\n * ## Abort semantics\n *\n * `handle(config, signal?)` honours `AbortSignal` :\n *\n * - If `signal.aborted === true` on entry → returns a rejected Promise with\n * `new DOMException('Prompt aborted', 'AbortError')`, does NOT set\n * `activePrompt`.\n * - Otherwise registers a once-only listener that rejects with the same\n * `DOMException` on abort, clearing the active state.\n *\n * `AbortError` is the Web Platform convention (matches `fetch()`,\n * `Response.body.cancel()`, etc.) — callers can branch on `err.name ===\n * 'AbortError'` without importing any mcp-ui type.\n */\n\nimport { createSignal, type Accessor } from 'solid-js'\nimport type { ChatPromptConfig, ChatPromptResponse } from '../types/chat-bus'\n\n// ─── Error class ─────────────────────────────────────────────\n\n/**\n * Thrown when an active `showChatPrompt` Promise is rejected because a new\n * prompt arrived before the previous one resolved. Consumers can use\n * `instanceof PromptReplacedError` or `err.name === 'PromptReplacedError'` to\n * branch (retry, bail, log).\n *\n * @experimental\n * @since v5.2.0\n */\nexport class PromptReplacedError extends Error {\n readonly name = 'PromptReplacedError' as const\n constructor(message = 'Prompt replaced by a newer one') {\n super(message)\n }\n}\n\n// ─── Controller shape ────────────────────────────────────────\n\nexport interface ChatPromptController {\n /**\n * Register as the bus handler :\n * `bus.commands.handle('showChatPrompt', ctrl.handle)`\n */\n handle: (config: ChatPromptConfig, signal?: AbortSignal) => Promise<ChatPromptResponse>\n\n /**\n * Reactive accessor for the currently active prompt config (null when no\n * prompt is pending). Use in JSX to drive `<ChatPrompt>` rendering.\n */\n activePrompt: Accessor<ChatPromptConfig | null>\n\n /** Call this from `<ChatPrompt>`'s `onSubmit` prop. */\n resolveActive: (response: ChatPromptResponse) => void\n\n /** Call this from `<ChatPrompt>`'s `onDismiss` prop. */\n dismissActive: () => void\n\n /**\n * Cancel the active prompt programmatically (e.g. on route change). Rejects\n * the pending Promise with the supplied reason or an `AbortError`.\n */\n abort: (reason?: string) => void\n}\n\n// ─── Factory ─────────────────────────────────────────────────\n\n/**\n * Create a stateful controller that owns the active prompt Promise, the\n * AbortSignal listener, and the re-entrance policy. See module JSDoc for\n * full usage.\n *\n * @experimental\n * @since v5.2.0\n */\nexport function createChatPromptController(): ChatPromptController {\n const [activePrompt, setActivePrompt] = createSignal<ChatPromptConfig | null>(null)\n\n interface PendingEntry {\n type: ChatPromptConfig['type']\n resolve: (r: ChatPromptResponse) => void\n reject: (err: unknown) => void\n signal?: AbortSignal\n onAbort?: () => void\n }\n\n let pending: PendingEntry | null = null\n\n function cleanupAbort(entry: PendingEntry): void {\n if (entry.signal && entry.onAbort) {\n entry.signal.removeEventListener('abort', entry.onAbort)\n }\n }\n\n function clearPending(): void {\n if (pending) {\n cleanupAbort(pending)\n pending = null\n }\n setActivePrompt(null)\n }\n\n function handle(\n config: ChatPromptConfig,\n signal?: AbortSignal\n ): Promise<ChatPromptResponse> {\n // Re-entrance : synchronously reject the previous Promise before\n // installing the new prompt. The caller's .catch sees the rejection\n // on the microtask boundary regardless.\n if (pending) {\n const previous = pending\n pending = null\n cleanupAbort(previous)\n previous.reject(new PromptReplacedError())\n }\n\n // Abort already tripped on entry : return a rejected Promise without\n // ever showing the UI.\n if (signal?.aborted) {\n setActivePrompt(null)\n return Promise.reject(new DOMException('Prompt aborted', 'AbortError'))\n }\n\n return new Promise<ChatPromptResponse>((resolve, reject) => {\n const entry: PendingEntry = { type: config.type, resolve, reject, signal }\n\n if (signal) {\n entry.onAbort = () => {\n // If this entry is still active, reject + clear. If a newer prompt\n // has since replaced it, the cleanup already ran — no-op.\n if (pending === entry) {\n pending = null\n cleanupAbort(entry)\n setActivePrompt(null)\n reject(new DOMException('Prompt aborted', 'AbortError'))\n }\n }\n signal.addEventListener('abort', entry.onAbort, { once: true })\n }\n\n pending = entry\n setActivePrompt(config)\n })\n }\n\n function resolveActive(response: ChatPromptResponse): void {\n if (!pending) return\n const entry = pending\n pending = null\n cleanupAbort(entry)\n setActivePrompt(null)\n entry.resolve(response)\n }\n\n function dismissActive(): void {\n if (!pending) return\n const entry = pending\n pending = null\n cleanupAbort(entry)\n setActivePrompt(null)\n // Surface as a resolved Promise with dismissed: true — matches existing\n // ChatPrompt onDismiss contract from v4.x.\n entry.resolve({ type: entry.type, value: '', label: '', dismissed: true })\n }\n\n function abort(reason = 'Prompt aborted'): void {\n if (!pending) return\n const entry = pending\n pending = null\n cleanupAbort(entry)\n setActivePrompt(null)\n entry.reject(new DOMException(reason, 'AbortError'))\n }\n\n return {\n handle,\n activePrompt,\n resolveActive,\n dismissActive,\n abort,\n }\n}\n"],"names":["createSignal"],"mappings":";;;;;;AA8DO,MAAM,4BAA4B,MAAM;AAAA,EAE7C,YAAY,UAAU,kCAAkC;AACtD,UAAM,OAAO;AAFN,gCAAO;AAAA,EAGhB;AACF;AAwCO,SAAS,6BAAmD;AACjE,QAAM,CAAC,cAAc,eAAe,IAAIA,QAAAA,aAAsC,IAAI;AAUlF,MAAI,UAA+B;AAEnC,WAAS,aAAa,OAA2B;AAC/C,QAAI,MAAM,UAAU,MAAM,SAAS;AACjC,YAAM,OAAO,oBAAoB,SAAS,MAAM,OAAO;AAAA,IACzD;AAAA,EACF;AAUA,WAAS,OACP,QACA,QAC6B;AAI7B,QAAI,SAAS;AACX,YAAM,WAAW;AACjB,gBAAU;AACV,mBAAa,QAAQ;AACrB,eAAS,OAAO,IAAI,qBAAqB;AAAA,IAC3C;AAIA,QAAI,iCAAQ,SAAS;AACnB,sBAAgB,IAAI;AACpB,aAAO,QAAQ,OAAO,IAAI,aAAa,kBAAkB,YAAY,CAAC;AAAA,IACxE;AAEA,WAAO,IAAI,QAA4B,CAAC,SAAS,WAAW;AAC1D,YAAM,QAAsB,EAAE,MAAM,OAAO,MAAM,SAAS,QAAQ,OAAA;AAElE,UAAI,QAAQ;AACV,cAAM,UAAU,MAAM;AAGpB,cAAI,YAAY,OAAO;AACrB,sBAAU;AACV,yBAAa,KAAK;AAClB,4BAAgB,IAAI;AACpB,mBAAO,IAAI,aAAa,kBAAkB,YAAY,CAAC;AAAA,UACzD;AAAA,QACF;AACA,eAAO,iBAAiB,SAAS,MAAM,SAAS,EAAE,MAAM,MAAM;AAAA,MAChE;AAEA,gBAAU;AACV,sBAAgB,MAAM;AAAA,IACxB,CAAC;AAAA,EACH;AAEA,WAAS,cAAc,UAAoC;AACzD,QAAI,CAAC,QAAS;AACd,UAAM,QAAQ;AACd,cAAU;AACV,iBAAa,KAAK;AAClB,oBAAgB,IAAI;AACpB,UAAM,QAAQ,QAAQ;AAAA,EACxB;AAEA,WAAS,gBAAsB;AAC7B,QAAI,CAAC,QAAS;AACd,UAAM,QAAQ;AACd,cAAU;AACV,iBAAa,KAAK;AAClB,oBAAgB,IAAI;AAGpB,UAAM,QAAQ,EAAE,MAAM,MAAM,MAAM,OAAO,IAAI,OAAO,IAAI,WAAW,KAAA,CAAM;AAAA,EAC3E;AAEA,WAAS,MAAM,SAAS,kBAAwB;AAC9C,QAAI,CAAC,QAAS;AACd,UAAM,QAAQ;AACd,cAAU;AACV,iBAAa,KAAK;AAClB,oBAAgB,IAAI;AACpB,UAAM,OAAO,IAAI,aAAa,QAAQ,YAAY,CAAC;AAAA,EACrD;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;;;"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * createChatPromptController — centralised lifecycle for `showChatPrompt`
3
+ *
4
+ * @experimental
5
+ * @since v5.2.0
6
+ *
7
+ * The controller owns the resolver closure, AbortSignal wiring, and
8
+ * re-entrance policy in one primitive. Consumers go from ~20 LOC of manual
9
+ * wiring per app to :
10
+ *
11
+ * ```ts
12
+ * const ctrl = createChatPromptController()
13
+ * bus.commands.handle('showChatPrompt', ctrl.handle)
14
+ * // ...
15
+ * <Show when={ctrl.activePrompt()}>
16
+ * {(cfg) => (
17
+ * <ChatPrompt
18
+ * config={cfg()}
19
+ * onSubmit={ctrl.resolveActive}
20
+ * onDismiss={ctrl.dismissActive}
21
+ * />
22
+ * )}
23
+ * </Show>
24
+ * ```
25
+ *
26
+ * ## Re-entrance policy
27
+ *
28
+ * If a new `showChatPrompt` arrives while a previous Promise is still
29
+ * pending, the previous Promise rejects **synchronously** with a
30
+ * `PromptReplacedError` before the new prompt is installed. Callers that
31
+ * care can branch on `err instanceof PromptReplacedError` or `err.name ===
32
+ * 'PromptReplacedError'`.
33
+ *
34
+ * ## Abort semantics
35
+ *
36
+ * `handle(config, signal?)` honours `AbortSignal` :
37
+ *
38
+ * - If `signal.aborted === true` on entry → returns a rejected Promise with
39
+ * `new DOMException('Prompt aborted', 'AbortError')`, does NOT set
40
+ * `activePrompt`.
41
+ * - Otherwise registers a once-only listener that rejects with the same
42
+ * `DOMException` on abort, clearing the active state.
43
+ *
44
+ * `AbortError` is the Web Platform convention (matches `fetch()`,
45
+ * `Response.body.cancel()`, etc.) — callers can branch on `err.name ===
46
+ * 'AbortError'` without importing any mcp-ui type.
47
+ */
48
+ import { type Accessor } from 'solid-js';
49
+ import type { ChatPromptConfig, ChatPromptResponse } from '../types/chat-bus';
50
+ /**
51
+ * Thrown when an active `showChatPrompt` Promise is rejected because a new
52
+ * prompt arrived before the previous one resolved. Consumers can use
53
+ * `instanceof PromptReplacedError` or `err.name === 'PromptReplacedError'` to
54
+ * branch (retry, bail, log).
55
+ *
56
+ * @experimental
57
+ * @since v5.2.0
58
+ */
59
+ export declare class PromptReplacedError extends Error {
60
+ readonly name: "PromptReplacedError";
61
+ constructor(message?: string);
62
+ }
63
+ export interface ChatPromptController {
64
+ /**
65
+ * Register as the bus handler :
66
+ * `bus.commands.handle('showChatPrompt', ctrl.handle)`
67
+ */
68
+ handle: (config: ChatPromptConfig, signal?: AbortSignal) => Promise<ChatPromptResponse>;
69
+ /**
70
+ * Reactive accessor for the currently active prompt config (null when no
71
+ * prompt is pending). Use in JSX to drive `<ChatPrompt>` rendering.
72
+ */
73
+ activePrompt: Accessor<ChatPromptConfig | null>;
74
+ /** Call this from `<ChatPrompt>`'s `onSubmit` prop. */
75
+ resolveActive: (response: ChatPromptResponse) => void;
76
+ /** Call this from `<ChatPrompt>`'s `onDismiss` prop. */
77
+ dismissActive: () => void;
78
+ /**
79
+ * Cancel the active prompt programmatically (e.g. on route change). Rejects
80
+ * the pending Promise with the supplied reason or an `AbortError`.
81
+ */
82
+ abort: (reason?: string) => void;
83
+ }
84
+ /**
85
+ * Create a stateful controller that owns the active prompt Promise, the
86
+ * AbortSignal listener, and the re-entrance policy. See module JSDoc for
87
+ * full usage.
88
+ *
89
+ * @experimental
90
+ * @since v5.2.0
91
+ */
92
+ export declare function createChatPromptController(): ChatPromptController;
93
+ //# sourceMappingURL=chat-prompt-controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-prompt-controller.d.ts","sourceRoot":"","sources":["../../src/services/chat-prompt-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAA;AACtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAI7E;;;;;;;;GAQG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,IAAI,EAAG,qBAAqB,CAAS;gBAClC,OAAO,SAAmC;CAGvD;AAID,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAA;IAEvF;;;OAGG;IACH,YAAY,EAAE,QAAQ,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;IAE/C,uDAAuD;IACvD,aAAa,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAA;IAErD,wDAAwD;IACxD,aAAa,EAAE,MAAM,IAAI,CAAA;IAEzB;;;OAGG;IACH,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CACjC;AAID;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,IAAI,oBAAoB,CA0GjE"}
@@ -0,0 +1,83 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import { createSignal } from "solid-js";
5
+ class PromptReplacedError extends Error {
6
+ constructor(message = "Prompt replaced by a newer one") {
7
+ super(message);
8
+ __publicField(this, "name", "PromptReplacedError");
9
+ }
10
+ }
11
+ function createChatPromptController() {
12
+ const [activePrompt, setActivePrompt] = createSignal(null);
13
+ let pending = null;
14
+ function cleanupAbort(entry) {
15
+ if (entry.signal && entry.onAbort) {
16
+ entry.signal.removeEventListener("abort", entry.onAbort);
17
+ }
18
+ }
19
+ function handle(config, signal) {
20
+ if (pending) {
21
+ const previous = pending;
22
+ pending = null;
23
+ cleanupAbort(previous);
24
+ previous.reject(new PromptReplacedError());
25
+ }
26
+ if (signal == null ? void 0 : signal.aborted) {
27
+ setActivePrompt(null);
28
+ return Promise.reject(new DOMException("Prompt aborted", "AbortError"));
29
+ }
30
+ return new Promise((resolve, reject) => {
31
+ const entry = { type: config.type, resolve, reject, signal };
32
+ if (signal) {
33
+ entry.onAbort = () => {
34
+ if (pending === entry) {
35
+ pending = null;
36
+ cleanupAbort(entry);
37
+ setActivePrompt(null);
38
+ reject(new DOMException("Prompt aborted", "AbortError"));
39
+ }
40
+ };
41
+ signal.addEventListener("abort", entry.onAbort, { once: true });
42
+ }
43
+ pending = entry;
44
+ setActivePrompt(config);
45
+ });
46
+ }
47
+ function resolveActive(response) {
48
+ if (!pending) return;
49
+ const entry = pending;
50
+ pending = null;
51
+ cleanupAbort(entry);
52
+ setActivePrompt(null);
53
+ entry.resolve(response);
54
+ }
55
+ function dismissActive() {
56
+ if (!pending) return;
57
+ const entry = pending;
58
+ pending = null;
59
+ cleanupAbort(entry);
60
+ setActivePrompt(null);
61
+ entry.resolve({ type: entry.type, value: "", label: "", dismissed: true });
62
+ }
63
+ function abort(reason = "Prompt aborted") {
64
+ if (!pending) return;
65
+ const entry = pending;
66
+ pending = null;
67
+ cleanupAbort(entry);
68
+ setActivePrompt(null);
69
+ entry.reject(new DOMException(reason, "AbortError"));
70
+ }
71
+ return {
72
+ handle,
73
+ activePrompt,
74
+ resolveActive,
75
+ dismissActive,
76
+ abort
77
+ };
78
+ }
79
+ export {
80
+ PromptReplacedError,
81
+ createChatPromptController
82
+ };
83
+ //# sourceMappingURL=chat-prompt-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-prompt-controller.js","sources":["../../src/services/chat-prompt-controller.ts"],"sourcesContent":["/**\n * createChatPromptController — centralised lifecycle for `showChatPrompt`\n *\n * @experimental\n * @since v5.2.0\n *\n * The controller owns the resolver closure, AbortSignal wiring, and\n * re-entrance policy in one primitive. Consumers go from ~20 LOC of manual\n * wiring per app to :\n *\n * ```ts\n * const ctrl = createChatPromptController()\n * bus.commands.handle('showChatPrompt', ctrl.handle)\n * // ...\n * <Show when={ctrl.activePrompt()}>\n * {(cfg) => (\n * <ChatPrompt\n * config={cfg()}\n * onSubmit={ctrl.resolveActive}\n * onDismiss={ctrl.dismissActive}\n * />\n * )}\n * </Show>\n * ```\n *\n * ## Re-entrance policy\n *\n * If a new `showChatPrompt` arrives while a previous Promise is still\n * pending, the previous Promise rejects **synchronously** with a\n * `PromptReplacedError` before the new prompt is installed. Callers that\n * care can branch on `err instanceof PromptReplacedError` or `err.name ===\n * 'PromptReplacedError'`.\n *\n * ## Abort semantics\n *\n * `handle(config, signal?)` honours `AbortSignal` :\n *\n * - If `signal.aborted === true` on entry → returns a rejected Promise with\n * `new DOMException('Prompt aborted', 'AbortError')`, does NOT set\n * `activePrompt`.\n * - Otherwise registers a once-only listener that rejects with the same\n * `DOMException` on abort, clearing the active state.\n *\n * `AbortError` is the Web Platform convention (matches `fetch()`,\n * `Response.body.cancel()`, etc.) — callers can branch on `err.name ===\n * 'AbortError'` without importing any mcp-ui type.\n */\n\nimport { createSignal, type Accessor } from 'solid-js'\nimport type { ChatPromptConfig, ChatPromptResponse } from '../types/chat-bus'\n\n// ─── Error class ─────────────────────────────────────────────\n\n/**\n * Thrown when an active `showChatPrompt` Promise is rejected because a new\n * prompt arrived before the previous one resolved. Consumers can use\n * `instanceof PromptReplacedError` or `err.name === 'PromptReplacedError'` to\n * branch (retry, bail, log).\n *\n * @experimental\n * @since v5.2.0\n */\nexport class PromptReplacedError extends Error {\n readonly name = 'PromptReplacedError' as const\n constructor(message = 'Prompt replaced by a newer one') {\n super(message)\n }\n}\n\n// ─── Controller shape ────────────────────────────────────────\n\nexport interface ChatPromptController {\n /**\n * Register as the bus handler :\n * `bus.commands.handle('showChatPrompt', ctrl.handle)`\n */\n handle: (config: ChatPromptConfig, signal?: AbortSignal) => Promise<ChatPromptResponse>\n\n /**\n * Reactive accessor for the currently active prompt config (null when no\n * prompt is pending). Use in JSX to drive `<ChatPrompt>` rendering.\n */\n activePrompt: Accessor<ChatPromptConfig | null>\n\n /** Call this from `<ChatPrompt>`'s `onSubmit` prop. */\n resolveActive: (response: ChatPromptResponse) => void\n\n /** Call this from `<ChatPrompt>`'s `onDismiss` prop. */\n dismissActive: () => void\n\n /**\n * Cancel the active prompt programmatically (e.g. on route change). Rejects\n * the pending Promise with the supplied reason or an `AbortError`.\n */\n abort: (reason?: string) => void\n}\n\n// ─── Factory ─────────────────────────────────────────────────\n\n/**\n * Create a stateful controller that owns the active prompt Promise, the\n * AbortSignal listener, and the re-entrance policy. See module JSDoc for\n * full usage.\n *\n * @experimental\n * @since v5.2.0\n */\nexport function createChatPromptController(): ChatPromptController {\n const [activePrompt, setActivePrompt] = createSignal<ChatPromptConfig | null>(null)\n\n interface PendingEntry {\n type: ChatPromptConfig['type']\n resolve: (r: ChatPromptResponse) => void\n reject: (err: unknown) => void\n signal?: AbortSignal\n onAbort?: () => void\n }\n\n let pending: PendingEntry | null = null\n\n function cleanupAbort(entry: PendingEntry): void {\n if (entry.signal && entry.onAbort) {\n entry.signal.removeEventListener('abort', entry.onAbort)\n }\n }\n\n function clearPending(): void {\n if (pending) {\n cleanupAbort(pending)\n pending = null\n }\n setActivePrompt(null)\n }\n\n function handle(\n config: ChatPromptConfig,\n signal?: AbortSignal\n ): Promise<ChatPromptResponse> {\n // Re-entrance : synchronously reject the previous Promise before\n // installing the new prompt. The caller's .catch sees the rejection\n // on the microtask boundary regardless.\n if (pending) {\n const previous = pending\n pending = null\n cleanupAbort(previous)\n previous.reject(new PromptReplacedError())\n }\n\n // Abort already tripped on entry : return a rejected Promise without\n // ever showing the UI.\n if (signal?.aborted) {\n setActivePrompt(null)\n return Promise.reject(new DOMException('Prompt aborted', 'AbortError'))\n }\n\n return new Promise<ChatPromptResponse>((resolve, reject) => {\n const entry: PendingEntry = { type: config.type, resolve, reject, signal }\n\n if (signal) {\n entry.onAbort = () => {\n // If this entry is still active, reject + clear. If a newer prompt\n // has since replaced it, the cleanup already ran — no-op.\n if (pending === entry) {\n pending = null\n cleanupAbort(entry)\n setActivePrompt(null)\n reject(new DOMException('Prompt aborted', 'AbortError'))\n }\n }\n signal.addEventListener('abort', entry.onAbort, { once: true })\n }\n\n pending = entry\n setActivePrompt(config)\n })\n }\n\n function resolveActive(response: ChatPromptResponse): void {\n if (!pending) return\n const entry = pending\n pending = null\n cleanupAbort(entry)\n setActivePrompt(null)\n entry.resolve(response)\n }\n\n function dismissActive(): void {\n if (!pending) return\n const entry = pending\n pending = null\n cleanupAbort(entry)\n setActivePrompt(null)\n // Surface as a resolved Promise with dismissed: true — matches existing\n // ChatPrompt onDismiss contract from v4.x.\n entry.resolve({ type: entry.type, value: '', label: '', dismissed: true })\n }\n\n function abort(reason = 'Prompt aborted'): void {\n if (!pending) return\n const entry = pending\n pending = null\n cleanupAbort(entry)\n setActivePrompt(null)\n entry.reject(new DOMException(reason, 'AbortError'))\n }\n\n return {\n handle,\n activePrompt,\n resolveActive,\n dismissActive,\n abort,\n }\n}\n"],"names":[],"mappings":";;;;AA8DO,MAAM,4BAA4B,MAAM;AAAA,EAE7C,YAAY,UAAU,kCAAkC;AACtD,UAAM,OAAO;AAFN,gCAAO;AAAA,EAGhB;AACF;AAwCO,SAAS,6BAAmD;AACjE,QAAM,CAAC,cAAc,eAAe,IAAI,aAAsC,IAAI;AAUlF,MAAI,UAA+B;AAEnC,WAAS,aAAa,OAA2B;AAC/C,QAAI,MAAM,UAAU,MAAM,SAAS;AACjC,YAAM,OAAO,oBAAoB,SAAS,MAAM,OAAO;AAAA,IACzD;AAAA,EACF;AAUA,WAAS,OACP,QACA,QAC6B;AAI7B,QAAI,SAAS;AACX,YAAM,WAAW;AACjB,gBAAU;AACV,mBAAa,QAAQ;AACrB,eAAS,OAAO,IAAI,qBAAqB;AAAA,IAC3C;AAIA,QAAI,iCAAQ,SAAS;AACnB,sBAAgB,IAAI;AACpB,aAAO,QAAQ,OAAO,IAAI,aAAa,kBAAkB,YAAY,CAAC;AAAA,IACxE;AAEA,WAAO,IAAI,QAA4B,CAAC,SAAS,WAAW;AAC1D,YAAM,QAAsB,EAAE,MAAM,OAAO,MAAM,SAAS,QAAQ,OAAA;AAElE,UAAI,QAAQ;AACV,cAAM,UAAU,MAAM;AAGpB,cAAI,YAAY,OAAO;AACrB,sBAAU;AACV,yBAAa,KAAK;AAClB,4BAAgB,IAAI;AACpB,mBAAO,IAAI,aAAa,kBAAkB,YAAY,CAAC;AAAA,UACzD;AAAA,QACF;AACA,eAAO,iBAAiB,SAAS,MAAM,SAAS,EAAE,MAAM,MAAM;AAAA,MAChE;AAEA,gBAAU;AACV,sBAAgB,MAAM;AAAA,IACxB,CAAC;AAAA,EACH;AAEA,WAAS,cAAc,UAAoC;AACzD,QAAI,CAAC,QAAS;AACd,UAAM,QAAQ;AACd,cAAU;AACV,iBAAa,KAAK;AAClB,oBAAgB,IAAI;AACpB,UAAM,QAAQ,QAAQ;AAAA,EACxB;AAEA,WAAS,gBAAsB;AAC7B,QAAI,CAAC,QAAS;AACd,UAAM,QAAQ;AACd,cAAU;AACV,iBAAa,KAAK;AAClB,oBAAgB,IAAI;AAGpB,UAAM,QAAQ,EAAE,MAAM,MAAM,MAAM,OAAO,IAAI,OAAO,IAAI,WAAW,KAAA,CAAM;AAAA,EAC3E;AAEA,WAAS,MAAM,SAAS,kBAAwB;AAC9C,QAAI,CAAC,QAAS;AACd,UAAM,QAAQ;AACd,cAAU;AACV,iBAAa,KAAK;AAClB,oBAAgB,IAAI;AACpB,UAAM,OAAO,IAAI,aAAa,QAAQ,YAAY,CAAC;AAAA,EACrD;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
@@ -1,90 +1,118 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const web = require("solid-js/web");
4
+ const solidJs = require("solid-js");
3
5
  const store = require("solid-js/store");
4
- const [scratchpadStore, setScratchpadStore] = store.createStore({ current: null, pinned: false });
5
- function useScratchpadState() {
6
+ function createScratchpadStore() {
7
+ const [scratchpadStore, setScratchpadStore] = store.createStore({
8
+ current: null,
9
+ pinned: false
10
+ });
11
+ const dispatch = (event) => {
12
+ var _a, _b;
13
+ if (event.action === "create") {
14
+ console.info(`%c[MCP-UI] dispatchScratchpad%c create id=${event.id} sections=${((_a = event.sections) == null ? void 0 : _a.length) || 0} status=${event.status || "loading"}${event.pinned ? " pinned" : ""}`, "color: #10b981; font-weight: bold", "color: inherit");
15
+ setScratchpadStore({
16
+ current: {
17
+ id: event.id,
18
+ title: event.title || "",
19
+ sections: event.sections || [],
20
+ filters: event.filters || {},
21
+ preview: event.preview,
22
+ agentMessages: event.agentMessages || [],
23
+ status: event.status || "loading",
24
+ previewEndpoint: event.previewEndpoint,
25
+ previewDebounce: event.previewDebounce,
26
+ previewMethod: event.previewMethod,
27
+ previewHeaders: event.previewHeaders,
28
+ turn: event.turn,
29
+ totalTurns: event.totalTurns,
30
+ turnHistory: event.turnHistory
31
+ },
32
+ pinned: event.pinned || false
33
+ });
34
+ } else if (event.action === "update") {
35
+ console.info(`%c[MCP-UI] dispatchScratchpad%c update id=${event.id} sectionMode=${event.sectionMode || "replace"} sections=${((_b = event.sections) == null ? void 0 : _b.length) || 0} status=${event.status || "-"}`, "color: #3b82f6; font-weight: bold", "color: inherit");
36
+ setScratchpadStore(store.produce((s) => {
37
+ var _a2;
38
+ if (!s.current || s.current.id !== event.id) {
39
+ console.warn(`[MCP-UI] dispatchScratchpad: update for id=${event.id} but current is ${((_a2 = s.current) == null ? void 0 : _a2.id) || "null"}. Ignoring.`);
40
+ return;
41
+ }
42
+ if (event.sections) {
43
+ const mode = event.sectionMode || "replace";
44
+ if (mode === "replace") {
45
+ s.current.sections = event.sections;
46
+ } else if (mode === "append") {
47
+ s.current.sections = [...s.current.sections, ...event.sections];
48
+ } else if (mode === "upsert") {
49
+ let matchCount = 0;
50
+ for (const incoming of event.sections) {
51
+ const idx = s.current.sections.findIndex((sec) => sec.id === incoming.id);
52
+ if (idx >= 0) {
53
+ s.current.sections[idx] = incoming;
54
+ matchCount++;
55
+ } else {
56
+ s.current.sections.push(incoming);
57
+ }
58
+ }
59
+ if (matchCount === 0 && event.sections.length > 0) {
60
+ console.warn(`[MCP-UI] dispatchScratchpad: sectionMode='upsert' but no IDs matched. Incoming: [${event.sections.map((s2) => s2.id).join(", ")}] Existing: [${s.current.sections.map((s2) => s2.id).join(", ")}]. All appended.`);
61
+ }
62
+ }
63
+ }
64
+ if (event.agentMessages) s.current.agentMessages = event.agentMessages;
65
+ if (event.status) s.current.status = event.status;
66
+ if (event.filters) s.current.filters = event.filters;
67
+ if (event.preview) s.current.preview = event.preview;
68
+ if (event.pinned != null) s.pinned = event.pinned;
69
+ if (event.turnHistory) s.current.turnHistory = event.turnHistory;
70
+ if (event.turn != null) s.current.turn = event.turn;
71
+ }));
72
+ } else if (event.action === "close") {
73
+ console.info(`%c[MCP-UI] dispatchScratchpad%c close id=${event.id}`, "color: #6b7280; font-weight: bold", "color: inherit");
74
+ setScratchpadStore({
75
+ current: null,
76
+ pinned: false
77
+ });
78
+ }
79
+ };
6
80
  return {
81
+ dispatch,
7
82
  state: () => scratchpadStore.current,
8
83
  pinned: () => scratchpadStore.pinned,
9
- close: () => setScratchpadStore({ current: null, pinned: false })
84
+ close: () => setScratchpadStore({
85
+ current: null,
86
+ pinned: false
87
+ })
10
88
  };
11
89
  }
90
+ const defaultStore = createScratchpadStore();
12
91
  function dispatchScratchpad(event) {
13
- var _a, _b;
14
- if (event.action === "create") {
15
- console.info(
16
- `%c[MCP-UI] dispatchScratchpad%c create id=${event.id} sections=${((_a = event.sections) == null ? void 0 : _a.length) || 0} status=${event.status || "loading"}${event.pinned ? " pinned" : ""}`,
17
- "color: #10b981; font-weight: bold",
18
- "color: inherit"
19
- );
20
- setScratchpadStore({
21
- current: {
22
- id: event.id,
23
- title: event.title || "",
24
- sections: event.sections || [],
25
- filters: event.filters || {},
26
- preview: event.preview,
27
- agentMessages: event.agentMessages || [],
28
- status: event.status || "loading",
29
- previewEndpoint: event.previewEndpoint,
30
- previewDebounce: event.previewDebounce,
31
- previewMethod: event.previewMethod,
32
- previewHeaders: event.previewHeaders,
33
- turn: event.turn,
34
- totalTurns: event.totalTurns,
35
- turnHistory: event.turnHistory
36
- },
37
- pinned: event.pinned || false
38
- });
39
- } else if (event.action === "update") {
40
- console.info(
41
- `%c[MCP-UI] dispatchScratchpad%c update id=${event.id} sectionMode=${event.sectionMode || "replace"} sections=${((_b = event.sections) == null ? void 0 : _b.length) || 0} status=${event.status || "-"}`,
42
- "color: #3b82f6; font-weight: bold",
43
- "color: inherit"
44
- );
45
- setScratchpadStore(store.produce((s) => {
46
- var _a2;
47
- if (!s.current || s.current.id !== event.id) {
48
- console.warn(`[MCP-UI] dispatchScratchpad: update for id=${event.id} but current is ${((_a2 = s.current) == null ? void 0 : _a2.id) || "null"}. Ignoring.`);
49
- return;
50
- }
51
- if (event.sections) {
52
- const mode = event.sectionMode || "replace";
53
- if (mode === "replace") {
54
- s.current.sections = event.sections;
55
- } else if (mode === "append") {
56
- s.current.sections = [...s.current.sections, ...event.sections];
57
- } else if (mode === "upsert") {
58
- let matchCount = 0;
59
- for (const incoming of event.sections) {
60
- const idx = s.current.sections.findIndex((sec) => sec.id === incoming.id);
61
- if (idx >= 0) {
62
- s.current.sections[idx] = incoming;
63
- matchCount++;
64
- } else {
65
- s.current.sections.push(incoming);
66
- }
67
- }
68
- if (matchCount === 0 && event.sections.length > 0) {
69
- console.warn(
70
- `[MCP-UI] dispatchScratchpad: sectionMode='upsert' but no IDs matched. Incoming: [${event.sections.map((s2) => s2.id).join(", ")}] Existing: [${s.current.sections.map((s2) => s2.id).join(", ")}]. All appended.`
71
- );
72
- }
73
- }
74
- }
75
- if (event.agentMessages) s.current.agentMessages = event.agentMessages;
76
- if (event.status) s.current.status = event.status;
77
- if (event.filters) s.current.filters = event.filters;
78
- if (event.preview) s.current.preview = event.preview;
79
- if (event.pinned != null) s.pinned = event.pinned;
80
- if (event.turnHistory) s.current.turnHistory = event.turnHistory;
81
- if (event.turn != null) s.current.turn = event.turn;
82
- }));
83
- } else if (event.action === "close") {
84
- console.info(`%c[MCP-UI] dispatchScratchpad%c close id=${event.id}`, "color: #6b7280; font-weight: bold", "color: inherit");
85
- setScratchpadStore({ current: null, pinned: false });
86
- }
92
+ defaultStore.dispatch(event);
93
+ }
94
+ const ScratchpadStoreContext = solidJs.createContext(void 0);
95
+ const ScratchpadStoreProvider = (props) => {
96
+ const store2 = props.store ?? createScratchpadStore();
97
+ return web.createComponent(ScratchpadStoreContext.Provider, {
98
+ value: store2,
99
+ get children() {
100
+ return props.children;
101
+ }
102
+ });
103
+ };
104
+ function useScratchpadState() {
105
+ const scoped = solidJs.useContext(ScratchpadStoreContext);
106
+ const handle = scoped ?? defaultStore;
107
+ return {
108
+ state: handle.state,
109
+ pinned: handle.pinned,
110
+ close: handle.close
111
+ };
87
112
  }
113
+ exports.ScratchpadStoreContext = ScratchpadStoreContext;
114
+ exports.ScratchpadStoreProvider = ScratchpadStoreProvider;
115
+ exports.createScratchpadStore = createScratchpadStore;
88
116
  exports.dispatchScratchpad = dispatchScratchpad;
89
117
  exports.useScratchpadState = useScratchpadState;
90
118
  //# sourceMappingURL=scratchpad-store.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"scratchpad-store.cjs","sources":["../../src/stores/scratchpad-store.ts"],"sourcesContent":["/**\n * Scratchpad Store — singleton reactive state for HITL scratchpad\n * v3.0.3: Eliminates ChatBus relay chain race condition\n *\n * @experimental\n *\n * Parser calls dispatchScratchpad() → store updates → ScratchpadPanel reads reactively.\n * Zero bus, zero relay, zero race condition.\n *\n * **Known limitation (v4.3.9):** This store is a module-level singleton, not\n * a context-scoped factory. Two `ScratchpadPanel` instances in the same app\n * will share the same state. Multi-panel scenarios (e.g. chat + admin dashboard\n * both showing scratchpads simultaneously) are unsupported. Host apps that need\n * isolated scratchpads should not reuse this store — wait for v4.4.0 which\n * will expose `createScratchpadStore()` factory for per-panel instances.\n */\n\nimport { createStore, produce } from 'solid-js/store'\nimport type { ScratchpadState, ScratchpadEvent, ScratchpadSection } from '../types/chat-bus'\n\nconst [scratchpadStore, setScratchpadStore] = createStore<{\n current: ScratchpadState | null\n pinned: boolean\n}>({ current: null, pinned: false })\n\n/**\n * Hook for the COMPONENT — reads the scratchpad state reactively.\n *\n * @example\n * const { state, pinned, close } = useScratchpadState()\n * <Show when={state()}>\n * <ScratchpadPanel state={state()!} pinned={pinned()} onClose={close} />\n * </Show>\n */\nexport function useScratchpadState() {\n return {\n state: () => scratchpadStore.current,\n pinned: () => scratchpadStore.pinned,\n close: () => setScratchpadStore({ current: null, pinned: false }),\n }\n}\n\n/**\n * Function for the PARSER/STORE — mutates the scratchpad state.\n * Called from the SSE callback, no bus needed.\n *\n * @example\n * // In your SSE parser callback — ONE LINE\n * onScratchpad: (data) => dispatchScratchpad(data as ScratchpadEvent)\n */\nexport function dispatchScratchpad(event: ScratchpadEvent): void {\n // DX1: lifecycle logging\n if (event.action === 'create') {\n console.info(\n `%c[MCP-UI] dispatchScratchpad%c create id=${event.id} sections=${event.sections?.length || 0} status=${event.status || 'loading'}${event.pinned ? ' pinned' : ''}`,\n 'color: #10b981; font-weight: bold', 'color: inherit'\n )\n setScratchpadStore({\n current: {\n id: event.id,\n title: event.title || '',\n sections: event.sections || [],\n filters: event.filters || {},\n preview: event.preview,\n agentMessages: event.agentMessages || [],\n status: event.status || 'loading',\n previewEndpoint: (event as any).previewEndpoint,\n previewDebounce: (event as any).previewDebounce,\n previewMethod: (event as any).previewMethod,\n previewHeaders: (event as any).previewHeaders,\n turn: (event as any).turn,\n totalTurns: (event as any).totalTurns,\n turnHistory: (event as any).turnHistory,\n },\n pinned: event.pinned || false,\n })\n } else if (event.action === 'update') {\n console.info(\n `%c[MCP-UI] dispatchScratchpad%c update id=${event.id} sectionMode=${event.sectionMode || 'replace'} sections=${event.sections?.length || 0} status=${event.status || '-'}`,\n 'color: #3b82f6; font-weight: bold', 'color: inherit'\n )\n setScratchpadStore(produce((s) => {\n if (!s.current || s.current.id !== event.id) {\n console.warn(`[MCP-UI] dispatchScratchpad: update for id=${event.id} but current is ${s.current?.id || 'null'}. Ignoring.`)\n return\n }\n\n if (event.sections) {\n const mode = event.sectionMode || 'replace'\n if (mode === 'replace') {\n s.current.sections = event.sections\n } else if (mode === 'append') {\n s.current.sections = [...s.current.sections, ...event.sections]\n } else if (mode === 'upsert') {\n let matchCount = 0\n for (const incoming of event.sections) {\n const idx = s.current.sections.findIndex((sec: ScratchpadSection) => sec.id === incoming.id)\n if (idx >= 0) {\n s.current.sections[idx] = incoming\n matchCount++\n } else {\n s.current.sections.push(incoming)\n }\n }\n if (matchCount === 0 && event.sections.length > 0) {\n console.warn(\n `[MCP-UI] dispatchScratchpad: sectionMode='upsert' but no IDs matched. ` +\n `Incoming: [${event.sections.map((s: ScratchpadSection) => s.id).join(', ')}] ` +\n `Existing: [${s.current.sections.map((s: ScratchpadSection) => s.id).join(', ')}]. All appended.`\n )\n }\n }\n }\n if (event.agentMessages) s.current.agentMessages = event.agentMessages\n if (event.status) s.current.status = event.status\n if (event.filters) s.current.filters = event.filters\n if (event.preview) s.current.preview = event.preview\n if (event.pinned != null) s.pinned = event.pinned\n if ((event as any).turnHistory) s.current.turnHistory = (event as any).turnHistory\n if ((event as any).turn != null) s.current.turn = (event as any).turn\n }))\n } else if (event.action === 'close') {\n console.info(`%c[MCP-UI] dispatchScratchpad%c close id=${event.id}`, 'color: #6b7280; font-weight: bold', 'color: inherit')\n setScratchpadStore({ current: null, pinned: false })\n }\n}\n"],"names":["createStore","produce","_a","s"],"mappings":"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"}
1
+ {"version":3,"file":"scratchpad-store.cjs","sources":["../../src/stores/scratchpad-store.tsx"],"sourcesContent":["/**\n * Scratchpad Store — reactive state for HITL scratchpad\n *\n * @experimental\n *\n * **v5.2.0 :** the store is now a factory (`createScratchpadStore()`) with a\n * module-level singleton kept as default. Two consumption modes :\n *\n * 1. **Singleton mode (default, zero-config)** — `dispatchScratchpad(event)` +\n * `useScratchpadState()` read/write the module singleton. This is the v4.x\n * path and keeps working unchanged.\n *\n * 2. **Multi-instance mode** — wrap a subtree in `<ScratchpadStoreProvider>`\n * (it creates a scoped `ScratchpadStoreHandle` internally, or use your own\n * via the `store` prop). `useScratchpadState()` auto-detects the context\n * and reads from it; `ScratchpadPanel` mounted inside the provider reads\n * the scoped store. Non-reactive callers (SSE parsers) should pass the\n * handle explicitly — do NOT try to reach context from a non-reactive\n * scope.\n */\n\nimport { createContext, useContext, type ParentComponent, type JSX } from 'solid-js'\nimport { createStore, produce } from 'solid-js/store'\nimport type { ScratchpadState, ScratchpadEvent, ScratchpadSection } from '../types/chat-bus'\n\n// ─── Handle shape ─────────────────────────────────────────────\n\nexport interface ScratchpadStoreHandle {\n /** Mutate the store from an SSE/parser callback. */\n dispatch: (event: ScratchpadEvent) => void\n /** Reactive accessor for the current scratchpad state (null when closed). */\n state: () => ScratchpadState | null\n /** Reactive accessor for the pinned flag. */\n pinned: () => boolean\n /** Close the scratchpad (equivalent to dispatching an action='close'). */\n close: () => void\n}\n\n// ─── Factory ──────────────────────────────────────────────────\n\n/**\n * Create an isolated scratchpad store instance.\n *\n * Use this when you need two or more scratchpads live at the same time\n * (e.g. chat scratchpad + admin dashboard scratchpad). Pair with\n * `<ScratchpadStoreProvider store={...}>` to scope a SolidJS subtree.\n *\n * @experimental\n * @since v5.2.0\n */\nexport function createScratchpadStore(): ScratchpadStoreHandle {\n const [scratchpadStore, setScratchpadStore] = createStore<{\n current: ScratchpadState | null\n pinned: boolean\n }>({ current: null, pinned: false })\n\n const dispatch = (event: ScratchpadEvent): void => {\n if (event.action === 'create') {\n console.info(\n `%c[MCP-UI] dispatchScratchpad%c create id=${event.id} sections=${event.sections?.length || 0} status=${event.status || 'loading'}${event.pinned ? ' pinned' : ''}`,\n 'color: #10b981; font-weight: bold',\n 'color: inherit'\n )\n setScratchpadStore({\n current: {\n id: event.id,\n title: event.title || '',\n sections: event.sections || [],\n filters: event.filters || {},\n preview: event.preview,\n agentMessages: event.agentMessages || [],\n status: event.status || 'loading',\n previewEndpoint: (event as any).previewEndpoint,\n previewDebounce: (event as any).previewDebounce,\n previewMethod: (event as any).previewMethod,\n previewHeaders: (event as any).previewHeaders,\n turn: (event as any).turn,\n totalTurns: (event as any).totalTurns,\n turnHistory: (event as any).turnHistory,\n },\n pinned: event.pinned || false,\n })\n } else if (event.action === 'update') {\n console.info(\n `%c[MCP-UI] dispatchScratchpad%c update id=${event.id} sectionMode=${event.sectionMode || 'replace'} sections=${event.sections?.length || 0} status=${event.status || '-'}`,\n 'color: #3b82f6; font-weight: bold',\n 'color: inherit'\n )\n setScratchpadStore(\n produce((s) => {\n if (!s.current || s.current.id !== event.id) {\n console.warn(\n `[MCP-UI] dispatchScratchpad: update for id=${event.id} but current is ${s.current?.id || 'null'}. Ignoring.`\n )\n return\n }\n\n if (event.sections) {\n const mode = event.sectionMode || 'replace'\n if (mode === 'replace') {\n s.current.sections = event.sections\n } else if (mode === 'append') {\n s.current.sections = [...s.current.sections, ...event.sections]\n } else if (mode === 'upsert') {\n let matchCount = 0\n for (const incoming of event.sections) {\n const idx = s.current.sections.findIndex(\n (sec: ScratchpadSection) => sec.id === incoming.id\n )\n if (idx >= 0) {\n s.current.sections[idx] = incoming\n matchCount++\n } else {\n s.current.sections.push(incoming)\n }\n }\n if (matchCount === 0 && event.sections.length > 0) {\n console.warn(\n `[MCP-UI] dispatchScratchpad: sectionMode='upsert' but no IDs matched. ` +\n `Incoming: [${event.sections.map((s: ScratchpadSection) => s.id).join(', ')}] ` +\n `Existing: [${s.current.sections.map((s: ScratchpadSection) => s.id).join(', ')}]. All appended.`\n )\n }\n }\n }\n if (event.agentMessages) s.current.agentMessages = event.agentMessages\n if (event.status) s.current.status = event.status\n if (event.filters) s.current.filters = event.filters\n if (event.preview) s.current.preview = event.preview\n if (event.pinned != null) s.pinned = event.pinned\n if ((event as any).turnHistory) s.current.turnHistory = (event as any).turnHistory\n if ((event as any).turn != null) s.current.turn = (event as any).turn\n })\n )\n } else if (event.action === 'close') {\n console.info(\n `%c[MCP-UI] dispatchScratchpad%c close id=${event.id}`,\n 'color: #6b7280; font-weight: bold',\n 'color: inherit'\n )\n setScratchpadStore({ current: null, pinned: false })\n }\n }\n\n return {\n dispatch,\n state: () => scratchpadStore.current,\n pinned: () => scratchpadStore.pinned,\n close: () => setScratchpadStore({ current: null, pinned: false }),\n }\n}\n\n// ─── Module-level singleton (v4.x-compatible default) ─────────\n\nconst defaultStore: ScratchpadStoreHandle = createScratchpadStore()\n\n/**\n * Function for the PARSER/STORE — mutates the **module-level singleton**\n * scratchpad state. Use this when you only need one scratchpad at a time\n * (single-instance consumer, the v4.x pattern).\n *\n * For multi-instance scenarios, prefer `createScratchpadStore()` and pass the\n * handle around explicitly.\n *\n * @example\n * // In your SSE parser callback — ONE LINE\n * onScratchpad: (data) => dispatchScratchpad(data as ScratchpadEvent)\n */\nexport function dispatchScratchpad(event: ScratchpadEvent): void {\n defaultStore.dispatch(event)\n}\n\n// ─── Context (v5.2.0) ─────────────────────────────────────────\n\n/**\n * Context for a scoped scratchpad store. Populated by\n * `<ScratchpadStoreProvider>`. Read by `useScratchpadState()` with automatic\n * fallback to the module-level singleton when the context is absent.\n *\n * @experimental\n * @since v5.2.0\n */\nexport const ScratchpadStoreContext = createContext<ScratchpadStoreHandle | undefined>(undefined)\n\n/**\n * Provide a scoped `ScratchpadStoreHandle` to a SolidJS subtree. Children\n * reading via `useScratchpadState()` or rendering a `<ScratchpadPanel>` will\n * bind to this store instead of the module singleton.\n *\n * If no `store` prop is passed, a fresh store is created for the provider's\n * lifetime. Pass `store` explicitly when you need the handle outside the\n * tree (e.g. in an SSE parser that lives at the app root).\n *\n * @experimental\n * @since v5.2.0\n *\n * @example\n * const chatStore = createScratchpadStore()\n * const adminStore = createScratchpadStore()\n *\n * <ScratchpadStoreProvider store={chatStore}>\n * <ChatInterface />\n * </ScratchpadStoreProvider>\n * <ScratchpadStoreProvider store={adminStore}>\n * <AdminDashboard />\n * </ScratchpadStoreProvider>\n */\nexport const ScratchpadStoreProvider: ParentComponent<{\n store?: ScratchpadStoreHandle\n}> = (props): JSX.Element => {\n const store = props.store ?? createScratchpadStore()\n return (\n <ScratchpadStoreContext.Provider value={store}>{props.children}</ScratchpadStoreContext.Provider>\n )\n}\n\n// ─── Reactive hook (context-aware) ────────────────────────────\n\n/**\n * Hook for the COMPONENT — reads the scratchpad state reactively.\n *\n * **v5.2.0 :** if called inside a `<ScratchpadStoreProvider>`, reads the\n * scoped handle; otherwise falls back to the module singleton. Old v4.x\n * consumers keep working unchanged.\n *\n * @example\n * const { state, pinned, close } = useScratchpadState()\n * <Show when={state()}>\n * <ScratchpadPanel state={state()!} pinned={pinned()} onClose={close} />\n * </Show>\n */\nexport function useScratchpadState(): {\n state: () => ScratchpadState | null\n pinned: () => boolean\n close: () => void\n} {\n const scoped = useContext(ScratchpadStoreContext)\n const handle = scoped ?? defaultStore\n return {\n state: handle.state,\n pinned: handle.pinned,\n close: handle.close,\n }\n}\n"],"names":["createScratchpadStore","scratchpadStore","setScratchpadStore","createStore","current","pinned","dispatch","event","action","console","info","id","sections","length","status","title","filters","preview","agentMessages","previewEndpoint","previewDebounce","previewMethod","previewHeaders","turn","totalTurns","turnHistory","sectionMode","produce","s","warn","mode","matchCount","incoming","idx","findIndex","sec","push","map","join","state","close","defaultStore","dispatchScratchpad","ScratchpadStoreContext","createContext","undefined","ScratchpadStoreProvider","props","store","_$createComponent","Provider","value","children","useScratchpadState","scoped","useContext","handle"],"mappings":"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"}