@seed-ship/mcp-ui-solid 4.3.0 → 4.3.2
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/CHANGELOG.md +26 -0
- package/dist/components/ScratchpadPanel.cjs +500 -250
- package/dist/components/ScratchpadPanel.cjs.map +1 -1
- package/dist/components/ScratchpadPanel.d.ts +2 -0
- package/dist/components/ScratchpadPanel.d.ts.map +1 -1
- package/dist/components/ScratchpadPanel.js +500 -250
- package/dist/components/ScratchpadPanel.js.map +1 -1
- package/dist/components/UIResourceRenderer.cjs +201 -165
- package/dist/components/UIResourceRenderer.cjs.map +1 -1
- package/dist/components/UIResourceRenderer.d.ts.map +1 -1
- package/dist/components/UIResourceRenderer.js +201 -165
- package/dist/components/UIResourceRenderer.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ScratchpadPanel.tsx +139 -1
- package/src/components/UIResourceRenderer.tsx +37 -5
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -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
|
|
|
@@ -345,6 +345,7 @@ function TableRenderer(props: {
|
|
|
345
345
|
setSortDir('asc')
|
|
346
346
|
}
|
|
347
347
|
setClientPage(0)
|
|
348
|
+
setProgressivePages(1)
|
|
348
349
|
}
|
|
349
350
|
|
|
350
351
|
const sortedRows = createMemo(() => {
|
|
@@ -374,22 +375,36 @@ function TableRenderer(props: {
|
|
|
374
375
|
return sortDir() === 'asc' ? '\u2191' : '\u2193'
|
|
375
376
|
}
|
|
376
377
|
|
|
377
|
-
// ─── Client-side pagination (v4.0.4)
|
|
378
|
+
// ─── Client-side pagination (v4.0.4, progressive mode v4.3.2) ─────
|
|
378
379
|
const clientPageSize = () => tableParams.pageSize ?? 25
|
|
379
380
|
const hasServerPagination = () => !!tableParams.pagination
|
|
381
|
+
const isProgressiveMode = () => !!tableParams.showAllLabel
|
|
380
382
|
const needsClientPagination = () =>
|
|
381
383
|
!hasServerPagination() && clientPageSize() > 0 && sortedRows().length > clientPageSize()
|
|
382
384
|
const [clientPage, setClientPage] = createSignal(tableParams.initialPage ?? 0)
|
|
385
|
+
// Progressive mode: track how many pages to show (append)
|
|
386
|
+
const [progressivePages, setProgressivePages] = createSignal(1)
|
|
383
387
|
const clientTotalPages = () => needsClientPagination() ? Math.ceil(sortedRows().length / clientPageSize()) : 1
|
|
384
388
|
const clientVisibleRows = createMemo(() => {
|
|
385
389
|
if (!needsClientPagination()) return sortedRows()
|
|
390
|
+
if (isProgressiveMode()) {
|
|
391
|
+
// Progressive: show first N * pageSize rows
|
|
392
|
+
return sortedRows().slice(0, progressivePages() * clientPageSize())
|
|
393
|
+
}
|
|
386
394
|
const start = clientPage() * clientPageSize()
|
|
387
395
|
return sortedRows().slice(start, start + clientPageSize())
|
|
388
396
|
})
|
|
389
|
-
const clientRangeStart = () => needsClientPagination()
|
|
397
|
+
const clientRangeStart = () => needsClientPagination()
|
|
398
|
+
? (isProgressiveMode() ? 1 : clientPage() * clientPageSize() + 1)
|
|
399
|
+
: 1
|
|
390
400
|
const clientRangeEnd = () => needsClientPagination()
|
|
391
|
-
?
|
|
401
|
+
? (isProgressiveMode()
|
|
402
|
+
? Math.min(progressivePages() * clientPageSize(), sortedRows().length)
|
|
403
|
+
: Math.min((clientPage() + 1) * clientPageSize(), sortedRows().length))
|
|
392
404
|
: sortedRows().length
|
|
405
|
+
const progressiveHasMore = () => isProgressiveMode() && needsClientPagination() && progressivePages() < clientTotalPages()
|
|
406
|
+
const progressiveRemaining = () => sortedRows().length - progressivePages() * clientPageSize()
|
|
407
|
+
const showMoreLabel = () => tableParams.showAllLabel || 'Show more'
|
|
393
408
|
|
|
394
409
|
// ─── Virtualization ──────────────────────────────────────
|
|
395
410
|
const [virtualizer, setVirtualizer] = createSignal<any>(null)
|
|
@@ -685,8 +700,8 @@ function TableRenderer(props: {
|
|
|
685
700
|
</div>
|
|
686
701
|
</Show>
|
|
687
702
|
|
|
688
|
-
{/* Client-side
|
|
689
|
-
<Show when={needsClientPagination()}>
|
|
703
|
+
{/* Client-side paged pagination (v4.0.4) */}
|
|
704
|
+
<Show when={needsClientPagination() && !isProgressiveMode()}>
|
|
690
705
|
<div class="mt-3 flex items-center justify-between text-xs text-gray-500 dark:text-gray-400">
|
|
691
706
|
<span>
|
|
692
707
|
Showing {clientRangeStart()}–{clientRangeEnd()} of {allRows().length.toLocaleString('fr-FR')}
|
|
@@ -710,6 +725,23 @@ function TableRenderer(props: {
|
|
|
710
725
|
</div>
|
|
711
726
|
</div>
|
|
712
727
|
</Show>
|
|
728
|
+
|
|
729
|
+
{/* Client-side progressive pagination (v4.3.2) */}
|
|
730
|
+
<Show when={needsClientPagination() && isProgressiveMode()}>
|
|
731
|
+
<div class="mt-3 flex flex-col items-center gap-2 text-xs text-gray-500 dark:text-gray-400">
|
|
732
|
+
<span>
|
|
733
|
+
{clientRangeStart()}–{clientRangeEnd()} of {allRows().length.toLocaleString('fr-FR')}
|
|
734
|
+
</span>
|
|
735
|
+
<Show when={progressiveHasMore()}>
|
|
736
|
+
<button
|
|
737
|
+
class="px-4 py-1.5 rounded-md bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-300 transition-colors"
|
|
738
|
+
onClick={() => setProgressivePages(p => p + 1)}
|
|
739
|
+
>
|
|
740
|
+
{showMoreLabel()} ({Math.min(progressiveRemaining(), clientPageSize())} suivant{Math.min(progressiveRemaining(), clientPageSize()) > 1 ? 'es' : 'e'})
|
|
741
|
+
</button>
|
|
742
|
+
</Show>
|
|
743
|
+
</div>
|
|
744
|
+
</Show>
|
|
713
745
|
</div>
|
|
714
746
|
</div>
|
|
715
747
|
</ExpandableWrapper>
|