@seed-ship/mcp-ui-solid 6.8.1 → 6.9.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.
- package/CHANGELOG.md +49 -0
- package/dist/components/ChartJSRenderer.cjs +27 -13
- package/dist/components/ChartJSRenderer.cjs.map +1 -1
- package/dist/components/ChartJSRenderer.d.ts.map +1 -1
- package/dist/components/ChartJSRenderer.js +28 -14
- package/dist/components/ChartJSRenderer.js.map +1 -1
- package/dist/components/DegradedFallback.cjs +73 -0
- package/dist/components/DegradedFallback.cjs.map +1 -0
- package/dist/components/DegradedFallback.d.ts +37 -0
- package/dist/components/DegradedFallback.d.ts.map +1 -0
- package/dist/components/DegradedFallback.js +73 -0
- package/dist/components/DegradedFallback.js.map +1 -0
- package/dist/components/GraphRenderer.cjs +30 -15
- package/dist/components/GraphRenderer.cjs.map +1 -1
- package/dist/components/GraphRenderer.d.ts.map +1 -1
- package/dist/components/GraphRenderer.js +31 -16
- package/dist/components/GraphRenderer.js.map +1 -1
- package/dist/components/MapRenderer.cjs +128 -107
- package/dist/components/MapRenderer.cjs.map +1 -1
- package/dist/components/MapRenderer.d.ts.map +1 -1
- package/dist/components/MapRenderer.js +129 -108
- package/dist/components/MapRenderer.js.map +1 -1
- package/dist/index.cjs +4 -4
- package/dist/index.js +1 -1
- package/dist/services/validation.cjs +43 -9
- package/dist/services/validation.cjs.map +1 -1
- package/dist/services/validation.d.ts.map +1 -1
- package/dist/services/validation.js +43 -9
- package/dist/services/validation.js.map +1 -1
- package/dist/utils/degraded-projections.cjs +87 -0
- package/dist/utils/degraded-projections.cjs.map +1 -0
- package/dist/utils/degraded-projections.d.ts +64 -0
- package/dist/utils/degraded-projections.d.ts.map +1 -0
- package/dist/utils/degraded-projections.js +87 -0
- package/dist/utils/degraded-projections.js.map +1 -0
- package/package.json +1 -1
- package/src/components/ChartJSRenderer.tsx +94 -85
- package/src/components/DegradedFallback.test.tsx +61 -0
- package/src/components/DegradedFallback.tsx +93 -0
- package/src/components/GraphRenderer.tsx +26 -4
- package/src/components/MapRenderer.tsx +446 -392
- package/src/services/validation.test.ts +298 -232
- package/src/services/validation.ts +210 -136
- package/src/utils/degraded-projections.test.ts +113 -0
- package/src/utils/degraded-projections.ts +149 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DegradedFallback — the middle rung of the renderer fallback ladder
|
|
3
|
+
* (audit 2026-05-30, P2.5).
|
|
4
|
+
*
|
|
5
|
+
* Each heavy renderer (graph / map / chart) follows the same contract:
|
|
6
|
+
* 1. native render when its peer lib is available and succeeds;
|
|
7
|
+
* 2. **degraded but useful** view when the native render throws — this
|
|
8
|
+
* component: a visible notice + a plain data table so the user still
|
|
9
|
+
* sees the underlying data instead of a blank space;
|
|
10
|
+
* 3. (the caller also emits a `component:error` telemetry event).
|
|
11
|
+
*
|
|
12
|
+
* Pure / presentational — no peer deps, no side effects — so a render-path
|
|
13
|
+
* failure in a heavy lib can never cascade into the fallback itself, and it
|
|
14
|
+
* is trivially unit-testable. Rows/cells are rendered as text; callers are
|
|
15
|
+
* responsible for stringifying complex cell values.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { Component, For, Show } from 'solid-js';
|
|
19
|
+
|
|
20
|
+
export interface DegradedFallbackProps {
|
|
21
|
+
/** Short, human-readable reason the native render was skipped/failed. */
|
|
22
|
+
message: string;
|
|
23
|
+
/**
|
|
24
|
+
* Column headers for the degraded data table. When omitted (or empty),
|
|
25
|
+
* only the notice banner is shown.
|
|
26
|
+
*/
|
|
27
|
+
columns?: string[];
|
|
28
|
+
/** Row data — each row is an array of cells aligned to `columns`. */
|
|
29
|
+
rows?: Array<Array<string | number>>;
|
|
30
|
+
/**
|
|
31
|
+
* Caption under the table. Defaults to a generic
|
|
32
|
+
* "interactive view unavailable" line.
|
|
33
|
+
*/
|
|
34
|
+
caption?: string;
|
|
35
|
+
/** Max rows to render before truncating (default 50). */
|
|
36
|
+
maxRows?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const DegradedFallback: Component<DegradedFallbackProps> = (props) => {
|
|
40
|
+
const maxRows = () => props.maxRows ?? 50;
|
|
41
|
+
const allRows = () => props.rows ?? [];
|
|
42
|
+
const shownRows = () => allRows().slice(0, maxRows());
|
|
43
|
+
const hiddenCount = () => Math.max(0, allRows().length - shownRows().length);
|
|
44
|
+
const hasTable = () => (props.columns?.length ?? 0) > 0 && allRows().length > 0;
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div
|
|
48
|
+
class="w-full rounded-lg border border-amber-200 bg-amber-50 p-3 dark:border-amber-800 dark:bg-amber-900/20"
|
|
49
|
+
role="alert"
|
|
50
|
+
>
|
|
51
|
+
<p class="text-sm font-medium text-amber-900 dark:text-amber-100">{props.message}</p>
|
|
52
|
+
<p class="mt-0.5 text-xs text-amber-700 dark:text-amber-300">
|
|
53
|
+
{props.caption ?? 'Showing the underlying data — the interactive view is unavailable.'}
|
|
54
|
+
</p>
|
|
55
|
+
|
|
56
|
+
<Show when={hasTable()}>
|
|
57
|
+
<div class="mt-2 max-h-64 overflow-auto rounded border border-amber-200 dark:border-amber-800">
|
|
58
|
+
<table class="w-full border-collapse text-left text-xs">
|
|
59
|
+
<thead class="sticky top-0 bg-amber-100 dark:bg-amber-900/40">
|
|
60
|
+
<tr>
|
|
61
|
+
<For each={props.columns}>
|
|
62
|
+
{(col) => (
|
|
63
|
+
<th class="px-2 py-1 font-medium text-amber-900 dark:text-amber-100">{col}</th>
|
|
64
|
+
)}
|
|
65
|
+
</For>
|
|
66
|
+
</tr>
|
|
67
|
+
</thead>
|
|
68
|
+
<tbody>
|
|
69
|
+
<For each={shownRows()}>
|
|
70
|
+
{(row) => (
|
|
71
|
+
<tr class="border-t border-amber-100 dark:border-amber-800/60">
|
|
72
|
+
<For each={props.columns}>
|
|
73
|
+
{(_col, i) => (
|
|
74
|
+
<td class="px-2 py-1 text-amber-800 dark:text-amber-200">
|
|
75
|
+
{String(row[i()] ?? '')}
|
|
76
|
+
</td>
|
|
77
|
+
)}
|
|
78
|
+
</For>
|
|
79
|
+
</tr>
|
|
80
|
+
)}
|
|
81
|
+
</For>
|
|
82
|
+
</tbody>
|
|
83
|
+
</table>
|
|
84
|
+
</div>
|
|
85
|
+
<Show when={hiddenCount() > 0}>
|
|
86
|
+
<p class="mt-1 text-[10px] text-amber-600 dark:text-amber-400">
|
|
87
|
+
+{hiddenCount()} more rows not shown.
|
|
88
|
+
</p>
|
|
89
|
+
</Show>
|
|
90
|
+
</Show>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
@@ -28,6 +28,9 @@ import type {
|
|
|
28
28
|
} from '@seed-ship/mcp-ui-spec';
|
|
29
29
|
import { ExpandableWrapper, useExpanded } from './ExpandableWrapper';
|
|
30
30
|
import { PortalDropdownMenu } from './PortalDropdownMenu';
|
|
31
|
+
import { DegradedFallback } from './DegradedFallback';
|
|
32
|
+
import { graphToDegradedTable } from '../utils/degraded-projections';
|
|
33
|
+
import { useTelemetry } from '../context/MCPUITelemetryContext';
|
|
31
34
|
|
|
32
35
|
// Module-scoped lazy import promise — first call triggers the dynamic
|
|
33
36
|
// import, subsequent calls reuse the resolved module.
|
|
@@ -220,6 +223,7 @@ export interface GraphRendererProps {
|
|
|
220
223
|
export const GraphRenderer: Component<GraphRendererProps> = (props) => {
|
|
221
224
|
const params = () => props.component.params as GraphComponentParams;
|
|
222
225
|
const isExpanded = useExpanded();
|
|
226
|
+
const telemetry = useTelemetry();
|
|
223
227
|
const [available, setAvailable] = createSignal<boolean | null>(null);
|
|
224
228
|
const [error, setError] = createSignal<string | undefined>();
|
|
225
229
|
const [exportMenuOpen, setExportMenuOpen] = createSignal(false);
|
|
@@ -293,7 +297,18 @@ export const GraphRenderer: Component<GraphRendererProps> = (props) => {
|
|
|
293
297
|
graphInstance = new (Graph as any)(config);
|
|
294
298
|
await graphInstance.render();
|
|
295
299
|
} catch (err) {
|
|
296
|
-
|
|
300
|
+
const message = err instanceof Error ? err.message : 'Failed to render graph';
|
|
301
|
+
setError(message);
|
|
302
|
+
// Fallback ladder (P2.5): the native G6 render threw — emit telemetry
|
|
303
|
+
// so the failure is observable, then degrade to the edge/node table
|
|
304
|
+
// below instead of leaving a blank canvas.
|
|
305
|
+
telemetry?.dispatch({
|
|
306
|
+
type: 'render:error',
|
|
307
|
+
errorMessage: message,
|
|
308
|
+
id: props.component.id ?? '',
|
|
309
|
+
componentType: 'graph',
|
|
310
|
+
ts: Date.now(),
|
|
311
|
+
});
|
|
297
312
|
}
|
|
298
313
|
});
|
|
299
314
|
|
|
@@ -424,19 +439,26 @@ export const GraphRenderer: Component<GraphRendererProps> = (props) => {
|
|
|
424
439
|
</PortalDropdownMenu>
|
|
425
440
|
</div>
|
|
426
441
|
|
|
442
|
+
{/* Native G6 canvas — hidden once a render error degrades us. */}
|
|
427
443
|
<div
|
|
428
444
|
ref={containerRef}
|
|
429
445
|
class={`bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden ${
|
|
430
|
-
|
|
431
|
-
}`}
|
|
446
|
+
error() ? 'hidden' : ''
|
|
447
|
+
} ${isExpanded() ? 'flex-1 min-h-0' : ''}`}
|
|
432
448
|
style={
|
|
433
449
|
isExpanded()
|
|
434
450
|
? `height: 100%; width: ${params().width ?? '100%'};`
|
|
435
451
|
: `height: ${params().height ?? '400px'}; width: ${params().width ?? '100%'};`
|
|
436
452
|
}
|
|
437
453
|
/>
|
|
454
|
+
{/* Fallback ladder (P2.5): degrade to an edge/node table on error
|
|
455
|
+
rather than showing a bare message. Export menu stays usable. */}
|
|
438
456
|
<Show when={error()}>
|
|
439
|
-
<
|
|
457
|
+
<DegradedFallback
|
|
458
|
+
message={`Graph rendering failed: ${error()}`}
|
|
459
|
+
caption="Showing the graph data as a table — the interactive view is unavailable."
|
|
460
|
+
{...graphToDegradedTable(params())}
|
|
461
|
+
/>
|
|
440
462
|
</Show>
|
|
441
463
|
</div>
|
|
442
464
|
</ExpandableWrapper>
|