@syntrologie/runtime-sdk 2.2.0-canary.8 → 2.2.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 (45) hide show
  1. package/README.md +2 -1
  2. package/dist/actions/types.d.ts +7 -0
  3. package/dist/antiFlicker.d.ts +2 -0
  4. package/dist/apps/builtinRuntimeModules.generated.d.ts +20 -0
  5. package/dist/{chunk-MEBUEMEZ.js → chunk-V4MDQX67.js} +2868 -1417
  6. package/dist/chunk-V4MDQX67.js.map +7 -0
  7. package/dist/configFetcher.d.ts +3 -1
  8. package/dist/context/ContextManager.d.ts +4 -0
  9. package/dist/diagnostics/service-worker-check.d.ts +23 -0
  10. package/dist/editorLoader.d.ts +8 -2
  11. package/dist/index.d.ts +3 -0
  12. package/dist/index.js +1563 -12
  13. package/dist/index.js.map +4 -4
  14. package/dist/integrations/gtm-bridge.d.ts +36 -0
  15. package/dist/navigation/NavigationMonitor.d.ts +45 -0
  16. package/dist/overlays/runtime/utils/AnchorWatcher.d.ts +22 -0
  17. package/dist/overlays/types.d.ts +2 -0
  18. package/dist/react.js +1 -1
  19. package/dist/runtime.d.ts +3 -0
  20. package/dist/smart-canvas.esm.js +62 -36
  21. package/dist/smart-canvas.esm.js.map +4 -4
  22. package/dist/smart-canvas.js +15828 -23049
  23. package/dist/smart-canvas.js.map +4 -4
  24. package/dist/smart-canvas.min.js +62 -36
  25. package/dist/smart-canvas.min.js.map +4 -4
  26. package/dist/telemetry/adapters/posthog.d.ts +19 -0
  27. package/dist/telemetry/consent.d.ts +62 -0
  28. package/dist/version.d.ts +1 -1
  29. package/dist/widgets/WidgetRegistry.d.ts +10 -0
  30. package/package.json +13 -4
  31. package/schema/canvas-config.schema.json +124 -22
  32. package/scripts/syntroReactPlugin.mjs +113 -0
  33. package/dist/adaptives/adaptive-chatbot/index.js +0 -9
  34. package/dist/adaptives/adaptive-chatbot/index.js.map +0 -7
  35. package/dist/adaptives/adaptive-content/index.js +0 -22
  36. package/dist/adaptives/adaptive-content/index.js.map +0 -7
  37. package/dist/adaptives/adaptive-faq/index.js +0 -28
  38. package/dist/adaptives/adaptive-faq/index.js.map +0 -7
  39. package/dist/adaptives/adaptive-gamification/index.js +0 -2
  40. package/dist/adaptives/adaptive-gamification/index.js.map +0 -7
  41. package/dist/adaptives/adaptive-nav/index.js +0 -27
  42. package/dist/adaptives/adaptive-nav/index.js.map +0 -7
  43. package/dist/adaptives/adaptive-overlays/index.js +0 -94
  44. package/dist/adaptives/adaptive-overlays/index.js.map +0 -7
  45. package/dist/chunk-MEBUEMEZ.js.map +0 -7
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  EventBus,
10
10
  ExecutorRegistry,
11
11
  MAX_VISIBLE_TOASTS,
12
+ NavigationMonitor,
12
13
  NotificationToastStack,
13
14
  RUNTIME_VERSION,
14
15
  RuntimeProvider,
@@ -28,6 +29,7 @@ import {
28
29
  WidgetRegistry,
29
30
  appRegistry,
30
31
  applyStaticSlotStyles,
32
+ base,
31
33
  border,
32
34
  brand,
33
35
  cleanupAppContext,
@@ -73,6 +75,8 @@ import {
73
75
  normalizePostHogEvent,
74
76
  playEnterAnimation,
75
77
  playExitAnimation,
78
+ purple,
79
+ red,
76
80
  registerConfigPredicates,
77
81
  registerSmartCanvasElement,
78
82
  resolveConfigUri,
@@ -97,7 +101,7 @@ import {
97
101
  validateAction,
98
102
  validateActions,
99
103
  widgetRegistry
100
- } from "./chunk-MEBUEMEZ.js";
104
+ } from "./chunk-V4MDQX67.js";
101
105
  import {
102
106
  AddClassZ,
103
107
  BadgePositionZ,
@@ -133,33 +137,1573 @@ import {
133
137
  } from "./chunk-AYTRRBR5.js";
134
138
 
135
139
  // src/index.ts
136
- import React from "react";
140
+ import React4 from "react";
137
141
  import { createPortal as createPortal2, flushSync } from "react-dom";
138
142
  import * as ReactDOMClient from "react-dom/client";
139
143
 
144
+ // ../adaptives/adaptive-chatbot/dist/ChatAssistant.js
145
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
146
+ import React, { useEffect, useRef as useRef2 } from "react";
147
+ import { createRoot } from "react-dom/client";
148
+
149
+ // ../adaptives/adaptive-chatbot/dist/useChat.js
150
+ import { useCallback, useRef, useState } from "react";
151
+
152
+ // ../adaptives/adaptive-chatbot/dist/actionParser.js
153
+ var JSON_FENCE_RE = /```json\s*\n([\s\S]*?)```/g;
154
+ function parseActions(input) {
155
+ const actions = [];
156
+ let displayText = input;
157
+ const matches = [];
158
+ let match;
159
+ JSON_FENCE_RE.lastIndex = 0;
160
+ while ((match = JSON_FENCE_RE.exec(input)) !== null) {
161
+ matches.push({
162
+ fullMatch: match[0],
163
+ json: match[1],
164
+ index: match.index
165
+ });
166
+ }
167
+ for (let i = matches.length - 1; i >= 0; i--) {
168
+ const { fullMatch, json } = matches[i];
169
+ let parsed;
170
+ try {
171
+ parsed = JSON.parse(json);
172
+ } catch {
173
+ continue;
174
+ }
175
+ if (typeof parsed === "object" && parsed !== null && "kind" in parsed && typeof parsed.kind === "string") {
176
+ actions.unshift(parsed);
177
+ displayText = displayText.replace(fullMatch, "");
178
+ }
179
+ }
180
+ displayText = displayText.replace(/\n{3,}/g, "\n\n").trim();
181
+ return { displayText, actions };
182
+ }
183
+
184
+ // ../adaptives/adaptive-chatbot/dist/apiClient.js
185
+ var AuthError = class extends Error {
186
+ constructor(message) {
187
+ super(message);
188
+ this.name = "AuthError";
189
+ }
190
+ };
191
+ function getAuthHeaders() {
192
+ const cookieMatch = document.cookie.match(/stytch_session_jwt=([^;]*)/);
193
+ if (!cookieMatch || !cookieMatch[1]) {
194
+ throw new AuthError("No authentication token found");
195
+ }
196
+ const workspaceId = localStorage.getItem("syntrologie_workspace_id");
197
+ if (!workspaceId) {
198
+ throw new AuthError("No workspace ID found");
199
+ }
200
+ return {
201
+ Authorization: `Bearer ${cookieMatch[1]}`,
202
+ "X-Workspace-Id": workspaceId
203
+ };
204
+ }
205
+ async function sendMessage(url, request) {
206
+ const authHeaders = getAuthHeaders();
207
+ const response = await fetch(url, {
208
+ method: "POST",
209
+ headers: {
210
+ "Content-Type": "application/json",
211
+ ...authHeaders
212
+ },
213
+ body: JSON.stringify(request)
214
+ });
215
+ if (!response.ok) {
216
+ if (response.status === 401) {
217
+ throw new AuthError("Session expired or unauthorized");
218
+ }
219
+ throw new Error(`Chat request failed: ${response.status} ${response.statusText}`);
220
+ }
221
+ return response.json();
222
+ }
223
+
224
+ // ../adaptives/adaptive-chatbot/dist/useChat.js
225
+ var nextId = 0;
226
+ function generateId() {
227
+ return `msg-${Date.now()}-${++nextId}`;
228
+ }
229
+ function useChat(options) {
230
+ const { backendUrl, tileId, runtime: runtime7, greeting, maxHistory = 20, mlflowRunId, config } = options;
231
+ const [messages, setMessages] = useState(() => {
232
+ if (greeting) {
233
+ return [
234
+ {
235
+ id: generateId(),
236
+ role: "assistant",
237
+ text: greeting,
238
+ timestamp: Date.now()
239
+ }
240
+ ];
241
+ }
242
+ return [];
243
+ });
244
+ const [isLoading, setIsLoading] = useState(false);
245
+ const [error, setError] = useState(null);
246
+ const batchHandleRef = useRef(null);
247
+ const send = useCallback(async (text) => {
248
+ var _a;
249
+ const trimmed = text.trim();
250
+ if (!trimmed)
251
+ return;
252
+ setError(null);
253
+ const userMessage = {
254
+ id: generateId(),
255
+ role: "user",
256
+ text: trimmed,
257
+ timestamp: Date.now()
258
+ };
259
+ setMessages((prev) => [...prev, userMessage]);
260
+ setIsLoading(true);
261
+ try {
262
+ const currentMessages = [...messages, userMessage];
263
+ const historySlice = currentMessages.slice(-maxHistory).map((m) => ({
264
+ role: m.role,
265
+ content: m.text
266
+ }));
267
+ const response = await sendMessage(backendUrl, {
268
+ message: trimmed,
269
+ history: historySlice,
270
+ mlflow_run_id: mlflowRunId,
271
+ current_config: config
272
+ });
273
+ const { displayText, actions } = parseActions(response.response);
274
+ const assistantMessage = {
275
+ id: generateId(),
276
+ role: "assistant",
277
+ text: displayText,
278
+ timestamp: Date.now()
279
+ };
280
+ setMessages((prev) => [...prev, assistantMessage]);
281
+ if (actions.length > 0) {
282
+ if ((_a = batchHandleRef.current) == null ? void 0 : _a.isApplied()) {
283
+ await batchHandleRef.current.revertAll();
284
+ }
285
+ batchHandleRef.current = await runtime7.actions.applyBatch(actions);
286
+ runtime7.events.publish("chatbot.actions_applied", {
287
+ count: actions.length,
288
+ kinds: actions.map((a) => a.kind),
289
+ tileId
290
+ });
291
+ }
292
+ } catch (err) {
293
+ const message = err instanceof Error ? err.message : "An unexpected error occurred";
294
+ setError(message);
295
+ } finally {
296
+ setIsLoading(false);
297
+ }
298
+ }, [backendUrl, messages, maxHistory, mlflowRunId, config, runtime7, tileId]);
299
+ const clearMessages = useCallback(() => {
300
+ var _a;
301
+ setMessages([]);
302
+ setError(null);
303
+ if ((_a = batchHandleRef.current) == null ? void 0 : _a.isApplied()) {
304
+ batchHandleRef.current.revertAll();
305
+ batchHandleRef.current = null;
306
+ }
307
+ sessionStorage.removeItem(`syntro:chatbot:history:${tileId}`);
308
+ }, [tileId]);
309
+ return {
310
+ messages,
311
+ isLoading,
312
+ error,
313
+ sendMessage: send,
314
+ clearMessages
315
+ };
316
+ }
317
+
318
+ // ../adaptives/adaptive-chatbot/dist/ChatAssistant.js
319
+ var styles = {
320
+ container: {
321
+ display: "flex",
322
+ flexDirection: "column",
323
+ height: "100%",
324
+ fontFamily: "system-ui, -apple-system, sans-serif",
325
+ fontSize: "14px",
326
+ touchAction: "none"
327
+ },
328
+ messageList: {
329
+ flex: 1,
330
+ overflowY: "auto",
331
+ padding: "12px",
332
+ display: "flex",
333
+ flexDirection: "column",
334
+ gap: "8px"
335
+ },
336
+ messageBubble: {
337
+ maxWidth: "85%",
338
+ padding: "8px 12px",
339
+ borderRadius: "12px",
340
+ lineHeight: 1.5,
341
+ wordBreak: "break-word"
342
+ },
343
+ userMessage: {
344
+ alignSelf: "flex-end",
345
+ backgroundColor: purple[4],
346
+ color: base.white,
347
+ borderBottomRightRadius: "4px"
348
+ },
349
+ assistantMessage: {
350
+ alignSelf: "flex-start",
351
+ backgroundColor: "rgba(255, 255, 255, 0.08)",
352
+ color: slateGrey[10],
353
+ borderBottomLeftRadius: "4px"
354
+ },
355
+ loadingDots: {
356
+ alignSelf: "flex-start",
357
+ padding: "8px 16px",
358
+ backgroundColor: "rgba(255, 255, 255, 0.05)",
359
+ borderRadius: "12px",
360
+ color: slateGrey[8],
361
+ fontSize: "13px"
362
+ },
363
+ errorBanner: {
364
+ padding: "8px 12px",
365
+ backgroundColor: "rgba(239, 68, 68, 0.1)",
366
+ color: red[4],
367
+ fontSize: "13px",
368
+ borderRadius: "8px",
369
+ margin: "0 12px"
370
+ },
371
+ inputForm: {
372
+ display: "flex",
373
+ gap: "8px",
374
+ padding: "12px",
375
+ borderTop: "1px solid rgba(255, 255, 255, 0.06)"
376
+ },
377
+ input: {
378
+ flex: 1,
379
+ padding: "8px 12px",
380
+ borderRadius: "8px",
381
+ border: "1px solid rgba(255, 255, 255, 0.1)",
382
+ backgroundColor: "rgba(0, 0, 0, 0.2)",
383
+ color: slateGrey[12],
384
+ fontSize: "14px",
385
+ outline: "none",
386
+ fontFamily: "inherit"
387
+ },
388
+ sendButton: {
389
+ padding: "8px 16px",
390
+ borderRadius: "8px",
391
+ border: "none",
392
+ backgroundColor: purple[4],
393
+ color: base.white,
394
+ fontWeight: 600,
395
+ fontSize: "13px",
396
+ cursor: "pointer",
397
+ whiteSpace: "nowrap"
398
+ },
399
+ sendButtonDisabled: {
400
+ opacity: 0.5,
401
+ cursor: "not-allowed"
402
+ }
403
+ };
404
+ function MessageBubble({ message }) {
405
+ const isUser = message.role === "user";
406
+ const bubbleStyle = {
407
+ ...styles.messageBubble,
408
+ ...isUser ? styles.userMessage : styles.assistantMessage
409
+ };
410
+ return _jsx("div", { style: bubbleStyle, children: message.text });
411
+ }
412
+ function ChatAssistant({ config, runtime: runtime7, tileId }) {
413
+ const { messages, isLoading, error, sendMessage: sendMessage2, clearMessages: _clearMessages } = useChat({
414
+ backendUrl: config.backendUrl,
415
+ tileId,
416
+ runtime: runtime7,
417
+ greeting: config.greeting,
418
+ maxHistory: config.maxHistory,
419
+ mlflowRunId: config.mlflowRunId
420
+ });
421
+ const messageListRef = useRef2(null);
422
+ const inputRef = useRef2(null);
423
+ useEffect(() => {
424
+ if (messageListRef.current) {
425
+ messageListRef.current.scrollTop = messageListRef.current.scrollHeight;
426
+ }
427
+ }, []);
428
+ const handleSubmit = (e) => {
429
+ e.preventDefault();
430
+ const input = inputRef.current;
431
+ if (!input || !input.value.trim() || isLoading)
432
+ return;
433
+ const text = input.value;
434
+ input.value = "";
435
+ sendMessage2(text);
436
+ };
437
+ return _jsxs("div", { style: styles.container, "data-testid": "chat-assistant", children: [_jsxs("div", { ref: messageListRef, style: styles.messageList, children: [messages.map((msg) => _jsx(MessageBubble, { message: msg }, msg.id)), isLoading && _jsx("div", { style: styles.loadingDots, children: "Thinking..." })] }), error && _jsx("div", { style: styles.errorBanner, children: error }), _jsxs("form", { onSubmit: handleSubmit, style: styles.inputForm, children: [_jsx("input", { ref: inputRef, style: styles.input, placeholder: "Ask anything...", disabled: isLoading, "data-testid": "chat-input" }), _jsx("button", { type: "submit", disabled: isLoading, style: {
438
+ ...styles.sendButton,
439
+ ...isLoading ? styles.sendButtonDisabled : {}
440
+ }, "data-testid": "chat-send", children: "Send" })] })] });
441
+ }
442
+ var ChatAssistantMountableWidget = {
443
+ mount(container, mountConfig) {
444
+ const { config, runtime: runtime7, tileId = "chatbot-widget" } = mountConfig || {};
445
+ if (config && runtime7 && typeof createRoot === "function") {
446
+ const root = createRoot(container);
447
+ root.render(React.createElement(ChatAssistant, {
448
+ config,
449
+ runtime: runtime7,
450
+ tileId
451
+ }));
452
+ return () => {
453
+ root.unmount();
454
+ };
455
+ }
456
+ if (!config || !runtime7) {
457
+ container.innerHTML = `<div style="padding: 16px; color: ${slateGrey[8]};">Chat widget requires config and runtime.</div>`;
458
+ return () => {
459
+ container.innerHTML = "";
460
+ };
461
+ }
462
+ container.innerHTML = `
463
+ <div style="padding: 16px; font-family: system-ui; color: ${slateGrey[10]};">
464
+ <p style="margin: 0 0 8px; color: ${slateGrey[8]};">${config.greeting || "Hi! How can I help?"}</p>
465
+ <p style="margin: 0; font-size: 12px; color: ${slateGrey[7]};">Chat widget mounted. Awaiting React renderer.</p>
466
+ </div>
467
+ `;
468
+ return () => {
469
+ container.innerHTML = "";
470
+ };
471
+ }
472
+ };
473
+
474
+ // ../adaptives/adaptive-chatbot/dist/runtime.js
475
+ var runtime3 = {
476
+ id: "adaptive-chatbot",
477
+ version: "1.0.0",
478
+ name: "Chat Assistant",
479
+ description: "AI chat assistant with action execution capabilities",
480
+ /** No action executors — chatbot uses existing action kinds via applyBatch */
481
+ executors: [],
482
+ /** Widget definitions for the runtime's WidgetRegistry */
483
+ widgets: [
484
+ {
485
+ id: "adaptive-chatbot:assistant",
486
+ component: ChatAssistantMountableWidget,
487
+ metadata: {
488
+ name: "Chat Assistant",
489
+ description: "AI-powered chat assistant that can execute DOM actions",
490
+ icon: "\u{1F4AC}"
491
+ }
492
+ }
493
+ ]
494
+ };
495
+
496
+ // ../adaptives/adaptive-faq/dist/executors.js
497
+ function resolveItem(store, itemId, itemQuestion) {
498
+ if (itemId) {
499
+ const found = store.getState().items.find((i) => i.config.id === itemId);
500
+ if (found)
501
+ return found;
502
+ }
503
+ if (itemQuestion) {
504
+ const found = store.findByQuestion(itemQuestion);
505
+ if (found)
506
+ return found;
507
+ }
508
+ throw new Error("FAQ item not found");
509
+ }
510
+ async function executeScrollToFaq(action, context, store) {
511
+ var _a;
512
+ const item = resolveItem(store, action.itemId, action.itemQuestion);
513
+ const { id } = item.config;
514
+ if (action.expand !== false) {
515
+ store.expand(id);
516
+ }
517
+ const el = document.querySelector(`[data-faq-item-id="${id}"]`);
518
+ if (el) {
519
+ el.scrollIntoView({
520
+ behavior: (_a = action.behavior) != null ? _a : "smooth"
521
+ });
522
+ }
523
+ context.publishEvent("faq:scroll_to", { itemId: id });
524
+ return {
525
+ cleanup: () => {
526
+ }
527
+ };
528
+ }
529
+ async function executeToggleFaqItem(action, context, store) {
530
+ var _a;
531
+ const item = resolveItem(store, action.itemId, action.itemQuestion);
532
+ const { id } = item.config;
533
+ const desiredState = (_a = action.state) != null ? _a : "toggle";
534
+ let newState;
535
+ switch (desiredState) {
536
+ case "open":
537
+ store.expand(id);
538
+ newState = "open";
539
+ break;
540
+ case "closed":
541
+ store.collapse(id);
542
+ newState = "closed";
543
+ break;
544
+ default: {
545
+ const wasExpanded = store.getState().expandedItems.has(id);
546
+ store.toggle(id);
547
+ newState = wasExpanded ? "closed" : "open";
548
+ break;
549
+ }
550
+ }
551
+ context.publishEvent("faq:toggle", { itemId: id, newState });
552
+ return {
553
+ cleanup: () => {
554
+ }
555
+ };
556
+ }
557
+ async function executeUpdateFaq(action, context, store) {
558
+ var _a, _b, _c;
559
+ switch (action.operation) {
560
+ case "add": {
561
+ const items = (_a = action.items) != null ? _a : [];
562
+ const position = action.position === "prepend" ? "prepend" : "append";
563
+ store.addItems(items, position);
564
+ break;
565
+ }
566
+ case "remove": {
567
+ if (!action.itemId) {
568
+ throw new Error("FAQ item not found");
569
+ }
570
+ const exists = store.getState().items.some((i) => i.config.id === action.itemId);
571
+ if (!exists) {
572
+ throw new Error("FAQ item not found");
573
+ }
574
+ store.removeItem(action.itemId);
575
+ break;
576
+ }
577
+ case "reorder": {
578
+ const order = (_b = action.order) != null ? _b : [];
579
+ store.reorderItems(order);
580
+ break;
581
+ }
582
+ case "replace": {
583
+ const items = (_c = action.items) != null ? _c : [];
584
+ store.replaceItems(items);
585
+ break;
586
+ }
587
+ }
588
+ context.publishEvent("faq:update", { operation: action.operation });
589
+ return {
590
+ cleanup: () => {
591
+ }
592
+ };
593
+ }
594
+ var executorDefinitions = [
595
+ { kind: "faq:scroll_to", executor: executeScrollToFaq },
596
+ { kind: "faq:toggle_item", executor: executeToggleFaqItem },
597
+ { kind: "faq:update", executor: executeUpdateFaq }
598
+ ];
599
+
600
+ // ../adaptives/adaptive-faq/dist/FAQWidget.js
601
+ import { jsx as _jsx2, jsxs as _jsxs2 } from "react/jsx-runtime";
602
+ import React2, { useCallback as useCallback2, useEffect as useEffect2, useMemo, useReducer, useState as useState2 } from "react";
603
+ import { createRoot as createRoot2 } from "react-dom/client";
604
+ function getAnswerText(answer) {
605
+ if (typeof answer === "string")
606
+ return answer;
607
+ if (answer.type === "rich")
608
+ return answer.html;
609
+ return answer.content;
610
+ }
611
+ function renderAnswer(answer) {
612
+ if (typeof answer === "string") {
613
+ return _jsx2("p", { style: { margin: 0 }, children: answer });
614
+ }
615
+ if (answer.type === "rich") {
616
+ return _jsx2("div", { style: { margin: 0 }, dangerouslySetInnerHTML: { __html: answer.html } });
617
+ }
618
+ return _jsx2("p", { style: { margin: 0 }, children: answer.content });
619
+ }
620
+ function resolveFeedbackConfig(feedback) {
621
+ if (!feedback)
622
+ return null;
623
+ if (feedback === true) {
624
+ return { style: "thumbs" };
625
+ }
626
+ return feedback;
627
+ }
628
+ function getFeedbackPrompt(feedbackConfig) {
629
+ return feedbackConfig.prompt || "Was this helpful?";
630
+ }
631
+ var baseStyles = {
632
+ container: {
633
+ fontFamily: "system-ui, -apple-system, sans-serif",
634
+ maxWidth: "800px",
635
+ margin: "0 auto"
636
+ },
637
+ searchWrapper: {
638
+ marginBottom: "16px"
639
+ },
640
+ searchInput: {
641
+ width: "100%",
642
+ padding: "12px 16px",
643
+ borderRadius: "8px",
644
+ fontSize: "14px",
645
+ outline: "none",
646
+ transition: "border-color 0.15s ease"
647
+ },
648
+ accordion: {
649
+ display: "flex",
650
+ flexDirection: "column",
651
+ gap: "8px"
652
+ },
653
+ item: {
654
+ borderRadius: "8px",
655
+ overflow: "hidden",
656
+ transition: "box-shadow 0.15s ease"
657
+ },
658
+ question: {
659
+ width: "100%",
660
+ padding: "16px 20px",
661
+ display: "flex",
662
+ alignItems: "center",
663
+ justifyContent: "space-between",
664
+ border: "none",
665
+ cursor: "pointer",
666
+ fontSize: "15px",
667
+ fontWeight: 500,
668
+ textAlign: "left",
669
+ transition: "background-color 0.15s ease"
670
+ },
671
+ chevron: {
672
+ fontSize: "18px",
673
+ transition: "transform 0.2s ease"
674
+ },
675
+ answer: {
676
+ padding: "0 20px 16px 20px",
677
+ fontSize: "14px",
678
+ lineHeight: 1.6,
679
+ overflow: "hidden",
680
+ transition: "max-height 0.2s ease, padding 0.2s ease"
681
+ },
682
+ category: {
683
+ display: "inline-block",
684
+ fontSize: "11px",
685
+ fontWeight: 600,
686
+ textTransform: "uppercase",
687
+ letterSpacing: "0.05em",
688
+ padding: "4px 8px",
689
+ borderRadius: "4px",
690
+ marginBottom: "8px"
691
+ },
692
+ categoryHeader: {
693
+ fontSize: "13px",
694
+ fontWeight: 700,
695
+ textTransform: "uppercase",
696
+ letterSpacing: "0.05em",
697
+ padding: "12px 4px 6px 4px",
698
+ marginTop: "8px"
699
+ },
700
+ feedback: {
701
+ display: "flex",
702
+ alignItems: "center",
703
+ gap: "8px",
704
+ marginTop: "12px",
705
+ paddingTop: "10px",
706
+ borderTop: "1px solid rgba(0, 0, 0, 0.08)",
707
+ fontSize: "13px"
708
+ },
709
+ feedbackButton: {
710
+ background: "none",
711
+ border: "1px solid transparent",
712
+ cursor: "pointer",
713
+ fontSize: "16px",
714
+ padding: "4px 8px",
715
+ borderRadius: "4px",
716
+ transition: "background-color 0.15s ease, border-color 0.15s ease"
717
+ },
718
+ feedbackButtonSelected: {
719
+ borderColor: "rgba(0, 0, 0, 0.2)",
720
+ backgroundColor: "rgba(0, 0, 0, 0.04)"
721
+ },
722
+ emptyState: {
723
+ textAlign: "center",
724
+ padding: "48px 24px",
725
+ fontSize: "14px"
726
+ },
727
+ noResults: {
728
+ textAlign: "center",
729
+ padding: "32px 16px",
730
+ fontSize: "14px"
731
+ }
732
+ };
733
+ var themeStyles = {
734
+ light: {
735
+ container: {
736
+ backgroundColor: base.white,
737
+ color: slateGrey[1]
738
+ },
739
+ searchInput: {
740
+ backgroundColor: slateGrey[12],
741
+ border: `1px solid ${slateGrey[11]}`,
742
+ color: slateGrey[1]
743
+ },
744
+ item: {
745
+ backgroundColor: slateGrey[12],
746
+ border: `1px solid ${slateGrey[11]}`
747
+ },
748
+ itemExpanded: {
749
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.08)"
750
+ },
751
+ question: {
752
+ backgroundColor: "transparent",
753
+ color: slateGrey[1]
754
+ },
755
+ questionHover: {
756
+ backgroundColor: slateGrey[12]
757
+ },
758
+ answer: {
759
+ color: slateGrey[6]
760
+ },
761
+ category: {
762
+ backgroundColor: purple[8],
763
+ color: purple[2]
764
+ },
765
+ categoryHeader: {
766
+ color: slateGrey[7]
767
+ },
768
+ emptyState: {
769
+ color: slateGrey[8]
770
+ },
771
+ feedbackPrompt: {
772
+ color: slateGrey[7]
773
+ }
774
+ },
775
+ dark: {
776
+ container: {
777
+ backgroundColor: slateGrey[1],
778
+ color: slateGrey[12]
779
+ },
780
+ searchInput: {
781
+ backgroundColor: slateGrey[3],
782
+ border: `1px solid ${slateGrey[5]}`,
783
+ color: slateGrey[12]
784
+ },
785
+ item: {
786
+ backgroundColor: slateGrey[3],
787
+ border: `1px solid ${slateGrey[5]}`
788
+ },
789
+ itemExpanded: {
790
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.3)"
791
+ },
792
+ question: {
793
+ backgroundColor: "transparent",
794
+ color: slateGrey[12]
795
+ },
796
+ questionHover: {
797
+ backgroundColor: slateGrey[5]
798
+ },
799
+ answer: {
800
+ color: slateGrey[8]
801
+ },
802
+ category: {
803
+ backgroundColor: purple[0],
804
+ color: purple[6]
805
+ },
806
+ categoryHeader: {
807
+ color: slateGrey[8]
808
+ },
809
+ emptyState: {
810
+ color: slateGrey[7]
811
+ },
812
+ feedbackPrompt: {
813
+ color: slateGrey[8]
814
+ }
815
+ }
816
+ };
817
+ function FAQItem({ item, isExpanded, onToggle, theme, feedbackConfig, feedbackValue, onFeedback }) {
818
+ const [isHovered, setIsHovered] = useState2(false);
819
+ const colors = themeStyles[theme];
820
+ const { question, answer } = item.config;
821
+ const itemStyle = {
822
+ ...baseStyles.item,
823
+ ...colors.item,
824
+ ...isExpanded ? colors.itemExpanded : {}
825
+ };
826
+ const questionStyle = {
827
+ ...baseStyles.question,
828
+ ...colors.question,
829
+ ...isHovered ? colors.questionHover : {}
830
+ };
831
+ const chevronStyle = {
832
+ ...baseStyles.chevron,
833
+ transform: isExpanded ? "rotate(180deg)" : "rotate(0deg)"
834
+ };
835
+ const answerStyle = {
836
+ ...baseStyles.answer,
837
+ ...colors.answer,
838
+ maxHeight: isExpanded ? "500px" : "0",
839
+ paddingBottom: isExpanded ? "16px" : "0"
840
+ };
841
+ const feedbackStyle = {
842
+ ...baseStyles.feedback,
843
+ ...colors.feedbackPrompt
844
+ };
845
+ return _jsxs2("div", { style: itemStyle, "data-faq-item-id": item.config.id, children: [_jsxs2("button", { type: "button", style: questionStyle, onClick: onToggle, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), "aria-expanded": isExpanded, children: [_jsx2("span", { children: question }), _jsx2("span", { style: chevronStyle, children: "\u25BC" })] }), _jsxs2("div", { style: answerStyle, "aria-hidden": !isExpanded, children: [renderAnswer(answer), isExpanded && feedbackConfig && _jsxs2("div", { style: feedbackStyle, children: [_jsx2("span", { children: getFeedbackPrompt(feedbackConfig) }), _jsx2("button", { type: "button", style: {
846
+ ...baseStyles.feedbackButton,
847
+ ...feedbackValue === "up" ? baseStyles.feedbackButtonSelected : {}
848
+ }, "aria-label": "Thumbs up", onClick: () => onFeedback(item.config.id, question, "up"), children: "\u{1F44D}" }), _jsx2("button", { type: "button", style: {
849
+ ...baseStyles.feedbackButton,
850
+ ...feedbackValue === "down" ? baseStyles.feedbackButtonSelected : {}
851
+ }, "aria-label": "Thumbs down", onClick: () => onFeedback(item.config.id, question, "down"), children: "\u{1F44E}" })] })] })] });
852
+ }
853
+ function FAQWidget({ config, runtime: runtime7, instanceId }) {
854
+ const [renderTick, forceUpdate] = useReducer((x) => x + 1, 0);
855
+ const [expandedIds, setExpandedIds] = useState2(/* @__PURE__ */ new Set());
856
+ const [searchQuery, setSearchQuery] = useState2("");
857
+ const [feedbackState, setFeedbackState] = useState2(/* @__PURE__ */ new Map());
858
+ const feedbackConfig = useMemo(() => resolveFeedbackConfig(config.feedback), [config.feedback]);
859
+ useEffect2(() => {
860
+ const unsubscribe = runtime7.context.subscribe(() => {
861
+ forceUpdate();
862
+ });
863
+ return unsubscribe;
864
+ }, [runtime7.context]);
865
+ useEffect2(() => {
866
+ var _a;
867
+ if (!((_a = runtime7.accumulator) == null ? void 0 : _a.subscribe))
868
+ return;
869
+ return runtime7.accumulator.subscribe(() => {
870
+ forceUpdate();
871
+ });
872
+ }, [runtime7.accumulator]);
873
+ useEffect2(() => {
874
+ var _a, _b;
875
+ if (!config.scope || !((_a = runtime7.accumulator) == null ? void 0 : _a.register))
876
+ return;
877
+ const { events: eventNames, urlContains, props: propFilters } = config.scope;
878
+ const keys = /* @__PURE__ */ new Set();
879
+ for (const action of config.actions) {
880
+ if (((_b = action.showWhen) == null ? void 0 : _b.type) === "rules") {
881
+ for (const rule of action.showWhen.rules) {
882
+ for (const cond of rule.conditions) {
883
+ if (cond.type === "event_count" && cond.key) {
884
+ keys.add(cond.key);
885
+ }
886
+ }
887
+ }
888
+ }
889
+ }
890
+ for (const key of keys) {
891
+ runtime7.accumulator.register(key, (event) => {
892
+ var _a2, _b2, _c;
893
+ if (!eventNames.includes(event.name))
894
+ return false;
895
+ if (urlContains) {
896
+ const pathname = String((_b2 = (_a2 = event.props) == null ? void 0 : _a2.pathname) != null ? _b2 : "");
897
+ if (!pathname.includes(urlContains))
898
+ return false;
899
+ }
900
+ if (propFilters) {
901
+ for (const [k, v] of Object.entries(propFilters)) {
902
+ if (((_c = event.props) == null ? void 0 : _c[k]) !== v)
903
+ return false;
904
+ }
905
+ }
906
+ return true;
907
+ });
908
+ }
909
+ }, [config.scope, config.actions, runtime7.accumulator]);
910
+ useEffect2(() => {
911
+ if (!runtime7.events.subscribe)
912
+ return;
913
+ if (runtime7.events.getRecent) {
914
+ const recentEvents = runtime7.events.getRecent({ patterns: ["^action\\.tooltip_cta_clicked$", "^action\\.modal_cta_clicked$"] }, 10);
915
+ const pendingEvent = recentEvents.filter((e) => {
916
+ var _a;
917
+ const actionId = (_a = e.props) == null ? void 0 : _a.actionId;
918
+ return typeof actionId === "string" && actionId.startsWith("faq:open:");
919
+ }).pop();
920
+ if (pendingEvent && Date.now() - pendingEvent.ts < 1e4) {
921
+ const questionId = pendingEvent.props.actionId.replace("faq:open:", "");
922
+ setExpandedIds(/* @__PURE__ */ new Set([questionId]));
923
+ requestAnimationFrame(() => {
924
+ const el = document.querySelector(`[data-faq-item-id="${questionId}"]`);
925
+ if (el)
926
+ el.scrollIntoView({ behavior: "smooth", block: "center" });
927
+ });
928
+ }
929
+ }
930
+ const unsubscribe = runtime7.events.subscribe({ patterns: ["^action\\.tooltip_cta_clicked$", "^action\\.modal_cta_clicked$"] }, (event) => {
931
+ var _a;
932
+ const actionId = (_a = event.props) == null ? void 0 : _a.actionId;
933
+ if (typeof actionId !== "string" || !actionId.startsWith("faq:open:"))
934
+ return;
935
+ const questionId = actionId.replace("faq:open:", "");
936
+ setExpandedIds(/* @__PURE__ */ new Set([questionId]));
937
+ requestAnimationFrame(() => {
938
+ const el = document.querySelector(`[data-faq-item-id="${questionId}"]`);
939
+ if (el)
940
+ el.scrollIntoView({ behavior: "smooth", block: "center" });
941
+ });
942
+ runtime7.events.publish("canvas.requestOpen");
943
+ });
944
+ return unsubscribe;
945
+ }, [runtime7]);
946
+ const visibleQuestions = useMemo(() => config.actions.filter((q) => {
947
+ if (!q.showWhen)
948
+ return true;
949
+ const result = runtime7.evaluateSync(q.showWhen);
950
+ return result.value;
951
+ }), [config.actions, runtime7, renderTick]);
952
+ const orderedQuestions = useMemo(() => {
953
+ if (config.ordering === "priority") {
954
+ return [...visibleQuestions].sort((a, b) => {
955
+ var _a, _b;
956
+ return ((_a = b.config.priority) != null ? _a : 0) - ((_b = a.config.priority) != null ? _b : 0);
957
+ });
958
+ }
959
+ return visibleQuestions;
960
+ }, [visibleQuestions, config.ordering]);
961
+ const filteredQuestions = useMemo(() => {
962
+ if (!config.searchable || !searchQuery.trim()) {
963
+ return orderedQuestions;
964
+ }
965
+ const query = searchQuery.toLowerCase();
966
+ return orderedQuestions.filter((q) => {
967
+ var _a;
968
+ return q.config.question.toLowerCase().includes(query) || getAnswerText(q.config.answer).toLowerCase().includes(query) || ((_a = q.config.category) == null ? void 0 : _a.toLowerCase().includes(query));
969
+ });
970
+ }, [orderedQuestions, searchQuery, config.searchable]);
971
+ const categoryGroups = useMemo(() => {
972
+ const groups = /* @__PURE__ */ new Map();
973
+ for (const q of filteredQuestions) {
974
+ const cat = q.config.category;
975
+ if (!groups.has(cat)) {
976
+ groups.set(cat, []);
977
+ }
978
+ groups.get(cat).push(q);
979
+ }
980
+ return groups;
981
+ }, [filteredQuestions]);
982
+ const hasCategories = useMemo(() => filteredQuestions.some((q) => q.config.category), [filteredQuestions]);
983
+ const resolvedTheme = useMemo(() => {
984
+ var _a;
985
+ if (config.theme && config.theme !== "auto")
986
+ return config.theme;
987
+ if (typeof window !== "undefined") {
988
+ return ((_a = window.matchMedia) == null ? void 0 : _a.call(window, "(prefers-color-scheme: dark)").matches) ? "dark" : "light";
989
+ }
990
+ return "light";
991
+ }, [config.theme]);
992
+ const handleToggle = useCallback2((id) => {
993
+ setExpandedIds((prev) => {
994
+ const next = new Set(prev);
995
+ if (config.expandBehavior === "single") {
996
+ if (prev.has(id)) {
997
+ return /* @__PURE__ */ new Set();
998
+ }
999
+ return /* @__PURE__ */ new Set([id]);
1000
+ }
1001
+ if (prev.has(id)) {
1002
+ next.delete(id);
1003
+ } else {
1004
+ next.add(id);
1005
+ }
1006
+ return next;
1007
+ });
1008
+ runtime7.events.publish("faq:toggled", {
1009
+ instanceId,
1010
+ questionId: id,
1011
+ expanded: !expandedIds.has(id),
1012
+ timestamp: Date.now()
1013
+ });
1014
+ }, [config.expandBehavior, runtime7.events, instanceId, expandedIds]);
1015
+ const handleFeedback = useCallback2((itemId, question, value) => {
1016
+ setFeedbackState((prev) => {
1017
+ const next = new Map(prev);
1018
+ next.set(itemId, value);
1019
+ return next;
1020
+ });
1021
+ runtime7.events.publish("faq:feedback", {
1022
+ itemId,
1023
+ question,
1024
+ value
1025
+ });
1026
+ }, [runtime7.events]);
1027
+ const containerStyle = {
1028
+ ...baseStyles.container,
1029
+ ...themeStyles[resolvedTheme].container
1030
+ };
1031
+ const searchInputStyle = {
1032
+ ...baseStyles.searchInput,
1033
+ ...themeStyles[resolvedTheme].searchInput
1034
+ };
1035
+ const emptyStateStyle = {
1036
+ ...baseStyles.emptyState,
1037
+ ...themeStyles[resolvedTheme].emptyState
1038
+ };
1039
+ const categoryHeaderStyle = {
1040
+ ...baseStyles.categoryHeader,
1041
+ ...themeStyles[resolvedTheme].categoryHeader
1042
+ };
1043
+ const renderItems = (items) => items.map((q) => _jsx2(FAQItem, { item: q, isExpanded: expandedIds.has(q.config.id), onToggle: () => handleToggle(q.config.id), theme: resolvedTheme, feedbackConfig, feedbackValue: feedbackState.get(q.config.id), onFeedback: handleFeedback }, q.config.id));
1044
+ if (visibleQuestions.length === 0) {
1045
+ return _jsx2("div", { style: containerStyle, "data-adaptive-id": instanceId, "data-adaptive-type": "adaptive-faq", children: _jsx2("div", { style: emptyStateStyle, children: "No FAQ questions available." }) });
1046
+ }
1047
+ return _jsxs2("div", { style: containerStyle, "data-adaptive-id": instanceId, "data-adaptive-type": "adaptive-faq", children: [config.searchable && _jsx2("div", { style: baseStyles.searchWrapper, children: _jsx2("input", { type: "text", placeholder: "Search questions...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), style: searchInputStyle }) }), _jsx2("div", { style: baseStyles.accordion, children: hasCategories ? Array.from(categoryGroups.entries()).map(([category, items]) => _jsxs2(React2.Fragment, { children: [category && _jsx2("div", { style: categoryHeaderStyle, "data-category-header": category, children: category }), renderItems(items)] }, category != null ? category : "__ungrouped")) : renderItems(filteredQuestions) }), config.searchable && filteredQuestions.length === 0 && searchQuery && _jsxs2("div", { style: { ...baseStyles.noResults, ...themeStyles[resolvedTheme].emptyState }, children: ['No questions found matching "', searchQuery, '"'] })] });
1048
+ }
1049
+ var FAQMountableWidget = {
1050
+ mount(container, config) {
1051
+ const { runtime: runtime7, instanceId = "faq-widget", ...faqConfig } = config || {
1052
+ expandBehavior: "single",
1053
+ searchable: false,
1054
+ theme: "auto",
1055
+ actions: []
1056
+ };
1057
+ if (runtime7 && typeof createRoot2 === "function") {
1058
+ const root = createRoot2(container);
1059
+ root.render(React2.createElement(FAQWidget, {
1060
+ config: faqConfig,
1061
+ runtime: runtime7,
1062
+ instanceId
1063
+ }));
1064
+ return () => {
1065
+ root.unmount();
1066
+ };
1067
+ }
1068
+ const questions = faqConfig.actions || [];
1069
+ container.innerHTML = `
1070
+ <div style="font-family: system-ui; max-width: 800px;">
1071
+ ${questions.map((q) => `
1072
+ <div style="margin-bottom: 8px; padding: 16px; background: ${slateGrey[12]}; border-radius: 8px;">
1073
+ <strong>${q.config.question}</strong>
1074
+ <p style="margin-top: 8px; color: ${slateGrey[6]};">${getAnswerText(q.config.answer)}</p>
1075
+ </div>
1076
+ `).join("")}
1077
+ </div>
1078
+ `;
1079
+ return () => {
1080
+ container.innerHTML = "";
1081
+ };
1082
+ }
1083
+ };
1084
+
1085
+ // ../adaptives/adaptive-faq/dist/runtime.js
1086
+ var runtime4 = {
1087
+ id: "adaptive-faq",
1088
+ version: "2.0.0",
1089
+ name: "FAQ Accordion",
1090
+ description: "Collapsible Q&A accordion with actions, rich content, feedback, and personalization",
1091
+ /**
1092
+ * Action executors for programmatic FAQ interaction.
1093
+ */
1094
+ executors: executorDefinitions,
1095
+ /**
1096
+ * Widget definitions for the runtime's WidgetRegistry.
1097
+ */
1098
+ widgets: [
1099
+ {
1100
+ id: "adaptive-faq:accordion",
1101
+ component: FAQMountableWidget,
1102
+ metadata: {
1103
+ name: "FAQ Accordion",
1104
+ description: "Collapsible Q&A accordion with search, categories, and feedback",
1105
+ icon: "\u2753"
1106
+ }
1107
+ }
1108
+ ],
1109
+ /**
1110
+ * Extract notify watcher entries from tile config props.
1111
+ * The runtime evaluates these continuously (even with drawer closed)
1112
+ * and publishes faq:question_revealed when showWhen transitions false → true.
1113
+ */
1114
+ notifyWatchers(props) {
1115
+ var _a;
1116
+ const actions = (_a = props.actions) != null ? _a : [];
1117
+ return actions.filter((a) => a.notify && a.showWhen).map((a) => ({
1118
+ id: `faq:${a.config.id}`,
1119
+ strategy: a.showWhen,
1120
+ eventName: "faq:question_revealed",
1121
+ eventProps: {
1122
+ questionId: a.config.id,
1123
+ question: a.config.question,
1124
+ title: a.notify.title,
1125
+ body: a.notify.body,
1126
+ icon: a.notify.icon
1127
+ }
1128
+ }));
1129
+ }
1130
+ };
1131
+
1132
+ // ../adaptives/adaptive-gamification/dist/runtime.js
1133
+ var executeAwardBadge = async (action, context) => {
1134
+ const { badgeId } = action;
1135
+ context.publishEvent("gamification.badge_awarded", {
1136
+ badgeId,
1137
+ awardedAt: Date.now()
1138
+ });
1139
+ return {
1140
+ cleanup: () => {
1141
+ }
1142
+ };
1143
+ };
1144
+ var executeAddPoints = async (action, context) => {
1145
+ const { points, reason } = action;
1146
+ context.publishEvent("gamification.points_added", {
1147
+ points,
1148
+ reason,
1149
+ timestamp: Date.now()
1150
+ });
1151
+ return {
1152
+ cleanup: () => {
1153
+ }
1154
+ };
1155
+ };
1156
+ var badgeTriggerHandler = {
1157
+ names: ["page_view", "button_click"],
1158
+ handler: (_event, _ctx) => {
1159
+ console.log("[Gamification] Event received for badge trigger check");
1160
+ }
1161
+ };
1162
+ var executors = [
1163
+ { kind: "gamification:awardBadge", executor: executeAwardBadge },
1164
+ { kind: "gamification:addPoints", executor: executeAddPoints }
1165
+ ];
1166
+ var eventHandlers = [badgeTriggerHandler];
1167
+ var runtime5 = {
1168
+ id: "adaptive-gamification",
1169
+ version: "1.0.0",
1170
+ name: "Gamification",
1171
+ description: "Badges, rewards, points, and engagement mechanics",
1172
+ executors,
1173
+ eventHandlers
1174
+ };
1175
+
1176
+ // ../adaptives/adaptive-nav/dist/NavWidget.js
1177
+ import { jsx as _jsx3, jsxs as _jsxs3 } from "react/jsx-runtime";
1178
+ import React3, { useCallback as useCallback3, useEffect as useEffect3, useMemo as useMemo2, useReducer as useReducer2, useState as useState3 } from "react";
1179
+ import { createRoot as createRoot3 } from "react-dom/client";
1180
+ function escapeHtml(str) {
1181
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1182
+ }
1183
+ var baseStyles2 = {
1184
+ container: {
1185
+ fontFamily: "system-ui, -apple-system, sans-serif",
1186
+ padding: "8px",
1187
+ maxWidth: "100%",
1188
+ overflow: "hidden"
1189
+ },
1190
+ accordion: {
1191
+ display: "flex",
1192
+ flexDirection: "column",
1193
+ gap: "4px"
1194
+ },
1195
+ item: {
1196
+ borderRadius: "8px",
1197
+ overflow: "hidden",
1198
+ transition: "box-shadow 0.2s ease"
1199
+ },
1200
+ header: {
1201
+ display: "flex",
1202
+ alignItems: "center",
1203
+ gap: "8px",
1204
+ width: "100%",
1205
+ padding: "12px 16px",
1206
+ border: "none",
1207
+ cursor: "pointer",
1208
+ fontSize: "14px",
1209
+ fontWeight: 500,
1210
+ fontFamily: "inherit",
1211
+ textAlign: "left",
1212
+ transition: "background-color 0.15s ease"
1213
+ },
1214
+ chevron: {
1215
+ fontSize: "10px",
1216
+ transition: "transform 0.2s ease",
1217
+ marginLeft: "auto",
1218
+ flexShrink: 0
1219
+ },
1220
+ icon: {
1221
+ fontSize: "16px",
1222
+ flexShrink: 0
1223
+ },
1224
+ body: {
1225
+ overflow: "hidden",
1226
+ transition: "max-height 0.25s ease, padding-bottom 0.25s ease",
1227
+ padding: "0 16px"
1228
+ },
1229
+ description: {
1230
+ fontSize: "13px",
1231
+ lineHeight: "1.5",
1232
+ margin: 0
1233
+ },
1234
+ linkButton: {
1235
+ display: "inline-flex",
1236
+ alignItems: "center",
1237
+ gap: "4px",
1238
+ marginTop: "10px",
1239
+ padding: "6px 12px",
1240
+ borderRadius: "6px",
1241
+ textDecoration: "none",
1242
+ fontSize: "13px",
1243
+ fontWeight: 500,
1244
+ cursor: "pointer",
1245
+ border: "none",
1246
+ transition: "background-color 0.15s ease"
1247
+ },
1248
+ categoryHeader: {
1249
+ fontSize: "11px",
1250
+ fontWeight: 600,
1251
+ textTransform: "uppercase",
1252
+ letterSpacing: "0.05em",
1253
+ padding: "12px 4px 4px"
1254
+ },
1255
+ emptyState: {
1256
+ fontSize: "13px",
1257
+ padding: "16px",
1258
+ textAlign: "center"
1259
+ }
1260
+ };
1261
+ var themeStyles2 = {
1262
+ light: {
1263
+ container: {
1264
+ backgroundColor: base.white,
1265
+ color: slateGrey[1]
1266
+ },
1267
+ item: {
1268
+ backgroundColor: slateGrey[12],
1269
+ border: `1px solid ${slateGrey[11]}`
1270
+ },
1271
+ itemExpanded: {
1272
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.08)"
1273
+ },
1274
+ header: {
1275
+ backgroundColor: "transparent",
1276
+ color: slateGrey[1]
1277
+ },
1278
+ headerHover: {
1279
+ backgroundColor: slateGrey[12]
1280
+ },
1281
+ body: {
1282
+ color: slateGrey[6]
1283
+ },
1284
+ linkButton: {
1285
+ backgroundColor: purple[8],
1286
+ color: purple[2]
1287
+ },
1288
+ categoryHeader: {
1289
+ color: slateGrey[7]
1290
+ },
1291
+ emptyState: {
1292
+ color: slateGrey[8]
1293
+ }
1294
+ },
1295
+ dark: {
1296
+ container: {
1297
+ backgroundColor: slateGrey[1],
1298
+ color: slateGrey[12]
1299
+ },
1300
+ item: {
1301
+ backgroundColor: slateGrey[3],
1302
+ border: `1px solid ${slateGrey[5]}`
1303
+ },
1304
+ itemExpanded: {
1305
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.3)"
1306
+ },
1307
+ header: {
1308
+ backgroundColor: "transparent",
1309
+ color: slateGrey[12]
1310
+ },
1311
+ headerHover: {
1312
+ backgroundColor: slateGrey[5]
1313
+ },
1314
+ body: {
1315
+ color: slateGrey[8]
1316
+ },
1317
+ linkButton: {
1318
+ backgroundColor: purple[0],
1319
+ color: purple[6]
1320
+ },
1321
+ categoryHeader: {
1322
+ color: slateGrey[8]
1323
+ },
1324
+ emptyState: {
1325
+ color: slateGrey[7]
1326
+ }
1327
+ }
1328
+ };
1329
+ function NavTipItem({ item, isExpanded, onToggle, onNavigate, theme }) {
1330
+ const [isHovered, setIsHovered] = useState3(false);
1331
+ const colors = themeStyles2[theme];
1332
+ const { title, description, href, icon, external } = item.config;
1333
+ const itemStyle = {
1334
+ ...baseStyles2.item,
1335
+ ...colors.item,
1336
+ ...isExpanded ? colors.itemExpanded : {}
1337
+ };
1338
+ const headerStyle = {
1339
+ ...baseStyles2.header,
1340
+ ...colors.header,
1341
+ ...isHovered ? colors.headerHover : {}
1342
+ };
1343
+ const chevronStyle = {
1344
+ ...baseStyles2.chevron,
1345
+ transform: isExpanded ? "rotate(180deg)" : "rotate(0deg)"
1346
+ };
1347
+ const bodyStyle = {
1348
+ ...baseStyles2.body,
1349
+ ...colors.body,
1350
+ maxHeight: isExpanded ? "500px" : "0",
1351
+ paddingBottom: isExpanded ? "16px" : "0"
1352
+ };
1353
+ const handleLinkClick = (e) => {
1354
+ e.preventDefault();
1355
+ e.stopPropagation();
1356
+ if (href) {
1357
+ onNavigate(href, external != null ? external : false);
1358
+ }
1359
+ };
1360
+ return _jsxs3("div", { style: itemStyle, "data-nav-tip-id": item.config.id, children: [_jsxs3("button", { type: "button", style: headerStyle, onClick: onToggle, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), "aria-expanded": isExpanded, children: [icon && _jsx3("span", { style: baseStyles2.icon, children: icon }), _jsx3("span", { children: title }), _jsx3("span", { style: chevronStyle, children: "\u25BC" })] }), _jsxs3("div", { style: bodyStyle, "aria-hidden": !isExpanded, children: [_jsx3("p", { style: baseStyles2.description, children: description }), href && _jsxs3("a", { href, onClick: handleLinkClick, style: { ...baseStyles2.linkButton, ...colors.linkButton }, target: external ? "_blank" : void 0, rel: external ? "noopener noreferrer" : void 0, children: ["Go ", external ? "\u2197" : "\u2192"] })] })] });
1361
+ }
1362
+ function NavWidget({ config, runtime: runtime7, instanceId }) {
1363
+ const [renderTick, forceUpdate] = useReducer2((x) => x + 1, 0);
1364
+ const [expandedIds, setExpandedIds] = useState3(/* @__PURE__ */ new Set());
1365
+ useEffect3(() => {
1366
+ const unsubscribe = runtime7.context.subscribe(() => {
1367
+ forceUpdate();
1368
+ });
1369
+ return unsubscribe;
1370
+ }, [runtime7.context]);
1371
+ useEffect3(() => {
1372
+ var _a;
1373
+ if (!((_a = runtime7.accumulator) == null ? void 0 : _a.subscribe))
1374
+ return;
1375
+ return runtime7.accumulator.subscribe(() => {
1376
+ forceUpdate();
1377
+ });
1378
+ }, [runtime7.accumulator]);
1379
+ useEffect3(() => {
1380
+ var _a, _b;
1381
+ if (!config.scope || !((_a = runtime7.accumulator) == null ? void 0 : _a.register))
1382
+ return;
1383
+ const { events: eventNames, urlContains, props: propFilters } = config.scope;
1384
+ const keys = /* @__PURE__ */ new Set();
1385
+ for (const action of config.actions) {
1386
+ if (((_b = action.showWhen) == null ? void 0 : _b.type) === "rules") {
1387
+ for (const rule of action.showWhen.rules) {
1388
+ for (const cond of rule.conditions) {
1389
+ if (cond.type === "event_count" && cond.key) {
1390
+ keys.add(cond.key);
1391
+ }
1392
+ }
1393
+ }
1394
+ }
1395
+ }
1396
+ for (const key of keys) {
1397
+ runtime7.accumulator.register(key, (event) => {
1398
+ var _a2, _b2, _c;
1399
+ if (!eventNames.includes(event.name))
1400
+ return false;
1401
+ if (urlContains) {
1402
+ const pathname = String((_b2 = (_a2 = event.props) == null ? void 0 : _a2.pathname) != null ? _b2 : "");
1403
+ if (!pathname.includes(urlContains))
1404
+ return false;
1405
+ }
1406
+ if (propFilters) {
1407
+ for (const [k, v] of Object.entries(propFilters)) {
1408
+ if (((_c = event.props) == null ? void 0 : _c[k]) !== v)
1409
+ return false;
1410
+ }
1411
+ }
1412
+ return true;
1413
+ });
1414
+ }
1415
+ }, [config.scope, config.actions, runtime7.accumulator]);
1416
+ const visibleTips = useMemo2(() => config.actions.filter((tip) => {
1417
+ if (!tip.showWhen)
1418
+ return true;
1419
+ try {
1420
+ const result = runtime7.evaluateSync(tip.showWhen);
1421
+ return result.value;
1422
+ } catch {
1423
+ return false;
1424
+ }
1425
+ }), [config.actions, runtime7, renderTick]);
1426
+ const categoryGroups = useMemo2(() => {
1427
+ const groups = /* @__PURE__ */ new Map();
1428
+ for (const tip of visibleTips) {
1429
+ const cat = tip.config.category;
1430
+ if (!groups.has(cat)) {
1431
+ groups.set(cat, []);
1432
+ }
1433
+ groups.get(cat).push(tip);
1434
+ }
1435
+ return groups;
1436
+ }, [visibleTips]);
1437
+ const hasCategories = useMemo2(() => visibleTips.some((t) => t.config.category), [visibleTips]);
1438
+ const resolvedTheme = useMemo2(() => {
1439
+ var _a;
1440
+ if (config.theme && config.theme !== "auto")
1441
+ return config.theme;
1442
+ if (typeof window !== "undefined") {
1443
+ return ((_a = window.matchMedia) == null ? void 0 : _a.call(window, "(prefers-color-scheme: dark)").matches) ? "dark" : "light";
1444
+ }
1445
+ return "light";
1446
+ }, [config.theme]);
1447
+ const handleToggle = useCallback3((id) => {
1448
+ setExpandedIds((prev) => {
1449
+ const wasExpanded = prev.has(id);
1450
+ let next;
1451
+ if (config.expandBehavior === "single") {
1452
+ for (const prevId of prev) {
1453
+ if (prevId !== id) {
1454
+ runtime7.events.publish("nav:toggled", {
1455
+ instanceId,
1456
+ tipId: prevId,
1457
+ expanded: false,
1458
+ timestamp: Date.now()
1459
+ });
1460
+ }
1461
+ }
1462
+ next = wasExpanded ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([id]);
1463
+ } else {
1464
+ next = new Set(prev);
1465
+ if (wasExpanded) {
1466
+ next.delete(id);
1467
+ } else {
1468
+ next.add(id);
1469
+ }
1470
+ }
1471
+ runtime7.events.publish("nav:toggled", {
1472
+ instanceId,
1473
+ tipId: id,
1474
+ expanded: !wasExpanded,
1475
+ timestamp: Date.now()
1476
+ });
1477
+ return next;
1478
+ });
1479
+ }, [config.expandBehavior, runtime7.events, instanceId]);
1480
+ const handleNavigate = useCallback3((href, external) => {
1481
+ const normalizedHref = href.trim().toLowerCase();
1482
+ if (normalizedHref.startsWith("javascript:") || normalizedHref.startsWith("data:")) {
1483
+ return;
1484
+ }
1485
+ runtime7.events.publish("nav:tip_clicked", {
1486
+ instanceId,
1487
+ href,
1488
+ external,
1489
+ timestamp: Date.now()
1490
+ });
1491
+ if (external) {
1492
+ window.open(href, "_blank", "noopener,noreferrer");
1493
+ } else {
1494
+ window.location.href = href;
1495
+ }
1496
+ }, [runtime7.events, instanceId]);
1497
+ const containerStyle = {
1498
+ ...baseStyles2.container,
1499
+ ...themeStyles2[resolvedTheme].container
1500
+ };
1501
+ const categoryHeaderStyle = {
1502
+ ...baseStyles2.categoryHeader,
1503
+ ...themeStyles2[resolvedTheme].categoryHeader
1504
+ };
1505
+ const emptyStateStyle = {
1506
+ ...baseStyles2.emptyState,
1507
+ ...themeStyles2[resolvedTheme].emptyState
1508
+ };
1509
+ const renderItems = (items) => items.map((tip) => _jsx3(NavTipItem, { item: tip, isExpanded: expandedIds.has(tip.config.id), onToggle: () => handleToggle(tip.config.id), onNavigate: handleNavigate, theme: resolvedTheme }, tip.config.id));
1510
+ if (visibleTips.length === 0) {
1511
+ return _jsx3("div", { style: containerStyle, "data-adaptive-id": instanceId, "data-adaptive-type": "adaptive-nav", children: _jsx3("div", { style: emptyStateStyle, children: "No navigation tips available." }) });
1512
+ }
1513
+ return _jsx3("div", { style: containerStyle, "data-adaptive-id": instanceId, "data-adaptive-type": "adaptive-nav", children: _jsx3("div", { style: baseStyles2.accordion, children: hasCategories ? Array.from(categoryGroups.entries()).map(([category, items]) => _jsxs3(React3.Fragment, { children: [category && _jsx3("div", { style: categoryHeaderStyle, "data-category-header": category, children: category }), renderItems(items)] }, category != null ? category : "__ungrouped")) : renderItems(visibleTips) }) });
1514
+ }
1515
+ var NavMountableWidget = {
1516
+ mount(container, config) {
1517
+ const { runtime: runtime7, instanceId = "nav-widget", ...navConfig } = config || {
1518
+ expandBehavior: "single",
1519
+ theme: "auto",
1520
+ actions: []
1521
+ };
1522
+ if (runtime7 && typeof createRoot3 === "function") {
1523
+ const root = createRoot3(container);
1524
+ root.render(React3.createElement(NavWidget, {
1525
+ config: navConfig,
1526
+ runtime: runtime7,
1527
+ instanceId
1528
+ }));
1529
+ return () => {
1530
+ root.unmount();
1531
+ };
1532
+ }
1533
+ const tips = navConfig.actions || [];
1534
+ container.innerHTML = `
1535
+ <div style="font-family: system-ui; max-width: 100%;">
1536
+ ${tips.map((tip) => `
1537
+ <div style="margin-bottom: 4px; padding: 12px 16px; background: ${slateGrey[12]}; border-radius: 8px;">
1538
+ ${tip.config.icon ? `<span>${escapeHtml(tip.config.icon)}</span> ` : ""}<strong>${escapeHtml(tip.config.title)}</strong>
1539
+ <p style="margin-top: 8px; color: ${slateGrey[6]}; font-size: 13px;">${escapeHtml(tip.config.description)}</p>
1540
+ ${tip.config.href ? `<a href="${escapeHtml(tip.config.href)}" style="color: ${purple[2]}; font-size: 13px;">Go &rarr;</a>` : ""}
1541
+ </div>
1542
+ `).join("")}
1543
+ </div>
1544
+ `;
1545
+ return () => {
1546
+ container.innerHTML = "";
1547
+ };
1548
+ }
1549
+ };
1550
+
1551
+ // ../adaptives/adaptive-nav/dist/runtime.js
1552
+ var executeScrollTo = async (action, context) => {
1553
+ var _a, _b, _c, _d;
1554
+ const anchorEl = context.resolveAnchor(action.anchorId);
1555
+ if (!anchorEl) {
1556
+ throw new Error(`Anchor not found: ${action.anchorId}`);
1557
+ }
1558
+ anchorEl.scrollIntoView({
1559
+ behavior: (_a = action.behavior) != null ? _a : "smooth",
1560
+ block: (_b = action.block) != null ? _b : "center",
1561
+ inline: (_c = action.inline) != null ? _c : "nearest"
1562
+ });
1563
+ context.publishEvent("action.applied", {
1564
+ id: context.generateId(),
1565
+ kind: "navigation:scrollTo",
1566
+ anchorId: action.anchorId,
1567
+ behavior: (_d = action.behavior) != null ? _d : "smooth"
1568
+ });
1569
+ return {
1570
+ cleanup: () => {
1571
+ }
1572
+ };
1573
+ };
1574
+ function isSameOrigin(url) {
1575
+ try {
1576
+ const parsed = new URL(url, window.location.origin);
1577
+ return parsed.origin === window.location.origin;
1578
+ } catch {
1579
+ return false;
1580
+ }
1581
+ }
1582
+ var executeNavigate = async (action, context) => {
1583
+ var _a;
1584
+ const url = action.url.trim();
1585
+ if (url.toLowerCase().startsWith("javascript:")) {
1586
+ throw new Error("javascript: URLs are not allowed");
1587
+ }
1588
+ const target = (_a = action.target) != null ? _a : "_self";
1589
+ context.publishEvent("action.applied", {
1590
+ id: context.generateId(),
1591
+ kind: "navigation:navigate",
1592
+ url: action.url,
1593
+ target
1594
+ });
1595
+ if (target === "_blank") {
1596
+ window.open(url, "_blank", "noopener,noreferrer");
1597
+ } else if (!action.forceFullNavigation && isSameOrigin(url)) {
1598
+ window.history.pushState(null, "", url);
1599
+ window.dispatchEvent(new PopStateEvent("popstate"));
1600
+ } else {
1601
+ window.location.href = url;
1602
+ }
1603
+ return {
1604
+ cleanup: () => {
1605
+ }
1606
+ };
1607
+ };
1608
+ var executors2 = [
1609
+ { kind: "navigation:scrollTo", executor: executeScrollTo },
1610
+ { kind: "navigation:navigate", executor: executeNavigate }
1611
+ ];
1612
+ var runtime6 = {
1613
+ id: "adaptive-nav",
1614
+ version: "2.0.0",
1615
+ name: "Navigation Tips",
1616
+ description: "Navigation actions and accordion-based tips with per-item conditional visibility",
1617
+ /**
1618
+ * Navigation action executors (scrollTo, navigate).
1619
+ */
1620
+ executors: executors2,
1621
+ /**
1622
+ * Widget definitions for the runtime's WidgetRegistry.
1623
+ */
1624
+ widgets: [
1625
+ {
1626
+ id: "adaptive-nav:tips",
1627
+ component: NavMountableWidget,
1628
+ metadata: {
1629
+ name: "Navigation Tips",
1630
+ description: "Accordion of contextual navigation tips with per-item visibility",
1631
+ icon: "\u{1F9ED}"
1632
+ }
1633
+ }
1634
+ ],
1635
+ /**
1636
+ * Extract notify watcher entries from tile config props.
1637
+ * The runtime evaluates these continuously (even with drawer closed)
1638
+ * and publishes nav:tip_revealed when showWhen transitions false → true.
1639
+ */
1640
+ notifyWatchers(props) {
1641
+ var _a;
1642
+ const actions = (_a = props.actions) != null ? _a : [];
1643
+ return actions.filter((a) => a.notify && a.showWhen).map((a) => ({
1644
+ id: `nav:${a.config.id}`,
1645
+ strategy: a.showWhen,
1646
+ eventName: "nav:tip_revealed",
1647
+ eventProps: {
1648
+ tipId: a.config.id,
1649
+ title: a.notify.title,
1650
+ body: a.notify.body,
1651
+ icon: a.notify.icon
1652
+ }
1653
+ }));
1654
+ }
1655
+ };
1656
+
1657
+ // src/apps/builtinRuntimeModules.generated.ts
1658
+ var allRuntimes = [runtime3, runtime, runtime4, runtime5, runtime6, runtime2];
1659
+ var builtinAdaptiveManifests = allRuntimes.map((r) => ({
1660
+ id: r.id,
1661
+ version: r.version,
1662
+ name: r.name,
1663
+ description: r.description,
1664
+ runtime: {
1665
+ actions: (r.executors || []).map(({ kind, executor }) => ({ kind, executor })),
1666
+ widgets: r.widgets,
1667
+ notifyWatchers: r.notifyWatchers,
1668
+ events: r.eventHandlers
1669
+ },
1670
+ editor: void 0,
1671
+ metadata: { isBuiltIn: true }
1672
+ }));
1673
+ function registerBuiltinRuntimeModules(registry) {
1674
+ let count = 0;
1675
+ for (const manifest of builtinAdaptiveManifests) {
1676
+ if (!registry.has(manifest.id)) {
1677
+ registry.register(manifest);
1678
+ count++;
1679
+ }
1680
+ }
1681
+ console.log("[Syntro Runtime] Registered", count, "built-in runtime modules");
1682
+ }
1683
+
140
1684
  // src/components/TileWheel.tsx
141
- import { useEffect, useMemo, useState } from "react";
1685
+ import { useEffect as useEffect4, useMemo as useMemo3, useState as useState4 } from "react";
142
1686
  import { jsx, jsxs } from "react/jsx-runtime";
143
1687
  function TileWheel({ tiles, intervalMs = 7e3, telemetry }) {
144
- const [index, setIndex] = useState(0);
145
- const ordered = useMemo(
1688
+ const [index, setIndex] = useState4(0);
1689
+ const ordered = useMemo3(
146
1690
  () => [...tiles].sort((a, b) => {
147
1691
  var _a, _b;
148
1692
  return ((_a = a.priority) != null ? _a : 0) - ((_b = b.priority) != null ? _b : 0);
149
1693
  }),
150
1694
  [tiles]
151
1695
  );
152
- useEffect(() => {
1696
+ useEffect4(() => {
153
1697
  telemetry == null ? void 0 : telemetry.trackCanvasOpened("wheel");
154
1698
  }, [telemetry]);
155
- useEffect(() => {
1699
+ useEffect4(() => {
156
1700
  if (ordered.length <= 1) return;
157
1701
  const id = setInterval(() => {
158
1702
  setIndex((prev) => (prev + 1) % ordered.length);
159
1703
  }, intervalMs);
160
1704
  return () => clearInterval(id);
161
1705
  }, [ordered, intervalMs]);
162
- useEffect(() => {
1706
+ useEffect4(() => {
163
1707
  const current = ordered[index];
164
1708
  if (current) {
165
1709
  telemetry == null ? void 0 : telemetry.trackRectangleViewed(current.id, "wheel");
@@ -394,10 +1938,10 @@ var createOverlayRecipeFetcher = ({
394
1938
  };
395
1939
 
396
1940
  // src/SmartCanvasPortal.tsx
397
- import { useLayoutEffect, useState as useState2 } from "react";
1941
+ import { useLayoutEffect, useState as useState5 } from "react";
398
1942
  import { createPortal } from "react-dom";
399
1943
  function SmartCanvasPortal({ element, children }) {
400
- const [mountNode, setMountNode] = useState2(null);
1944
+ const [mountNode, setMountNode] = useState5(null);
401
1945
  useLayoutEffect(() => {
402
1946
  if (!element) {
403
1947
  setMountNode(null);
@@ -626,14 +2170,19 @@ function validateFrequencyEntry(data) {
626
2170
  }
627
2171
 
628
2172
  // src/index.ts
2173
+ var RUNTIME_SDK_BUILD = typeof __BUILD_TIMESTAMP__ !== "undefined" ? `${__BUILD_TIMESTAMP__} (${__BUILD_GIT_HASH__})` : "dev";
629
2174
  if (typeof window !== "undefined") {
2175
+ console.log(`[Syntro Runtime] Build: ${RUNTIME_SDK_BUILD}`);
630
2176
  const existing = window.SynOS;
2177
+ const registry = (existing == null ? void 0 : existing.appRegistry) || appRegistry;
631
2178
  window.SynOS = {
632
2179
  ...existing,
633
- appRegistry: (existing == null ? void 0 : existing.appRegistry) || appRegistry,
634
- React,
2180
+ appRegistry: registry,
2181
+ _runtimeBuild: RUNTIME_SDK_BUILD,
2182
+ React: React4,
635
2183
  ReactDOM: { ...ReactDOMClient, createPortal: createPortal2, flushSync }
636
2184
  };
2185
+ registerBuiltinRuntimeModules(registry);
637
2186
  }
638
2187
  export {
639
2188
  ANIMATION_KEYFRAMES,
@@ -677,6 +2226,7 @@ export {
677
2226
  ModelStrategyZ,
678
2227
  MountWidgetZ,
679
2228
  NavigateZ,
2229
+ NavigationMonitor,
680
2230
  NormalizedEventZ,
681
2231
  NotificationToastStack,
682
2232
  PageContextZ,
@@ -685,6 +2235,7 @@ export {
685
2235
  ParallelZ,
686
2236
  PlacementZ,
687
2237
  PulseZ,
2238
+ RUNTIME_SDK_BUILD,
688
2239
  RUNTIME_VERSION,
689
2240
  RemoveClassZ,
690
2241
  RouteConditionZ,