@happyvertical/smrt-svelte 0.51.39 → 0.53.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/AGENTS.md +3 -0
- package/README.md +214 -1
- package/agents/components.md +8 -1
- package/agents/workspace.md +24 -0
- package/dist/Provider.svelte +19 -15
- package/dist/Provider.svelte.d.ts +2 -2
- package/dist/Provider.svelte.d.ts.map +1 -1
- package/dist/__tests__/provider-mode-harness.svelte +14 -0
- package/dist/__tests__/provider-mode-harness.svelte.d.ts +11 -0
- package/dist/__tests__/provider-mode-harness.svelte.d.ts.map +1 -0
- package/dist/__tests__/provider-mode-probe.svelte +13 -0
- package/dist/__tests__/provider-mode-probe.svelte.d.ts +8 -0
- package/dist/__tests__/provider-mode-probe.svelte.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/browser-speech.d.ts +11 -0
- package/dist/browser-ai/adapters/stt/browser-speech.d.ts.map +1 -1
- package/dist/browser-ai/adapters/stt/browser-speech.js +20 -4
- package/dist/browser-ai/adapters/stt/browser-speech.test.js +113 -0
- package/dist/browser-ai/adapters/stt/dictation-source.d.ts +3 -0
- package/dist/browser-ai/adapters/stt/dictation-source.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/dictation-source.js +38 -0
- package/dist/browser-ai/adapters/stt/dictation-source.test.js +52 -0
- package/dist/browser-ai/adapters/stt/index.d.ts +2 -1
- package/dist/browser-ai/adapters/stt/index.d.ts.map +1 -1
- package/dist/browser-ai/adapters/stt/index.js +2 -1
- package/dist/components/forms/FileUpload.svelte +120 -16
- package/dist/components/forms/FileUpload.svelte.d.ts +7 -1
- package/dist/components/forms/FileUpload.svelte.d.ts.map +1 -1
- package/dist/components/forms/Form.svelte +118 -8
- package/dist/components/forms/Form.svelte.d.ts +18 -2
- package/dist/components/forms/Form.svelte.d.ts.map +1 -1
- package/dist/components/forms/FormScope.svelte +202 -0
- package/dist/components/forms/FormScope.svelte.d.ts +22 -0
- package/dist/components/forms/FormScope.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/FileUpload.native-form.test.js +249 -0
- package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte +24 -0
- package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte.d.ts +10 -0
- package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/Form.ui-controls.test.js +119 -0
- package/dist/components/forms/__tests__/Form.webmcp.test.js +5 -3
- package/dist/components/forms/__tests__/FormScope.test.js +39 -0
- package/dist/components/forms/__tests__/form-scope.fixture.svelte +30 -0
- package/dist/components/forms/__tests__/form-scope.fixture.svelte.d.ts +10 -0
- package/dist/components/forms/__tests__/form-scope.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte +84 -0
- package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte.d.ts +14 -0
- package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/index.d.ts +1 -0
- package/dist/components/forms/index.d.ts.map +1 -1
- package/dist/components/forms/index.js +1 -0
- package/dist/components/workspace/README.md +11 -0
- package/dist/components/workspace/__tests__/AdminShell.responsive.test.js +597 -0
- package/dist/components/workspace/__tests__/AdminShell.test.js +257 -1
- package/dist/components/workspace/__tests__/TenantNav.test.js +115 -0
- package/dist/components/workspace/__tests__/WorkspaceAccountMenu.test.js +6 -0
- package/dist/components/workspace/__tests__/admin-shell-header-overflow.test.js +25 -0
- package/dist/components/workspace/__tests__/admin-shell-mobile.test.js +217 -0
- package/dist/components/workspace/__tests__/admin-shell-page-contracts.test.js +30 -0
- package/dist/components/workspace/__tests__/admin-shell-responsive-state.test.js +184 -0
- package/dist/components/workspace/__tests__/admin-shell-state.test.js +25 -0
- package/dist/components/workspace/__tests__/admin-shell-theme-hooks.test.js +36 -0
- package/dist/components/workspace/__tests__/shell-chrome-components.test.js +108 -0
- package/dist/components/workspace/admin-shell/AdminShell.svelte +1099 -59
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts +44 -1
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte +170 -0
- package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte.d.ts +11 -0
- package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/PhoneTopBar.svelte +93 -0
- package/dist/components/workspace/admin-shell/PhoneTopBar.svelte.d.ts +11 -0
- package/dist/components/workspace/admin-shell/PhoneTopBar.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ShellNavToggle.svelte +88 -0
- package/dist/components/workspace/admin-shell/ShellNavToggle.svelte.d.ts +14 -0
- package/dist/components/workspace/admin-shell/ShellNavToggle.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ShellTitle.svelte +42 -0
- package/dist/components/workspace/admin-shell/ShellTitle.svelte.d.ts +11 -0
- package/dist/components/workspace/admin-shell/ShellTitle.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/TenantNav.svelte +116 -13
- package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts +8 -2
- package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte +16 -2
- package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte.d.ts +2 -0
- package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/mobile-shell-dom.d.ts +37 -0
- package/dist/components/workspace/admin-shell/mobile-shell-dom.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/mobile-shell-dom.js +150 -0
- package/dist/components/workspace/admin-shell/mobile-shell.d.ts +191 -0
- package/dist/components/workspace/admin-shell/mobile-shell.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/mobile-shell.js +165 -0
- package/dist/components/workspace/admin-shell/settings.d.ts +27 -2
- package/dist/components/workspace/admin-shell/settings.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/settings.js +84 -2
- package/dist/components/workspace/admin-shell/state.svelte.d.ts +57 -1
- package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/state.svelte.js +156 -6
- package/dist/components/workspace/admin-shell/types.d.ts +115 -1
- package/dist/components/workspace/admin-shell/types.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/types.js +13 -0
- package/dist/components/workspace/index.d.ts +11 -4
- package/dist/components/workspace/index.d.ts.map +1 -1
- package/dist/components/workspace/index.js +12 -3
- package/dist/i18n/strings.forms.d.ts +3 -0
- package/dist/i18n/strings.forms.d.ts.map +1 -1
- package/dist/i18n/strings.forms.js +3 -0
- package/dist/i18n/strings.workspace.d.ts +14 -0
- package/dist/i18n/strings.workspace.d.ts.map +1 -1
- package/dist/i18n/strings.workspace.js +14 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/provider-mode.test.js +86 -0
- package/dist/state/app-state.svelte.d.ts.map +1 -1
- package/dist/state/app-state.svelte.js +8 -8
- package/dist/web/__tests__/assistant-dock.integration.svelte.test.js +2 -2
- package/dist/web/__tests__/list-sort-surface.fixture.svelte +41 -0
- package/dist/web/__tests__/list-sort-surface.fixture.svelte.d.ts +15 -0
- package/dist/web/__tests__/list-sort-surface.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/list-sort-surface.svelte.test.js +59 -0
- package/dist/web/__tests__/surfaces.child.fixture.svelte +69 -0
- package/dist/web/__tests__/surfaces.child.fixture.svelte.d.ts +16 -0
- package/dist/web/__tests__/surfaces.child.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/surfaces.fixture.svelte +26 -0
- package/dist/web/__tests__/surfaces.fixture.svelte.d.ts +18 -0
- package/dist/web/__tests__/surfaces.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/surfaces.svelte.test.js +140 -0
- package/dist/web/__tests__/webmcp-ui.test.js +18 -0
- package/dist/web/index.d.ts +4 -1
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +4 -1
- package/dist/web/link-surface.svelte.d.ts +14 -0
- package/dist/web/link-surface.svelte.d.ts.map +1 -0
- package/dist/web/link-surface.svelte.js +49 -0
- package/dist/web/list-surface.svelte.d.ts +80 -0
- package/dist/web/list-surface.svelte.d.ts.map +1 -0
- package/dist/web/list-surface.svelte.js +309 -0
- package/dist/web/step-surface.svelte.d.ts +17 -0
- package/dist/web/step-surface.svelte.d.ts.map +1 -0
- package/dist/web/step-surface.svelte.js +56 -0
- package/dist/web/webmcp-ui-context.d.ts +7 -0
- package/dist/web/webmcp-ui-context.d.ts.map +1 -1
- package/dist/web/webmcp-ui-context.js +10 -0
- package/dist/web/webmcp-ui.d.ts.map +1 -1
- package/dist/web/webmcp-ui.js +16 -3
- package/package.json +17 -17
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useListSurface` — mount a page's rendered list on the nearest
|
|
3
|
+
* `<Provider webmcp>` data-surface registry in one call.
|
|
4
|
+
*
|
|
5
|
+
* A thin convenience over {@link mountListDataSurface} that owns the whole
|
|
6
|
+
* lifecycle (descriptor, controller, identity-keyed re-mount, context
|
|
7
|
+
* updates, destroy) and adds what a bare mount does not:
|
|
8
|
+
*
|
|
9
|
+
* - the visible rows, projected to the declared columns and capped, are
|
|
10
|
+
* published as `state.rows`, so an agent reading the surface sees what the
|
|
11
|
+
* person sees even on a page with no server catalog surface;
|
|
12
|
+
* - a `find` control (when any column is `searchable`) looks rows up by text:
|
|
13
|
+
* payload `{ text }` (or a bare string) keeps the rows whose searchable
|
|
14
|
+
* columns contain it, case-insensitively, across ALL rows — not only the
|
|
15
|
+
* published first page — and publishes them as `state.rows` with
|
|
16
|
+
* `state.find = { text, matches }`. An empty text clears it. It narrows
|
|
17
|
+
* only what the agent reads; the page's own list is unchanged.
|
|
18
|
+
*
|
|
19
|
+
* - sorting (when `sort` and `onSort` are given): the current order is
|
|
20
|
+
* published as `state.sort` and in the table snapshot, `sortable` columns
|
|
21
|
+
* are declared sortable, and a `set-sorting` control
|
|
22
|
+
* (`{ sorting: [{ columnId, direction }] }`, one rule) calls `onSort` — the
|
|
23
|
+
* same path the page's own column headers take, so a server-paginated
|
|
24
|
+
* list re-queries in that order (see `list-sort` in
|
|
25
|
+
* `@happyvertical/smrt-ui/data`).
|
|
26
|
+
*
|
|
27
|
+
* Otherwise a read-only mirror: no remote filter/page/selection commands are
|
|
28
|
+
* accepted (the page's own controls stay the source of truth); `refresh` is
|
|
29
|
+
* honoured when supplied. Only declared columns leave the page: never declare
|
|
30
|
+
* a secret (tokens, keys), and mark names/emails the person can see as
|
|
31
|
+
* `personal`. No-op without a WebMCP Provider. Call during component init.
|
|
32
|
+
*/
|
|
33
|
+
import { createDataTableController, } from '@happyvertical/smrt-ui/data';
|
|
34
|
+
import { onDestroy } from 'svelte';
|
|
35
|
+
import { mountListDataSurface, } from './list-data-surface.svelte.js';
|
|
36
|
+
import { tryGetWebMcpUiContext } from './webmcp-ui-context.js';
|
|
37
|
+
function sortEnabled(options) {
|
|
38
|
+
return (typeof options.onSort === 'function' &&
|
|
39
|
+
options.columns.some((column) => column.sortable));
|
|
40
|
+
}
|
|
41
|
+
const DEFAULT_MAX_ROWS = 50;
|
|
42
|
+
const MAX_QUERY_ROWS = 300;
|
|
43
|
+
const MAX_FIND_TEXT = 200;
|
|
44
|
+
function toJsonPrimitive(value) {
|
|
45
|
+
if (value === null || value === undefined)
|
|
46
|
+
return null;
|
|
47
|
+
if (typeof value === 'string' || typeof value === 'boolean')
|
|
48
|
+
return value;
|
|
49
|
+
if (typeof value === 'number')
|
|
50
|
+
return Number.isFinite(value) ? value : null;
|
|
51
|
+
if (value instanceof Date)
|
|
52
|
+
return Number.isNaN(value.getTime()) ? null : value.toISOString();
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
/** Project rows to the declared columns only; everything else is dropped. */
|
|
56
|
+
export function projectListRows(rows, columns, maxRows = DEFAULT_MAX_ROWS) {
|
|
57
|
+
return rows.slice(0, maxRows).map((row) => {
|
|
58
|
+
const projected = {};
|
|
59
|
+
for (const column of columns) {
|
|
60
|
+
projected[column.id] = toJsonPrimitive(row[column.id]);
|
|
61
|
+
}
|
|
62
|
+
return projected;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function normalizeText(value) {
|
|
66
|
+
return value.normalize('NFKD').toLowerCase().replace(/\s+/g, ' ').trim();
|
|
67
|
+
}
|
|
68
|
+
/** Rows whose searchable columns contain `text` (case- and accent-insensitive). */
|
|
69
|
+
export function findListRows(rows, columns, text) {
|
|
70
|
+
const wanted = normalizeText(text);
|
|
71
|
+
if (!wanted)
|
|
72
|
+
return [...rows];
|
|
73
|
+
const searchable = columns.filter((column) => column.searchable);
|
|
74
|
+
return rows.filter((row) => searchable.some((column) => {
|
|
75
|
+
const value = row[column.id];
|
|
76
|
+
return ((typeof value === 'string' || typeof value === 'number') &&
|
|
77
|
+
normalizeText(String(value))
|
|
78
|
+
.replace(/\p{M}/gu, '')
|
|
79
|
+
.includes(wanted.replace(/\p{M}/gu, '')));
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
/** The descriptor {@link useListSurface} mounts. */
|
|
83
|
+
export function listSurfaceDescriptor(options) {
|
|
84
|
+
const rowKey = options.columns.find((column) => column.rowKey)?.id ??
|
|
85
|
+
options.columns[0]?.id ??
|
|
86
|
+
'id';
|
|
87
|
+
const searchableIds = options.columns
|
|
88
|
+
.filter((column) => column.searchable)
|
|
89
|
+
.map((column) => column.id);
|
|
90
|
+
const sortableIds = sortEnabled(options)
|
|
91
|
+
? options.columns
|
|
92
|
+
.filter((column) => column.sortable)
|
|
93
|
+
.map((column) => column.id)
|
|
94
|
+
: [];
|
|
95
|
+
const columns = options.columns.map((column) => ({
|
|
96
|
+
id: column.id,
|
|
97
|
+
label: column.label,
|
|
98
|
+
...(column.description ? { description: column.description } : {}),
|
|
99
|
+
...(column.sensitivity ? { sensitivity: column.sensitivity } : {}),
|
|
100
|
+
capabilities: [
|
|
101
|
+
'read',
|
|
102
|
+
...(column.searchable ? ['search'] : []),
|
|
103
|
+
...(sortableIds.includes(column.id) ? ['sort'] : []),
|
|
104
|
+
],
|
|
105
|
+
...(sortableIds.includes(column.id)
|
|
106
|
+
? { operators: { sort: ['asc', 'desc'] } }
|
|
107
|
+
: {}),
|
|
108
|
+
...(column.id === rowKey
|
|
109
|
+
? { role: 'row-key' }
|
|
110
|
+
: column.status
|
|
111
|
+
? { role: 'status' }
|
|
112
|
+
: {}),
|
|
113
|
+
}));
|
|
114
|
+
const searchLabels = options.columns
|
|
115
|
+
.filter((column) => column.searchable)
|
|
116
|
+
.map((column) => column.label.toLowerCase());
|
|
117
|
+
return {
|
|
118
|
+
version: 1,
|
|
119
|
+
identity: {
|
|
120
|
+
kind: options.kind ?? 'list',
|
|
121
|
+
surfaceId: options.surfaceId,
|
|
122
|
+
...(options.subject ? { subject: options.subject } : {}),
|
|
123
|
+
},
|
|
124
|
+
schemaVersion: 1,
|
|
125
|
+
label: options.label,
|
|
126
|
+
description: options.description,
|
|
127
|
+
rowKey,
|
|
128
|
+
columns,
|
|
129
|
+
query: {
|
|
130
|
+
modes: ['rows'],
|
|
131
|
+
projectableColumnIds: columns.map((column) => column.id),
|
|
132
|
+
...(searchableIds.length > 0
|
|
133
|
+
? { searchableColumnIds: searchableIds }
|
|
134
|
+
: {}),
|
|
135
|
+
filterableColumnIds: [],
|
|
136
|
+
sortableColumnIds: sortableIds,
|
|
137
|
+
},
|
|
138
|
+
controls: [
|
|
139
|
+
...(options.refresh ? [{ id: 'refresh', label: 'Refresh' }] : []),
|
|
140
|
+
...(sortableIds.length > 0
|
|
141
|
+
? [
|
|
142
|
+
{
|
|
143
|
+
id: 'set-sorting',
|
|
144
|
+
label: 'Sort',
|
|
145
|
+
description: `Re-order the list the way its column headers do. Payload: { sorting: [{ columnId, direction: 'asc' | 'desc' }] } with exactly one rule; sortable columns: ${sortableIds.join(', ')}. The current order is state.sort.`,
|
|
146
|
+
},
|
|
147
|
+
]
|
|
148
|
+
: []),
|
|
149
|
+
...(searchableIds.length > 0
|
|
150
|
+
? [
|
|
151
|
+
{
|
|
152
|
+
id: 'find',
|
|
153
|
+
label: 'Find',
|
|
154
|
+
description: `Look rows up by ${searchLabels.join(' or ')}. Payload: { text } — rows containing it (any case) are published in state.rows; empty text clears.`,
|
|
155
|
+
},
|
|
156
|
+
]
|
|
157
|
+
: []),
|
|
158
|
+
],
|
|
159
|
+
actions: [],
|
|
160
|
+
// Constant: a mount publishes its descriptor once, so a row count
|
|
161
|
+
// captured here would cap the surface for the life of the mount.
|
|
162
|
+
limits: {
|
|
163
|
+
maxQueryRows: MAX_QUERY_ROWS,
|
|
164
|
+
maxQueryBytes: 1_000_000,
|
|
165
|
+
maxSelectionSize: MAX_QUERY_ROWS,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function findTextFromPayload(payload) {
|
|
170
|
+
const raw = typeof payload === 'string'
|
|
171
|
+
? payload
|
|
172
|
+
: payload && typeof payload === 'object' && !Array.isArray(payload)
|
|
173
|
+
? ['text', 'q', 'query', 'search', 'title']
|
|
174
|
+
.map((key) => payload[key])
|
|
175
|
+
.find((value) => typeof value === 'string')
|
|
176
|
+
: undefined;
|
|
177
|
+
if (typeof raw !== 'string' || raw.length > MAX_FIND_TEXT)
|
|
178
|
+
return null;
|
|
179
|
+
return raw;
|
|
180
|
+
}
|
|
181
|
+
function surfaceContext(options, findText) {
|
|
182
|
+
const visible = findText
|
|
183
|
+
? findListRows(options.rows, options.columns, findText)
|
|
184
|
+
: options.rows;
|
|
185
|
+
const rows = projectListRows(visible, options.columns, options.maxRows);
|
|
186
|
+
return {
|
|
187
|
+
...(options.context ?? {}),
|
|
188
|
+
...(options.sort
|
|
189
|
+
? {
|
|
190
|
+
sort: {
|
|
191
|
+
columnId: options.sort.columnId,
|
|
192
|
+
direction: options.sort.direction,
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
: {}),
|
|
196
|
+
rowCount: options.rows.length,
|
|
197
|
+
rows,
|
|
198
|
+
rowsTruncated: visible.length > rows.length,
|
|
199
|
+
find: findText ? { text: findText, matches: visible.length } : undefined,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
export function useListSurface(getOptions, registry) {
|
|
203
|
+
const uiContext = tryGetWebMcpUiContext();
|
|
204
|
+
let handle = null;
|
|
205
|
+
let mountedKey = null;
|
|
206
|
+
let mountedRegistry = null;
|
|
207
|
+
let latest = null;
|
|
208
|
+
let controller = null;
|
|
209
|
+
let findText = '';
|
|
210
|
+
const sortingOf = (options) => options.sort
|
|
211
|
+
? [{ columnId: options.sort.columnId, direction: options.sort.direction }]
|
|
212
|
+
: [];
|
|
213
|
+
const teardown = () => {
|
|
214
|
+
handle?.destroy();
|
|
215
|
+
handle = null;
|
|
216
|
+
controller = null;
|
|
217
|
+
mountedKey = null;
|
|
218
|
+
mountedRegistry = null;
|
|
219
|
+
findText = '';
|
|
220
|
+
};
|
|
221
|
+
$effect(() => {
|
|
222
|
+
const options = getOptions();
|
|
223
|
+
const resolved = registry ??
|
|
224
|
+
(uiContext?.enabled ? uiContext.dataSurfaceRegistry : undefined);
|
|
225
|
+
latest = options;
|
|
226
|
+
if (!resolved || !options) {
|
|
227
|
+
teardown();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
// Re-mount on identity change: SvelteKit reuses a page component across
|
|
231
|
+
// a param-only navigation, and a mount keeps its first subject.
|
|
232
|
+
const key = JSON.stringify([
|
|
233
|
+
options.surfaceId,
|
|
234
|
+
options.kind ?? 'list',
|
|
235
|
+
options.subject ?? null,
|
|
236
|
+
options.columns.map((column) => [
|
|
237
|
+
column.id,
|
|
238
|
+
column.searchable === true,
|
|
239
|
+
column.sortable === true,
|
|
240
|
+
]),
|
|
241
|
+
Boolean(options.refresh),
|
|
242
|
+
sortEnabled(options),
|
|
243
|
+
]);
|
|
244
|
+
if (key !== mountedKey || resolved !== mountedRegistry) {
|
|
245
|
+
teardown();
|
|
246
|
+
const context = surfaceContext(options, '');
|
|
247
|
+
delete context.find;
|
|
248
|
+
const sortable = sortEnabled(options);
|
|
249
|
+
const columnIds = options.columns.map((column) => column.id);
|
|
250
|
+
const mountedController = sortable
|
|
251
|
+
? createDataTableController({
|
|
252
|
+
columnIds,
|
|
253
|
+
// Controlled: the page's `sort` is the source of truth; an agent's
|
|
254
|
+
// command is only a proposal until `onSort` applies it.
|
|
255
|
+
state: {
|
|
256
|
+
...createDataTableController({ columnIds }).getState(),
|
|
257
|
+
sorting: sortingOf(options),
|
|
258
|
+
},
|
|
259
|
+
})
|
|
260
|
+
: createDataTableController({ columnIds });
|
|
261
|
+
controller = mountedController;
|
|
262
|
+
handle = mountListDataSurface({
|
|
263
|
+
registry: resolved,
|
|
264
|
+
descriptor: listSurfaceDescriptor(options),
|
|
265
|
+
controller: mountedController,
|
|
266
|
+
context: context,
|
|
267
|
+
acceptsTableCommand: (command) => sortable &&
|
|
268
|
+
command.type === 'setSorting' &&
|
|
269
|
+
command.sorting.length === 1,
|
|
270
|
+
...(sortable
|
|
271
|
+
? {
|
|
272
|
+
applyControlledState: async (next) => {
|
|
273
|
+
const rule = next.sorting[0];
|
|
274
|
+
if (!rule || !latest?.onSort)
|
|
275
|
+
return undefined;
|
|
276
|
+
const result = await latest.onSort({
|
|
277
|
+
columnId: rule.columnId,
|
|
278
|
+
direction: rule.direction,
|
|
279
|
+
});
|
|
280
|
+
return result === false ? undefined : next;
|
|
281
|
+
},
|
|
282
|
+
}
|
|
283
|
+
: {}),
|
|
284
|
+
...(options.refresh ? { refresh: options.refresh } : {}),
|
|
285
|
+
onControl: (controlId, payload) => {
|
|
286
|
+
if (controlId !== 'find' || !latest || !handle)
|
|
287
|
+
return false;
|
|
288
|
+
const text = findTextFromPayload(payload);
|
|
289
|
+
if (text === null)
|
|
290
|
+
return false;
|
|
291
|
+
findText = text.trim();
|
|
292
|
+
handle.update(surfaceContext(latest, findText));
|
|
293
|
+
return true;
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
mountedKey = key;
|
|
297
|
+
mountedRegistry = resolved;
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (controller?.isControlled()) {
|
|
301
|
+
controller.replaceState({
|
|
302
|
+
...controller.getState(),
|
|
303
|
+
sorting: sortingOf(options),
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
handle?.update(surfaceContext(options, findText));
|
|
307
|
+
});
|
|
308
|
+
onDestroy(teardown);
|
|
309
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useStepSurface` — mount a wizard or other multi-step flow on the nearest
|
|
3
|
+
* `<Provider webmcp>` data-surface registry, so an agent can read where the
|
|
4
|
+
* person is and move the flow with `next` / `back` / `go-to`. A step whose
|
|
5
|
+
* forward button saves or creates something (`nextWrites`) is never pressed
|
|
6
|
+
* by an agent: `next` calls `showNext` (reveal + highlight the button) and
|
|
7
|
+
* the person presses it. See `registerStepSurface` in
|
|
8
|
+
* `@happyvertical/smrt-ui/data`.
|
|
9
|
+
*
|
|
10
|
+
* Handlers are read at command time, so pass the page's own functions — the
|
|
11
|
+
* ones its buttons call. A no-op without a WebMCP Provider. Call during
|
|
12
|
+
* component init.
|
|
13
|
+
*/
|
|
14
|
+
import { type DataSurfaceRegistry, type StepSurfaceOptions } from '@happyvertical/smrt-ui/data';
|
|
15
|
+
export type UseStepSurfaceOptions = Omit<StepSurfaceOptions, 'registry'>;
|
|
16
|
+
export declare function useStepSurface(getOptions: () => UseStepSurfaceOptions | null, registry?: DataSurfaceRegistry): void;
|
|
17
|
+
//# sourceMappingURL=step-surface.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-surface.svelte.d.ts","sourceRoot":"","sources":["../../src/web/step-surface.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EACL,KAAK,mBAAmB,EAGxB,KAAK,kBAAkB,EACxB,MAAM,6BAA6B,CAAC;AAIrC,MAAM,MAAM,qBAAqB,GAAG,IAAI,CAAC,kBAAkB,EAAE,UAAU,CAAC,CAAC;AAEzE,wBAAgB,cAAc,CAC5B,UAAU,EAAE,MAAM,qBAAqB,GAAG,IAAI,EAC9C,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,IAAI,CA2CN"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useStepSurface` — mount a wizard or other multi-step flow on the nearest
|
|
3
|
+
* `<Provider webmcp>` data-surface registry, so an agent can read where the
|
|
4
|
+
* person is and move the flow with `next` / `back` / `go-to`. A step whose
|
|
5
|
+
* forward button saves or creates something (`nextWrites`) is never pressed
|
|
6
|
+
* by an agent: `next` calls `showNext` (reveal + highlight the button) and
|
|
7
|
+
* the person presses it. See `registerStepSurface` in
|
|
8
|
+
* `@happyvertical/smrt-ui/data`.
|
|
9
|
+
*
|
|
10
|
+
* Handlers are read at command time, so pass the page's own functions — the
|
|
11
|
+
* ones its buttons call. A no-op without a WebMCP Provider. Call during
|
|
12
|
+
* component init.
|
|
13
|
+
*/
|
|
14
|
+
import { registerStepSurface, } from '@happyvertical/smrt-ui/data';
|
|
15
|
+
import { onDestroy } from 'svelte';
|
|
16
|
+
import { tryGetWebMcpUiContext } from './webmcp-ui-context.js';
|
|
17
|
+
export function useStepSurface(getOptions, registry) {
|
|
18
|
+
const uiContext = tryGetWebMcpUiContext();
|
|
19
|
+
let handle = null;
|
|
20
|
+
let mountedKey = null;
|
|
21
|
+
let mountedRegistry = null;
|
|
22
|
+
const teardown = () => {
|
|
23
|
+
handle?.destroy();
|
|
24
|
+
handle = null;
|
|
25
|
+
mountedKey = null;
|
|
26
|
+
mountedRegistry = null;
|
|
27
|
+
};
|
|
28
|
+
$effect(() => {
|
|
29
|
+
const options = getOptions();
|
|
30
|
+
const resolved = registry ??
|
|
31
|
+
(uiContext?.enabled ? uiContext.dataSurfaceRegistry : undefined);
|
|
32
|
+
if (!resolved || !options) {
|
|
33
|
+
teardown();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const key = JSON.stringify([options.surfaceId, options.subject ?? null]);
|
|
37
|
+
if (key !== mountedKey || resolved !== mountedRegistry) {
|
|
38
|
+
teardown();
|
|
39
|
+
handle = registerStepSurface({ ...options, registry: resolved });
|
|
40
|
+
mountedKey = key;
|
|
41
|
+
mountedRegistry = resolved;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
handle?.update({
|
|
45
|
+
steps: options.steps,
|
|
46
|
+
current: options.current,
|
|
47
|
+
next: options.next,
|
|
48
|
+
nextWrites: options.nextWrites ?? false,
|
|
49
|
+
showNext: options.showNext,
|
|
50
|
+
back: options.back,
|
|
51
|
+
goTo: options.goTo,
|
|
52
|
+
state: options.state,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
onDestroy(teardown);
|
|
56
|
+
}
|
|
@@ -7,6 +7,13 @@ export interface WebMcpUiContext {
|
|
|
7
7
|
}
|
|
8
8
|
export declare function setWebMcpUiContext(context: WebMcpUiContext): void;
|
|
9
9
|
export declare function tryGetWebMcpUiContext(): WebMcpUiContext | null;
|
|
10
|
+
/**
|
|
11
|
+
* The nearest Provider's mounted-UI registries, or `null` when there is no
|
|
12
|
+
* Provider or its WebMCP UI is off (#2915). Use this to fall back to a local
|
|
13
|
+
* registry instead of catching {@link useWebMcpUi}'s throw. Call during
|
|
14
|
+
* component initialization, like any context read.
|
|
15
|
+
*/
|
|
16
|
+
export declare function tryUseWebMcpUi(): WebMcpUiContext | null;
|
|
10
17
|
/** Return the mounted-UI registries owned by the nearest SMRT Provider. */
|
|
11
18
|
export declare function useWebMcpUi(): WebMcpUiContext;
|
|
12
19
|
//# sourceMappingURL=webmcp-ui-context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webmcp-ui-context.d.ts","sourceRoot":"","sources":["../../src/web/webmcp-ui-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAK/E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,eAAe,EAAE,0BAA0B,CAAC;IACrD,QAAQ,CAAC,mBAAmB,EAAE,mBAAmB,CAAC;CACnD;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAEjE;AAED,wBAAgB,qBAAqB,IAAI,eAAe,GAAG,IAAI,CAE9D;AAED,2EAA2E;AAC3E,wBAAgB,WAAW,IAAI,eAAe,CAQ7C"}
|
|
1
|
+
{"version":3,"file":"webmcp-ui-context.d.ts","sourceRoot":"","sources":["../../src/web/webmcp-ui-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAK/E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,eAAe,EAAE,0BAA0B,CAAC;IACrD,QAAQ,CAAC,mBAAmB,EAAE,mBAAmB,CAAC;CACnD;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAEjE;AAED,wBAAgB,qBAAqB,IAAI,eAAe,GAAG,IAAI,CAE9D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,eAAe,GAAG,IAAI,CAGvD;AAED,2EAA2E;AAC3E,wBAAgB,WAAW,IAAI,eAAe,CAQ7C"}
|
|
@@ -6,6 +6,16 @@ export function setWebMcpUiContext(context) {
|
|
|
6
6
|
export function tryGetWebMcpUiContext() {
|
|
7
7
|
return getContext(WEBMCP_UI_CONTEXT_KEY) ?? null;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* The nearest Provider's mounted-UI registries, or `null` when there is no
|
|
11
|
+
* Provider or its WebMCP UI is off (#2915). Use this to fall back to a local
|
|
12
|
+
* registry instead of catching {@link useWebMcpUi}'s throw. Call during
|
|
13
|
+
* component initialization, like any context read.
|
|
14
|
+
*/
|
|
15
|
+
export function tryUseWebMcpUi() {
|
|
16
|
+
const context = tryGetWebMcpUiContext();
|
|
17
|
+
return context?.enabled ? context : null;
|
|
18
|
+
}
|
|
9
19
|
/** Return the mounted-UI registries owned by the nearest SMRT Provider. */
|
|
10
20
|
export function useWebMcpUi() {
|
|
11
21
|
const context = tryGetWebMcpUiContext();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webmcp-ui.d.ts","sourceRoot":"","sources":["../../src/web/webmcp-ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,mBAAmB,EAGzB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAKV,0BAA0B,EAE3B,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"webmcp-ui.d.ts","sourceRoot":"","sources":["../../src/web/webmcp-ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,mBAAmB,EAGzB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAKV,0BAA0B,EAE3B,MAAM,8BAA8B,CAAC;AAOtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AA0DzD,MAAM,WAAW,4BAA4B;IAC3C,eAAe,EAAE,0BAA0B,CAAC;IAC5C,mBAAmB,EAAE,mBAAmB,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,QAAQ,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,sBAAsB,CAAA;KAAE,CAAC;CACtD;AAED,UAAU,sBAAsB;IAC9B,YAAY,CACV,IAAI,EAAE,cAAc,EACpB,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GACjC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AA6fD,4EAA4E;AAC5E,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,4BAA4B,GACpC,MAAM,IAAI,CA6FZ"}
|
package/dist/web/webmcp-ui.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DATA_SURFACE_IDENTIFIER_MAX_LENGTH, DATA_SURFACE_MAX_REQUEST_BYTES, } from '@happyvertical/smrt-ui/data';
|
|
2
|
-
import {
|
|
2
|
+
import { WEBMCP_TOOL_EFFECT } from '@happyvertical/smrt-web/webmcp-page-tools';
|
|
3
|
+
import { markWebMcpProposalTool, reserveWebMcpToolNames, } from '@happyvertical/smrt-web/webmcp-tool-names';
|
|
3
4
|
const CONTROL_ACTIONS = new Set([
|
|
4
5
|
'focus',
|
|
5
6
|
'reveal',
|
|
@@ -315,7 +316,7 @@ function tools(prefix, controlRegistry, dataSurfaceRegistry) {
|
|
|
315
316
|
})),
|
|
316
317
|
{
|
|
317
318
|
name: `${prefix}execute_form_control`,
|
|
318
|
-
description: 'Execute an allowed command on a mounted SMRT form control. Agent mutations are consent-gated.',
|
|
319
|
+
description: 'Execute an allowed command on a mounted SMRT form control. Agent mutations are consent-gated. An agent never moves keyboard focus: `focus` scrolls the control into view and highlights it.',
|
|
319
320
|
inputSchema: {
|
|
320
321
|
type: 'object',
|
|
321
322
|
required: ['action', 'identity'],
|
|
@@ -490,7 +491,19 @@ export function registerWebMcpUiTools(options) {
|
|
|
490
491
|
};
|
|
491
492
|
try {
|
|
492
493
|
for (const tool of specs) {
|
|
493
|
-
|
|
494
|
+
// The in-page tool registry (#2908) reads the resolved effect from this
|
|
495
|
+
// stamp. The two execute tools are `write`, not destructive: an agent
|
|
496
|
+
// can only stage values or change visible state; apply, clear, and undo
|
|
497
|
+
// need a separate human-confirmed path, so their mutations are
|
|
498
|
+
// proposals. Their browser annotations are unchanged. The proposal
|
|
499
|
+
// brand is what lets the in-page assistant run those writes without a
|
|
500
|
+
// confirmation step; the tool-name lock's `ui` label never does.
|
|
501
|
+
const stamped = Object.assign({
|
|
502
|
+
[WEBMCP_TOOL_EFFECT]: tool.annotations?.readOnlyHint
|
|
503
|
+
? 'read'
|
|
504
|
+
: 'write',
|
|
505
|
+
}, tool, { execute: markWebMcpProposalTool(tool.execute) });
|
|
506
|
+
const registration = modelContext.registerTool(stamped, {
|
|
494
507
|
signal: controller.signal,
|
|
495
508
|
});
|
|
496
509
|
if (registration) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.0",
|
|
4
4
|
"smrtJsdoc": "strict",
|
|
5
5
|
"description": "Svelte 5 components for SMRT user management - auth, users, tenants, roles, permissions, groups",
|
|
6
6
|
"type": "module",
|
|
@@ -126,12 +126,12 @@
|
|
|
126
126
|
"access": "public"
|
|
127
127
|
},
|
|
128
128
|
"dependencies": {
|
|
129
|
-
"@happyvertical/logger": "^0.
|
|
130
|
-
"@happyvertical/smrt-languages": "0.
|
|
131
|
-
"@happyvertical/smrt-mcp-apps": "0.
|
|
132
|
-
"@happyvertical/smrt-types": "0.
|
|
133
|
-
"@happyvertical/smrt-ui": "0.
|
|
134
|
-
"@happyvertical/smrt-web": "0.
|
|
129
|
+
"@happyvertical/logger": "^0.98.0",
|
|
130
|
+
"@happyvertical/smrt-languages": "0.53.0",
|
|
131
|
+
"@happyvertical/smrt-mcp-apps": "0.53.0",
|
|
132
|
+
"@happyvertical/smrt-types": "0.53.0",
|
|
133
|
+
"@happyvertical/smrt-ui": "0.53.0",
|
|
134
|
+
"@happyvertical/smrt-web": "0.53.0",
|
|
135
135
|
"@tanstack/db": "^0.6.14",
|
|
136
136
|
"@tanstack/svelte-db": "^0.1.91",
|
|
137
137
|
"esm-env": "^1.2.2"
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
"@xenova/transformers": "^2.17.2",
|
|
144
144
|
"bitgpu": "^0.19.1",
|
|
145
145
|
"chrono-node": "^2.10.1",
|
|
146
|
-
"svelte": "^5.57.
|
|
146
|
+
"svelte": "^5.57.0"
|
|
147
147
|
},
|
|
148
148
|
"peerDependenciesMeta": {
|
|
149
149
|
"@huggingface/transformers": {
|
|
@@ -166,14 +166,14 @@
|
|
|
166
166
|
}
|
|
167
167
|
},
|
|
168
168
|
"devDependencies": {
|
|
169
|
-
"@happyvertical/smrt-agents": "0.
|
|
170
|
-
"@happyvertical/smrt-chat": "0.
|
|
171
|
-
"@happyvertical/smrt-content": "0.
|
|
172
|
-
"@happyvertical/smrt-core": "0.
|
|
173
|
-
"@happyvertical/smrt-reports": "0.
|
|
174
|
-
"@happyvertical/smrt-scanner": "0.
|
|
175
|
-
"@happyvertical/smrt-tenancy": "0.
|
|
176
|
-
"@happyvertical/smrt-users": "0.
|
|
169
|
+
"@happyvertical/smrt-agents": "0.53.0",
|
|
170
|
+
"@happyvertical/smrt-chat": "0.53.0",
|
|
171
|
+
"@happyvertical/smrt-content": "0.53.0",
|
|
172
|
+
"@happyvertical/smrt-core": "0.53.0",
|
|
173
|
+
"@happyvertical/smrt-reports": "0.53.0",
|
|
174
|
+
"@happyvertical/smrt-scanner": "0.53.0",
|
|
175
|
+
"@happyvertical/smrt-tenancy": "0.53.0",
|
|
176
|
+
"@happyvertical/smrt-users": "0.53.0",
|
|
177
177
|
"@sveltejs/package": "^2.5.8",
|
|
178
178
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
179
179
|
"@testing-library/jest-dom": "^7.0.1",
|
|
@@ -192,7 +192,7 @@
|
|
|
192
192
|
"dev": "svelte-package -i src --watch",
|
|
193
193
|
"check": "pnpm exec svelte-check --tsconfig ./tsconfig.svelte.json",
|
|
194
194
|
"test": "pnpm run test:unit && pnpm run test:conformance",
|
|
195
|
-
"test:unit": "vitest run --exclude src/web/__tests__/data-surface-conformance.integration.svelte.test.ts && node --test scripts
|
|
195
|
+
"test:unit": "vitest run --exclude src/web/__tests__/data-surface-conformance.integration.svelte.test.ts && node --test scripts/*.test.mjs",
|
|
196
196
|
"test:conformance": "node scripts/ensure-conformance-build.mjs && SMRT_SVELTE_PUBLISHED_ENTRY=1 vitest run src/web/__tests__/data-surface-conformance.integration.svelte.test.ts",
|
|
197
197
|
"typecheck:conformance": "tsc --noEmit -p ./tsconfig.conformance.json",
|
|
198
198
|
"typecheck": "tsc --noEmit && pnpm run typecheck:conformance && node ../../scripts/svelte-check-a11y.mjs --tsconfig ./tsconfig.svelte.json",
|