@seed-ship/mcp-ui-solid 4.3.0 → 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seed-ship/mcp-ui-solid",
3
- "version": "4.3.0",
3
+ "version": "4.3.1",
4
4
  "description": "SolidJS components for rendering MCP-generated UI resources",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -36,6 +36,8 @@ export interface ScratchpadPanelProps {
36
36
  debug?: boolean
37
37
  /** Show mini debug overlay */
38
38
  debugOverlay?: boolean
39
+ /** Show collapsible debug trace panel under forms */
40
+ debugTrace?: boolean
39
41
  closable?: boolean
40
42
  autoCloseDelay?: number
41
43
  collapsible?: boolean
@@ -243,6 +245,7 @@ export const ScratchpadPanel: Component<ScratchpadPanelProps> = (props) => {
243
245
  onAction={handleAction}
244
246
  onSectionEdit={props.onSectionEdit}
245
247
  onSubmit={props.onSubmit}
248
+ debugTrace={props.debugTrace}
246
249
  />
247
250
  )}
248
251
  </For>
@@ -373,6 +376,7 @@ const SectionRenderer: Component<{
373
376
  onAction?: (action: string, data?: unknown) => void
374
377
  onSectionEdit?: (sectionId: string, content: unknown) => void
375
378
  onSubmit?: (sectionId: string, values: Record<string, unknown>) => void
379
+ debugTrace?: boolean
376
380
  }> = (props) => {
377
381
  return (
378
382
  <div class="px-4 py-3 animate-[slideDown_0.2s_ease-out]">
@@ -383,7 +387,7 @@ const SectionRenderer: Component<{
383
387
  <Match when={props.section.type === 'message'}><p class="text-sm text-gray-700 dark:text-gray-300">{String(props.section.content)}</p></Match>
384
388
  <Match when={props.section.type === 'action'}><ActionSection content={parseContent(props.section.content)} onAction={props.onAction} /></Match>
385
389
  <Match when={props.section.type === 'steps'}><EnrichedStepsSection content={parseContent(props.section.content)} onAction={props.onAction} onFilterChange={props.onFilterChange} /></Match>
386
- <Match when={props.section.type === 'form'}><EmbeddedFormSection content={parseContent(props.section.content)} sectionId={props.section.id} onAction={props.onAction} onSubmit={props.onSubmit} /></Match>
390
+ <Match when={props.section.type === 'form'}><EmbeddedFormSection content={parseContent(props.section.content)} sectionId={props.section.id} onAction={props.onAction} onSubmit={props.onSubmit} debugTrace={props.debugTrace} /></Match>
387
391
  <Match when={props.section.type === 'understanding'}><UnderstandingSection content={parseContent(props.section.content)} /></Match>
388
392
  <Match when={props.section.type === 'feedback'}><FeedbackSection content={parseContent(props.section.content)} onAction={props.onAction} /></Match>
389
393
  <Match when={props.section.type === 'prompt'}><PromptSection content={parseContent(props.section.content)} onAction={props.onAction} /></Match>
@@ -524,6 +528,7 @@ const EmbeddedFormSection: Component<{
524
528
  sectionId: string
525
529
  onAction?: (action: string, data?: unknown) => void
526
530
  onSubmit?: (sectionId: string, values: Record<string, unknown>) => void
531
+ debugTrace?: boolean
527
532
  }> = (props) => {
528
533
  const [dynamicOptions, setDynamicOptions] = createSignal<Record<string, Array<{ label: string; value: string }>>>({})
529
534
 
@@ -631,6 +636,9 @@ const EmbeddedFormSection: Component<{
631
636
  return dynOpts ? { ...field, options: dynOpts } as FormFieldParams : field as FormFieldParams
632
637
  }
633
638
 
639
+ // Debug trace: track submitted values
640
+ const [submittedValues, setSubmittedValues] = createSignal<Record<string, any> | null>(null)
641
+
634
642
  const handleSubmit = (e: Event) => {
635
643
  e.preventDefault()
636
644
 
@@ -644,6 +652,8 @@ const EmbeddedFormSection: Component<{
644
652
  .filter(([, v]) => v !== undefined && v !== '' && !(Array.isArray(v) && v.length === 0))
645
653
  )
646
654
 
655
+ setSubmittedValues(values)
656
+
647
657
  // DX1 Etape 7: form submit log
648
658
  console.info(`%c[MCP-UI] Form submitted%c section=${props.sectionId} fields=${Object.keys(values).join(',')}`, 'color: #8b5cf6; font-weight: bold', 'color: inherit')
649
659
 
@@ -671,6 +681,7 @@ const EmbeddedFormSection: Component<{
671
681
  const showToast = () => allFieldsPrefilled() && !userInteracted() && !expanded() && countdown() != null
672
682
 
673
683
  return (
684
+ <>
674
685
  <Show when={!showToast()} fallback={
675
686
  <div class="flex items-center gap-3 p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg text-sm">
676
687
  <span class="flex-1 text-blue-800 dark:text-blue-200 font-medium">
@@ -721,6 +732,133 @@ const EmbeddedFormSection: Component<{
721
732
  </div>
722
733
  </form>
723
734
  </Show>
735
+
736
+ {/* Debug trace panel */}
737
+ <Show when={props.debugTrace}>
738
+ <FormDebugTrace
739
+ fields={config().fields}
740
+ formData={formData()}
741
+ submittedValues={submittedValues()}
742
+ autoSubmitDelay={config().autoSubmitDelay}
743
+ userInteracted={userInteracted()}
744
+ rawContent={props.content}
745
+ />
746
+ </Show>
747
+ </>
748
+ )
749
+ }
750
+
751
+ // ─── Form Debug Trace Panel ─────────────────────────────────
752
+
753
+ const FormDebugTrace: Component<{
754
+ fields: any[]
755
+ formData: Record<string, any>
756
+ submittedValues: Record<string, any> | null
757
+ autoSubmitDelay?: number
758
+ userInteracted: boolean
759
+ rawContent: unknown
760
+ }> = (props) => {
761
+ const [open, setOpen] = createSignal(false)
762
+ const [showRaw, setShowRaw] = createSignal(false)
763
+
764
+ const prefilledCount = () => props.fields.filter((f: any) => f.prefill != null).length
765
+ const requiredFields = () => props.fields.filter((f: any) => f.required)
766
+ const missingRequired = () => requiredFields().filter((f: any) => f.prefill == null)
767
+
768
+ const autoSubmitReason = () => {
769
+ if (!props.autoSubmitDelay) return 'no autoSubmitDelay configured'
770
+ if (missingRequired().length > 0) return `${missingRequired().length} required field(s) without prefill`
771
+ if (props.userInteracted) return 'user interacted — cancelled'
772
+ return 'all conditions met'
773
+ }
774
+
775
+ // Server-side _debug data
776
+ const serverDebug = () => (props.rawContent as any)?._debug
777
+
778
+ return (
779
+ <div class="mt-2 border border-gray-200 dark:border-gray-700 rounded-md text-xs font-mono">
780
+ <button
781
+ type="button"
782
+ onClick={() => setOpen(!open())}
783
+ class="w-full px-3 py-1.5 text-left text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 flex items-center gap-1"
784
+ >
785
+ <span>{open() ? '\u25BE' : '\u25B8'}</span>
786
+ Debug trace ({prefilledCount()}/{props.fields.length} prefilled)
787
+ </button>
788
+ <Show when={open()}>
789
+ <div class="px-3 pb-3 space-y-2 text-gray-600 dark:text-gray-400 border-t border-gray-200 dark:border-gray-700 pt-2">
790
+ <div>Fields: {props.fields.length} total, {prefilledCount()} prefilled</div>
791
+
792
+ <For each={props.fields}>
793
+ {(field: any) => (
794
+ <div class="pl-2 border-l-2 border-gray-200 dark:border-gray-600 space-y-0.5">
795
+ <div class="text-gray-800 dark:text-gray-200 font-medium">{field.name}:</div>
796
+ <Show when={field.prefill != null}>
797
+ <div> prefill: {JSON.stringify(field.prefill)}</div>
798
+ </Show>
799
+ <Show when={!field.prefill}>
800
+ <div class="text-amber-500"> prefill: (none)</div>
801
+ </Show>
802
+ <Show when={field.source}><div> source: {field.source}</div></Show>
803
+ <Show when={field.displayHint}><div> displayHint: "{field.displayHint}"</div></Show>
804
+ <Show when={field.muted}><div> muted: true</div></Show>
805
+ <Show when={field.prefillMode}><div> prefillMode: {field.prefillMode}</div></Show>
806
+ <Show when={field.valueFormat}><div> valueFormat: /{field.valueFormat}/</div></Show>
807
+ <Show when={props.submittedValues}>
808
+ {(() => {
809
+ const v = props.submittedValues![field.name]
810
+ const hasValue = v !== undefined && v !== '' && !(Array.isArray(v) && v.length === 0)
811
+ return (
812
+ <div class={hasValue ? 'text-green-600 dark:text-green-400' : 'text-gray-400'}>
813
+ {'\u2192'} submitted: {hasValue ? `${JSON.stringify(v)} \u2713` : '(empty)'}
814
+ </div>
815
+ )
816
+ })()}
817
+ </Show>
818
+ </div>
819
+ )}
820
+ </For>
821
+
822
+ <div class="pt-1 border-t border-gray-200 dark:border-gray-600">
823
+ autoSubmit: {props.autoSubmitDelay ? 'true' : 'false'}
824
+ {props.autoSubmitDelay ? ` (${props.autoSubmitDelay}ms)` : ''}
825
+ <div> reason: {autoSubmitReason()}</div>
826
+ </div>
827
+
828
+ <Show when={serverDebug()}>
829
+ <div class="pt-1 border-t border-gray-200 dark:border-gray-600">
830
+ <div class="font-medium text-gray-800 dark:text-gray-200">Server _debug:</div>
831
+ <Show when={serverDebug()?.resolvers}>
832
+ <For each={serverDebug().resolvers}>
833
+ {(r: any) => (
834
+ <div class="pl-2">
835
+ {r.field}: {r.resolver} "{r.input}" {'\u2192'} "{r.output}" ({r.ms}ms)
836
+ </div>
837
+ )}
838
+ </For>
839
+ </Show>
840
+ <Show when={serverDebug()?.routing}>
841
+ <div class="pl-2">routing: {serverDebug().routing.topic} via {serverDebug().routing.method} ({serverDebug().routing.ms}ms)</div>
842
+ </Show>
843
+ <Show when={serverDebug()?.missingFields}>
844
+ <div class="pl-2 text-amber-500">missing: {serverDebug().missingFields.join(', ')}</div>
845
+ </Show>
846
+ </div>
847
+ </Show>
848
+
849
+ <div class="pt-1 border-t border-gray-200 dark:border-gray-600">
850
+ <button type="button" onClick={() => setShowRaw(!showRaw())} class="text-blue-500 hover:text-blue-700 underline">
851
+ {showRaw() ? 'Hide' : 'Show'} raw SSE payload
852
+ </button>
853
+ <Show when={showRaw()}>
854
+ <pre class="mt-1 p-2 bg-gray-50 dark:bg-gray-900 rounded max-h-48 overflow-auto text-[10px]">
855
+ {JSON.stringify(props.rawContent, null, 2)}
856
+ </pre>
857
+ </Show>
858
+ </div>
859
+ </div>
860
+ </Show>
861
+ </div>
724
862
  )
725
863
  }
726
864