@happyvertical/smrt-svelte 0.43.3 → 0.43.5

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.
Files changed (118) hide show
  1. package/AGENTS.md +45 -0
  2. package/README.md +73 -0
  3. package/dist/Provider.svelte +83 -26
  4. package/dist/Provider.svelte.d.ts +4 -12
  5. package/dist/Provider.svelte.d.ts.map +1 -1
  6. package/dist/__tests__/provider-webmcp-harness.svelte +14 -0
  7. package/dist/__tests__/provider-webmcp-harness.svelte.d.ts +8 -0
  8. package/dist/__tests__/provider-webmcp-harness.svelte.d.ts.map +1 -0
  9. package/dist/components/forms/AddressInput.svelte +155 -42
  10. package/dist/components/forms/AddressInput.svelte.d.ts.map +1 -1
  11. package/dist/components/forms/CheckboxInput.svelte +40 -13
  12. package/dist/components/forms/CheckboxInput.svelte.d.ts.map +1 -1
  13. package/dist/components/forms/DateRangeInput.svelte +132 -16
  14. package/dist/components/forms/DateRangeInput.svelte.d.ts.map +1 -1
  15. package/dist/components/forms/DateTimeInput.svelte +48 -14
  16. package/dist/components/forms/DateTimeInput.svelte.d.ts.map +1 -1
  17. package/dist/components/forms/Form.svelte +667 -61
  18. package/dist/components/forms/Form.svelte.d.ts +6 -2
  19. package/dist/components/forms/Form.svelte.d.ts.map +1 -1
  20. package/dist/components/forms/MeasurementInput.svelte +137 -31
  21. package/dist/components/forms/MeasurementInput.svelte.d.ts.map +1 -1
  22. package/dist/components/forms/MoneyInput.svelte +42 -23
  23. package/dist/components/forms/MoneyInput.svelte.d.ts.map +1 -1
  24. package/dist/components/forms/NumberInput.svelte +59 -27
  25. package/dist/components/forms/NumberInput.svelte.d.ts.map +1 -1
  26. package/dist/components/forms/PhoneInput.svelte +51 -22
  27. package/dist/components/forms/PhoneInput.svelte.d.ts.map +1 -1
  28. package/dist/components/forms/SelectInput.svelte +38 -22
  29. package/dist/components/forms/SelectInput.svelte.d.ts.map +1 -1
  30. package/dist/components/forms/TextInput.svelte +32 -18
  31. package/dist/components/forms/TextInput.svelte.d.ts.map +1 -1
  32. package/dist/components/forms/TextareaInput.svelte +25 -18
  33. package/dist/components/forms/TextareaInput.svelte.d.ts.map +1 -1
  34. package/dist/components/forms/__tests__/AddressInput.behavior.test.js +94 -0
  35. package/dist/components/forms/__tests__/Form.behavior.test.js +1 -1
  36. package/dist/components/forms/__tests__/Form.extraction.test.js +68 -0
  37. package/dist/components/forms/__tests__/Form.test.js +28 -0
  38. package/dist/components/forms/__tests__/Form.webmcp.test.js +2120 -111
  39. package/dist/components/forms/__tests__/FormMicButton.test.js +0 -1
  40. package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte +30 -0
  41. package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte.d.ts +7 -0
  42. package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte.d.ts.map +1 -0
  43. package/dist/components/forms/__tests__/async-validation-form.fixture.svelte +19 -0
  44. package/dist/components/forms/__tests__/async-validation-form.fixture.svelte.d.ts +9 -0
  45. package/dist/components/forms/__tests__/async-validation-form.fixture.svelte.d.ts.map +1 -0
  46. package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte +21 -0
  47. package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte.d.ts +8 -0
  48. package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte.d.ts.map +1 -0
  49. package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte +27 -0
  50. package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte.d.ts +9 -0
  51. package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte.d.ts.map +1 -0
  52. package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte +48 -0
  53. package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte.d.ts +10 -0
  54. package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte.d.ts.map +1 -0
  55. package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte +26 -0
  56. package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte.d.ts +13 -0
  57. package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte.d.ts.map +1 -0
  58. package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte +52 -0
  59. package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte.d.ts +16 -0
  60. package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte.d.ts.map +1 -0
  61. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte +108 -5
  62. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts +34 -0
  63. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts.map +1 -1
  64. package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte +24 -0
  65. package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte.d.ts +11 -0
  66. package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte.d.ts.map +1 -0
  67. package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte +69 -4
  68. package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte.d.ts +32 -0
  69. package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte.d.ts.map +1 -1
  70. package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte +44 -0
  71. package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte.d.ts +9 -0
  72. package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte.d.ts.map +1 -0
  73. package/dist/components/forms/__tests__/policy-field.fixture.svelte +35 -0
  74. package/dist/components/forms/__tests__/policy-field.fixture.svelte.d.ts +8 -0
  75. package/dist/components/forms/__tests__/policy-field.fixture.svelte.d.ts.map +1 -0
  76. package/dist/components/forms/prepare-field-value.d.ts +5 -0
  77. package/dist/components/forms/prepare-field-value.d.ts.map +1 -0
  78. package/dist/components/forms/prepare-field-value.js +15 -0
  79. package/dist/index.d.ts +3 -0
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +2 -0
  82. package/dist/provider-webmcp.test.js +244 -0
  83. package/dist/state/form-context.d.ts +28 -6
  84. package/dist/state/form-context.d.ts.map +1 -1
  85. package/dist/state/form-context.js +75 -7
  86. package/dist/web/__tests__/provider-ui-registry.fixture.svelte +43 -0
  87. package/dist/web/__tests__/provider-ui-registry.fixture.svelte.d.ts +13 -0
  88. package/dist/web/__tests__/provider-ui-registry.fixture.svelte.d.ts.map +1 -0
  89. package/dist/web/__tests__/provider-ui-registry.test.js +235 -0
  90. package/dist/web/__tests__/webmcp-composed.child.fixture.svelte +62 -0
  91. package/dist/web/__tests__/webmcp-composed.child.fixture.svelte.d.ts +8 -0
  92. package/dist/web/__tests__/webmcp-composed.child.fixture.svelte.d.ts.map +1 -0
  93. package/dist/web/__tests__/webmcp-composed.fixture.svelte +52 -0
  94. package/dist/web/__tests__/webmcp-composed.fixture.svelte.d.ts +12 -0
  95. package/dist/web/__tests__/webmcp-composed.fixture.svelte.d.ts.map +1 -0
  96. package/dist/web/__tests__/webmcp-composed.integration.svelte.test.js +247 -0
  97. package/dist/web/__tests__/webmcp-ssr.fixture.svelte +13 -0
  98. package/dist/web/__tests__/webmcp-ssr.fixture.svelte.d.ts +19 -0
  99. package/dist/web/__tests__/webmcp-ssr.fixture.svelte.d.ts.map +1 -0
  100. package/dist/web/__tests__/webmcp-ui.test.js +586 -0
  101. package/dist/web/__tests__/webmcp.test.js +21 -1
  102. package/dist/web/index.d.ts +3 -0
  103. package/dist/web/index.d.ts.map +1 -1
  104. package/dist/web/index.js +2 -0
  105. package/dist/web/webmcp-provider.d.ts +23 -0
  106. package/dist/web/webmcp-provider.d.ts.map +1 -0
  107. package/dist/web/webmcp-provider.js +1 -0
  108. package/dist/web/webmcp-ui-context.d.ts +12 -0
  109. package/dist/web/webmcp-ui-context.d.ts.map +1 -0
  110. package/dist/web/webmcp-ui-context.js +16 -0
  111. package/dist/web/webmcp-ui.d.ts +21 -0
  112. package/dist/web/webmcp-ui.d.ts.map +1 -0
  113. package/dist/web/webmcp-ui.js +474 -0
  114. package/dist/web/webmcp.d.ts +1 -1
  115. package/dist/web/webmcp.svelte.d.ts +4 -1
  116. package/dist/web/webmcp.svelte.d.ts.map +1 -1
  117. package/dist/web/webmcp.svelte.js +6 -1
  118. package/package.json +8 -6
package/AGENTS.md CHANGED
@@ -16,6 +16,15 @@ subpath you are editing. This file keeps what holds in every module.
16
16
  | `src/components/settings/` (`./settings`) | `SettingsCatalog`, `paginateSettingsCatalog`, and the summary-vs-detail scalability contract | [agents/settings.md](agents/settings.md) |
17
17
  | `src/components/workspace/` (`./workspace` + `./web`) | the AdminShell family and its principles, the legacy ToolsDock surface, the `./web` activity-feed and `updateAvailable` adapters, and server-side dock gates | [agents/workspace.md](agents/workspace.md) |
18
18
  | `src/web/remote-query.svelte.ts` | Svelte 5 binding for query-shaped remote pages: rows, page, totals, loading/refreshing/stale/error, retry, last-updated, and query-scoped live subscriptions (#2445) | — |
19
+ | `src/web/webmcp-provider.ts` | Provider config for generated data/model WebMCP tools: definitions, effect policy, namespace, budget, legacy/canonical filters, and fetcher seams (#2520) | — |
20
+ | `src/web/webmcp-ui.ts` | Fixed, low-cardinality WebMCP adapter over the Provider's mounted form-control and data-surface registries (#2521) | — |
21
+
22
+ The composed WebMCP fixture in
23
+ `src/web/__tests__/webmcp-composed.integration.svelte.test.ts` mounts a real
24
+ Provider, rich Form, DataTable/DataSurface, and a bespoke `useWebMcpTool`
25
+ component intent. Preserve its lifecycle assertions: SSR and missing
26
+ `document.modelContext` are no-ops, mounted tools are fixed-cardinality, and
27
+ all registrations abort when their owning component unmounts.
19
28
 
20
29
  ## The UI split — primitive-adoption contract (#1589)
21
30
 
@@ -53,6 +62,17 @@ migration; flips strict per package as it adopts the primitives).
53
62
 
54
63
  Wraps app in `+layout.svelte`. Provides auth state, permissions, WebSocket, and AI capabilities.
55
64
 
65
+ The rich `Form` uses smrt-ui's shared staged-edit review contract. Agent
66
+ proposals never mutate field bindings directly: they remain staged until a
67
+ human applies or discards them in the form's local review surface. Keep this
68
+ state machine in smrt-ui; smrt-svelte contributes Provider-backed field
69
+ registration and consumes the shared component rather than duplicating it.
70
+ The generated WebMCP form tool proposes registry stages; it never writes or
71
+ submits field values directly.
72
+ Rich `FieldDefinition` registrations may carry a `subject` for record-qualified
73
+ identity. The Form bridge also folds live DOM disabled/read-only state into the
74
+ registry and omits those fields from WebMCP schemas and staging.
75
+
56
76
  ```svelte
57
77
  <script>
58
78
  let { data, children } = $props();
@@ -64,6 +84,26 @@ Wraps app in `+layout.svelte`. Provides auth state, permissions, WebSocket, and
64
84
  </Provider>
65
85
  ```
66
86
 
87
+ An object `webmcp` config with `definitions` registers generated data/model
88
+ tools through the same registrar as direct smrt-web callers. With no `effects`
89
+ policy, only `read`-effect tools register; `write` and `destructive` require
90
+ explicit opt-in. Use `filter` for legacy collection definitions and `filterTool`
91
+ for canonical definitions; a filter supplied for definitions of the other kind
92
+ fails closed. Namespace, budget, resolver, annotation, and atomic-validation
93
+ behavior must stay identical between Provider and direct registration. The
94
+ authenticated REST route remains the execution authorization boundary.
95
+
96
+ With the fixed mounted-UI adapter enabled, Provider also owns shared
97
+ control/data-surface registries and registers six fixed `smrt_ui_*` tools. This
98
+ adapter is a separate, consent-gated surface and is not governed by the
99
+ generated-model effect policy. Descendant rich Forms use the shared
100
+ control registry unless their explicit `interactionRegistry` prop overrides it.
101
+ Supply `webmcp.ui.dataSurfaceRegistry` when mounted surface components already
102
+ share an application registry. The adapter resolves registry state at execution
103
+ time, never reads the DOM, never accepts agent confirmation, and removes the
104
+ entire tool set with one abort signal. A document may have only one active
105
+ adapter for a prefix; configure distinct prefixes for intentional coexistence.
106
+
67
107
  ## Hooks
68
108
 
69
109
  | Hook | Returns |
@@ -78,6 +118,11 @@ Wraps app in `+layout.svelte`. Provides auth state, permissions, WebSocket, and
78
118
 
79
119
  ## AI System
80
120
 
121
+ - Rich Form WebMCP tools only stage review proposals. Structured payloads are
122
+ limited to published schema properties, money uses safe integer minor units,
123
+ and injected legacy registries without `executeBatch` use ordered `execute`
124
+ calls.
125
+
81
126
  - **Preload strategies**: `none`, `eager`, `idle` (recommended), `on-visible`
82
127
  - **Warm client cache**: module-level Map survives navigation/remounts -- avoids re-downloading WASM/models
83
128
  - **Adapters**: STT (browser-speech, whisper-cpp, whisper-wasm), TTS (browser-synthesis), LLM (webllm, transformers-llm)
package/README.md CHANGED
@@ -92,6 +92,79 @@ hydrating its whole collection. It exposes `rows`, `page`, `total`,
92
92
  </Provider>
93
93
  ```
94
94
 
95
+ The Provider can own generated WebMCP tools for the same lifecycle. Its policy
96
+ is identical to `registerWebMcpTools`; omitted `effects` exposes reads only:
97
+
98
+ ```svelte
99
+ <Provider webmcp={{
100
+ definitions,
101
+ effects: ['read', 'write'],
102
+ namespace: 'workspace',
103
+ maxTools: 24
104
+ }}>
105
+ {@render children()}
106
+ </Provider>
107
+ ```
108
+
109
+ This controls capability exposure, not authorization. Tool execution still
110
+ crosses the authenticated REST boundary and must retain its auth and tenancy
111
+ guards. The `effects`, `filter`, and `filterTool` policy applies to generated
112
+ data/model tools only; the fixed mounted-UI adapter has the separate controls
113
+ described below.
114
+
115
+ WebMCP test doubles and polyfills should implement the browser's
116
+ promise-returning `document.modelContext.registerTool()` contract; declare the
117
+ function `async` when migrating older void-returning fixtures.
118
+
119
+ ### Mounted UI through WebMCP
120
+
121
+ `<Provider webmcp>` registers six fixed `smrt_ui_*` tools for the mounted UI:
122
+ list, inspect, and execute for form controls and data surfaces. The tool set does
123
+ not change as components mount and unmount; each call reads the current
124
+ transport-neutral registries instead of inspecting or simulating the DOM.
125
+
126
+ Forms automatically join the Provider's control registry. An explicit Form
127
+ `interactionRegistry` still takes precedence. Pass the same data-surface
128
+ registry used by `DataTable` or `CollectionToolbar` when those mounted surfaces
129
+ should be discoverable:
130
+
131
+ ```svelte
132
+ <script lang="ts">
133
+ import { createDataSurfaceRegistry } from '@happyvertical/smrt-ui/data';
134
+ import { Provider } from '@happyvertical/smrt-svelte';
135
+
136
+ const surfaces = createDataSurfaceRegistry();
137
+ </script>
138
+
139
+ <Provider webmcp={{ ui: { dataSurfaceRegistry: surfaces } }}>
140
+ <!-- pass {surfaces} to mounted data-surface components -->
141
+ {@render children()}
142
+ </Provider>
143
+ ```
144
+
145
+ The default prefix is `smrt_ui_`. Configure `ui.prefix` when multiple Providers
146
+ must coexist in one document; the same prefix cannot be registered twice.
147
+ `ui: false` disables only the fixed UI adapter while leaving generated model
148
+ tools enabled. For compatibility, an object config that omits `ui` continues to
149
+ enable only generated model tools; use `webmcp={true}` or provide `ui: {}` to
150
+ enable the mounted-UI adapter.
151
+
152
+ Form commands always run with `source: 'agent'`. WebMCP input cannot assert
153
+ confirmation: staging is allowed by the registry policy, while apply, clear,
154
+ and undo require a separate human-confirmed path. Secret control values and
155
+ hidden data-surface columns are not serialized. Read responses are marked as
156
+ untrusted content. Bespoke `useWebMcpTool` and `<Form webmcp>` tools retain their
157
+ existing lifecycle and submit behavior.
158
+
159
+ Custom rich fields may continue to call `registerField(field)` and later
160
+ `unregisterField(name)`. New code should retain and invoke the disposer returned
161
+ by `registerField`: it is bound to that exact registration, so cleanup cannot
162
+ remove a same-name replacement. The return value is additive; legacy form
163
+ contexts whose `registerField` returns `void` remain supported. Context accessors
164
+ bind legacy name-based cleanup to registrations made by that caller, so
165
+ overlapping same-name fields can unmount in either order without retaining a
166
+ detached control.
167
+
95
168
  ### Form Components
96
169
 
97
170
  ```svelte
@@ -1,14 +1,11 @@
1
1
  <script lang="ts">
2
+ import { createDataSurfaceRegistry } from '@happyvertical/smrt-ui/data';
3
+ import { createControlInteractionRegistry } from '@happyvertical/smrt-ui/forms';
2
4
  import {
3
5
  createI18nContext,
4
6
  type I18nSnapshot,
5
7
  setI18nContext,
6
8
  } from '@happyvertical/smrt-ui/i18n';
7
- import {
8
- type RegisterWebMcpToolsOptions,
9
- type SmrtWebClient,
10
- type SmrtWebCollectionDefinition,
11
- } from '@happyvertical/smrt-web';
12
9
  import type { Snippet } from 'svelte';
13
10
  import { onDestroy, untrack } from 'svelte';
14
11
  import { logger } from './internal/logger.js';
@@ -21,6 +18,9 @@ import type {
21
18
  } from './state/app-state.js';
22
19
  import { createAppState } from './state/app-state.svelte.js';
23
20
  import { setAppStateContext } from './state/context.js';
21
+ import type { WebMcpProviderConfig } from './web/webmcp-provider.js';
22
+ import { registerWebMcpUiTools } from './web/webmcp-ui.js';
23
+ import { setWebMcpUiContext } from './web/webmcp-ui-context.js';
24
24
 
25
25
  interface Props {
26
26
  /**
@@ -84,9 +84,9 @@ interface Props {
84
84
  */
85
85
  i18n?: I18nSnapshot;
86
86
  /**
87
- * Opt in to the generated collection tools for this browser surface.
88
- * WebMCP is feature-detected by smrt-web, so this is safe during SSR and on
89
- * browsers that do not expose `document.modelContext`.
87
+ * Opt in to generated data tools and the fixed mounted-UI tool adapter for
88
+ * this browser surface. WebMCP is feature-detected, so this is safe during
89
+ * SSR and on browsers that do not expose `document.modelContext`.
90
90
  */
91
91
  webmcp?: boolean | WebMcpProviderConfig;
92
92
  /**
@@ -95,15 +95,6 @@ interface Props {
95
95
  children: Snippet;
96
96
  }
97
97
 
98
- export interface WebMcpProviderConfig {
99
- definitions?: SmrtWebCollectionDefinition[];
100
- client?: SmrtWebClient;
101
- basePath?: string;
102
- fetchFn?: typeof fetch;
103
- scope?: string;
104
- filter?: RegisterWebMcpToolsOptions['filter'];
105
- }
106
-
107
98
  const {
108
99
  mode,
109
100
  autoEnableSmrt = true,
@@ -135,6 +126,35 @@ const appState = createAppState({
135
126
  },
136
127
  });
137
128
 
129
+ const localControlRegistry = createControlInteractionRegistry();
130
+ const localDataSurfaceRegistry = createDataSurfaceRegistry();
131
+ const webMcpConfig = $derived(typeof webmcp === 'object' ? webmcp : undefined);
132
+ const webMcpUiConfig = $derived(
133
+ webmcp === true
134
+ ? {}
135
+ : typeof webMcpConfig?.ui === 'object'
136
+ ? webMcpConfig.ui
137
+ : undefined,
138
+ );
139
+ const resolvedControlRegistry = $derived(
140
+ webMcpUiConfig?.controlRegistry ?? localControlRegistry,
141
+ );
142
+ const resolvedDataSurfaceRegistry = $derived(
143
+ webMcpUiConfig?.dataSurfaceRegistry ?? localDataSurfaceRegistry,
144
+ );
145
+
146
+ setWebMcpUiContext({
147
+ get enabled() {
148
+ return webMcpUiConfig !== undefined;
149
+ },
150
+ get controlRegistry() {
151
+ return resolvedControlRegistry;
152
+ },
153
+ get dataSurfaceRegistry() {
154
+ return resolvedDataSurfaceRegistry;
155
+ },
156
+ });
157
+
138
158
  $effect(() => {
139
159
  const currentPreferences = untrack(() => appState.state.session.preferences);
140
160
  if (currentPreferences.autoEnableSmrt === autoEnableSmrt) {
@@ -149,6 +169,21 @@ $effect(() => {
149
169
  });
150
170
  });
151
171
 
172
+ // Register one fixed UI tool set. Mounted components change registry contents,
173
+ // not the document-level WebMCP tool set.
174
+ $effect(() => {
175
+ if (typeof window === 'undefined' || !webmcp || !webMcpUiConfig) return;
176
+ try {
177
+ return registerWebMcpUiTools({
178
+ controlRegistry: resolvedControlRegistry,
179
+ dataSurfaceRegistry: resolvedDataSurfaceRegistry,
180
+ ...(webMcpUiConfig.prefix ? { prefix: webMcpUiConfig.prefix } : {}),
181
+ });
182
+ } catch (error) {
183
+ logger.warn('Provider: WebMCP UI tool registration rejected', { error });
184
+ }
185
+ });
186
+
152
187
  // Keep generated data-plane tools scoped to this Provider. The registrar
153
188
  // feature-detects WebMCP itself; effects do not run during SSR.
154
189
  $effect(() => {
@@ -160,16 +195,38 @@ $effect(() => {
160
195
 
161
196
  // Keep the data-plane engine out of applications that do not opt in. The
162
197
  // dynamic import also means SSR never evaluates browser-only engine code.
163
- void import('@happyvertical/smrt-web').then(({ registerWebMcpTools }) => {
164
- if (cancelled) return;
165
- dispose = registerWebMcpTools(config.definitions ?? [], {
166
- ...(config.client ? { client: config.client } : {}),
167
- ...(config.basePath ? { basePath: config.basePath } : {}),
168
- ...(config.fetchFn ? { fetchFn: config.fetchFn } : {}),
169
- ...(config.scope ? { scope: config.scope } : {}),
170
- ...(config.filter ? { filter: config.filter } : {}),
198
+ void import('@happyvertical/smrt-web')
199
+ .then(({ registerWebMcpTools }) => {
200
+ if (cancelled) return;
201
+ const registration = registerWebMcpTools(config.definitions ?? [], {
202
+ client: config.client,
203
+ basePath: config.basePath,
204
+ fetchFn: config.fetchFn,
205
+ scope: config.scope,
206
+ filter: config.filter,
207
+ filterTool: config.filterTool,
208
+ resolveFetchers: config.resolveFetchers,
209
+ resolveToolFetchers: config.resolveToolFetchers,
210
+ effects: config.effects,
211
+ namespace: config.namespace,
212
+ maxTools: config.maxTools,
213
+ });
214
+ dispose = registration;
215
+ void registration.ready.catch((error) => {
216
+ if (!cancelled) {
217
+ logger.warn('Provider: WebMCP data tool registration rejected', {
218
+ error,
219
+ });
220
+ }
221
+ });
222
+ })
223
+ .catch((error) => {
224
+ if (!cancelled) {
225
+ logger.warn('Provider: WebMCP data tool registration rejected', {
226
+ error,
227
+ });
228
+ }
171
229
  });
172
- });
173
230
 
174
231
  return () => {
175
232
  cancelled = true;
@@ -1,15 +1,7 @@
1
1
  import { type I18nSnapshot } from '@happyvertical/smrt-ui/i18n';
2
- import { type RegisterWebMcpToolsOptions, type SmrtWebClient, type SmrtWebCollectionDefinition } from '@happyvertical/smrt-web';
3
2
  import type { Snippet } from 'svelte';
4
3
  import type { AIConfig, AILoadingState, AppMode, SocketConfig, User } from './state/app-state.js';
5
- export interface WebMcpProviderConfig {
6
- definitions?: SmrtWebCollectionDefinition[];
7
- client?: SmrtWebClient;
8
- basePath?: string;
9
- fetchFn?: typeof fetch;
10
- scope?: string;
11
- filter?: RegisterWebMcpToolsOptions['filter'];
12
- }
4
+ import type { WebMcpProviderConfig } from './web/webmcp-provider.js';
13
5
  interface Props {
14
6
  /**
15
7
  * Initial mode: 'default' | 'smrt'
@@ -72,9 +64,9 @@ interface Props {
72
64
  */
73
65
  i18n?: I18nSnapshot;
74
66
  /**
75
- * Opt in to the generated collection tools for this browser surface.
76
- * WebMCP is feature-detected by smrt-web, so this is safe during SSR and on
77
- * browsers that do not expose `document.modelContext`.
67
+ * Opt in to generated data tools and the fixed mounted-UI tool adapter for
68
+ * this browser surface. WebMCP is feature-detected, so this is safe during
69
+ * SSR and on browsers that do not expose `document.modelContext`.
78
70
  */
79
71
  webmcp?: boolean | WebMcpProviderConfig;
80
72
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"Provider.svelte.d.ts","sourceRoot":"","sources":["../src/Provider.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,YAAY,EAElB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,KAAK,0BAA0B,EAC/B,KAAK,aAAa,EAClB,KAAK,2BAA2B,EACjC,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGtC,OAAO,KAAK,EACV,QAAQ,EACR,cAAc,EACd,OAAO,EACP,YAAY,EACZ,IAAI,EACL,MAAM,sBAAsB,CAAC;AAK9B,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,2BAA2B,EAAE,CAAC;IAC5C,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,0BAA0B,CAAC,QAAQ,CAAC,CAAC;CAC/C;AAED,UAAU,KAAK;IACb;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;;;OAGG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;;;;;;;;;;;;;OAeG;IACH,EAAE,CAAC,EAAE,QAAQ,CAAC;IACd;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC;;OAEG;IACH,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IACpD;;;;;OAKG;IACH,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,oBAAoB,CAAC;IACxC;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAC;CACnB;AA4KD,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"Provider.svelte.d.ts","sourceRoot":"","sources":["../src/Provider.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,YAAY,EAElB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGtC,OAAO,KAAK,EACV,QAAQ,EACR,cAAc,EACd,OAAO,EACP,YAAY,EACZ,IAAI,EACL,MAAM,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAKrE,UAAU,KAAK;IACb;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;;;OAGG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;;;;;;;;;;;;;OAeG;IACH,EAAE,CAAC,EAAE,QAAQ,CAAC;IACd;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC;;OAEG;IACH,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IACpD;;;;;OAKG;IACH,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,oBAAoB,CAAC;IACxC;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAC;CACnB;AAiPD,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
@@ -0,0 +1,14 @@
1
+ <script lang="ts">
2
+ import Provider from '../Provider.svelte';
3
+ import type { WebMcpProviderConfig } from '../web/webmcp-provider.js';
4
+
5
+ interface Props {
6
+ webmcp: WebMcpProviderConfig;
7
+ }
8
+
9
+ const { webmcp }: Props = $props();
10
+ </script>
11
+
12
+ <Provider {webmcp}>
13
+ <span></span>
14
+ </Provider>
@@ -0,0 +1,8 @@
1
+ import type { WebMcpProviderConfig } from '../web/webmcp-provider.js';
2
+ interface Props {
3
+ webmcp: WebMcpProviderConfig;
4
+ }
5
+ declare const ProviderWebmcpHarness: import("svelte").Component<Props, {}, "">;
6
+ type ProviderWebmcpHarness = ReturnType<typeof ProviderWebmcpHarness>;
7
+ export default ProviderWebmcpHarness;
8
+ //# sourceMappingURL=provider-webmcp-harness.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-webmcp-harness.svelte.d.ts","sourceRoot":"","sources":["../../src/__tests__/provider-webmcp-harness.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAGtE,UAAU,KAAK;IACb,MAAM,EAAE,oBAAoB,CAAC;CAC9B;AAeD,QAAA,MAAM,qBAAqB,2CAAwC,CAAC;AACpE,KAAK,qBAAqB,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACtE,eAAe,qBAAqB,CAAC"}
@@ -1,12 +1,16 @@
1
1
  <script lang="ts">
2
2
  import { useI18n } from '@happyvertical/smrt-ui/i18n';
3
- import { onDestroy, onMount } from 'svelte';
3
+ import { untrack } from 'svelte';
4
4
  import { useAppState } from '../../hooks/useAppState.svelte.js';
5
5
  import { M } from '../../i18n/strings.forms.js';
6
6
  import {
7
7
  type FieldDefinition,
8
8
  tryGetFormContext,
9
9
  } from '../../state/form-context.js';
10
+ import {
11
+ invalidStagedValue,
12
+ prepareTextFieldValue,
13
+ } from './prepare-field-value.js';
10
14
  import type { AddressValue } from './types.js';
11
15
 
12
16
  const { t } = useI18n();
@@ -80,8 +84,66 @@ let {
80
84
  onchange,
81
85
  }: Props = $props();
82
86
 
87
+ function countryValues(): string[] {
88
+ return [...new Set(countries.map((option) => option.value))];
89
+ }
90
+
91
+ function provinceValues(): string[] {
92
+ return [
93
+ ...new Set([
94
+ ...(!required ? [''] : []),
95
+ ...provinces.map((option) => option.value),
96
+ ]),
97
+ ];
98
+ }
99
+
100
+ function schemaForField(field: AddressField): Record<string, unknown> {
101
+ if (field === 'country') return { type: 'string', enum: countryValues() };
102
+ if (field === 'province') return { type: 'string', enum: provinceValues() };
103
+ return { type: 'string' };
104
+ }
105
+
106
+ function validatesAddressMembers(candidate: unknown): boolean {
107
+ if (typeof candidate !== 'object' || Array.isArray(candidate)) return false;
108
+ const address = candidate as Record<string, unknown>;
109
+ if (
110
+ ![Object.prototype, null].includes(Object.getPrototypeOf(address)) ||
111
+ !Object.keys(address).every((key) =>
112
+ fields.includes(key as AddressField),
113
+ ) ||
114
+ !fields.every(
115
+ (field) =>
116
+ !Object.hasOwn(address, field) || typeof address[field] === 'string',
117
+ )
118
+ ) {
119
+ return false;
120
+ }
121
+ if (
122
+ (Object.hasOwn(address, 'country') &&
123
+ !countryValues().includes(address.country as string)) ||
124
+ (Object.hasOwn(address, 'province') &&
125
+ !provinceValues().includes(address.province as string))
126
+ ) {
127
+ return false;
128
+ }
129
+ return true;
130
+ }
131
+
132
+ function validatesAddressCandidate(candidate: unknown): boolean {
133
+ if (candidate === null || candidate === undefined) return !required;
134
+ if (!validatesAddressMembers(candidate)) return false;
135
+ if (!required) return true;
136
+ const address = candidate as Record<string, unknown>;
137
+ return fields.every(
138
+ (field) =>
139
+ typeof address[field] === 'string' && address[field].trim().length > 0,
140
+ );
141
+ }
142
+
83
143
  const app = useAppState();
84
144
  const formContext = tryGetFormContext();
145
+ const fieldOwnerId = $props.id();
146
+ const fieldOwnerToken = `smrt-rich-field-${fieldOwnerId}`;
85
147
 
86
148
  const isSmrt = $derived(app.state.mode === 'smrt');
87
149
 
@@ -90,7 +152,7 @@ let street = $state(value.street ?? '');
90
152
  let city = $state(value.city ?? '');
91
153
  let province = $state(value.province ?? '');
92
154
  let postalCode = $state(value.postalCode ?? '');
93
- let country = $state(value.country ?? 'CA');
155
+ let country = $state(value.country ?? untrack(() => countries[0]?.value ?? ''));
94
156
 
95
157
  // Keep in sync with external value changes
96
158
  $effect(() => {
@@ -105,13 +167,18 @@ $effect(() => {
105
167
  country = value.country;
106
168
  });
107
169
 
108
- function updateValue() {
170
+ function currentValue(): Partial<AddressValue> {
109
171
  const newValue: Partial<AddressValue> = {};
110
172
  if (fields.includes('street')) newValue.street = street;
111
173
  if (fields.includes('city')) newValue.city = city;
112
174
  if (fields.includes('province')) newValue.province = province;
113
175
  if (fields.includes('postalCode')) newValue.postalCode = postalCode;
114
176
  if (fields.includes('country')) newValue.country = country;
177
+ return newValue;
178
+ }
179
+
180
+ function updateValue() {
181
+ const newValue = currentValue();
115
182
  value = newValue;
116
183
  onchange?.(newValue);
117
184
  }
@@ -141,11 +208,16 @@ function parseSpokenAddress(text: string): Partial<AddressValue> {
141
208
  }
142
209
  }
143
210
 
144
- // Try to extract country
145
- if (/\bcanada\b/i.test(normalized)) {
146
- result.country = 'CA';
147
- } else if (/\bunited states\b|\busa\b|\bu\.?s\.?a?\.?\b/i.test(normalized)) {
148
- result.country = 'US';
211
+ // Try to extract a currently configured country by its label or value.
212
+ for (const option of countries) {
213
+ const matches = [option.label, option.value].some((candidate) => {
214
+ const escaped = candidate.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
215
+ return new RegExp(`(^|\\W)${escaped}(?=$|\\W)`, 'i').test(normalized);
216
+ });
217
+ if (matches) {
218
+ result.country = option.value;
219
+ break;
220
+ }
149
221
  }
150
222
 
151
223
  // Common city extraction patterns
@@ -192,26 +264,33 @@ const primaryFieldId = $derived.by(() => {
192
264
  });
193
265
 
194
266
  // Register with form context
195
- onMount(() => {
267
+ $effect(() => {
196
268
  if (formContext) {
269
+ const registeredName = name;
197
270
  const fieldDef: FieldDefinition = {
198
- name,
271
+ name: registeredName,
272
+ ownerToken: fieldOwnerToken,
199
273
  type: 'address',
200
- label,
201
- description:
202
- description ||
203
- 'A mailing address with street, city, province, postal code, and country',
274
+ get label() {
275
+ return label;
276
+ },
277
+ get description() {
278
+ return (
279
+ description ||
280
+ 'A mailing address with street, city, province, postal code, and country'
281
+ );
282
+ },
204
283
  setValue: (v: unknown) => {
205
284
  if (v === null || v === undefined) {
206
285
  street = '';
207
286
  city = '';
208
287
  province = '';
209
288
  postalCode = '';
210
- country = 'CA';
289
+ country = countries[0]?.value ?? '';
211
290
  updateValue();
212
291
  return;
213
292
  }
214
- if (typeof v === 'object' && v !== null) {
293
+ if (validatesAddressMembers(v)) {
215
294
  const addr = v as Partial<AddressValue>;
216
295
  if (addr.street !== undefined) street = addr.street;
217
296
  if (addr.city !== undefined) city = addr.city;
@@ -222,36 +301,70 @@ onMount(() => {
222
301
  return;
223
302
  }
224
303
  // Try to parse spoken address
225
- const parsed = parseSpokenAddress(String(v));
226
- if (Object.keys(parsed).length > 0) {
227
- if (parsed.street) street = parsed.street;
228
- if (parsed.city) city = parsed.city;
229
- if (parsed.province) province = parsed.province;
230
- if (parsed.postalCode) postalCode = parsed.postalCode;
231
- if (parsed.country) country = parsed.country;
304
+ const parsed = Object.fromEntries(
305
+ Object.entries(parseSpokenAddress(String(v))).filter(([field]) =>
306
+ fields.includes(field as AddressField),
307
+ ),
308
+ ) as Partial<AddressValue>;
309
+ if (Object.keys(parsed).length > 0 && validatesAddressMembers(parsed)) {
310
+ if (parsed.street !== undefined) street = parsed.street;
311
+ if (parsed.city !== undefined) city = parsed.city;
312
+ if (parsed.province !== undefined) province = parsed.province;
313
+ if (parsed.postalCode !== undefined) postalCode = parsed.postalCode;
314
+ if (parsed.country !== undefined) country = parsed.country;
232
315
  updateValue();
233
316
  }
234
317
  },
235
- getValue: () => value,
236
- constraints: { required },
237
- webMcpSchema: {
238
- type: 'object',
239
- properties: Object.fromEntries(
240
- fields.map((field) => [field, { type: 'string' }]),
241
- ),
242
- ...(required ? { required: [...fields] } : {}),
318
+ getValue: currentValue,
319
+ prepareValue: (candidate) => {
320
+ if (candidate === null || candidate === undefined) {
321
+ return Object.fromEntries(
322
+ fields.map((field) => [
323
+ field,
324
+ field === 'country' ? (countries[0]?.value ?? '') : '',
325
+ ]),
326
+ );
327
+ }
328
+ if (typeof candidate === 'object' && !Array.isArray(candidate)) {
329
+ return candidate;
330
+ }
331
+ const parsed = parseSpokenAddress(prepareTextFieldValue(candidate));
332
+ const configured = Object.fromEntries(
333
+ Object.entries(parsed).filter(([field]) =>
334
+ fields.includes(field as AddressField),
335
+ ),
336
+ );
337
+ return Object.keys(configured).length > 0
338
+ ? { ...currentValue(), ...configured }
339
+ : invalidStagedValue();
340
+ },
341
+ clear: () => {
342
+ street = '';
343
+ city = '';
344
+ province = '';
345
+ postalCode = '';
346
+ country = countries[0]?.value ?? '';
347
+ updateValue();
348
+ return true;
349
+ },
350
+ getState: () => ({ disabled }),
351
+ get constraints() {
352
+ return { required };
353
+ },
354
+ get webMcpSchema() {
355
+ return {
356
+ type: 'object',
357
+ additionalProperties: false,
358
+ properties: Object.fromEntries(
359
+ fields.map((field) => [field, schemaForField(field)]),
360
+ ),
361
+ ...(required ? { required: [...fields] } : {}),
362
+ };
243
363
  },
244
- validate: () =>
245
- !required ||
246
- fields.every((field) => String(value[field] ?? '').trim().length > 0),
364
+ validateValue: validatesAddressCandidate,
365
+ validate: () => validatesAddressCandidate(currentValue()),
247
366
  };
248
- formContext.registerField(fieldDef);
249
- }
250
- });
251
-
252
- onDestroy(() => {
253
- if (formContext) {
254
- formContext.unregisterField(name);
367
+ return formContext.registerField(fieldDef);
255
368
  }
256
369
  });
257
370
 
@@ -281,7 +394,7 @@ function handleCountryChange(e: Event) {
281
394
  }
282
395
  </script>
283
396
 
284
- <div class="smrt-address">
397
+ <div class="smrt-address" data-smrt-field-owner={fieldOwnerToken}>
285
398
  {#if label}
286
399
  <label class="smrt-label" for={primaryFieldId}>
287
400
  {label}
@@ -1 +1 @@
1
- {"version":3,"file":"AddressInput.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/AddressInput.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,KAAK,YAAY,GAAG,MAAM,YAAY,CAAC;AAEvC,MAAM,WAAW,KAAK;IACpB,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC9B,2BAA2B;IAC3B,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IACxB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,0CAA0C;IAC1C,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,gCAAgC;IAChC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC;CACnD;AAmUD,QAAA,MAAM,YAAY,gDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"AddressInput.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/AddressInput.svelte.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,KAAK,YAAY,GAAG,MAAM,YAAY,CAAC;AAEvC,MAAM,WAAW,KAAK;IACpB,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC9B,2BAA2B;IAC3B,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IACxB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,0CAA0C;IAC1C,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,gCAAgC;IAChC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC;CACnD;AAibD,QAAA,MAAM,YAAY,gDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}