@qontinui/ui-bridge 0.2.0 → 0.3.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.
Files changed (108) hide show
  1. package/dist/ai/index.d.mts +4 -4
  2. package/dist/ai/index.d.ts +4 -4
  3. package/dist/babel-plugin/index.js +515 -0
  4. package/dist/babel-plugin/index.js.map +1 -0
  5. package/dist/babel-plugin/index.mjs +499 -0
  6. package/dist/babel-plugin/index.mjs.map +1 -0
  7. package/dist/control/index.d.mts +5 -5
  8. package/dist/control/index.d.ts +5 -5
  9. package/dist/core/index.d.mts +115 -44
  10. package/dist/core/index.d.ts +115 -44
  11. package/dist/core/index.js +0 -1560
  12. package/dist/core/index.js.map +1 -1
  13. package/dist/core/index.mjs +1 -1549
  14. package/dist/core/index.mjs.map +1 -1
  15. package/dist/debug/index.d.mts +3 -3
  16. package/dist/debug/index.d.ts +3 -3
  17. package/dist/index.d.mts +7 -8
  18. package/dist/index.d.ts +7 -8
  19. package/dist/index.js +859 -873
  20. package/dist/index.js.map +1 -1
  21. package/dist/index.mjs +860 -862
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/{metrics-C9XRi_mL.d.ts → metrics-BfiT_rhZ.d.ts} +2 -2
  24. package/dist/{metrics-NC3csD0R.d.mts → metrics-DTA2bwG7.d.mts} +2 -2
  25. package/dist/native/control/index.js +453 -0
  26. package/dist/native/control/index.js.map +1 -0
  27. package/dist/native/control/index.mjs +450 -0
  28. package/dist/native/control/index.mjs.map +1 -0
  29. package/dist/native/core/index.js +486 -0
  30. package/dist/native/core/index.js.map +1 -0
  31. package/dist/native/core/index.mjs +475 -0
  32. package/dist/native/core/index.mjs.map +1 -0
  33. package/dist/native/debug/index.js +451 -0
  34. package/dist/native/debug/index.js.map +1 -0
  35. package/dist/native/debug/index.mjs +449 -0
  36. package/dist/native/debug/index.mjs.map +1 -0
  37. package/dist/native/index.js +2274 -0
  38. package/dist/native/index.js.map +1 -0
  39. package/dist/native/index.mjs +2246 -0
  40. package/dist/native/index.mjs.map +1 -0
  41. package/dist/native/react/index.js +1401 -0
  42. package/dist/native/react/index.js.map +1 -0
  43. package/dist/native/react/index.mjs +1389 -0
  44. package/dist/native/react/index.mjs.map +1 -0
  45. package/dist/native/server/index.js +415 -0
  46. package/dist/native/server/index.js.map +1 -0
  47. package/dist/native/server/index.mjs +410 -0
  48. package/dist/native/server/index.mjs.map +1 -0
  49. package/dist/react/index.d.mts +20 -7
  50. package/dist/react/index.d.ts +20 -7
  51. package/dist/react/index.js +42 -4
  52. package/dist/react/index.js.map +1 -1
  53. package/dist/react/index.mjs +42 -4
  54. package/dist/react/index.mjs.map +1 -1
  55. package/dist/{registry-CIEDjbQ9.d.ts → registry-BKLEm-yk.d.ts} +9 -15
  56. package/dist/{registry-SsSDq46X.d.mts → registry-BmZgyCz8.d.mts} +9 -15
  57. package/dist/render-log/index.d.mts +1 -1
  58. package/dist/render-log/index.d.ts +1 -1
  59. package/dist/server/express.d.mts +36 -0
  60. package/dist/server/express.d.ts +36 -0
  61. package/dist/server/express.js +196 -0
  62. package/dist/server/express.js.map +1 -0
  63. package/dist/server/express.mjs +192 -0
  64. package/dist/server/express.mjs.map +1 -0
  65. package/dist/server/handlers.d.mts +93 -0
  66. package/dist/server/handlers.d.ts +93 -0
  67. package/dist/server/handlers.js +4278 -0
  68. package/dist/server/handlers.js.map +1 -0
  69. package/dist/server/handlers.mjs +4275 -0
  70. package/dist/server/handlers.mjs.map +1 -0
  71. package/dist/server/index.d.mts +10 -0
  72. package/dist/server/index.d.ts +10 -0
  73. package/dist/server/index.js +5352 -0
  74. package/dist/server/index.js.map +1 -0
  75. package/dist/server/index.mjs +5337 -0
  76. package/dist/server/index.mjs.map +1 -0
  77. package/dist/server/nextjs.d.mts +126 -0
  78. package/dist/server/nextjs.d.ts +126 -0
  79. package/dist/server/nextjs.js +287 -0
  80. package/dist/server/nextjs.js.map +1 -0
  81. package/dist/server/nextjs.mjs +282 -0
  82. package/dist/server/nextjs.mjs.map +1 -0
  83. package/dist/server/standalone.d.mts +6 -0
  84. package/dist/server/standalone.d.ts +6 -0
  85. package/dist/server/standalone.js +719 -0
  86. package/dist/server/standalone.js.map +1 -0
  87. package/dist/server/standalone.mjs +715 -0
  88. package/dist/server/standalone.mjs.map +1 -0
  89. package/dist/standalone-BURj8J3G.d.ts +212 -0
  90. package/dist/standalone-Dwmel29d.d.mts +212 -0
  91. package/dist/swc-plugin/index.d.mts +79 -0
  92. package/dist/swc-plugin/index.d.ts +79 -0
  93. package/dist/swc-plugin/index.js +15 -0
  94. package/dist/swc-plugin/index.js.map +1 -0
  95. package/dist/swc-plugin/index.mjs +9 -0
  96. package/dist/swc-plugin/index.mjs.map +1 -0
  97. package/dist/{types-CFT3Dnx4.d.mts → types-B5Q0GVo0.d.mts} +115 -3
  98. package/dist/{types-Dr6tH-bm.d.mts → types-B7J7noLK.d.mts} +1 -1
  99. package/dist/{types-oCTrRxSw.d.ts → types-BkNRILUa.d.ts} +1 -1
  100. package/dist/types-CEQLnFMv.d.mts +156 -0
  101. package/dist/types-CHnlwiTK.d.ts +156 -0
  102. package/dist/{types-BvCfFuEV.d.ts → types-DfPqwU-i.d.ts} +115 -3
  103. package/dist/{types-CPMbN_Iw.d.mts → types-jKVgTI6_.d.mts} +356 -160
  104. package/dist/{types-CPMbN_Iw.d.ts → types-jKVgTI6_.d.ts} +356 -160
  105. package/package.json +106 -3
  106. package/swc-plugin-wasm/ui_bridge_swc_plugin.wasm +0 -0
  107. package/dist/websocket-client-CX4QJesI.d.ts +0 -124
  108. package/dist/websocket-client-C_Na0OSp.d.mts +0 -124
package/dist/index.js CHANGED
@@ -3,8 +3,442 @@
3
3
  var react = require('react');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
5
 
6
+ // src/core/websocket-client.ts
7
+ function generateId() {
8
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
9
+ }
10
+ var UIBridgeWSClient = class {
11
+ constructor(config) {
12
+ this.ws = null;
13
+ this.state = "disconnected";
14
+ this.clientId = null;
15
+ this.reconnectAttempts = 0;
16
+ this.reconnectTimer = null;
17
+ this.pingTimer = null;
18
+ this.pendingRequests = /* @__PURE__ */ new Map();
19
+ // Event listeners
20
+ this.connectionListeners = /* @__PURE__ */ new Set();
21
+ this.eventListeners = /* @__PURE__ */ new Map();
22
+ this.errorListeners = /* @__PURE__ */ new Set();
23
+ // Current subscriptions
24
+ this.subscriptions = {};
25
+ this.config = {
26
+ url: config.url,
27
+ autoReconnect: config.autoReconnect ?? true,
28
+ reconnectDelay: config.reconnectDelay ?? 1e3,
29
+ maxReconnectAttempts: config.maxReconnectAttempts ?? 10,
30
+ pingInterval: config.pingInterval ?? 3e4,
31
+ connectionTimeout: config.connectionTimeout ?? 1e4
32
+ };
33
+ }
34
+ /**
35
+ * Get current connection state
36
+ */
37
+ get connectionState() {
38
+ return this.state;
39
+ }
40
+ /**
41
+ * Get assigned client ID
42
+ */
43
+ get id() {
44
+ return this.clientId;
45
+ }
46
+ /**
47
+ * Connect to the WebSocket server
48
+ */
49
+ connect() {
50
+ return new Promise((resolve, reject) => {
51
+ if (this.ws && this.state === "connected") {
52
+ resolve();
53
+ return;
54
+ }
55
+ this.setState("connecting");
56
+ try {
57
+ this.ws = new WebSocket(this.config.url);
58
+ } catch (error) {
59
+ this.setState("disconnected");
60
+ reject(error);
61
+ return;
62
+ }
63
+ const connectionTimeout = setTimeout(() => {
64
+ if (this.state === "connecting") {
65
+ this.ws?.close();
66
+ this.setState("disconnected");
67
+ reject(new Error("Connection timeout"));
68
+ }
69
+ }, this.config.connectionTimeout);
70
+ this.ws.onopen = () => {
71
+ clearTimeout(connectionTimeout);
72
+ };
73
+ this.ws.onmessage = (event) => {
74
+ try {
75
+ const message = JSON.parse(event.data);
76
+ this.handleMessage(message);
77
+ if (message.type === "welcome") {
78
+ clearTimeout(connectionTimeout);
79
+ this.reconnectAttempts = 0;
80
+ this.setState("connected");
81
+ this.startPingInterval();
82
+ if (this.subscriptions.events?.length || this.subscriptions.elementIds?.length || this.subscriptions.componentIds?.length) {
83
+ this.subscribe(this.subscriptions);
84
+ }
85
+ resolve();
86
+ }
87
+ } catch (error) {
88
+ console.error("Failed to parse WebSocket message:", error);
89
+ }
90
+ };
91
+ this.ws.onerror = (_event) => {
92
+ clearTimeout(connectionTimeout);
93
+ const error = new Error("WebSocket error");
94
+ this.notifyError(error);
95
+ if (this.state === "connecting") {
96
+ reject(error);
97
+ }
98
+ };
99
+ this.ws.onclose = () => {
100
+ clearTimeout(connectionTimeout);
101
+ this.stopPingInterval();
102
+ this.clientId = null;
103
+ const wasConnected = this.state === "connected";
104
+ this.setState("disconnected");
105
+ for (const [_id, pending] of this.pendingRequests) {
106
+ clearTimeout(pending.timeout);
107
+ pending.reject(new Error("Connection closed"));
108
+ }
109
+ this.pendingRequests.clear();
110
+ if (wasConnected && this.config.autoReconnect && (this.config.maxReconnectAttempts === 0 || this.reconnectAttempts < this.config.maxReconnectAttempts)) {
111
+ this.scheduleReconnect();
112
+ }
113
+ };
114
+ });
115
+ }
116
+ /**
117
+ * Disconnect from the server
118
+ */
119
+ disconnect() {
120
+ if (this.reconnectTimer) {
121
+ clearTimeout(this.reconnectTimer);
122
+ this.reconnectTimer = null;
123
+ }
124
+ this.stopPingInterval();
125
+ if (this.ws) {
126
+ this.ws.close();
127
+ this.ws = null;
128
+ }
129
+ this.setState("disconnected");
130
+ }
131
+ /**
132
+ * Subscribe to events
133
+ */
134
+ async subscribe(options) {
135
+ this.subscriptions = { ...this.subscriptions, ...options };
136
+ const response = await this.sendRequest({
137
+ id: generateId(),
138
+ type: "subscribe",
139
+ timestamp: Date.now(),
140
+ payload: options
141
+ });
142
+ return response.events;
143
+ }
144
+ /**
145
+ * Unsubscribe from events
146
+ */
147
+ async unsubscribe(events) {
148
+ if (events) {
149
+ this.subscriptions.events = this.subscriptions.events?.filter((e) => !events.includes(e));
150
+ } else {
151
+ this.subscriptions = {};
152
+ }
153
+ const response = await this.sendRequest({
154
+ id: generateId(),
155
+ type: "unsubscribe",
156
+ timestamp: Date.now(),
157
+ payload: { events }
158
+ });
159
+ return response.events;
160
+ }
161
+ /**
162
+ * Find elements
163
+ */
164
+ async find(options) {
165
+ const response = await this.sendRequest({
166
+ id: generateId(),
167
+ type: "find",
168
+ timestamp: Date.now(),
169
+ payload: options
170
+ });
171
+ return response.elements;
172
+ }
173
+ /**
174
+ * Discover elements
175
+ * @deprecated Use find() instead
176
+ */
177
+ async discover(options) {
178
+ return this.find(options);
179
+ }
180
+ /**
181
+ * Get element details
182
+ */
183
+ async getElement(elementId, includeState = true) {
184
+ const response = await this.sendRequest({
185
+ id: generateId(),
186
+ type: "getElement",
187
+ timestamp: Date.now(),
188
+ payload: { elementId, includeState }
189
+ });
190
+ return response.element;
191
+ }
192
+ /**
193
+ * Get full snapshot
194
+ */
195
+ async getSnapshot() {
196
+ const response = await this.sendRequest({
197
+ id: generateId(),
198
+ type: "getSnapshot",
199
+ timestamp: Date.now()
200
+ });
201
+ return response;
202
+ }
203
+ /**
204
+ * Execute action on an element
205
+ */
206
+ async executeAction(elementId, action) {
207
+ const response = await this.sendRequest({
208
+ id: generateId(),
209
+ type: "executeAction",
210
+ timestamp: Date.now(),
211
+ payload: { elementId, action }
212
+ });
213
+ return response;
214
+ }
215
+ /**
216
+ * Execute component action
217
+ */
218
+ async executeComponentAction(componentId, action, params) {
219
+ const response = await this.sendRequest({
220
+ id: generateId(),
221
+ type: "executeComponentAction",
222
+ timestamp: Date.now(),
223
+ payload: { componentId, action, params }
224
+ });
225
+ return response;
226
+ }
227
+ /**
228
+ * Execute workflow with optional progress streaming
229
+ */
230
+ async executeWorkflow(workflowId, params, onProgress) {
231
+ const id = generateId();
232
+ const progressHandler = onProgress ? (message) => {
233
+ if (message.type === "workflowProgress" && message.requestId === id) {
234
+ onProgress({
235
+ currentStep: message.payload.currentStep,
236
+ totalSteps: message.payload.totalSteps,
237
+ step: {
238
+ id: message.payload.step.id,
239
+ status: message.payload.step.status
240
+ }
241
+ });
242
+ }
243
+ } : void 0;
244
+ const response = await this.sendRequest(
245
+ {
246
+ id,
247
+ type: "executeWorkflow",
248
+ timestamp: Date.now(),
249
+ payload: { workflowId, params, streamProgress: !!onProgress }
250
+ },
251
+ progressHandler
252
+ );
253
+ return response;
254
+ }
255
+ /**
256
+ * Add connection state listener
257
+ */
258
+ onConnectionChange(listener) {
259
+ this.connectionListeners.add(listener);
260
+ return () => this.connectionListeners.delete(listener);
261
+ }
262
+ /**
263
+ * Add event listener
264
+ */
265
+ onEvent(eventType, listener) {
266
+ if (!this.eventListeners.has(eventType)) {
267
+ this.eventListeners.set(eventType, /* @__PURE__ */ new Set());
268
+ }
269
+ this.eventListeners.get(eventType).add(listener);
270
+ return () => this.eventListeners.get(eventType)?.delete(listener);
271
+ }
272
+ /**
273
+ * Add error listener
274
+ */
275
+ onError(listener) {
276
+ this.errorListeners.add(listener);
277
+ return () => this.errorListeners.delete(listener);
278
+ }
279
+ // Private methods
280
+ setState(state) {
281
+ this.state = state;
282
+ for (const listener of this.connectionListeners) {
283
+ try {
284
+ listener(state);
285
+ } catch (error) {
286
+ console.error("Connection listener error:", error);
287
+ }
288
+ }
289
+ }
290
+ handleMessage(message) {
291
+ switch (message.type) {
292
+ case "welcome":
293
+ this.clientId = message.payload.clientId;
294
+ break;
295
+ case "pong":
296
+ break;
297
+ case "subscribed":
298
+ case "unsubscribed":
299
+ break;
300
+ case "event":
301
+ this.notifyEvent(message.payload);
302
+ break;
303
+ case "response":
304
+ this.handleResponse(message);
305
+ break;
306
+ case "error":
307
+ if (message.requestId) {
308
+ this.handleResponse({
309
+ ...message,
310
+ type: "response",
311
+ requestId: message.requestId,
312
+ payload: {
313
+ success: false,
314
+ error: message.payload.message
315
+ }
316
+ });
317
+ } else {
318
+ this.notifyError(new Error(message.payload.message));
319
+ }
320
+ break;
321
+ }
322
+ }
323
+ handleResponse(message) {
324
+ const pending = this.pendingRequests.get(message.requestId);
325
+ if (!pending) return;
326
+ clearTimeout(pending.timeout);
327
+ this.pendingRequests.delete(message.requestId);
328
+ if (message.type === "response") {
329
+ if (message.payload.success) {
330
+ pending.resolve(message.payload.data);
331
+ } else {
332
+ pending.reject(new Error(message.payload.error || "Request failed"));
333
+ }
334
+ }
335
+ }
336
+ notifyEvent(event) {
337
+ const typeListeners = this.eventListeners.get(event.type);
338
+ if (typeListeners) {
339
+ for (const listener of typeListeners) {
340
+ try {
341
+ listener(event);
342
+ } catch (error) {
343
+ console.error("Event listener error:", error);
344
+ }
345
+ }
346
+ }
347
+ const wildcardListeners = this.eventListeners.get("*");
348
+ if (wildcardListeners) {
349
+ for (const listener of wildcardListeners) {
350
+ try {
351
+ listener(event);
352
+ } catch (error) {
353
+ console.error("Event listener error:", error);
354
+ }
355
+ }
356
+ }
357
+ }
358
+ notifyError(error) {
359
+ for (const listener of this.errorListeners) {
360
+ try {
361
+ listener(error);
362
+ } catch (e) {
363
+ console.error("Error listener error:", e);
364
+ }
365
+ }
366
+ }
367
+ sendRequest(message, progressHandler) {
368
+ return new Promise((resolve, reject) => {
369
+ if (!this.ws || this.state !== "connected") {
370
+ reject(new Error("Not connected"));
371
+ return;
372
+ }
373
+ const timeout = setTimeout(() => {
374
+ this.pendingRequests.delete(message.id);
375
+ reject(new Error("Request timeout"));
376
+ }, 3e4);
377
+ this.pendingRequests.set(message.id, {
378
+ resolve,
379
+ reject,
380
+ timeout
381
+ });
382
+ if (progressHandler && this.ws) {
383
+ const originalHandler = this.ws.onmessage;
384
+ const wsRef = this.ws;
385
+ const wrappedHandler = (event) => {
386
+ try {
387
+ const msg = JSON.parse(event.data);
388
+ if (msg.type === "workflowProgress") {
389
+ progressHandler(msg);
390
+ }
391
+ } catch {
392
+ }
393
+ if (originalHandler) {
394
+ originalHandler.call(wsRef, event);
395
+ }
396
+ };
397
+ this.ws.onmessage = wrappedHandler;
398
+ }
399
+ this.ws.send(JSON.stringify(message));
400
+ });
401
+ }
402
+ scheduleReconnect() {
403
+ if (this.reconnectTimer) return;
404
+ this.setState("reconnecting");
405
+ this.reconnectAttempts++;
406
+ const delay = Math.min(
407
+ this.config.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1),
408
+ 3e4
409
+ );
410
+ this.reconnectTimer = setTimeout(() => {
411
+ this.reconnectTimer = null;
412
+ this.connect().catch(() => {
413
+ });
414
+ }, delay);
415
+ }
416
+ startPingInterval() {
417
+ if (this.config.pingInterval <= 0) return;
418
+ this.pingTimer = setInterval(() => {
419
+ if (this.ws && this.state === "connected") {
420
+ this.ws.send(
421
+ JSON.stringify({
422
+ id: generateId(),
423
+ type: "ping",
424
+ timestamp: Date.now()
425
+ })
426
+ );
427
+ }
428
+ }, this.config.pingInterval);
429
+ }
430
+ stopPingInterval() {
431
+ if (this.pingTimer) {
432
+ clearInterval(this.pingTimer);
433
+ this.pingTimer = null;
434
+ }
435
+ }
436
+ };
437
+ function createWSClient(config) {
438
+ return new UIBridgeWSClient(config);
439
+ }
440
+
6
441
  // src/core/element-identifier.ts
7
- var ID_ATTRIBUTES = ["data-ui-id", "data-testid", "data-awas-element", "id"];
8
442
  function generateXPath(element) {
9
443
  if (element.id) {
10
444
  return `//*[@id="${element.id}"]`;
@@ -188,39 +622,6 @@ function findElementByIdentifier(identifier, root = document) {
188
622
  }
189
623
  return null;
190
624
  }
191
- function findAllElementsByIdentifier(pattern, root = document) {
192
- const results = [];
193
- try {
194
- const elements = root.querySelectorAll(pattern);
195
- results.push(...Array.from(elements));
196
- if (results.length > 0) return results;
197
- } catch {
198
- }
199
- const partials = [
200
- `[data-ui-id*="${pattern}"]`,
201
- `[data-testid*="${pattern}"]`,
202
- `[data-awas-element*="${pattern}"]`,
203
- `[id*="${pattern}"]`
204
- ];
205
- for (const selector of partials) {
206
- try {
207
- const elements = root.querySelectorAll(selector);
208
- for (const el of elements) {
209
- if (!results.includes(el)) {
210
- results.push(el);
211
- }
212
- }
213
- } catch {
214
- }
215
- }
216
- return results;
217
- }
218
- function elementMatchesIdentifier(element, identifier) {
219
- if (typeof identifier === "string") {
220
- return element.getAttribute("data-ui-id") === identifier || element.getAttribute("data-testid") === identifier || element.getAttribute("data-awas-element") === identifier || element.id === identifier || element.matches(identifier);
221
- }
222
- return identifier.uiId && element.getAttribute("data-ui-id") === identifier.uiId || identifier.testId && element.getAttribute("data-testid") === identifier.testId || identifier.awasId && element.getAttribute("data-awas-element") === identifier.awasId || identifier.htmlId && element.id === identifier.htmlId || false;
223
- }
224
625
 
225
626
  // src/ai/fuzzy-matcher.ts
226
627
  var DEFAULT_FUZZY_CONFIG = {
@@ -1260,937 +1661,512 @@ var UIBridgeRegistry = class {
1260
1661
  }
1261
1662
  /**
1262
1663
  * Register a component
1263
- */
1264
- registerComponent(id, options) {
1265
- const registered = {
1266
- id,
1267
- name: options.name,
1268
- description: options.description,
1269
- actions: options.actions?.map((a) => ({
1270
- id: a.id,
1271
- label: a.label,
1272
- description: a.description,
1273
- handler: a.handler
1274
- })) ?? [],
1275
- elementIds: options.elementIds,
1276
- registeredAt: Date.now(),
1277
- mounted: true
1278
- };
1279
- this.components.set(id, registered);
1280
- this.emit("component:registered", { id, name: options.name });
1281
- return registered;
1282
- }
1283
- /**
1284
- * Unregister a component
1285
- */
1286
- unregisterComponent(id) {
1287
- const component = this.components.get(id);
1288
- if (component) {
1289
- component.mounted = false;
1290
- this.components.delete(id);
1291
- this.emit("component:unregistered", { id });
1292
- return true;
1293
- }
1294
- return false;
1295
- }
1296
- /**
1297
- * Get a registered component
1298
- */
1299
- getComponent(id) {
1300
- return this.components.get(id);
1301
- }
1302
- /**
1303
- * Get all registered components
1304
- */
1305
- getAllComponents() {
1306
- return Array.from(this.components.values());
1307
- }
1308
- /**
1309
- * Register a workflow
1310
- */
1311
- registerWorkflow(workflow) {
1312
- this.workflows.set(workflow.id, workflow);
1313
- return workflow;
1314
- }
1315
- /**
1316
- * Unregister a workflow
1317
- */
1318
- unregisterWorkflow(id) {
1319
- return this.workflows.delete(id);
1320
- }
1321
- /**
1322
- * Get a workflow
1323
- */
1324
- getWorkflow(id) {
1325
- return this.workflows.get(id);
1326
- }
1327
- /**
1328
- * Get all workflows
1329
- */
1330
- getAllWorkflows() {
1331
- return Array.from(this.workflows.values());
1332
- }
1333
- // ==========================================================================
1334
- // State Management
1335
- // ==========================================================================
1336
- /**
1337
- * Register a state
1338
- */
1339
- registerState(state) {
1340
- this.states.set(state.id, state);
1341
- this.emit("element:registered", { id: state.id, type: "state", name: state.name });
1342
- return state;
1343
- }
1344
- /**
1345
- * Unregister a state
1346
- */
1347
- unregisterState(id) {
1348
- const state = this.states.get(id);
1349
- if (state) {
1350
- this.activeStates.delete(id);
1351
- this.states.delete(id);
1352
- this.emit("element:unregistered", { id, type: "state" });
1353
- return true;
1354
- }
1355
- return false;
1356
- }
1357
- /**
1358
- * Get a registered state
1359
- */
1360
- getState(id) {
1361
- return this.states.get(id);
1362
- }
1363
- /**
1364
- * Get all registered states
1365
- */
1366
- getAllStates() {
1367
- return Array.from(this.states.values());
1368
- }
1369
- /**
1370
- * Register a state group
1371
- */
1372
- registerStateGroup(group) {
1373
- this.stateGroups.set(group.id, group);
1374
- return group;
1375
- }
1376
- /**
1377
- * Unregister a state group
1378
- */
1379
- unregisterStateGroup(id) {
1380
- return this.stateGroups.delete(id);
1381
- }
1382
- /**
1383
- * Get a state group
1384
- */
1385
- getStateGroup(id) {
1386
- return this.stateGroups.get(id);
1387
- }
1388
- /**
1389
- * Get all state groups
1390
- */
1391
- getAllStateGroups() {
1392
- return Array.from(this.stateGroups.values());
1393
- }
1394
- /**
1395
- * Register a transition
1396
- */
1397
- registerTransition(transition) {
1398
- this.transitions.set(transition.id, transition);
1399
- return transition;
1400
- }
1401
- /**
1402
- * Unregister a transition
1403
- */
1404
- unregisterTransition(id) {
1405
- return this.transitions.delete(id);
1406
- }
1407
- /**
1408
- * Get a transition
1409
- */
1410
- getTransition(id) {
1411
- return this.transitions.get(id);
1412
- }
1413
- /**
1414
- * Get all transitions
1415
- */
1416
- getAllTransitions() {
1417
- return Array.from(this.transitions.values());
1418
- }
1419
- /**
1420
- * Get currently active states
1421
- */
1422
- getActiveStates() {
1423
- return Array.from(this.activeStates);
1424
- }
1425
- /**
1426
- * Check if a state is active
1427
- */
1428
- isStateActive(id) {
1429
- return this.activeStates.has(id);
1430
- }
1431
- /**
1432
- * Activate a state
1433
- */
1434
- activateState(id) {
1435
- const state = this.states.get(id);
1436
- if (!state) {
1437
- return false;
1438
- }
1439
- for (const activeId of this.activeStates) {
1440
- const activeState = this.states.get(activeId);
1441
- if (activeState?.blocking && activeState.id !== id) {
1442
- return false;
1443
- }
1444
- if (activeState?.blocks?.includes(id)) {
1445
- return false;
1446
- }
1447
- }
1448
- const wasActive = this.activeStates.has(id);
1449
- this.activeStates.add(id);
1450
- if (!wasActive) {
1451
- this.emit("element:stateChanged", {
1452
- stateId: id,
1453
- active: true,
1454
- activeStates: this.getActiveStates()
1455
- });
1456
- }
1457
- return true;
1664
+ */
1665
+ registerComponent(id, options) {
1666
+ const registered = {
1667
+ id,
1668
+ name: options.name,
1669
+ description: options.description,
1670
+ actions: options.actions?.map((a) => ({
1671
+ id: a.id,
1672
+ label: a.label,
1673
+ description: a.description,
1674
+ handler: a.handler
1675
+ })) ?? [],
1676
+ elementIds: options.elementIds,
1677
+ registeredAt: Date.now(),
1678
+ mounted: true,
1679
+ getState: options.getState,
1680
+ getComputed: options.getComputed
1681
+ };
1682
+ this.components.set(id, registered);
1683
+ this.emit("component:registered", { id, name: options.name });
1684
+ return registered;
1458
1685
  }
1459
1686
  /**
1460
- * Deactivate a state
1687
+ * Unregister a component
1461
1688
  */
1462
- deactivateState(id) {
1463
- const wasActive = this.activeStates.has(id);
1464
- this.activeStates.delete(id);
1465
- if (wasActive) {
1466
- this.emit("element:stateChanged", {
1467
- stateId: id,
1468
- active: false,
1469
- activeStates: this.getActiveStates()
1470
- });
1689
+ unregisterComponent(id) {
1690
+ const component = this.components.get(id);
1691
+ if (component) {
1692
+ component.mounted = false;
1693
+ this.components.delete(id);
1694
+ this.emit("component:unregistered", { id });
1695
+ return true;
1471
1696
  }
1472
- return wasActive;
1697
+ return false;
1473
1698
  }
1474
1699
  /**
1475
- * Activate multiple states
1700
+ * Get a registered component
1476
1701
  */
1477
- activateStates(ids) {
1478
- const activated = [];
1479
- for (const id of ids) {
1480
- if (this.activateState(id)) {
1481
- activated.push(id);
1482
- }
1483
- }
1484
- return activated;
1702
+ getComponent(id) {
1703
+ return this.components.get(id);
1485
1704
  }
1486
1705
  /**
1487
- * Deactivate multiple states
1706
+ * Get all registered components
1488
1707
  */
1489
- deactivateStates(ids) {
1490
- const deactivated = [];
1491
- for (const id of ids) {
1492
- if (this.deactivateState(id)) {
1493
- deactivated.push(id);
1494
- }
1495
- }
1496
- return deactivated;
1708
+ getAllComponents() {
1709
+ return Array.from(this.components.values());
1497
1710
  }
1498
1711
  /**
1499
- * Activate a state group (all states in the group)
1712
+ * Get the current state and computed properties of a component
1500
1713
  */
1501
- activateStateGroup(groupId) {
1502
- const group = this.stateGroups.get(groupId);
1503
- if (!group) return [];
1504
- return this.activateStates(group.states);
1714
+ getComponentState(id) {
1715
+ const component = this.components.get(id);
1716
+ if (!component || !component.mounted) {
1717
+ return null;
1718
+ }
1719
+ return {
1720
+ state: component.getState?.() ?? {},
1721
+ computed: component.getComputed?.() ?? {},
1722
+ timestamp: Date.now()
1723
+ };
1505
1724
  }
1506
1725
  /**
1507
- * Deactivate a state group (all states in the group)
1726
+ * Register a workflow
1508
1727
  */
1509
- deactivateStateGroup(groupId) {
1510
- const group = this.stateGroups.get(groupId);
1511
- if (!group) return [];
1512
- return this.deactivateStates(group.states);
1728
+ registerWorkflow(workflow) {
1729
+ this.workflows.set(workflow.id, workflow);
1730
+ return workflow;
1513
1731
  }
1514
1732
  /**
1515
- * Check if a transition can be executed from current state
1733
+ * Unregister a workflow
1516
1734
  */
1517
- canExecuteTransition(transitionId) {
1518
- const transition = this.transitions.get(transitionId);
1519
- if (!transition) return false;
1520
- return transition.fromStates.some((stateId) => this.activeStates.has(stateId));
1735
+ unregisterWorkflow(id) {
1736
+ return this.workflows.delete(id);
1521
1737
  }
1522
1738
  /**
1523
- * Execute a transition
1739
+ * Get a workflow
1524
1740
  */
1525
- async executeTransition(transitionId) {
1526
- const startTime = performance.now();
1527
- const transition = this.transitions.get(transitionId);
1528
- if (!transition) {
1529
- return {
1530
- success: false,
1531
- activatedStates: [],
1532
- deactivatedStates: [],
1533
- error: `Transition not found: ${transitionId}`,
1534
- durationMs: performance.now() - startTime
1535
- };
1536
- }
1537
- if (!this.canExecuteTransition(transitionId)) {
1538
- return {
1539
- success: false,
1540
- activatedStates: [],
1541
- deactivatedStates: [],
1542
- error: "Precondition not met: none of the fromStates are active",
1543
- failedPhase: "precondition",
1544
- durationMs: performance.now() - startTime
1545
- };
1546
- }
1547
- try {
1548
- const deactivated = this.deactivateStates(transition.exitStates);
1549
- if (transition.exitGroups) {
1550
- for (const groupId of transition.exitGroups) {
1551
- deactivated.push(...this.deactivateStateGroup(groupId));
1552
- }
1553
- }
1554
- const activated = this.activateStates(transition.activateStates);
1555
- if (transition.activateGroups) {
1556
- for (const groupId of transition.activateGroups) {
1557
- activated.push(...this.activateStateGroup(groupId));
1558
- }
1559
- }
1560
- return {
1561
- success: true,
1562
- activatedStates: activated,
1563
- deactivatedStates: deactivated,
1564
- durationMs: performance.now() - startTime
1565
- };
1566
- } catch (error) {
1567
- return {
1568
- success: false,
1569
- activatedStates: [],
1570
- deactivatedStates: [],
1571
- error: error instanceof Error ? error.message : String(error),
1572
- failedPhase: "execution",
1573
- durationMs: performance.now() - startTime
1574
- };
1575
- }
1741
+ getWorkflow(id) {
1742
+ return this.workflows.get(id);
1576
1743
  }
1577
1744
  /**
1578
- * Find a path from current state to target states
1579
- *
1580
- * Uses a simple BFS algorithm for pathfinding.
1581
- * For more advanced pathfinding (Dijkstra, A*), use the Python state manager service.
1745
+ * Get all workflows
1582
1746
  */
1583
- findPath(targetStates) {
1584
- if (targetStates.every((t) => this.activeStates.has(t))) {
1585
- return {
1586
- found: true,
1587
- transitions: [],
1588
- totalCost: 0,
1589
- targetStates,
1590
- estimatedSteps: 0
1591
- };
1592
- }
1593
- const queue = [
1594
- { activeStates: new Set(this.activeStates), path: [], cost: 0 }
1595
- ];
1596
- const visited = /* @__PURE__ */ new Set();
1597
- while (queue.length > 0) {
1598
- const current = queue.shift();
1599
- const stateKey = Array.from(current.activeStates).sort().join(",");
1600
- if (visited.has(stateKey)) continue;
1601
- visited.add(stateKey);
1602
- if (targetStates.every((t) => current.activeStates.has(t))) {
1603
- return {
1604
- found: true,
1605
- transitions: current.path,
1606
- totalCost: current.cost,
1607
- targetStates,
1608
- estimatedSteps: current.path.length
1609
- };
1610
- }
1611
- for (const transition of this.transitions.values()) {
1612
- const canExecute = transition.fromStates.some((s) => current.activeStates.has(s));
1613
- if (!canExecute) continue;
1614
- const newActive = new Set(current.activeStates);
1615
- for (const s of transition.exitStates) newActive.delete(s);
1616
- for (const s of transition.activateStates) newActive.add(s);
1617
- const newCost = current.cost + (transition.pathCost ?? 1);
1618
- queue.push({
1619
- activeStates: newActive,
1620
- path: [...current.path, transition.id],
1621
- cost: newCost
1622
- });
1623
- }
1624
- }
1625
- return {
1626
- found: false,
1627
- transitions: [],
1628
- totalCost: 0,
1629
- targetStates,
1630
- estimatedSteps: 0
1631
- };
1747
+ getAllWorkflows() {
1748
+ return Array.from(this.workflows.values());
1632
1749
  }
1750
+ // ==========================================================================
1751
+ // State Management
1752
+ // ==========================================================================
1633
1753
  /**
1634
- * Navigate to target states using pathfinding
1754
+ * Register a state
1635
1755
  */
1636
- async navigateTo(targetStates) {
1637
- const startTime = performance.now();
1638
- const path = this.findPath(targetStates);
1639
- if (!path.found) {
1640
- return {
1641
- success: false,
1642
- path,
1643
- executedTransitions: [],
1644
- finalActiveStates: this.getActiveStates(),
1645
- error: `No path found to target states: ${targetStates.join(", ")}`,
1646
- durationMs: performance.now() - startTime
1647
- };
1648
- }
1649
- const executedTransitions = [];
1650
- for (const transitionId of path.transitions) {
1651
- const result = await this.executeTransition(transitionId);
1652
- if (!result.success) {
1653
- return {
1654
- success: false,
1655
- path,
1656
- executedTransitions,
1657
- finalActiveStates: this.getActiveStates(),
1658
- error: result.error,
1659
- durationMs: performance.now() - startTime
1660
- };
1661
- }
1662
- executedTransitions.push(transitionId);
1663
- }
1664
- return {
1665
- success: true,
1666
- path,
1667
- executedTransitions,
1668
- finalActiveStates: this.getActiveStates(),
1669
- durationMs: performance.now() - startTime
1670
- };
1756
+ registerState(state) {
1757
+ this.states.set(state.id, state);
1758
+ this.emit("element:registered", { id: state.id, type: "state", name: state.name });
1759
+ return state;
1671
1760
  }
1672
1761
  /**
1673
- * Create a state snapshot
1762
+ * Unregister a state
1674
1763
  */
1675
- createStateSnapshot() {
1676
- return {
1677
- timestamp: Date.now(),
1678
- activeStates: this.getActiveStates(),
1679
- states: this.getAllStates(),
1680
- groups: this.getAllStateGroups(),
1681
- transitions: this.getAllTransitions()
1682
- };
1764
+ unregisterState(id) {
1765
+ const state = this.states.get(id);
1766
+ if (state) {
1767
+ this.activeStates.delete(id);
1768
+ this.states.delete(id);
1769
+ this.emit("element:unregistered", { id, type: "state" });
1770
+ return true;
1771
+ }
1772
+ return false;
1683
1773
  }
1684
1774
  /**
1685
- * Create a snapshot of the current state
1775
+ * Get a registered state
1686
1776
  */
1687
- createSnapshot() {
1688
- return {
1689
- timestamp: Date.now(),
1690
- elements: this.getAllElements().map((el) => ({
1691
- id: el.id,
1692
- type: el.type,
1693
- label: el.label,
1694
- identifier: el.getIdentifier(),
1695
- state: el.getState(),
1696
- actions: el.actions,
1697
- customActions: el.customActions ? Object.keys(el.customActions) : void 0
1698
- })),
1699
- components: this.getAllComponents().map((comp) => ({
1700
- id: comp.id,
1701
- name: comp.name,
1702
- description: comp.description,
1703
- actions: comp.actions.map((a) => a.id),
1704
- elementIds: comp.elementIds
1705
- })),
1706
- workflows: this.getAllWorkflows().map((wf) => ({
1707
- id: wf.id,
1708
- name: wf.name,
1709
- description: wf.description,
1710
- stepCount: wf.steps.length
1711
- }))
1712
- };
1777
+ getState(id) {
1778
+ return this.states.get(id);
1713
1779
  }
1714
1780
  /**
1715
- * Clear all registrations
1781
+ * Get all registered states
1716
1782
  */
1717
- clear() {
1718
- this.elements.clear();
1719
- this.components.clear();
1720
- this.workflows.clear();
1721
- this.eventListeners.clear();
1722
- this.states.clear();
1723
- this.stateGroups.clear();
1724
- this.transitions.clear();
1725
- this.activeStates.clear();
1783
+ getAllStates() {
1784
+ return Array.from(this.states.values());
1726
1785
  }
1727
1786
  /**
1728
- * Get registry statistics
1787
+ * Register a state group
1729
1788
  */
1730
- getStats() {
1731
- const elements = this.getAllElements();
1732
- const components = this.getAllComponents();
1733
- return {
1734
- elementCount: elements.length,
1735
- componentCount: components.length,
1736
- workflowCount: this.workflows.size,
1737
- mountedElementCount: elements.filter((e) => e.mounted).length,
1738
- mountedComponentCount: components.filter((c) => c.mounted).length,
1739
- stateCount: this.states.size,
1740
- stateGroupCount: this.stateGroups.size,
1741
- transitionCount: this.transitions.size,
1742
- activeStateCount: this.activeStates.size
1743
- };
1744
- }
1745
- };
1746
- var globalRegistry = null;
1747
- function getGlobalRegistry() {
1748
- if (!globalRegistry) {
1749
- globalRegistry = new UIBridgeRegistry();
1750
- }
1751
- return globalRegistry;
1752
- }
1753
- function setGlobalRegistry(registry) {
1754
- globalRegistry = registry;
1755
- }
1756
- function resetGlobalRegistry() {
1757
- globalRegistry?.clear();
1758
- globalRegistry = null;
1759
- }
1760
-
1761
- // src/core/websocket-client.ts
1762
- function generateId() {
1763
- return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
1764
- }
1765
- var UIBridgeWSClient = class {
1766
- constructor(config) {
1767
- this.ws = null;
1768
- this.state = "disconnected";
1769
- this.clientId = null;
1770
- this.reconnectAttempts = 0;
1771
- this.reconnectTimer = null;
1772
- this.pingTimer = null;
1773
- this.pendingRequests = /* @__PURE__ */ new Map();
1774
- // Event listeners
1775
- this.connectionListeners = /* @__PURE__ */ new Set();
1776
- this.eventListeners = /* @__PURE__ */ new Map();
1777
- this.errorListeners = /* @__PURE__ */ new Set();
1778
- // Current subscriptions
1779
- this.subscriptions = {};
1780
- this.config = {
1781
- url: config.url,
1782
- autoReconnect: config.autoReconnect ?? true,
1783
- reconnectDelay: config.reconnectDelay ?? 1e3,
1784
- maxReconnectAttempts: config.maxReconnectAttempts ?? 10,
1785
- pingInterval: config.pingInterval ?? 3e4,
1786
- connectionTimeout: config.connectionTimeout ?? 1e4
1787
- };
1789
+ registerStateGroup(group) {
1790
+ this.stateGroups.set(group.id, group);
1791
+ return group;
1788
1792
  }
1789
1793
  /**
1790
- * Get current connection state
1794
+ * Unregister a state group
1791
1795
  */
1792
- get connectionState() {
1793
- return this.state;
1796
+ unregisterStateGroup(id) {
1797
+ return this.stateGroups.delete(id);
1794
1798
  }
1795
1799
  /**
1796
- * Get assigned client ID
1800
+ * Get a state group
1797
1801
  */
1798
- get id() {
1799
- return this.clientId;
1802
+ getStateGroup(id) {
1803
+ return this.stateGroups.get(id);
1800
1804
  }
1801
1805
  /**
1802
- * Connect to the WebSocket server
1806
+ * Get all state groups
1803
1807
  */
1804
- connect() {
1805
- return new Promise((resolve, reject) => {
1806
- if (this.ws && this.state === "connected") {
1807
- resolve();
1808
- return;
1809
- }
1810
- this.setState("connecting");
1811
- try {
1812
- this.ws = new WebSocket(this.config.url);
1813
- } catch (error) {
1814
- this.setState("disconnected");
1815
- reject(error);
1816
- return;
1817
- }
1818
- const connectionTimeout = setTimeout(() => {
1819
- if (this.state === "connecting") {
1820
- this.ws?.close();
1821
- this.setState("disconnected");
1822
- reject(new Error("Connection timeout"));
1823
- }
1824
- }, this.config.connectionTimeout);
1825
- this.ws.onopen = () => {
1826
- clearTimeout(connectionTimeout);
1827
- };
1828
- this.ws.onmessage = (event) => {
1829
- try {
1830
- const message = JSON.parse(event.data);
1831
- this.handleMessage(message);
1832
- if (message.type === "welcome") {
1833
- clearTimeout(connectionTimeout);
1834
- this.reconnectAttempts = 0;
1835
- this.setState("connected");
1836
- this.startPingInterval();
1837
- if (this.subscriptions.events?.length || this.subscriptions.elementIds?.length || this.subscriptions.componentIds?.length) {
1838
- this.subscribe(this.subscriptions);
1839
- }
1840
- resolve();
1841
- }
1842
- } catch (error) {
1843
- console.error("Failed to parse WebSocket message:", error);
1844
- }
1845
- };
1846
- this.ws.onerror = (_event) => {
1847
- clearTimeout(connectionTimeout);
1848
- const error = new Error("WebSocket error");
1849
- this.notifyError(error);
1850
- if (this.state === "connecting") {
1851
- reject(error);
1852
- }
1853
- };
1854
- this.ws.onclose = () => {
1855
- clearTimeout(connectionTimeout);
1856
- this.stopPingInterval();
1857
- this.clientId = null;
1858
- const wasConnected = this.state === "connected";
1859
- this.setState("disconnected");
1860
- for (const [_id, pending] of this.pendingRequests) {
1861
- clearTimeout(pending.timeout);
1862
- pending.reject(new Error("Connection closed"));
1863
- }
1864
- this.pendingRequests.clear();
1865
- if (wasConnected && this.config.autoReconnect && (this.config.maxReconnectAttempts === 0 || this.reconnectAttempts < this.config.maxReconnectAttempts)) {
1866
- this.scheduleReconnect();
1867
- }
1868
- };
1869
- });
1808
+ getAllStateGroups() {
1809
+ return Array.from(this.stateGroups.values());
1870
1810
  }
1871
1811
  /**
1872
- * Disconnect from the server
1812
+ * Register a transition
1873
1813
  */
1874
- disconnect() {
1875
- if (this.reconnectTimer) {
1876
- clearTimeout(this.reconnectTimer);
1877
- this.reconnectTimer = null;
1878
- }
1879
- this.stopPingInterval();
1880
- if (this.ws) {
1881
- this.ws.close();
1882
- this.ws = null;
1883
- }
1884
- this.setState("disconnected");
1814
+ registerTransition(transition) {
1815
+ this.transitions.set(transition.id, transition);
1816
+ return transition;
1885
1817
  }
1886
1818
  /**
1887
- * Subscribe to events
1819
+ * Unregister a transition
1888
1820
  */
1889
- async subscribe(options) {
1890
- this.subscriptions = { ...this.subscriptions, ...options };
1891
- const response = await this.sendRequest({
1892
- id: generateId(),
1893
- type: "subscribe",
1894
- timestamp: Date.now(),
1895
- payload: options
1896
- });
1897
- return response.events;
1821
+ unregisterTransition(id) {
1822
+ return this.transitions.delete(id);
1898
1823
  }
1899
1824
  /**
1900
- * Unsubscribe from events
1825
+ * Get a transition
1901
1826
  */
1902
- async unsubscribe(events) {
1903
- if (events) {
1904
- this.subscriptions.events = this.subscriptions.events?.filter((e) => !events.includes(e));
1905
- } else {
1906
- this.subscriptions = {};
1907
- }
1908
- const response = await this.sendRequest({
1909
- id: generateId(),
1910
- type: "unsubscribe",
1911
- timestamp: Date.now(),
1912
- payload: { events }
1913
- });
1914
- return response.events;
1827
+ getTransition(id) {
1828
+ return this.transitions.get(id);
1915
1829
  }
1916
1830
  /**
1917
- * Find elements
1831
+ * Get all transitions
1918
1832
  */
1919
- async find(options) {
1920
- const response = await this.sendRequest({
1921
- id: generateId(),
1922
- type: "find",
1923
- timestamp: Date.now(),
1924
- payload: options
1925
- });
1926
- return response.elements;
1833
+ getAllTransitions() {
1834
+ return Array.from(this.transitions.values());
1927
1835
  }
1928
1836
  /**
1929
- * Discover elements
1930
- * @deprecated Use find() instead
1837
+ * Get currently active states
1931
1838
  */
1932
- async discover(options) {
1933
- return this.find(options);
1839
+ getActiveStates() {
1840
+ return Array.from(this.activeStates);
1934
1841
  }
1935
1842
  /**
1936
- * Get element details
1843
+ * Check if a state is active
1937
1844
  */
1938
- async getElement(elementId, includeState = true) {
1939
- const response = await this.sendRequest({
1940
- id: generateId(),
1941
- type: "getElement",
1942
- timestamp: Date.now(),
1943
- payload: { elementId, includeState }
1944
- });
1945
- return response.element;
1845
+ isStateActive(id) {
1846
+ return this.activeStates.has(id);
1946
1847
  }
1947
1848
  /**
1948
- * Get full snapshot
1849
+ * Activate a state
1949
1850
  */
1950
- async getSnapshot() {
1951
- const response = await this.sendRequest({
1952
- id: generateId(),
1953
- type: "getSnapshot",
1954
- timestamp: Date.now()
1955
- });
1956
- return response;
1851
+ activateState(id) {
1852
+ const state = this.states.get(id);
1853
+ if (!state) {
1854
+ return false;
1855
+ }
1856
+ for (const activeId of this.activeStates) {
1857
+ const activeState = this.states.get(activeId);
1858
+ if (activeState?.blocking && activeState.id !== id) {
1859
+ return false;
1860
+ }
1861
+ if (activeState?.blocks?.includes(id)) {
1862
+ return false;
1863
+ }
1864
+ }
1865
+ const wasActive = this.activeStates.has(id);
1866
+ this.activeStates.add(id);
1867
+ if (!wasActive) {
1868
+ this.emit("element:stateChanged", {
1869
+ stateId: id,
1870
+ active: true,
1871
+ activeStates: this.getActiveStates()
1872
+ });
1873
+ }
1874
+ return true;
1957
1875
  }
1958
1876
  /**
1959
- * Execute action on an element
1877
+ * Deactivate a state
1960
1878
  */
1961
- async executeAction(elementId, action) {
1962
- const response = await this.sendRequest({
1963
- id: generateId(),
1964
- type: "executeAction",
1965
- timestamp: Date.now(),
1966
- payload: { elementId, action }
1967
- });
1968
- return response;
1879
+ deactivateState(id) {
1880
+ const wasActive = this.activeStates.has(id);
1881
+ this.activeStates.delete(id);
1882
+ if (wasActive) {
1883
+ this.emit("element:stateChanged", {
1884
+ stateId: id,
1885
+ active: false,
1886
+ activeStates: this.getActiveStates()
1887
+ });
1888
+ }
1889
+ return wasActive;
1969
1890
  }
1970
1891
  /**
1971
- * Execute component action
1892
+ * Activate multiple states
1972
1893
  */
1973
- async executeComponentAction(componentId, action, params) {
1974
- const response = await this.sendRequest({
1975
- id: generateId(),
1976
- type: "executeComponentAction",
1977
- timestamp: Date.now(),
1978
- payload: { componentId, action, params }
1979
- });
1980
- return response;
1894
+ activateStates(ids) {
1895
+ const activated = [];
1896
+ for (const id of ids) {
1897
+ if (this.activateState(id)) {
1898
+ activated.push(id);
1899
+ }
1900
+ }
1901
+ return activated;
1981
1902
  }
1982
1903
  /**
1983
- * Execute workflow with optional progress streaming
1904
+ * Deactivate multiple states
1984
1905
  */
1985
- async executeWorkflow(workflowId, params, onProgress) {
1986
- const id = generateId();
1987
- const progressHandler = onProgress ? (message) => {
1988
- if (message.type === "workflowProgress" && message.requestId === id) {
1989
- onProgress({
1990
- currentStep: message.payload.currentStep,
1991
- totalSteps: message.payload.totalSteps,
1992
- step: {
1993
- id: message.payload.step.id,
1994
- status: message.payload.step.status
1995
- }
1996
- });
1906
+ deactivateStates(ids) {
1907
+ const deactivated = [];
1908
+ for (const id of ids) {
1909
+ if (this.deactivateState(id)) {
1910
+ deactivated.push(id);
1997
1911
  }
1998
- } : void 0;
1999
- const response = await this.sendRequest(
2000
- {
2001
- id,
2002
- type: "executeWorkflow",
2003
- timestamp: Date.now(),
2004
- payload: { workflowId, params, streamProgress: !!onProgress }
2005
- },
2006
- progressHandler
2007
- );
2008
- return response;
1912
+ }
1913
+ return deactivated;
2009
1914
  }
2010
1915
  /**
2011
- * Add connection state listener
1916
+ * Activate a state group (all states in the group)
2012
1917
  */
2013
- onConnectionChange(listener) {
2014
- this.connectionListeners.add(listener);
2015
- return () => this.connectionListeners.delete(listener);
1918
+ activateStateGroup(groupId) {
1919
+ const group = this.stateGroups.get(groupId);
1920
+ if (!group) return [];
1921
+ return this.activateStates(group.states);
2016
1922
  }
2017
1923
  /**
2018
- * Add event listener
1924
+ * Deactivate a state group (all states in the group)
2019
1925
  */
2020
- onEvent(eventType, listener) {
2021
- if (!this.eventListeners.has(eventType)) {
2022
- this.eventListeners.set(eventType, /* @__PURE__ */ new Set());
2023
- }
2024
- this.eventListeners.get(eventType).add(listener);
2025
- return () => this.eventListeners.get(eventType)?.delete(listener);
1926
+ deactivateStateGroup(groupId) {
1927
+ const group = this.stateGroups.get(groupId);
1928
+ if (!group) return [];
1929
+ return this.deactivateStates(group.states);
2026
1930
  }
2027
1931
  /**
2028
- * Add error listener
1932
+ * Check if a transition can be executed from current state
2029
1933
  */
2030
- onError(listener) {
2031
- this.errorListeners.add(listener);
2032
- return () => this.errorListeners.delete(listener);
1934
+ canExecuteTransition(transitionId) {
1935
+ const transition = this.transitions.get(transitionId);
1936
+ if (!transition) return false;
1937
+ return transition.fromStates.some((stateId) => this.activeStates.has(stateId));
2033
1938
  }
2034
- // Private methods
2035
- setState(state) {
2036
- this.state = state;
2037
- for (const listener of this.connectionListeners) {
2038
- try {
2039
- listener(state);
2040
- } catch (error) {
2041
- console.error("Connection listener error:", error);
2042
- }
1939
+ /**
1940
+ * Execute a transition
1941
+ */
1942
+ async executeTransition(transitionId) {
1943
+ const startTime = performance.now();
1944
+ const transition = this.transitions.get(transitionId);
1945
+ if (!transition) {
1946
+ return {
1947
+ success: false,
1948
+ activatedStates: [],
1949
+ deactivatedStates: [],
1950
+ error: `Transition not found: ${transitionId}`,
1951
+ durationMs: performance.now() - startTime
1952
+ };
2043
1953
  }
2044
- }
2045
- handleMessage(message) {
2046
- switch (message.type) {
2047
- case "welcome":
2048
- this.clientId = message.payload.clientId;
2049
- break;
2050
- case "pong":
2051
- break;
2052
- case "subscribed":
2053
- case "unsubscribed":
2054
- break;
2055
- case "event":
2056
- this.notifyEvent(message.payload);
2057
- break;
2058
- case "response":
2059
- this.handleResponse(message);
2060
- break;
2061
- case "error":
2062
- if (message.requestId) {
2063
- this.handleResponse({
2064
- ...message,
2065
- type: "response",
2066
- requestId: message.requestId,
2067
- payload: {
2068
- success: false,
2069
- error: message.payload.message
2070
- }
2071
- });
2072
- } else {
2073
- this.notifyError(new Error(message.payload.message));
1954
+ if (!this.canExecuteTransition(transitionId)) {
1955
+ return {
1956
+ success: false,
1957
+ activatedStates: [],
1958
+ deactivatedStates: [],
1959
+ error: "Precondition not met: none of the fromStates are active",
1960
+ failedPhase: "precondition",
1961
+ durationMs: performance.now() - startTime
1962
+ };
1963
+ }
1964
+ try {
1965
+ const deactivated = this.deactivateStates(transition.exitStates);
1966
+ if (transition.exitGroups) {
1967
+ for (const groupId of transition.exitGroups) {
1968
+ deactivated.push(...this.deactivateStateGroup(groupId));
2074
1969
  }
2075
- break;
1970
+ }
1971
+ const activated = this.activateStates(transition.activateStates);
1972
+ if (transition.activateGroups) {
1973
+ for (const groupId of transition.activateGroups) {
1974
+ activated.push(...this.activateStateGroup(groupId));
1975
+ }
1976
+ }
1977
+ return {
1978
+ success: true,
1979
+ activatedStates: activated,
1980
+ deactivatedStates: deactivated,
1981
+ durationMs: performance.now() - startTime
1982
+ };
1983
+ } catch (error) {
1984
+ return {
1985
+ success: false,
1986
+ activatedStates: [],
1987
+ deactivatedStates: [],
1988
+ error: error instanceof Error ? error.message : String(error),
1989
+ failedPhase: "execution",
1990
+ durationMs: performance.now() - startTime
1991
+ };
2076
1992
  }
2077
1993
  }
2078
- handleResponse(message) {
2079
- const pending = this.pendingRequests.get(message.requestId);
2080
- if (!pending) return;
2081
- clearTimeout(pending.timeout);
2082
- this.pendingRequests.delete(message.requestId);
2083
- if (message.type === "response") {
2084
- if (message.payload.success) {
2085
- pending.resolve(message.payload.data);
2086
- } else {
2087
- pending.reject(new Error(message.payload.error || "Request failed"));
2088
- }
1994
+ /**
1995
+ * Find a path from current state to target states
1996
+ *
1997
+ * Uses a simple BFS algorithm for pathfinding.
1998
+ * For more advanced pathfinding (Dijkstra, A*), use the Python state manager service.
1999
+ */
2000
+ findPath(targetStates) {
2001
+ if (targetStates.every((t) => this.activeStates.has(t))) {
2002
+ return {
2003
+ found: true,
2004
+ transitions: [],
2005
+ totalCost: 0,
2006
+ targetStates,
2007
+ estimatedSteps: 0
2008
+ };
2089
2009
  }
2090
- }
2091
- notifyEvent(event) {
2092
- const typeListeners = this.eventListeners.get(event.type);
2093
- if (typeListeners) {
2094
- for (const listener of typeListeners) {
2095
- try {
2096
- listener(event);
2097
- } catch (error) {
2098
- console.error("Event listener error:", error);
2099
- }
2010
+ const queue = [
2011
+ { activeStates: new Set(this.activeStates), path: [], cost: 0 }
2012
+ ];
2013
+ const visited = /* @__PURE__ */ new Set();
2014
+ while (queue.length > 0) {
2015
+ const current = queue.shift();
2016
+ const stateKey = Array.from(current.activeStates).sort().join(",");
2017
+ if (visited.has(stateKey)) continue;
2018
+ visited.add(stateKey);
2019
+ if (targetStates.every((t) => current.activeStates.has(t))) {
2020
+ return {
2021
+ found: true,
2022
+ transitions: current.path,
2023
+ totalCost: current.cost,
2024
+ targetStates,
2025
+ estimatedSteps: current.path.length
2026
+ };
2100
2027
  }
2101
- }
2102
- const wildcardListeners = this.eventListeners.get("*");
2103
- if (wildcardListeners) {
2104
- for (const listener of wildcardListeners) {
2105
- try {
2106
- listener(event);
2107
- } catch (error) {
2108
- console.error("Event listener error:", error);
2109
- }
2028
+ for (const transition of this.transitions.values()) {
2029
+ const canExecute = transition.fromStates.some((s) => current.activeStates.has(s));
2030
+ if (!canExecute) continue;
2031
+ const newActive = new Set(current.activeStates);
2032
+ for (const s of transition.exitStates) newActive.delete(s);
2033
+ for (const s of transition.activateStates) newActive.add(s);
2034
+ const newCost = current.cost + (transition.pathCost ?? 1);
2035
+ queue.push({
2036
+ activeStates: newActive,
2037
+ path: [...current.path, transition.id],
2038
+ cost: newCost
2039
+ });
2110
2040
  }
2111
2041
  }
2042
+ return {
2043
+ found: false,
2044
+ transitions: [],
2045
+ totalCost: 0,
2046
+ targetStates,
2047
+ estimatedSteps: 0
2048
+ };
2112
2049
  }
2113
- notifyError(error) {
2114
- for (const listener of this.errorListeners) {
2115
- try {
2116
- listener(error);
2117
- } catch (e) {
2118
- console.error("Error listener error:", e);
2119
- }
2050
+ /**
2051
+ * Navigate to target states using pathfinding
2052
+ */
2053
+ async navigateTo(targetStates) {
2054
+ const startTime = performance.now();
2055
+ const path = this.findPath(targetStates);
2056
+ if (!path.found) {
2057
+ return {
2058
+ success: false,
2059
+ path,
2060
+ executedTransitions: [],
2061
+ finalActiveStates: this.getActiveStates(),
2062
+ error: `No path found to target states: ${targetStates.join(", ")}`,
2063
+ durationMs: performance.now() - startTime
2064
+ };
2120
2065
  }
2121
- }
2122
- sendRequest(message, progressHandler) {
2123
- return new Promise((resolve, reject) => {
2124
- if (!this.ws || this.state !== "connected") {
2125
- reject(new Error("Not connected"));
2126
- return;
2127
- }
2128
- const timeout = setTimeout(() => {
2129
- this.pendingRequests.delete(message.id);
2130
- reject(new Error("Request timeout"));
2131
- }, 3e4);
2132
- this.pendingRequests.set(message.id, {
2133
- resolve,
2134
- reject,
2135
- timeout
2136
- });
2137
- if (progressHandler && this.ws) {
2138
- const originalHandler = this.ws.onmessage;
2139
- const wsRef = this.ws;
2140
- const wrappedHandler = (event) => {
2141
- try {
2142
- const msg = JSON.parse(event.data);
2143
- if (msg.type === "workflowProgress") {
2144
- progressHandler(msg);
2145
- }
2146
- } catch {
2147
- }
2148
- if (originalHandler) {
2149
- originalHandler.call(wsRef, event);
2150
- }
2066
+ const executedTransitions = [];
2067
+ for (const transitionId of path.transitions) {
2068
+ const result = await this.executeTransition(transitionId);
2069
+ if (!result.success) {
2070
+ return {
2071
+ success: false,
2072
+ path,
2073
+ executedTransitions,
2074
+ finalActiveStates: this.getActiveStates(),
2075
+ error: result.error,
2076
+ durationMs: performance.now() - startTime
2151
2077
  };
2152
- this.ws.onmessage = wrappedHandler;
2153
2078
  }
2154
- this.ws.send(JSON.stringify(message));
2155
- });
2079
+ executedTransitions.push(transitionId);
2080
+ }
2081
+ return {
2082
+ success: true,
2083
+ path,
2084
+ executedTransitions,
2085
+ finalActiveStates: this.getActiveStates(),
2086
+ durationMs: performance.now() - startTime
2087
+ };
2156
2088
  }
2157
- scheduleReconnect() {
2158
- if (this.reconnectTimer) return;
2159
- this.setState("reconnecting");
2160
- this.reconnectAttempts++;
2161
- const delay = Math.min(
2162
- this.config.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1),
2163
- 3e4
2164
- );
2165
- this.reconnectTimer = setTimeout(() => {
2166
- this.reconnectTimer = null;
2167
- this.connect().catch(() => {
2168
- });
2169
- }, delay);
2089
+ /**
2090
+ * Create a state snapshot
2091
+ */
2092
+ createStateSnapshot() {
2093
+ return {
2094
+ timestamp: Date.now(),
2095
+ activeStates: this.getActiveStates(),
2096
+ states: this.getAllStates(),
2097
+ groups: this.getAllStateGroups(),
2098
+ transitions: this.getAllTransitions()
2099
+ };
2170
2100
  }
2171
- startPingInterval() {
2172
- if (this.config.pingInterval <= 0) return;
2173
- this.pingTimer = setInterval(() => {
2174
- if (this.ws && this.state === "connected") {
2175
- this.ws.send(
2176
- JSON.stringify({
2177
- id: generateId(),
2178
- type: "ping",
2179
- timestamp: Date.now()
2180
- })
2181
- );
2182
- }
2183
- }, this.config.pingInterval);
2101
+ /**
2102
+ * Create a snapshot of the current state
2103
+ */
2104
+ createSnapshot() {
2105
+ return {
2106
+ timestamp: Date.now(),
2107
+ elements: this.getAllElements().map((el) => ({
2108
+ id: el.id,
2109
+ type: el.type,
2110
+ label: el.label,
2111
+ identifier: el.getIdentifier(),
2112
+ state: el.getState(),
2113
+ actions: el.actions,
2114
+ customActions: el.customActions ? Object.keys(el.customActions) : void 0
2115
+ })),
2116
+ components: this.getAllComponents().map((comp) => ({
2117
+ id: comp.id,
2118
+ name: comp.name,
2119
+ description: comp.description,
2120
+ actions: comp.actions.map((a) => a.id),
2121
+ elementIds: comp.elementIds
2122
+ })),
2123
+ workflows: this.getAllWorkflows().map((wf) => ({
2124
+ id: wf.id,
2125
+ name: wf.name,
2126
+ description: wf.description,
2127
+ stepCount: wf.steps.length
2128
+ }))
2129
+ };
2184
2130
  }
2185
- stopPingInterval() {
2186
- if (this.pingTimer) {
2187
- clearInterval(this.pingTimer);
2188
- this.pingTimer = null;
2189
- }
2131
+ /**
2132
+ * Clear all registrations
2133
+ */
2134
+ clear() {
2135
+ this.elements.clear();
2136
+ this.components.clear();
2137
+ this.workflows.clear();
2138
+ this.eventListeners.clear();
2139
+ this.states.clear();
2140
+ this.stateGroups.clear();
2141
+ this.transitions.clear();
2142
+ this.activeStates.clear();
2143
+ }
2144
+ /**
2145
+ * Get registry statistics
2146
+ */
2147
+ getStats() {
2148
+ const elements = this.getAllElements();
2149
+ const components = this.getAllComponents();
2150
+ return {
2151
+ elementCount: elements.length,
2152
+ componentCount: components.length,
2153
+ workflowCount: this.workflows.size,
2154
+ mountedElementCount: elements.filter((e) => e.mounted).length,
2155
+ mountedComponentCount: components.filter((c) => c.mounted).length,
2156
+ stateCount: this.states.size,
2157
+ stateGroupCount: this.stateGroups.size,
2158
+ transitionCount: this.transitions.size,
2159
+ activeStateCount: this.activeStates.size
2160
+ };
2190
2161
  }
2191
2162
  };
2192
- function createWSClient(config) {
2193
- return new UIBridgeWSClient(config);
2163
+ var globalRegistry = null;
2164
+ function setGlobalRegistry(registry) {
2165
+ globalRegistry = registry;
2166
+ }
2167
+ function resetGlobalRegistry() {
2168
+ globalRegistry?.clear();
2169
+ globalRegistry = null;
2194
2170
  }
2195
2171
 
2196
2172
  // src/control/action-executor.ts
@@ -4095,11 +4071,31 @@ function useUIComponent(options) {
4095
4071
  const registeredRef = react.useRef(false);
4096
4072
  const actionsRef = react.useRef(options.actions || []);
4097
4073
  const elementIdsRef = react.useRef(options.elementIds || []);
4074
+ const stateRef = react.useRef(options.state);
4075
+ const computedRef = react.useRef(options.computed);
4098
4076
  const { id, name, description, autoRegister = true } = options;
4099
4077
  react.useEffect(() => {
4100
4078
  actionsRef.current = options.actions || [];
4101
4079
  elementIdsRef.current = options.elementIds || [];
4102
- }, [options.actions, options.elementIds]);
4080
+ stateRef.current = options.state;
4081
+ computedRef.current = options.computed;
4082
+ }, [options.actions, options.elementIds, options.state, options.computed]);
4083
+ const createGetComputed = react.useCallback(() => {
4084
+ return () => {
4085
+ const computed = computedRef.current;
4086
+ if (!computed) return {};
4087
+ const result = {};
4088
+ for (const [key, def] of Object.entries(computed)) {
4089
+ try {
4090
+ const getter = typeof def === "function" ? def : def.getter;
4091
+ result[key] = getter();
4092
+ } catch {
4093
+ result[key] = void 0;
4094
+ }
4095
+ }
4096
+ return result;
4097
+ };
4098
+ }, []);
4103
4099
  const register = react.useCallback(() => {
4104
4100
  if (!bridge || registeredRef.current) return;
4105
4101
  bridge.registry.registerComponent(id, {
@@ -4111,10 +4107,12 @@ function useUIComponent(options) {
4111
4107
  description: a.description,
4112
4108
  handler: a.handler
4113
4109
  })),
4114
- elementIds: elementIdsRef.current
4110
+ elementIds: elementIdsRef.current,
4111
+ getState: stateRef.current,
4112
+ getComputed: createGetComputed()
4115
4113
  });
4116
4114
  registeredRef.current = true;
4117
- }, [bridge, id, name, description]);
4115
+ }, [bridge, id, name, description, createGetComputed]);
4118
4116
  const unregister = react.useCallback(() => {
4119
4117
  if (!bridge || !registeredRef.current) return;
4120
4118
  bridge.registry.unregisterComponent(id);
@@ -8718,7 +8716,6 @@ exports.DOMChangeObserver = DOMChangeObserver;
8718
8716
  exports.DefaultActionExecutor = DefaultActionExecutor;
8719
8717
  exports.DefaultWorkflowEngine = DefaultWorkflowEngine;
8720
8718
  exports.ErrorCodes = ErrorCodes;
8721
- exports.ID_ATTRIBUTES = ID_ATTRIBUTES;
8722
8719
  exports.InMemoryRenderLogStorage = InMemoryRenderLogStorage;
8723
8720
  exports.InfoPanel = InfoPanel;
8724
8721
  exports.Inspector = Inspector;
@@ -8730,7 +8727,6 @@ exports.SearchEngine = SearchEngine;
8730
8727
  exports.SemanticDiffManager = SemanticDiffManager;
8731
8728
  exports.SemanticSnapshotManager = SemanticSnapshotManager;
8732
8729
  exports.UIBridgeProvider = UIBridgeProvider;
8733
- exports.UIBridgeRegistry = UIBridgeRegistry;
8734
8730
  exports.UIBridgeWSClient = UIBridgeWSClient;
8735
8731
  exports.areSynonyms = areSynonyms;
8736
8732
  exports.captureDOMSnapshot = captureDOMSnapshot;
@@ -8739,7 +8735,6 @@ exports.computeDiff = computeDiff;
8739
8735
  exports.createActionExecutor = createActionExecutor;
8740
8736
  exports.createAssertionExecutor = createAssertionExecutor;
8741
8737
  exports.createDiffManager = createDiffManager;
8742
- exports.createElementIdentifier = createElementIdentifier;
8743
8738
  exports.createErrorContext = createErrorContext;
8744
8739
  exports.createMetricsCollector = createMetricsCollector;
8745
8740
  exports.createNLActionExecutor = createNLActionExecutor;
@@ -8751,19 +8746,15 @@ exports.createWSClient = createWSClient;
8751
8746
  exports.createWorkflowEngine = createWorkflowEngine;
8752
8747
  exports.describeAction = describeAction;
8753
8748
  exports.describeDiff = describeDiff;
8754
- exports.elementMatchesIdentifier = elementMatchesIdentifier;
8755
8749
  exports.extractModifiers = extractModifiers;
8756
- exports.findAllElementsByIdentifier = findAllElementsByIdentifier;
8757
8750
  exports.findAllMatches = findAllMatches;
8758
8751
  exports.findBestMatch = findBestMatch;
8759
- exports.findElementByIdentifier = findElementByIdentifier;
8760
8752
  exports.formatDuration = formatDuration;
8761
8753
  exports.formatErrorContext = formatErrorContext;
8762
8754
  exports.formatPercentage = formatPercentage;
8763
8755
  exports.fuzzyContains = fuzzyContains;
8764
8756
  exports.fuzzyMatch = fuzzyMatch;
8765
8757
  exports.generateAliases = generateAliases;
8766
- exports.generateCSSSelector = generateCSSSelector;
8767
8758
  exports.generateDescription = generateDescription;
8768
8759
  exports.generateDiffSummary = generateDiffSummary;
8769
8760
  exports.generateElementDescription = generateElementDescription;
@@ -8772,10 +8763,7 @@ exports.generatePageSummary = generatePageSummary;
8772
8763
  exports.generatePurpose = generatePurpose;
8773
8764
  exports.generateSnapshotSummary = generateSnapshotSummary;
8774
8765
  exports.generateSuggestedActions = generateSuggestedActions;
8775
- exports.generateXPath = generateXPath;
8776
- exports.getBestIdentifier = getBestIdentifier;
8777
8766
  exports.getBestRecoverySuggestion = getBestRecoverySuggestion;
8778
- exports.getGlobalRegistry = getGlobalRegistry;
8779
8767
  exports.getSynonyms = getSynonyms;
8780
8768
  exports.hasSignificantChanges = hasSignificantChanges;
8781
8769
  exports.inferPageType = inferPageType;
@@ -8788,8 +8776,6 @@ exports.ngramSimilarity = ngramSimilarity;
8788
8776
  exports.normalizeString = normalizeString;
8789
8777
  exports.parseNLInstruction = parseNLInstruction;
8790
8778
  exports.parseNLInstructions = parseNLInstructions;
8791
- exports.resetGlobalRegistry = resetGlobalRegistry;
8792
- exports.setGlobalRegistry = setGlobalRegistry;
8793
8779
  exports.splitCompoundInstruction = splitCompoundInstruction;
8794
8780
  exports.tokenSimilarity = tokenSimilarity;
8795
8781
  exports.tokenize = tokenize;