@hyperfixi/intent-element 2.4.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/dist/index.js ADDED
@@ -0,0 +1,470 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/schema-registry.ts
12
+ var IntentSchemaRegistry, intentRegistry;
13
+ var init_schema_registry = __esm({
14
+ "src/schema-registry.ts"() {
15
+ "use strict";
16
+ IntentSchemaRegistry = class {
17
+ constructor() {
18
+ this.schemas = /* @__PURE__ */ new Map();
19
+ }
20
+ /** Register a command schema. Overwrites any existing schema for the same action. */
21
+ register(schema) {
22
+ this.schemas.set(schema.action, schema);
23
+ }
24
+ /** Register multiple schemas at once. */
25
+ registerAll(schemas) {
26
+ for (const schema of schemas) this.register(schema);
27
+ }
28
+ /** Look up a schema by action name. Returns undefined if not registered. */
29
+ get(action) {
30
+ return this.schemas.get(action);
31
+ }
32
+ /** Check whether a schema is registered for the given action. */
33
+ has(action) {
34
+ return this.schemas.has(action);
35
+ }
36
+ /** Remove a schema. */
37
+ unregister(action) {
38
+ this.schemas.delete(action);
39
+ }
40
+ /** Clear all registered schemas. */
41
+ clear() {
42
+ this.schemas.clear();
43
+ }
44
+ /** Number of registered schemas. */
45
+ get size() {
46
+ return this.schemas.size;
47
+ }
48
+ /**
49
+ * Fetch and register schemas from a JSON endpoint.
50
+ * The endpoint must return an array of CommandSchema objects.
51
+ * Throws if the response is not OK, not an array, or contains malformed schemas.
52
+ */
53
+ async loadFrom(url) {
54
+ const response = await fetch(url);
55
+ if (!response.ok) {
56
+ throw new Error(
57
+ `Failed to load schemas from ${url}: ${response.status} ${response.statusText}`
58
+ );
59
+ }
60
+ const body = await response.json();
61
+ if (!Array.isArray(body)) {
62
+ throw new Error(`Expected array of schemas from ${url}, got ${typeof body}`);
63
+ }
64
+ for (let i = 0; i < body.length; i++) {
65
+ const s = body[i];
66
+ if (typeof s?.action !== "string" || !Array.isArray(s?.roles)) {
67
+ throw new Error(
68
+ `Schema at index ${i} from ${url} is missing required fields "action" (string) and "roles" (array)`
69
+ );
70
+ }
71
+ }
72
+ this.registerAll(body);
73
+ }
74
+ };
75
+ intentRegistry = new IntentSchemaRegistry();
76
+ }
77
+ });
78
+
79
+ // src/sandbox.ts
80
+ async function sandboxed(fn, timeoutMs = DEFAULT_TIMEOUT_MS) {
81
+ let timedOut = false;
82
+ let timeoutHandle;
83
+ const timeoutPromise = new Promise((_, reject) => {
84
+ timeoutHandle = setTimeout(() => {
85
+ timedOut = true;
86
+ reject(new Error(`LSE execution timed out after ${timeoutMs}ms`));
87
+ }, timeoutMs);
88
+ });
89
+ try {
90
+ const result = await Promise.race([fn(), timeoutPromise]);
91
+ clearTimeout(timeoutHandle);
92
+ return { ok: true, result };
93
+ } catch (err) {
94
+ clearTimeout(timeoutHandle);
95
+ const error = err instanceof Error ? err : new Error(String(err));
96
+ return { ok: false, error, timedOut };
97
+ }
98
+ }
99
+ var DEFAULT_TIMEOUT_MS;
100
+ var init_sandbox = __esm({
101
+ "src/sandbox.ts"() {
102
+ "use strict";
103
+ DEFAULT_TIMEOUT_MS = 5e3;
104
+ }
105
+ });
106
+
107
+ // src/lse-intent.ts
108
+ var lse_intent_exports = {};
109
+ __export(lse_intent_exports, {
110
+ LSEIntentElement: () => LSEIntentElement
111
+ });
112
+ import { fromProtocolJSON, validateProtocolJSON } from "@lokascript/intent";
113
+ function getRuntime() {
114
+ const w = globalThis;
115
+ const api = w["hyperfixi"];
116
+ if (api && typeof api["evalLSENode"] === "function") {
117
+ return api;
118
+ }
119
+ return null;
120
+ }
121
+ function parseTriggerValue(value) {
122
+ const v = value.trim().toLowerCase();
123
+ if (v === "" || v === "load") return { kind: "load" };
124
+ if (v === "manual") return { kind: "manual" };
125
+ if (v === "intersect") return { kind: "intersect" };
126
+ if (v === "submit") return { kind: "submit" };
127
+ return { kind: "event", eventName: v };
128
+ }
129
+ var LSEIntentElement;
130
+ var init_lse_intent = __esm({
131
+ "src/lse-intent.ts"() {
132
+ "use strict";
133
+ init_schema_registry();
134
+ init_sandbox();
135
+ LSEIntentElement = class extends HTMLElement {
136
+ constructor() {
137
+ super(...arguments);
138
+ this._node = null;
139
+ this._diagnostics = [];
140
+ this._abortController = null;
141
+ this._triggerCleanup = null;
142
+ this._initInFlight = false;
143
+ this._initPending = false;
144
+ }
145
+ // ── Lifecycle ──────────────────────────────────────────────────────────────
146
+ connectedCallback() {
147
+ void this._initialize();
148
+ }
149
+ disconnectedCallback() {
150
+ this._abortController?.abort();
151
+ this._abortController = null;
152
+ this._tearDownTrigger();
153
+ }
154
+ attributeChangedCallback(name, _old, _new) {
155
+ if (name !== "src" && name !== "disabled" && name !== "trigger") return;
156
+ if (!this.isConnected) return;
157
+ void this._initialize();
158
+ }
159
+ // ── Public accessors ───────────────────────────────────────────────────────
160
+ /** The parsed SemanticNode, or null if not yet loaded / invalid. */
161
+ get node() {
162
+ return this._node;
163
+ }
164
+ /** Diagnostics from the last validation attempt. Returns a snapshot copy. */
165
+ get diagnostics() {
166
+ return [...this._diagnostics];
167
+ }
168
+ /**
169
+ * Re-run preparation and execution explicitly. Useful after updating the
170
+ * inline JSON, and required when `trigger="manual"`. Always executes
171
+ * regardless of trigger mode.
172
+ */
173
+ async refresh() {
174
+ const prepared = await this._prepare();
175
+ if (!prepared) return;
176
+ await this._execute(prepared.node);
177
+ }
178
+ // ── Private: initialization and trigger dispatch ───────────────────────────
179
+ /**
180
+ * Serialize concurrent `_initialize` calls. If one is already running,
181
+ * mark a re-init as pending and return. The in-flight init will re-run
182
+ * once after it finishes so the final attribute/JSON state is reflected.
183
+ */
184
+ async _initialize() {
185
+ if (this._initInFlight) {
186
+ this._initPending = true;
187
+ return;
188
+ }
189
+ this._initInFlight = true;
190
+ try {
191
+ await this._doInitialize();
192
+ while (this._initPending) {
193
+ this._initPending = false;
194
+ await this._doInitialize();
195
+ }
196
+ } finally {
197
+ this._initInFlight = false;
198
+ }
199
+ }
200
+ async _doInitialize() {
201
+ this._tearDownTrigger();
202
+ if (this.hasAttribute("disabled")) return;
203
+ const prepared = await this._prepare();
204
+ if (!prepared) return;
205
+ if (!this.isConnected) return;
206
+ const spec = this._resolveTriggerSpec(prepared.raw);
207
+ switch (spec.kind) {
208
+ case "load":
209
+ await this._execute(prepared.node);
210
+ return;
211
+ case "manual":
212
+ return;
213
+ case "intersect":
214
+ this._wireIntersect(prepared.node);
215
+ return;
216
+ case "submit":
217
+ this._wireSubmit(prepared.node);
218
+ return;
219
+ case "event":
220
+ this._wireEvent(prepared.node, spec.eventName);
221
+ return;
222
+ }
223
+ }
224
+ _resolveTriggerSpec(raw) {
225
+ const attr = this.getAttribute("trigger");
226
+ if (attr !== null) {
227
+ return parseTriggerValue(attr);
228
+ }
229
+ const triggerObj = raw["trigger"];
230
+ if (triggerObj && typeof triggerObj === "object" && !Array.isArray(triggerObj)) {
231
+ const eventName = triggerObj["event"];
232
+ if (typeof eventName === "string" && eventName.length > 0) {
233
+ return parseTriggerValue(eventName);
234
+ }
235
+ }
236
+ return { kind: "load" };
237
+ }
238
+ // ── Private: trigger wiring ────────────────────────────────────────────────
239
+ _tearDownTrigger() {
240
+ if (this._triggerCleanup) {
241
+ this._triggerCleanup();
242
+ this._triggerCleanup = null;
243
+ }
244
+ }
245
+ _wireEvent(node, eventName) {
246
+ const handler = (_ev) => {
247
+ void this._execute(node);
248
+ };
249
+ this.addEventListener(eventName, handler);
250
+ this._triggerCleanup = () => this.removeEventListener(eventName, handler);
251
+ }
252
+ _wireSubmit(node) {
253
+ const form = this.closest("form");
254
+ if (!form) {
255
+ this._diagnostics.push({
256
+ severity: "warning",
257
+ code: "NO_ANCESTOR_FORM",
258
+ message: 'trigger="submit" requires an ancestor <form>; none found.'
259
+ });
260
+ return;
261
+ }
262
+ const handler = (ev) => {
263
+ ev.preventDefault();
264
+ void this._execute(node);
265
+ };
266
+ form.addEventListener("submit", handler);
267
+ this._triggerCleanup = () => form.removeEventListener("submit", handler);
268
+ }
269
+ _wireIntersect(node) {
270
+ if (typeof IntersectionObserver === "undefined") {
271
+ this._diagnostics.push({
272
+ severity: "warning",
273
+ code: "NO_INTERSECTION_OBSERVER",
274
+ message: 'trigger="intersect" requires IntersectionObserver; not available in this environment.'
275
+ });
276
+ return;
277
+ }
278
+ const observer = new IntersectionObserver((entries) => {
279
+ for (const entry of entries) {
280
+ if (entry.isIntersecting) {
281
+ observer.disconnect();
282
+ void this._execute(node);
283
+ return;
284
+ }
285
+ }
286
+ });
287
+ observer.observe(this);
288
+ this._triggerCleanup = () => observer.disconnect();
289
+ }
290
+ // ── Private: prepare + execute ─────────────────────────────────────────────
291
+ /**
292
+ * Read the JSON, validate, deserialize, and emit `lse:validated`. Returns
293
+ * the prepared node and raw JSON on success, or null on failure (in which
294
+ * case `lse:error` has already been emitted).
295
+ */
296
+ async _prepare() {
297
+ const raw = await this._readJSON();
298
+ if (raw === null) return null;
299
+ const wireDiags = validateProtocolJSON(raw);
300
+ const hasErrors = wireDiags.some((d) => d.severity === "error");
301
+ if (hasErrors) {
302
+ this._diagnostics = wireDiags;
303
+ this._node = null;
304
+ this._showError();
305
+ this._emit("lse:error", { diagnostics: wireDiags, error: null });
306
+ return null;
307
+ }
308
+ let node;
309
+ try {
310
+ node = fromProtocolJSON(raw);
311
+ } catch (err) {
312
+ const error = err instanceof Error ? err : new Error(String(err));
313
+ this._diagnostics = [
314
+ { severity: "error", code: "DESERIALIZE_ERROR", message: error.message }
315
+ ];
316
+ this._node = null;
317
+ this._showError();
318
+ this._emit("lse:error", { diagnostics: this._diagnostics, error });
319
+ return null;
320
+ }
321
+ const schema = intentRegistry.get(node.action);
322
+ const schemaDiags = schema ? this._validateSchema(node, schema) : [];
323
+ this._node = node;
324
+ this._diagnostics = [...wireDiags, ...schemaDiags];
325
+ this._emit("lse:validated", { node, diagnostics: [...this._diagnostics] });
326
+ return { node, raw };
327
+ }
328
+ /**
329
+ * Execute a prepared node via the hyperfixi runtime. Emits `lse:executed`
330
+ * on success or `lse:error` on failure. Safe to call multiple times.
331
+ *
332
+ * **Event-handler unwrap.** If the prepared node is an event-handler (from
333
+ * either verbose wire format or compact `trigger` sugar), the element's
334
+ * own `trigger` attribute has already wired the DOM event listener — the
335
+ * wire-format event metadata is redundant at this point. We unwrap the
336
+ * event-handler and execute each body command directly, rather than passing
337
+ * the event-handler node to `evalLSENode` (which would attempt to re-wire
338
+ * a listener at runtime, effectively discarding the body).
339
+ */
340
+ async _execute(node) {
341
+ const runtime = getRuntime();
342
+ if (!runtime) {
343
+ this._diagnostics.push({
344
+ severity: "warning",
345
+ code: "NO_RUNTIME",
346
+ message: "hyperfixi runtime not found. Load hyperfixi.js before intent-element.iife.js to enable execution."
347
+ });
348
+ return;
349
+ }
350
+ const executables = node.kind === "event-handler" ? [...node.body ?? []] : [node];
351
+ const rawTimeout = this.getAttribute("timeout");
352
+ const timeoutMs = rawTimeout !== null ? parseInt(rawTimeout, 10) || 5e3 : 5e3;
353
+ const results = [];
354
+ for (const cmd of executables) {
355
+ const result = await sandboxed(() => runtime.evalLSENode(cmd, this), timeoutMs);
356
+ if (result.ok) {
357
+ results.push(result.result);
358
+ } else {
359
+ this._diagnostics.push({
360
+ severity: "error",
361
+ code: result.timedOut ? "EXECUTION_TIMEOUT" : "EXECUTION_ERROR",
362
+ message: result.error?.message ?? "Unknown execution error"
363
+ });
364
+ this._showError();
365
+ this._emit("lse:error", { diagnostics: [...this._diagnostics], error: result.error });
366
+ return;
367
+ }
368
+ }
369
+ this._emit("lse:executed", {
370
+ node,
371
+ // For single-body cases keep the legacy `result` shape (first/only value);
372
+ // for multi-body cases expose the full array via `results` too.
373
+ result: results.length === 1 ? results[0] : results,
374
+ results
375
+ });
376
+ }
377
+ async _readJSON() {
378
+ const src = this.getAttribute("src");
379
+ if (src) {
380
+ this._abortController?.abort();
381
+ this._abortController = new AbortController();
382
+ try {
383
+ const response = await fetch(src, { signal: this._abortController.signal });
384
+ if (!response.ok) {
385
+ this._emitFetchError(src, response.status);
386
+ return null;
387
+ }
388
+ return await response.json();
389
+ } catch (err) {
390
+ if (err instanceof Error && err.name === "AbortError") return null;
391
+ this._emitFetchError(src, 0, err instanceof Error ? err : void 0);
392
+ return null;
393
+ }
394
+ }
395
+ const script = this.querySelector('script[type="application/lse+json"]');
396
+ if (script) {
397
+ try {
398
+ return JSON.parse(script.textContent ?? "");
399
+ } catch {
400
+ const diag = {
401
+ severity: "error",
402
+ code: "INVALID_JSON",
403
+ message: "Inline JSON is not valid JSON"
404
+ };
405
+ this._diagnostics = [diag];
406
+ this._showError();
407
+ this._emit("lse:error", { diagnostics: [diag], error: null });
408
+ return null;
409
+ }
410
+ }
411
+ return null;
412
+ }
413
+ _validateSchema(node, schema) {
414
+ const diags = [];
415
+ for (const roleSpec of schema.roles) {
416
+ const value = node.roles.get(roleSpec.role);
417
+ if (roleSpec.required && !value) {
418
+ diags.push({
419
+ severity: "error",
420
+ code: "MISSING_REQUIRED_ROLE",
421
+ message: `Required role "${roleSpec.role}" is missing for command "${node.action}"`
422
+ });
423
+ continue;
424
+ }
425
+ if (value && roleSpec.expectedTypes && roleSpec.expectedTypes.length > 0) {
426
+ if (!roleSpec.expectedTypes.includes(value.type)) {
427
+ diags.push({
428
+ severity: "warning",
429
+ code: "UNEXPECTED_ROLE_TYPE",
430
+ message: `Role "${roleSpec.role}" has type "${value.type}" but expected one of: ${roleSpec.expectedTypes.join(", ")}`
431
+ });
432
+ }
433
+ }
434
+ }
435
+ return diags;
436
+ }
437
+ _showError() {
438
+ const errorSlot = this.querySelector('[slot="error"]');
439
+ if (errorSlot instanceof HTMLElement) {
440
+ errorSlot.style.display = "";
441
+ }
442
+ }
443
+ _emit(type, detail) {
444
+ this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
445
+ }
446
+ _emitFetchError(src, status, error) {
447
+ const message = status ? `Failed to fetch ${src}: HTTP ${status}` : `Failed to fetch ${src}${error ? ": " + error.message : ""}`;
448
+ const diag = { severity: "error", code: "FETCH_ERROR", message };
449
+ this._diagnostics = [diag];
450
+ this._showError();
451
+ this._emit("lse:error", { diagnostics: [diag], error: error ?? null });
452
+ }
453
+ };
454
+ LSEIntentElement.observedAttributes = ["src", "disabled", "timeout", "trigger"];
455
+ }
456
+ });
457
+
458
+ // src/index.ts
459
+ init_lse_intent();
460
+ init_schema_registry();
461
+ if (typeof customElements !== "undefined" && !customElements.get("lse-intent")) {
462
+ Promise.resolve().then(() => (init_lse_intent(), lse_intent_exports)).then(({ LSEIntentElement: LSEIntentElement2 }) => {
463
+ customElements.define("lse-intent", LSEIntentElement2);
464
+ });
465
+ }
466
+ export {
467
+ LSEIntentElement,
468
+ intentRegistry
469
+ };
470
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schema-registry.ts","../src/sandbox.ts","../src/lse-intent.ts","../src/index.ts"],"sourcesContent":["/**\n * Client-side schema registry for <lse-intent>.\n *\n * Schemas registered here are used to validate protocol JSON before execution.\n * Register once per page; the registry is a singleton per module.\n */\n\nimport type { CommandSchema } from '@lokascript/intent';\n\nclass IntentSchemaRegistry {\n private schemas = new Map<string, CommandSchema>();\n\n /** Register a command schema. Overwrites any existing schema for the same action. */\n register(schema: CommandSchema): void {\n this.schemas.set(schema.action, schema);\n }\n\n /** Register multiple schemas at once. */\n registerAll(schemas: CommandSchema[]): void {\n for (const schema of schemas) this.register(schema);\n }\n\n /** Look up a schema by action name. Returns undefined if not registered. */\n get(action: string): CommandSchema | undefined {\n return this.schemas.get(action);\n }\n\n /** Check whether a schema is registered for the given action. */\n has(action: string): boolean {\n return this.schemas.has(action);\n }\n\n /** Remove a schema. */\n unregister(action: string): void {\n this.schemas.delete(action);\n }\n\n /** Clear all registered schemas. */\n clear(): void {\n this.schemas.clear();\n }\n\n /** Number of registered schemas. */\n get size(): number {\n return this.schemas.size;\n }\n\n /**\n * Fetch and register schemas from a JSON endpoint.\n * The endpoint must return an array of CommandSchema objects.\n * Throws if the response is not OK, not an array, or contains malformed schemas.\n */\n async loadFrom(url: string): Promise<void> {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(\n `Failed to load schemas from ${url}: ${response.status} ${response.statusText}`\n );\n }\n const body: unknown = await response.json();\n if (!Array.isArray(body)) {\n throw new Error(`Expected array of schemas from ${url}, got ${typeof body}`);\n }\n for (let i = 0; i < body.length; i++) {\n const s = body[i] as Record<string, unknown>;\n if (typeof s?.action !== 'string' || !Array.isArray(s?.roles)) {\n throw new Error(\n `Schema at index ${i} from ${url} is missing required fields \"action\" (string) and \"roles\" (array)`\n );\n }\n }\n this.registerAll(body as CommandSchema[]);\n }\n}\n\n/** Singleton registry — shared across all <lse-intent> elements on the page. */\nexport const intentRegistry = new IntentSchemaRegistry();\n","/**\n * Sandboxed execution wrapper for <lse-intent>.\n *\n * Wraps the hyperfixi runtime call with:\n * - try/catch for execution errors\n * - configurable timeout (default 5s)\n * - structured error reporting\n */\n\nexport interface SandboxResult {\n ok: boolean;\n result?: unknown;\n error?: Error;\n timedOut?: boolean;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5000;\n\n/**\n * Execute `fn` with a timeout. If it exceeds `timeoutMs`, the promise\n * rejects with an error whose `timedOut` flag is set.\n */\nexport async function sandboxed(\n fn: () => Promise<unknown>,\n timeoutMs = DEFAULT_TIMEOUT_MS\n): Promise<SandboxResult> {\n let timedOut = false;\n let timeoutHandle: ReturnType<typeof setTimeout> | undefined;\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutHandle = setTimeout(() => {\n timedOut = true;\n reject(new Error(`LSE execution timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n });\n\n try {\n const result = await Promise.race([fn(), timeoutPromise]);\n clearTimeout(timeoutHandle);\n return { ok: true, result };\n } catch (err) {\n clearTimeout(timeoutHandle);\n const error = err instanceof Error ? err : new Error(String(err));\n return { ok: false, error, timedOut };\n }\n}\n","/**\n * <lse-intent> Custom Element\n *\n * Accepts LSE protocol JSON via an inline <script type=\"application/lse+json\">\n * child (or a `src` attribute), validates it, and executes it via the hyperfixi\n * runtime according to a declarative trigger model. Degrades gracefully when\n * the runtime is unavailable.\n *\n * ## Trigger modes\n *\n * The `trigger` attribute controls when validation + execution fires:\n *\n * - `load` (default) — fire immediately on `connectedCallback`\n * - `click` — fire on click anywhere inside the element\n * - `submit` — fire on submit of the closest ancestor `<form>`; preventDefault is called synchronously\n * - `intersect` — fire when the element scrolls into view (IntersectionObserver; one-shot)\n * - `manual` — never fire automatically; caller must invoke `.refresh()`\n * - (any other value) — treated as a DOM event name, wired via addEventListener on the element\n *\n * If no `trigger` attribute is set, the element inspects the JSON wire-format\n * `trigger.event` sugar field (protocol/spec/wire-format.md lines 441–474) and\n * uses it as the event name. If neither is present, defaults to `load`.\n *\n * @example\n * ```html\n * <!-- Load on connect (default) -->\n * <lse-intent>\n * <script type=\"application/lse+json\">\n * {\"action\":\"toggle\",\"roles\":{\"patient\":{\"type\":\"selector\",\"value\":\".active\"}}}\n * </script>\n * </lse-intent>\n *\n * <!-- Fire on click -->\n * <lse-intent trigger=\"click\">\n * <button slot=\"trigger\">Toggle</button>\n * <script type=\"application/lse+json\">\n * {\"action\":\"toggle\",\"roles\":{\"patient\":{\"type\":\"selector\",\"value\":\".active\"}}}\n * </script>\n * </lse-intent>\n *\n * <!-- Auto-wire from JSON trigger.event sugar -->\n * <lse-intent>\n * <button>Toggle</button>\n * <script type=\"application/lse+json\">\n * {\"action\":\"toggle\",\"roles\":{\"patient\":{\"type\":\"selector\",\"value\":\".active\"}},\"trigger\":{\"event\":\"click\"}}\n * </script>\n * </lse-intent>\n *\n * <!-- Manual (caller drives refresh) -->\n * <lse-intent trigger=\"manual\" id=\"my-intent\">...</lse-intent>\n * <script>document.getElementById('my-intent').refresh()</script>\n *\n * <!-- Fetch from URL, fire on intersect -->\n * <lse-intent src=\"/intents/toggle-sidebar.json\" trigger=\"intersect\"></lse-intent>\n * ```\n *\n * Events dispatched on the element:\n * - `lse:validated` — after schema check, before execution (detail: { node, diagnostics })\n * - `lse:executed` — after successful execution (detail: { node, result })\n * - `lse:error` — on validation or execution failure (detail: { diagnostics, error })\n */\n\nimport { fromProtocolJSON, validateProtocolJSON } from '@lokascript/intent';\nimport type { SemanticNode, IRDiagnostic, CommandSchema, SemanticValue } from '@lokascript/intent';\nimport { intentRegistry } from './schema-registry.js';\nimport { sandboxed } from './sandbox.js';\n\n// ---------------------------------------------------------------------------\n// Runtime bridge — resolved lazily so the element works without @hyperfixi/core\n// ---------------------------------------------------------------------------\n\ninterface HyperfiziRuntime {\n evalLSENode(node: SemanticNode, element?: Element): Promise<unknown>;\n}\n\nfunction getRuntime(): HyperfiziRuntime | null {\n const w = globalThis as Record<string, unknown>;\n const api = w['hyperfixi'];\n if (api && typeof (api as Record<string, unknown>)['evalLSENode'] === 'function') {\n return api as HyperfiziRuntime;\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Trigger resolution\n// ---------------------------------------------------------------------------\n\ntype TriggerSpec =\n | { kind: 'load' }\n | { kind: 'manual' }\n | { kind: 'intersect' }\n | { kind: 'submit' }\n | { kind: 'event'; eventName: string };\n\nfunction parseTriggerValue(value: string): TriggerSpec {\n const v = value.trim().toLowerCase();\n if (v === '' || v === 'load') return { kind: 'load' };\n if (v === 'manual') return { kind: 'manual' };\n if (v === 'intersect') return { kind: 'intersect' };\n if (v === 'submit') return { kind: 'submit' };\n return { kind: 'event', eventName: v };\n}\n\n// ---------------------------------------------------------------------------\n// <lse-intent>\n// ---------------------------------------------------------------------------\n\ninterface PreparedNode {\n node: SemanticNode;\n raw: Record<string, unknown>;\n}\n\nexport class LSEIntentElement extends HTMLElement {\n static observedAttributes = ['src', 'disabled', 'timeout', 'trigger'];\n\n private _node: SemanticNode | null = null;\n private _diagnostics: IRDiagnostic[] = [];\n private _abortController: AbortController | null = null;\n private _triggerCleanup: (() => void) | null = null;\n private _initInFlight = false;\n private _initPending = false;\n\n // ── Lifecycle ──────────────────────────────────────────────────────────────\n\n connectedCallback(): void {\n void this._initialize();\n }\n\n disconnectedCallback(): void {\n this._abortController?.abort();\n this._abortController = null;\n this._tearDownTrigger();\n }\n\n attributeChangedCallback(name: string, _old: string | null, _new: string | null): void {\n if (name !== 'src' && name !== 'disabled' && name !== 'trigger') return;\n // Only react to attribute changes when connected. Before connect, the\n // connectedCallback will run _initialize once with the final attribute\n // state. This avoids races between setAttribute() calls that happen\n // before the element is appended to the document.\n if (!this.isConnected) return;\n void this._initialize();\n }\n\n // ── Public accessors ───────────────────────────────────────────────────────\n\n /** The parsed SemanticNode, or null if not yet loaded / invalid. */\n get node(): SemanticNode | null {\n return this._node;\n }\n\n /** Diagnostics from the last validation attempt. Returns a snapshot copy. */\n get diagnostics(): readonly IRDiagnostic[] {\n return [...this._diagnostics];\n }\n\n /**\n * Re-run preparation and execution explicitly. Useful after updating the\n * inline JSON, and required when `trigger=\"manual\"`. Always executes\n * regardless of trigger mode.\n */\n async refresh(): Promise<void> {\n const prepared = await this._prepare();\n if (!prepared) return;\n await this._execute(prepared.node);\n }\n\n // ── Private: initialization and trigger dispatch ───────────────────────────\n\n /**\n * Serialize concurrent `_initialize` calls. If one is already running,\n * mark a re-init as pending and return. The in-flight init will re-run\n * once after it finishes so the final attribute/JSON state is reflected.\n */\n private async _initialize(): Promise<void> {\n if (this._initInFlight) {\n this._initPending = true;\n return;\n }\n this._initInFlight = true;\n try {\n await this._doInitialize();\n while (this._initPending) {\n this._initPending = false;\n await this._doInitialize();\n }\n } finally {\n this._initInFlight = false;\n }\n }\n\n private async _doInitialize(): Promise<void> {\n // Always tear down any existing trigger wiring before re-initializing.\n this._tearDownTrigger();\n\n if (this.hasAttribute('disabled')) return;\n\n const prepared = await this._prepare();\n if (!prepared) return;\n\n // The element may have been disconnected while _prepare() was awaiting\n // a fetch or JSON parse. If so, don't wire triggers — disconnectedCallback\n // already ran cleanup.\n if (!this.isConnected) return;\n\n const spec = this._resolveTriggerSpec(prepared.raw);\n\n switch (spec.kind) {\n case 'load':\n await this._execute(prepared.node);\n return;\n case 'manual':\n return;\n case 'intersect':\n this._wireIntersect(prepared.node);\n return;\n case 'submit':\n this._wireSubmit(prepared.node);\n return;\n case 'event':\n this._wireEvent(prepared.node, spec.eventName);\n return;\n }\n }\n\n private _resolveTriggerSpec(raw: Record<string, unknown>): TriggerSpec {\n // 1. Explicit trigger attribute wins.\n const attr = this.getAttribute('trigger');\n if (attr !== null) {\n return parseTriggerValue(attr);\n }\n\n // 2. Wire-format trigger.event sugar in the JSON.\n const triggerObj = raw['trigger'];\n if (triggerObj && typeof triggerObj === 'object' && !Array.isArray(triggerObj)) {\n const eventName = (triggerObj as Record<string, unknown>)['event'];\n if (typeof eventName === 'string' && eventName.length > 0) {\n return parseTriggerValue(eventName);\n }\n }\n\n // 3. Default.\n return { kind: 'load' };\n }\n\n // ── Private: trigger wiring ────────────────────────────────────────────────\n\n private _tearDownTrigger(): void {\n if (this._triggerCleanup) {\n this._triggerCleanup();\n this._triggerCleanup = null;\n }\n }\n\n private _wireEvent(node: SemanticNode, eventName: string): void {\n const handler = (_ev: Event): void => {\n void this._execute(node);\n };\n this.addEventListener(eventName, handler);\n this._triggerCleanup = () => this.removeEventListener(eventName, handler);\n }\n\n private _wireSubmit(node: SemanticNode): void {\n const form = this.closest('form');\n if (!form) {\n this._diagnostics.push({\n severity: 'warning',\n code: 'NO_ANCESTOR_FORM',\n message: 'trigger=\"submit\" requires an ancestor <form>; none found.',\n });\n return;\n }\n const handler = (ev: Event): void => {\n // preventDefault synchronously so the form does not navigate while\n // execution is in flight. If execution fails, the diagnostics event\n // still fires but the form stays on the page.\n ev.preventDefault();\n void this._execute(node);\n };\n form.addEventListener('submit', handler);\n this._triggerCleanup = () => form.removeEventListener('submit', handler);\n }\n\n private _wireIntersect(node: SemanticNode): void {\n if (typeof IntersectionObserver === 'undefined') {\n this._diagnostics.push({\n severity: 'warning',\n code: 'NO_INTERSECTION_OBSERVER',\n message:\n 'trigger=\"intersect\" requires IntersectionObserver; not available in this environment.',\n });\n return;\n }\n const observer = new IntersectionObserver(entries => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n // One-shot: disconnect after first intersection.\n observer.disconnect();\n void this._execute(node);\n return;\n }\n }\n });\n observer.observe(this);\n this._triggerCleanup = () => observer.disconnect();\n }\n\n // ── Private: prepare + execute ─────────────────────────────────────────────\n\n /**\n * Read the JSON, validate, deserialize, and emit `lse:validated`. Returns\n * the prepared node and raw JSON on success, or null on failure (in which\n * case `lse:error` has already been emitted).\n */\n private async _prepare(): Promise<PreparedNode | null> {\n const raw = await this._readJSON();\n if (raw === null) return null;\n\n // Validate wire format\n const wireDiags = validateProtocolJSON(raw);\n const hasErrors = wireDiags.some(d => d.severity === 'error');\n\n if (hasErrors) {\n this._diagnostics = wireDiags;\n this._node = null;\n this._showError();\n this._emit('lse:error', { diagnostics: wireDiags, error: null });\n return null;\n }\n\n // Deserialize to SemanticNode\n let node: SemanticNode;\n try {\n node = fromProtocolJSON(raw as unknown as Parameters<typeof fromProtocolJSON>[0]);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n this._diagnostics = [\n { severity: 'error', code: 'DESERIALIZE_ERROR', message: error.message },\n ];\n this._node = null;\n this._showError();\n this._emit('lse:error', { diagnostics: this._diagnostics, error });\n return null;\n }\n\n // Optional schema validation\n const schema = intentRegistry.get(node.action);\n const schemaDiags: IRDiagnostic[] = schema ? this._validateSchema(node, schema) : [];\n\n this._node = node;\n this._diagnostics = [...wireDiags, ...schemaDiags];\n this._emit('lse:validated', { node, diagnostics: [...this._diagnostics] });\n\n return { node, raw };\n }\n\n /**\n * Execute a prepared node via the hyperfixi runtime. Emits `lse:executed`\n * on success or `lse:error` on failure. Safe to call multiple times.\n *\n * **Event-handler unwrap.** If the prepared node is an event-handler (from\n * either verbose wire format or compact `trigger` sugar), the element's\n * own `trigger` attribute has already wired the DOM event listener — the\n * wire-format event metadata is redundant at this point. We unwrap the\n * event-handler and execute each body command directly, rather than passing\n * the event-handler node to `evalLSENode` (which would attempt to re-wire\n * a listener at runtime, effectively discarding the body).\n */\n private async _execute(node: SemanticNode): Promise<void> {\n const runtime = getRuntime();\n if (!runtime) {\n // Runtime not loaded — emit a warning but don't error.\n this._diagnostics.push({\n severity: 'warning',\n code: 'NO_RUNTIME',\n message:\n 'hyperfixi runtime not found. Load hyperfixi.js before intent-element.iife.js to enable execution.',\n });\n return;\n }\n\n // Unwrap event-handler nodes into their body commands. See the comment\n // above and examples/llm-native-todo-demo/README.md for the motivation.\n const executables: SemanticNode[] =\n node.kind === 'event-handler'\n ? [...((node as { body?: SemanticNode[] }).body ?? [])]\n : [node];\n\n const rawTimeout = this.getAttribute('timeout');\n const timeoutMs = rawTimeout !== null ? parseInt(rawTimeout, 10) || 5000 : 5000;\n\n // Execute body commands sequentially. A single failure stops the sequence\n // and emits `lse:error`; all successful commands before the failure have\n // already mutated the DOM.\n const results: unknown[] = [];\n for (const cmd of executables) {\n const result = await sandboxed(() => runtime.evalLSENode(cmd, this), timeoutMs);\n if (result.ok) {\n results.push(result.result);\n } else {\n this._diagnostics.push({\n severity: 'error',\n code: result.timedOut ? 'EXECUTION_TIMEOUT' : 'EXECUTION_ERROR',\n message: result.error?.message ?? 'Unknown execution error',\n });\n this._showError();\n this._emit('lse:error', { diagnostics: [...this._diagnostics], error: result.error });\n return;\n }\n }\n\n this._emit('lse:executed', {\n node,\n // For single-body cases keep the legacy `result` shape (first/only value);\n // for multi-body cases expose the full array via `results` too.\n result: results.length === 1 ? results[0] : results,\n results,\n });\n }\n\n private async _readJSON(): Promise<Record<string, unknown> | null> {\n // 1. src attribute — fetch remote JSON\n const src = this.getAttribute('src');\n if (src) {\n this._abortController?.abort();\n this._abortController = new AbortController();\n try {\n const response = await fetch(src, { signal: this._abortController.signal });\n if (!response.ok) {\n this._emitFetchError(src, response.status);\n return null;\n }\n return (await response.json()) as Record<string, unknown>;\n } catch (err) {\n // AbortError means the element was disconnected — silently ignore\n if (err instanceof Error && err.name === 'AbortError') return null;\n this._emitFetchError(src, 0, err instanceof Error ? err : undefined);\n return null;\n }\n }\n\n // 2. Inline <script type=\"application/lse+json\"> child\n const script = this.querySelector('script[type=\"application/lse+json\"]');\n if (script) {\n try {\n return JSON.parse(script.textContent ?? '') as Record<string, unknown>;\n } catch {\n const diag: IRDiagnostic = {\n severity: 'error',\n code: 'INVALID_JSON',\n message: 'Inline JSON is not valid JSON',\n };\n this._diagnostics = [diag];\n this._showError();\n this._emit('lse:error', { diagnostics: [diag], error: null });\n return null;\n }\n }\n\n return null;\n }\n\n private _validateSchema(node: SemanticNode, schema: CommandSchema): IRDiagnostic[] {\n const diags: IRDiagnostic[] = [];\n for (const roleSpec of schema.roles) {\n const value = node.roles.get(roleSpec.role) as SemanticValue | undefined;\n\n if (roleSpec.required && !value) {\n diags.push({\n severity: 'error',\n code: 'MISSING_REQUIRED_ROLE',\n message: `Required role \"${roleSpec.role}\" is missing for command \"${node.action}\"`,\n });\n continue;\n }\n\n if (value && roleSpec.expectedTypes && roleSpec.expectedTypes.length > 0) {\n if (\n !roleSpec.expectedTypes.includes(value.type as (typeof roleSpec.expectedTypes)[number])\n ) {\n diags.push({\n severity: 'warning',\n code: 'UNEXPECTED_ROLE_TYPE',\n message: `Role \"${roleSpec.role}\" has type \"${value.type}\" but expected one of: ${roleSpec.expectedTypes.join(', ')}`,\n });\n }\n }\n }\n return diags;\n }\n\n private _showError(): void {\n const errorSlot = this.querySelector('[slot=\"error\"]');\n if (errorSlot instanceof HTMLElement) {\n errorSlot.style.display = '';\n }\n }\n\n private _emit(type: string, detail: Record<string, unknown>): void {\n this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));\n }\n\n private _emitFetchError(src: string, status: number, error?: Error): void {\n const message = status\n ? `Failed to fetch ${src}: HTTP ${status}`\n : `Failed to fetch ${src}${error ? ': ' + error.message : ''}`;\n const diag: IRDiagnostic = { severity: 'error', code: 'FETCH_ERROR', message };\n this._diagnostics = [diag];\n this._showError();\n this._emit('lse:error', { diagnostics: [diag], error: error ?? null });\n }\n}\n","/**\n * @hyperfixi/intent-element\n *\n * Browser custom element that validates LSE protocol JSON and executes it\n * via the hyperfixi runtime. Zero-dependency validation via @lokascript/intent;\n * execution delegated to window.hyperfixi.evalLSENode (peer dep).\n *\n * Auto-registers <lse-intent> when loaded as a browser script.\n *\n * @example\n * ```html\n * <script src=\"hyperfixi.js\"></script>\n * <script src=\"intent-element.iife.global.js\"></script>\n *\n * <lse-intent>\n * <script type=\"application/lse+json\">\n * {\"action\":\"toggle\",\"roles\":{\"patient\":{\"type\":\"selector\",\"value\":\".active\"}},\"trigger\":{\"event\":\"click\"}}\n * </script>\n * <button slot=\"trigger\">Toggle sidebar</button>\n * </lse-intent>\n * ```\n */\n\nexport { LSEIntentElement } from './lse-intent.js';\nexport { intentRegistry } from './schema-registry.js';\nexport type { SandboxResult } from './sandbox.js';\n\n// Auto-register the custom element when loaded in a browser context\nif (typeof customElements !== 'undefined' && !customElements.get('lse-intent')) {\n // Dynamic import to avoid circular reference from the export above\n import('./lse-intent.js').then(({ LSEIntentElement }) => {\n customElements.define('lse-intent', LSEIntentElement);\n });\n}\n"],"mappings":";;;;;;;;;;;AAAA,IASM,sBAmEO;AA5Eb;AAAA;AAAA;AASA,IAAM,uBAAN,MAA2B;AAAA,MAA3B;AACE,aAAQ,UAAU,oBAAI,IAA2B;AAAA;AAAA;AAAA,MAGjD,SAAS,QAA6B;AACpC,aAAK,QAAQ,IAAI,OAAO,QAAQ,MAAM;AAAA,MACxC;AAAA;AAAA,MAGA,YAAY,SAAgC;AAC1C,mBAAW,UAAU,QAAS,MAAK,SAAS,MAAM;AAAA,MACpD;AAAA;AAAA,MAGA,IAAI,QAA2C;AAC7C,eAAO,KAAK,QAAQ,IAAI,MAAM;AAAA,MAChC;AAAA;AAAA,MAGA,IAAI,QAAyB;AAC3B,eAAO,KAAK,QAAQ,IAAI,MAAM;AAAA,MAChC;AAAA;AAAA,MAGA,WAAW,QAAsB;AAC/B,aAAK,QAAQ,OAAO,MAAM;AAAA,MAC5B;AAAA;AAAA,MAGA,QAAc;AACZ,aAAK,QAAQ,MAAM;AAAA,MACrB;AAAA;AAAA,MAGA,IAAI,OAAe;AACjB,eAAO,KAAK,QAAQ;AAAA,MACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,MAAM,SAAS,KAA4B;AACzC,cAAM,WAAW,MAAM,MAAM,GAAG;AAChC,YAAI,CAAC,SAAS,IAAI;AAChB,gBAAM,IAAI;AAAA,YACR,+BAA+B,GAAG,KAAK,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,UAC/E;AAAA,QACF;AACA,cAAM,OAAgB,MAAM,SAAS,KAAK;AAC1C,YAAI,CAAC,MAAM,QAAQ,IAAI,GAAG;AACxB,gBAAM,IAAI,MAAM,kCAAkC,GAAG,SAAS,OAAO,IAAI,EAAE;AAAA,QAC7E;AACA,iBAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,gBAAM,IAAI,KAAK,CAAC;AAChB,cAAI,OAAO,GAAG,WAAW,YAAY,CAAC,MAAM,QAAQ,GAAG,KAAK,GAAG;AAC7D,kBAAM,IAAI;AAAA,cACR,mBAAmB,CAAC,SAAS,GAAG;AAAA,YAClC;AAAA,UACF;AAAA,QACF;AACA,aAAK,YAAY,IAAuB;AAAA,MAC1C;AAAA,IACF;AAGO,IAAM,iBAAiB,IAAI,qBAAqB;AAAA;AAAA;;;ACtDvD,eAAsB,UACpB,IACA,YAAY,oBACY;AACxB,MAAI,WAAW;AACf,MAAI;AAEJ,QAAM,iBAAiB,IAAI,QAAe,CAAC,GAAG,WAAW;AACvD,oBAAgB,WAAW,MAAM;AAC/B,iBAAW;AACX,aAAO,IAAI,MAAM,iCAAiC,SAAS,IAAI,CAAC;AAAA,IAClE,GAAG,SAAS;AAAA,EACd,CAAC;AAED,MAAI;AACF,UAAM,SAAS,MAAM,QAAQ,KAAK,CAAC,GAAG,GAAG,cAAc,CAAC;AACxD,iBAAa,aAAa;AAC1B,WAAO,EAAE,IAAI,MAAM,OAAO;AAAA,EAC5B,SAAS,KAAK;AACZ,iBAAa,aAAa;AAC1B,UAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAChE,WAAO,EAAE,IAAI,OAAO,OAAO,SAAS;AAAA,EACtC;AACF;AA7CA,IAgBM;AAhBN;AAAA;AAAA;AAgBA,IAAM,qBAAqB;AAAA;AAAA;;;AChB3B;AAAA;AAAA;AAAA;AA8DA,SAAS,kBAAkB,4BAA4B;AAavD,SAAS,aAAsC;AAC7C,QAAM,IAAI;AACV,QAAM,MAAM,EAAE,WAAW;AACzB,MAAI,OAAO,OAAQ,IAAgC,aAAa,MAAM,YAAY;AAChF,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAaA,SAAS,kBAAkB,OAA4B;AACrD,QAAM,IAAI,MAAM,KAAK,EAAE,YAAY;AACnC,MAAI,MAAM,MAAM,MAAM,OAAQ,QAAO,EAAE,MAAM,OAAO;AACpD,MAAI,MAAM,SAAU,QAAO,EAAE,MAAM,SAAS;AAC5C,MAAI,MAAM,YAAa,QAAO,EAAE,MAAM,YAAY;AAClD,MAAI,MAAM,SAAU,QAAO,EAAE,MAAM,SAAS;AAC5C,SAAO,EAAE,MAAM,SAAS,WAAW,EAAE;AACvC;AAtGA,IAiHa;AAjHb;AAAA;AAAA;AAgEA;AACA;AAgDO,IAAM,mBAAN,cAA+B,YAAY;AAAA,MAA3C;AAAA;AAGL,aAAQ,QAA6B;AACrC,aAAQ,eAA+B,CAAC;AACxC,aAAQ,mBAA2C;AACnD,aAAQ,kBAAuC;AAC/C,aAAQ,gBAAgB;AACxB,aAAQ,eAAe;AAAA;AAAA;AAAA,MAIvB,oBAA0B;AACxB,aAAK,KAAK,YAAY;AAAA,MACxB;AAAA,MAEA,uBAA6B;AAC3B,aAAK,kBAAkB,MAAM;AAC7B,aAAK,mBAAmB;AACxB,aAAK,iBAAiB;AAAA,MACxB;AAAA,MAEA,yBAAyB,MAAc,MAAqB,MAA2B;AACrF,YAAI,SAAS,SAAS,SAAS,cAAc,SAAS,UAAW;AAKjE,YAAI,CAAC,KAAK,YAAa;AACvB,aAAK,KAAK,YAAY;AAAA,MACxB;AAAA;AAAA;AAAA,MAKA,IAAI,OAA4B;AAC9B,eAAO,KAAK;AAAA,MACd;AAAA;AAAA,MAGA,IAAI,cAAuC;AACzC,eAAO,CAAC,GAAG,KAAK,YAAY;AAAA,MAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,MAAM,UAAyB;AAC7B,cAAM,WAAW,MAAM,KAAK,SAAS;AACrC,YAAI,CAAC,SAAU;AACf,cAAM,KAAK,SAAS,SAAS,IAAI;AAAA,MACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASA,MAAc,cAA6B;AACzC,YAAI,KAAK,eAAe;AACtB,eAAK,eAAe;AACpB;AAAA,QACF;AACA,aAAK,gBAAgB;AACrB,YAAI;AACF,gBAAM,KAAK,cAAc;AACzB,iBAAO,KAAK,cAAc;AACxB,iBAAK,eAAe;AACpB,kBAAM,KAAK,cAAc;AAAA,UAC3B;AAAA,QACF,UAAE;AACA,eAAK,gBAAgB;AAAA,QACvB;AAAA,MACF;AAAA,MAEA,MAAc,gBAA+B;AAE3C,aAAK,iBAAiB;AAEtB,YAAI,KAAK,aAAa,UAAU,EAAG;AAEnC,cAAM,WAAW,MAAM,KAAK,SAAS;AACrC,YAAI,CAAC,SAAU;AAKf,YAAI,CAAC,KAAK,YAAa;AAEvB,cAAM,OAAO,KAAK,oBAAoB,SAAS,GAAG;AAElD,gBAAQ,KAAK,MAAM;AAAA,UACjB,KAAK;AACH,kBAAM,KAAK,SAAS,SAAS,IAAI;AACjC;AAAA,UACF,KAAK;AACH;AAAA,UACF,KAAK;AACH,iBAAK,eAAe,SAAS,IAAI;AACjC;AAAA,UACF,KAAK;AACH,iBAAK,YAAY,SAAS,IAAI;AAC9B;AAAA,UACF,KAAK;AACH,iBAAK,WAAW,SAAS,MAAM,KAAK,SAAS;AAC7C;AAAA,QACJ;AAAA,MACF;AAAA,MAEQ,oBAAoB,KAA2C;AAErE,cAAM,OAAO,KAAK,aAAa,SAAS;AACxC,YAAI,SAAS,MAAM;AACjB,iBAAO,kBAAkB,IAAI;AAAA,QAC/B;AAGA,cAAM,aAAa,IAAI,SAAS;AAChC,YAAI,cAAc,OAAO,eAAe,YAAY,CAAC,MAAM,QAAQ,UAAU,GAAG;AAC9E,gBAAM,YAAa,WAAuC,OAAO;AACjE,cAAI,OAAO,cAAc,YAAY,UAAU,SAAS,GAAG;AACzD,mBAAO,kBAAkB,SAAS;AAAA,UACpC;AAAA,QACF;AAGA,eAAO,EAAE,MAAM,OAAO;AAAA,MACxB;AAAA;AAAA,MAIQ,mBAAyB;AAC/B,YAAI,KAAK,iBAAiB;AACxB,eAAK,gBAAgB;AACrB,eAAK,kBAAkB;AAAA,QACzB;AAAA,MACF;AAAA,MAEQ,WAAW,MAAoB,WAAyB;AAC9D,cAAM,UAAU,CAAC,QAAqB;AACpC,eAAK,KAAK,SAAS,IAAI;AAAA,QACzB;AACA,aAAK,iBAAiB,WAAW,OAAO;AACxC,aAAK,kBAAkB,MAAM,KAAK,oBAAoB,WAAW,OAAO;AAAA,MAC1E;AAAA,MAEQ,YAAY,MAA0B;AAC5C,cAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,YAAI,CAAC,MAAM;AACT,eAAK,aAAa,KAAK;AAAA,YACrB,UAAU;AAAA,YACV,MAAM;AAAA,YACN,SAAS;AAAA,UACX,CAAC;AACD;AAAA,QACF;AACA,cAAM,UAAU,CAAC,OAAoB;AAInC,aAAG,eAAe;AAClB,eAAK,KAAK,SAAS,IAAI;AAAA,QACzB;AACA,aAAK,iBAAiB,UAAU,OAAO;AACvC,aAAK,kBAAkB,MAAM,KAAK,oBAAoB,UAAU,OAAO;AAAA,MACzE;AAAA,MAEQ,eAAe,MAA0B;AAC/C,YAAI,OAAO,yBAAyB,aAAa;AAC/C,eAAK,aAAa,KAAK;AAAA,YACrB,UAAU;AAAA,YACV,MAAM;AAAA,YACN,SACE;AAAA,UACJ,CAAC;AACD;AAAA,QACF;AACA,cAAM,WAAW,IAAI,qBAAqB,aAAW;AACnD,qBAAW,SAAS,SAAS;AAC3B,gBAAI,MAAM,gBAAgB;AAExB,uBAAS,WAAW;AACpB,mBAAK,KAAK,SAAS,IAAI;AACvB;AAAA,YACF;AAAA,UACF;AAAA,QACF,CAAC;AACD,iBAAS,QAAQ,IAAI;AACrB,aAAK,kBAAkB,MAAM,SAAS,WAAW;AAAA,MACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASA,MAAc,WAAyC;AACrD,cAAM,MAAM,MAAM,KAAK,UAAU;AACjC,YAAI,QAAQ,KAAM,QAAO;AAGzB,cAAM,YAAY,qBAAqB,GAAG;AAC1C,cAAM,YAAY,UAAU,KAAK,OAAK,EAAE,aAAa,OAAO;AAE5D,YAAI,WAAW;AACb,eAAK,eAAe;AACpB,eAAK,QAAQ;AACb,eAAK,WAAW;AAChB,eAAK,MAAM,aAAa,EAAE,aAAa,WAAW,OAAO,KAAK,CAAC;AAC/D,iBAAO;AAAA,QACT;AAGA,YAAI;AACJ,YAAI;AACF,iBAAO,iBAAiB,GAAwD;AAAA,QAClF,SAAS,KAAK;AACZ,gBAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAChE,eAAK,eAAe;AAAA,YAClB,EAAE,UAAU,SAAS,MAAM,qBAAqB,SAAS,MAAM,QAAQ;AAAA,UACzE;AACA,eAAK,QAAQ;AACb,eAAK,WAAW;AAChB,eAAK,MAAM,aAAa,EAAE,aAAa,KAAK,cAAc,MAAM,CAAC;AACjE,iBAAO;AAAA,QACT;AAGA,cAAM,SAAS,eAAe,IAAI,KAAK,MAAM;AAC7C,cAAM,cAA8B,SAAS,KAAK,gBAAgB,MAAM,MAAM,IAAI,CAAC;AAEnF,aAAK,QAAQ;AACb,aAAK,eAAe,CAAC,GAAG,WAAW,GAAG,WAAW;AACjD,aAAK,MAAM,iBAAiB,EAAE,MAAM,aAAa,CAAC,GAAG,KAAK,YAAY,EAAE,CAAC;AAEzE,eAAO,EAAE,MAAM,IAAI;AAAA,MACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAcA,MAAc,SAAS,MAAmC;AACxD,cAAM,UAAU,WAAW;AAC3B,YAAI,CAAC,SAAS;AAEZ,eAAK,aAAa,KAAK;AAAA,YACrB,UAAU;AAAA,YACV,MAAM;AAAA,YACN,SACE;AAAA,UACJ,CAAC;AACD;AAAA,QACF;AAIA,cAAM,cACJ,KAAK,SAAS,kBACV,CAAC,GAAK,KAAmC,QAAQ,CAAC,CAAE,IACpD,CAAC,IAAI;AAEX,cAAM,aAAa,KAAK,aAAa,SAAS;AAC9C,cAAM,YAAY,eAAe,OAAO,SAAS,YAAY,EAAE,KAAK,MAAO;AAK3E,cAAM,UAAqB,CAAC;AAC5B,mBAAW,OAAO,aAAa;AAC7B,gBAAM,SAAS,MAAM,UAAU,MAAM,QAAQ,YAAY,KAAK,IAAI,GAAG,SAAS;AAC9E,cAAI,OAAO,IAAI;AACb,oBAAQ,KAAK,OAAO,MAAM;AAAA,UAC5B,OAAO;AACL,iBAAK,aAAa,KAAK;AAAA,cACrB,UAAU;AAAA,cACV,MAAM,OAAO,WAAW,sBAAsB;AAAA,cAC9C,SAAS,OAAO,OAAO,WAAW;AAAA,YACpC,CAAC;AACD,iBAAK,WAAW;AAChB,iBAAK,MAAM,aAAa,EAAE,aAAa,CAAC,GAAG,KAAK,YAAY,GAAG,OAAO,OAAO,MAAM,CAAC;AACpF;AAAA,UACF;AAAA,QACF;AAEA,aAAK,MAAM,gBAAgB;AAAA,UACzB;AAAA;AAAA;AAAA,UAGA,QAAQ,QAAQ,WAAW,IAAI,QAAQ,CAAC,IAAI;AAAA,UAC5C;AAAA,QACF,CAAC;AAAA,MACH;AAAA,MAEA,MAAc,YAAqD;AAEjE,cAAM,MAAM,KAAK,aAAa,KAAK;AACnC,YAAI,KAAK;AACP,eAAK,kBAAkB,MAAM;AAC7B,eAAK,mBAAmB,IAAI,gBAAgB;AAC5C,cAAI;AACF,kBAAM,WAAW,MAAM,MAAM,KAAK,EAAE,QAAQ,KAAK,iBAAiB,OAAO,CAAC;AAC1E,gBAAI,CAAC,SAAS,IAAI;AAChB,mBAAK,gBAAgB,KAAK,SAAS,MAAM;AACzC,qBAAO;AAAA,YACT;AACA,mBAAQ,MAAM,SAAS,KAAK;AAAA,UAC9B,SAAS,KAAK;AAEZ,gBAAI,eAAe,SAAS,IAAI,SAAS,aAAc,QAAO;AAC9D,iBAAK,gBAAgB,KAAK,GAAG,eAAe,QAAQ,MAAM,MAAS;AACnE,mBAAO;AAAA,UACT;AAAA,QACF;AAGA,cAAM,SAAS,KAAK,cAAc,qCAAqC;AACvE,YAAI,QAAQ;AACV,cAAI;AACF,mBAAO,KAAK,MAAM,OAAO,eAAe,EAAE;AAAA,UAC5C,QAAQ;AACN,kBAAM,OAAqB;AAAA,cACzB,UAAU;AAAA,cACV,MAAM;AAAA,cACN,SAAS;AAAA,YACX;AACA,iBAAK,eAAe,CAAC,IAAI;AACzB,iBAAK,WAAW;AAChB,iBAAK,MAAM,aAAa,EAAE,aAAa,CAAC,IAAI,GAAG,OAAO,KAAK,CAAC;AAC5D,mBAAO;AAAA,UACT;AAAA,QACF;AAEA,eAAO;AAAA,MACT;AAAA,MAEQ,gBAAgB,MAAoB,QAAuC;AACjF,cAAM,QAAwB,CAAC;AAC/B,mBAAW,YAAY,OAAO,OAAO;AACnC,gBAAM,QAAQ,KAAK,MAAM,IAAI,SAAS,IAAI;AAE1C,cAAI,SAAS,YAAY,CAAC,OAAO;AAC/B,kBAAM,KAAK;AAAA,cACT,UAAU;AAAA,cACV,MAAM;AAAA,cACN,SAAS,kBAAkB,SAAS,IAAI,6BAA6B,KAAK,MAAM;AAAA,YAClF,CAAC;AACD;AAAA,UACF;AAEA,cAAI,SAAS,SAAS,iBAAiB,SAAS,cAAc,SAAS,GAAG;AACxE,gBACE,CAAC,SAAS,cAAc,SAAS,MAAM,IAA+C,GACtF;AACA,oBAAM,KAAK;AAAA,gBACT,UAAU;AAAA,gBACV,MAAM;AAAA,gBACN,SAAS,SAAS,SAAS,IAAI,eAAe,MAAM,IAAI,0BAA0B,SAAS,cAAc,KAAK,IAAI,CAAC;AAAA,cACrH,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,MAEQ,aAAmB;AACzB,cAAM,YAAY,KAAK,cAAc,gBAAgB;AACrD,YAAI,qBAAqB,aAAa;AACpC,oBAAU,MAAM,UAAU;AAAA,QAC5B;AAAA,MACF;AAAA,MAEQ,MAAM,MAAc,QAAuC;AACjE,aAAK,cAAc,IAAI,YAAY,MAAM,EAAE,QAAQ,SAAS,MAAM,UAAU,KAAK,CAAC,CAAC;AAAA,MACrF;AAAA,MAEQ,gBAAgB,KAAa,QAAgB,OAAqB;AACxE,cAAM,UAAU,SACZ,mBAAmB,GAAG,UAAU,MAAM,KACtC,mBAAmB,GAAG,GAAG,QAAQ,OAAO,MAAM,UAAU,EAAE;AAC9D,cAAM,OAAqB,EAAE,UAAU,SAAS,MAAM,eAAe,QAAQ;AAC7E,aAAK,eAAe,CAAC,IAAI;AACzB,aAAK,WAAW;AAChB,aAAK,MAAM,aAAa,EAAE,aAAa,CAAC,IAAI,GAAG,OAAO,SAAS,KAAK,CAAC;AAAA,MACvE;AAAA,IACF;AA9YE,IADW,iBACJ,qBAAqB,CAAC,OAAO,YAAY,WAAW,SAAS;AAAA;AAAA;;;AC3FtE;AACA;AAIA,IAAI,OAAO,mBAAmB,eAAe,CAAC,eAAe,IAAI,YAAY,GAAG;AAE9E,wEAA0B,KAAK,CAAC,EAAE,kBAAAA,kBAAiB,MAAM;AACvD,mBAAe,OAAO,cAAcA,iBAAgB;AAAA,EACtD,CAAC;AACH;","names":["LSEIntentElement"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ "use strict";var HyperFixiIntentElement=(()=>{var g=Object.defineProperty;var $=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var O=Object.prototype.hasOwnProperty;var h=(r,t)=>()=>(r&&(t=r(r=0)),t);var b=(r,t)=>{for(var e in t)g(r,e,{get:t[e],enumerable:!0})},L=(r,t,e,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of M(t))!O.call(r,i)&&i!==e&&g(r,i,{get:()=>t[i],enumerable:!(o=$(t,i))||o.enumerable});return r};var C=r=>L(g({},"__esModule",{value:!0}),r);function I(r,t){return t?{type:"literal",value:r,dataType:t}:{type:"literal",value:r}}function D(r,t){return t?{type:"selector",value:r,selectorKind:t}:{type:"selector",value:r}}function B(r){return{type:"reference",value:r}}function _(r){return{type:"expression",raw:r}}function P(r,t=!0){return{type:"flag",name:r,enabled:t}}function w(r,t,e){let o=t instanceof Map?t:new Map(Object.entries(t)),i={kind:"command",action:r,roles:o};return e?{...i,metadata:e}:i}function E(r,t,e,o,i){let n=t instanceof Map?t:new Map(Object.entries(t));return{kind:"event-handler",action:r,roles:n,body:e,...i&&{eventModifiers:i},...o&&{metadata:o}}}function q(r,t,e,o,i){let n=t instanceof Map?t:new Map(Object.entries(t));return{kind:"conditional",action:r,roles:n,thenBranch:e,...o&&{elseBranch:o},...i&&{metadata:i}}}function W(r,t="sequential",e){let o={kind:"compound",action:"compound",roles:new Map,statements:r,chainType:t};return e?{...o,metadata:e}:o}function F(r,t,e,o,i,n,s){let a=t instanceof Map?t:new Map(Object.entries(t));return{kind:"loop",action:r,roles:a,loopVariant:e,body:o,...i&&{loopVariable:i},...n&&{indexVariable:n},...s&&{metadata:s}}}function S(r,t,e,o){return{kind:"command",action:"try",roles:new Map,body:r,...t&&t.length>0?{catchBranch:t}:{},...e&&e.length>0?{finallyBranch:e}:{},...o?{metadata:o}:{}}}function G(r,t,e){return{kind:"command",action:r,roles:new Map,asyncVariant:r,asyncBody:t,...e?{metadata:e}:{}}}function U(r,t,e,o){return{kind:"command",action:"match",roles:r instanceof Map?r:new Map(Object.entries(r)),arms:t,...e&&e.length>0?{defaultArm:e}:{},...o?{metadata:o}:{}}}function c(r){let t=new Map(Object.entries(r.roles??{}).map(([n,s])=>[n,N(s)])),e=r.annotations?.map(n=>n.value!==void 0?{name:n.name,value:n.value}:{name:n.name}),o=r.diagnostics?.map(n=>({severity:n.level,message:n.message,...n.code?{code:n.code}:{},...n.source?{source:n.source}:{},...n.suggestions&&n.suggestions.length>0?{suggestions:n.suggestions}:{}}));if(r.trigger){let n=r.trigger.event;t.set("event",I(n,"string"));let s=new Map(t);s.delete("event");let a=w(r.action,s),u=E("on",t,[a],{sourceLanguage:"json"},r.trigger.modifiers??{});return l(u,e,o)}let i=r.kind??"command";if(i==="event-handler"){let n=(r.body??[]).map(c);return l(E(r.action,t,n),e,o)}if(i==="compound"){let n=(r.statements??[]).map(c);return l(W(n,r.chainType??"sequential"),e,o)}if(r.thenBranch&&r.thenBranch.length>0){let n=r.thenBranch.map(c),s=r.elseBranch?r.elseBranch.map(c):void 0;return l(q(r.action,t,n,s),e,o)}if(r.loopVariant){let n=(r.loopBody??[]).map(c);return l(F(r.action,t,r.loopVariant,n,r.loopVariable,r.indexVariable),e,o)}if(r.catchBranch||r.finallyBranch){let n=(r.body??[]).map(c),s=r.catchBranch?r.catchBranch.map(c):void 0,a=r.finallyBranch?r.finallyBranch.map(c):void 0;return l(S(n,s,a),e,o)}if(r.asyncVariant){let n=(r.asyncBody??[]).map(c);return l(G(r.asyncVariant,n),e,o)}if(r.arms&&r.arms.length>0){let n=r.arms.map(a=>({pattern:N(a.pattern),body:a.body.map(c)})),s=r.defaultArm?r.defaultArm.map(c):void 0;return l(U(t,n,s),e,o)}return r.body&&r.body.length>0&&i==="command"?l(S(r.body.map(c)),e,o):l(w(r.action,t),e,o)}function l(r,t,e){return(!t||t.length===0)&&(!e||e.length===0)?r:{...r,...t&&t.length>0?{annotations:t}:{},...e&&e.length>0?{diagnostics:e}:{}}}function N(r){switch(r.type){case"selector":return D(String(r.value??""),r.selectorKind);case"literal":return I(r.value!==void 0?r.value:"",r.dataType);case"reference":return B(String(r.value??""));case"expression":return _(r.raw??String(r.value??""));case"flag":return P(r.name??String(r.value??""),r.enabled??(typeof r.value=="boolean"?r.value:!0));case"property-path":return _(String(r.value??r.raw??""))}}function k(r){let t=[];if(typeof r!="object"||r===null)return[{severity:"error",code:"INVALID_ROOT",message:"Root must be a JSON object"}];let e=r,o="kind"in e?String(e.kind):"command";if("kind"in e&&!J.has(o)&&t.push({severity:"error",code:"INVALID_KIND",message:`Invalid kind "${e.kind}". Must be one of: command, event-handler, compound`}),"action"in e?(typeof e.action!="string"||e.action.length===0)&&t.push({severity:"error",code:"INVALID_ACTION",message:"action must be a non-empty string"}):t.push({severity:"error",code:"MISSING_ACTION",message:"Missing required field: action"}),!("roles"in e)&&o!=="compound")t.push({severity:"error",code:"MISSING_ROLES",message:"Missing required field: roles"});else if("roles"in e&&(typeof e.roles!="object"||e.roles===null||Array.isArray(e.roles)))t.push({severity:"error",code:"INVALID_ROLES",message:"roles must be a plain object"});else if("roles"in e&&e.roles)for(let[i,n]of Object.entries(e.roles)){if(typeof n!="object"||n===null){t.push({severity:"error",code:"INVALID_ROLE_VALUE",message:`Role "${i}" value must be an object`});continue}let s=n;(!("type"in s)||!z.has(String(s.type)))&&t.push({severity:"error",code:"INVALID_VALUE_TYPE",message:`Role "${i}" has invalid type "${s.type}"`}),s.type==="flag"&&(typeof s.name!="string"&&!("value"in s)&&t.push({severity:"error",code:"MISSING_FLAG_NAME",message:`Flag role "${i}" missing required field: name (or value)`}),typeof s.enabled!="boolean"&&!("value"in s)&&t.push({severity:"error",code:"MISSING_FLAG_ENABLED",message:`Flag role "${i}" missing required field: enabled (or value)`})),s.type==="expression"&&typeof s.raw!="string"&&!("value"in s)&&t.push({severity:"error",code:"MISSING_EXPRESSION_RAW",message:`Expression role "${i}" missing required field: raw (or value)`})}if("trigger"in e&&e.trigger){let i=e.trigger;typeof i!="object"||i===null?t.push({severity:"error",code:"INVALID_TRIGGER",message:"trigger must be an object"}):(!i.event||typeof i.event!="string")&&t.push({severity:"error",code:"INVALID_TRIGGER",message:"trigger.event is required and must be a non-empty string"})}if(o==="event-handler"&&!("trigger"in e)&&(!("body"in e)||!Array.isArray(e.body))&&t.push({severity:"error",code:"MISSING_BODY",message:"event-handler node requires a body array"}),o==="compound"&&((!("statements"in e)||!Array.isArray(e.statements))&&t.push({severity:"error",code:"MISSING_STATEMENTS",message:"compound node requires a statements array"}),"chainType"in e&&!Y.has(String(e.chainType))&&t.push({severity:"error",code:"INVALID_CHAIN_TYPE",message:`Invalid chainType "${e.chainType}"`})),"diagnostics"in e&&!Array.isArray(e.diagnostics)&&t.push({severity:"error",code:"INVALID_DIAGNOSTICS",message:"diagnostics must be an array"}),"annotations"in e)if(!Array.isArray(e.annotations))t.push({severity:"error",code:"INVALID_ANNOTATIONS",message:"annotations must be an array"});else for(let i=0;i<e.annotations.length;i++){let n=e.annotations[i];if(!n||typeof n!="object"){t.push({severity:"error",code:"INVALID_ANNOTATION_ENTRY",message:`annotations[${i}] must be an object`});continue}(typeof n.name!="string"||n.name.length===0)&&t.push({severity:"error",code:"MISSING_ANNOTATION_NAME",message:`annotations[${i}] missing required field: name`})}if("asyncVariant"in e&&!H.has(String(e.asyncVariant))&&t.push({severity:"error",code:"INVALID_ASYNC_VARIANT",message:`Invalid asyncVariant "${e.asyncVariant}". Must be "all" or "race"`}),"arms"in e)if(!Array.isArray(e.arms))t.push({severity:"error",code:"INVALID_ARMS",message:"arms must be an array"});else for(let i=0;i<e.arms.length;i++){let n=e.arms[i];if(!n||typeof n!="object"){t.push({severity:"error",code:"INVALID_ARM_ENTRY",message:`arms[${i}] must be an object`});continue}(!n.pattern||typeof n.pattern!="object")&&t.push({severity:"error",code:"MISSING_ARM_PATTERN",message:`arms[${i}] missing required field: pattern`}),Array.isArray(n.body)||t.push({severity:"error",code:"MISSING_ARM_BODY",message:`arms[${i}] missing required field: body (array)`})}if("diagnostics"in e&&Array.isArray(e.diagnostics))for(let i=0;i<e.diagnostics.length;i++){let n=e.diagnostics[i];if(!n||typeof n!="object"){t.push({severity:"error",code:"INVALID_DIAGNOSTIC_ENTRY",message:`diagnostics[${i}] must be an object`});continue}K.has(String(n.level))||t.push({severity:"error",code:"INVALID_DIAGNOSTIC_LEVEL",message:`diagnostics[${i}].level must be "error", "warning", or "info"`}),typeof n.message!="string"&&t.push({severity:"error",code:"MISSING_DIAGNOSTIC_MESSAGE",message:`diagnostics[${i}] missing required field: message`})}return t}var J,z,Y,H,K,A=h(()=>{"use strict";J=new Set(["command","event-handler","compound"]),z=new Set(["selector","literal","reference","expression","property-path","flag"]),Y=new Set(["then","and","async","sequential","pipe"]),H=new Set(["all","race"]),K=new Set(["error","warning","info"])});var p,m,y=h(()=>{"use strict";p=class{constructor(){this.schemas=new Map}register(t){this.schemas.set(t.action,t)}registerAll(t){for(let e of t)this.register(e)}get(t){return this.schemas.get(t)}has(t){return this.schemas.has(t)}unregister(t){this.schemas.delete(t)}clear(){this.schemas.clear()}get size(){return this.schemas.size}async loadFrom(t){let e=await fetch(t);if(!e.ok)throw new Error(`Failed to load schemas from ${t}: ${e.status} ${e.statusText}`);let o=await e.json();if(!Array.isArray(o))throw new Error(`Expected array of schemas from ${t}, got ${typeof o}`);for(let i=0;i<o.length;i++){let n=o[i];if(typeof n?.action!="string"||!Array.isArray(n?.roles))throw new Error(`Schema at index ${i} from ${t} is missing required fields "action" (string) and "roles" (array)`)}this.registerAll(o)}},m=new p});async function R(r,t=5e3){let e=!1,o,i=new Promise((n,s)=>{o=setTimeout(()=>{e=!0,s(new Error(`LSE execution timed out after ${t}ms`))},t)});try{let n=await Promise.race([r(),i]);return clearTimeout(o),{ok:!0,result:n}}catch(n){return clearTimeout(o),{ok:!1,error:n instanceof Error?n:new Error(String(n)),timedOut:e}}}var T=h(()=>{"use strict"});var x={};b(x,{LSEIntentElement:()=>f});function X(){let t=globalThis.hyperfixi;return t&&typeof t.evalLSENode=="function"?t:null}function V(r){let t=r.trim().toLowerCase();return t===""||t==="load"?{kind:"load"}:t==="manual"?{kind:"manual"}:t==="intersect"?{kind:"intersect"}:t==="submit"?{kind:"submit"}:{kind:"event",eventName:t}}var f,v=h(()=>{"use strict";A();y();T();f=class extends HTMLElement{constructor(){super(...arguments);this._node=null;this._diagnostics=[];this._abortController=null;this._triggerCleanup=null;this._initInFlight=!1;this._initPending=!1}connectedCallback(){this._initialize()}disconnectedCallback(){this._abortController?.abort(),this._abortController=null,this._tearDownTrigger()}attributeChangedCallback(e,o,i){e!=="src"&&e!=="disabled"&&e!=="trigger"||this.isConnected&&this._initialize()}get node(){return this._node}get diagnostics(){return[...this._diagnostics]}async refresh(){let e=await this._prepare();e&&await this._execute(e.node)}async _initialize(){if(this._initInFlight){this._initPending=!0;return}this._initInFlight=!0;try{for(await this._doInitialize();this._initPending;)this._initPending=!1,await this._doInitialize()}finally{this._initInFlight=!1}}async _doInitialize(){if(this._tearDownTrigger(),this.hasAttribute("disabled"))return;let e=await this._prepare();if(!e||!this.isConnected)return;let o=this._resolveTriggerSpec(e.raw);switch(o.kind){case"load":await this._execute(e.node);return;case"manual":return;case"intersect":this._wireIntersect(e.node);return;case"submit":this._wireSubmit(e.node);return;case"event":this._wireEvent(e.node,o.eventName);return}}_resolveTriggerSpec(e){let o=this.getAttribute("trigger");if(o!==null)return V(o);let i=e.trigger;if(i&&typeof i=="object"&&!Array.isArray(i)){let n=i.event;if(typeof n=="string"&&n.length>0)return V(n)}return{kind:"load"}}_tearDownTrigger(){this._triggerCleanup&&(this._triggerCleanup(),this._triggerCleanup=null)}_wireEvent(e,o){let i=n=>{this._execute(e)};this.addEventListener(o,i),this._triggerCleanup=()=>this.removeEventListener(o,i)}_wireSubmit(e){let o=this.closest("form");if(!o){this._diagnostics.push({severity:"warning",code:"NO_ANCESTOR_FORM",message:'trigger="submit" requires an ancestor <form>; none found.'});return}let i=n=>{n.preventDefault(),this._execute(e)};o.addEventListener("submit",i),this._triggerCleanup=()=>o.removeEventListener("submit",i)}_wireIntersect(e){if(typeof IntersectionObserver>"u"){this._diagnostics.push({severity:"warning",code:"NO_INTERSECTION_OBSERVER",message:'trigger="intersect" requires IntersectionObserver; not available in this environment.'});return}let o=new IntersectionObserver(i=>{for(let n of i)if(n.isIntersecting){o.disconnect(),this._execute(e);return}});o.observe(this),this._triggerCleanup=()=>o.disconnect()}async _prepare(){let e=await this._readJSON();if(e===null)return null;let o=k(e);if(o.some(u=>u.severity==="error"))return this._diagnostics=o,this._node=null,this._showError(),this._emit("lse:error",{diagnostics:o,error:null}),null;let n;try{n=c(e)}catch(u){let d=u instanceof Error?u:new Error(String(u));return this._diagnostics=[{severity:"error",code:"DESERIALIZE_ERROR",message:d.message}],this._node=null,this._showError(),this._emit("lse:error",{diagnostics:this._diagnostics,error:d}),null}let s=m.get(n.action),a=s?this._validateSchema(n,s):[];return this._node=n,this._diagnostics=[...o,...a],this._emit("lse:validated",{node:n,diagnostics:[...this._diagnostics]}),{node:n,raw:e}}async _execute(e){let o=X();if(!o){this._diagnostics.push({severity:"warning",code:"NO_RUNTIME",message:"hyperfixi runtime not found. Load hyperfixi.js before intent-element.iife.js to enable execution."});return}let i=e.kind==="event-handler"?[...e.body??[]]:[e],n=this.getAttribute("timeout"),s=n!==null&&parseInt(n,10)||5e3,a=[];for(let u of i){let d=await R(()=>o.evalLSENode(u,this),s);if(d.ok)a.push(d.result);else{this._diagnostics.push({severity:"error",code:d.timedOut?"EXECUTION_TIMEOUT":"EXECUTION_ERROR",message:d.error?.message??"Unknown execution error"}),this._showError(),this._emit("lse:error",{diagnostics:[...this._diagnostics],error:d.error});return}}this._emit("lse:executed",{node:e,result:a.length===1?a[0]:a,results:a})}async _readJSON(){let e=this.getAttribute("src");if(e){this._abortController?.abort(),this._abortController=new AbortController;try{let i=await fetch(e,{signal:this._abortController.signal});return i.ok?await i.json():(this._emitFetchError(e,i.status),null)}catch(i){return i instanceof Error&&i.name==="AbortError"||this._emitFetchError(e,0,i instanceof Error?i:void 0),null}}let o=this.querySelector('script[type="application/lse+json"]');if(o)try{return JSON.parse(o.textContent??"")}catch{let i={severity:"error",code:"INVALID_JSON",message:"Inline JSON is not valid JSON"};return this._diagnostics=[i],this._showError(),this._emit("lse:error",{diagnostics:[i],error:null}),null}return null}_validateSchema(e,o){let i=[];for(let n of o.roles){let s=e.roles.get(n.role);if(n.required&&!s){i.push({severity:"error",code:"MISSING_REQUIRED_ROLE",message:`Required role "${n.role}" is missing for command "${e.action}"`});continue}s&&n.expectedTypes&&n.expectedTypes.length>0&&(n.expectedTypes.includes(s.type)||i.push({severity:"warning",code:"UNEXPECTED_ROLE_TYPE",message:`Role "${n.role}" has type "${s.type}" but expected one of: ${n.expectedTypes.join(", ")}`}))}return i}_showError(){let e=this.querySelector('[slot="error"]');e instanceof HTMLElement&&(e.style.display="")}_emit(e,o){this.dispatchEvent(new CustomEvent(e,{detail:o,bubbles:!0,composed:!0}))}_emitFetchError(e,o,i){let s={severity:"error",code:"FETCH_ERROR",message:o?`Failed to fetch ${e}: HTTP ${o}`:`Failed to fetch ${e}${i?": "+i.message:""}`};this._diagnostics=[s],this._showError(),this._emit("lse:error",{diagnostics:[s],error:i??null})}};f.observedAttributes=["src","disabled","timeout","trigger"]});var Q={};b(Q,{LSEIntentElement:()=>f,intentRegistry:()=>m});v();y();typeof customElements<"u"&&!customElements.get("lse-intent")&&Promise.resolve().then(()=>(v(),x)).then(({LSEIntentElement:r})=>{customElements.define("lse-intent",r)});return C(Q);})();
2
+ //# sourceMappingURL=intent-element.iife.global.js.map