@seed-ship/mcp-ui-solid 5.5.0 → 5.6.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 +62 -0
- package/dist/components/FormRenderer.cjs +13 -2
- package/dist/components/FormRenderer.cjs.map +1 -1
- package/dist/components/FormRenderer.d.ts.map +1 -1
- package/dist/components/FormRenderer.js +13 -2
- package/dist/components/FormRenderer.js.map +1 -1
- package/dist/components/GenerativeUIErrorBoundary.cjs +11 -0
- package/dist/components/GenerativeUIErrorBoundary.cjs.map +1 -1
- package/dist/components/GenerativeUIErrorBoundary.d.ts.map +1 -1
- package/dist/components/GenerativeUIErrorBoundary.js +11 -0
- package/dist/components/GenerativeUIErrorBoundary.js.map +1 -1
- package/dist/components/StreamingUIRenderer.cjs +49 -3
- package/dist/components/StreamingUIRenderer.cjs.map +1 -1
- package/dist/components/StreamingUIRenderer.d.ts.map +1 -1
- package/dist/components/StreamingUIRenderer.js +51 -5
- package/dist/components/StreamingUIRenderer.js.map +1 -1
- package/dist/components/UIResourceRenderer.cjs +62 -3
- package/dist/components/UIResourceRenderer.cjs.map +1 -1
- package/dist/components/UIResourceRenderer.d.ts.map +1 -1
- package/dist/components/UIResourceRenderer.js +64 -5
- package/dist/components/UIResourceRenderer.js.map +1 -1
- package/dist/context/MCPUITelemetryContext.cjs +25 -0
- package/dist/context/MCPUITelemetryContext.cjs.map +1 -0
- package/dist/context/MCPUITelemetryContext.d.ts +36 -0
- package/dist/context/MCPUITelemetryContext.d.ts.map +1 -0
- package/dist/context/MCPUITelemetryContext.js +25 -0
- package/dist/context/MCPUITelemetryContext.js.map +1 -0
- package/dist/index.cjs +6 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-ui-spec/dist/schemas.cjs +16 -5
- package/dist/mcp-ui-spec/dist/schemas.cjs.map +1 -1
- package/dist/mcp-ui-spec/dist/schemas.js +16 -5
- package/dist/mcp-ui-spec/dist/schemas.js.map +1 -1
- package/dist/services/telemetry.cjs +56 -0
- package/dist/services/telemetry.cjs.map +1 -0
- package/dist/services/telemetry.d.ts +87 -0
- package/dist/services/telemetry.d.ts.map +1 -0
- package/dist/services/telemetry.js +56 -0
- package/dist/services/telemetry.js.map +1 -0
- package/dist/services/validation.cjs +28 -26
- package/dist/services/validation.cjs.map +1 -1
- package/dist/services/validation.d.ts.map +1 -1
- package/dist/services/validation.js +29 -27
- package/dist/services/validation.js.map +1 -1
- package/package.json +2 -2
- package/src/components/FormRenderer.tsx +14 -0
- package/src/components/GenerativeUIErrorBoundary.tsx +17 -1
- package/src/components/StreamingUIRenderer.tsx +55 -3
- package/src/components/UIResourceRenderer.tsx +79 -3
- package/src/context/MCPUITelemetryContext.test.tsx +119 -0
- package/src/context/MCPUITelemetryContext.tsx +71 -0
- package/src/index.ts +15 -0
- package/src/services/telemetry.test.ts +134 -0
- package/src/services/telemetry.ts +149 -0
- package/src/services/validation.test.ts +53 -3
- package/src/services/validation.ts +54 -44
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,68 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [5.6.0] - 2026-04-27
|
|
9
|
+
|
|
10
|
+
Closes B.1 migration (14/17 ComponentTypes spec-driven) AND ships B.5 — UI
|
|
11
|
+
telemetry sink, both per deposium audit `MCP-UI-AUDIT-2026-04-26.md` §M.
|
|
12
|
+
|
|
13
|
+
### Added — B.5 UI telemetry sink
|
|
14
|
+
|
|
15
|
+
- **`<MCPUITelemetryProvider sink options>`** + **`useTelemetry()`** hook + **`MCPUITelemetryContext`**. Provider is **OPTIONAL** — when absent, all dispatch sites no-op (zero behavior change for apps that don't opt in).
|
|
16
|
+
- **`createTelemetryDispatcher(sink, options)`** exposed for advanced usage / SSR contexts / testing without a Provider.
|
|
17
|
+
- **`TelemetryEvent`** discriminated union (6 event types) :
|
|
18
|
+
- `component:mounted` (id + componentType + ts)
|
|
19
|
+
- `component:rendered` (+ durationMs, read from existing v5.4.0 perf marks — no double measurement)
|
|
20
|
+
- `component:unmounted`
|
|
21
|
+
- `validation:failed` (+ errorCount + firstErrorCode — **NO** payload data, **NO** error messages)
|
|
22
|
+
- `render:error` (+ errorMessage from `<GenerativeUIErrorBoundary>` — **NO** stack trace, **NO** payload)
|
|
23
|
+
- `action:dispatched` (+ actionName from `<ActionRenderer>` clicks and `<FormRenderer>` submits — **NO** form values)
|
|
24
|
+
- **`TelemetryOptions`** : `sampleRate` (default 1.0), `bufferMs` (default 100), `bufferMax` (default 50), `sampleByType` (per-event-type override — e.g. `{ 'render:error': 1.0 }` to keep all errors while sampling mounted/rendered at 0.1).
|
|
25
|
+
- **Sink contract** : receives a **batch** (`TelemetryEvent[]`) — even with `bufferMs: 0` (single-element array). **FAIL-OPEN** : sink throws or rejects silently, never crashes the renderer.
|
|
26
|
+
- **Privacy hard rule** : no event carries the component params / data brut, only meta + types + counts + timing. Safe to log centrally.
|
|
27
|
+
- New file `src/services/telemetry.ts` (dispatcher) + `src/context/MCPUITelemetryContext.tsx` (Provider).
|
|
28
|
+
|
|
29
|
+
### Changed — B.1 final migration (map + form to Zod)
|
|
30
|
+
|
|
31
|
+
- **Dep bump** : `@seed-ship/mcp-ui-spec` `^5.0.1` → `^5.0.2` (relaxed map.center to LatLngPoint union + form field.name regex per deposium audit §L answers).
|
|
32
|
+
- `validation.ts` : **`map` and `form` joined `SPEC_VALIDATORS`** dispatch. Closed B.1 to **14/17 ComponentTypes** Zod-driven. The remaining 3 (`chart`, `table`, `modal`) stay imperative as documented (rich validators / nothing to validate).
|
|
33
|
+
- `map` post-spec chained check preserved : "center OR markers" rule stays imperative because Zod can't express it without `.refine()` overhead.
|
|
34
|
+
- Legacy codes preserved via mapper : `EMPTY_FORM`, `INVALID_MAP` still emitted on failure.
|
|
35
|
+
|
|
36
|
+
### Tests
|
|
37
|
+
|
|
38
|
+
- `src/services/telemetry.test.ts` — **+10 tests** for dispatcher (batch delivery, buffering, bufferMax force-flush, manual flush idempotency, fail-open on throw + on rejected promise, sampling 0/1/per-type override).
|
|
39
|
+
- `src/context/MCPUITelemetryContext.test.tsx` — **+5 integration tests** (no Provider = no events, mounted dispatch, validation:failed shape with privacy assertion on key set, fail-open on render, sampleRate=0 drops everything).
|
|
40
|
+
- Existing 530/530 tests untouched, all still pass.
|
|
41
|
+
- Total solid suite : **545/545 tests pass** (vs 530 on v5.5.1, +15 net).
|
|
42
|
+
|
|
43
|
+
### Non-breaking
|
|
44
|
+
|
|
45
|
+
- All v5.5.x APIs unchanged.
|
|
46
|
+
- Apps without `<MCPUITelemetryProvider>` see **zero behavior change**.
|
|
47
|
+
- Only newly-added shapes (`{lat,lng}` for map, `kebab-case` / dot-paths for form field names) accepted that were previously rejected — no shape that worked before is rejected now.
|
|
48
|
+
|
|
49
|
+
### What deposium can now do
|
|
50
|
+
|
|
51
|
+
1. Wrap their app : `<MCPUITelemetryProvider sink={batchedSinkToBackend}>`.
|
|
52
|
+
2. Implement the consumer-side `/admin/ui-telemetry` endpoint (~3-4h per §M.6.4).
|
|
53
|
+
3. Aggregate in their dashboard : P50/P95 render durations per ComponentType, top validation:failed codes, action dispatch counts.
|
|
54
|
+
|
|
55
|
+
## [5.5.1] - 2026-04-27
|
|
56
|
+
|
|
57
|
+
### Security — Iframe whitelist bug fix
|
|
58
|
+
|
|
59
|
+
- **`validateIframeDomain` predicate bug** : `services/validation.ts:572` checked `allowed === 'localhost'` (the whitelist entry being literally the string `'localhost'`) instead of `domain === 'localhost'` (the URL's hostname being localhost). Once `'localhost'` was added to `DEFAULT_IFRAME_DOMAINS` (Sprint 0+, present since the beginning), the whitelist became **fully inoperative** — every external URL passed the check. Affected all iframe + video components rendered through `<UIResourceRenderer>` / `<StreamingUIRenderer>`.
|
|
60
|
+
- **Fix** : loopback (`localhost` and `127.0.0.x`) is detected on the URL's hostname only ; the literal `'localhost'` whitelist entry is skipped from the iteration. Whitelisted domains + their subdomains continue to pass as before.
|
|
61
|
+
- **Impact** : pre-v5.5.1, an LLM-generated iframe with `https://evil.example.com/x` would render. Post-v5.5.1, it's rejected with `DOMAIN_NOT_WHITELISTED` (the existing code, behavior now actually fires).
|
|
62
|
+
- **Backward compat** : Apps using `<iframe url="https://..." />` with **legitimately whitelisted** URLs are unaffected. Apps relying on the bug to display arbitrary iframes will now see them rejected — workaround is `iframePolicy: 'extend'` + `customIframeDomains: [...]` or `iframePolicy: 'allow-all'` (latter for trusted contexts only).
|
|
63
|
+
|
|
64
|
+
### Tests
|
|
65
|
+
|
|
66
|
+
- `services/validation.test.ts` : new `describe('validateIframeDomain — security regression (v5.5.1)')` block — 8 tests locking in the correct behavior (rejects external + typo-squat domains, accepts whitelist + subdomains + localhost + 127.0.0.x, respects `allow-all` and `extend` policies).
|
|
67
|
+
- 2 test cosmetics: `chartType` typo → `type` at `validation.test.ts:50`, `artifact` moved from `PASSTHROUGH_TYPES` to `VALIDATED_TYPES` (it has its own case since v5.5.0).
|
|
68
|
+
- Total solid suite : **530/530 tests pass** (vs 523 on v5.5.0, +7 net).
|
|
69
|
+
|
|
8
70
|
## [5.5.0] - 2026-04-27
|
|
9
71
|
|
|
10
72
|
B.1 PR2 — `services/validation.ts` migration vers Zod schemas spec-driven (cf. audit deposium `MCP-UI-AUDIT-2026-04-26.md` §I + greenlight §J). **Non-breaking, aucune migration consumer requise.**
|
|
@@ -7,6 +7,7 @@ const useAction = require("../hooks/useAction.cjs");
|
|
|
7
7
|
const validation = require("../services/validation.cjs");
|
|
8
8
|
const useConditionalField = require("../hooks/useConditionalField.cjs");
|
|
9
9
|
const useFormPersistence = require("../hooks/useFormPersistence.cjs");
|
|
10
|
+
const MCPUITelemetryContext = require("../context/MCPUITelemetryContext.cjs");
|
|
10
11
|
var _tmpl$ = /* @__PURE__ */ web.template(`<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">`), _tmpl$2 = /* @__PURE__ */ web.template(`<div class="mt-4 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md"><p class="text-sm text-red-600 dark:text-red-400"role=alert>`), _tmpl$3 = /* @__PURE__ */ web.template(`<div class="mt-4 flex items-center gap-3 p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md"><span class="text-sm text-blue-700 dark:text-blue-300"><!$><!/> in <!$><!/>s...</span><button type=button class="text-sm text-blue-600 dark:text-blue-400 underline hover:text-blue-800 dark:hover:text-blue-200">Cancel`), _tmpl$4 = /* @__PURE__ */ web.template(`<button type=button class="px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">Reset`), _tmpl$5 = /* @__PURE__ */ web.template(`<div class="w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4"><!$><!/><form novalidate><!$><!/><div></div><!$><!/><!$><!/><div class="flex gap-2 pt-4 mt-4 border-t border-gray-200 dark:border-gray-700"><button type=submit class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"></button><!$><!/>`), _tmpl$6 = /* @__PURE__ */ web.template(`<p class="text-xs text-gray-500 dark:text-gray-400 mb-3"><!$><!/> champ<!$><!/> pré-rempli<!$><!/> sur <!$><!/>`), _tmpl$7 = /* @__PURE__ */ web.template(`<span class="flex items-center gap-2"><span class="animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full"></span>Submitting...`);
|
|
11
12
|
function getFieldDefault(type) {
|
|
12
13
|
switch (type) {
|
|
@@ -29,6 +30,7 @@ const FormRenderer = (props) => {
|
|
|
29
30
|
const {
|
|
30
31
|
execute
|
|
31
32
|
} = useAction.useAction();
|
|
33
|
+
const telemetry = MCPUITelemetryContext.useTelemetry();
|
|
32
34
|
let clearPersisted;
|
|
33
35
|
const getVisibleFields = () => {
|
|
34
36
|
return params().fields.filter((field) => {
|
|
@@ -116,7 +118,7 @@ const FormRenderer = (props) => {
|
|
|
116
118
|
}
|
|
117
119
|
};
|
|
118
120
|
const handleSubmit = async (e) => {
|
|
119
|
-
var _a, _b, _c, _d, _e;
|
|
121
|
+
var _a, _b, _c, _d, _e, _f;
|
|
120
122
|
e.preventDefault();
|
|
121
123
|
setIsSubmitting(true);
|
|
122
124
|
setErrors({});
|
|
@@ -166,7 +168,16 @@ const FormRenderer = (props) => {
|
|
|
166
168
|
if (clearPersisted) {
|
|
167
169
|
clearPersisted();
|
|
168
170
|
}
|
|
169
|
-
(
|
|
171
|
+
if (telemetry) {
|
|
172
|
+
telemetry.dispatch({
|
|
173
|
+
type: "action:dispatched",
|
|
174
|
+
id: props.component.id,
|
|
175
|
+
componentType: "form",
|
|
176
|
+
actionName: ((_e = params().submitAction) == null ? void 0 : _e.toolName) ?? "submit",
|
|
177
|
+
ts: Date.now()
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
(_f = props.onSubmit) == null ? void 0 : _f.call(props, visibleFormData);
|
|
170
181
|
setIsSubmitting(false);
|
|
171
182
|
};
|
|
172
183
|
const layoutClass = () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormRenderer.cjs","sources":["../../src/components/FormRenderer.tsx"],"sourcesContent":["/**\n * FormRenderer - Main form component\n * Sprint 1: Form Foundation\n * Sprint 2: Conditional field visibility (showWhen)\n * Sprint 4: Form state persistence\n */\n\nimport { Component, createSignal, For, Show, onMount, createEffect, onCleanup } from 'solid-js'\nimport { FormFieldRenderer } from './FormFieldRenderer'\nimport type { UIComponent, FormComponentParams, FormFieldParams } from '../types'\nimport { useAction } from '../hooks/useAction'\nimport { validateFormData } from '../services/validation'\nimport { evaluateCondition } from '../hooks/useConditionalField'\nimport { useFormPersistence } from '../hooks/useFormPersistence'\n\nexport interface FormRendererProps {\n component: UIComponent\n onSubmit?: (data: Record<string, any>) => void\n onError?: (errors: Record<string, string>) => void\n}\n\n/**\n * Get default value for a field type\n */\nfunction getFieldDefault(type: FormFieldParams['type']): any {\n switch (type) {\n case 'checkbox':\n return false\n case 'number':\n return undefined\n case 'select':\n case 'radio':\n return ''\n default:\n return ''\n }\n}\n\nexport const FormRenderer: Component<FormRendererProps> = (props) => {\n const params = () => props.component.params as FormComponentParams\n const [formData, setFormData] = createSignal<Record<string, any>>({})\n const [errors, setErrors] = createSignal<Record<string, string>>({})\n const [isSubmitting, setIsSubmitting] = createSignal(false)\n const { execute } = useAction()\n\n // Form persistence (Sprint 4)\n let clearPersisted: (() => void) | undefined\n\n /**\n * Get fields that are currently visible based on showWhen conditions\n */\n const getVisibleFields = (): FormFieldParams[] => {\n return params().fields.filter((field) => {\n if (!field.showWhen) return true\n return evaluateCondition(field.showWhen, formData())\n })\n }\n\n // Auto-submit countdown state (v4.2.0)\n const [countdown, setCountdown] = createSignal<number | null>(null)\n let countdownTimer: ReturnType<typeof setInterval> | null = null\n const [userInteracted, setUserInteracted] = createSignal(false)\n\n const cancelCountdown = () => {\n if (countdownTimer) {\n clearInterval(countdownTimer)\n countdownTimer = null\n }\n setCountdown(null)\n }\n\n const handleUserInteraction = () => {\n if (!userInteracted()) {\n setUserInteracted(true)\n cancelCountdown()\n }\n }\n\n onCleanup(() => cancelCountdown())\n\n /**\n * Check if all required fields have prefill values\n */\n const allRequiredPrefilled = (): boolean => {\n return params().fields\n .filter((f) => f.required)\n .every((f) => f.prefill != null)\n }\n\n // Initialize form data with default values, applying prefill (v4.2.0)\n const initializeForm = (clearStorage = false) => {\n const initial: Record<string, any> = {}\n for (const field of params().fields) {\n // prefill takes priority over defaultValue\n initial[field.name] = field.prefill ?? field.defaultValue ?? getFieldDefault(field.type)\n }\n setFormData(initial)\n setErrors({})\n\n // Clear persisted data if requested\n if (clearStorage && clearPersisted) {\n clearPersisted()\n }\n }\n\n // Initialize on mount\n onMount(() => {\n initializeForm()\n })\n\n // Setup persistence if persistKey is provided (Sprint 4)\n createEffect(() => {\n const persistKey = params().persistKey\n if (persistKey) {\n const persistence = useFormPersistence({\n persistKey,\n formData,\n setFormData,\n excludeFields: params().excludeFromPersistence,\n expiresIn: params().persistExpiresIn,\n })\n clearPersisted = persistence.clearPersisted\n }\n })\n\n // Auto-submit countdown (v4.2.0)\n createEffect(() => {\n const delay = params().autoSubmitDelay\n if (!delay || !allRequiredPrefilled() || userInteracted()) return\n\n let remaining = Math.ceil(delay / 1000)\n setCountdown(remaining)\n\n countdownTimer = setInterval(() => {\n remaining--\n if (remaining <= 0) {\n cancelCountdown()\n // Trigger submit programmatically\n const form = document.querySelector(`#form-${props.component.id}`) as HTMLFormElement | null\n if (form) form.requestSubmit()\n } else {\n setCountdown(remaining)\n }\n }, 1000)\n })\n\n const handleFieldChange = (name: string, value: any) => {\n handleUserInteraction()\n setFormData((prev) => ({ ...prev, [name]: value }))\n // Clear error on change\n if (errors()[name]) {\n setErrors((prev) => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { [name]: _removed, ...rest } = prev\n return rest\n })\n }\n }\n\n const handleSubmit = async (e: Event) => {\n e.preventDefault()\n setIsSubmitting(true)\n setErrors({})\n\n // Get only visible fields for validation and submission\n const visibleFields = getVisibleFields()\n const visibleFieldNames = new Set(visibleFields.map((f) => f.name))\n\n // Filter form data to only include visible fields\n const visibleFormData: Record<string, any> = {}\n for (const [key, value] of Object.entries(formData())) {\n if (visibleFieldNames.has(key)) {\n visibleFormData[key] = value\n }\n }\n\n // Validate only visible fields\n const validationResult = validateFormData(visibleFormData, visibleFields)\n if (!validationResult.valid) {\n setErrors(validationResult.errors)\n setIsSubmitting(false)\n props.onError?.(validationResult.errors)\n return\n }\n\n // Submit via tool call if specified\n if (params().submitAction?.toolName) {\n try {\n const result = await execute(params().submitAction!.toolName, {\n ...params().submitAction!.params,\n formData: visibleFormData,\n })\n if (!result.success) {\n setErrors({ _form: result.error || 'Submission failed' })\n setIsSubmitting(false)\n props.onError?.({ _form: result.error || 'Submission failed' })\n return\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Submission failed'\n setErrors({ _form: errorMessage })\n setIsSubmitting(false)\n props.onError?.({ _form: errorMessage })\n return\n }\n }\n\n // Clear persisted data on successful submit\n if (clearPersisted) {\n clearPersisted()\n }\n\n props.onSubmit?.(visibleFormData)\n setIsSubmitting(false)\n }\n\n const layoutClass = () => {\n switch (params().layout) {\n case 'horizontal':\n return 'grid grid-cols-2 gap-4'\n case 'inline':\n return 'flex flex-wrap gap-4 items-end'\n default:\n return 'space-y-4'\n }\n }\n\n return (\n <div class=\"w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4\">\n <Show when={params().title}>\n <h3 class=\"text-lg font-semibold text-gray-900 dark:text-white mb-4\">\n {params().title}\n </h3>\n </Show>\n\n <form id={`form-${props.component.id}`} onSubmit={handleSubmit} noValidate>\n {/* Proposal 3: prefill summary */}\n <Show when={params().fields.some((f) => f.prefill != null)}>\n {(() => {\n const prefilled = params().fields.filter((f) => f.prefill != null).length\n const total = params().fields.length\n return (\n <p class=\"text-xs text-gray-500 dark:text-gray-400 mb-3\">\n {prefilled} champ{prefilled > 1 ? 's' : ''} pré-rempli{prefilled > 1 ? 's' : ''} sur {total}\n </p>\n )\n })()}\n </Show>\n <div class={layoutClass()}>\n <For each={params().fields}>\n {(field) => (\n <FormFieldRenderer\n field={field}\n value={formData()[field.name]}\n error={errors()[field.name]}\n onChange={(value) => handleFieldChange(field.name, value)}\n disabled={isSubmitting() || field.disabled}\n formData={formData}\n />\n )}\n </For>\n </div>\n\n <Show when={errors()._form}>\n <div class=\"mt-4 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md\">\n <p class=\"text-sm text-red-600 dark:text-red-400\" role=\"alert\">\n {errors()._form}\n </p>\n </div>\n </Show>\n\n {/* Auto-submit countdown (v4.2.0) */}\n <Show when={countdown() != null}>\n <div class=\"mt-4 flex items-center gap-3 p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md\">\n <span class=\"text-sm text-blue-700 dark:text-blue-300\">\n {params().submitLabel || 'Submit'} in {countdown()}s...\n </span>\n <button\n type=\"button\"\n onClick={() => { cancelCountdown(); setUserInteracted(true) }}\n class=\"text-sm text-blue-600 dark:text-blue-400 underline hover:text-blue-800 dark:hover:text-blue-200\"\n >\n Cancel\n </button>\n </div>\n </Show>\n\n <div class=\"flex gap-2 pt-4 mt-4 border-t border-gray-200 dark:border-gray-700\">\n <button\n type=\"submit\"\n disabled={isSubmitting()}\n class=\"px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n {isSubmitting() ? (\n <span class=\"flex items-center gap-2\">\n <span class=\"animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full\" />\n Submitting...\n </span>\n ) : (\n params().submitLabel || 'Submit'\n )}\n </button>\n <Show when={params().showReset}>\n <button\n type=\"button\"\n onClick={() => initializeForm(true)}\n disabled={isSubmitting()}\n class=\"px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n Reset\n </button>\n </Show>\n </div>\n </form>\n </div>\n )\n}\n"],"names":["getFieldDefault","type","undefined","FormRenderer","props","params","component","formData","setFormData","createSignal","errors","setErrors","isSubmitting","setIsSubmitting","execute","useAction","clearPersisted","getVisibleFields","fields","filter","field","showWhen","evaluateCondition","countdown","setCountdown","countdownTimer","userInteracted","setUserInteracted","cancelCountdown","clearInterval","handleUserInteraction","onCleanup","allRequiredPrefilled","f","required","every","prefill","initializeForm","clearStorage","initial","name","defaultValue","onMount","createEffect","persistKey","persistence","useFormPersistence","excludeFields","excludeFromPersistence","expiresIn","persistExpiresIn","delay","autoSubmitDelay","remaining","Math","ceil","setInterval","form","document","querySelector","id","requestSubmit","handleFieldChange","value","prev","_removed","rest","handleSubmit","e","preventDefault","visibleFields","visibleFieldNames","Set","map","visibleFormData","key","Object","entries","has","validationResult","validateFormData","valid","onError","submitAction","toolName","result","success","_form","error","errorMessage","Error","message","onSubmit","layoutClass","layout","_el$","_$getNextElement","_tmpl$5","_el$25","firstChild","_el$26","_co$7","_$getNextMarker","nextSibling","_el$3","_el$19","_el$20","_co$4","_el$4","_el$21","_el$22","_co$5","_el$23","_el$24","_co$6","_el$14","_el$15","_el$17","_el$18","_co$3","_$insert","_$createComponent","Show","when","title","children","_el$2","_tmpl$","addEventListener","some","prefilled","length","total","_el$27","_tmpl$6","_el$31","_el$32","_co$8","_el$28","_el$33","_el$34","_co$9","_el$29","_el$35","_el$36","_co$0","_el$30","_el$37","_el$38","_co$1","For","each","FormFieldRenderer","onChange","disabled","_el$5","_tmpl$2","_el$6","_el$7","_tmpl$3","_el$8","_el$1","_el$10","_co$","_el$9","_el$11","_el$12","_co$2","_el$13","submitLabel","$$click","_$runHydrationEvents","_c$","_$memo","_tmpl$7","showReset","_el$16","_tmpl$4","_$effect","_$setProperty","_p$","_v$","_v$2","_v$3","_$setAttribute","t","_$className","a","_$delegateEvents"],"mappings":";;;;;;;;;;AAwBA,SAASA,gBAAgBC,MAAoC;AAC3D,UAAQA,MAAAA;AAAAA,IACN,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAOC;AAAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EAAA;AAEb;AAEO,MAAMC,eAA8CC,CAAAA,UAAU;AACnE,QAAMC,SAASA,MAAMD,MAAME,UAAUD;AACrC,QAAM,CAACE,UAAUC,WAAW,IAAIC,QAAAA,aAAkC,CAAA,CAAE;AACpE,QAAM,CAACC,QAAQC,SAAS,IAAIF,QAAAA,aAAqC,CAAA,CAAE;AACnE,QAAM,CAACG,cAAcC,eAAe,IAAIJ,QAAAA,aAAa,KAAK;AAC1D,QAAM;AAAA,IAAEK;AAAAA,EAAAA,IAAYC,oBAAAA;AAGpB,MAAIC;AAKJ,QAAMC,mBAAmBA,MAAyB;AAChD,WAAOZ,OAAAA,EAASa,OAAOC,OAAQC,CAAAA,UAAU;AACvC,UAAI,CAACA,MAAMC,SAAU,QAAO;AAC5B,aAAOC,sCAAkBF,MAAMC,UAAUd,SAAAA,CAAU;AAAA,IACrD,CAAC;AAAA,EACH;AAGA,QAAM,CAACgB,WAAWC,YAAY,IAAIf,QAAAA,aAA4B,IAAI;AAClE,MAAIgB,iBAAwD;AAC5D,QAAM,CAACC,gBAAgBC,iBAAiB,IAAIlB,QAAAA,aAAa,KAAK;AAE9D,QAAMmB,kBAAkBA,MAAM;AAC5B,QAAIH,gBAAgB;AAClBI,oBAAcJ,cAAc;AAC5BA,uBAAiB;AAAA,IACnB;AACAD,iBAAa,IAAI;AAAA,EACnB;AAEA,QAAMM,wBAAwBA,MAAM;AAClC,QAAI,CAACJ,kBAAkB;AACrBC,wBAAkB,IAAI;AACtBC,sBAAAA;AAAAA,IACF;AAAA,EACF;AAEAG,UAAAA,UAAU,MAAMH,iBAAiB;AAKjC,QAAMI,uBAAuBA,MAAe;AAC1C,WAAO3B,OAAAA,EAASa,OACbC,OAAQc,CAAAA,MAAMA,EAAEC,QAAQ,EACxBC,MAAOF,CAAAA,MAAMA,EAAEG,WAAW,IAAI;AAAA,EACnC;AAGA,QAAMC,iBAAiBA,CAACC,eAAe,UAAU;AAC/C,UAAMC,UAA+B,CAAA;AACrC,eAAWnB,SAASf,OAAAA,EAASa,QAAQ;AAEnCqB,cAAQnB,MAAMoB,IAAI,IAAIpB,MAAMgB,WAAWhB,MAAMqB,gBAAgBzC,gBAAgBoB,MAAMnB,IAAI;AAAA,IACzF;AACAO,gBAAY+B,OAAO;AACnB5B,cAAU,CAAA,CAAE;AAGZ,QAAI2B,gBAAgBtB,gBAAgB;AAClCA,qBAAAA;AAAAA,IACF;AAAA,EACF;AAGA0B,UAAAA,QAAQ,MAAM;AACZL,mBAAAA;AAAAA,EACF,CAAC;AAGDM,UAAAA,aAAa,MAAM;AACjB,UAAMC,aAAavC,SAASuC;AAC5B,QAAIA,YAAY;AACd,YAAMC,cAAcC,mBAAAA,mBAAmB;AAAA,QACrCF;AAAAA,QACArC;AAAAA,QACAC;AAAAA,QACAuC,eAAe1C,SAAS2C;AAAAA,QACxBC,WAAW5C,SAAS6C;AAAAA,MAAAA,CACrB;AACDlC,uBAAiB6B,YAAY7B;AAAAA,IAC/B;AAAA,EACF,CAAC;AAGD2B,UAAAA,aAAa,MAAM;AACjB,UAAMQ,QAAQ9C,SAAS+C;AACvB,QAAI,CAACD,SAAS,CAACnB,qBAAAA,KAA0BN,iBAAkB;AAE3D,QAAI2B,YAAYC,KAAKC,KAAKJ,QAAQ,GAAI;AACtC3B,iBAAa6B,SAAS;AAEtB5B,qBAAiB+B,YAAY,MAAM;AACjCH;AACA,UAAIA,aAAa,GAAG;AAClBzB,wBAAAA;AAEA,cAAM6B,OAAOC,SAASC,cAAc,SAASvD,MAAME,UAAUsD,EAAE,EAAE;AACjE,YAAIH,WAAWI,cAAAA;AAAAA,MACjB,OAAO;AACLrC,qBAAa6B,SAAS;AAAA,MACxB;AAAA,IACF,GAAG,GAAI;AAAA,EACT,CAAC;AAED,QAAMS,oBAAoBA,CAACtB,MAAcuB,UAAe;AACtDjC,0BAAAA;AACAtB,gBAAawD,CAAAA,UAAU;AAAA,MAAE,GAAGA;AAAAA,MAAM,CAACxB,IAAI,GAAGuB;AAAAA,IAAAA,EAAQ;AAElD,QAAIrD,OAAAA,EAAS8B,IAAI,GAAG;AAClB7B,gBAAWqD,CAAAA,SAAS;AAElB,cAAM;AAAA,UAAE,CAACxB,IAAI,GAAGyB;AAAAA,UAAU,GAAGC;AAAAA,QAAAA,IAASF;AACtC,eAAOE;AAAAA,MACT,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAMC,eAAe,OAAOC,MAAa;;AACvCA,MAAEC,eAAAA;AACFxD,oBAAgB,IAAI;AACpBF,cAAU,CAAA,CAAE;AAGZ,UAAM2D,gBAAgBrD,iBAAAA;AACtB,UAAMsD,oBAAoB,IAAIC,IAAIF,cAAcG,IAAKxC,CAAAA,MAAMA,EAAEO,IAAI,CAAC;AAGlE,UAAMkC,kBAAuC,CAAA;AAC7C,eAAW,CAACC,KAAKZ,KAAK,KAAKa,OAAOC,QAAQtE,SAAAA,CAAU,GAAG;AACrD,UAAIgE,kBAAkBO,IAAIH,GAAG,GAAG;AAC9BD,wBAAgBC,GAAG,IAAIZ;AAAAA,MACzB;AAAA,IACF;AAGA,UAAMgB,mBAAmBC,WAAAA,iBAAiBN,iBAAiBJ,aAAa;AACxE,QAAI,CAACS,iBAAiBE,OAAO;AAC3BtE,gBAAUoE,iBAAiBrE,MAAM;AACjCG,sBAAgB,KAAK;AACrBT,kBAAM8E,YAAN9E,+BAAgB2E,iBAAiBrE;AACjC;AAAA,IACF;AAGA,SAAIL,YAAAA,EAAS8E,iBAAT9E,mBAAuB+E,UAAU;AACnC,UAAI;AACF,cAAMC,SAAS,MAAMvE,QAAQT,OAAAA,EAAS8E,aAAcC,UAAU;AAAA,UAC5D,GAAG/E,OAAAA,EAAS8E,aAAc9E;AAAAA,UAC1BE,UAAUmE;AAAAA,QAAAA,CACX;AACD,YAAI,CAACW,OAAOC,SAAS;AACnB3E,oBAAU;AAAA,YAAE4E,OAAOF,OAAOG,SAAS;AAAA,UAAA,CAAqB;AACxD3E,0BAAgB,KAAK;AACrBT,sBAAM8E,YAAN9E,+BAAgB;AAAA,YAAEmF,OAAOF,OAAOG,SAAS;AAAA,UAAA;AACzC;AAAA,QACF;AAAA,MACF,SAASA,OAAO;AACd,cAAMC,eAAeD,iBAAiBE,QAAQF,MAAMG,UAAU;AAC9DhF,kBAAU;AAAA,UAAE4E,OAAOE;AAAAA,QAAAA,CAAc;AACjC5E,wBAAgB,KAAK;AACrBT,oBAAM8E,YAAN9E,+BAAgB;AAAA,UAAEmF,OAAOE;AAAAA,QAAAA;AACzB;AAAA,MACF;AAAA,IACF;AAGA,QAAIzE,gBAAgB;AAClBA,qBAAAA;AAAAA,IACF;AAEAZ,gBAAMwF,aAANxF,+BAAiBsE;AACjB7D,oBAAgB,KAAK;AAAA,EACvB;AAEA,QAAMgF,cAAcA,MAAM;AACxB,YAAQxF,OAAAA,EAASyF,QAAAA;AAAAA,MACf,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT;AACE,eAAO;AAAA,IAAA;AAAA,EAEb;AAEA,UAAA,MAAA;AAAA,QAAAC,OAAAC,IAAAA,eAAAC,OAAA,GAAAC,SAAAH,KAAAI,YAAA,CAAAC,QAAAC,KAAA,IAAAC,IAAAA,cAAAJ,OAAAK,WAAA,GAAAC,QAAAJ,OAAAG,aAAAE,SAAAD,MAAAL,YAAA,CAAAO,QAAAC,KAAA,IAAAL,IAAAA,cAAAG,OAAAF,WAAA,GAAAK,QAAAF,OAAAH,aAAAM,SAAAD,MAAAL,aAAA,CAAAO,QAAAC,KAAA,IAAAT,IAAAA,cAAAO,OAAAN,WAAA,GAAAS,SAAAF,OAAAP,aAAA,CAAAU,QAAAC,KAAA,IAAAZ,IAAAA,cAAAU,OAAAT,WAAA,GAAAY,SAAAF,OAAAV,aAAAa,SAAAD,OAAAhB,YAAAkB,SAAAD,OAAAb,aAAA,CAAAe,QAAAC,KAAA,IAAAjB,IAAAA,cAAAe,OAAAd,WAAA;AAAAiB,eAAAzB,MAAA0B,IAAAA,gBAEKC,cAAI;AAAA,MAAA,IAACC,OAAI;AAAA,eAAEtH,SAASuH;AAAAA,MAAK;AAAA,MAAA,IAAAC,WAAA;AAAA,YAAAC,QAAA9B,IAAAA,eAAA+B,MAAA;AAAAP,YAAAA,OAAAM,OAAA,MAErBzH,OAAAA,EAASuH,KAAK;AAAA,eAAAE;AAAAA,MAAA;AAAA,IAAA,CAAA,GAAA1B,QAAAC,KAAA;AAAAG,UAAAwB,iBAAA,UAI+B7D,YAAY;AAAAqD,eAAAhB,OAAAiB,IAAAA,gBAE3DC,cAAI;AAAA,MAAA,IAACC,OAAI;AAAA,eAAEtH,SAASa,OAAO+G,KAAMhG,CAAAA,MAAMA,EAAEG,WAAW,IAAI;AAAA,MAAC;AAAA,MAAA,IAAAyF,WAAA;AAAA,gBACtD,MAAM;AACN,gBAAMK,YAAY7H,SAASa,OAAOC,OAAQc,CAAAA,MAAMA,EAAEG,WAAW,IAAI,EAAE+F;AACnE,gBAAMC,QAAQ/H,SAASa,OAAOiH;AAC9B,kBAAA,MAAA;AAAA,gBAAAE,SAAArC,IAAAA,eAAAsC,OAAA,GAAAC,SAAAF,OAAAlC,YAAA,CAAAqC,QAAAC,KAAA,IAAAnC,IAAAA,cAAAiC,OAAAhC,WAAA,GAAAmC,SAAAF,OAAAjC,aAAAoC,SAAAD,OAAAnC,aAAA,CAAAqC,QAAAC,KAAA,IAAAvC,IAAAA,cAAAqC,OAAApC,WAAA,GAAAuC,SAAAF,OAAArC,aAAAwC,SAAAD,OAAAvC,aAAA,CAAAyC,QAAAC,KAAA,IAAA3C,IAAAA,cAAAyC,OAAAxC,WAAA,GAAA2C,SAAAF,OAAAzC,aAAA4C,SAAAD,OAAA3C,aAAA,CAAA6C,QAAAC,KAAA,IAAA/C,kBAAA6C,OAAA5C,WAAA;AAAAiB,gBAAAA,OAAAa,QAEKH,WAASM,QAAAC,KAAA;AAAAjB,gBAAAA,OAAAa,QAAQH,YAAY,IAAI,MAAM,IAAEU,QAAAC,KAAA;AAAArB,gBAAAA,OAAAa,QAAaH,YAAY,IAAI,MAAM,IAAEc,QAAAC,KAAA;AAAAzB,gBAAAA,OAAAa,QAAOD,OAAKgB,QAAAC,KAAA;AAAA,mBAAAhB;AAAAA,UAAA,GAAA;AAAA,QAGjG,GAAA;AAAA,MAAI;AAAA,IAAA,CAAA,GAAA3B,QAAAC,KAAA;AAAAa,eAAAZ,OAAAa,IAAAA,gBAGH6B,aAAG;AAAA,MAAA,IAACC,OAAI;AAAA,eAAElJ,SAASa;AAAAA,MAAM;AAAA,MAAA2G,UACtBzG,CAAAA,UAAKqG,IAAAA,gBACJ+B,qCAAiB;AAAA,QAChBpI;AAAAA,QAAY,IACZ2C,QAAK;AAAA,iBAAExD,SAAAA,EAAWa,MAAMoB,IAAI;AAAA,QAAC;AAAA,QAAA,IAC7BgD,QAAK;AAAA,iBAAE9E,OAAAA,EAASU,MAAMoB,IAAI;AAAA,QAAC;AAAA,QAC3BiH,UAAW1F,CAAAA,UAAUD,kBAAkB1C,MAAMoB,MAAMuB,KAAK;AAAA,QAAC,IACzD2F,WAAQ;AAAA,iBAAE9I,aAAAA,KAAkBQ,MAAMsI;AAAAA,QAAQ;AAAA,QAC1CnJ;AAAAA,MAAAA,CAAkB;AAAA,IAAA,CAErB,CAAA;AAAAiH,eAAAhB,OAAAiB,IAAAA,gBAIJC,cAAI;AAAA,MAAA,IAACC,OAAI;AAAA,eAAEjH,SAAS6E;AAAAA,MAAK;AAAA,MAAA,IAAAsC,WAAA;AAAA,YAAA8B,QAAA3D,IAAAA,eAAA4D,OAAA,GAAAC,QAAAF,MAAAxD;AAAAqB,YAAAA,OAAAqC,OAAA,MAGnBnJ,OAAAA,EAAS6E,KAAK;AAAA,eAAAoE;AAAAA,MAAA;AAAA,IAAA,CAAA,GAAA7C,QAAAC,KAAA;AAAAS,eAAAhB,OAAAiB,IAAAA,gBAMpBC,cAAI;AAAA,MAAA,IAACC,OAAI;AAAA,eAAEpG,eAAe;AAAA,MAAI;AAAA,MAAA,IAAAsG,WAAA;AAAA,YAAAiC,QAAA9D,IAAAA,eAAA+D,OAAA,GAAAC,QAAAF,MAAA3D,YAAA8D,QAAAD,MAAA7D,YAAA,CAAA+D,QAAAC,IAAA,IAAA7D,IAAAA,cAAA2D,MAAA1D,WAAA,GAAA6D,QAAAF,OAAA3D,aAAA8D,SAAAD,MAAA7D,aAAA,CAAA+D,QAAAC,KAAA,IAAAjE,kBAAA+D,OAAA9D,WAAA;AAAA+D,eAAA/D;AAAAA,YAAAiE,SAAAR,MAAAzD;AAAAiB,YAAAA,OAAAwC,OAAA,MAGxB3J,OAAAA,EAASoK,eAAe,UAAQP,QAAAC,IAAA;AAAA3C,YAAAA,OAAAwC,OAAMzI,WAAS+I,QAAAC,KAAA;AAAAC,eAAAE,UAIvC,MAAM;AAAE9I,0BAAAA;AAAmBD,4BAAkB,IAAI;AAAA,QAAE;AAACgJ,+BAAAA;AAAA,eAAAb;AAAAA,MAAA;AAAA,IAAA,CAAA,GAAA7C,QAAAC,KAAA;AAAAM,QAAAA,OAAAJ,SAAA,MAAA;AAAA,UAAAwD,MAAAC,IAAAA,KAAA,MAAA,CAAA,CAc9DjK,cAAc;AAAA,aAAA,MAAdgK,QAAA5E,IAAAA,eAAA8E,OAAA,IAMCzK,OAAAA,EAASoK,eAAe;AAAA,IACzB,IAAA;AAAAjD,eAAAL,QAAAM,IAAAA,gBAEFC,cAAI;AAAA,MAAA,IAACC,OAAI;AAAA,eAAEtH,SAAS0K;AAAAA,MAAS;AAAA,MAAA,IAAAlD,WAAA;AAAA,YAAAmD,SAAAhF,IAAAA,eAAAiF,OAAA;AAAAD,eAAAN,UAGjB,MAAMrI,eAAe,IAAI;AAAC6I,YAAAA,aAAAC,IAAAA,YAAAH,QAAA,YACzBpK,aAAAA,CAAc,CAAA;AAAA+J,+BAAAA;AAAA,eAAAK;AAAAA,MAAA;AAAA,IAAA,CAAA,GAAA1D,QAAAC,KAAA;AAAA2D,QAAAA,OAAAE,CAAAA,QAAA;AAAA,UAAAC,MAvEtB,QAAQjL,MAAME,UAAUsD,EAAE,IAAE0H,OAaxBzF,eAAa0F,OA0CX3K,aAAAA;AAAcyK,cAAAD,IAAAhH,KAAAoH,IAAAA,aAAAhF,OAAA,MAAA4E,IAAAhH,IAAAiH,GAAA;AAAAC,eAAAF,IAAAK,KAAAC,IAAAA,UAAA9E,OAAAwE,IAAAK,IAAAH,IAAA;AAAAC,eAAAH,IAAAO,KAAAR,IAAAA,YAAA/D,QAAA,YAAAgE,IAAAO,IAAAJ,IAAA;AAAA,aAAAH;AAAAA,IAAA,GAAA;AAAA,MAAAhH,GAAAlE;AAAAA,MAAAuL,GAAAvL;AAAAA,MAAAyL,GAAAzL;AAAAA,IAAAA,CAAA;AAAA,WAAA6F;AAAAA,EAAA,GAAA;AA0BpC;AAAC6F,IAAAA,eAAA,CAAA,OAAA,CAAA;;"}
|
|
1
|
+
{"version":3,"file":"FormRenderer.cjs","sources":["../../src/components/FormRenderer.tsx"],"sourcesContent":["/**\n * FormRenderer - Main form component\n * Sprint 1: Form Foundation\n * Sprint 2: Conditional field visibility (showWhen)\n * Sprint 4: Form state persistence\n */\n\nimport { Component, createSignal, For, Show, onMount, createEffect, onCleanup } from 'solid-js'\nimport { FormFieldRenderer } from './FormFieldRenderer'\nimport type { UIComponent, FormComponentParams, FormFieldParams } from '../types'\nimport { useAction } from '../hooks/useAction'\nimport { validateFormData } from '../services/validation'\nimport { evaluateCondition } from '../hooks/useConditionalField'\nimport { useFormPersistence } from '../hooks/useFormPersistence'\nimport { useTelemetry } from '../context/MCPUITelemetryContext'\n\nexport interface FormRendererProps {\n component: UIComponent\n onSubmit?: (data: Record<string, any>) => void\n onError?: (errors: Record<string, string>) => void\n}\n\n/**\n * Get default value for a field type\n */\nfunction getFieldDefault(type: FormFieldParams['type']): any {\n switch (type) {\n case 'checkbox':\n return false\n case 'number':\n return undefined\n case 'select':\n case 'radio':\n return ''\n default:\n return ''\n }\n}\n\nexport const FormRenderer: Component<FormRendererProps> = (props) => {\n const params = () => props.component.params as FormComponentParams\n const [formData, setFormData] = createSignal<Record<string, any>>({})\n const [errors, setErrors] = createSignal<Record<string, string>>({})\n const [isSubmitting, setIsSubmitting] = createSignal(false)\n const { execute } = useAction()\n const telemetry = useTelemetry()\n\n // Form persistence (Sprint 4)\n let clearPersisted: (() => void) | undefined\n\n /**\n * Get fields that are currently visible based on showWhen conditions\n */\n const getVisibleFields = (): FormFieldParams[] => {\n return params().fields.filter((field) => {\n if (!field.showWhen) return true\n return evaluateCondition(field.showWhen, formData())\n })\n }\n\n // Auto-submit countdown state (v4.2.0)\n const [countdown, setCountdown] = createSignal<number | null>(null)\n let countdownTimer: ReturnType<typeof setInterval> | null = null\n const [userInteracted, setUserInteracted] = createSignal(false)\n\n const cancelCountdown = () => {\n if (countdownTimer) {\n clearInterval(countdownTimer)\n countdownTimer = null\n }\n setCountdown(null)\n }\n\n const handleUserInteraction = () => {\n if (!userInteracted()) {\n setUserInteracted(true)\n cancelCountdown()\n }\n }\n\n onCleanup(() => cancelCountdown())\n\n /**\n * Check if all required fields have prefill values\n */\n const allRequiredPrefilled = (): boolean => {\n return params().fields\n .filter((f) => f.required)\n .every((f) => f.prefill != null)\n }\n\n // Initialize form data with default values, applying prefill (v4.2.0)\n const initializeForm = (clearStorage = false) => {\n const initial: Record<string, any> = {}\n for (const field of params().fields) {\n // prefill takes priority over defaultValue\n initial[field.name] = field.prefill ?? field.defaultValue ?? getFieldDefault(field.type)\n }\n setFormData(initial)\n setErrors({})\n\n // Clear persisted data if requested\n if (clearStorage && clearPersisted) {\n clearPersisted()\n }\n }\n\n // Initialize on mount\n onMount(() => {\n initializeForm()\n })\n\n // Setup persistence if persistKey is provided (Sprint 4)\n createEffect(() => {\n const persistKey = params().persistKey\n if (persistKey) {\n const persistence = useFormPersistence({\n persistKey,\n formData,\n setFormData,\n excludeFields: params().excludeFromPersistence,\n expiresIn: params().persistExpiresIn,\n })\n clearPersisted = persistence.clearPersisted\n }\n })\n\n // Auto-submit countdown (v4.2.0)\n createEffect(() => {\n const delay = params().autoSubmitDelay\n if (!delay || !allRequiredPrefilled() || userInteracted()) return\n\n let remaining = Math.ceil(delay / 1000)\n setCountdown(remaining)\n\n countdownTimer = setInterval(() => {\n remaining--\n if (remaining <= 0) {\n cancelCountdown()\n // Trigger submit programmatically\n const form = document.querySelector(`#form-${props.component.id}`) as HTMLFormElement | null\n if (form) form.requestSubmit()\n } else {\n setCountdown(remaining)\n }\n }, 1000)\n })\n\n const handleFieldChange = (name: string, value: any) => {\n handleUserInteraction()\n setFormData((prev) => ({ ...prev, [name]: value }))\n // Clear error on change\n if (errors()[name]) {\n setErrors((prev) => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { [name]: _removed, ...rest } = prev\n return rest\n })\n }\n }\n\n const handleSubmit = async (e: Event) => {\n e.preventDefault()\n setIsSubmitting(true)\n setErrors({})\n\n // Get only visible fields for validation and submission\n const visibleFields = getVisibleFields()\n const visibleFieldNames = new Set(visibleFields.map((f) => f.name))\n\n // Filter form data to only include visible fields\n const visibleFormData: Record<string, any> = {}\n for (const [key, value] of Object.entries(formData())) {\n if (visibleFieldNames.has(key)) {\n visibleFormData[key] = value\n }\n }\n\n // Validate only visible fields\n const validationResult = validateFormData(visibleFormData, visibleFields)\n if (!validationResult.valid) {\n setErrors(validationResult.errors)\n setIsSubmitting(false)\n props.onError?.(validationResult.errors)\n return\n }\n\n // Submit via tool call if specified\n if (params().submitAction?.toolName) {\n try {\n const result = await execute(params().submitAction!.toolName, {\n ...params().submitAction!.params,\n formData: visibleFormData,\n })\n if (!result.success) {\n setErrors({ _form: result.error || 'Submission failed' })\n setIsSubmitting(false)\n props.onError?.({ _form: result.error || 'Submission failed' })\n return\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Submission failed'\n setErrors({ _form: errorMessage })\n setIsSubmitting(false)\n props.onError?.({ _form: errorMessage })\n return\n }\n }\n\n // Clear persisted data on successful submit\n if (clearPersisted) {\n clearPersisted()\n }\n\n // Telemetry: action:dispatched on successful submit (B.5 — v5.6.0).\n // Privacy: only the action name (toolName or 'submit'), NO form values.\n if (telemetry) {\n telemetry.dispatch({\n type: 'action:dispatched',\n id: props.component.id,\n componentType: 'form',\n actionName: params().submitAction?.toolName ?? 'submit',\n ts: Date.now(),\n })\n }\n\n props.onSubmit?.(visibleFormData)\n setIsSubmitting(false)\n }\n\n const layoutClass = () => {\n switch (params().layout) {\n case 'horizontal':\n return 'grid grid-cols-2 gap-4'\n case 'inline':\n return 'flex flex-wrap gap-4 items-end'\n default:\n return 'space-y-4'\n }\n }\n\n return (\n <div class=\"w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4\">\n <Show when={params().title}>\n <h3 class=\"text-lg font-semibold text-gray-900 dark:text-white mb-4\">\n {params().title}\n </h3>\n </Show>\n\n <form id={`form-${props.component.id}`} onSubmit={handleSubmit} noValidate>\n {/* Proposal 3: prefill summary */}\n <Show when={params().fields.some((f) => f.prefill != null)}>\n {(() => {\n const prefilled = params().fields.filter((f) => f.prefill != null).length\n const total = params().fields.length\n return (\n <p class=\"text-xs text-gray-500 dark:text-gray-400 mb-3\">\n {prefilled} champ{prefilled > 1 ? 's' : ''} pré-rempli{prefilled > 1 ? 's' : ''} sur {total}\n </p>\n )\n })()}\n </Show>\n <div class={layoutClass()}>\n <For each={params().fields}>\n {(field) => (\n <FormFieldRenderer\n field={field}\n value={formData()[field.name]}\n error={errors()[field.name]}\n onChange={(value) => handleFieldChange(field.name, value)}\n disabled={isSubmitting() || field.disabled}\n formData={formData}\n />\n )}\n </For>\n </div>\n\n <Show when={errors()._form}>\n <div class=\"mt-4 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md\">\n <p class=\"text-sm text-red-600 dark:text-red-400\" role=\"alert\">\n {errors()._form}\n </p>\n </div>\n </Show>\n\n {/* Auto-submit countdown (v4.2.0) */}\n <Show when={countdown() != null}>\n <div class=\"mt-4 flex items-center gap-3 p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md\">\n <span class=\"text-sm text-blue-700 dark:text-blue-300\">\n {params().submitLabel || 'Submit'} in {countdown()}s...\n </span>\n <button\n type=\"button\"\n onClick={() => { cancelCountdown(); setUserInteracted(true) }}\n class=\"text-sm text-blue-600 dark:text-blue-400 underline hover:text-blue-800 dark:hover:text-blue-200\"\n >\n Cancel\n </button>\n </div>\n </Show>\n\n <div class=\"flex gap-2 pt-4 mt-4 border-t border-gray-200 dark:border-gray-700\">\n <button\n type=\"submit\"\n disabled={isSubmitting()}\n class=\"px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n {isSubmitting() ? (\n <span class=\"flex items-center gap-2\">\n <span class=\"animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full\" />\n Submitting...\n </span>\n ) : (\n params().submitLabel || 'Submit'\n )}\n </button>\n <Show when={params().showReset}>\n <button\n type=\"button\"\n onClick={() => initializeForm(true)}\n disabled={isSubmitting()}\n class=\"px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n Reset\n </button>\n </Show>\n </div>\n </form>\n </div>\n )\n}\n"],"names":["getFieldDefault","type","undefined","FormRenderer","props","params","component","formData","setFormData","createSignal","errors","setErrors","isSubmitting","setIsSubmitting","execute","useAction","telemetry","useTelemetry","clearPersisted","getVisibleFields","fields","filter","field","showWhen","evaluateCondition","countdown","setCountdown","countdownTimer","userInteracted","setUserInteracted","cancelCountdown","clearInterval","handleUserInteraction","onCleanup","allRequiredPrefilled","f","required","every","prefill","initializeForm","clearStorage","initial","name","defaultValue","onMount","createEffect","persistKey","persistence","useFormPersistence","excludeFields","excludeFromPersistence","expiresIn","persistExpiresIn","delay","autoSubmitDelay","remaining","Math","ceil","setInterval","form","document","querySelector","id","requestSubmit","handleFieldChange","value","prev","_removed","rest","handleSubmit","e","preventDefault","visibleFields","visibleFieldNames","Set","map","visibleFormData","key","Object","entries","has","validationResult","validateFormData","valid","onError","submitAction","toolName","result","success","_form","error","errorMessage","Error","message","dispatch","componentType","actionName","ts","Date","now","onSubmit","layoutClass","layout","_el$","_$getNextElement","_tmpl$5","_el$25","firstChild","_el$26","_co$7","_$getNextMarker","nextSibling","_el$3","_el$19","_el$20","_co$4","_el$4","_el$21","_el$22","_co$5","_el$23","_el$24","_co$6","_el$14","_el$15","_el$17","_el$18","_co$3","_$insert","_$createComponent","Show","when","title","children","_el$2","_tmpl$","addEventListener","some","prefilled","length","total","_el$27","_tmpl$6","_el$31","_el$32","_co$8","_el$28","_el$33","_el$34","_co$9","_el$29","_el$35","_el$36","_co$0","_el$30","_el$37","_el$38","_co$1","For","each","FormFieldRenderer","onChange","disabled","_el$5","_tmpl$2","_el$6","_el$7","_tmpl$3","_el$8","_el$1","_el$10","_co$","_el$9","_el$11","_el$12","_co$2","_el$13","submitLabel","$$click","_$runHydrationEvents","_c$","_$memo","_tmpl$7","showReset","_el$16","_tmpl$4","_$effect","_$setProperty","_p$","_v$","_v$2","_v$3","_$setAttribute","t","_$className","a","_$delegateEvents"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormRenderer.d.ts","sourceRoot":"","sources":["../../src/components/FormRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAA6D,MAAM,UAAU,CAAA;AAE/F,OAAO,KAAK,EAAE,WAAW,EAAwC,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"FormRenderer.d.ts","sourceRoot":"","sources":["../../src/components/FormRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAA6D,MAAM,UAAU,CAAA;AAE/F,OAAO,KAAK,EAAE,WAAW,EAAwC,MAAM,UAAU,CAAA;AAOjF,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,WAAW,CAAA;IACtB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,CAAA;IAC9C,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAA;CACnD;AAmBD,eAAO,MAAM,YAAY,EAAE,SAAS,CAAC,iBAAiB,CAmSrD,CAAA"}
|
|
@@ -5,6 +5,7 @@ import { useAction } from "../hooks/useAction.js";
|
|
|
5
5
|
import { validateFormData } from "../services/validation.js";
|
|
6
6
|
import { evaluateCondition } from "../hooks/useConditionalField.js";
|
|
7
7
|
import { useFormPersistence } from "../hooks/useFormPersistence.js";
|
|
8
|
+
import { useTelemetry } from "../context/MCPUITelemetryContext.js";
|
|
8
9
|
var _tmpl$ = /* @__PURE__ */ template(`<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">`), _tmpl$2 = /* @__PURE__ */ template(`<div class="mt-4 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md"><p class="text-sm text-red-600 dark:text-red-400"role=alert>`), _tmpl$3 = /* @__PURE__ */ template(`<div class="mt-4 flex items-center gap-3 p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md"><span class="text-sm text-blue-700 dark:text-blue-300"><!$><!/> in <!$><!/>s...</span><button type=button class="text-sm text-blue-600 dark:text-blue-400 underline hover:text-blue-800 dark:hover:text-blue-200">Cancel`), _tmpl$4 = /* @__PURE__ */ template(`<button type=button class="px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">Reset`), _tmpl$5 = /* @__PURE__ */ template(`<div class="w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4"><!$><!/><form novalidate><!$><!/><div></div><!$><!/><!$><!/><div class="flex gap-2 pt-4 mt-4 border-t border-gray-200 dark:border-gray-700"><button type=submit class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"></button><!$><!/>`), _tmpl$6 = /* @__PURE__ */ template(`<p class="text-xs text-gray-500 dark:text-gray-400 mb-3"><!$><!/> champ<!$><!/> pré-rempli<!$><!/> sur <!$><!/>`), _tmpl$7 = /* @__PURE__ */ template(`<span class="flex items-center gap-2"><span class="animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full"></span>Submitting...`);
|
|
9
10
|
function getFieldDefault(type) {
|
|
10
11
|
switch (type) {
|
|
@@ -27,6 +28,7 @@ const FormRenderer = (props) => {
|
|
|
27
28
|
const {
|
|
28
29
|
execute
|
|
29
30
|
} = useAction();
|
|
31
|
+
const telemetry = useTelemetry();
|
|
30
32
|
let clearPersisted;
|
|
31
33
|
const getVisibleFields = () => {
|
|
32
34
|
return params().fields.filter((field) => {
|
|
@@ -114,7 +116,7 @@ const FormRenderer = (props) => {
|
|
|
114
116
|
}
|
|
115
117
|
};
|
|
116
118
|
const handleSubmit = async (e) => {
|
|
117
|
-
var _a, _b, _c, _d, _e;
|
|
119
|
+
var _a, _b, _c, _d, _e, _f;
|
|
118
120
|
e.preventDefault();
|
|
119
121
|
setIsSubmitting(true);
|
|
120
122
|
setErrors({});
|
|
@@ -164,7 +166,16 @@ const FormRenderer = (props) => {
|
|
|
164
166
|
if (clearPersisted) {
|
|
165
167
|
clearPersisted();
|
|
166
168
|
}
|
|
167
|
-
(
|
|
169
|
+
if (telemetry) {
|
|
170
|
+
telemetry.dispatch({
|
|
171
|
+
type: "action:dispatched",
|
|
172
|
+
id: props.component.id,
|
|
173
|
+
componentType: "form",
|
|
174
|
+
actionName: ((_e = params().submitAction) == null ? void 0 : _e.toolName) ?? "submit",
|
|
175
|
+
ts: Date.now()
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
(_f = props.onSubmit) == null ? void 0 : _f.call(props, visibleFormData);
|
|
168
179
|
setIsSubmitting(false);
|
|
169
180
|
};
|
|
170
181
|
const layoutClass = () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormRenderer.js","sources":["../../src/components/FormRenderer.tsx"],"sourcesContent":["/**\n * FormRenderer - Main form component\n * Sprint 1: Form Foundation\n * Sprint 2: Conditional field visibility (showWhen)\n * Sprint 4: Form state persistence\n */\n\nimport { Component, createSignal, For, Show, onMount, createEffect, onCleanup } from 'solid-js'\nimport { FormFieldRenderer } from './FormFieldRenderer'\nimport type { UIComponent, FormComponentParams, FormFieldParams } from '../types'\nimport { useAction } from '../hooks/useAction'\nimport { validateFormData } from '../services/validation'\nimport { evaluateCondition } from '../hooks/useConditionalField'\nimport { useFormPersistence } from '../hooks/useFormPersistence'\n\nexport interface FormRendererProps {\n component: UIComponent\n onSubmit?: (data: Record<string, any>) => void\n onError?: (errors: Record<string, string>) => void\n}\n\n/**\n * Get default value for a field type\n */\nfunction getFieldDefault(type: FormFieldParams['type']): any {\n switch (type) {\n case 'checkbox':\n return false\n case 'number':\n return undefined\n case 'select':\n case 'radio':\n return ''\n default:\n return ''\n }\n}\n\nexport const FormRenderer: Component<FormRendererProps> = (props) => {\n const params = () => props.component.params as FormComponentParams\n const [formData, setFormData] = createSignal<Record<string, any>>({})\n const [errors, setErrors] = createSignal<Record<string, string>>({})\n const [isSubmitting, setIsSubmitting] = createSignal(false)\n const { execute } = useAction()\n\n // Form persistence (Sprint 4)\n let clearPersisted: (() => void) | undefined\n\n /**\n * Get fields that are currently visible based on showWhen conditions\n */\n const getVisibleFields = (): FormFieldParams[] => {\n return params().fields.filter((field) => {\n if (!field.showWhen) return true\n return evaluateCondition(field.showWhen, formData())\n })\n }\n\n // Auto-submit countdown state (v4.2.0)\n const [countdown, setCountdown] = createSignal<number | null>(null)\n let countdownTimer: ReturnType<typeof setInterval> | null = null\n const [userInteracted, setUserInteracted] = createSignal(false)\n\n const cancelCountdown = () => {\n if (countdownTimer) {\n clearInterval(countdownTimer)\n countdownTimer = null\n }\n setCountdown(null)\n }\n\n const handleUserInteraction = () => {\n if (!userInteracted()) {\n setUserInteracted(true)\n cancelCountdown()\n }\n }\n\n onCleanup(() => cancelCountdown())\n\n /**\n * Check if all required fields have prefill values\n */\n const allRequiredPrefilled = (): boolean => {\n return params().fields\n .filter((f) => f.required)\n .every((f) => f.prefill != null)\n }\n\n // Initialize form data with default values, applying prefill (v4.2.0)\n const initializeForm = (clearStorage = false) => {\n const initial: Record<string, any> = {}\n for (const field of params().fields) {\n // prefill takes priority over defaultValue\n initial[field.name] = field.prefill ?? field.defaultValue ?? getFieldDefault(field.type)\n }\n setFormData(initial)\n setErrors({})\n\n // Clear persisted data if requested\n if (clearStorage && clearPersisted) {\n clearPersisted()\n }\n }\n\n // Initialize on mount\n onMount(() => {\n initializeForm()\n })\n\n // Setup persistence if persistKey is provided (Sprint 4)\n createEffect(() => {\n const persistKey = params().persistKey\n if (persistKey) {\n const persistence = useFormPersistence({\n persistKey,\n formData,\n setFormData,\n excludeFields: params().excludeFromPersistence,\n expiresIn: params().persistExpiresIn,\n })\n clearPersisted = persistence.clearPersisted\n }\n })\n\n // Auto-submit countdown (v4.2.0)\n createEffect(() => {\n const delay = params().autoSubmitDelay\n if (!delay || !allRequiredPrefilled() || userInteracted()) return\n\n let remaining = Math.ceil(delay / 1000)\n setCountdown(remaining)\n\n countdownTimer = setInterval(() => {\n remaining--\n if (remaining <= 0) {\n cancelCountdown()\n // Trigger submit programmatically\n const form = document.querySelector(`#form-${props.component.id}`) as HTMLFormElement | null\n if (form) form.requestSubmit()\n } else {\n setCountdown(remaining)\n }\n }, 1000)\n })\n\n const handleFieldChange = (name: string, value: any) => {\n handleUserInteraction()\n setFormData((prev) => ({ ...prev, [name]: value }))\n // Clear error on change\n if (errors()[name]) {\n setErrors((prev) => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { [name]: _removed, ...rest } = prev\n return rest\n })\n }\n }\n\n const handleSubmit = async (e: Event) => {\n e.preventDefault()\n setIsSubmitting(true)\n setErrors({})\n\n // Get only visible fields for validation and submission\n const visibleFields = getVisibleFields()\n const visibleFieldNames = new Set(visibleFields.map((f) => f.name))\n\n // Filter form data to only include visible fields\n const visibleFormData: Record<string, any> = {}\n for (const [key, value] of Object.entries(formData())) {\n if (visibleFieldNames.has(key)) {\n visibleFormData[key] = value\n }\n }\n\n // Validate only visible fields\n const validationResult = validateFormData(visibleFormData, visibleFields)\n if (!validationResult.valid) {\n setErrors(validationResult.errors)\n setIsSubmitting(false)\n props.onError?.(validationResult.errors)\n return\n }\n\n // Submit via tool call if specified\n if (params().submitAction?.toolName) {\n try {\n const result = await execute(params().submitAction!.toolName, {\n ...params().submitAction!.params,\n formData: visibleFormData,\n })\n if (!result.success) {\n setErrors({ _form: result.error || 'Submission failed' })\n setIsSubmitting(false)\n props.onError?.({ _form: result.error || 'Submission failed' })\n return\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Submission failed'\n setErrors({ _form: errorMessage })\n setIsSubmitting(false)\n props.onError?.({ _form: errorMessage })\n return\n }\n }\n\n // Clear persisted data on successful submit\n if (clearPersisted) {\n clearPersisted()\n }\n\n props.onSubmit?.(visibleFormData)\n setIsSubmitting(false)\n }\n\n const layoutClass = () => {\n switch (params().layout) {\n case 'horizontal':\n return 'grid grid-cols-2 gap-4'\n case 'inline':\n return 'flex flex-wrap gap-4 items-end'\n default:\n return 'space-y-4'\n }\n }\n\n return (\n <div class=\"w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4\">\n <Show when={params().title}>\n <h3 class=\"text-lg font-semibold text-gray-900 dark:text-white mb-4\">\n {params().title}\n </h3>\n </Show>\n\n <form id={`form-${props.component.id}`} onSubmit={handleSubmit} noValidate>\n {/* Proposal 3: prefill summary */}\n <Show when={params().fields.some((f) => f.prefill != null)}>\n {(() => {\n const prefilled = params().fields.filter((f) => f.prefill != null).length\n const total = params().fields.length\n return (\n <p class=\"text-xs text-gray-500 dark:text-gray-400 mb-3\">\n {prefilled} champ{prefilled > 1 ? 's' : ''} pré-rempli{prefilled > 1 ? 's' : ''} sur {total}\n </p>\n )\n })()}\n </Show>\n <div class={layoutClass()}>\n <For each={params().fields}>\n {(field) => (\n <FormFieldRenderer\n field={field}\n value={formData()[field.name]}\n error={errors()[field.name]}\n onChange={(value) => handleFieldChange(field.name, value)}\n disabled={isSubmitting() || field.disabled}\n formData={formData}\n />\n )}\n </For>\n </div>\n\n <Show when={errors()._form}>\n <div class=\"mt-4 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md\">\n <p class=\"text-sm text-red-600 dark:text-red-400\" role=\"alert\">\n {errors()._form}\n </p>\n </div>\n </Show>\n\n {/* Auto-submit countdown (v4.2.0) */}\n <Show when={countdown() != null}>\n <div class=\"mt-4 flex items-center gap-3 p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md\">\n <span class=\"text-sm text-blue-700 dark:text-blue-300\">\n {params().submitLabel || 'Submit'} in {countdown()}s...\n </span>\n <button\n type=\"button\"\n onClick={() => { cancelCountdown(); setUserInteracted(true) }}\n class=\"text-sm text-blue-600 dark:text-blue-400 underline hover:text-blue-800 dark:hover:text-blue-200\"\n >\n Cancel\n </button>\n </div>\n </Show>\n\n <div class=\"flex gap-2 pt-4 mt-4 border-t border-gray-200 dark:border-gray-700\">\n <button\n type=\"submit\"\n disabled={isSubmitting()}\n class=\"px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n {isSubmitting() ? (\n <span class=\"flex items-center gap-2\">\n <span class=\"animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full\" />\n Submitting...\n </span>\n ) : (\n params().submitLabel || 'Submit'\n )}\n </button>\n <Show when={params().showReset}>\n <button\n type=\"button\"\n onClick={() => initializeForm(true)}\n disabled={isSubmitting()}\n class=\"px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n Reset\n </button>\n </Show>\n </div>\n </form>\n </div>\n )\n}\n"],"names":["getFieldDefault","type","undefined","FormRenderer","props","params","component","formData","setFormData","createSignal","errors","setErrors","isSubmitting","setIsSubmitting","execute","useAction","clearPersisted","getVisibleFields","fields","filter","field","showWhen","evaluateCondition","countdown","setCountdown","countdownTimer","userInteracted","setUserInteracted","cancelCountdown","clearInterval","handleUserInteraction","onCleanup","allRequiredPrefilled","f","required","every","prefill","initializeForm","clearStorage","initial","name","defaultValue","onMount","createEffect","persistKey","persistence","useFormPersistence","excludeFields","excludeFromPersistence","expiresIn","persistExpiresIn","delay","autoSubmitDelay","remaining","Math","ceil","setInterval","form","document","querySelector","id","requestSubmit","handleFieldChange","value","prev","_removed","rest","handleSubmit","e","preventDefault","visibleFields","visibleFieldNames","Set","map","visibleFormData","key","Object","entries","has","validationResult","validateFormData","valid","onError","submitAction","toolName","result","success","_form","error","errorMessage","Error","message","onSubmit","layoutClass","layout","_el$","_$getNextElement","_tmpl$5","_el$25","firstChild","_el$26","_co$7","_$getNextMarker","nextSibling","_el$3","_el$19","_el$20","_co$4","_el$4","_el$21","_el$22","_co$5","_el$23","_el$24","_co$6","_el$14","_el$15","_el$17","_el$18","_co$3","_$insert","_$createComponent","Show","when","title","children","_el$2","_tmpl$","addEventListener","some","prefilled","length","total","_el$27","_tmpl$6","_el$31","_el$32","_co$8","_el$28","_el$33","_el$34","_co$9","_el$29","_el$35","_el$36","_co$0","_el$30","_el$37","_el$38","_co$1","For","each","FormFieldRenderer","onChange","disabled","_el$5","_tmpl$2","_el$6","_el$7","_tmpl$3","_el$8","_el$1","_el$10","_co$","_el$9","_el$11","_el$12","_co$2","_el$13","submitLabel","$$click","_$runHydrationEvents","_c$","_$memo","_tmpl$7","showReset","_el$16","_tmpl$4","_$effect","_$setProperty","_p$","_v$","_v$2","_v$3","_$setAttribute","t","_$className","a","_$delegateEvents"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FormRenderer.js","sources":["../../src/components/FormRenderer.tsx"],"sourcesContent":["/**\n * FormRenderer - Main form component\n * Sprint 1: Form Foundation\n * Sprint 2: Conditional field visibility (showWhen)\n * Sprint 4: Form state persistence\n */\n\nimport { Component, createSignal, For, Show, onMount, createEffect, onCleanup } from 'solid-js'\nimport { FormFieldRenderer } from './FormFieldRenderer'\nimport type { UIComponent, FormComponentParams, FormFieldParams } from '../types'\nimport { useAction } from '../hooks/useAction'\nimport { validateFormData } from '../services/validation'\nimport { evaluateCondition } from '../hooks/useConditionalField'\nimport { useFormPersistence } from '../hooks/useFormPersistence'\nimport { useTelemetry } from '../context/MCPUITelemetryContext'\n\nexport interface FormRendererProps {\n component: UIComponent\n onSubmit?: (data: Record<string, any>) => void\n onError?: (errors: Record<string, string>) => void\n}\n\n/**\n * Get default value for a field type\n */\nfunction getFieldDefault(type: FormFieldParams['type']): any {\n switch (type) {\n case 'checkbox':\n return false\n case 'number':\n return undefined\n case 'select':\n case 'radio':\n return ''\n default:\n return ''\n }\n}\n\nexport const FormRenderer: Component<FormRendererProps> = (props) => {\n const params = () => props.component.params as FormComponentParams\n const [formData, setFormData] = createSignal<Record<string, any>>({})\n const [errors, setErrors] = createSignal<Record<string, string>>({})\n const [isSubmitting, setIsSubmitting] = createSignal(false)\n const { execute } = useAction()\n const telemetry = useTelemetry()\n\n // Form persistence (Sprint 4)\n let clearPersisted: (() => void) | undefined\n\n /**\n * Get fields that are currently visible based on showWhen conditions\n */\n const getVisibleFields = (): FormFieldParams[] => {\n return params().fields.filter((field) => {\n if (!field.showWhen) return true\n return evaluateCondition(field.showWhen, formData())\n })\n }\n\n // Auto-submit countdown state (v4.2.0)\n const [countdown, setCountdown] = createSignal<number | null>(null)\n let countdownTimer: ReturnType<typeof setInterval> | null = null\n const [userInteracted, setUserInteracted] = createSignal(false)\n\n const cancelCountdown = () => {\n if (countdownTimer) {\n clearInterval(countdownTimer)\n countdownTimer = null\n }\n setCountdown(null)\n }\n\n const handleUserInteraction = () => {\n if (!userInteracted()) {\n setUserInteracted(true)\n cancelCountdown()\n }\n }\n\n onCleanup(() => cancelCountdown())\n\n /**\n * Check if all required fields have prefill values\n */\n const allRequiredPrefilled = (): boolean => {\n return params().fields\n .filter((f) => f.required)\n .every((f) => f.prefill != null)\n }\n\n // Initialize form data with default values, applying prefill (v4.2.0)\n const initializeForm = (clearStorage = false) => {\n const initial: Record<string, any> = {}\n for (const field of params().fields) {\n // prefill takes priority over defaultValue\n initial[field.name] = field.prefill ?? field.defaultValue ?? getFieldDefault(field.type)\n }\n setFormData(initial)\n setErrors({})\n\n // Clear persisted data if requested\n if (clearStorage && clearPersisted) {\n clearPersisted()\n }\n }\n\n // Initialize on mount\n onMount(() => {\n initializeForm()\n })\n\n // Setup persistence if persistKey is provided (Sprint 4)\n createEffect(() => {\n const persistKey = params().persistKey\n if (persistKey) {\n const persistence = useFormPersistence({\n persistKey,\n formData,\n setFormData,\n excludeFields: params().excludeFromPersistence,\n expiresIn: params().persistExpiresIn,\n })\n clearPersisted = persistence.clearPersisted\n }\n })\n\n // Auto-submit countdown (v4.2.0)\n createEffect(() => {\n const delay = params().autoSubmitDelay\n if (!delay || !allRequiredPrefilled() || userInteracted()) return\n\n let remaining = Math.ceil(delay / 1000)\n setCountdown(remaining)\n\n countdownTimer = setInterval(() => {\n remaining--\n if (remaining <= 0) {\n cancelCountdown()\n // Trigger submit programmatically\n const form = document.querySelector(`#form-${props.component.id}`) as HTMLFormElement | null\n if (form) form.requestSubmit()\n } else {\n setCountdown(remaining)\n }\n }, 1000)\n })\n\n const handleFieldChange = (name: string, value: any) => {\n handleUserInteraction()\n setFormData((prev) => ({ ...prev, [name]: value }))\n // Clear error on change\n if (errors()[name]) {\n setErrors((prev) => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { [name]: _removed, ...rest } = prev\n return rest\n })\n }\n }\n\n const handleSubmit = async (e: Event) => {\n e.preventDefault()\n setIsSubmitting(true)\n setErrors({})\n\n // Get only visible fields for validation and submission\n const visibleFields = getVisibleFields()\n const visibleFieldNames = new Set(visibleFields.map((f) => f.name))\n\n // Filter form data to only include visible fields\n const visibleFormData: Record<string, any> = {}\n for (const [key, value] of Object.entries(formData())) {\n if (visibleFieldNames.has(key)) {\n visibleFormData[key] = value\n }\n }\n\n // Validate only visible fields\n const validationResult = validateFormData(visibleFormData, visibleFields)\n if (!validationResult.valid) {\n setErrors(validationResult.errors)\n setIsSubmitting(false)\n props.onError?.(validationResult.errors)\n return\n }\n\n // Submit via tool call if specified\n if (params().submitAction?.toolName) {\n try {\n const result = await execute(params().submitAction!.toolName, {\n ...params().submitAction!.params,\n formData: visibleFormData,\n })\n if (!result.success) {\n setErrors({ _form: result.error || 'Submission failed' })\n setIsSubmitting(false)\n props.onError?.({ _form: result.error || 'Submission failed' })\n return\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Submission failed'\n setErrors({ _form: errorMessage })\n setIsSubmitting(false)\n props.onError?.({ _form: errorMessage })\n return\n }\n }\n\n // Clear persisted data on successful submit\n if (clearPersisted) {\n clearPersisted()\n }\n\n // Telemetry: action:dispatched on successful submit (B.5 — v5.6.0).\n // Privacy: only the action name (toolName or 'submit'), NO form values.\n if (telemetry) {\n telemetry.dispatch({\n type: 'action:dispatched',\n id: props.component.id,\n componentType: 'form',\n actionName: params().submitAction?.toolName ?? 'submit',\n ts: Date.now(),\n })\n }\n\n props.onSubmit?.(visibleFormData)\n setIsSubmitting(false)\n }\n\n const layoutClass = () => {\n switch (params().layout) {\n case 'horizontal':\n return 'grid grid-cols-2 gap-4'\n case 'inline':\n return 'flex flex-wrap gap-4 items-end'\n default:\n return 'space-y-4'\n }\n }\n\n return (\n <div class=\"w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4\">\n <Show when={params().title}>\n <h3 class=\"text-lg font-semibold text-gray-900 dark:text-white mb-4\">\n {params().title}\n </h3>\n </Show>\n\n <form id={`form-${props.component.id}`} onSubmit={handleSubmit} noValidate>\n {/* Proposal 3: prefill summary */}\n <Show when={params().fields.some((f) => f.prefill != null)}>\n {(() => {\n const prefilled = params().fields.filter((f) => f.prefill != null).length\n const total = params().fields.length\n return (\n <p class=\"text-xs text-gray-500 dark:text-gray-400 mb-3\">\n {prefilled} champ{prefilled > 1 ? 's' : ''} pré-rempli{prefilled > 1 ? 's' : ''} sur {total}\n </p>\n )\n })()}\n </Show>\n <div class={layoutClass()}>\n <For each={params().fields}>\n {(field) => (\n <FormFieldRenderer\n field={field}\n value={formData()[field.name]}\n error={errors()[field.name]}\n onChange={(value) => handleFieldChange(field.name, value)}\n disabled={isSubmitting() || field.disabled}\n formData={formData}\n />\n )}\n </For>\n </div>\n\n <Show when={errors()._form}>\n <div class=\"mt-4 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md\">\n <p class=\"text-sm text-red-600 dark:text-red-400\" role=\"alert\">\n {errors()._form}\n </p>\n </div>\n </Show>\n\n {/* Auto-submit countdown (v4.2.0) */}\n <Show when={countdown() != null}>\n <div class=\"mt-4 flex items-center gap-3 p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md\">\n <span class=\"text-sm text-blue-700 dark:text-blue-300\">\n {params().submitLabel || 'Submit'} in {countdown()}s...\n </span>\n <button\n type=\"button\"\n onClick={() => { cancelCountdown(); setUserInteracted(true) }}\n class=\"text-sm text-blue-600 dark:text-blue-400 underline hover:text-blue-800 dark:hover:text-blue-200\"\n >\n Cancel\n </button>\n </div>\n </Show>\n\n <div class=\"flex gap-2 pt-4 mt-4 border-t border-gray-200 dark:border-gray-700\">\n <button\n type=\"submit\"\n disabled={isSubmitting()}\n class=\"px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n {isSubmitting() ? (\n <span class=\"flex items-center gap-2\">\n <span class=\"animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full\" />\n Submitting...\n </span>\n ) : (\n params().submitLabel || 'Submit'\n )}\n </button>\n <Show when={params().showReset}>\n <button\n type=\"button\"\n onClick={() => initializeForm(true)}\n disabled={isSubmitting()}\n class=\"px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n Reset\n </button>\n </Show>\n </div>\n </form>\n </div>\n )\n}\n"],"names":["getFieldDefault","type","undefined","FormRenderer","props","params","component","formData","setFormData","createSignal","errors","setErrors","isSubmitting","setIsSubmitting","execute","useAction","telemetry","useTelemetry","clearPersisted","getVisibleFields","fields","filter","field","showWhen","evaluateCondition","countdown","setCountdown","countdownTimer","userInteracted","setUserInteracted","cancelCountdown","clearInterval","handleUserInteraction","onCleanup","allRequiredPrefilled","f","required","every","prefill","initializeForm","clearStorage","initial","name","defaultValue","onMount","createEffect","persistKey","persistence","useFormPersistence","excludeFields","excludeFromPersistence","expiresIn","persistExpiresIn","delay","autoSubmitDelay","remaining","Math","ceil","setInterval","form","document","querySelector","id","requestSubmit","handleFieldChange","value","prev","_removed","rest","handleSubmit","e","preventDefault","visibleFields","visibleFieldNames","Set","map","visibleFormData","key","Object","entries","has","validationResult","validateFormData","valid","onError","submitAction","toolName","result","success","_form","error","errorMessage","Error","message","dispatch","componentType","actionName","ts","Date","now","onSubmit","layoutClass","layout","_el$","_$getNextElement","_tmpl$5","_el$25","firstChild","_el$26","_co$7","_$getNextMarker","nextSibling","_el$3","_el$19","_el$20","_co$4","_el$4","_el$21","_el$22","_co$5","_el$23","_el$24","_co$6","_el$14","_el$15","_el$17","_el$18","_co$3","_$insert","_$createComponent","Show","when","title","children","_el$2","_tmpl$","addEventListener","some","prefilled","length","total","_el$27","_tmpl$6","_el$31","_el$32","_co$8","_el$28","_el$33","_el$34","_co$9","_el$29","_el$35","_el$36","_co$0","_el$30","_el$37","_el$38","_co$1","For","each","FormFieldRenderer","onChange","disabled","_el$5","_tmpl$2","_el$6","_el$7","_tmpl$3","_el$8","_el$1","_el$10","_co$","_el$9","_el$11","_el$12","_co$2","_el$13","submitLabel","$$click","_$runHydrationEvents","_c$","_$memo","_tmpl$7","showReset","_el$16","_tmpl$4","_$effect","_$setProperty","_p$","_v$","_v$2","_v$3","_$setAttribute","t","_$className","a","_$delegateEvents"],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const web = require("solid-js/web");
|
|
4
4
|
const solidJs = require("solid-js");
|
|
5
5
|
const logger$1 = require("../utils/logger.cjs");
|
|
6
|
+
const MCPUITelemetryContext = require("../context/MCPUITelemetryContext.cjs");
|
|
6
7
|
var _tmpl$ = /* @__PURE__ */ web.template(`<p class="text-xs text-yellow-600 dark:text-yellow-400 mt-2 font-mono">`), _tmpl$2 = /* @__PURE__ */ web.template(`<button class="mt-3 text-xs font-medium text-yellow-800 dark:text-yellow-200 hover:text-yellow-900 dark:hover:text-yellow-100 underline">Retry Rendering`), _tmpl$3 = /* @__PURE__ */ web.template(`<div class="w-full h-full bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4"><div class="flex items-start gap-3"><div class=flex-shrink-0><svg class="w-5 h-5 text-yellow-600 dark:text-yellow-400"fill=none stroke=currentColor viewBox="0 0 24 24"><path stroke-linecap=round stroke-linejoin=round stroke-width=2 d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg></div><div class="flex-1 min-w-0"><p class="text-sm font-medium text-yellow-900 dark:text-yellow-100">Component Failed to Render</p><p class="text-xs text-yellow-700 dark:text-yellow-300 mt-1">Type: <!$><!/> | ID: <!$><!/>...</p><!$><!/><!$><!/>`);
|
|
7
8
|
const logger = logger$1.createLogger("generative-ui");
|
|
8
9
|
function DefaultErrorFallback(props) {
|
|
@@ -42,6 +43,7 @@ function DefaultErrorFallback(props) {
|
|
|
42
43
|
const GenerativeUIErrorBoundary = (props) => {
|
|
43
44
|
const [retryKey, setRetryKey] = solidJs.createSignal(0);
|
|
44
45
|
const [renderStartTime, setRenderStartTime] = solidJs.createSignal(0);
|
|
46
|
+
const telemetry = MCPUITelemetryContext.useTelemetry();
|
|
45
47
|
solidJs.onMount(() => {
|
|
46
48
|
if (typeof performance !== "undefined") {
|
|
47
49
|
setRenderStartTime(performance.now());
|
|
@@ -76,6 +78,15 @@ const GenerativeUIErrorBoundary = (props) => {
|
|
|
76
78
|
componentId: props.componentId,
|
|
77
79
|
details: errorContext
|
|
78
80
|
});
|
|
81
|
+
if (telemetry) {
|
|
82
|
+
telemetry.dispatch({
|
|
83
|
+
type: "render:error",
|
|
84
|
+
id: props.componentId,
|
|
85
|
+
componentType: props.componentType,
|
|
86
|
+
errorMessage: error.message,
|
|
87
|
+
ts: Date.now()
|
|
88
|
+
});
|
|
89
|
+
}
|
|
79
90
|
};
|
|
80
91
|
const handleRetry = () => {
|
|
81
92
|
const newRetryCount = retryKey() + 1;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GenerativeUIErrorBoundary.cjs","sources":["../../src/components/GenerativeUIErrorBoundary.tsx"],"sourcesContent":["/**\n * Generative UI Error Boundary with Telemetry\n * Phase 0: Error isolation + structured logging\n *\n * Features:\n * - Component-level error isolation\n * - Structured logging with context\n * - Performance timing\n * - Retry mechanism\n * - User-friendly fallback UI\n */\n\nimport { Component, ErrorBoundary, createSignal, Show, onMount } from 'solid-js'\nimport { isServer } from 'solid-js/web'\nimport { createLogger } from '../utils/logger'\nimport type { RendererError } from '../types'\n\nconst logger = createLogger('generative-ui')\n\n/**\n * Props for GenerativeUIErrorBoundary\n */\nexport interface GenerativeUIErrorBoundaryProps {\n /**\n * Component identifier for telemetry\n */\n componentId: string\n\n /**\n * Component type for context\n */\n componentType: string\n\n /**\n * Error callback\n */\n onError?: (error: RendererError) => void\n\n /**\n * Allow retry on error\n */\n allowRetry?: boolean\n\n /**\n * Child components to wrap\n */\n children: any\n\n /**\n * Custom fallback UI (optional)\n */\n fallback?: (error: Error, retry?: () => void) => any\n}\n\n/**\n * Default fallback UI for errors\n */\nfunction DefaultErrorFallback(props: {\n error: Error\n componentId: string\n componentType: string\n allowRetry?: boolean\n onRetry?: () => void\n}) {\n return (\n <div class=\"w-full h-full bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4\">\n <div class=\"flex items-start gap-3\">\n <div class=\"flex-shrink-0\">\n <svg\n class=\"w-5 h-5 text-yellow-600 dark:text-yellow-400\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <div class=\"flex-1 min-w-0\">\n <p class=\"text-sm font-medium text-yellow-900 dark:text-yellow-100\">\n Component Failed to Render\n </p>\n <p class=\"text-xs text-yellow-700 dark:text-yellow-300 mt-1\">\n Type: {props.componentType || 'unknown'} | ID: {props.componentId?.slice(0, 8) || 'unknown'}...\n </p>\n <Show when={import.meta.env.DEV}>\n <p class=\"text-xs text-yellow-600 dark:text-yellow-400 mt-2 font-mono\">\n {props.error.message}\n </p>\n </Show>\n <Show when={props.allowRetry}>\n <button\n onClick={props.onRetry}\n class=\"mt-3 text-xs font-medium text-yellow-800 dark:text-yellow-200 hover:text-yellow-900 dark:hover:text-yellow-100 underline\"\n >\n Retry Rendering\n </button>\n </Show>\n </div>\n </div>\n </div>\n )\n}\n\n/**\n * Generative UI Error Boundary Component\n */\nexport const GenerativeUIErrorBoundary: Component<GenerativeUIErrorBoundaryProps> = (props) => {\n const [retryKey, setRetryKey] = createSignal(0)\n const [renderStartTime, setRenderStartTime] = createSignal(0)\n\n // SSR-safe: Initialize performance timing on client only\n onMount(() => {\n if (typeof performance !== 'undefined') {\n setRenderStartTime(performance.now())\n }\n })\n\n // Handle error with telemetry\n const handleError = (error: Error) => {\n // SSR-safe: Calculate render duration\n const renderDuration = !isServer && typeof performance !== 'undefined'\n ? performance.now() - renderStartTime()\n : 0\n\n // SSR-safe: Get client-only context\n const userAgent = !isServer && typeof navigator !== 'undefined'\n ? navigator.userAgent\n : 'server'\n const viewport = !isServer && typeof window !== 'undefined'\n ? { width: window.innerWidth, height: window.innerHeight }\n : { width: 0, height: 0 }\n\n // Structure error context\n const errorContext = {\n componentId: props.componentId,\n componentType: props.componentType,\n errorMessage: error.message,\n errorStack: error.stack,\n renderDuration,\n retryCount: retryKey(),\n timestamp: new Date().toISOString(),\n userAgent,\n viewport,\n }\n\n // Log to structured logger\n logger.error(`Component render failed: ${props.componentType}`, errorContext)\n\n // Call error callback\n props.onError?.({\n type: 'render',\n message: error.message,\n componentId: props.componentId,\n details: errorContext,\n })\n\n // In production, send to monitoring service\n if (import.meta.env.PROD) {\n // Future: Send to Sentry or other APM\n // Sentry.captureException(error, { contexts: { component: errorContext } })\n }\n }\n\n // Retry mechanism\n const handleRetry = () => {\n const newRetryCount = retryKey() + 1\n logger.info(`Retrying component render: ${props.componentType}`, {\n componentId: props.componentId,\n retryCount: newRetryCount,\n })\n setRetryKey(newRetryCount)\n }\n\n return (\n <ErrorBoundary\n fallback={(error) => {\n handleError(error)\n\n // Use custom fallback if provided\n if (props.fallback) {\n return props.fallback(error, props.allowRetry ? handleRetry : undefined)\n }\n\n // Default fallback\n return (\n <DefaultErrorFallback\n error={error}\n componentId={props.componentId}\n componentType={props.componentType}\n allowRetry={props.allowRetry}\n onRetry={handleRetry}\n />\n )\n }}\n >\n {/* Key prop for forcing remount on retry */}\n {(() => {\n void retryKey() // Access signal to track changes\n return <>{props.children}</>\n })()}\n </ErrorBoundary>\n )\n}\n\n/**\n * Performance monitoring wrapper\n * Logs render times for performance analysis\n */\nexport function withPerformanceMonitoring<P extends { componentId: string; componentType: string }>(\n WrappedComponent: Component<P>\n) {\n return (props: P) => {\n const [renderStart, setRenderStart] = createSignal(0)\n\n // Log render start\n logger.debug(`Component render start: ${props.componentType}`, {\n componentId: props.componentId,\n timestamp: new Date().toISOString(),\n })\n\n // SSR-safe: Measure on mount completion (client-side only)\n onMount(() => {\n if (typeof performance !== 'undefined') {\n setRenderStart(performance.now())\n\n requestAnimationFrame(() => {\n const renderEnd = performance.now()\n const duration = renderEnd - renderStart()\n\n logger.info(`Component rendered: ${props.componentType}`, {\n componentId: props.componentId,\n renderDuration: duration,\n timestamp: new Date().toISOString(),\n })\n\n // Warn if render is slow (>50ms target)\n if (duration > 50) {\n logger.warn(`Slow component render: ${props.componentType}`, {\n componentId: props.componentId,\n renderDuration: duration,\n threshold: 50,\n })\n }\n })\n }\n })\n\n return <WrappedComponent {...props} />\n }\n}\n\n/**\n * Hook to track component lifecycle events\n */\nexport function useComponentTelemetry(componentId: string, componentType: string) {\n const [mountTime, setMountTime] = createSignal(0)\n\n // SSR-safe: Initialize mount time on client only\n onMount(() => {\n if (typeof performance !== 'undefined') {\n setMountTime(performance.now())\n }\n\n // Log mount\n logger.debug(`Component mounted: ${componentType}`, {\n componentId,\n timestamp: new Date().toISOString(),\n })\n })\n\n // Return cleanup function for unmount\n return () => {\n const lifetime = !isServer && typeof performance !== 'undefined'\n ? performance.now() - mountTime()\n : 0\n logger.debug(`Component unmounted: ${componentType}`, {\n componentId,\n lifetime,\n timestamp: new Date().toISOString(),\n })\n }\n}\n"],"names":["logger","createLogger","DefaultErrorFallback","props","_el$","_$getNextElement","_tmpl$3","_el$2","firstChild","_el$3","_el$4","nextSibling","_el$5","_el$6","_el$7","_el$0","_el$1","_co$","_$getNextMarker","_el$8","_el$10","_el$11","_co$2","_el$14","_el$15","_co$3","_el$16","_el$17","_co$4","_$insert","componentType","componentId","slice","_$createComponent","Show","when","import","children","_el$12","_tmpl$","error","message","allowRetry","_el$13","_tmpl$2","_$addEventListener","onRetry","_$runHydrationEvents","GenerativeUIErrorBoundary","retryKey","setRetryKey","createSignal","renderStartTime","setRenderStartTime","onMount","performance","now","handleError","renderDuration","isServer","userAgent","navigator","viewport","window","width","innerWidth","height","innerHeight","errorContext","errorMessage","errorStack","stack","retryCount","timestamp","Date","toISOString","onError","type","details","handleRetry","newRetryCount","info","ErrorBoundary","fallback","undefined","_$memo","_$delegateEvents"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GenerativeUIErrorBoundary.cjs","sources":["../../src/components/GenerativeUIErrorBoundary.tsx"],"sourcesContent":["/**\n * Generative UI Error Boundary with Telemetry\n * Phase 0: Error isolation + structured logging\n *\n * Features:\n * - Component-level error isolation\n * - Structured logging with context\n * - Performance timing\n * - Retry mechanism\n * - User-friendly fallback UI\n */\n\nimport { Component, ErrorBoundary, createSignal, Show, onMount } from 'solid-js'\nimport { isServer } from 'solid-js/web'\nimport { createLogger } from '../utils/logger'\nimport type { RendererError, ComponentType } from '../types'\nimport { useTelemetry } from '../context/MCPUITelemetryContext'\n\nconst logger = createLogger('generative-ui')\n\n/**\n * Props for GenerativeUIErrorBoundary\n */\nexport interface GenerativeUIErrorBoundaryProps {\n /**\n * Component identifier for telemetry\n */\n componentId: string\n\n /**\n * Component type for context\n */\n componentType: string\n\n /**\n * Error callback\n */\n onError?: (error: RendererError) => void\n\n /**\n * Allow retry on error\n */\n allowRetry?: boolean\n\n /**\n * Child components to wrap\n */\n children: any\n\n /**\n * Custom fallback UI (optional)\n */\n fallback?: (error: Error, retry?: () => void) => any\n}\n\n/**\n * Default fallback UI for errors\n */\nfunction DefaultErrorFallback(props: {\n error: Error\n componentId: string\n componentType: string\n allowRetry?: boolean\n onRetry?: () => void\n}) {\n return (\n <div class=\"w-full h-full bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4\">\n <div class=\"flex items-start gap-3\">\n <div class=\"flex-shrink-0\">\n <svg\n class=\"w-5 h-5 text-yellow-600 dark:text-yellow-400\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <div class=\"flex-1 min-w-0\">\n <p class=\"text-sm font-medium text-yellow-900 dark:text-yellow-100\">\n Component Failed to Render\n </p>\n <p class=\"text-xs text-yellow-700 dark:text-yellow-300 mt-1\">\n Type: {props.componentType || 'unknown'} | ID: {props.componentId?.slice(0, 8) || 'unknown'}...\n </p>\n <Show when={import.meta.env.DEV}>\n <p class=\"text-xs text-yellow-600 dark:text-yellow-400 mt-2 font-mono\">\n {props.error.message}\n </p>\n </Show>\n <Show when={props.allowRetry}>\n <button\n onClick={props.onRetry}\n class=\"mt-3 text-xs font-medium text-yellow-800 dark:text-yellow-200 hover:text-yellow-900 dark:hover:text-yellow-100 underline\"\n >\n Retry Rendering\n </button>\n </Show>\n </div>\n </div>\n </div>\n )\n}\n\n/**\n * Generative UI Error Boundary Component\n */\nexport const GenerativeUIErrorBoundary: Component<GenerativeUIErrorBoundaryProps> = (props) => {\n const [retryKey, setRetryKey] = createSignal(0)\n const [renderStartTime, setRenderStartTime] = createSignal(0)\n // Telemetry sink (B.5 — v5.6.0). Null when no Provider is mounted.\n const telemetry = useTelemetry()\n\n // SSR-safe: Initialize performance timing on client only\n onMount(() => {\n if (typeof performance !== 'undefined') {\n setRenderStartTime(performance.now())\n }\n })\n\n // Handle error with telemetry\n const handleError = (error: Error) => {\n // SSR-safe: Calculate render duration\n const renderDuration = !isServer && typeof performance !== 'undefined'\n ? performance.now() - renderStartTime()\n : 0\n\n // SSR-safe: Get client-only context\n const userAgent = !isServer && typeof navigator !== 'undefined'\n ? navigator.userAgent\n : 'server'\n const viewport = !isServer && typeof window !== 'undefined'\n ? { width: window.innerWidth, height: window.innerHeight }\n : { width: 0, height: 0 }\n\n // Structure error context\n const errorContext = {\n componentId: props.componentId,\n componentType: props.componentType,\n errorMessage: error.message,\n errorStack: error.stack,\n renderDuration,\n retryCount: retryKey(),\n timestamp: new Date().toISOString(),\n userAgent,\n viewport,\n }\n\n // Log to structured logger\n logger.error(`Component render failed: ${props.componentType}`, errorContext)\n\n // Call error callback\n props.onError?.({\n type: 'render',\n message: error.message,\n componentId: props.componentId,\n details: errorContext,\n })\n\n // Dispatch render:error to telemetry sink (B.5 — v5.6.0). Only the\n // message (capped semantically by the consumer if desired) — NO stack\n // trace, NO component payload, per privacy hard rule (§M.6.2).\n if (telemetry) {\n telemetry.dispatch({\n type: 'render:error',\n id: props.componentId,\n componentType: props.componentType as ComponentType,\n errorMessage: error.message,\n ts: Date.now(),\n })\n }\n\n // In production, send to monitoring service\n if (import.meta.env.PROD) {\n // Future: Send to Sentry or other APM\n // Sentry.captureException(error, { contexts: { component: errorContext } })\n }\n }\n\n // Retry mechanism\n const handleRetry = () => {\n const newRetryCount = retryKey() + 1\n logger.info(`Retrying component render: ${props.componentType}`, {\n componentId: props.componentId,\n retryCount: newRetryCount,\n })\n setRetryKey(newRetryCount)\n }\n\n return (\n <ErrorBoundary\n fallback={(error) => {\n handleError(error)\n\n // Use custom fallback if provided\n if (props.fallback) {\n return props.fallback(error, props.allowRetry ? handleRetry : undefined)\n }\n\n // Default fallback\n return (\n <DefaultErrorFallback\n error={error}\n componentId={props.componentId}\n componentType={props.componentType}\n allowRetry={props.allowRetry}\n onRetry={handleRetry}\n />\n )\n }}\n >\n {/* Key prop for forcing remount on retry */}\n {(() => {\n void retryKey() // Access signal to track changes\n return <>{props.children}</>\n })()}\n </ErrorBoundary>\n )\n}\n\n/**\n * Performance monitoring wrapper\n * Logs render times for performance analysis\n */\nexport function withPerformanceMonitoring<P extends { componentId: string; componentType: string }>(\n WrappedComponent: Component<P>\n) {\n return (props: P) => {\n const [renderStart, setRenderStart] = createSignal(0)\n\n // Log render start\n logger.debug(`Component render start: ${props.componentType}`, {\n componentId: props.componentId,\n timestamp: new Date().toISOString(),\n })\n\n // SSR-safe: Measure on mount completion (client-side only)\n onMount(() => {\n if (typeof performance !== 'undefined') {\n setRenderStart(performance.now())\n\n requestAnimationFrame(() => {\n const renderEnd = performance.now()\n const duration = renderEnd - renderStart()\n\n logger.info(`Component rendered: ${props.componentType}`, {\n componentId: props.componentId,\n renderDuration: duration,\n timestamp: new Date().toISOString(),\n })\n\n // Warn if render is slow (>50ms target)\n if (duration > 50) {\n logger.warn(`Slow component render: ${props.componentType}`, {\n componentId: props.componentId,\n renderDuration: duration,\n threshold: 50,\n })\n }\n })\n }\n })\n\n return <WrappedComponent {...props} />\n }\n}\n\n/**\n * Hook to track component lifecycle events\n */\nexport function useComponentTelemetry(componentId: string, componentType: string) {\n const [mountTime, setMountTime] = createSignal(0)\n\n // SSR-safe: Initialize mount time on client only\n onMount(() => {\n if (typeof performance !== 'undefined') {\n setMountTime(performance.now())\n }\n\n // Log mount\n logger.debug(`Component mounted: ${componentType}`, {\n componentId,\n timestamp: new Date().toISOString(),\n })\n })\n\n // Return cleanup function for unmount\n return () => {\n const lifetime = !isServer && typeof performance !== 'undefined'\n ? performance.now() - mountTime()\n : 0\n logger.debug(`Component unmounted: ${componentType}`, {\n componentId,\n lifetime,\n timestamp: new Date().toISOString(),\n })\n }\n}\n"],"names":["logger","createLogger","DefaultErrorFallback","props","_el$","_$getNextElement","_tmpl$3","_el$2","firstChild","_el$3","_el$4","nextSibling","_el$5","_el$6","_el$7","_el$0","_el$1","_co$","_$getNextMarker","_el$8","_el$10","_el$11","_co$2","_el$14","_el$15","_co$3","_el$16","_el$17","_co$4","_$insert","componentType","componentId","slice","_$createComponent","Show","when","import","children","_el$12","_tmpl$","error","message","allowRetry","_el$13","_tmpl$2","_$addEventListener","onRetry","_$runHydrationEvents","GenerativeUIErrorBoundary","retryKey","setRetryKey","createSignal","renderStartTime","setRenderStartTime","telemetry","useTelemetry","onMount","performance","now","handleError","renderDuration","isServer","userAgent","navigator","viewport","window","width","innerWidth","height","innerHeight","errorContext","errorMessage","errorStack","stack","retryCount","timestamp","Date","toISOString","onError","type","details","dispatch","id","ts","handleRetry","newRetryCount","info","ErrorBoundary","fallback","undefined","_$memo","_$delegateEvents"],"mappings":";;;;;;;AAkBA,MAAMA,SAASC,SAAAA,aAAa,eAAe;AAwC3C,SAASC,qBAAqBC,OAM3B;AACD,UAAA,MAAA;AAAA,QAAAC,OAAAC,IAAAA,eAAAC,OAAA,GAAAC,QAAAH,KAAAI,YAAAC,QAAAF,MAAAC,YAAAE,QAAAD,MAAAE,aAAAC,QAAAF,MAAAF,YAAAK,QAAAD,MAAAD,aAAAG,QAAAD,MAAAL,YAAAO,QAAAD,MAAAH,aAAA,CAAAK,OAAAC,IAAA,IAAAC,IAAAA,cAAAH,MAAAJ,WAAA,GAAAQ,QAAAH,MAAAL,aAAAS,SAAAD,MAAAR,aAAA,CAAAU,QAAAC,KAAA,IAAAJ,IAAAA,cAAAE,OAAAT,WAAA;AAAAU,WAAAV;AAAAA,QAAAY,SAAAV,MAAAF,aAAA,CAAAa,QAAAC,KAAA,IAAAP,IAAAA,cAAAK,OAAAZ,WAAA,GAAAe,SAAAF,OAAAb,aAAA,CAAAgB,QAAAC,KAAA,IAAAV,IAAAA,cAAAQ,OAAAf,WAAA;AAAAkB,QAAAA,OAAAhB,OAAA,MAuBiBV,MAAM2B,iBAAiB,WAASd,OAAAC,IAAA;AAAAY,QAAAA,OAAAhB,OAAA;;AAASV,0BAAM4B,gBAAN5B,mBAAmB6B,MAAM,GAAG,OAAM;AAAA,OAASX,QAAAC,KAAA;AAAAO,eAAAnB,OAAAuB,IAAAA,gBAE5FC,cAAI;AAAA,MAAA,IAACC,OAAI;AAAA,eAAEC;AAAAA,MAAmB;AAAA,MAAA,IAAAC,WAAA;AAAA,YAAAC,SAAAjC,IAAAA,eAAAkC,MAAA;AAAAV,YAAAA,OAAAS,QAAA,MAE1BnC,MAAMqC,MAAMC,OAAO;AAAA,eAAAH;AAAAA,MAAA;AAAA,IAAA,CAAA,GAAAd,QAAAC,KAAA;AAAAI,eAAAnB,OAAAuB,IAAAA,gBAGvBC,cAAI;AAAA,MAAA,IAACC,OAAI;AAAA,eAAEhC,MAAMuC;AAAAA,MAAU;AAAA,MAAA,IAAAL,WAAA;AAAA,YAAAM,SAAAtC,IAAAA,eAAAuC,OAAA;AAAAC,YAAAA,iBAAAF,QAAA,SAEfxC,MAAM2C,SAAO,IAAA;AAAAC,+BAAAA;AAAA,eAAAJ;AAAAA,MAAA;AAAA,IAAA,CAAA,GAAAhB,QAAAC,KAAA;AAAA,WAAAxB;AAAAA,EAAA,GAAA;AAUpC;AAKO,MAAM4C,4BAAwE7C,CAAAA,UAAU;AAC7F,QAAM,CAAC8C,UAAUC,WAAW,IAAIC,QAAAA,aAAa,CAAC;AAC9C,QAAM,CAACC,iBAAiBC,kBAAkB,IAAIF,QAAAA,aAAa,CAAC;AAE5D,QAAMG,YAAYC,sBAAAA,aAAAA;AAGlBC,UAAAA,QAAQ,MAAM;AACZ,QAAI,OAAOC,gBAAgB,aAAa;AACtCJ,yBAAmBI,YAAYC,KAAK;AAAA,IACtC;AAAA,EACF,CAAC;AAGD,QAAMC,cAAcA,CAACnB,UAAiB;;AAEpC,UAAMoB,iBAAiB,CAACC,IAAAA,YAAY,OAAOJ,gBAAgB,cACvDA,YAAYC,IAAAA,IAAQN,gBAAAA,IACpB;AAGJ,UAAMU,YAAY,CAACD,gBAAY,OAAOE,cAAc,cAChDA,UAAUD,YACV;AACJ,UAAME,WAAW,CAACH,IAAAA,YAAY,OAAOI,WAAW,cAC5C;AAAA,MAAEC,OAAOD,OAAOE;AAAAA,MAAYC,QAAQH,OAAOI;AAAAA,IAAAA,IAC3C;AAAA,MAAEH,OAAO;AAAA,MAAGE,QAAQ;AAAA,IAAA;AAGxB,UAAME,eAAe;AAAA,MACnBvC,aAAa5B,MAAM4B;AAAAA,MACnBD,eAAe3B,MAAM2B;AAAAA,MACrByC,cAAc/B,MAAMC;AAAAA,MACpB+B,YAAYhC,MAAMiC;AAAAA,MAClBb;AAAAA,MACAc,YAAYzB,SAAAA;AAAAA,MACZ0B,YAAW,oBAAIC,KAAAA,GAAOC,YAAAA;AAAAA,MACtBf;AAAAA,MACAE;AAAAA,IAAAA;AAIFhE,WAAOwC,MAAM,4BAA4BrC,MAAM2B,aAAa,IAAIwC,YAAY;AAG5EnE,gBAAM2E,YAAN3E,+BAAgB;AAAA,MACd4E,MAAM;AAAA,MACNtC,SAASD,MAAMC;AAAAA,MACfV,aAAa5B,MAAM4B;AAAAA,MACnBiD,SAASV;AAAAA,IAAAA;AAMX,QAAIhB,WAAW;AACbA,gBAAU2B,SAAS;AAAA,QACjBF,MAAM;AAAA,QACNG,IAAI/E,MAAM4B;AAAAA,QACVD,eAAe3B,MAAM2B;AAAAA,QACrByC,cAAc/B,MAAMC;AAAAA,QACpB0C,IAAIP,KAAKlB,IAAAA;AAAAA,MAAI,CACd;AAAA,IACH;AAAA,EAOF;AAGA,QAAM0B,cAAcA,MAAM;AACxB,UAAMC,gBAAgBpC,aAAa;AACnCjD,WAAOsF,KAAK,8BAA8BnF,MAAM2B,aAAa,IAAI;AAAA,MAC/DC,aAAa5B,MAAM4B;AAAAA,MACnB2C,YAAYW;AAAAA,IAAAA,CACb;AACDnC,gBAAYmC,aAAa;AAAA,EAC3B;AAEA,SAAApD,IAAAA,gBACGsD,QAAAA,eAAa;AAAA,IACZC,UAAWhD,CAAAA,UAAU;AACnBmB,kBAAYnB,KAAK;AAGjB,UAAIrC,MAAMqF,UAAU;AAClB,eAAOrF,MAAMqF,SAAShD,OAAOrC,MAAMuC,aAAa0C,cAAcK,MAAS;AAAA,MACzE;AAGA,aAAAxD,IAAAA,gBACG/B,sBAAoB;AAAA,QACnBsC;AAAAA,QAAY,IACZT,cAAW;AAAA,iBAAE5B,MAAM4B;AAAAA,QAAW;AAAA,QAAA,IAC9BD,gBAAa;AAAA,iBAAE3B,MAAM2B;AAAAA,QAAa;AAAA,QAAA,IAClCY,aAAU;AAAA,iBAAEvC,MAAMuC;AAAAA,QAAU;AAAA,QAC5BI,SAASsC;AAAAA,MAAAA,CAAW;AAAA,IAG1B;AAAA,IAAC,IAAA/C,WAAA;AAAA,cAGC,MAAM;AACN,aAAKY,SAAAA;AACL,eAAAyC,IAAAA,KAAA,MAAUvF,MAAMkC,QAAQ;AAAA,MAC1B,GAAA;AAAA,IAAI;AAAA,EAAA,CAAA;AAGV;AA+ECsD,IAAAA,eAAA,CAAA,OAAA,CAAA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GenerativeUIErrorBoundary.d.ts","sourceRoot":"","sources":["../../src/components/GenerativeUIErrorBoundary.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAA8C,MAAM,UAAU,CAAA;AAGhF,OAAO,KAAK,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"GenerativeUIErrorBoundary.d.ts","sourceRoot":"","sources":["../../src/components/GenerativeUIErrorBoundary.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAA8C,MAAM,UAAU,CAAA;AAGhF,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,UAAU,CAAA;AAK5D;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC7C;;OAEG;IACH,WAAW,EAAE,MAAM,CAAA;IAEnB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAA;IAErB;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IAExC;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IAEpB;;OAEG;IACH,QAAQ,EAAE,GAAG,CAAA;IAEb;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,MAAM,IAAI,KAAK,GAAG,CAAA;CACrD;AAwDD;;GAEG;AACH,eAAO,MAAM,yBAAyB,EAAE,SAAS,CAAC,8BAA8B,CA+G/E,CAAA;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,CAAC,SAAS;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,EAChG,gBAAgB,EAAE,SAAS,CAAC,CAAC,CAAC,IAEtB,OAAO,CAAC,oCAsCjB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,cA2B/E"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { delegateEvents, createComponent, memo, isServer, getNextElement, template, getNextMarker, insert, addEventListener, runHydrationEvents } from "solid-js/web";
|
|
2
2
|
import { createSignal, onMount, ErrorBoundary, Show } from "solid-js";
|
|
3
3
|
import { createLogger } from "../utils/logger.js";
|
|
4
|
+
import { useTelemetry } from "../context/MCPUITelemetryContext.js";
|
|
4
5
|
var _tmpl$ = /* @__PURE__ */ template(`<p class="text-xs text-yellow-600 dark:text-yellow-400 mt-2 font-mono">`), _tmpl$2 = /* @__PURE__ */ template(`<button class="mt-3 text-xs font-medium text-yellow-800 dark:text-yellow-200 hover:text-yellow-900 dark:hover:text-yellow-100 underline">Retry Rendering`), _tmpl$3 = /* @__PURE__ */ template(`<div class="w-full h-full bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4"><div class="flex items-start gap-3"><div class=flex-shrink-0><svg class="w-5 h-5 text-yellow-600 dark:text-yellow-400"fill=none stroke=currentColor viewBox="0 0 24 24"><path stroke-linecap=round stroke-linejoin=round stroke-width=2 d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg></div><div class="flex-1 min-w-0"><p class="text-sm font-medium text-yellow-900 dark:text-yellow-100">Component Failed to Render</p><p class="text-xs text-yellow-700 dark:text-yellow-300 mt-1">Type: <!$><!/> | ID: <!$><!/>...</p><!$><!/><!$><!/>`);
|
|
5
6
|
const logger = createLogger("generative-ui");
|
|
6
7
|
function DefaultErrorFallback(props) {
|
|
@@ -40,6 +41,7 @@ function DefaultErrorFallback(props) {
|
|
|
40
41
|
const GenerativeUIErrorBoundary = (props) => {
|
|
41
42
|
const [retryKey, setRetryKey] = createSignal(0);
|
|
42
43
|
const [renderStartTime, setRenderStartTime] = createSignal(0);
|
|
44
|
+
const telemetry = useTelemetry();
|
|
43
45
|
onMount(() => {
|
|
44
46
|
if (typeof performance !== "undefined") {
|
|
45
47
|
setRenderStartTime(performance.now());
|
|
@@ -74,6 +76,15 @@ const GenerativeUIErrorBoundary = (props) => {
|
|
|
74
76
|
componentId: props.componentId,
|
|
75
77
|
details: errorContext
|
|
76
78
|
});
|
|
79
|
+
if (telemetry) {
|
|
80
|
+
telemetry.dispatch({
|
|
81
|
+
type: "render:error",
|
|
82
|
+
id: props.componentId,
|
|
83
|
+
componentType: props.componentType,
|
|
84
|
+
errorMessage: error.message,
|
|
85
|
+
ts: Date.now()
|
|
86
|
+
});
|
|
87
|
+
}
|
|
77
88
|
};
|
|
78
89
|
const handleRetry = () => {
|
|
79
90
|
const newRetryCount = retryKey() + 1;
|