@syntrologie/adapt-faq 2.28.0 → 2.29.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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/executors.ts", "../../../canvas-sdk/src/flip.ts", "../src/FAQWidgetLit.ts", "../src/faq-styles.ts", "../src/renderHealth.ts", "../src/runtime.ts"],
4
+ "sourcesContent": ["/**\n * Adaptive FAQ - Action Executors\n *\n * Three executors that operate on the FAQStore:\n * - executeScrollToFaq: scroll to a FAQ item and optionally expand it\n * - executeToggleFaqItem: open / close / toggle a FAQ item\n * - executeUpdateFaq: add, remove, reorder, or replace FAQ items\n */\n\nimport type { FAQStore } from './state';\nimport type {\n ExecutorContext,\n ExecutorResult,\n FAQQuestionAction,\n ScrollToFaqAction,\n ToggleFaqItemAction,\n UpdateFaqAction,\n} from './types';\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Resolve a FAQ item by direct ID or by fuzzy question text match.\n * Throws if neither yields a result.\n */\nfunction resolveItem(store: FAQStore, itemId?: string, itemQuestion?: string): FAQQuestionAction {\n if (itemId) {\n const found = store.getState().items.find((i) => i.config.id === itemId);\n if (found) return found;\n }\n\n if (itemQuestion) {\n const found = store.findByQuestion(itemQuestion);\n if (found) return found;\n }\n\n throw new Error('FAQ item not found');\n}\n\n// ============================================================================\n// executeScrollToFaq\n// ============================================================================\n\n/**\n * Scroll to a FAQ item in the DOM and optionally expand it.\n *\n * Looks up the item by `itemId` or `itemQuestion`, scrolls the matching\n * `[data-faq-item-id]` element into view, and expands it unless\n * `expand` is explicitly set to `false`.\n */\nexport async function executeScrollToFaq(\n action: ScrollToFaqAction,\n context: ExecutorContext,\n store: FAQStore\n): Promise<ExecutorResult> {\n const item = resolveItem(store, action.itemId, action.itemQuestion);\n const { id } = item.config;\n\n // Expand the item unless explicitly told not to\n if (action.expand !== false) {\n store.expand(id);\n }\n\n // Scroll the DOM element into view (may be absent in test environments)\n const el = document.querySelector(`[data-faq-item-id=\"${id}\"]`);\n if (el) {\n el.scrollIntoView({\n behavior: action.behavior ?? 'smooth',\n });\n }\n\n // Publish analytics event\n context.publishEvent('faq:scroll_to', { itemId: id });\n\n return {\n cleanup: () => {\n // Optionally collapse on revert\n },\n };\n}\n\n// ============================================================================\n// executeToggleFaqItem\n// ============================================================================\n\n/**\n * Open, close, or toggle a FAQ item's expanded state.\n */\nexport async function executeToggleFaqItem(\n action: ToggleFaqItemAction,\n context: ExecutorContext,\n store: FAQStore\n): Promise<ExecutorResult> {\n const item = resolveItem(store, action.itemId, action.itemQuestion);\n const { id } = item.config;\n const desiredState = action.state ?? 'toggle';\n\n let newState: 'open' | 'closed';\n\n switch (desiredState) {\n case 'open':\n store.expand(id);\n newState = 'open';\n break;\n case 'closed':\n store.collapse(id);\n newState = 'closed';\n break;\n default: {\n const wasExpanded = store.getState().expandedItems.has(id);\n store.toggle(id);\n newState = wasExpanded ? 'closed' : 'open';\n break;\n }\n }\n\n context.publishEvent('faq:toggle', { itemId: id, newState });\n\n return {\n cleanup: () => {\n // Revert toggle on cleanup\n },\n };\n}\n\n// ============================================================================\n// executeUpdateFaq\n// ============================================================================\n\n/**\n * Add, remove, reorder, or replace FAQ items in the store.\n */\nexport async function executeUpdateFaq(\n action: UpdateFaqAction,\n context: ExecutorContext,\n store: FAQStore\n): Promise<ExecutorResult> {\n switch (action.operation) {\n case 'add': {\n const items = action.items ?? [];\n const position = action.position === 'prepend' ? 'prepend' : 'append';\n store.addItems(items, position);\n break;\n }\n\n case 'remove': {\n if (!action.itemId) {\n throw new Error('FAQ item not found');\n }\n // Verify the item exists before removing\n const exists = store.getState().items.some((i) => i.config.id === action.itemId);\n if (!exists) {\n throw new Error('FAQ item not found');\n }\n store.removeItem(action.itemId);\n break;\n }\n\n case 'reorder': {\n const order = action.order ?? [];\n store.reorderItems(order);\n break;\n }\n\n case 'replace': {\n const items = action.items ?? [];\n store.replaceItems(items);\n break;\n }\n }\n\n context.publishEvent('faq:update', { operation: action.operation });\n\n return {\n cleanup: () => {\n // Could snapshot previous state for undo\n },\n };\n}\n\n// ============================================================================\n// Executor Definitions for Registration\n// ============================================================================\n\n/**\n * All executors provided by adaptive-faq.\n * These are registered with the runtime's ExecutorRegistry.\n */\nexport const executorDefinitions = [\n { kind: 'faq:scroll_to', executor: executeScrollToFaq },\n { kind: 'faq:toggle_item', executor: executeToggleFaqItem },\n { kind: 'faq:update', executor: executeUpdateFaq },\n] as const;\n", "/**\n * Shared canvas flip primitive \u2014 the front-list \u21C4 back-detail 3D takeover.\n *\n * A canvas tile that swaps between a LIST face and a single-item DETAIL face\n * does so with a 3D rotateY flip (mirroring the product card's `data-face`\n * idiom) so the two faces feel consistent inside velvet's bounded deck card.\n * The FAQ, the product card, and velvet each grew their own copy of this same\n * technique; this module is the single source they can share. The flip is a\n * canvas-LAYER presentation concern (layout + animation), not an adaptive one \u2014\n * so it lives in `@syntrologie/canvas-sdk`, not in any one widget.\n *\n * Two hard-won contracts are baked in and MUST be preserved by every consumer:\n *\n * 1. **The 3D machinery is gated to the animating window only.** perspective /\n * preserve-3d / rotateY / per-face backface exist ONLY while a flip runs;\n * at rest the active face renders FLAT (`transform:none`) and the inactive\n * face is `display:none`. A resting face left on a rotateY+perspective GPU\n * layer blanks its text on some Android GPUs and rasterizes it soft\n * everywhere (the same hazard the product card's variant flip hit).\n *\n * 2. **The back-face teardown timer is coupled to the CSS transition token.**\n * On flip-OUT the detail stays mounted through the rotation (never blanked\n * mid-flip); it clears on the container's own `animationend`, or \u2014 if that\n * event is dropped (backgrounded tab, engine quirk, reduced-motion) \u2014 a belt\n * timer whose delay is `>=` the computed `--vc-transition-duration`. A host\n * that sets a slower token must never blank the back face early.\n *\n * `prefers-reduced-motion` disables the rotation: the controller settles\n * synchronously (no animation to await).\n *\n * Everything is **namespace-parameterized** (`ns`) so each consumer keeps its\n * own attribute + keyframe names and velvet's generic `[data-active-face]`\n * rotate rule can't double-apply: FAQ uses `ns:'faq'` (`data-faq-*`,\n * `sc-faq-flip-*`), reviews use `ns:'rev'`, etc.\n */\n\nimport type { ReactiveController, ReactiveControllerHost } from 'lit';\n\nexport type FlipFace = 'front' | 'back';\nexport type FlipDir = 'in' | 'out';\n\n// Flip transition window FALLBACK, used only when the computed\n// `--vc-transition-duration` token can't be read (no live layout, jsdom). The\n// velvet house tokens are ~240ms; we clear the open detail a little after that\n// so the back face isn't blanked before the flip visually completes.\nconst FLIP_TRANSITION_MS = 360;\n\n// Minimum extra time added on top of the CSS transition duration before the\n// back face is torn down, so the flip has visibly settled first.\nconst FLIP_CLEAR_BUFFER_MS = 120;\n\n/** Whether the visitor has asked for reduced motion \u2014 the flip is disabled and\n * the takeover swaps instantly instead of rotating. */\nexport function prefersReducedMotion(): boolean {\n if (typeof window === 'undefined') return false;\n return window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;\n}\n\nexport interface FlipStyleOptions {\n /** Attribute namespace, e.g. `faq` \u2192 `data-faq-flip`, `data-faq-active-face`. */\n ns: string;\n /** Keyframe name prefix, e.g. `sc-faq-flip` \u2192 `sc-faq-flip-in` / `-out`. */\n keyframePrefix: string;\n}\n\n/**\n * The self-injected flip CSS, scoped to `scope` (a selector such as\n * `[data-adaptive-id=\"\u2026\"]`) and namespaced by `opts.ns`. Returns a raw CSS\n * string \u2014 the caller wraps it in a `<style>` (light-DOM widgets) or an\n * adopted stylesheet.\n *\n * Two deliberate departures from a naive rotate rule (both required for depth\n * and safety, see the module doc):\n * - PERSPECTIVE lives on the PARENT viewport wrapper (a descendant's transform\n * gets no depth from perspective on the element that itself rotates), and is\n * ALSO folded into the rotate transform so depth survives a bounded\n * `overflow:hidden` ancestor.\n * - The active-face attribute is NAMESPACED so velvet's generic\n * `[data-active-face]` rule can't also reach this container.\n */\nexport function flipStyles(scope: string, opts: FlipStyleOptions): string {\n const f = opts.ns;\n const kf = opts.keyframePrefix;\n return `\n ${scope} [data-${f}-flip] {\n position: relative;\n }\n /* --- 3D machinery: ONLY while the flip is animating --- */\n /* Perspective (the composited GPU context) lives on the parent viewport,\n gated to the animating window so the RESTING open face never sits on a\n perspective layer (GPU/compositing hazard: rotateY+perspective at rest\n blanks text on some Android GPUs, rasterizes soft everywhere). */\n ${scope} [data-${f}-flip-viewport][data-${f}-flip-animating] {\n perspective: 1000px;\n }\n ${scope} [data-${f}-flip][data-${f}-flip-animating] {\n transform-style: preserve-3d;\n }\n ${scope} [data-${f}-flip][data-${f}-flip-animating] > [data-face] {\n backface-visibility: hidden;\n -webkit-backface-visibility: hidden;\n }\n ${scope} [data-${f}-flip][data-${f}-flip-animating] > [data-face=\"front\"] {\n position: relative;\n }\n ${scope} [data-${f}-flip][data-${f}-flip-animating] > [data-face=\"back\"] {\n position: absolute;\n inset: 0;\n transform: rotateY(180deg);\n }\n ${scope} [data-${f}-flip][data-${f}-flip-animating][data-${f}-flip-dir=\"in\"] {\n animation: ${kf}-in var(--vc-transition-duration, 240ms)\n var(--vc-transition-easing, ease-out) forwards;\n }\n ${scope} [data-${f}-flip][data-${f}-flip-animating][data-${f}-flip-dir=\"out\"] {\n animation: ${kf}-out var(--vc-transition-duration, 240ms)\n var(--vc-transition-easing, ease-out) forwards;\n }\n @keyframes ${kf}-in {\n from { transform: perspective(1000px) rotateY(0deg); }\n to { transform: perspective(1000px) rotateY(180deg); }\n }\n @keyframes ${kf}-out {\n from { transform: perspective(1000px) rotateY(180deg); }\n to { transform: perspective(1000px) rotateY(0deg); }\n }\n /* --- RESTING state: the active face is FLAT, the inactive face is gone --- */\n ${scope} [data-${f}-flip]:not([data-${f}-flip-animating]) {\n transform: none;\n }\n ${scope} [data-${f}-flip]:not([data-${f}-flip-animating]) > [data-face] {\n position: relative;\n transform: none;\n }\n ${scope} [data-${f}-flip]:not([data-${f}-flip-animating])[data-${f}-active-face=\"front\"] > [data-face=\"back\"] {\n display: none;\n }\n ${scope} [data-${f}-flip]:not([data-${f}-flip-animating])[data-${f}-active-face=\"back\"] > [data-face=\"front\"] {\n display: none;\n }\n @media (prefers-reduced-motion: reduce) {\n ${scope} [data-${f}-flip] { animation: none; }\n }\n `;\n}\n\nexport interface FlipControllerOptions {\n /**\n * animationName prefix \u2014 the running keyframes are `${keyframePrefix}-in` and\n * `${keyframePrefix}-out`. MUST match the `keyframePrefix` passed to\n * {@link flipStyles} so `onAnimationEnd` recognises the container's animation.\n */\n keyframePrefix: string;\n /** Fallback transition window (ms) when the CSS token can't be read. */\n fallbackMs?: number;\n /** Extra ms added on top of the token before back-face teardown. */\n clearBufferMs?: number;\n}\n\ntype HostEl = ReactiveControllerHost & HTMLElement;\n\n/**\n * Owns the flip state machine: which item is open (`openId`), which face is\n * active, whether a rotation is running, and the settle timer coupled to the\n * CSS transition token. A consumer widget renders two faces (front list + back\n * detail) and drives them from this controller's state; the controller calls\n * `host.requestUpdate()` on every transition so the render stays in sync.\n *\n * The controller is deliberately UI-agnostic: focus management, row selectors,\n * and the meaning of an id are the widget's concern. It exposes exactly the\n * transitions every consumer needs \u2014 {@link openTo}, {@link back},\n * {@link reset}, {@link onAnimationEnd} \u2014 and no more.\n */\nexport class FlipController implements ReactiveController {\n private readonly host: HostEl;\n private readonly keyframePrefix: string;\n private readonly fallbackMs: number;\n private readonly clearBufferMs: number;\n\n /** Open item id (null = list view). Kept populated through a flip-OUT so the\n * back face never blanks mid-rotation; cleared once the flip settles. */\n openId: string | null = null;\n /** Which face is showing. Both faces stay mounted; this drives the CSS. */\n activeFace: FlipFace = 'front';\n /** True ONLY while a rotation runs \u2014 the window the 3D machinery exists for. */\n animating = false;\n /** Which keyframes run: 'in' = front\u2192back (open), 'out' = back\u2192front (close). */\n dir: FlipDir = 'in';\n\n // Belt timer that force-SETTLES the flip if the container's `animationend`\n // never fires; also the point a flip-OUT finally clears `openId`.\n private settleTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(host: HostEl, opts: FlipControllerOptions) {\n this.host = host;\n this.keyframePrefix = opts.keyframePrefix;\n this.fallbackMs = opts.fallbackMs ?? FLIP_TRANSITION_MS;\n this.clearBufferMs = opts.clearBufferMs ?? FLIP_CLEAR_BUFFER_MS;\n host.addController(this);\n }\n\n hostDisconnected(): void {\n this.clearTimer();\n }\n\n /**\n * Open `id` to the DETAIL (back) face and flip to it. A re-open before a\n * previous flip settled clears the pending settle and restarts cleanly. Under\n * reduced motion, settles straight to the flat back face (no animation).\n */\n openTo(id: string): void {\n this.clearTimer();\n this.openId = id;\n this.activeFace = 'back';\n if (prefersReducedMotion()) {\n this.animating = false;\n this.host.requestUpdate();\n return;\n }\n this.dir = 'in';\n this.animating = true;\n this.settleTimer = setTimeout(this.settle, this.durationMs());\n this.host.requestUpdate();\n }\n\n /**\n * Flip from the DETAIL (back) face back to the LIST (front) face. With motion,\n * `openId` is kept through the rotation (back face stays populated, never\n * blank mid-flip) and cleared on settle; under reduced motion it clears\n * synchronously.\n */\n back(): void {\n this.activeFace = 'front';\n this.clearTimer();\n if (prefersReducedMotion()) {\n this.animating = false;\n this.openId = null;\n } else {\n this.dir = 'out';\n this.animating = true;\n this.settleTimer = setTimeout(this.settle, this.durationMs());\n }\n this.host.requestUpdate();\n }\n\n /**\n * Hard reset to the front face \u2014 the open item is gone (removed / gated out),\n * so there's nothing to keep mounted through a flip-out. Clears the timer and\n * settles synchronously.\n */\n reset(): void {\n this.clearTimer();\n this.animating = false;\n this.openId = null;\n this.activeFace = 'front';\n this.host.requestUpdate();\n }\n\n /** animationend on the flip container \u2192 the rotation is done, render flat. */\n onAnimationEnd = (e: AnimationEvent): void => {\n if (\n e.animationName === `${this.keyframePrefix}-in` ||\n e.animationName === `${this.keyframePrefix}-out`\n ) {\n this.settle();\n }\n };\n\n /** Tear down the 3D machinery so the shown face renders FLAT. A flip-OUT also\n * clears `openId` here \u2014 the moment it's safe to empty the back face. */\n private settle = (): void => {\n this.clearTimer();\n this.animating = false;\n if (this.activeFace === 'front') {\n this.openId = null;\n }\n this.host.requestUpdate();\n };\n\n private clearTimer(): void {\n if (this.settleTimer !== null) {\n clearTimeout(this.settleTimer);\n this.settleTimer = null;\n }\n }\n\n /**\n * The flip transition duration in ms, coupled to the CSS. Reads the computed\n * `--vc-transition-duration` token off the host so the JS tear-down timer is\n * ALWAYS `>=` the CSS transition \u2014 a host that sets the token slower than our\n * fallback no longer blanks the back face before the flip finishes. Falls\n * back to `fallbackMs` when the token is absent/unreadable/malformed.\n */\n private durationMs(): number {\n let tokenMs = 0;\n if (typeof window !== 'undefined' && typeof window.getComputedStyle === 'function') {\n try {\n const raw = window\n .getComputedStyle(this.host)\n .getPropertyValue('--vc-transition-duration')\n .trim();\n if (raw.endsWith('ms')) tokenMs = Number.parseFloat(raw);\n else if (raw.endsWith('s')) tokenMs = Number.parseFloat(raw) * 1000;\n } catch {\n // getComputedStyle can throw on a detached element \u2014 fall through.\n }\n }\n // A malformed token (e.g. \"abcms\") makes parseFloat return NaN. Left\n // unguarded, Math.max(NaN, fallback) is NaN \u2192 setTimeout(\u2026, NaN) \u2248 0, which\n // clears openId instantly and blanks the back face mid-flip \u2014 the exact bug\n // this method exists to prevent. Treat any non-finite token as absent.\n if (!Number.isFinite(tokenMs)) tokenMs = 0;\n return Math.max(tokenMs, this.fallbackMs) + (tokenMs > 0 ? this.clearBufferMs : 0);\n }\n}\n", "/**\n * Adaptive FAQ - FAQWidgetLit\n *\n * Lit web component equivalent of FAQWidget.tsx.\n * Renders a collapsible Q&A accordion with search, category grouping,\n * feedback, and markdown rendering \u2014 all as a custom element with no\n * Shadow DOM (light DOM via createRenderRoot).\n *\n * Tag name: <syntro-faq-accordion>\n *\n * Decorator-free: uses `static override properties` (tsconfig has no\n * experimentalDecorators).\n */\n\nimport { purple } from '@syntro/design-system/tokens';\nimport { FlipController, flipStyles } from '@syntrologie/canvas-sdk/flip';\nimport { dispatchDiveDeeper } from '@syntrologie/sdk-contracts';\nimport { html, LitElement, nothing } from 'lit';\nimport { styleMap } from 'lit/directives/style-map.js';\nimport { unsafeHTML } from 'lit/directives/unsafe-html.js';\nimport { getAnswerText, renderAnswerHtml } from './answerRendering';\nimport { baseStyles, themeStyles } from './faq-styles';\nimport type { FAQWidgetRuntime } from './faq-types';\nimport { RenderHealthController, type RenderHealthProbe } from './renderHealth';\nimport type {\n FAQAnswer,\n FAQConfig,\n FAQQuestionAction,\n FeedbackConfig,\n FeedbackValue,\n} from './types';\n\n// ============================================================================\n// Utility \u2014 styleMap accepts Record<string, string | number | undefined>\n// but its TypeScript signature is overly narrow in some Lit versions.\n// Cast through unknown to avoid false positives when style values include\n// numeric CSS properties (fontWeight, lineHeight, etc.).\n// ============================================================================\n\nfunction sm(styles: Record<string, unknown>): Record<string, string> {\n return styles as unknown as Record<string, string>;\n}\n\nfunction resolveFeedbackConfig(\n feedback: boolean | FeedbackConfig | undefined\n): FeedbackConfig | null {\n if (!feedback) return null;\n if (feedback === true) return { style: 'thumbs' };\n return feedback;\n}\n\nfunction getFeedbackPrompt(feedbackConfig: FeedbackConfig): string {\n return feedbackConfig.prompt ?? 'Was this helpful?';\n}\n\n/**\n * Small deterministic string hash (djb2, base36) for content-derived atomic\n * FAQ row ids \u2014 stable per question across re-mounts so expand/feedback state\n * keyed by id sticks to the right row even if the list reorders.\n */\nfunction hashFaqId(text: string): string {\n let h = 5381;\n for (let i = 0; i < text.length; i++) {\n h = (Math.imul(h, 33) ^ text.charCodeAt(i)) >>> 0;\n }\n return h.toString(36);\n}\n\n/**\n * Escape a value for safe use inside a DOUBLE-QUOTED CSS attribute selector\n * (`[attr=\"\u2026\"]`). Question ids are content-derived (hashed) or LLM-supplied, so\n * a stray double-quote or backslash would break `querySelector`. We escape only\n * the two characters that are special inside a double-quoted string \u2014 NOT via\n * `CSS.escape`, which escapes for an *identifier* context (unquoted) and would\n * over-escape here.\n */\nfunction cssAttrEscape(value: string): string {\n return value.replace(/[\"\\\\]/g, '\\\\$&');\n}\n\nfunction resolveTheme(theme: FAQConfig['theme'] | undefined): 'light' | 'dark' {\n if (theme && theme !== 'auto') return theme;\n if (typeof window !== 'undefined') {\n return window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n }\n return 'light';\n}\n\n// The default (shared) instanceId. Two default-id tiles would cross-scope their\n// injected <style> (both scoped to `[data-adaptive-id=\"faq-widget\"]`), so when\n// the id is left at the default we fall back to a per-element generated uid.\nconst DEFAULT_INSTANCE_ID = 'faq-widget';\n\n// Monotonic counter for generated scope uids (default-instanceId fallback).\nlet _faqUidCounter = 0;\n\n// ============================================================================\n// FAQAccordionElement \u2014 Lit element\n// ============================================================================\n\n/**\n * <syntro-faq-accordion> \u2014 light-DOM Lit web component.\n *\n * Set properties imperatively (no attribute serialisation for objects):\n * el.faqConfig = { expandBehavior: 'single', ... };\n * el.runtime = runtimeInstance;\n * el.instanceId = 'my-faq';\n */\nexport class FAQAccordionElement extends LitElement {\n // -----------------------------------------------------------------------\n // Reactive properties (no decorators \u2014 tsconfig forbids experimentalDecorators)\n // -----------------------------------------------------------------------\n\n static override properties = {\n // Public API \u2014 set from the outside\n faqConfig: { attribute: false },\n runtime: { attribute: false },\n instanceId: { type: String },\n\n // Internal reactive state (prefixed with _ to signal \"private\").\n // The flip state machine \u2014 open item, active face, animating flag, direction,\n // and the token-coupled settle timer \u2014 lives in a shared FlipController (see\n // {@link _flip}); the controller drives re-renders via host.requestUpdate().\n // `_openId` / `_activeFace` / `_flipAnimating` / `_flipDir` are exposed as\n // getters over that controller (below) so the render + tests read the same\n // state without duplicating the machinery.\n _highlightId: { state: true },\n _searchQuery: { state: true },\n _feedbackState: { state: true },\n _hoveredId: { state: true },\n };\n\n // -----------------------------------------------------------------------\n // Property declarations\n // -----------------------------------------------------------------------\n\n faqConfig: FAQConfig = {\n expandBehavior: 'single',\n searchable: false,\n theme: 'auto',\n actions: [],\n };\n\n runtime: FAQWidgetRuntime | null = null;\n\n instanceId: string = DEFAULT_INSTANCE_ID;\n\n // Per-element generated scope uid, used ONLY when `instanceId` is left at the\n // shared default so two default-id tiles don't cross-scope their injected\n // <style> (both would otherwise target `[data-adaptive-id=\"faq-widget\"]`).\n // Generated lazily and once per element (see {@link _scopeId}).\n private _generatedUid: string | null = null;\n\n // Internal state.\n // The master\u2192detail takeover swaps the whole widget to a single Q&A (so a long\n // answer never overflows the bounded velvet tile) via a FLIP: front = the\n // question list, back = the open question's detail. The flip's state machine \u2014\n // open id, active face, animating flag, direction, and the token-coupled\n // settle timer with its GPU-teardown/reduced-motion contract \u2014 lives in the\n // shared canvas FlipController; the FAQ keeps only its own concerns (focus,\n // ids, content) here. `sc-faq-flip` keyframes + the `faq` attribute namespace\n // match what {@link _renderFlipStyles} emits.\n private readonly _flip = new FlipController(this, { keyframePrefix: 'sc-faq-flip' });\n\n // Read-through accessors so the render + the flip test-suite see the flip\n // state without duplicating it. `_openId` null = LIST view; a value = the open\n // question. `_activeFace` drives the CSS rotateY.\n get _openId(): string | null {\n return this._flip.openId;\n }\n get _activeFace(): 'front' | 'back' {\n return this._flip.activeFace;\n }\n get _flipAnimating(): boolean {\n return this._flip.animating;\n }\n\n _highlightId: string | null = null;\n _searchQuery: string = '';\n _feedbackState: Map<string, FeedbackValue> = new Map();\n _hoveredId: string | null = null;\n\n // Focus management (a11y): after a flip renders, move focus to the face that\n // just became active \u2014 the \u2039 Back button on flip-to-back, and the originating\n // question row on flip-to-front \u2014 so keyboard/AT users follow the takeover.\n // Consumed (and cleared) in `updated()`. `_focusReturnId` remembers WHICH row\n // opened the detail so focus returns to that exact row, not just the first.\n private _pendingFocus: 'front' | 'back' | null = null;\n private _focusReturnId: string | null = null;\n\n // Subscription cleanup handles\n private _unsubContext: (() => void) | null = null;\n private _unsubAccumulator: (() => void) | null = null;\n private _unsubStateChanged: (() => void) | null = null;\n private _unsubCta: (() => void) | null = null;\n private _unsubDeepLink: (() => void) | null = null;\n private _unsubSessionMetrics: (() => void) | null = null;\n private _highlightTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Compositional-child wiring \u2014 faq:question items the LLM mounts into this\n // accordion's tile arrive as `element.compositional_append` events (see\n // adaptive-chatbot's ItemHandler). Without this subscription the questions\n // are published into the void and the accordion renders empty.\n private _unsubCompositional: (() => void) | null = null;\n private _tileId: string | null = null;\n /** Instance ids already appended via the compositional bus \u2014 dedups\n * the subscribe-then-replay path (and re-subscribes on runtime change). */\n private _llmAppendedIds: Set<string> = new Set();\n\n constructor() {\n super();\n // Runtime self-detection: emits app:faq_accordion:blank / :clipped after\n // paint if the mounted accordion has area but paints no content (the blank\n // FAQ tile bug). Self-registers via host.addController \u2014 no stored ref.\n new RenderHealthController(this, () => this.#healthProbe());\n }\n\n /** Measure only the SETTLED accordion (at least one visible question). An\n * empty accordion renders `nothing` (runtime) or a reassurance box (no\n * runtime) and is never \"blank\". NON-PII stateKey. */\n #healthProbe(): RenderHealthProbe | null {\n const visible = this._visibleQuestions();\n if (visible.length === 0) return null;\n const root = this.querySelector('[data-adaptive-type=\"adaptive-faq\"]');\n if (!root) return null;\n return {\n root,\n stateKey: `faq|${visible.length}|${this._activeFace}`,\n category: 'faq_accordion',\n widgetKind: 'adaptive-faq:accordion',\n };\n }\n\n // -----------------------------------------------------------------------\n // Light DOM \u2014 no Shadow DOM so CSS variables from the host page apply\n // -----------------------------------------------------------------------\n\n override createRenderRoot() {\n return this;\n }\n\n // -----------------------------------------------------------------------\n // Lifecycle\n // -----------------------------------------------------------------------\n\n override connectedCallback() {\n super.connectedCallback();\n this._subscribeAll();\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._unsubscribeAll();\n if (this._highlightTimer !== null) {\n clearTimeout(this._highlightTimer);\n this._highlightTimer = null;\n }\n // The flip's settle timer is torn down by the FlipController's own\n // hostDisconnected \u2014 no manual clear needed here.\n }\n\n // Re-subscribe when runtime changes (property may be set after connectedCallback)\n override updated(changedProps: Map<string, unknown>) {\n if (changedProps.has('runtime')) {\n this._unsubscribeAll();\n this._subscribeAll();\n }\n this._applyPendingFocus();\n }\n\n /**\n * Move focus to the face that just became active after a flip (a11y). On\n * flip-to-back, focus the \u2039 Back button; on flip-to-front, focus the question\n * row that originally opened the detail (so keyboard/AT users land back where\n * they were). No-op when nothing is pending or the target isn't in the DOM\n * yet (the flip-to-front row may not exist if the list changed).\n */\n private _applyPendingFocus() {\n if (this._pendingFocus === null) return;\n const target = this._pendingFocus;\n this._pendingFocus = null;\n if (target === 'back') {\n const back = this.querySelector<HTMLElement>('[data-faq-back]');\n back?.focus();\n return;\n }\n // Flip to front: return focus to the originating row's button.\n const id = this._focusReturnId;\n this._focusReturnId = null;\n if (!id) return;\n const row = this.querySelector<HTMLElement>(`[data-faq-item-id=\"${cssAttrEscape(id)}\"] button`);\n row?.focus();\n }\n\n // -----------------------------------------------------------------------\n // Subscription management\n // -----------------------------------------------------------------------\n\n private _subscribeAll() {\n if (!this.runtime) return;\n\n // Context changes \u2192 force re-render\n this._unsubContext = this.runtime.context.subscribe(() => {\n this._reevaluateOpenAndUpdate();\n });\n\n // Accumulator changes \u2192 force re-render (for event_count triggerWhen)\n if (this.runtime.accumulator?.subscribe) {\n this._unsubAccumulator = this.runtime.accumulator.subscribe(() => {\n this._reevaluateOpenAndUpdate();\n });\n }\n\n // Session metric changes \u2192 force re-render (for session_metric triggerWhen)\n if (this.runtime.sessionMetrics?.subscribe) {\n this._unsubSessionMetrics = this.runtime.sessionMetrics.subscribe(() => {\n this._reevaluateOpenAndUpdate();\n });\n }\n\n if (this.runtime.events.subscribe) {\n // State changes \u2192 force re-render (for state_equals triggerWhen)\n this._unsubStateChanged = this.runtime.events.subscribe({ names: ['state.changed'] }, () => {\n this._reevaluateOpenAndUpdate();\n });\n\n // faq:open:* events from overlay CTA clicks\n // Check EventBus history for pending faq:open events\n if (this.runtime.events.getRecent) {\n const recentEvents = this.runtime.events.getRecent(\n { patterns: ['^action\\\\.tooltip_cta_clicked$', '^action\\\\.modal_cta_clicked$'] },\n 10\n );\n const pendingEvent = recentEvents\n .filter((e) => {\n const actionId = e.props?.actionId;\n return typeof actionId === 'string' && actionId.startsWith('faq:open:');\n })\n .pop();\n\n if (pendingEvent && Date.now() - pendingEvent.ts < 10000) {\n const questionId = (pendingEvent.props!.actionId as string).replace('faq:open:', '');\n // Takeover: flip the widget to the DETAIL (back) face for that question\n // (previously this expanded the row inline).\n this._openToDetail(questionId);\n }\n }\n\n this._unsubCta = this.runtime.events.subscribe(\n { patterns: ['^action\\\\.tooltip_cta_clicked$', '^action\\\\.modal_cta_clicked$'] },\n (event) => {\n // Same staleness guard as the history scan above: some EventBus\n // implementations replay recent history into new subscribers. Without\n // this guard the history scan AND this live handler both fire for the\n // same old event \u2014 a spurious second `canvas.requestOpen`. Skip any\n // event older than ~10s (a fresh click always has ts\u2248now).\n const ts = (event as { ts?: number }).ts;\n if (typeof ts === 'number' && Date.now() - ts >= 10000) return;\n const actionId = event.props?.actionId;\n if (typeof actionId !== 'string' || !actionId.startsWith('faq:open:')) return;\n const questionId = actionId.replace('faq:open:', '');\n // Takeover: flip to the question's detail (back) face instead of inline expand.\n this._openToDetail(questionId);\n this.runtime?.events.publish('canvas.requestOpen');\n }\n );\n }\n\n // notification.deep_link events\n if (this.runtime.events.subscribe) {\n const handleDeepLink = (event: { name: string; props?: Record<string, unknown> }) => {\n const tileId = event.props?.tileId as string | undefined;\n const itemId = event.props?.itemId as string | undefined;\n if (tileId !== this.instanceId) return;\n if (!itemId) return;\n\n // Takeover: flip to the question's detail (back) face (previously inline expand).\n this._openToDetail(itemId);\n this._highlightId = itemId;\n\n if (this._highlightTimer !== null) clearTimeout(this._highlightTimer);\n this._highlightTimer = setTimeout(() => {\n this._highlightId = null;\n this._highlightTimer = null;\n }, 1500);\n };\n\n // Check recent events (may have fired before widget mounted)\n if (this.runtime.events.getRecent) {\n const recent = this.runtime.events.getRecent({ names: ['notification.deep_link'] }, 5);\n const pending = recent\n .filter((e) => (e.props?.tileId as string) === this.instanceId && e.props?.itemId)\n .pop();\n if (pending && Date.now() - pending.ts < 10000) {\n handleDeepLink(pending);\n }\n }\n\n this._unsubDeepLink = this.runtime.events.subscribe(\n { names: ['notification.deep_link'] },\n (event) => {\n // Mirror the history-scan staleness guard: skip replayed events older\n // than ~10s so a bus that replays history into new subscribers doesn't\n // double-handle the same deep link (history scan + live handler).\n const ts = (event as { ts?: number }).ts;\n if (typeof ts === 'number' && Date.now() - ts >= 10000) return;\n handleDeepLink(event);\n }\n );\n }\n\n // Compositional children: faq:question items mounted into this\n // accordion's tile. The ItemHandler publishes one event per kind-\n // agnostic item mount; we filter to our tile and route into\n // `faqConfig.actions`. Mirrors AdaptiveChipsStrip's CompositionalContainer.\n this._subscribeCompositional();\n }\n\n /**\n * Subscribe to `element.compositional_*` events targeting this accordion's\n * tile, and ask the element store to replay any items it already holds for\n * us (covers the case where the accordion mounts AFTER the item did \u2014 the\n * inline-slot hydration race that otherwise silently drops the question).\n */\n private _subscribeCompositional() {\n const bus = this.runtime?.events;\n if (!bus?.subscribe) return;\n this._tileId = this._resolveTileId();\n if (!this._tileId) return;\n const tileId = this._tileId;\n this._unsubCompositional = bus.subscribe((event) => {\n const props = event.props ?? {};\n if (props.tile_id !== tileId) return;\n const instanceId = String(props.instance_id ?? '');\n if (event.name === 'element.compositional_append') {\n this._insertItem(\n instanceId,\n props.item as FAQQuestionAction,\n (props.position as 'append' | 'prepend') ?? 'append'\n );\n } else if (event.name === 'element.compositional_patch') {\n this._patchItem(instanceId, props.item as FAQQuestionAction);\n } else if (event.name === 'element.compositional_remove') {\n this._removeItem(instanceId);\n }\n });\n bus.publish('element.compositional_replay_request', { tile_id: tileId });\n }\n\n /** Tile id for compositional filtering: the enclosing tile card's\n * `data-tile-id` (set by SyntroTileCard), falling back to `instanceId`\n * when the accordion is mounted outside a tile card. */\n private _resolveTileId(): string | null {\n const anchor = this.closest<HTMLElement>('[data-tile-id]');\n const fromDom = anchor?.getAttribute('data-tile-id');\n if (fromDom) return fromDom;\n return this.instanceId && this.instanceId !== DEFAULT_INSTANCE_ID ? this.instanceId : null;\n }\n\n /** Append (or prepend) a faq:question the LLM mounted. The wire shape is a\n * fully-formed `{kind, config}` envelope, so we store it verbatim. */\n private _insertItem(\n instanceId: string,\n item: FAQQuestionAction,\n position: 'append' | 'prepend'\n ): void {\n if (!instanceId || !item?.config?.id) return;\n if (this._llmAppendedIds.has(instanceId)) return; // dedup replay / re-subscribe\n this._llmAppendedIds.add(instanceId);\n const actions = this.faqConfig.actions ?? [];\n const next = position === 'prepend' ? [item, ...actions] : [...actions, item];\n // Reassign so Lit sees the change (faqConfig is attribute:false).\n this.faqConfig = { ...this.faqConfig, actions: next };\n }\n\n /** Replace an existing question's content (full replacement). */\n private _patchItem(instanceId: string, item: FAQQuestionAction): void {\n if (!instanceId || !item?.config) return;\n const actions = this.faqConfig.actions ?? [];\n const idx = actions.findIndex((a) => a.config.id === instanceId);\n if (idx < 0) return;\n const next = [...actions];\n next[idx] = item;\n // If the patch changes the open question's id, the flip's `openId` would\n // point at a gone id and the detail view would fall back to the list.\n // Re-point it (no flip \u2014 same face, new id) so the open question stays open\n // through a patch.\n if (this._flip.openId === instanceId && item.config.id !== instanceId) {\n this._flip.openId = item.config.id;\n }\n this.faqConfig = { ...this.faqConfig, actions: next };\n // A patch can also gate the open question OUT (e.g. a new `triggerWhen` that\n // now evaluates false, or content that drops it from the ordered list). If\n // the open id no longer resolves, reset BOTH `_openId` and `_activeFace` to\n // the front. Without resetting `_activeFace`, render() would locally force\n // the front face while leaving state on 'back' \u2014 so a later re-appearance of\n // the question would snap straight to the back face with no flip animation.\n this._resetFaceIfOpenStale();\n }\n\n /** Remove a question by instance id. */\n private _removeItem(instanceId: string): void {\n if (!instanceId) return;\n const actions = this.faqConfig.actions ?? [];\n const next = actions.filter((a) => a.config.id !== instanceId);\n if (next.length === actions.length) return;\n this._llmAppendedIds.delete(instanceId);\n // Removing the currently-open question would leave `_openId` pointing at a\n // gone id. Flip back to the list view instead of resolving to an empty\n // detail. Clear synchronously (the source question is gone \u2014 there's nothing\n // to keep mounted through a flip-out) and cancel any pending back-clear.\n if (this._flip.openId === instanceId) {\n // The source question is gone \u2014 nothing to keep mounted through a flip-out,\n // so hard-reset the flip to the front face.\n this._flip.reset();\n // The open detail set _pendingFocus/_focusReturnId (a11y focus return to\n // the originating row). That row is now gone \u2014 clear the dead focus intent\n // so the next render's _applyPendingFocus doesn't chase a missing element.\n this._pendingFocus = null;\n this._focusReturnId = null;\n }\n this.faqConfig = { ...this.faqConfig, actions: next };\n }\n\n /**\n * Re-evaluate whether the open question is still visible, then re-render.\n * Called from the subscription-driven re-render paths (context / accumulator /\n * sessionMetric changes). Those subscriptions can flip an open question's\n * `triggerWhen` to false WITHOUT a compositional patch/remove \u2014 so without\n * this hook the stale-open reset (`_resetFaceIfOpenStale`, otherwise only\n * invoked on patch/remove) would never fire on the triggerWhen path, and an\n * open question gated out this way would snap straight to the back face on\n * re-appearance. `_resetFaceIfOpenStale` mutates reactive state (which itself\n * schedules an update), and we still `requestUpdate()` for the no-op case.\n */\n private _reevaluateOpenAndUpdate(): void {\n this._resetFaceIfOpenStale();\n this.requestUpdate();\n }\n\n /**\n * If `_openId` no longer resolves against the visible/ordered list (gated out\n * by triggerWhen, removed, or patched away), reset BOTH `_openId` and\n * `_activeFace` to the front (and clear the pending a11y focus intent).\n * Resolves against the SAME `ordered` list render() uses (visible + ordered,\n * NOT search-filtered). Also cancels any pending back-clear timer. Invoked on\n * compositional patch/remove AND on the subscription-driven re-render paths\n * (via {@link _reevaluateOpenAndUpdate}) so the triggerWhen-gated-out case is\n * genuinely covered. Keeping `_activeFace` on 'back' after the open id went\n * stale is the bug this guards: render() would force the front face locally\n * but leave state on 'back', so a later re-appearance snaps to the back face\n * with no animation.\n */\n private _resetFaceIfOpenStale(): void {\n if (this._flip.openId === null) return;\n const ordered = this._orderedQuestions(this._visibleQuestions());\n const stillPresent = ordered.some((q) => q.config.id === this._flip.openId);\n if (stillPresent) return;\n this._flip.reset();\n // The open detail set _pendingFocus/_focusReturnId; the originating row is\n // now gated out, so clear the dead focus intent (see _removeItem).\n this._pendingFocus = null;\n this._focusReturnId = null;\n }\n\n private _unsubscribeAll() {\n this._unsubContext?.();\n this._unsubAccumulator?.();\n this._unsubSessionMetrics?.();\n this._unsubStateChanged?.();\n this._unsubCta?.();\n this._unsubDeepLink?.();\n this._unsubCompositional?.();\n this._unsubContext = null;\n this._unsubAccumulator = null;\n this._unsubSessionMetrics = null;\n this._unsubStateChanged = null;\n this._unsubCta = null;\n this._unsubDeepLink = null;\n this._unsubCompositional = null;\n }\n\n // -----------------------------------------------------------------------\n // Handlers\n // -----------------------------------------------------------------------\n\n /**\n * Master\u2192detail takeover: tapping a list row opens its single-Q&A detail\n * view (the whole widget swaps), so a long answer fills \u2014 and never\n * overflows \u2014 the bounded velvet tile. There is no simultaneous multi-expand;\n * one question owns the tile at a time.\n */\n private _handleOpen(id: string) {\n this._openToDetail(id);\n\n this.runtime?.events.publish('faq:toggled', {\n instanceId: this.instanceId,\n questionId: id,\n expanded: true,\n timestamp: Date.now(),\n });\n }\n\n /**\n * Put the widget into the DETAIL (back) face for `id` and flip to it. Shared\n * by the tap handler and the event-driven entry points (faq:open CTA,\n * notification.deep_link) so every \"open this question\" path flips the same\n * way. Cancels any pending back-clear timer (a re-open before the flip-out\n * finishes must keep the detail mounted). Remembers the originating question\n * id so focus can return to that exact row on flip-to-front (a11y), and moves\n * focus to the \u2039 Back button once the back face renders.\n */\n private _openToDetail(id: string) {\n // Remember the originating row so focus can return to it on flip-to-front\n // (a11y), and move focus to the \u2039 Back button once the back face renders.\n // The flip itself (settle timer, reduced-motion, GPU teardown) is the shared\n // FlipController's job.\n this._pendingFocus = 'back';\n this._focusReturnId = id;\n this._flip.openTo(id);\n }\n\n /** Back affordance: FLIP from the detail (back) face to the list (front) face.\n * The FlipController keeps `openId` populated through the rotation (never\n * blank mid-flip) and clears it on settle \u2014 with reduced motion it clears\n * synchronously. Focus returns to the originating question row. */\n private _handleBack() {\n // Capture the open id BEFORE the flip (reduced motion clears it synchronously).\n const id = this._flip.openId;\n // Return focus to the row the visitor opened from (a11y).\n this._pendingFocus = 'front';\n this._flip.back();\n\n if (id !== null) {\n this.runtime?.events.publish('faq:toggled', {\n instanceId: this.instanceId,\n questionId: id,\n expanded: false,\n timestamp: Date.now(),\n });\n }\n }\n\n private _handleFeedback(itemId: string, question: string, value: FeedbackValue) {\n const next = new Map(this._feedbackState);\n next.set(itemId, value);\n this._feedbackState = next;\n\n this.runtime?.events.publish('faq:feedback', { itemId, question, value });\n }\n\n // -----------------------------------------------------------------------\n // Computed helpers\n // -----------------------------------------------------------------------\n\n /**\n * Unified render list. Merges compositionally-appended rows\n * (`faqConfig.actions`, the container-then-stream path) with atomically\n * authored rows (`faqConfig.questions`, the struct_list path) normalized\n * into the same `FAQQuestionAction` shape. The atomic path is how the LLM\n * mounts a complete FAQ in one call \u2014 so it renders whole, never empty.\n */\n private _allQuestions(): FAQQuestionAction[] {\n const compositional = this.faqConfig.actions ?? [];\n // Content-derived id (not positional). Expand/feedback state is keyed by id;\n // if the LLM re-mounts the FAQ with a reordered or shortened list, a\n // positional `atomic-${i}` would re-point that state at a different\n // question. Hashing the question text keeps the id stable per question\n // across re-mounts. A `-N` suffix disambiguates same-text rows (including\n // empty strings, which all hash to the same value) so they never share\n // expand/feedback state \u2014 stable as long as their relative order is.\n const seen = new Map<string, number>();\n const atomic = (this.faqConfig.questions ?? []).map((q): FAQQuestionAction => {\n let id = `atomic-${hashFaqId(q.question)}`;\n const dup = seen.get(id) ?? 0;\n seen.set(id, dup + 1);\n if (dup > 0) id = `${id}-${dup}`;\n return {\n kind: 'faq:question',\n config: {\n id,\n question: q.question,\n answer: q.answer,\n category: q.category,\n },\n };\n });\n return [...atomic, ...compositional];\n }\n\n private _visibleQuestions(): FAQQuestionAction[] {\n return this._allQuestions().filter((q) => {\n if (!q.triggerWhen) return true;\n if (!this.runtime) return true;\n const result = this.runtime.evaluateSync<boolean>(q.triggerWhen);\n return result.value;\n });\n }\n\n private _orderedQuestions(visible: FAQQuestionAction[]): FAQQuestionAction[] {\n if (this.faqConfig.ordering === 'priority') {\n return [...visible].sort((a, b) => (b.config.priority ?? 0) - (a.config.priority ?? 0));\n }\n return visible;\n }\n\n private _filteredQuestions(ordered: FAQQuestionAction[]): FAQQuestionAction[] {\n const q = this._searchQuery.trim().toLowerCase();\n if (!this.faqConfig.searchable || !q) return ordered;\n return ordered.filter(\n (item) =>\n item.config.question.toLowerCase().includes(q) ||\n getAnswerText(item.config.answer).toLowerCase().includes(q) ||\n item.config.category?.toLowerCase().includes(q)\n );\n }\n\n private _categoryGroups(\n filtered: FAQQuestionAction[]\n ): Map<string | undefined, FAQQuestionAction[]> {\n const groups = new Map<string | undefined, FAQQuestionAction[]>();\n for (const item of filtered) {\n const cat = item.config.category;\n if (!groups.has(cat)) groups.set(cat, []);\n groups.get(cat)!.push(item);\n }\n return groups;\n }\n\n // -----------------------------------------------------------------------\n // Render helpers\n // -----------------------------------------------------------------------\n\n /**\n * The `data-adaptive-id` scope for this element's injected <style>. When\n * `instanceId` is left at the shared default (`'faq-widget'`), two tiles would\n * both scope to `[data-adaptive-id=\"faq-widget\"]` and cross-style each other.\n * In that case we fall back to a per-element generated uid so each default tile\n * gets its own scope. A caller-provided instanceId is used verbatim.\n */\n private get _scopeId(): string {\n if (this.instanceId && this.instanceId !== DEFAULT_INSTANCE_ID) return this.instanceId;\n if (this._generatedUid === null) {\n _faqUidCounter += 1;\n this._generatedUid = `faq-${Date.now().toString(36)}-${_faqUidCounter}`;\n }\n return this._generatedUid;\n }\n\n /**\n * Self-injected flip CSS, scoped to THIS instance (`[data-adaptive-id=\"\u2026\"]`)\n * so it can't leak onto a sibling widget. The FAQ tile is a light-DOM widget\n * mounted across several canvases (velvet, default, onflow) and standalone;\n * velvet ships a GENERIC `[data-active-face]` flip rule (for the product card),\n * but the other hosts don't \u2014 so the FAQ carries the SAME 3D-flip technique,\n * now from the shared canvas primitive ({@link flipStyles}) rather than a local\n * copy. The `faq` namespace keeps it on `data-faq-*` / `sc-faq-flip-*` so\n * velvet's generic rule can't double-apply (the GPU/perspective + resting-flat\n * contract lives in the primitive). MUST match the FlipController's\n * `keyframePrefix: 'sc-faq-flip'`.\n */\n private _renderFlipStyles() {\n const scope = `[data-adaptive-id=\"${this._scopeId}\"]`;\n const css = flipStyles(scope, { ns: 'faq', keyframePrefix: 'sc-faq-flip' });\n return html`<style>${css}</style>`;\n }\n\n private _renderAnswer(answer: FAQAnswer) {\n const html_str = renderAnswerHtml(answer);\n // Item 5: cap the answer render to a few readable lines (a declared\n // line-clamp ellipsis box). A longer answer isn't scrolled in the cramped\n // takeover \u2014 \"Dive deeper\" carries the visitor to the full answer in chat.\n return html`<div style=${styleMap(sm(baseStyles.detailAnswerClamp))} data-faq-markdown=\"\">${unsafeHTML(html_str)}</div>`;\n }\n\n private _renderFeedback(\n item: FAQQuestionAction,\n feedbackConfig: FeedbackConfig,\n feedbackValue: FeedbackValue | undefined,\n theme: 'light' | 'dark'\n ) {\n const colors = themeStyles[theme];\n const feedbackStyle = { ...baseStyles.feedback, ...colors.feedbackPrompt };\n\n return html`\n <div style=${styleMap(sm(feedbackStyle))}>\n <span>${getFeedbackPrompt(feedbackConfig)}</span>\n <button\n type=\"button\"\n style=${styleMap(\n sm({\n ...baseStyles.feedbackButton,\n ...(feedbackValue === 'up' ? baseStyles.feedbackButtonSelected : {}),\n })\n )}\n aria-label=\"Thumbs up\"\n @click=${() => this._handleFeedback(item.config.id, item.config.question, 'up')}\n >\\uD83D\\uDC4D</button>\n <button\n type=\"button\"\n style=${styleMap(\n sm({\n ...baseStyles.feedbackButton,\n ...(feedbackValue === 'down' ? baseStyles.feedbackButtonSelected : {}),\n })\n )}\n aria-label=\"Thumbs down\"\n @click=${() => this._handleFeedback(item.config.id, item.config.question, 'down')}\n >\\uD83D\\uDC4E</button>\n </div>\n `;\n }\n\n /**\n * LIST-view row. A tappable question with its chevron \\u2014 but NO inline answer\n * body. Tapping it takes over the tile with the question's detail view (see\n * {@link _renderDetail}). Keeps the per-row look/markers/highlight from the\n * old accordion minus the inline-expanded answer that overflowed the tile.\n */\n private _renderItem(item: FAQQuestionAction, isLast: boolean, theme: 'light' | 'dark') {\n const colors = themeStyles[theme];\n const isHighlighted = this._highlightId === item.config.id;\n const isHovered = this._hoveredId === item.config.id;\n\n const itemStyle = {\n ...baseStyles.item,\n ...colors.item,\n ...(isHighlighted\n ? {\n boxShadow: `0 0 0 2px ${purple[4]}, 0 0 12px rgba(106, 89, 206, 0.4)`,\n transition: 'box-shadow 0.3s ease',\n }\n : {}),\n ...(!isLast ? { borderBottom: 'var(--sc-content-item-divider, none)' } : {}),\n };\n\n const questionStyle = {\n ...baseStyles.question,\n ...colors.question,\n ...(isHovered ? colors.questionHover : {}),\n };\n\n const chevronStyle = {\n ...baseStyles.chevron,\n transform: 'rotate(0deg)',\n };\n\n return html`\n <div\n style=${styleMap(sm(itemStyle))}\n data-faq-item-id=${item.config.id}\n >\n <button\n type=\"button\"\n style=${styleMap(sm(questionStyle))}\n aria-expanded=${false}\n @click=${() => this._handleOpen(item.config.id)}\n @mouseenter=${() => {\n this._hoveredId = item.config.id;\n }}\n @mouseleave=${() => {\n this._hoveredId = null;\n }}\n >\n <span>${item.config.question}</span>\n <span style=${styleMap(sm(chevronStyle))}>\\u203A</span>\n </button>\n </div>\n `;\n }\n\n private _renderItems(items: FAQQuestionAction[], theme: 'light' | 'dark') {\n return items.map((item, index) => this._renderItem(item, index === items.length - 1, theme));\n }\n\n /**\n * DETAIL view \\u2014 the master\\u2192detail takeover. Renders ONLY the open question:\n * a back affordance, the question prominently, and its full answer via the\n * SAME {@link _renderAnswer}/`renderAnswerHtml` path (all answer formats still\n * work). The answer area scrolls if extremely long, so a single answer fills\n * the bounded velvet tile and never pushes content past the `overflow:hidden`\n * clip.\n */\n private _renderDetail(\n item: FAQQuestionAction,\n theme: 'light' | 'dark',\n feedbackConfig: FeedbackConfig | null\n ) {\n const colors = themeStyles[theme];\n\n const backStyle = {\n ...baseStyles.detailBack,\n ...colors.question,\n };\n const detailQuestionStyle = {\n ...baseStyles.detailQuestion,\n ...colors.question,\n };\n const detailAnswerStyle = {\n ...baseStyles.detailAnswer,\n ...colors.answer,\n };\n\n return html`\n <div style=${styleMap(sm(baseStyles.detail))} data-faq-detail=${item.config.id}>\n <button\n type=\"button\"\n style=${styleMap(sm(backStyle))}\n data-faq-back=\"\"\n aria-label=\"Back to questions\"\n @click=${() => this._handleBack()}\n >\n \\u2039 Back to questions\n </button>\n\n <div style=${styleMap(sm(detailQuestionStyle))} data-faq-detail-question>${item.config.question}</div>\n\n <div style=${styleMap(sm(detailAnswerStyle))} aria-hidden=${false}>\n ${this._renderAnswer(item.config.answer)}\n <div style=${styleMap(sm(baseStyles.detailFooter))}>\n <button\n type=\"button\"\n style=${styleMap(sm(baseStyles.detailDiveDeeper))}\n data-faq-dive-deeper\n @click=${() => this._handleDiveDeeper(item)}\n >Dive deeper \u203A</button>\n </div>\n ${\n feedbackConfig\n ? this._renderFeedback(\n item,\n feedbackConfig,\n this._feedbackState.get(item.config.id),\n theme\n )\n : nothing\n }\n </div>\n </div>\n `;\n }\n\n /**\n * \"Dive deeper\" (items 4/5) \u2014 hand the open question's context back to the\n * CHAT as a deep-dive turn, so a clamped answer never leaves the visitor\n * stuck. Canvas-agnostic: dispatches the neutral DIVE_DEEPER_EVENT; the\n * chat-bar mountable turns it into a user-visible turn. The answer text rides\n * along as (trimmed) context so the agent grounds its fuller reply.\n */\n private _handleDiveDeeper(item: FAQQuestionAction) {\n const question = item.config.question;\n const answerText = getAnswerText(item.config.answer);\n dispatchDiveDeeper({\n // State an UNMET NEED \u2014 never re-ask the question (BUG-1784153527).\n // `Tell me more about: ${question}` re-asked the very question whose\n // answer was already on screen, so the agent just answered it again\n // instead of asking what was missing. The question rides in `title` +\n // the turn-origin envelope.\n prompt: \"I'd like to dive deeper on this answer, I didn't see what I needed.\",\n title: question,\n context: answerText ? answerText.slice(0, 500) : undefined,\n });\n }\n\n // -----------------------------------------------------------------------\n // Render\n // -----------------------------------------------------------------------\n\n override render() {\n const theme = resolveTheme(this.faqConfig.theme);\n const colors = themeStyles[theme];\n const feedbackConfig = resolveFeedbackConfig(this.faqConfig.feedback);\n\n const visible = this._visibleQuestions();\n const ordered = this._orderedQuestions(visible);\n const filtered = this._filteredQuestions(ordered);\n const hasCategories = filtered.some((q) => q.config.category);\n const groups = hasCategories ? this._categoryGroups(filtered) : null;\n\n const containerStyle = {\n ...baseStyles.container,\n ...colors.container,\n };\n\n const emptyStateStyle = {\n ...baseStyles.emptyState,\n ...colors.emptyState,\n };\n\n const categoryHeaderStyle = {\n ...baseStyles.categoryHeader,\n ...colors.categoryHeader,\n };\n\n const searchInputStyle = {\n ...baseStyles.searchInput,\n ...colors.searchInput,\n };\n\n // Empty state \u2014 no visible questions at all.\n //\n // A runtime-backed FAQ is a compositional container: its `faq:question`\n // items stream in as `element.compositional_append` events after mount\n // (and a replay request). Rendering the \"all set\" reassurance box while\n // we're still empty flashes an empty shell on every mount \u2014 and leaves\n // an orphaned empty box if the agent mounted a container it never filled\n // (the recurring \"empty FAQ showed up then disappeared\" bug). Render\n // nothing until the first question lands. The reassurance copy is only\n // for a standalone (runtime-less) FAQ configured directly with no items.\n if (visible.length === 0) {\n if (this.runtime) return nothing;\n return html`\n <div\n style=${styleMap(sm(containerStyle))}\n data-adaptive-id=${this._scopeId}\n data-adaptive-type=\"adaptive-faq\"\n >\n <div style=${styleMap(sm(emptyStateStyle))}>\n You're all set for now! We'll surface answers here when they're relevant to what\n you're doing.\n </div>\n </div>\n `;\n }\n\n // Two-face FLIP takeover. BOTH faces are always in the DOM (a flip animates\n // between two live faces): front = the question LIST, back = the open\n // question's DETAIL. `_activeFace` drives the CSS rotateY via\n // `data-active-face` \u2014 the SAME mechanism the product card uses.\n //\n // We resolve `_openId` against the *visible/ordered* list (`ordered`), NOT\n // the search-filtered list (`filtered`) \u2014 search is a list-view concern\n // only, so a `faq:open` for a question that doesn't match an active\n // `_searchQuery` must still open its detail. A genuinely stale id (gated out\n // via triggerWhen, or removed) is absent from `ordered`, so the back face\n // renders empty and the container stays on the front face.\n const openItem =\n this._openId !== null ? ordered.find((q) => q.config.id === this._openId) : undefined;\n // If the open id resolved to nothing (stale), fall back to the front face so\n // we never show a rotated-but-empty back face.\n const activeFace = openItem ? this._activeFace : 'front';\n const backActive = activeFace === 'back';\n const flipAnimating = this._flipAnimating;\n\n return html`\n <div\n style=${styleMap(sm(containerStyle))}\n data-adaptive-id=${this._scopeId}\n data-adaptive-type=\"adaptive-faq\"\n >\n ${this._renderFlipStyles()}\n <div data-faq-flip-viewport ?data-faq-flip-animating=${flipAnimating}>\n <div\n data-faq-flip\n data-faq-active-face=${activeFace}\n ?data-faq-flip-animating=${flipAnimating}\n data-faq-flip-dir=${flipAnimating ? this._flip.dir : nothing}\n @animationend=${this._flip.onAnimationEnd}\n >\n <div data-face=\"front\" ?inert=${backActive} aria-hidden=${backActive}>\n ${this._renderListFace(\n groups,\n filtered,\n theme,\n searchInputStyle,\n categoryHeaderStyle,\n {\n ...baseStyles.noResults,\n ...colors.emptyState,\n }\n )}\n </div>\n <div data-face=\"back\" ?inert=${!backActive} aria-hidden=${!backActive}>\n ${openItem ? this._renderDetail(openItem, theme, feedbackConfig) : nothing}\n </div>\n </div>\n </div>\n </div>\n `;\n }\n\n /** The FRONT face \u2014 search box, the question list (optionally grouped by\n * category), and the no-results message. Extracted so both faces can live in\n * the DOM at once for the flip. */\n private _renderListFace(\n groups: Map<string | undefined, FAQQuestionAction[]> | null,\n filtered: FAQQuestionAction[],\n theme: 'light' | 'dark',\n searchInputStyle: Record<string, unknown>,\n categoryHeaderStyle: Record<string, unknown>,\n noResultsStyle: Record<string, unknown>\n ) {\n return html`\n ${\n this.faqConfig.searchable\n ? html`\n <div style=${styleMap(sm(baseStyles.searchWrapper))}>\n <style>\n [data-adaptive-id=\"${this._scopeId}\"] input::placeholder {\n color: var(--sc-content-search-color, inherit);\n opacity: 0.7;\n }\n </style>\n <input\n type=\"text\"\n placeholder=\"Search questions...\"\n .value=${this._searchQuery}\n style=${styleMap(sm(searchInputStyle))}\n @input=${(e: InputEvent) => {\n this._searchQuery = (e.target as HTMLInputElement).value;\n }}\n />\n </div>\n `\n : nothing\n }\n\n <div style=${styleMap(sm(baseStyles.accordion))}>\n ${\n groups\n ? Array.from(groups.entries()).map(\n ([category, items]) => html`\n ${\n category\n ? html`\n <div\n style=${styleMap(sm(categoryHeaderStyle))}\n data-category-header=${category}\n >\n ${category}\n </div>\n `\n : nothing\n }\n ${this._renderItems(items, theme)}\n `\n )\n : this._renderItems(filtered, theme)\n }\n </div>\n\n ${\n this.faqConfig.searchable && filtered.length === 0 && this._searchQuery\n ? html`\n <div style=${styleMap(sm(noResultsStyle))}>\n No questions found matching &quot;${this._searchQuery}&quot;\n </div>\n `\n : nothing\n }\n `;\n }\n}\n\n// ============================================================================\n// Custom element registration\n// ============================================================================\n\nif (!customElements.get('syntro-faq-accordion')) {\n customElements.define('syntro-faq-accordion', FAQAccordionElement);\n}\n\nexport default FAQAccordionElement;\n", "/**\n * Adaptive FAQ - Styles\n *\n * BaseStyles object (font, layout, colors, animations) and theme integration\n * for the FAQWidget component.\n */\n\nimport { purple, slateGrey } from '@syntro/design-system/tokens';\n\n// ============================================================================\n// Base Styles\n// ============================================================================\n\nexport const baseStyles = {\n container: {\n fontFamily: 'var(--sc-font-family, system-ui, -apple-system, sans-serif)',\n maxWidth: '800px',\n margin: '0 auto',\n },\n searchWrapper: {\n marginBottom: '8px',\n },\n searchInput: {\n width: '100%',\n padding: '12px 16px',\n borderRadius: '8px',\n fontSize: '14px',\n outline: 'none',\n transition: 'border-color 0.15s ease',\n backgroundColor: 'var(--sc-content-search-background)',\n color: 'var(--sc-content-search-color)',\n },\n accordion: {\n display: 'flex',\n flexDirection: 'column' as const,\n gap: 'var(--sc-content-item-gap, 6px)',\n },\n item: {\n borderRadius: 'var(--sc-content-item-border-radius, 10px)',\n border: 'var(--sc-content-item-border, 1px solid rgba(0, 0, 0, 0.08))',\n background: 'var(--sc-content-item-background, rgba(255, 255, 255, 0.5))',\n overflow: 'hidden',\n transition: 'box-shadow 0.15s ease',\n },\n question: {\n width: '100%',\n padding: 'var(--sc-content-item-padding, 12px 16px)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n border: 'none',\n cursor: 'pointer',\n fontSize: 'var(--sc-content-item-font-size, 15px)',\n fontWeight: 500,\n textAlign: 'left' as const,\n transition: 'background-color 0.15s ease',\n },\n chevron: {\n fontSize: '20px',\n transition: 'transform 0.2s ease',\n color: 'var(--sc-content-chevron-color, currentColor)',\n },\n answer: {\n padding: 'var(--sc-content-body-padding, 0 16px 12px 16px)',\n fontSize: 'var(--sc-content-body-font-size, 14px)',\n lineHeight: 1.6,\n overflow: 'hidden',\n transition: 'max-height 0.2s ease, padding 0.2s ease',\n },\n category: {\n display: 'inline-block',\n fontSize: '11px',\n fontWeight: 600,\n textTransform: 'uppercase' as const,\n letterSpacing: '0.05em',\n padding: '4px 8px',\n borderRadius: '4px',\n marginBottom: '8px',\n },\n categoryHeader: {\n fontSize: 'var(--sc-content-category-font-size, 12px)',\n fontWeight: 700,\n textTransform: 'uppercase' as const,\n letterSpacing: '0.05em',\n padding: 'var(--sc-content-category-padding, 8px 4px 4px 4px)',\n marginTop: 'var(--sc-content-category-gap, 4px)',\n },\n feedback: {\n display: 'flex',\n alignItems: 'center',\n gap: '8px',\n marginTop: '12px',\n paddingTop: '10px',\n borderTop: '1px solid rgba(0, 0, 0, 0.08)',\n fontSize: '13px',\n },\n feedbackButton: {\n background: 'none',\n border: '1px solid transparent',\n cursor: 'pointer',\n fontSize: '16px',\n padding: '4px 8px',\n borderRadius: '4px',\n transition: 'background-color 0.15s ease, border-color 0.15s ease',\n },\n feedbackButtonSelected: {\n borderColor: 'rgba(0, 0, 0, 0.2)',\n backgroundColor: 'rgba(0, 0, 0, 0.04)',\n },\n // \u2500\u2500 Master\u2192detail takeover \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Item 5: the detail view is a tight stack \u2014 back, then the QUESTION with the\n // ANSWER hugging right below it (the old 12px flex gap between Q and A wasted\n // vertical space and pushed the answer down). Row spacing is now set by\n // explicit per-element margins, so gap:0 here.\n detail: {\n display: 'flex',\n flexDirection: 'column' as const,\n gap: '0',\n minHeight: '0',\n // Inset the master\u2192detail takeover to match the list questions' padding so\n // a single Q&A taking over the tile doesn't render flush to the edges.\n // Vertical inset is tightened (12px\u219210px default) because the takeover lives\n // in the fixed-height velvet deck band, where every px of chrome pushes the\n // \"Dive deeper\" footer toward the card's overflow:hidden clip edge\n // (BUG-1783496844). Horizontal stays 16px to keep alignment with the list.\n padding: 'var(--sc-content-item-padding, 10px 16px)',\n },\n detailBack: {\n display: 'inline-flex',\n alignItems: 'center',\n gap: '4px',\n alignSelf: 'flex-start',\n background: 'none',\n border: 'none',\n padding: '4px 0',\n // Clear space below the back control (was the container gap). Tightened\n // 10px\u21926px to reclaim vertical room in the bounded deck band so the\n // \"Dive deeper\" footer stays clear of the card clip edge (BUG-1783496844).\n marginBottom: '6px',\n cursor: 'pointer',\n fontSize: '13px',\n fontWeight: 600,\n opacity: 0.85,\n },\n detailQuestion: {\n fontSize: 'var(--sc-content-item-font-size, 16px)',\n fontWeight: 600,\n lineHeight: 1.4,\n // Tight to the answer \u2014 kill the wasted Q\u2194A gap.\n marginBottom: '4px',\n },\n detailAnswer: {\n fontSize: 'var(--sc-content-body-font-size, 14px)',\n lineHeight: 1.6,\n // No longer a long scroll region \u2014 the answer render is line-clamped\n // (detailAnswerClamp) and \"Dive deeper\" carries the user to the full answer\n // in chat. flex:1 keeps the footer (dive-deeper + feedback) pinned below.\n display: 'flex',\n flexDirection: 'column' as const,\n flex: '1 1 auto',\n minHeight: '0',\n },\n // The answer TEXT render, capped to a few readable lines with a visible\n // ellipsis cap (a declared -webkit-line-clamp box \u2014 the clip battery treats\n // this as an intentional ellipsis, not a hidden-content bug). The cap is\n // token-tunable so a roomy surface can raise it.\n detailAnswerClamp: {\n margin: '0',\n display: '-webkit-box',\n // Default cap lowered 4\u21923 lines: the takeover renders inside the\n // fixed-height velvet deck card (overflow:hidden), so a 4-line preview\n // plus chrome pushed the \"Dive deeper\" footer past the clip edge on\n // smaller decks / two-line questions (BUG-1783496844). 3 lines is a\n // compact preview; \"Dive deeper\" carries the full answer to chat. Roomy\n // surfaces can raise it via the token.\n // Capital-W vendor prefix: lowercase `webkitLineClamp` emits\n // `webkit-line-clamp` (no leading dash), which the CSS parser drops \u2014\n // this clamp silently never applied until 2026-07-23.\n WebkitLineClamp: 'var(--sc-faq-answer-clamp, 3)',\n WebkitBoxOrient: 'vertical' as const,\n overflow: 'hidden',\n // Shrink-ready: as a flex child of detailAnswer, yield space first so that\n // WHEN a host bounds the tile height (definite deck-card height) the answer\n // gives up lines before the pinned footer is ever clipped.\n flex: '1 1 auto',\n minHeight: '0',\n },\n // Footer row under the answer: the \"Dive deeper\" chip sits bottom-right.\n detailFooter: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'flex-end',\n gap: '8px',\n // Tightened 8px\u21926px to reclaim deck-band vertical room (BUG-1783496844).\n marginTop: '6px',\n // Never shrink or clip the footer \u2014 the answer above yields space first.\n flexShrink: 0,\n },\n detailDiveDeeper: {\n flex: '0 0 auto',\n padding: '4px 10px',\n borderRadius: '9999px',\n border: '1px solid var(--sc-color-border, rgba(0,0,0,0.14))',\n background: 'transparent',\n cursor: 'pointer',\n font: '600 12px/1.2 var(--sc-font-family, inherit)',\n color: 'var(--sc-color-primary, #3d8a5e)',\n whiteSpace: 'nowrap' as const,\n },\n emptyState: {\n textAlign: 'center' as const,\n padding: '48px 24px',\n fontSize: '14px',\n },\n noResults: {\n textAlign: 'center' as const,\n padding: '32px 16px',\n fontSize: '14px',\n },\n} as const;\n\n// ============================================================================\n// Theme Styles\n// ============================================================================\n\nexport const themeStyles = {\n light: {\n container: {\n backgroundColor: 'transparent',\n color: 'inherit',\n },\n searchInput: {\n border: `1px solid ${slateGrey[11]}`,\n },\n item: {\n backgroundColor: 'var(--sc-content-background)',\n borderTop: 'var(--sc-content-border)',\n borderRight: 'var(--sc-content-border)',\n borderBottom: 'var(--sc-content-border)',\n borderLeft: 'var(--sc-content-border)',\n },\n itemExpanded: {\n boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)',\n },\n question: {\n backgroundColor: 'transparent',\n color: 'var(--sc-content-text-color)',\n },\n questionHover: {\n backgroundColor: 'var(--sc-content-background-hover)',\n },\n answer: {\n color: 'var(--sc-content-text-secondary-color)',\n },\n category: {\n backgroundColor: purple[8],\n color: purple[2],\n },\n categoryHeader: {\n color: slateGrey[7],\n },\n emptyState: {\n color: slateGrey[8],\n },\n feedbackPrompt: {\n color: slateGrey[7],\n },\n },\n dark: {\n container: {\n backgroundColor: 'transparent',\n color: 'inherit',\n },\n searchInput: {\n border: `1px solid ${slateGrey[5]}`,\n },\n item: {\n backgroundColor: 'var(--sc-content-background)',\n borderTop: 'var(--sc-content-border)',\n borderRight: 'var(--sc-content-border)',\n borderBottom: 'var(--sc-content-border)',\n borderLeft: 'var(--sc-content-border)',\n },\n itemExpanded: {\n boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',\n },\n question: {\n backgroundColor: 'transparent',\n color: 'var(--sc-content-text-color)',\n },\n questionHover: {\n backgroundColor: 'var(--sc-content-background-hover)',\n },\n answer: {\n color: 'var(--sc-content-text-secondary-color)',\n },\n category: {\n backgroundColor: purple[0],\n color: purple[6],\n },\n categoryHeader: {\n color: slateGrey[8],\n },\n emptyState: {\n color: slateGrey[7],\n },\n feedbackPrompt: {\n color: slateGrey[8],\n },\n },\n};\n", "/**\n * renderHealth \u2014 runtime self-detection of blank render and undeclared clipping\n * for the FAQ accordion.\n *\n * A tile can look correct in tests yet paint blank on a real host (a device\n * compositing quirk, a font that measures wider, a broken image CDN). After the\n * accordion paints, this module re-measures it and emits a PostHog signal via\n * the ONE consent-gated door (`window.SynOS.runtime.events.emit`) when it finds a\n * blank tile or an undeclared clip. Field-RUM for \"the FAQ broke on a customer's\n * page\".\n *\n * This is a lean, package-LOCAL copy of the pattern adaptive-product owns:\n * `@syntrologie/adapt-faq` does not (and should not) depend on\n * `@syntrologie/adapt-product`, and the two share no runtime-util package. The\n * blank check is identical; the clip check is the generic text-overflow subset\n * (the FAQ has no `[data-critical-text]` identity nodes, so the product card's\n * Tier-1 identity rule does not apply here).\n * Follow-up: extract a shared runtime-util package so both adaptives import one\n * copy.\n *\n * COE-051 lesson: `publish()` only notifies in-process canvas subscribers \u2014 it\n * NEVER reaches PostHog. `emit()` is the sanctioned capture path (the EventBus\n * forwards emit()'d events to its consent-gated posthogCapture sink). The event\n * name MUST satisfy validateEventName (`app:{category}:{action}`, colon segments)\n * or the bus silently drops it.\n *\n * NON-PII discipline: only element tag/class PATHS and pixel numbers ever leave\n * the widget \u2014 never text content, never customer copy.\n */\nimport type { ReactiveController, ReactiveControllerHost } from 'lit';\n\nexport interface ClipFinding {\n /** Structural path to the offending node (NON-PII), e.g. `div.faq>button`. */\n path: string;\n /** Horizontal overflow in px, rounded. */\n over_px: number;\n}\n\nexport interface RenderHealth {\n /** Undeclared clips found inside the tile (empty = clean). */\n clips: ClipFinding[];\n /** True when the tile occupies real area but painted no text and no complete\n * image (a blank tile). */\n blank: boolean;\n}\n\n/** Minimum tile area (px\u00B2) below which \"blank\" is not meaningful. */\nconst BLANK_MIN_AREA = 60 * 60;\n\n/** Short NON-PII path: up to 3 ancestors of `tag.class` joined by `>`. */\nfunction elementPath(el: Element, stopAt: Element): string {\n const seg = (e: Element): string => {\n const tag = e.tagName.toLowerCase();\n const cls =\n typeof e.className === 'string' && e.className.trim()\n ? `.${e.className.trim().split(/\\s+/)[0]}`\n : '';\n return `${tag}${cls}`;\n };\n const parts: string[] = [seg(el)];\n let cur: Element | null = el.parentElement;\n let hops = 0;\n while (cur && cur !== stopAt && hops < 3) {\n parts.unshift(seg(cur));\n cur = cur.parentElement;\n hops++;\n }\n return parts.join('>');\n}\n\n/** True if `el` (or an ancestor up to `root`) declares the clip is intentional. */\nfunction hasClipOkAncestor(el: Element, root: Element): boolean {\n let cur: Element | null = el;\n while (cur && cur !== root.parentElement) {\n if (cur.hasAttribute?.('data-clip-ok')) return true;\n cur = cur.parentElement;\n }\n return false;\n}\n\n/**\n * Measure the render health of a tile root. Pure DOM read \u2014 no side effects, no\n * telemetry. Safe to call any time after paint.\n * - text-clip: an element with its own text whose scrollWidth exceeds\n * clientWidth by >1px, with no textOverflow:ellipsis, not under [data-clip-ok].\n * - blank: the tile has real area but no painted text and no complete image.\n */\nexport function measureRenderHealth(root: Element): RenderHealth {\n const clips: ClipFinding[] = [];\n const seen = new Set<string>();\n const push = (f: ClipFinding): void => {\n const k = `${f.path}|${f.over_px}`;\n if (seen.has(k)) return;\n seen.add(k);\n clips.push(f);\n };\n\n let hasText = false;\n let hasCompleteImage = false;\n\n for (const el of root.querySelectorAll('*')) {\n const cs = getComputedStyle(el);\n if (cs.display === 'none' || cs.visibility === 'hidden') continue;\n const r = el.getBoundingClientRect();\n if (r.width === 0 || r.height === 0) continue;\n\n const ownText = Array.from(el.childNodes).some(\n (n) => n.nodeType === 3 && (n.textContent ?? '').trim().length > 0\n );\n if (ownText) hasText = true;\n if (el.tagName === 'IMG') {\n const img = el as HTMLImageElement;\n if (img.complete && img.naturalWidth > 0 && img.dataset.imageError !== 'true') {\n hasCompleteImage = true;\n }\n }\n\n if (\n ownText &&\n el.scrollWidth > el.clientWidth + 1 &&\n cs.textOverflow !== 'ellipsis' &&\n !hasClipOkAncestor(el, root)\n ) {\n push({ path: elementPath(el, root), over_px: el.scrollWidth - el.clientWidth });\n }\n }\n\n const rect = root.getBoundingClientRect();\n const area = rect.width * rect.height;\n const blank = area >= BLANK_MIN_AREA && !hasText && !hasCompleteImage;\n\n return { clips, blank };\n}\n\ninterface SynOSEventsWindow {\n SynOS?: {\n runtime?: {\n events?: {\n // The ONLY transport that reaches PostHog (COE-051).\n emit?: (name: string, props: Record<string, unknown>) => unknown;\n };\n };\n };\n}\n\n/** Emit a telemetry event on the PostHog-reaching path. Never throws. */\nfunction synEmit(name: string, props: Record<string, unknown>): void {\n if (typeof window === 'undefined') return;\n try {\n const emit = (window as unknown as SynOSEventsWindow).SynOS?.runtime?.events?.emit;\n if (typeof emit === 'function') emit(name, props);\n } catch {\n // Telemetry must never crash the widget.\n }\n}\n\nexport interface RenderHealthReportOptions {\n /** Event category: emits `app:<category>:blank` / `:clipped`. */\n category: string;\n /** `widget_kind` tag included in every payload. */\n widgetKind: string;\n /** Extra NON-PII context merged into every payload. */\n context?: Record<string, unknown>;\n}\n\nconst CLEAN: RenderHealth = { clips: [], blank: false };\n\n/** Measure `root` and, if it clips or paints blank, emit the self-detection\n * event via the consent-gated emit() path. Returns the measured health. Never\n * throws. */\nexport function reportRenderHealth(root: Element, opts: RenderHealthReportOptions): RenderHealth {\n try {\n const health = measureRenderHealth(root);\n if (health.clips.length === 0 && !health.blank) return health;\n const base: Record<string, unknown> = { ...(opts.context ?? {}), widget_kind: opts.widgetKind };\n if (health.blank) {\n synEmit(`app:${opts.category}:blank`, base);\n return health;\n }\n const paths = health.clips.map((c) => c.path).sort();\n const over_px = health.clips.reduce((m, c) => Math.max(m, c.over_px), 0);\n synEmit(`app:${opts.category}:clipped`, {\n paths: paths.join(','),\n over_px,\n clip_count: health.clips.length,\n ...base,\n });\n return health;\n } catch {\n return CLEAN;\n }\n}\n\n/** What the widget's probe returns for one measurement pass. */\nexport interface RenderHealthProbe extends RenderHealthReportOptions {\n /** The element to measure, or `null` to SKIP this pass (unsettled state). */\n root: Element | null;\n /** Stable key for the current visual state; a state reports AT MOST ONCE. */\n stateKey: string;\n}\n\n/**\n * Lit ReactiveController that runs a widget's render-health probe after each\n * paint settles (rAF then a 0ms timeout), at most once per visual state.\n */\nexport class RenderHealthController implements ReactiveController {\n #probe: () => RenderHealthProbe | null;\n #reported = new Set<string>();\n #raf: number | undefined;\n\n constructor(host: ReactiveControllerHost, probe: () => RenderHealthProbe | null) {\n this.#probe = probe;\n host.addController(this);\n }\n\n hostUpdated(): void {\n this.#schedule();\n }\n\n hostDisconnected(): void {\n this.#cancel();\n }\n\n #schedule(): void {\n if (typeof window === 'undefined') return;\n if (this.#raf !== undefined) return;\n const raf =\n typeof window.requestAnimationFrame === 'function'\n ? window.requestAnimationFrame.bind(window)\n : (cb: FrameRequestCallback) => window.setTimeout(() => cb(0), 0);\n this.#raf = raf(() => {\n window.setTimeout(() => {\n this.#raf = undefined;\n this.#run();\n }, 0);\n }) as unknown as number;\n }\n\n #run(): void {\n try {\n const probe = this.#probe();\n if (!probe || !probe.root) return;\n if (this.#reported.has(probe.stateKey)) return;\n this.#reported.add(probe.stateKey);\n reportRenderHealth(probe.root, probe);\n } catch {\n // Self-detection must never crash the widget.\n }\n }\n\n #cancel(): void {\n if (\n this.#raf !== undefined &&\n typeof window !== 'undefined' &&\n typeof window.cancelAnimationFrame === 'function'\n ) {\n window.cancelAnimationFrame(this.#raf);\n }\n this.#raf = undefined;\n }\n}\n", "/**\n * Adaptive FAQ - Runtime Module\n *\n * Runtime manifest for the FAQ accordion adaptive.\n * Mounts the Lit web component <syntro-faq-accordion>.\n * Provides action executors and widget registration.\n */\n\nimport { type MountPlumbing, stripMountPlumbing } from '@syntrologie/sdk-contracts';\nimport { executorDefinitions } from './executors';\nimport './FAQWidgetLit'; // registers <syntro-faq-accordion> custom element\nimport type { FAQWidgetRuntime } from './faq-types';\nimport type { FAQConfig, FAQQuestionAction } from './types';\n\n// ============================================================================\n// Lit Mountable Widget\n// ============================================================================\n\nconst DEFAULT_FAQ_CONFIG: FAQConfig = {\n expandBehavior: 'single',\n searchable: false,\n theme: 'auto',\n actions: [],\n};\n\n/**\n * Mountable widget interface for <syntro-faq-accordion> (Lit web component).\n */\nexport const FAQWidgetLitMountable = {\n mount(\n container: HTMLElement,\n config?: (FAQConfig & MountPlumbing & { runtime?: FAQWidgetRuntime }) | null\n ) {\n const incoming = config ?? null;\n const stripped = stripMountPlumbing<FAQConfig & { runtime?: FAQWidgetRuntime }>(incoming);\n // stripMountPlumbing does NOT strip the non-canonical `runtime` consumer key\n // on this widget because `runtime` is a canonical plumbing key. Pull the\n // explicit references back out for the element wiring.\n const runtime = incoming?.runtime as FAQWidgetRuntime | undefined;\n const instanceId = incoming?.instanceId ?? 'faq-widget';\n const faqConfig: FAQConfig = incoming ? (stripped as FAQConfig) : { ...DEFAULT_FAQ_CONFIG };\n\n const el = document.createElement('syntro-faq-accordion') as HTMLElement & {\n faqConfig: FAQConfig;\n runtime: FAQWidgetRuntime | null;\n instanceId: string;\n };\n\n Object.assign(el, {\n faqConfig,\n runtime: runtime ?? null,\n instanceId,\n });\n\n container.appendChild(el);\n return () => el.remove();\n },\n};\n\n// ============================================================================\n// App Runtime Manifest\n// ============================================================================\n\n/**\n * Runtime manifest for adaptive-faq.\n *\n * Provides:\n * - FAQ action executors (scroll_to, toggle_item, update)\n * - Widget-based accordion using the Lit web component\n */\nexport const runtime = {\n id: 'adaptive-faq',\n version: '2.0.0',\n name: 'FAQ Accordion',\n description:\n 'Collapsible Q&A accordion with actions, rich content, feedback, and personalization',\n\n /**\n * Action executors for programmatic FAQ interaction.\n */\n executors: executorDefinitions,\n\n /**\n * Widget definitions for the runtime's WidgetRegistry.\n */\n widgets: [\n {\n id: 'adaptive-faq:accordion',\n component: FAQWidgetLitMountable,\n metadata: {\n name: 'FAQ Accordion',\n description: 'Collapsible Q&A accordion with search, categories, and feedback',\n icon: '\u2753',\n subtitle: 'Curated just for you.',\n },\n },\n ],\n\n /**\n * Extract notify watcher entries from tile config props.\n * The runtime evaluates these continuously (even with drawer closed)\n * and publishes faq:question_revealed when triggerWhen transitions false \u2192 true.\n */\n notifyWatchers(props: Record<string, unknown>) {\n const actions = (props.actions ?? []) as FAQQuestionAction[];\n return actions\n .filter((a) => a.notify && a.triggerWhen)\n .map((a) => ({\n id: `faq:${a.config.id}`,\n strategy: a.triggerWhen!,\n eventName: 'faq:question_revealed',\n eventProps: {\n questionId: a.config.id,\n question: a.config.question,\n title: a.notify!.title,\n body: a.notify!.body,\n icon: a.notify!.icon,\n },\n }));\n },\n};\n\nexport default runtime;\n"],
5
+ "mappings": "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",
6
+ "names": ["runtime"]
7
+ }
@@ -99,6 +99,8 @@ var FrequencyLimitConditionZ = z.object({
99
99
  var MatchOpZ = z.object({
100
100
  equals: z.union([z.string(), z.number(), z.boolean()]).optional(),
101
101
  contains: z.string().optional()
102
+ }).refine((operator) => Number(operator.equals !== void 0) + Number(operator.contains !== void 0) === 1, {
103
+ message: "Exactly one of equals or contains must be specified."
102
104
  }).describe("Match operator for counter filters. Exactly one of equals or contains must be specified.");
103
105
  var CounterDefZ = z.object({
104
106
  events: z.array(CountableEventZ).min(1).describe("Event names to count. Use values from the countable events enum."),
@@ -173,7 +175,27 @@ var NotifyZ = z.object({
173
175
  icon: z.string().optional()
174
176
  }).nullable().optional();
175
177
 
178
+ // ../../../node_modules/@lit/context/lib/create-context.js
179
+ function n(n2) {
180
+ return n2;
181
+ }
182
+
183
+ // ../../sdk-contracts/dist/canvas-context.js
184
+ var canvasRuntimeContext = n("syntrologie:canvas-runtime");
185
+
186
+ // ../../sdk-contracts/dist/dive-deeper.js
187
+ var DIVE_DEEPER_EVENT = "syntro:chat:dive-deeper";
188
+ function dispatchDiveDeeper(detail) {
189
+ if (typeof window === "undefined")
190
+ return;
191
+ window.dispatchEvent(new CustomEvent(DIVE_DEEPER_EVENT, { detail }));
192
+ }
193
+
194
+ // ../../sdk-contracts/dist/routes.js
195
+ var utf8Decoder = new TextDecoder("utf-8", { fatal: false });
196
+
176
197
  export {
198
+ dispatchDiveDeeper,
177
199
  stripMountPlumbing,
178
200
  AnchorIdZ,
179
201
  AuthoringFieldsZ,
@@ -181,4 +203,32 @@ export {
181
203
  TriggerWhenZ,
182
204
  NotifyZ
183
205
  };
184
- //# sourceMappingURL=chunk-IMSLXYXR.js.map
206
+ /*! Bundled license information:
207
+
208
+ @lit/context/lib/context-request-event.js:
209
+ @lit/context/lib/create-context.js:
210
+ @lit/context/lib/controllers/context-consumer.js:
211
+ @lit/context/lib/value-notifier.js:
212
+ @lit/context/lib/controllers/context-provider.js:
213
+ @lit/context/lib/context-root.js:
214
+ (**
215
+ * @license
216
+ * Copyright 2021 Google LLC
217
+ * SPDX-License-Identifier: BSD-3-Clause
218
+ *)
219
+
220
+ @lit/context/lib/decorators/provide.js:
221
+ (**
222
+ * @license
223
+ * Copyright 2017 Google LLC
224
+ * SPDX-License-Identifier: BSD-3-Clause
225
+ *)
226
+
227
+ @lit/context/lib/decorators/consume.js:
228
+ (**
229
+ * @license
230
+ * Copyright 2022 Google LLC
231
+ * SPDX-License-Identifier: BSD-3-Clause
232
+ *)
233
+ */
234
+ //# sourceMappingURL=chunk-YNDAVOD7.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../sdk-contracts/dist/mount-plumbing.js", "../../../sdk-contracts/dist/schemas.js", "../../../../node_modules/@lit/context/src/lib/create-context.ts", "../../../sdk-contracts/dist/canvas-context.js", "../../../sdk-contracts/dist/dive-deeper.js", "../../../sdk-contracts/dist/routes.js"],
4
+ "sourcesContent": ["/**\n * Mount contract types and helper for adaptive widget mountables.\n *\n * The `WidgetRegistry` in `@syntrologie/runtime-sdk` delivers props to each\n * mountable as `{ ...tile.props, instanceId, runtime, tileId? }` spread flat\n * (see `MountableContract.test.ts` in runtime-sdk for the end-to-end lockdown).\n *\n * Adaptives that strip plumbing manually maintain private blacklists that\n * silently drift when the contract grows (PR #2234 and #2238 documented this).\n * `stripMountPlumbing` centralizes the list so adding a new plumbing key in\n * the future is a one-line change here that every adaptive picks up automatically.\n *\n * Adaptives whose widget schemas use Zod `.strict()` MUST call this before\n * validating, or strict-mode will reject the runtime-injected keys and the\n * widget will silently render its empty/error state.\n */\nexport const MOUNT_PLUMBING_KEYS = ['instanceId', 'runtime', 'tileId'];\nexport function stripMountPlumbing(config) {\n if (!config || typeof config !== 'object') {\n return {};\n }\n const out = { ...config };\n for (const key of MOUNT_PLUMBING_KEYS) {\n delete out[key];\n }\n return out;\n}\n", "/**\n * Shared Zod schemas for decision strategies, conditions, and event scoping.\n *\n * These are the canonical definitions \u2014 runtime-sdk and all adaptive packages\n * should import from here instead of duplicating.\n */\nimport { z } from 'zod';\n// =============================================================================\n// ANCHOR ID SCHEMA\n// =============================================================================\nexport const AnchorIdZ = z\n .object({\n selector: z.string(),\n route: z.union([z.string(), z.array(z.string())]),\n})\n .strict();\n// =============================================================================\n// AUTHORING FIELDS \u2014 id / title / description / validation\n//\n// Shared fields every action carries. `id` is the action identifier the\n// runtime uses to dispatch, dedupe, and drop/replace actions \u2014 it is NOT\n// stripped before serving. `title` / `description` / `validation` are\n// authoring-only metadata stripped server-side in `to_runtime_config`\n// (platform/backend/app/domains/experiments/helpers.py).\n//\n// They all appear in the JSON Schema (and therefore in the tactician's\n// prompt) because the LLM needs to know they are valid action properties \u2014\n// otherwise schema validation would reject what the prompt commands.\n//\n// Each action variant should `.extend(AuthoringFieldsZ)` alongside any\n// triggerWhen/condition extensions.\n// =============================================================================\nexport const AuthoringFieldsZ = {\n id: z.string().optional().describe('Stable action identifier (e.g. \"act_3db6a14d2ab0\").'),\n title: z\n .string()\n .max(200)\n .optional()\n .describe('Authoring-only: short label shown on the action plan dashboard. Stripped before serving to the runtime SDK.'),\n description: z\n .string()\n .max(1000)\n .optional()\n .describe('Authoring-only: one-sentence explanation of what this action does and why. Stripped before serving to the runtime SDK.'),\n validation: z\n .array(z.string().max(500))\n .max(10)\n .optional()\n .describe('Authoring-only: ordered steps a reviewer can follow to trigger this action and visually confirm it works. Each entry is one step. Stripped before serving to the runtime SDK.'),\n};\n// =============================================================================\n// TRIGGER VOCABULARY \u2014 canonical lists of valid event names, metric keys, etc.\n// These flow through to the JSON schema as enums and are used by the LLM prompt.\n// =============================================================================\n/** Events that can be counted in event_count conditions.\n *\n * Every value here must be an event the runtime actually emits \u2014 either a\n * PostHog-autocapture normalization (ui.click/scroll/input/change/submit) or\n * an event-processor detector (ui.hover/idle/scroll_thrash/focus_bounce/\n * hesitate/rage_click). Do not add aspirational names; a trigger counting an\n * event nothing emits never fires.\n */\nexport const COUNTABLE_EVENTS = [\n // User interactions (from PostHog autocapture normalization)\n 'ui.click',\n 'ui.scroll',\n 'ui.input',\n 'ui.change',\n 'ui.submit',\n // Behavioral detectors (from event-processor)\n 'ui.hover',\n 'ui.idle',\n 'ui.scroll_thrash',\n 'ui.focus_bounce',\n 'ui.hesitate',\n 'ui.rage_click',\n // Navigation\n 'nav.page_view',\n 'nav.page_leave',\n];\nexport const CountableEventZ = z\n .enum(COUNTABLE_EVENTS)\n .describe('Event name to count. ui.* = user interactions and behavioral detectors (hesitate, rage_click, scroll_thrash, focus_bounce, idle, hover); nav.* = page navigation.');\n/** Valid session metric keys. */\nexport const SESSION_METRIC_KEYS = ['time_on_page', 'page_views', 'scroll_depth'];\nexport const SessionMetricKeyZ = z\n .enum(SESSION_METRIC_KEYS)\n .describe('Session metric key. time_on_page = seconds on current page, page_views = pages visited this session, scroll_depth = 0-100 percentage.');\n/** Element chain match field prefixes for counter filters. */\nexport const ELEMENT_MATCH_FIELDS = ['tag_name', '$el_text'];\n// Note: attr__* is a dynamic prefix (attr__data-id, attr__class, attr__href, etc.)\n// and cannot be enumerated. The match key is either one of ELEMENT_MATCH_FIELDS\n// or starts with \"attr__\".\n// =============================================================================\n// CONDITION SCHEMAS\n// =============================================================================\nexport const PageUrlConditionZ = z\n .object({\n type: z.literal('page_url'),\n url: z.string().describe('URL path to match (e.g. \"/pricing\", \"/dashboard\")'),\n})\n .describe('Fires when the current page URL matches. Use for page-specific actions. ' +\n 'Example: {\"type\": \"page_url\", \"url\": \"/pricing\"}');\nexport const RouteConditionZ = z\n .object({\n type: z.literal('route'),\n routeId: z.string().describe('Named route ID from the route filter'),\n})\n .describe('Fires when the current route matches a named route ID.');\nexport const AnchorVisibleConditionZ = z\n .object({\n type: z.literal('anchor_visible'),\n anchorId: z.string().describe('CSS selector of the anchor element'),\n state: z\n .enum(['visible', 'present', 'absent'])\n .describe('\"visible\" = in viewport, \"present\" = in DOM, \"absent\" = not in DOM'),\n})\n .describe(\"Fires based on a DOM element's visibility state. \" +\n 'Example: {\"type\": \"anchor_visible\", \"anchorId\": \"#cta-button\", \"state\": \"visible\"}');\nexport const EventOccurredConditionZ = z\n .object({\n type: z.literal('event_occurred'),\n eventName: z.string().describe('Event name (e.g. \"ui.click\", \"$pageview\")'),\n withinMs: z.number().optional().describe('Time window in ms. Omit = any time this session.'),\n})\n .describe('Fires when a specific event has occurred during this session. ' +\n 'Example: {\"type\": \"event_occurred\", \"eventName\": \"ui.click\", \"withinMs\": 5000}');\nexport const StateEqualsConditionZ = z\n .object({\n type: z.literal('state_equals'),\n key: z\n .string()\n .describe('Key in the SDK persistent state store (localStorage). Only valid for keys the host app explicitly sets via syntro.state.set().'),\n value: z.unknown().describe('Expected value to match against'),\n})\n .describe('Checks the SDK persistent state store (localStorage). ONLY for host-app state set via syntro.state.set() \u2014 ' +\n 'NOT for user attributes like region, device, or UTM params (those are handled by segment targeting). ' +\n 'Do NOT use this for targeting. If you do not know the valid state keys, do not use this condition type.');\nexport const ViewportConditionZ = z\n .object({\n type: z.literal('viewport'),\n minWidth: z.number().optional().describe('Minimum viewport width in pixels'),\n maxWidth: z.number().optional().describe('Maximum viewport width in pixels'),\n minHeight: z.number().optional().describe('Minimum viewport height in pixels'),\n maxHeight: z.number().optional().describe('Maximum viewport height in pixels'),\n})\n .describe('Fires based on viewport (screen) size. Use for responsive behavior. ' +\n 'Example: {\"type\": \"viewport\", \"minWidth\": 768} \u2014 fires on tablet and larger.');\nexport const SessionMetricConditionZ = z\n .object({\n type: z.literal('session_metric'),\n key: SessionMetricKeyZ,\n operator: z.enum(['gte', 'lte', 'eq', 'gt', 'lt']),\n threshold: z.number().describe('Numeric threshold to compare against'),\n})\n .describe('Fires when a session metric crosses a threshold. Valid keys: \"time_on_page\" (seconds), ' +\n '\"page_views\" (count), \"scroll_depth\" (0-100). ' +\n 'Example: {\"type\": \"session_metric\", \"key\": \"time_on_page\", \"operator\": \"gte\", \"threshold\": 30}');\nexport const DismissedConditionZ = z\n .object({\n type: z.literal('dismissed'),\n key: z.string().describe('Dismissal key (usually a tile or action ID)'),\n inverted: z\n .boolean()\n .optional()\n .describe('When true, fires if NOT dismissed (default behavior)'),\n})\n .describe('Checks if an item has been dismissed by the user. Use with inverted: true to show only if not dismissed.');\nexport const CooldownActiveConditionZ = z\n .object({\n type: z.literal('cooldown_active'),\n key: z.string().describe('Cooldown key'),\n inverted: z.boolean().optional().describe('When true, fires if cooldown is NOT active'),\n})\n .describe('Checks if a cooldown timer is currently active. Use to prevent showing the same intervention too frequently.');\nexport const FrequencyLimitConditionZ = z\n .object({\n type: z.literal('frequency_limit'),\n key: z.string().describe('Frequency counter key'),\n limit: z.number().describe('Maximum allowed count'),\n inverted: z.boolean().optional().describe('When true, fires if limit NOT reached'),\n})\n .describe('Checks if a frequency limit has been reached. Use to cap how many times an action fires per session.');\nexport const MatchOpZ = z\n .object({\n equals: z.union([z.string(), z.number(), z.boolean()]).optional(),\n contains: z.string().optional(),\n})\n .refine((operator) => Number(operator.equals !== undefined) + Number(operator.contains !== undefined) === 1, {\n message: 'Exactly one of equals or contains must be specified.',\n})\n .describe('Match operator for counter filters. Exactly one of equals or contains must be specified.');\nexport const CounterDefZ = z\n .object({\n events: z\n .array(CountableEventZ)\n .min(1)\n .describe('Event names to count. Use values from the countable events enum.'),\n match: z\n .record(z.string(), MatchOpZ)\n .optional()\n .describe('Property filters. Keys are event prop names or element-chain fields ' +\n '(tag_name, $el_text, attr__*). All entries AND together.'),\n})\n .describe('Defines what events to count. Registered as an accumulator predicate at config-load time.');\nexport const EventCountConditionZ = z\n .object({\n type: z.literal('event_count'),\n key: z.string().describe('Unique key for this counter (used for accumulator registration)'),\n operator: z.enum(['gte', 'lte', 'eq', 'gt', 'lt']),\n count: z.number().int().min(0).describe('Target count threshold'),\n withinMs: z\n .number()\n .positive()\n .optional()\n .describe('Time window in ms. Omit = count across entire session.'),\n counter: CounterDefZ.optional().describe('Inline counter definition. Defines what events to count.'),\n})\n .describe('Fires when accumulated event count crosses a threshold. Most powerful trigger type. ' +\n 'Example: {\"type\": \"event_count\", \"key\": \"pricing-clicks\", \"operator\": \"gte\", \"count\": 3, ' +\n '\"counter\": {\"events\": [\"ui.click\"], \"match\": {\"attr__data-cta\": {\"contains\": \"pricing\"}}}}');\nexport const ConditionZ = z.discriminatedUnion('type', [\n PageUrlConditionZ,\n RouteConditionZ,\n AnchorVisibleConditionZ,\n EventOccurredConditionZ,\n StateEqualsConditionZ,\n ViewportConditionZ,\n SessionMetricConditionZ,\n DismissedConditionZ,\n CooldownActiveConditionZ,\n FrequencyLimitConditionZ,\n EventCountConditionZ,\n]);\n// =============================================================================\n// STRATEGY SCHEMAS\n// =============================================================================\nexport const RuleZ = z\n .object({\n conditions: z\n .array(ConditionZ)\n .describe('Array of conditions \u2014 ALL must match (AND logic) for this rule to fire.'),\n value: z\n .unknown()\n .describe('Value returned when all conditions match. For triggerWhen: true = fire the action.'),\n})\n .describe('A single rule. ALL conditions must match (AND logic). Rules in a strategy are evaluated ' +\n 'top-to-bottom \u2014 first rule where all conditions match wins and returns its value.');\nexport const RuleStrategyZ = z\n .object({\n type: z.literal('rules'),\n rules: z\n .array(RuleZ)\n .describe('Ordered list of rules. Evaluated top-to-bottom \u2014 first match wins.'),\n default: z\n .unknown()\n .describe('Fallback value when no rule matches. For triggerWhen: false = do not fire by default.'),\n})\n .describe('Rule-based strategy. Evaluates rules top-to-bottom. First rule where ALL conditions match ' +\n 'returns its value. If no rule matches, returns default. ' +\n 'For triggerWhen: set value=true on matching rules, default=false.');\nexport const ScoreStrategyZ = z\n .object({\n type: z.literal('score'),\n field: z.string(),\n threshold: z.number(),\n above: z.unknown(),\n below: z.unknown(),\n})\n .describe('Score-based strategy. Compares a field value against a threshold.');\nexport const ModelStrategyZ = z\n .object({\n type: z.literal('model'),\n modelId: z.string(),\n inputs: z.array(z.string()),\n outputMapping: z.record(z.string(), z.unknown()),\n default: z.unknown(),\n})\n .describe('ML model strategy. Sends inputs to a model and maps outputs.');\nexport const ExternalStrategyZ = z\n .object({\n type: z.literal('external'),\n endpoint: z.string(),\n method: z.enum(['GET', 'POST']).optional(),\n default: z.unknown(),\n timeoutMs: z.number().optional(),\n})\n .describe('External API strategy. Calls an endpoint to determine the value.');\nexport const DecisionStrategyZ = z.discriminatedUnion('type', [\n RuleStrategyZ,\n ScoreStrategyZ,\n ModelStrategyZ,\n ExternalStrategyZ,\n]);\n/** Canonical Zod schema for the optional triggerWhen field on actions and adaptive items. */\nexport const TriggerWhenZ = DecisionStrategyZ.nullable().optional();\n// =============================================================================\n// TRIGGER DOCUMENTATION \u2014 examples and match field docs\n// Exported as constants so the schema generator can inject them into the\n// JSON schema. The Python prompt builder reads them from the schema.\n// =============================================================================\n/** Complete triggerWhen examples showing the full rules wrapper structure. */\nexport const TRIGGER_EXAMPLES = [\n {\n name: 'Click count on a specific element',\n description: 'Fire when user clicks an element with data-id=\"hero-cta\" 2+ times',\n triggerWhen: {\n type: 'rules',\n rules: [\n {\n conditions: [\n {\n type: 'event_count',\n key: 'cta-clicks',\n operator: 'gte',\n count: 2,\n counter: {\n events: ['ui.click'],\n match: { 'attr__data-id': { equals: 'hero-cta' } },\n },\n },\n ],\n value: true,\n },\n ],\n default: false,\n },\n },\n {\n name: 'Time on page threshold',\n description: 'Fire after user spends 30+ seconds on the page',\n triggerWhen: {\n type: 'rules',\n rules: [\n {\n conditions: [\n {\n type: 'session_metric',\n key: 'time_on_page',\n operator: 'gte',\n threshold: 30,\n },\n ],\n value: true,\n },\n ],\n default: false,\n },\n },\n {\n name: 'Element visible in viewport',\n description: 'Fire when a DOM element becomes visible',\n triggerWhen: {\n type: 'rules',\n rules: [\n {\n conditions: [\n {\n type: 'anchor_visible',\n anchorId: '#pricing-section',\n state: 'visible',\n },\n ],\n value: true,\n },\n ],\n default: false,\n },\n },\n {\n name: 'No trigger (fire immediately)',\n description: 'Action fires as soon as the segment matches \u2014 no in-session condition needed',\n triggerWhen: null,\n },\n];\n/** Documentation for counter.match field keys. */\nexport const MATCH_FIELD_DOCS = {\n tag_name: 'HTML tag name (e.g. \"button\", \"a\", \"input\")',\n $el_text: 'Visible text content of the element',\n 'attr__*': 'HTML attribute prefixed with attr__. Example: attr__data-id matches the data-id attribute, ' +\n 'attr__class matches the class attribute, attr__href matches the href attribute.',\n};\n// =============================================================================\n// EVENT SCOPE SCHEMA\n// =============================================================================\n/** Scopes a widget to specific events/URLs. */\nexport const EventScopeZ = z.object({\n events: z.array(z.string()),\n urlContains: z.string().optional(),\n props: z.record(z.union([z.string(), z.number(), z.boolean()])).optional(),\n});\n// =============================================================================\n// NOTIFY SCHEMA\n// =============================================================================\n/** Toast notification config for triggerWhen transitions. */\nexport const NotifyZ = z\n .object({\n title: z.string().optional(),\n body: z.string().optional(),\n icon: z.string().optional(),\n})\n .nullable()\n .optional();\n", "/**\n * @license\n * Copyright 2021 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\n\n/**\n * The Context type defines a type brand to associate a key value with the context value type\n */\nexport type Context<KeyType, ValueType> = KeyType & {__context__: ValueType};\n\n/**\n * @deprecated use Context instead\n */\nexport type ContextKey<KeyType, ValueType> = Context<KeyType, ValueType>;\n\n/**\n * A helper type which can extract a Context value type from a Context type\n */\nexport type ContextType<Key extends Context<unknown, unknown>> =\n Key extends Context<unknown, infer ValueType> ? ValueType : never;\n\n/**\n * Creates a typed Context.\n *\n * Contexts are compared with strict equality.\n *\n * If you want two separate `createContext()` calls to referer to the same\n * context, then use a key that will by equal under strict equality like a\n * string for `Symbol.for()`:\n *\n * ```ts\n * // true\n * createContext('my-context') === createContext('my-context')\n * // true\n * createContext(Symbol.for('my-context')) === createContext(Symbol.for('my-context'))\n * ```\n *\n * If you want a context to be unique so that it's guaranteed to not collide\n * with other contexts, use a key that's unique under strict equality, like\n * a `Symbol()` or object.:\n *\n * ```\n * // false\n * createContext({}) === createContext({})\n * // false\n * createContext(Symbol('my-context')) === createContext(Symbol('my-context'))\n * ```\n *\n * @param key a context key value\n * @template ValueType the type of value that can be provided by this context.\n * @returns the context key value cast to `Context<K, ValueType>`\n */\nexport function createContext<ValueType, K = unknown>(key: K) {\n return key as Context<K, ValueType>;\n}\n", "/**\n * Canvas runtime context \u2014 the shared @lit/context symbol both\n * runtime-sdk (the provider) and canvas-sdk / canvas authors (the\n * consumers) use to thread a narrow runtime handle through the canvas\n * element tree.\n *\n * Living here keeps the symbol identity stable across both packages.\n * If canvas-sdk created its own symbol with `createContext(...)`, it\n * would never match the one runtime-sdk publishes, and `<sc-mount>`\n * would silently see `undefined` instead of the widget registry.\n *\n * The shape declared here is a NARROW VIEW of `SmartCanvasRuntime`.\n * Canvas-side code reads only this subset. The runtime-sdk's\n * `SmartCanvasRuntime` type is a structural superset.\n */\nimport { createContext } from '@lit/context';\n/**\n * The @lit/context symbol. Both runtime-sdk's ContextProvider and\n * canvas-sdk's ContextConsumer must import THIS exact symbol \u2014 not a\n * symbol with the same string name \u2014 for context propagation to work.\n */\nexport const canvasRuntimeContext = createContext('syntrologie:canvas-runtime');\n", "/**\n * \"Dive deeper\" contract \u2014 the ONE canonical way a space-constrained tile\n * (FAQ, comparison, \u2026) hands its content context back to the chat conversation\n * as a deep-dive turn.\n *\n * A tile that can only show a shallow slice of its content (a comparison matrix\n * capped to a few rows, an FAQ answer clamped to a few lines) renders a \"Dive\n * deeper\" affordance. Tapping it dispatches a `window` CustomEvent carrying the\n * tile's content summary; the chat-bar mountable listens for exactly this event\n * and turns it into a user-visible chat turn so the agent can help \u2014 no\n * re-scrolling the cramped tile.\n *\n * THE PROMPT STATES AN UNMET NEED. IT NEVER ASKS A QUESTION (BUG-1784153527).\n * Emitters used to author `Tell me more \u2014 how do A vs B compare?` / `Tell me\n * more about: <question>` \u2014 direct imperatives, in the visitor's own voice. The\n * agent dutifully answered them and never asked what was actually missing,\n * which is catastrophic exactly when the tile is the problem: a live\n * \"VelaLift Sports Bra vs RunCloud Quarter Sock Pack\" comparison (two products\n * with no decision between them) got a straight-faced answer that had to\n * conclude \"the real question isn't which is better\u2026\".\n *\n * A question demands an answer; a complaint invites a question. The tap means\n * \"this tile didn't land\", so the turn says only that \u2014 the specifics (product\n * names, the question text, what's on screen) ride in `title` / `context` and\n * the turn-origin envelope, where they ground the agent WITHOUT putting an\n * answerable question in the visitor's mouth.\n *\n * This lives in the neutral `@syntrologie/sdk-contracts` (not in any single\n * adaptive) so BOTH the emitters (adaptive-product comparison, adaptive-faq)\n * and the listener (adaptive-chatbot chat bar) share one wire shape without\n * depending on each other. The event is canvas-agnostic \u2014 it never references a\n * deck, a slot, or a specific host canvas.\n */\n/** The `window` CustomEvent name carrying a {@link DiveDeeperDetail}. */\nexport const DIVE_DEEPER_EVENT = 'syntro:chat:dive-deeper';\n/**\n * Dispatch a {@link DIVE_DEEPER_EVENT} on `window`. Safe in non-DOM contexts\n * (SSR / tests without a window) \u2014 it no-ops. Emitters call this; they never\n * reach into the chat session directly, keeping the adaptive side decoupled\n * from the chat implementation.\n */\nexport function dispatchDiveDeeper(detail) {\n if (typeof window === 'undefined')\n return;\n window.dispatchEvent(new CustomEvent(DIVE_DEEPER_EVENT, { detail }));\n}\n", "/**\n * Canonical route normalization. See `routes.md` for rules and\n * `normalize-route.cases.json` for the parity corpus shared with the\n * Python implementation in syntrologie_common/sdk/routing.py.\n *\n * Two exports \u2014 `normalizeRoute` for literal paths, `normalizeRoutePattern`\n * for activation patterns containing `*`, `**`, `:param`. Today they share\n * an implementation because the rules happen to be wildcard-safe (no\n * lowercase, unreserved-only decode, slash collapse preserves `**`).\n * The seam is preserved as separate exports so the API can diverge\n * without consumer churn if rules change.\n */\n// RFC 3986 reserved characters (gen-delims + sub-delims). When a `%XX`\n// sequence decodes to one of these bytes, we keep the percent-encoded\n// form \u2014 decoding would re-segment the path or change its meaning.\nconst RESERVED_BYTES = new Set([\n 0x21, // !\n 0x23, // #\n 0x24, // $\n 0x26, // &\n 0x27, // '\n 0x28, // (\n 0x29, // )\n 0x2a, // *\n 0x2b, // +\n 0x2c, // ,\n 0x2f, // /\n 0x3a, // :\n 0x3b, // ;\n 0x3d, // =\n 0x3f, // ?\n 0x40, // @\n 0x5b, // [\n 0x5d, // ]\n]);\nconst utf8Decoder = new TextDecoder('utf-8', { fatal: false });\n/** Decode `%XX` sequences for unreserved bytes only. Collapses\n * adjacent `%XX` runs into a UTF-8 decode so `%C3%A9` \u2192 `\u00E9`. */\nfunction decodeUnreservedOnly(input) {\n let out = '';\n let pending = [];\n const flushPending = () => {\n if (pending.length === 0)\n return;\n const bytes = new Uint8Array(pending);\n out += utf8Decoder.decode(bytes);\n pending = [];\n };\n let i = 0;\n while (i < input.length) {\n const ch = input[i];\n if (ch === '%' && i + 2 < input.length && isHex(input[i + 1]) && isHex(input[i + 2])) {\n const byte = parseInt(input.slice(i + 1, i + 3), 16);\n if (RESERVED_BYTES.has(byte)) {\n flushPending();\n // Keep raw, but normalize hex case to uppercase so the\n // canonical form is stable across input casing.\n out += `%${input.slice(i + 1, i + 3).toUpperCase()}`;\n i += 3;\n }\n else {\n pending.push(byte);\n i += 3;\n }\n }\n else {\n flushPending();\n out += ch;\n i += 1;\n }\n }\n flushPending();\n return out;\n}\nfunction isHex(c) {\n return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');\n}\n/** Strip query string and hash fragment. */\nfunction stripQueryAndHash(s) {\n const q = s.indexOf('?');\n if (q !== -1)\n s = s.slice(0, q);\n const h = s.indexOf('#');\n if (h !== -1)\n s = s.slice(0, h);\n return s;\n}\n/**\n * Normalize a literal path (e.g. `window.location.pathname`, an\n * action's `route` field, a wiki route key).\n *\n * Throws `TypeError` if the input is not an absolute path. Callers\n * that want a soft API should use {@link normalizeRouteWithChange}.\n */\nexport function normalizeRoute(path) {\n if (typeof path !== 'string' || path.length === 0) {\n throw new TypeError('normalizeRoute: input must be a non-empty string');\n }\n if (!path.startsWith('/')) {\n throw new TypeError(`normalizeRoute: input must be absolute (start with '/'); got ${JSON.stringify(path)}`);\n }\n let s = stripQueryAndHash(path);\n s = decodeUnreservedOnly(s);\n s = s.replace(/\\/+/g, '/');\n if (s.length > 1 && s.endsWith('/'))\n s = s.slice(0, -1);\n return s;\n}\n/**\n * Normalize an activation route pattern. Preserves `*`, `**`,\n * `:param` exactly. Today equivalent to {@link normalizeRoute} \u2014 kept\n * as a separate export so rules can diverge later without API churn.\n */\nexport function normalizeRoutePattern(pattern) {\n return normalizeRoute(pattern);\n}\n/**\n * Normalize a route and report whether the input was already\n * canonical. Used by authoring tools to decide whether to emit a\n * warning to the LLM.\n */\nexport function normalizeRouteWithChange(path) {\n const canonical = normalizeRoute(path);\n return { canonical, changed: canonical !== path };\n}\n/** Pattern-side counterpart of {@link normalizeRouteWithChange}. */\nexport function normalizeRoutePatternWithChange(pattern) {\n const canonical = normalizeRoutePattern(pattern);\n return { canonical, changed: canonical !== pattern };\n}\n/**\n * Case-insensitive comparison of two already-canonical paths. Use\n * this anywhere two routes are compared for equality (wiki lookups,\n * non-pattern action route gates) \u2014 preserves casing in the inputs\n * while honoring case-insensitive routing on the customer's site.\n */\nexport function routesMatch(a, b) {\n return a.toLowerCase() === b.toLowerCase();\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["createContext", "key"]
7
+ }
package/dist/editor.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  import {
14
14
  __commonJS,
15
15
  __toESM
16
- } from "./chunk-5WRI5ZAA.js";
16
+ } from "./chunk-7DTOSQNC.js";
17
17
 
18
18
  // ../../../node_modules/react/cjs/react-jsx-runtime.production.js
19
19
  var require_react_jsx_runtime_production = __commonJS({
@@ -3488,6 +3488,7 @@ var EditorTextareaLit = class extends LitElement6 {
3488
3488
  }
3489
3489
  }
3490
3490
  _handleChange(e) {
3491
+ e.stopPropagation();
3491
3492
  const target = e.target;
3492
3493
  this.value = target.value;
3493
3494
  target.style.height = "auto";
@@ -3501,6 +3502,7 @@ var EditorTextareaLit = class extends LitElement6 {
3501
3502
  );
3502
3503
  }
3503
3504
  _handleInput(e) {
3505
+ e.stopPropagation();
3504
3506
  const target = e.target;
3505
3507
  this.value = target.value;
3506
3508
  target.style.height = "auto";
@@ -3533,13 +3535,14 @@ var EditorTextareaLit = class extends LitElement6 {
3533
3535
  class=${classes}
3534
3536
  name=${this.name ?? ""}
3535
3537
  placeholder=${this.placeholder ?? ""}
3538
+ .value=${this.value ?? ""}
3536
3539
  ?disabled=${this.disabled}
3537
3540
  ?required=${this.required}
3538
3541
  ?readonly=${this.readonly}
3539
3542
  rows=${this.rows ?? ""}
3540
3543
  @change=${this._handleChange}
3541
3544
  @input=${this._handleInput}
3542
- >${this.value ?? ""}</textarea>
3545
+ ></textarea>
3543
3546
  </div>
3544
3547
  `;
3545
3548
  }