@seed-ship/mcp-ui-solid 3.0.1 → 3.0.3

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 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ import { ComponentToolbar } from "./components/ComponentToolbar.js";
22
22
  import { ChatBusProvider, useChatBus } from "./hooks/useChatBus.js";
23
23
  import { ChatPrompt } from "./components/ChatPrompt.js";
24
24
  import { ScratchpadPanel } from "./components/ScratchpadPanel.js";
25
+ import { dispatchScratchpad, useScratchpadState } from "./stores/scratchpad-store.js";
25
26
  import { GhostText, GhostTextInput } from "./components/GhostText.js";
26
27
  import { AutocompleteDropdown } from "./components/AutocompleteDropdown.js";
27
28
  import { AutocompleteFormField } from "./components/AutocompleteFormField.js";
@@ -70,6 +71,7 @@ export {
70
71
  createGroqPlugin,
71
72
  createRestPlugin,
72
73
  createSupabasePlugin,
74
+ dispatchScratchpad,
73
75
  evaluateCondition,
74
76
  getIframeSandbox,
75
77
  mergeScratchpadSections,
@@ -86,6 +88,7 @@ export {
86
88
  useMCPActionSafe,
87
89
  useModal,
88
90
  useResize,
91
+ useScratchpadState,
89
92
  useStreamingUI,
90
93
  useToolAction,
91
94
  validateComponent,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const store = require("solid-js/store");
4
+ const [scratchpadStore, setScratchpadStore] = store.createStore({ current: null, pinned: false });
5
+ function useScratchpadState() {
6
+ return {
7
+ state: () => scratchpadStore.current,
8
+ pinned: () => scratchpadStore.pinned,
9
+ close: () => setScratchpadStore({ current: null, pinned: false })
10
+ };
11
+ }
12
+ 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
+ }
87
+ }
88
+ exports.dispatchScratchpad = dispatchScratchpad;
89
+ exports.useScratchpadState = useScratchpadState;
90
+ //# sourceMappingURL=scratchpad-store.cjs.map
@@ -0,0 +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\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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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Scratchpad Store — singleton reactive state for HITL scratchpad
3
+ * v3.0.3: Eliminates ChatBus relay chain race condition
4
+ *
5
+ * @experimental
6
+ *
7
+ * Parser calls dispatchScratchpad() → store updates → ScratchpadPanel reads reactively.
8
+ * Zero bus, zero relay, zero race condition.
9
+ */
10
+ import type { ScratchpadState, ScratchpadEvent } from '../types/chat-bus';
11
+ /**
12
+ * Hook for the COMPONENT — reads the scratchpad state reactively.
13
+ *
14
+ * @example
15
+ * const { state, pinned, close } = useScratchpadState()
16
+ * <Show when={state()}>
17
+ * <ScratchpadPanel state={state()!} pinned={pinned()} onClose={close} />
18
+ * </Show>
19
+ */
20
+ export declare function useScratchpadState(): {
21
+ state: () => ScratchpadState | null;
22
+ pinned: () => boolean;
23
+ close: () => void;
24
+ };
25
+ /**
26
+ * Function for the PARSER/STORE — mutates the scratchpad state.
27
+ * Called from the SSE callback, no bus needed.
28
+ *
29
+ * @example
30
+ * // In your SSE parser callback — ONE LINE
31
+ * onScratchpad: (data) => dispatchScratchpad(data as ScratchpadEvent)
32
+ */
33
+ export declare function dispatchScratchpad(event: ScratchpadEvent): void;
34
+ //# sourceMappingURL=scratchpad-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scratchpad-store.d.ts","sourceRoot":"","sources":["../../src/stores/scratchpad-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAqB,MAAM,mBAAmB,CAAA;AAO5F;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB;;;;EAMjC;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CA2E/D"}
@@ -0,0 +1,90 @@
1
+ import { createStore, produce } from "solid-js/store";
2
+ const [scratchpadStore, setScratchpadStore] = createStore({ current: null, pinned: false });
3
+ function useScratchpadState() {
4
+ return {
5
+ state: () => scratchpadStore.current,
6
+ pinned: () => scratchpadStore.pinned,
7
+ close: () => setScratchpadStore({ current: null, pinned: false })
8
+ };
9
+ }
10
+ function dispatchScratchpad(event) {
11
+ var _a, _b;
12
+ if (event.action === "create") {
13
+ console.info(
14
+ `%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" : ""}`,
15
+ "color: #10b981; font-weight: bold",
16
+ "color: inherit"
17
+ );
18
+ setScratchpadStore({
19
+ current: {
20
+ id: event.id,
21
+ title: event.title || "",
22
+ sections: event.sections || [],
23
+ filters: event.filters || {},
24
+ preview: event.preview,
25
+ agentMessages: event.agentMessages || [],
26
+ status: event.status || "loading",
27
+ previewEndpoint: event.previewEndpoint,
28
+ previewDebounce: event.previewDebounce,
29
+ previewMethod: event.previewMethod,
30
+ previewHeaders: event.previewHeaders,
31
+ turn: event.turn,
32
+ totalTurns: event.totalTurns,
33
+ turnHistory: event.turnHistory
34
+ },
35
+ pinned: event.pinned || false
36
+ });
37
+ } else if (event.action === "update") {
38
+ console.info(
39
+ `%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 || "-"}`,
40
+ "color: #3b82f6; font-weight: bold",
41
+ "color: inherit"
42
+ );
43
+ setScratchpadStore(produce((s) => {
44
+ var _a2;
45
+ if (!s.current || s.current.id !== event.id) {
46
+ console.warn(`[MCP-UI] dispatchScratchpad: update for id=${event.id} but current is ${((_a2 = s.current) == null ? void 0 : _a2.id) || "null"}. Ignoring.`);
47
+ return;
48
+ }
49
+ if (event.sections) {
50
+ const mode = event.sectionMode || "replace";
51
+ if (mode === "replace") {
52
+ s.current.sections = event.sections;
53
+ } else if (mode === "append") {
54
+ s.current.sections = [...s.current.sections, ...event.sections];
55
+ } else if (mode === "upsert") {
56
+ let matchCount = 0;
57
+ for (const incoming of event.sections) {
58
+ const idx = s.current.sections.findIndex((sec) => sec.id === incoming.id);
59
+ if (idx >= 0) {
60
+ s.current.sections[idx] = incoming;
61
+ matchCount++;
62
+ } else {
63
+ s.current.sections.push(incoming);
64
+ }
65
+ }
66
+ if (matchCount === 0 && event.sections.length > 0) {
67
+ console.warn(
68
+ `[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.`
69
+ );
70
+ }
71
+ }
72
+ }
73
+ if (event.agentMessages) s.current.agentMessages = event.agentMessages;
74
+ if (event.status) s.current.status = event.status;
75
+ if (event.filters) s.current.filters = event.filters;
76
+ if (event.preview) s.current.preview = event.preview;
77
+ if (event.pinned != null) s.pinned = event.pinned;
78
+ if (event.turnHistory) s.current.turnHistory = event.turnHistory;
79
+ if (event.turn != null) s.current.turn = event.turn;
80
+ }));
81
+ } else if (event.action === "close") {
82
+ console.info(`%c[MCP-UI] dispatchScratchpad%c close id=${event.id}`, "color: #6b7280; font-weight: bold", "color: inherit");
83
+ setScratchpadStore({ current: null, pinned: false });
84
+ }
85
+ }
86
+ export {
87
+ dispatchScratchpad,
88
+ useScratchpadState
89
+ };
90
+ //# sourceMappingURL=scratchpad-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scratchpad-store.js","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\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":["_a","s"],"mappings":";AAaA,MAAM,CAAC,iBAAiB,kBAAkB,IAAI,YAG3C,EAAE,SAAS,MAAM,QAAQ,OAAO;AAW5B,SAAS,qBAAqB;AACnC,SAAO;AAAA,IACL,OAAO,MAAM,gBAAgB;AAAA,IAC7B,QAAQ,MAAM,gBAAgB;AAAA,IAC9B,OAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,QAAQ,OAAO;AAAA,EAAA;AAEpE;AAUO,SAAS,mBAAmB,OAA8B;;AAE/D,MAAI,MAAM,WAAW,UAAU;AAC7B,YAAQ;AAAA,MACN,6CAA6C,MAAM,EAAE,eAAa,WAAM,aAAN,mBAAgB,WAAU,CAAC,WAAW,MAAM,UAAU,SAAS,GAAG,MAAM,SAAS,YAAY,EAAE;AAAA,MACjK;AAAA,MAAqC;AAAA,IAAA;AAEvC,uBAAmB;AAAA,MACjB,SAAS;AAAA,QACP,IAAI,MAAM;AAAA,QACV,OAAO,MAAM,SAAS;AAAA,QACtB,UAAU,MAAM,YAAY,CAAA;AAAA,QAC5B,SAAS,MAAM,WAAW,CAAA;AAAA,QAC1B,SAAS,MAAM;AAAA,QACf,eAAe,MAAM,iBAAiB,CAAA;AAAA,QACtC,QAAQ,MAAM,UAAU;AAAA,QACxB,iBAAkB,MAAc;AAAA,QAChC,iBAAkB,MAAc;AAAA,QAChC,eAAgB,MAAc;AAAA,QAC9B,gBAAiB,MAAc;AAAA,QAC/B,MAAO,MAAc;AAAA,QACrB,YAAa,MAAc;AAAA,QAC3B,aAAc,MAAc;AAAA,MAAA;AAAA,MAE9B,QAAQ,MAAM,UAAU;AAAA,IAAA,CACzB;AAAA,EACH,WAAW,MAAM,WAAW,UAAU;AACpC,YAAQ;AAAA,MACN,6CAA6C,MAAM,EAAE,gBAAgB,MAAM,eAAe,SAAS,eAAa,WAAM,aAAN,mBAAgB,WAAU,CAAC,WAAW,MAAM,UAAU,GAAG;AAAA,MACzK;AAAA,MAAqC;AAAA,IAAA;AAEvC,uBAAmB,QAAQ,CAAC,MAAM;;AAChC,UAAI,CAAC,EAAE,WAAW,EAAE,QAAQ,OAAO,MAAM,IAAI;AAC3C,gBAAQ,KAAK,8CAA8C,MAAM,EAAE,qBAAmBA,MAAA,EAAE,YAAF,gBAAAA,IAAW,OAAM,MAAM,aAAa;AAC1H;AAAA,MACF;AAEA,UAAI,MAAM,UAAU;AAClB,cAAM,OAAO,MAAM,eAAe;AAClC,YAAI,SAAS,WAAW;AACtB,YAAE,QAAQ,WAAW,MAAM;AAAA,QAC7B,WAAW,SAAS,UAAU;AAC5B,YAAE,QAAQ,WAAW,CAAC,GAAG,EAAE,QAAQ,UAAU,GAAG,MAAM,QAAQ;AAAA,QAChE,WAAW,SAAS,UAAU;AAC5B,cAAI,aAAa;AACjB,qBAAW,YAAY,MAAM,UAAU;AACrC,kBAAM,MAAM,EAAE,QAAQ,SAAS,UAAU,CAAC,QAA2B,IAAI,OAAO,SAAS,EAAE;AAC3F,gBAAI,OAAO,GAAG;AACZ,gBAAE,QAAQ,SAAS,GAAG,IAAI;AAC1B;AAAA,YACF,OAAO;AACL,gBAAE,QAAQ,SAAS,KAAK,QAAQ;AAAA,YAClC;AAAA,UACF;AACA,cAAI,eAAe,KAAK,MAAM,SAAS,SAAS,GAAG;AACjD,oBAAQ;AAAA,cACN,oFACc,MAAM,SAAS,IAAI,CAACC,OAAyBA,GAAE,EAAE,EAAE,KAAK,IAAI,CAAC,gBAC7D,EAAE,QAAQ,SAAS,IAAI,CAACA,OAAyBA,GAAE,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA,YAAA;AAAA,UAEnF;AAAA,QACF;AAAA,MACF;AACA,UAAI,MAAM,cAAe,GAAE,QAAQ,gBAAgB,MAAM;AACzD,UAAI,MAAM,OAAQ,GAAE,QAAQ,SAAS,MAAM;AAC3C,UAAI,MAAM,QAAS,GAAE,QAAQ,UAAU,MAAM;AAC7C,UAAI,MAAM,QAAS,GAAE,QAAQ,UAAU,MAAM;AAC7C,UAAI,MAAM,UAAU,KAAM,GAAE,SAAS,MAAM;AAC3C,UAAK,MAAc,YAAa,GAAE,QAAQ,cAAe,MAAc;AACvE,UAAK,MAAc,QAAQ,KAAM,GAAE,QAAQ,OAAQ,MAAc;AAAA,IACnE,CAAC,CAAC;AAAA,EACJ,WAAW,MAAM,WAAW,SAAS;AACnC,YAAQ,KAAK,4CAA4C,MAAM,EAAE,IAAI,qCAAqC,gBAAgB;AAC1H,uBAAmB,EAAE,SAAS,MAAM,QAAQ,OAAO;AAAA,EACrD;AACF;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seed-ship/mcp-ui-solid",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
4
4
  "description": "SolidJS components for rendering MCP-generated UI resources",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -63,10 +63,49 @@ export const ScratchpadPanel: Component<ScratchpadPanelProps> = (props) => {
63
63
  console.log(`[ScratchpadPanel:${props.state.id}] ${event}`, data || '')
64
64
  }
65
65
 
66
+ // ─── DX1: Proactive console messages (always, not just debug) ───
67
+
68
+ const VALID_TRANSITIONS: Record<string, string[]> = {
69
+ loading: ['processing', 'waiting_human', 'error'],
70
+ waiting_human: ['processing', 'ready', 'complete', 'error'],
71
+ processing: ['ready', 'complete', 'error', 'waiting_human'],
72
+ ready: ['processing', 'complete', 'error', 'waiting_human'],
73
+ complete: [],
74
+ error: ['processing', 'ready', 'waiting_human'],
75
+ }
76
+ let prevStatus = props.state.status
77
+
78
+ // Etape 1: create log
79
+ console.info(
80
+ `%c[MCP-UI] Scratchpad created%c id=${props.state.id} sections=${props.state.sections?.length || 0} status=${props.state.status}${props.pinned ? ' pinned=true' : ''}`,
81
+ 'color: #10b981; font-weight: bold', 'color: inherit'
82
+ )
83
+
84
+ // Etape 3: status transitions + Etape 4: auto-close info
85
+ createEffect(() => {
86
+ const newStatus = props.state.status
87
+ if (newStatus !== prevStatus) {
88
+ console.info(`%c[MCP-UI] Scratchpad status%c ${props.state.id}: ${prevStatus} → ${newStatus}`, 'color: #3b82f6; font-weight: bold', 'color: inherit')
89
+ if (!VALID_TRANSITIONS[prevStatus]?.includes(newStatus)) {
90
+ console.warn(`[MCP-UI] Scratchpad ${props.state.id}: unusual transition ${prevStatus} → ${newStatus}. Expected: ${VALID_TRANSITIONS[prevStatus]?.join(', ') || 'none (terminal)'}`)
91
+ }
92
+ prevStatus = newStatus
93
+ }
94
+ // Etape 4
95
+ if (props.autoCloseDelay && newStatus !== 'complete') {
96
+ console.info(`[MCP-UI] Scratchpad ${props.state.id}: autoCloseDelay=${props.autoCloseDelay}ms but status='${newStatus}' — auto-close will NOT trigger.`)
97
+ }
98
+ })
99
+
66
100
  // Action aliases that auto-close the scratchpad
67
101
  const CLOSE_ALIASES = new Set(['done', 'close', 'dismiss', 'validate', 'cancel', 'sufficient'])
68
102
 
69
103
  const handleAction = (action: string, data?: unknown) => {
104
+ // DX1 Etape 5: action dispatch
105
+ console.info(`%c[MCP-UI] Action dispatched%c value='${action}' asyncAction=${!!props.asyncAction}`, 'color: #f59e0b; font-weight: bold', 'color: inherit')
106
+ if (!props.asyncAction && /^(try_alt:|retry|fetch|load)/.test(action)) {
107
+ console.warn(`[MCP-UI] ScratchpadPanel: action '${action}' looks async but asyncAction prop is not set. The button will NOT show a loading state.`)
108
+ }
70
109
  debugLog('onAction', { action, asyncAction: props.asyncAction, data })
71
110
  if (props.asyncAction && !CLOSE_ALIASES.has(action)) {
72
111
  setLoadingAction(action)
@@ -503,6 +542,9 @@ const EmbeddedFormSection: Component<{
503
542
  .filter(([, v]) => v !== undefined && v !== '' && !(Array.isArray(v) && v.length === 0))
504
543
  )
505
544
 
545
+ // DX1 Etape 7: form submit log
546
+ console.info(`%c[MCP-UI] Form submitted%c section=${props.sectionId} fields=${Object.keys(values).join(',')}`, 'color: #8b5cf6; font-weight: bold', 'color: inherit')
547
+
506
548
  if (props.onSubmit) {
507
549
  props.onSubmit(props.sectionId, values)
508
550
  } else {
@@ -849,7 +891,10 @@ const ErrorSectionRenderer: Component<{
849
891
  const [showDetails, setShowDetails] = createSignal(false)
850
892
  const data = () => {
851
893
  const c = props.content as any
852
- return { message: c?.message || 'Error', severity: c?.severity || 'error', retryAction: c?.retryAction, retryLabel: c?.retryLabel || 'Retry', details: c?.details, timestamp: c?.timestamp }
894
+ const d = { message: c?.message || 'Error', severity: c?.severity || 'error', retryAction: c?.retryAction, retryLabel: c?.retryLabel || 'Retry', details: c?.details, timestamp: c?.timestamp }
895
+ // DX1 Etape 8
896
+ console.info(`%c[MCP-UI] Error section rendered%c severity=${d.severity} retry=${!!d.retryAction}`, 'color: #ef4444; font-weight: bold', 'color: inherit')
897
+ return d
853
898
  }
854
899
  const isWarning = () => data().severity === 'warning'
855
900
 
package/src/index.ts CHANGED
@@ -42,6 +42,7 @@ export { ComponentToolbar } from './components/ComponentToolbar'
42
42
  export { ChatBusProvider, useChatBus } from './hooks/useChatBus'
43
43
  export { ChatPrompt } from './components/ChatPrompt'
44
44
  export { ScratchpadPanel } from './components/ScratchpadPanel'
45
+ export { dispatchScratchpad, useScratchpadState } from './stores/scratchpad-store'
45
46
 
46
47
  // Autocomplete Components
47
48
  export { GhostText, GhostTextInput } from './components/GhostText'
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Scratchpad Store — singleton reactive state for HITL scratchpad
3
+ * v3.0.3: Eliminates ChatBus relay chain race condition
4
+ *
5
+ * @experimental
6
+ *
7
+ * Parser calls dispatchScratchpad() → store updates → ScratchpadPanel reads reactively.
8
+ * Zero bus, zero relay, zero race condition.
9
+ */
10
+
11
+ import { createStore, produce } from 'solid-js/store'
12
+ import type { ScratchpadState, ScratchpadEvent, ScratchpadSection } from '../types/chat-bus'
13
+
14
+ const [scratchpadStore, setScratchpadStore] = createStore<{
15
+ current: ScratchpadState | null
16
+ pinned: boolean
17
+ }>({ current: null, pinned: false })
18
+
19
+ /**
20
+ * Hook for the COMPONENT — reads the scratchpad state reactively.
21
+ *
22
+ * @example
23
+ * const { state, pinned, close } = useScratchpadState()
24
+ * <Show when={state()}>
25
+ * <ScratchpadPanel state={state()!} pinned={pinned()} onClose={close} />
26
+ * </Show>
27
+ */
28
+ export function useScratchpadState() {
29
+ return {
30
+ state: () => scratchpadStore.current,
31
+ pinned: () => scratchpadStore.pinned,
32
+ close: () => setScratchpadStore({ current: null, pinned: false }),
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Function for the PARSER/STORE — mutates the scratchpad state.
38
+ * Called from the SSE callback, no bus needed.
39
+ *
40
+ * @example
41
+ * // In your SSE parser callback — ONE LINE
42
+ * onScratchpad: (data) => dispatchScratchpad(data as ScratchpadEvent)
43
+ */
44
+ export function dispatchScratchpad(event: ScratchpadEvent): void {
45
+ // DX1: lifecycle logging
46
+ if (event.action === 'create') {
47
+ console.info(
48
+ `%c[MCP-UI] dispatchScratchpad%c create id=${event.id} sections=${event.sections?.length || 0} status=${event.status || 'loading'}${event.pinned ? ' pinned' : ''}`,
49
+ 'color: #10b981; font-weight: bold', 'color: inherit'
50
+ )
51
+ setScratchpadStore({
52
+ current: {
53
+ id: event.id,
54
+ title: event.title || '',
55
+ sections: event.sections || [],
56
+ filters: event.filters || {},
57
+ preview: event.preview,
58
+ agentMessages: event.agentMessages || [],
59
+ status: event.status || 'loading',
60
+ previewEndpoint: (event as any).previewEndpoint,
61
+ previewDebounce: (event as any).previewDebounce,
62
+ previewMethod: (event as any).previewMethod,
63
+ previewHeaders: (event as any).previewHeaders,
64
+ turn: (event as any).turn,
65
+ totalTurns: (event as any).totalTurns,
66
+ turnHistory: (event as any).turnHistory,
67
+ },
68
+ pinned: event.pinned || false,
69
+ })
70
+ } else if (event.action === 'update') {
71
+ console.info(
72
+ `%c[MCP-UI] dispatchScratchpad%c update id=${event.id} sectionMode=${event.sectionMode || 'replace'} sections=${event.sections?.length || 0} status=${event.status || '-'}`,
73
+ 'color: #3b82f6; font-weight: bold', 'color: inherit'
74
+ )
75
+ setScratchpadStore(produce((s) => {
76
+ if (!s.current || s.current.id !== event.id) {
77
+ console.warn(`[MCP-UI] dispatchScratchpad: update for id=${event.id} but current is ${s.current?.id || 'null'}. Ignoring.`)
78
+ return
79
+ }
80
+
81
+ if (event.sections) {
82
+ const mode = event.sectionMode || 'replace'
83
+ if (mode === 'replace') {
84
+ s.current.sections = event.sections
85
+ } else if (mode === 'append') {
86
+ s.current.sections = [...s.current.sections, ...event.sections]
87
+ } else if (mode === 'upsert') {
88
+ let matchCount = 0
89
+ for (const incoming of event.sections) {
90
+ const idx = s.current.sections.findIndex((sec: ScratchpadSection) => sec.id === incoming.id)
91
+ if (idx >= 0) {
92
+ s.current.sections[idx] = incoming
93
+ matchCount++
94
+ } else {
95
+ s.current.sections.push(incoming)
96
+ }
97
+ }
98
+ if (matchCount === 0 && event.sections.length > 0) {
99
+ console.warn(
100
+ `[MCP-UI] dispatchScratchpad: sectionMode='upsert' but no IDs matched. ` +
101
+ `Incoming: [${event.sections.map((s: ScratchpadSection) => s.id).join(', ')}] ` +
102
+ `Existing: [${s.current.sections.map((s: ScratchpadSection) => s.id).join(', ')}]. All appended.`
103
+ )
104
+ }
105
+ }
106
+ }
107
+ if (event.agentMessages) s.current.agentMessages = event.agentMessages
108
+ if (event.status) s.current.status = event.status
109
+ if (event.filters) s.current.filters = event.filters
110
+ if (event.preview) s.current.preview = event.preview
111
+ if (event.pinned != null) s.pinned = event.pinned
112
+ if ((event as any).turnHistory) s.current.turnHistory = (event as any).turnHistory
113
+ if ((event as any).turn != null) s.current.turn = (event as any).turn
114
+ }))
115
+ } else if (event.action === 'close') {
116
+ console.info(`%c[MCP-UI] dispatchScratchpad%c close id=${event.id}`, 'color: #6b7280; font-weight: bold', 'color: inherit')
117
+ setScratchpadStore({ current: null, pinned: false })
118
+ }
119
+ }