@syntrologie/adapt-reviews 2.55.0 → 2.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn.js +2 -1
- package/dist/cdn.js.map +1 -1
- package/dist/chunk-IGIU6S7P.js +4051 -0
- package/dist/chunk-IGIU6S7P.js.map +7 -0
- package/dist/{chunk-ATGQNO24.js → chunk-M64YCKZP.js} +614 -15
- package/dist/chunk-M64YCKZP.js.map +7 -0
- package/dist/reviewsFlip.d.ts +10 -0
- package/dist/reviewsFlip.d.ts.map +1 -1
- package/dist/runtime.d.ts +2 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +2 -1
- package/dist/schema.js +13 -10
- package/dist/schema.js.map +1 -1
- package/package.json +2 -3
- package/dist/chunk-ATGQNO24.js.map +0 -7
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/reviewDisplay.ts", "../../../canvas-sdk/src/flip.ts", "../../../../node_modules/@lit/context/src/lib/create-context.ts", "../../../sdk-contracts/dist/canvas-context.js", "../../../sdk-contracts/dist/telemetry-events.js", "../../../sdk-contracts/dist/card-lifecycle.js", "../../../sdk-contracts/dist/detector-events.js", "../../../sdk-contracts/dist/routes.js", "../../../sdk-contracts/dist/schemas.js", "../src/reviewsFlip.ts", "../src/runtime.ts"],
|
|
4
|
+
"sourcesContent": ["import { html, LitElement, nothing } from 'lit';\nimport { styleMap } from 'lit/directives/style-map.js';\n\nexport type ReviewSource = 'imported' | 'submitted' | string;\n\nexport interface DisplayReview {\n /** Stable per-review id (dedup key). The reviews-tile hydrates by this. */\n id: string;\n product_id?: string | null;\n product_url?: string | null;\n rating: number;\n title?: string | null;\n body: string;\n reviewer_display_name?: string | null;\n verified_purchase?: boolean | null;\n created_at?: string | null;\n source: ReviewSource;\n}\n\nexport interface ReviewDisplayResponse {\n reviews: DisplayReview[];\n review_count: number;\n average_rating: number | null;\n}\n\nexport type SyntroAuthedFetch = (path: string, init?: RequestInit) => Promise<Response>;\nexport type LoadReviews = (\n limit: number,\n productId?: string | null\n) => Promise<ReviewDisplayResponse>;\n/** Hydrate an EXACT set of reviews by id (order preserved) \u2014 the id-driven\n * reviews tile's loader. Distinct from `LoadReviews` (recency/product scan). */\nexport type LoadReviewsByIds = (ids: string[]) => Promise<ReviewDisplayResponse>;\n\n/**\n * Host-page attribute a product page sets to declare which product the\n * reviews widget should scope to. Paired with the widget's\n * `productIdSelector` prop: the widget reads this attribute off the element\n * that selector matches. Keeping it a shared constant avoids drift between\n * the host page and the widget.\n */\nexport const PRODUCT_ID_ATTRIBUTE = 'data-syntro-product-id';\n\nexport async function fetchReviewDisplay(\n authedFetch: SyntroAuthedFetch,\n options: { limit?: number; productId?: string | null } = {}\n): Promise<ReviewDisplayResponse> {\n const limit = normalizeLimit(options.limit);\n const productId = options.productId?.trim();\n const productParam = productId ? `&product_id=${encodeURIComponent(productId)}` : '';\n const response = await authedFetch(`/api/adaptive/reviews?limit=${limit}${productParam}`, {\n method: 'GET',\n headers: { Accept: 'application/json' },\n });\n if (!response.ok) {\n throw new Error('Reviews are unavailable');\n }\n return (await response.json()) as ReviewDisplayResponse;\n}\n\nfunction normalizeLimit(limit: number | undefined): number {\n if (!Number.isFinite(limit)) return 20;\n return Math.min(50, Math.max(1, Math.trunc(limit as number)));\n}\n\n/**\n * Hydrate an exact, ordered set of reviews by id. The id-driven reviews tile\n * moves ONLY ids (the LLM never authors bodies); this fetches the real rows for\n * those ids from the runtime `?ids=` path. An empty id list resolves to an empty\n * response without a network call. Unknown ids are simply absent from the\n * response (the endpoint skips them, never 500s).\n */\nexport async function fetchReviewsByIds(\n authedFetch: SyntroAuthedFetch,\n ids: string[]\n): Promise<ReviewDisplayResponse> {\n const clean = ids.map((id) => id.trim()).filter((id) => id.length > 0);\n if (clean.length === 0) {\n return { reviews: [], review_count: 0, average_rating: null };\n }\n const idsParam = clean.map((id) => encodeURIComponent(id)).join(',');\n const response = await authedFetch(`/api/adaptive/reviews?ids=${idsParam}`, {\n method: 'GET',\n headers: { Accept: 'application/json' },\n });\n if (!response.ok) {\n throw new Error('Reviews are unavailable');\n }\n return (await response.json()) as ReviewDisplayResponse;\n}\n\nfunction defaultLoadReviews(\n limit: number,\n productId?: string | null\n): Promise<ReviewDisplayResponse> {\n const authedFetch = (window as unknown as { SynOS?: { authedFetch?: SyntroAuthedFetch } }).SynOS\n ?.authedFetch;\n if (!authedFetch) {\n return Promise.reject(new Error('Reviews are unavailable'));\n }\n return fetchReviewDisplay(authedFetch, { limit, productId });\n}\n\nfunction formatAverage(value: number | null): string {\n return typeof value === 'number' ? value.toFixed(1) : '0.0';\n}\n\nfunction reviewCountLabel(count: number): string {\n return count === 1 ? '1 review' : `${count} reviews`;\n}\n\nfunction createdAtLabel(value?: string | null): string | null {\n if (!value) return null;\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return null;\n return date.toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n}\n\nexport class SyntroReviews extends LitElement {\n static override properties = {\n heading: { type: String },\n limit: { type: Number },\n productId: { type: String },\n productIdSelector: { type: String },\n loadReviews: { attribute: false },\n _isLoading: { state: true },\n _error: { state: true },\n _data: { state: true },\n };\n\n heading = 'Customer reviews';\n limit = 20;\n /** Explicit product to scope reviews to. Overridden by `productIdSelector` when that resolves. */\n productId?: string;\n /**\n * CSS selector for a host-page element carrying `data-syntro-product-id`.\n * When set and resolvable, the widget scopes reviews to that product \u2014 this\n * is how one config drives per-product reviews across many product pages.\n * When unset (or unresolved), reviews are workspace-wide.\n */\n productIdSelector?: string;\n loadReviews?: LoadReviews;\n\n private _isLoading = true;\n private _error: string | null = null;\n private _data: ReviewDisplayResponse | null = null;\n private _requestId = 0;\n private _lastProductId: string | null = null;\n private _anchorObserver?: MutationObserver;\n private _observedAnchor: Element | null = null;\n\n override createRenderRoot() {\n return this;\n }\n\n override connectedCallback(): void {\n super.connectedCallback();\n this._syncAnchorObserver();\n void this._load();\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this._anchorObserver?.disconnect();\n this._anchorObserver = undefined;\n this._observedAnchor = null;\n }\n\n override updated(changed: Map<string, unknown>): void {\n if (changed.has('productIdSelector')) this._syncAnchorObserver();\n const reloadKeys = ['limit', 'productId', 'productIdSelector'];\n if (reloadKeys.some((key) => changed.has(key)) && !changed.has('_isLoading')) {\n void this._load();\n }\n }\n\n /**\n * Resolve the host-page element the `productIdSelector` points at. Guarded:\n * an invalid selector must not throw and brick the widget \u2014 treat it as\n * unresolved.\n */\n private _resolveAnchorEl(): Element | null {\n if (!this.productIdSelector || typeof document === 'undefined') return null;\n try {\n return document.querySelector(this.productIdSelector);\n } catch {\n return null;\n }\n }\n\n /**\n * Resolve the product to scope reviews to. A `productIdSelector` element\n * carrying `data-syntro-product-id` wins; otherwise fall back to the\n * explicit `productId`; otherwise null.\n */\n private _resolveProductId(): string | null {\n const fromDom = this._resolveAnchorEl()?.getAttribute(PRODUCT_ID_ATTRIBUTE)?.trim();\n if (fromDom) return fromDom;\n const explicit = this.productId?.trim();\n return explicit ? explicit : null;\n }\n\n /**\n * Watch the anchor's `data-syntro-product-id` so the widget re-scopes on SPA\n * navigation. On a route like `/product/:slug`, the host reconciles the SAME\n * anchor node and just mutates the attribute \u2014 nothing re-triggers a Lit\n * prop change, so without this observer the widget would keep showing the\n * previous product's reviews.\n */\n private _syncAnchorObserver(): void {\n const anchor = this._resolveAnchorEl();\n if (anchor === this._observedAnchor) return;\n this._anchorObserver?.disconnect();\n this._observedAnchor = anchor;\n if (!anchor || typeof MutationObserver === 'undefined') return;\n this._anchorObserver = new MutationObserver(() => {\n if (this._resolveProductId() !== this._lastProductId) void this._load();\n });\n this._anchorObserver.observe(anchor, {\n attributes: true,\n attributeFilter: [PRODUCT_ID_ATTRIBUTE],\n });\n }\n\n private async _load(): Promise<void> {\n const requestId = ++this._requestId;\n const productId = this._resolveProductId();\n // Fail closed: when a productIdSelector is configured but nothing resolves,\n // do NOT fall back to a workspace-wide fetch \u2014 that would bleed other\n // products' reviews onto this page. Show an empty state; the anchor\n // observer re-fires _load once `data-syntro-product-id` appears.\n if (this.productIdSelector && !productId) {\n this._lastProductId = null;\n this._isLoading = false;\n this._error = null;\n this._data = { reviews: [], review_count: 0, average_rating: null };\n return;\n }\n this._lastProductId = productId;\n this._isLoading = true;\n this._error = null;\n try {\n const loader = this.loadReviews ?? defaultLoadReviews;\n const data = await loader(normalizeLimit(this.limit), productId);\n if (requestId !== this._requestId) return;\n this._data = data;\n } catch {\n if (requestId !== this._requestId) return;\n this._data = null;\n this._error = 'Reviews are unavailable';\n } finally {\n if (requestId === this._requestId) {\n this._isLoading = false;\n }\n }\n }\n\n override render() {\n const rootStyles: Record<string, string> = {\n display: 'block',\n width: '100%',\n color: 'var(--sc-tile-text-color, var(--sc-overlay-text-color, #111827))',\n fontFamily: 'var(--sc-font-family, inherit)',\n };\n\n return html`\n <section data-syntro-reviews style=${styleMap(rootStyles)} aria-label=${this.heading}>\n ${\n this._isLoading\n ? this._renderFrame(html`<div style=${styleMap(this._stateStyles())}>Loading...</div>`)\n : this._error\n ? this._renderFrame(\n html`<div style=${styleMap(this._stateStyles())}>${this._error}</div>`\n )\n : this._data && this._data.reviews.length > 0\n ? this._renderReviews(this._data)\n : this._renderFrame(\n html`<div style=${styleMap(this._stateStyles())}>No reviews yet</div>`\n )\n }\n </section>\n `;\n }\n\n private _renderFrame(content: unknown) {\n return html`\n <div style=${styleMap(this._panelStyles())}>\n <div style=${styleMap(this._headingStyles())}>${this.heading}</div>\n ${content}\n </div>\n `;\n }\n\n private _renderReviews(data: ReviewDisplayResponse) {\n return html`\n <div style=${styleMap(this._panelStyles())}>\n <div style=${styleMap(this._summaryStyles())}>\n <div>\n <div style=${styleMap(this._headingStyles())}>${this.heading}</div>\n <div style=${styleMap(this._metaStyles())}>${reviewCountLabel(data.review_count)}</div>\n </div>\n <div style=${styleMap(this._ratingBadgeStyles())} aria-label=${`${formatAverage(data.average_rating)} out of 5`}>\n <span style=${styleMap({ fontSize: '20px', fontWeight: '700' })}>${formatAverage(data.average_rating)}</span>\n <span aria-hidden=\"true\">\u2605</span>\n </div>\n </div>\n <div style=${styleMap({ display: 'grid', gap: '10px' })}>\n ${data.reviews.map((review) => this._renderReview(review))}\n </div>\n </div>\n `;\n }\n\n private _renderReview(review: DisplayReview) {\n const date = createdAtLabel(review.created_at);\n return html`\n <article style=${styleMap(this._reviewStyles())}>\n <div style=${styleMap(this._reviewHeaderStyles())}>\n <div style=${styleMap({ fontWeight: '700' })}>${review.title || 'Review'}</div>\n <div aria-label=${`${review.rating} out of 5`} style=${styleMap(this._starsStyles())}>\n ${'\u2605'.repeat(Math.max(0, Math.min(5, Math.round(review.rating))))}\n </div>\n </div>\n <p style=${styleMap(this._bodyStyles())}>${review.body}</p>\n <div style=${styleMap(this._metaStyles())}>\n ${review.reviewer_display_name || 'Customer'}\n ${review.verified_purchase ? html`<span> \u00B7 Verified purchase</span>` : nothing}\n ${date ? html`<span> \u00B7 ${date}</span>` : nothing}\n </div>\n </article>\n `;\n }\n\n private _panelStyles(): Record<string, string> {\n return {\n boxSizing: 'border-box',\n border: '1px solid var(--sc-content-divider-color, rgba(148, 163, 184, 0.24))',\n borderRadius: '8px',\n background: 'var(--sc-tile-background, #ffffff)',\n padding: '14px',\n boxShadow: 'var(--sc-tile-shadow, 0 8px 24px rgba(15, 23, 42, 0.08))',\n };\n }\n\n private _headingStyles(): Record<string, string> {\n return {\n fontSize: '15px',\n lineHeight: '20px',\n fontWeight: '700',\n margin: '0',\n };\n }\n\n private _summaryStyles(): Record<string, string> {\n return {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: '12px',\n marginBottom: '12px',\n };\n }\n\n private _ratingBadgeStyles(): Record<string, string> {\n return {\n display: 'inline-flex',\n alignItems: 'center',\n gap: '4px',\n color: 'hsl(var(--sc-accent-color, 221 83% 53%))',\n whiteSpace: 'nowrap',\n };\n }\n\n private _reviewStyles(): Record<string, string> {\n return {\n borderTop: '1px solid var(--sc-content-divider-color, rgba(148, 163, 184, 0.2))',\n paddingTop: '10px',\n };\n }\n\n private _reviewHeaderStyles(): Record<string, string> {\n return {\n display: 'flex',\n alignItems: 'baseline',\n justifyContent: 'space-between',\n gap: '10px',\n fontSize: '14px',\n lineHeight: '20px',\n };\n }\n\n private _starsStyles(): Record<string, string> {\n return {\n color: 'hsl(var(--sc-accent-color, 221 83% 53%))',\n fontSize: '13px',\n lineHeight: '18px',\n whiteSpace: 'nowrap',\n };\n }\n\n private _bodyStyles(): Record<string, string> {\n return {\n margin: '6px 0',\n fontSize: '14px',\n lineHeight: '20px',\n };\n }\n\n private _metaStyles(): Record<string, string> {\n return {\n color: 'var(--sc-content-text-secondary-color, #64748b)',\n fontSize: '12px',\n lineHeight: '18px',\n };\n }\n\n private _stateStyles(): Record<string, string> {\n return {\n marginTop: '8px',\n color: 'var(--sc-content-text-secondary-color, #64748b)',\n fontSize: '14px',\n lineHeight: '20px',\n };\n }\n}\n\nif (!customElements.get('syntro-reviews')) {\n customElements.define('syntro-reviews', SyntroReviews);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'syntro-reviews': SyntroReviews;\n }\n}\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 * @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 * Telemetry catalog \u2014 the single source of truth for every `syntro_*` event\n * the runtime SDK emits and the backend queries (FEAT-1789150184).\n *\n * Names are `syntro_<subject>_<verb>`. Verbs are CLOSED: a new surface adds\n * subjects, never verbs. `scope` says which context must be active when the\n * event fires: `plan` (a config has been served) or `open` (the concierge is\n * open, so `open_id` and `surface` exist). `props` are the event's own\n * required properties; the envelope (variant_id, experiment_key, and while\n * open surface + open_id) is stamped by the core and not listed. The core\n * whitelists props to this list \u2014 anything else is dropped.\n *\n * `telemetry-events.json` is emitted from this file at build time and turned\n * into a Python enum for the backend (`make generate-types`).\n */\nexport const TELEMETRY_VERBS = [\n 'opened',\n 'closed',\n 'seen',\n 'triggered',\n 'interacted',\n 'paged',\n 'tapped',\n 'dismissed',\n 'sent',\n 'received',\n 'interrupted',\n 'toggled',\n 'served',\n];\n/**\n * What opened the concierge. `auto` = scroll handoff or a persisted-open\n * restore; `deep_link` = programmatic `open()`; `host` = the surface itself\n * gave the canvas the screen (an agent surface switching its card to\n * `fullscreen`). `host` is deliberately its own value: the SDK observes the\n * display-mode change, never the gesture behind it, so folding it into `fab`\n * or `auto` would assert intent nobody measured.\n */\nexport const CONCIERGE_OPEN_TRIGGERS = ['fab', 'auto', 'alert_chip', 'deep_link', 'host'];\n/**\n * What closed it. `fab` covers every pressed close affordance and programmatic\n * `close()`.\n *\n * `navigation` is NOT only \"the visitor clicked a link\". It is the catch-all\n * for every close that is not a gesture on the concierge itself, and today it\n * covers four distinct situations:\n * 1. a velvet nav-chip click / alert activate that navigates the host page;\n * 2. the empty-tileset auto-close (nothing left to show \u2014 not a gesture);\n * 3. the host removing the canvas element from the DOM while it was open;\n * 4. page unload (tab close / navigating away), emitted on `pagehide`.\n * Do not read `reason = 'navigation'` as intent. Splitting it is a catalog\n * change (the enum is spec-binding), so until that happens any \"why did they\n * leave?\" analysis must treat this value as \"ended without a gesture\".\n *\n * `host` is the agent-surface counterpart of the `host` open trigger: the\n * surface took the screen back and put the card inline again. It stays out of\n * `navigation` precisely so it does not inherit that value's ambiguity.\n */\nexport const CONCIERGE_CLOSE_REASONS = ['fab', 'escape', 'navigation', 'host'];\n/**\n * The in-app render container an open happened in, stamped on every\n * open-scope event. `overlay` is the bundled surface, `velvet` a custom\n * canvas mounted on the merchant's page, `inline` the canvas painted inside a\n * CHAT APP's card (surface type `mcp-app`).\n *\n * Closed, and pinned by the contract test, because every open-scope row the\n * backend has ever stored carries one of these: adding, removing or reusing a\n * value rewrites what the existing rows mean. Telegram is not a value here.\n * It renders through the same inline shell but reports as `overlay` or\n * `velvet`, the way it always has.\n */\nexport const TELEMETRY_SURFACES = ['velvet', 'overlay', 'inline'];\nexport const DECK_PAGE_METHODS = ['swipe', 'tap', 'auto'];\n/**\n * What the shopper left the canvas FOR. `page` is an alert chip or any other\n * editorial link; `product` is an anchor inside a tile (a product page, a\n * comparison row, a recommendation).\n *\n * Closed, like every other vocabulary here. It deliberately does NOT carry\n * the label or the destination: both are model- or merchant-authored text,\n * and this catalog carries none.\n */\nexport const LINK_OPEN_KINDS = ['page', 'product'];\n/**\n * How a product page came to open inside a chat-app card: a product link on a\n * tile, the rail of the conversation's picks inside the view, the view\n * reopening after the host remounted the card, or a related product the page\n * itself offered (a cross-sell tile's View).\n */\nexport const PRODUCT_VIEW_OPEN_SOURCES = ['tile', 'rail', 'restore', 'related'];\nexport const CHAT_SEND_VIAS = ['typed', 'intro_suggestion', 'pill', 'dive_deeper'];\n/**\n * Whether a search query looked like a single-term lookup or something the\n * takeover surface should reach for retrieval/ranking to answer. Stamped by\n * the interceptor's own classifier, never inferred downstream from the (never\n * emitted) query text.\n */\nexport const SEARCH_CLASSIFICATIONS = ['simple', 'complex'];\n/**\n * The layouts the search takeover surface can render results in. Closed to\n * what the surface actually ships, so a variant added here without a\n * renderer is a lie the catalog would otherwise let through.\n */\nexport const SEARCH_VARIANTS = ['grid', 'stacked', 'rail', 'hero', 'compare'];\n/**\n * How the visitor's search reached the interceptor. `enter` is a keydown on\n * the host's own search input; `submit` is the host's search form\n * submitting. These are the only two entry points the interceptor installs\n * on \u2014 no value here should exist that the SDK cannot itself produce.\n */\nexport const SEARCH_ENTRY_POINTS = ['enter', 'submit'];\n/**\n * Why the takeover fell back to the store's own native search instead of\n * rendering. `not_ready` = the adaptive or the platform adapter had not landed\n * yet; `error` = a request or a render threw, or a surface painted nothing;\n * `budget` = a latency budget was exceeded. Distinct from\n * `SEARCH_DISMISS_REASONS`: this enum is about the surface never taking over,\n * that one is about a takeover the visitor left.\n *\n * There is deliberately no `disabled`. A workspace the takeover is switched\n * off for never claims the shopper's Enter at all, so there is nothing to\n * report a fallback FROM \u2014 the store's search simply runs, as it does on\n * every storefront that has never heard of us. A value nothing can emit is a\n * value someone eventually reads as a real zero.\n */\nexport const SEARCH_FALLBACK_REASONS = ['not_ready', 'error', 'budget'];\n/**\n * Why an already-rendered search takeover closed. `escape` = Escape key or\n * an equivalent close gesture on our own panel; `navigation` = the visitor\n * followed a result or otherwise left the page; `native_link` = the visitor\n * used the surface's own \"see all results\" / native-search fallback link.\n * Revision 1 of this catalog reused `SEARCH_FALLBACK_REASONS` here, which is\n * wrong: a dismissal is a takeover that DID render, never a fallback.\n */\nexport const SEARCH_DISMISS_REASONS = ['escape', 'navigation', 'native_link'];\n/**\n * What kind of change a search follow-up represents relative to the search\n * already on screen. `refine` = the visitor typed a new/adjusted query;\n * `chip_removed` = a filter chip was cleared; `item_dismissed` = a single\n * result was dismissed from the surface without leaving it.\n */\nexport const SEARCH_DELTA_KINDS = ['refine', 'chip_removed', 'item_dismissed'];\nconst entry = (e) => e;\nexport const TELEMETRY_EVENTS = {\n // --- intervention metrics (April 2026) \u2014 wire names unchanged, all plan-scope\n syntro_config_served: entry({\n subject: 'config',\n verb: 'served',\n scope: 'plan',\n props: ['tiles', 'actions'],\n }),\n syntro_intervention_seen: entry({\n subject: 'intervention',\n verb: 'seen',\n scope: 'plan',\n props: ['intervention_id', 'intervention_kind'],\n }),\n syntro_intervention_triggered: entry({\n subject: 'intervention',\n verb: 'triggered',\n scope: 'plan',\n props: ['intervention_id', 'intervention_kind'],\n }),\n syntro_intervention_interacted: entry({\n subject: 'intervention',\n verb: 'interacted',\n scope: 'plan',\n props: ['intervention_id', 'intervention_kind', 'interaction_type'],\n }),\n // --- concierge (FEAT-1789150184) ---------------------------------------\n syntro_concierge_opened: entry({\n subject: 'concierge',\n verb: 'opened',\n scope: 'open',\n props: ['trigger', 'page_path'], // trigger: ConciergeOpenTrigger\n }),\n syntro_concierge_closed: entry({\n subject: 'concierge',\n verb: 'closed',\n scope: 'open',\n props: ['duration_ms', 'reason', 'messages_sent', 'tiles_dismissed', 'pages_viewed'], // reason: ConciergeCloseReason\n }),\n /**\n * The shopper asked to leave the canvas by a link. Emitted on intent, before\n * the host answers, so refused or denied opens are counted too (parity with\n * syntro_concierge_closed reason navigation).\n *\n * On a merchant's own page a link tap closes the canvas, and\n * `syntro_concierge_closed { reason: 'navigation' }` is the row that\n * records it. Inside a chat app's card the frame is sandboxed: the canvas\n * cannot navigate, does not close, and asks the host to open the link\n * instead, so that close row never fires. This is its counterpart, and it\n * carries the same `reason` so one query covers both surfaces.\n */\n syntro_link_opened: entry({\n subject: 'link',\n verb: 'opened',\n scope: 'open',\n props: ['kind', 'reason'], // kind: LinkOpenKind, reason: ConciergeCloseReason\n }),\n /**\n * A store's product page opened inside a chat-app card instead of leaving\n * through the host (the card's product view). `product` is the store's\n * handle, never the display name; a rail switch is a new open. Its\n * counterpart when the shopper goes back to the conversation's picks is\n * syntro_product_view_closed.\n *\n * Plan scope, like the card's own syntro_card_lifecycle_seen: an inline\n * card holds an open scope only while the host has it fullscreen, and a\n * view opened from a tile fires before the host answers that request (or\n * after it refused), so an open-scope row here would be dropped. The core\n * still stamps surface and open_id whenever an open is live.\n */\n syntro_product_view_opened: entry({\n subject: 'product_view',\n verb: 'opened',\n scope: 'plan',\n props: ['product', 'from'], // from: ProductViewOpenSource\n }),\n syntro_product_view_closed: entry({\n subject: 'product_view',\n verb: 'closed',\n scope: 'plan',\n props: ['product'],\n }),\n syntro_deck_paged: entry({\n subject: 'deck',\n verb: 'paged',\n scope: 'open',\n props: ['from_index', 'to_index', 'method'], // method: DeckPageMethod\n }),\n syntro_tile_dismissed: entry({\n subject: 'tile',\n verb: 'dismissed',\n scope: 'open',\n props: ['tile_id', 'tile_kind', 'visible_ms'],\n }),\n // A tile's own data load finished: the network wait between the concierge\n // mounting a tile and the shopper seeing its content (BUG-1784767817 was a\n // 30 s reviews hang here). `outcome` is 'ok' | 'error'; `requested` is how\n // many items the tile asked for, `received` how many came back.\n syntro_tile_data_received: entry({\n subject: 'tile_data',\n verb: 'received',\n scope: 'open',\n props: ['tile_id', 'tile_kind', 'total_ms', 'outcome', 'requested', 'received'],\n }),\n // The three chat atoms are OPEN-scope: they carry `surface` / `open_id` and\n // only make sense inside a concierge open. An INLINE chat bar (no concierge)\n // still dispatches them on every turn, and the core drops each one and\n // increments the `telemetry_dropped` health counter. That is EXPECTED on an\n // inline-chat host page, not a bug \u2014 `telemetry_dropped` there mixes \"real\n // defect\" with \"inline chat has no open scope\", so do not alert on it alone.\n syntro_chat_message_sent: entry({\n subject: 'chat_message',\n verb: 'sent',\n scope: 'open',\n props: ['turn', 'chars', 'via'], // via: ChatSendVia\n }),\n syntro_chat_reply_received: entry({\n subject: 'chat_reply',\n verb: 'received',\n scope: 'open',\n // mount_tools: the mount_* tool name of every mount this reply fired, in order (two product cards appear twice).\n props: ['turn', 'ttft_ms', 'total_ms', 'mounts', 'cards', 'mount_tools'],\n }),\n syntro_chat_interrupted: entry({\n subject: 'chat',\n verb: 'interrupted',\n scope: 'open',\n props: ['turn', 'after_ms'],\n }),\n syntro_chat_maximize_toggled: entry({\n subject: 'chat_maximize',\n verb: 'toggled',\n scope: 'open',\n props: ['maximized'],\n }),\n syntro_chat_takeover_triggered: entry({\n subject: 'chat_takeover',\n verb: 'triggered',\n scope: 'open',\n props: ['turn'],\n }),\n // --- search takeover (2026-09-17) ---------------------------------------\n // All plan-scope: the search interceptor fires before any concierge open\n // exists. No prop here ever carries query text (SEE the contract test's\n // no-text-prop rule) \u2014 classification/signal_hits are the interceptor's\n // own derived signals, never the string itself.\n syntro_search_sent: entry({\n subject: 'search',\n verb: 'sent',\n scope: 'plan',\n props: ['classification', 'word_count', 'signal_hits', 'entry_point', 'platform'], // classification: SearchClassification, entry_point: SearchEntryPoint\n }),\n // `ms_to_skeleton` is intentionally the only timing prop: it is always\n // knowable the instant the surface mounts. `ms_to_first_content` is NOT\n // declared here (revision 1 defect) because a surface that never painted\n // content still fires this event, and a required prop it cannot supply\n // would make TelemetryCore.emit drop exactly the row worth measuring.\n syntro_search_surface_seen: entry({\n subject: 'search_surface',\n verb: 'seen',\n scope: 'plan',\n props: ['variant', 'classification', 'result_count', 'ms_to_skeleton'], // variant: SearchVariant, classification: SearchClassification\n }),\n syntro_search_result_tapped: entry({\n subject: 'search_result',\n verb: 'tapped',\n scope: 'plan',\n props: ['variant', 'region_id', 'module', 'position'], // variant: SearchVariant\n }),\n syntro_search_followup_sent: entry({\n subject: 'search_followup',\n verb: 'sent',\n scope: 'plan',\n props: ['delta_kind', 'variant'], // delta_kind: SearchDeltaKind, variant: SearchVariant\n }),\n // Declares `ms_since_boot` (not a \"since surface mounted\" timing prop) so a\n // not-ready fallback the takeover never rendered is still measurable \u2014\n // revision 1 emitted nothing on this path.\n syntro_search_native_served: entry({\n subject: 'search_native',\n verb: 'served',\n scope: 'plan',\n props: ['reason', 'platform', 'ms_since_boot'], // reason: SearchFallbackReason\n }),\n syntro_search_takeover_dismissed: entry({\n subject: 'search_takeover',\n verb: 'dismissed',\n scope: 'plan',\n props: ['variant', 'reason'], // variant: SearchVariant, reason: SearchDismissReason\n }),\n // --- chat-app card lifecycle (2026-09-24) --------------------------------\n // One row per stage a card reached, from `reportCardStage` in\n // card-lifecycle.ts. Plan scope: most stages happen before a config is\n // served and none happen inside a concierge open. `stage` is a\n // CardLifecycleStage; `since_shell_ms` is the card shell's own clock, so a\n // card's rows order themselves from the first byte of the page. The same\n // stages reach the connector through the shell's beacon, which is the only\n // record of a card whose SDK never booted.\n syntro_card_lifecycle_seen: entry({\n subject: 'card_lifecycle',\n verb: 'seen',\n scope: 'plan',\n props: ['stage', 'since_shell_ms'], // stage: CardLifecycleStage\n }),\n};\nexport const TELEMETRY_EVENT_NAMES = Object.keys(TELEMETRY_EVENTS);\n/** DOM bridge: components dispatch this (bubbles + composed) and the core listens at document. */\nexport const TELEMETRY_DOM_EVENT = 'syntro:telemetry';\n/**\n * Build the exact object written to `src/telemetry-events.json` at build time\n * (`scripts/emit-telemetry-catalog.mjs`), which `make app-generate-telemetry-events`\n * turns into the Python enum. Lives here \u2014 not in the script \u2014 so the shape is\n * declared once and `telemetry-events.contract.test.ts` can prove the committed\n * JSON has not drifted from this file.\n */\nexport function buildTelemetryCatalogJson() {\n return {\n verbs: [...TELEMETRY_VERBS],\n events: Object.fromEntries(Object.entries(TELEMETRY_EVENTS).map(([name, e]) => [\n name,\n {\n subject: e.subject,\n verb: e.verb,\n scope: e.scope,\n props: [...e.props],\n },\n ])),\n };\n}\nexport function isTelemetryEventName(name) {\n // ES2020 target: hasOwnProperty via the prototype, not the ES2022 static helper.\n // biome-ignore lint/suspicious/noPrototypeBuiltins: Object.hasOwn is ES2022; repo target is ES2020.\n return Object.prototype.hasOwnProperty.call(TELEMETRY_EVENTS, name);\n}\n", "/**\n * Every stage of a chat-app card's life, as one closed vocabulary.\n *\n * A card that never starts leaves nothing behind: the SDK's telemetry only\n * exists once the SDK has booted and PostHog is up, which is exactly what did\n * not happen. So the card shell (the connector's `render-shell.ts`) installs a\n * tiny beacon as the FIRST script on the page, reports its own stages through\n * it, and exposes it as `window.__syntroCardBeacon` for the SDK's seams to\n * report theirs. The same stages then go to PostHog as one catalog event once\n * the telemetry core is listening.\n *\n * Shared by the connector (which refuses any stage not listed here), the host\n * bridge, the host-agent controller and the canvas. Adding a stage is a\n * contract change on both sides of the wire.\n */\nimport { TELEMETRY_DOM_EVENT } from './telemetry-events.js';\nexport const CARD_LIFECYCLE_STAGES = [\n // The shell, before any SDK code exists.\n 'shell_parsed',\n 'sdk_script_loaded',\n 'sdk_script_failed',\n 'csp_violation',\n 'js_error',\n 'visibility',\n 'pagehide',\n // The SDK's own seams.\n 'sdk_init',\n 'bridge_connecting',\n 'bridge_connected',\n 'bridge_failed',\n 'tool_result_received',\n 'tool_result_rejected',\n 'tool_result_missing',\n 'session_bound',\n 'frame_applied',\n 'tiles_rendered',\n 'tiles_empty',\n 'display_mode_changed',\n 'read_brand_view_called',\n 'read_brand_view_answered',\n // The published config asked for something this card cannot do; the card\n // does the safe thing instead (e.g. links out) and says which field.\n 'config_refused',\n // The card could not fetch its live config (GrowthBook failed or timed\n // out); `reason` says which. The card goes on as a storefront would.\n 'config_failed',\n];\n/**\n * The longest turn id and session ref a card event may carry. The connector\n * validates against these, and `reportCardStage` cuts any longer id to them\n * before it leaves: the shell keeps a turn id for every later event, so one\n * the connector refused would cost the rest of that card's events.\n */\nexport const CARD_TURN_ID_MAX = 100;\nexport const CARD_SESSION_REF_MAX = 300;\n/** The window property the card shell installs; absent everywhere else. */\nexport const CARD_BEACON_GLOBAL = '__syntroCardBeacon';\n/**\n * Report one stage. A no-op, returning false, wherever no card shell\n * installed the beacon, which is every storefront. Never throws into the\n * seam that called it: a card that cannot report must still render.\n */\nexport function reportCardStage(stage, detail) {\n if (typeof window === 'undefined')\n return false;\n const beacon = window[CARD_BEACON_GLOBAL];\n if (typeof beacon !== 'function')\n return false;\n let since;\n try {\n since = beacon(stage, boundIds(detail));\n }\n catch {\n // The beacon is best effort; the catalog event below still goes.\n }\n const sinceShellMs = Math.round(typeof since === 'number' && Number.isFinite(since) ? since : performance.now());\n // A listener that throws is reported by the event loop, never rethrown here.\n document.dispatchEvent(new CustomEvent(TELEMETRY_DOM_EVENT, {\n detail: {\n name: 'syntro_card_lifecycle_seen',\n props: { stage, since_shell_ms: sinceShellMs },\n },\n }));\n return true;\n}\nfunction boundIds(detail) {\n if (!detail)\n return detail;\n const { turnId, sessionRef } = detail;\n const longTurn = typeof turnId === 'string' && turnId.length > CARD_TURN_ID_MAX;\n const longRef = typeof sessionRef === 'string' && sessionRef.length > CARD_SESSION_REF_MAX;\n if (!longTurn && !longRef)\n return detail;\n return {\n ...detail,\n ...(longTurn ? { turnId: turnId.slice(0, CARD_TURN_ID_MAX) } : {}),\n ...(longRef ? { sessionRef: sessionRef.slice(0, CARD_SESSION_REF_MAX) } : {}),\n };\n}\n/**\n * Where a connector names the visitor a card is drawn for, on every tool\n * result it returns: `_meta['syntro/visitor'] = { id }`.\n *\n * The id rides the result and never the card's page, because a chat app keeps\n * one copy of that page per connector and serves it to every conversation. The\n * id is the one the connector resolved the agent's config with, so the card\n * that adopts it buckets into the same variation as the turn it renders.\n */\nexport const CARD_VISITOR_META_KEY = 'syntro/visitor';\n/**\n * The GrowthBook attributes that say \"a chat-app card\", as both evaluators of\n * a card's config set them: the runtime SDK in the card, once the host\n * handshake answers, and the connector, resolving the agent's turn. One\n * source, so the two cannot drift into different variations for one visitor.\n */\nexport const MCP_APP_SURFACE_ATTRIBUTES = Object.freeze({\n surface_type: 'mcp-app',\n surface: 'personal_agent',\n});\n/** The runtime SDK's own identity bounds (`IDENTITY_MIN`/`IDENTITY_MAX` in bootstrap-init.ts). */\nconst VISITOR_ID_MIN = 16;\nconst VISITOR_ID_MAX = 200;\n/**\n * The visitor one tool result names: its id, `absent` when the result names\n * nobody (another tool, or an older connector), or the reason it was refused.\n */\nexport function cardVisitorIdFrom(result) {\n const meta = result?._meta;\n if (!meta || typeof meta !== 'object')\n return { absent: true };\n const visitor = meta[CARD_VISITOR_META_KEY];\n if (visitor === undefined)\n return { absent: true };\n if (!visitor || typeof visitor !== 'object' || Array.isArray(visitor)) {\n return { refused: 'not_an_object' };\n }\n const id = visitor.id;\n if (typeof id !== 'string')\n return { refused: 'not_a_string' };\n const trimmed = id.trim();\n if (trimmed.length < VISITOR_ID_MIN)\n return { refused: 'too_short' };\n if (trimmed.length > VISITOR_ID_MAX)\n return { refused: 'too_long' };\n if (trimmed.startsWith('$'))\n return { refused: 'sentinel' };\n return { id: trimmed };\n}\n", "/**\n * The prop shapes of every CANONICAL runtime-bus event \u2014 the producer/consumer\n * contract for the visitor-behavior signals that reach the chat agent.\n *\n * ## Why this module exists\n *\n * Two independent packages have to agree on these prop names and nothing was\n * holding them to it:\n *\n * producer `packages/event-processor/src/detectors/*` (rrweb detectors)\n * `packages/runtime-sdk/src/instrumentation/*` (bus instrumentation)\n * consumer `packages/adaptives/adaptive-chatbot/src/observer/allowlist.ts`\n *\n * Both sides passed `Record<string, unknown>` around, so a disagreement was not\n * a build error \u2014 it was an observation rendered into the model's prompt with\n * its content missing. `hovered on ''`. `idle`. Shipped for months\n * (BUG-1786923485), and once before that as a name mismatch (BUG-1784146789).\n *\n * ## The rule this module enforces\n *\n * `CanonicalBusEventProps` is the ONE declaration. Producers emit through\n * `detectorEvent()` (event-processor), which types `props` as\n * `CanonicalBusEventProps[N]`; consumers render through an exhaustive\n * `Record<CanonicalBusEventName, \u2026>` whose functions receive\n * `Partial<CanonicalBusEventProps[N]>`. Therefore:\n *\n * - renaming a key in a detector \u2192 producer fails `tsc`\n * - renaming a key in the summarizer \u2192 consumer fails `tsc`\n * - adding an event with no summarizer case \u2192 consumer fails `tsc` (missing\n * key in the exhaustive record)\n * - renaming a key HERE \u2192 both sides fail `tsc`\n *\n * None of those can degrade to an empty string at runtime.\n *\n * ## Deliberately dependency-free\n *\n * No zod, no `@lit/context`, no DOM types \u2014 `@syntrologie/event-processor` has\n * zero runtime dependencies and imports this via the `./detector-events`\n * subpath so it stays that way.\n *\n * ## Deliberately WITHOUT string index signatures\n *\n * An index signature on a props type makes `props.anyTypoAtAll` legal and\n * silently reintroduces the exact bug this module exists to prevent. The one\n * index signature below is a template-literal pattern (`attr__${string}`), so\n * arbitrary DOM attributes are still expressible while `text` / `tag_name`\n * typos remain errors.\n */\nexport const DETECTOR_EVENT_NAMES = [\n 'ui.hover',\n 'ui.idle',\n 'ui.hesitate',\n 'ui.rage_click',\n 'ui.scroll_thrash',\n 'ui.focus_bounce',\n];\nexport const CANONICAL_BUS_EVENT_NAMES = [\n ...DETECTOR_EVENT_NAMES,\n 'nav.section_viewed',\n 'nav.scroll_depth',\n];\nconst _canonicalExhaustive = true;\nconst _detectorExhaustive = true;\nvoid _canonicalExhaustive;\nvoid _detectorExhaustive;\n/** Runtime membership test \u2014 narrows an arbitrary event name to the contract. */\nexport function isCanonicalBusEvent(name) {\n return CANONICAL_BUS_EVENT_NAMES.includes(name);\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", "/**\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';\nimport { normalizeRoute } from './routes.js';\n// =============================================================================\n// ANCHOR ID SCHEMA\n// =============================================================================\n// A selector containing \"{\" or \"}\" can never match a real DOM element via\n// querySelectorAll \u2014 those characters are exclusively CSS rule delimiters,\n// not valid selector syntax. They ARE, however, exactly what\n// content:hideBySelector's executor needs to escape the single CSS rule it\n// injects as raw text into a live <style> element\n// (`${selector} { ${prop}: ${value} !important; }` \u2014 see\n// executeHideBySelector in adaptive-content/src/runtime.ts): a selector\n// containing `}` closes that rule early and lets the rest of the string\n// splice in arbitrary attacker-controlled CSS anywhere on the host page\n// (defacement, CSS-based data exfiltration via attribute selectors,\n// clickjacking overlays). This constraint lives on THIS canonical AnchorIdZ\n// (not a hideBySelector-only variant, and not a second copy in a\n// downstream package) so that every action kind in every package \u2014 core\n// and adaptive \u2014 inherits it automatically: there is exactly one\n// `AnchorIdZ`, and every consumer imports it from here (SEC-067,\n// BUG-1786764688). No legitimate selector for any action kind needs a\n// literal brace.\nexport const NO_CSS_BREAKOUT_PATTERN = /^[^{}]*$/;\nexport const AnchorIdZ = z\n .object({\n selector: z\n .string()\n .regex(NO_CSS_BREAKOUT_PATTERN, {\n message: 'selector must not contain \"{\" or \"}\" \u2014 not valid CSS selector syntax, and content:hideBySelector injects this value directly into a <style> element where these characters would break out of the generated rule.',\n })\n .describe('CSS selector for the target element'),\n route: z\n .union([z.string(), z.array(z.string())])\n .superRefine((value, ctx) => {\n // Backend parity check (Finding 1, .superpowers/sdd/\n // duplicated-definitions-audit.md): the backend's\n // RouteCanonicalityCheck (platform/backend/app/services/\n // sdk_config_checks.py) rejects any actions[].anchorId.route that\n // isn't already in `normalize_route`'s canonical form (absolute,\n // no trailing slash, no doubled slashes, no query/hash, unreserved\n // percent-decoding only). Before this check, authoring accepted\n // configs the backend would 422 on (e.g. a trailing slash, or a\n // bare \"**\" missing the leading \"/\"). Reuses `normalizeRoute` from\n // ./routes.ts \u2014 the SAME function already parity-tested against\n // the Python implementation via normalize-route.cases.json \u2014 so\n // this is single-sourced, not a third reimplementation of the\n // canonicalization rule.\n for (const route of Array.isArray(value) ? value : [value]) {\n let canonical;\n try {\n canonical = normalizeRoute(route);\n }\n catch (err) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: `route must be an absolute path starting with \"/\" (got ${JSON.stringify(route)}): ${err instanceof Error ? err.message : String(err)}`,\n });\n continue;\n }\n if (canonical !== route) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: `route ${JSON.stringify(route)} is not canonical \u2014 use ${JSON.stringify(canonical)} (this must match what the backend's RouteCanonicalityCheck accepts)`,\n });\n }\n }\n })\n .describe('URL path(s) where this element exists'),\n})\n .strict()\n .describe('DOM element target. selector = CSS selector, route = URL path(s) where the element exists.');\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().describe('Notification title'),\n body: z.string().optional().describe('Notification body text'),\n icon: z.string().optional().describe('Notification icon (emoji or URL)'),\n})\n .describe('Optional toast notification shown when this action triggers.')\n .nullable()\n .optional();\n", "/**\n * adaptive-reviews \u2014 SyntroReviewsFlip (`<syntro-reviews-flip>`)\n *\n * The id-driven reviews TILE: a dumb, FAQ-style flip surface. It takes an\n * ordered list of review **ids** + an LLM-authored heading, hydrates the real\n * rows by id, and renders one line per review on the FRONT face; tapping a line\n * FLIPS the card to that review's full body on the BACK face. It carries NO\n * query and NO selection logic \u2014 `get_reviews(query)` (the semantic tool) does\n * all of that and hands the tile ids; the tile only ever moves ids and renders\n * real, server-hydrated bodies (the model never authors review text).\n *\n * The 3D flip is the shared canvas primitive (`@syntrologie/canvas-sdk/flip`),\n * namespaced `rev` (`data-rev-*` / `sc-rev-flip-*`) so velvet's generic\n * `[data-active-face]` rule and the FAQ's `data-faq-*` rules can't reach it.\n * Light DOM so the host page's `--sc-*` theme tokens apply.\n */\n\nimport { FlipController, flipStyles } from '@syntrologie/canvas-sdk/flip';\nimport { TELEMETRY_DOM_EVENT, type TelemetryDomDetail } from '@syntrologie/sdk-contracts';\nimport { html, LitElement, nothing } from 'lit';\nimport { styleMap } from 'lit/directives/style-map.js';\nimport {\n type DisplayReview,\n fetchReviewsByIds,\n type LoadReviewsByIds,\n type ReviewDisplayResponse,\n type SyntroAuthedFetch,\n} from './reviewDisplay.js';\n\nconst DEFAULT_INSTANCE_ID = 'reviews-flip';\n\n// Monotonic counter for generated scope uids (default-instanceId fallback), so\n// two default-id tiles don't cross-scope their injected <style>.\nlet _revUidCounter = 0;\n\nfunction starLabel(rating: number): string {\n return '\u2605'.repeat(Math.max(0, Math.min(5, Math.round(rating))));\n}\n\nfunction createdAtLabel(value?: string | null): string | null {\n if (!value) return null;\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return null;\n return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });\n}\n\n/** The one-line teaser for a front-face row: the title if present, else a\n * trimmed first line of the body. The full body only ever shows on the back. */\nfunction reviewOneLiner(review: DisplayReview): string {\n const title = review.title?.trim();\n if (title) return title;\n const body = (review.body ?? '').trim().replace(/\\s+/g, ' ');\n return body.length > 90 ? `${body.slice(0, 89)}\u2026` : body;\n}\n\n/** The registered widget id for this tile (re-exported by `runtime.ts`). */\nexport const REVIEWS_FLIP_WIDGET_ID = 'adaptive-reviews:reviews-flip';\n\nfunction defaultLoadReviewsByIds(ids: string[]): Promise<ReviewDisplayResponse> {\n const authedFetch = (window as unknown as { SynOS?: { authedFetch?: SyntroAuthedFetch } }).SynOS\n ?.authedFetch;\n if (!authedFetch) {\n return Promise.reject(new Error('Reviews are unavailable'));\n }\n return fetchReviewsByIds(authedFetch, ids);\n}\n\nexport class SyntroReviewsFlip extends LitElement {\n static override properties = {\n heading: { type: String },\n reviewIds: { attribute: false },\n max: { type: Number },\n instanceId: { type: String },\n loadReviewsByIds: { attribute: false },\n _isLoading: { state: true },\n _error: { state: true },\n _reviews: { state: true },\n };\n\n /** LLM-authored, grounded framing (\"What runners say about the ventilation\").\n * The tile renders it verbatim; it never fabricates one. */\n heading = 'Customer reviews';\n /** The ordered ids to hydrate + show. The ONLY thing the LLM moves in. */\n reviewIds: string[] = [];\n /** Optional cap on how many of `reviewIds` to render. */\n max?: number;\n instanceId: string = DEFAULT_INSTANCE_ID;\n /** Injectable loader (tests / non-SynOS hosts); defaults to the `?ids=` fetch. */\n loadReviewsByIds?: LoadReviewsByIds;\n\n private _isLoading = true;\n private _error: string | null = null;\n private _reviews: DisplayReview[] = [];\n private _requestId = 0;\n private _generatedUid: string | null = null;\n\n // The flip state machine (open id, active face, animating, settle timer, the\n // token-coupled GPU-teardown + reduced-motion contract) is the shared\n // controller; `sc-rev-flip` matches what _renderFlipStyles emits.\n private readonly _flip = new FlipController(this, { keyframePrefix: 'sc-rev-flip' });\n\n // a11y focus intent, consumed in updated(): move focus to the \u2039 Back button on\n // flip-to-back, and back to the originating row on flip-to-front.\n private _pendingFocus: 'front' | 'back' | null = null;\n private _focusReturnId: string | null = null;\n\n override createRenderRoot() {\n return this;\n }\n\n // (Re)hydrate when the id set changes. Doing this in willUpdate (not updated)\n // keeps the synchronous `_isLoading` flip inside the SAME update cycle \u2014 a\n // reactive write in updated() would schedule a wasteful follow-up update. The\n // async resolution's state write schedules its own (expected) update. Guarded\n // to `reviewIds`/`max` so the loader's own `_reviews`/`_isLoading` writes can't\n // re-trigger a load. The first update cycle includes any pre-connection props,\n // so this also covers initial mount without a separate connectedCallback load.\n override willUpdate(changed: Map<string, unknown>): void {\n if (changed.has('reviewIds') || changed.has('max')) {\n void this._load();\n }\n }\n\n override updated(): void {\n this._applyPendingFocus();\n }\n\n private get _scopeId(): string {\n if (this.instanceId && this.instanceId !== DEFAULT_INSTANCE_ID) return this.instanceId;\n if (this._generatedUid === null) {\n _revUidCounter += 1;\n this._generatedUid = `rev-${_revUidCounter}`;\n }\n return this._generatedUid;\n }\n\n /** The ids to actually render (capped by `max` when set). */\n private _effectiveIds(): string[] {\n const ids = (this.reviewIds ?? []).map((id) => id.trim()).filter((id) => id.length > 0);\n if (typeof this.max === 'number' && this.max >= 0) return ids.slice(0, this.max);\n return ids;\n }\n\n private async _load(): Promise<void> {\n const requestId = ++this._requestId;\n const ids = this._effectiveIds();\n if (ids.length === 0) {\n this._isLoading = false;\n this._error = null;\n this._reviews = [];\n return;\n }\n this._isLoading = true;\n this._error = null;\n const startedAt = performance.now();\n try {\n const loader = this.loadReviewsByIds ?? defaultLoadReviewsByIds;\n const data = await loader(ids);\n if (requestId !== this._requestId) return;\n // Preserve the requested order (the LLM's ranking); the endpoint should\n // already do this, but re-order defensively and drop unknown ids.\n const byId = new Map(data.reviews.map((r) => [r.id, r]));\n this._reviews = ids.map((id) => byId.get(id)).filter((r): r is DisplayReview => Boolean(r));\n this._reportLoad(startedAt, 'ok', ids.length, this._reviews.length);\n } catch {\n if (requestId !== this._requestId) return;\n this._reviews = [];\n this._error = 'Reviews are unavailable';\n this._reportLoad(startedAt, 'error', ids.length, 0);\n } finally {\n if (requestId === this._requestId) this._isLoading = false;\n }\n }\n\n /**\n * Report how long this load took. The reviews fetch is the network wait\n * between the concierge mounting this tile and the shopper seeing it\n * (BUG-1784767817 was a 30 s hang here), so it goes to telemetry on every\n * load rather than living only in the promotion playbook. Superseded loads\n * report nothing: their result is never shown.\n */\n private _reportLoad(\n startedAt: number,\n outcome: 'ok' | 'error',\n requested: number,\n received: number\n ): void {\n this.dispatchEvent(\n new CustomEvent<TelemetryDomDetail>(TELEMETRY_DOM_EVENT, {\n detail: {\n name: 'syntro_tile_data_received',\n props: {\n tile_id: this._scopeId,\n tile_kind: REVIEWS_FLIP_WIDGET_ID,\n total_ms: Math.round(performance.now() - startedAt),\n outcome,\n requested,\n received,\n },\n },\n bubbles: true,\n composed: true,\n })\n );\n }\n\n private _applyPendingFocus(): void {\n if (this._pendingFocus === null) return;\n const target = this._pendingFocus;\n this._pendingFocus = null;\n if (target === 'back') {\n this.querySelector<HTMLElement>('[data-rev-back]')?.focus();\n return;\n }\n const id = this._focusReturnId;\n this._focusReturnId = null;\n if (!id) return;\n this.querySelector<HTMLElement>(`[data-rev-item-id=\"${cssAttrEscape(id)}\"] button`)?.focus();\n }\n\n private _open(id: string): void {\n this._pendingFocus = 'back';\n this._focusReturnId = id;\n this._flip.openTo(id);\n }\n\n private _back(): void {\n this._pendingFocus = 'front';\n this._flip.back();\n }\n\n override render() {\n const rootStyles: Record<string, string> = {\n display: 'block',\n width: '100%',\n color: 'var(--sc-content-text-color, #111827)',\n fontFamily: 'var(--sc-font-family, inherit)',\n };\n\n return html`\n <section\n data-syntro-reviews-flip\n data-adaptive-id=${this._scopeId}\n data-adaptive-type=\"adaptive-reviews-flip\"\n style=${styleMap(rootStyles)}\n aria-label=${this.heading}\n >\n ${this._renderBody()}\n </section>\n `;\n }\n\n private _renderBody() {\n if (this._isLoading) return this._renderFrame(this._renderState('Loading\u2026'));\n if (this._error) return this._renderFrame(this._renderState(this._error));\n if (this._reviews.length === 0) return this._renderFrame(this._renderState('No reviews yet'));\n return this._renderFlip();\n }\n\n private _renderFrame(content: unknown) {\n return html`\n <div style=${styleMap(this._panelStyles())}>${content}</div>\n `;\n }\n\n /**\n * Loading / empty / error state. Previously a bare unpadded line that hugged\n * the tile's top-left corner (\"Loading\u2026\" with no padding, off-theme). Now the\n * message sits CENTERED with padding in an at-least-88px box (height:100% is\n * inert here \u2014 the host/panel chain isn't definite-height \u2014 so minHeight\n * governs; it's a padded, centered placeholder, not stray text). role=\"status\"\n * announces the loading/error line to assistive tech (was silent).\n */\n private _renderState(text: string) {\n return html`<div\n role=\"status\"\n style=${styleMap({\n boxSizing: 'border-box',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n height: '100%',\n minHeight: '88px',\n padding: '20px 16px',\n textAlign: 'center',\n })}\n ><span style=${styleMap(this._stateStyles())}>${text}</span></div>`;\n }\n\n private _renderFlip() {\n const scope = `[data-adaptive-id=\"${this._scopeId}\"]`;\n const openReview =\n this._flip.openId !== null\n ? this._reviews.find((r) => r.id === this._flip.openId)\n : undefined;\n const activeFace = openReview ? this._flip.activeFace : 'front';\n const backActive = activeFace === 'back';\n const animating = this._flip.animating;\n\n return html`\n <div style=${styleMap(this._panelStyles())}>\n <style>${flipStyles(scope, { ns: 'rev', keyframePrefix: 'sc-rev-flip' })}</style>\n <div data-rev-flip-viewport ?data-rev-flip-animating=${animating}>\n <div\n data-rev-flip\n data-rev-active-face=${activeFace}\n ?data-rev-flip-animating=${animating}\n data-rev-flip-dir=${animating ? this._flip.dir : nothing}\n @animationend=${this._flip.onAnimationEnd}\n >\n <div data-face=\"front\" ?inert=${backActive} aria-hidden=${backActive}>\n ${this._renderListFace()}\n </div>\n <div data-face=\"back\" ?inert=${!backActive} aria-hidden=${!backActive}>\n ${openReview ? this._renderDetail(openReview) : nothing}\n </div>\n </div>\n </div>\n </div>\n `;\n }\n\n private _renderListFace() {\n return html`\n <div\n style=${styleMap({\n display: 'grid',\n // Cap the column at the container width (min 0) so a nowrap one-liner\n // can't expand the grid past the tile \u2014 otherwise the row (and its \u203A\n // chevron) overflows the bounded deck card and the ellipsis never\n // engages.\n gridTemplateColumns: 'minmax(0, 1fr)',\n gap: 'var(--sc-content-item-gap, 6px)',\n })}\n >\n ${this._reviews.map((review) => this._renderRow(review))}\n </div>\n `;\n }\n\n private _renderRow(review: DisplayReview) {\n return html`\n <div style=${styleMap({ position: 'relative', minWidth: '0' })} data-rev-item-id=${review.id}>\n <button type=\"button\" style=${styleMap(this._rowStyles())} @click=${() => this._open(review.id)}>\n <span style=${styleMap(this._starsStyles())} aria-label=${`${review.rating} out of 5`}>\n ${starLabel(review.rating)}\n </span>\n <span style=${styleMap(this._oneLinerStyles())}>${reviewOneLiner(review)}</span>\n <span style=${styleMap(this._chevronStyles())}>\u203A</span>\n </button>\n </div>\n `;\n }\n\n private _renderDetail(review: DisplayReview) {\n const date = createdAtLabel(review.created_at);\n return html`\n <div style=${styleMap(this._detailCardStyles())} data-rev-detail=${review.id}>\n <button\n type=\"button\"\n data-rev-back\n style=${styleMap(this._backStyles())}\n aria-label=\"Back to reviews\"\n @click=${() => this._back()}\n >\n \u2039 Back to reviews\n </button>\n <div style=${styleMap(this._detailHeaderStyles())}>\n <div style=${styleMap({ fontWeight: '700', color: 'var(--sc-content-text-color, inherit)' })}>\n ${review.title || 'Review'}\n </div>\n <div style=${styleMap(this._starsStyles())} aria-label=${`${review.rating} out of 5`}>\n ${starLabel(review.rating)}\n </div>\n </div>\n <p style=${styleMap(this._bodyStyles())}>${review.body}</p>\n <div style=${styleMap(this._metaStyles())}>\n ${review.reviewer_display_name || 'Customer'}\n ${review.verified_purchase ? html`<span> \u00B7 Verified purchase</span>` : nothing}\n ${date ? html`<span> \u00B7 ${date}</span>` : nothing}\n </div>\n </div>\n `;\n }\n\n // Transparent container \u2014 the tile lives inside the canvas deck card, which\n // provides the surface chrome (bg/border/shadow). Like the FAQ, we don't draw\n // a second card; the rows are self-styled item cards (see _rowStyles).\n private _panelStyles(): Record<string, string> {\n return { boxSizing: 'border-box' };\n }\n\n // A FAQ-style item card (same --sc-content-* language as the FAQ questions) so\n // the two tiles read as one family in the deck.\n private _rowStyles(): Record<string, string> {\n return {\n display: 'flex',\n alignItems: 'center',\n gap: '8px',\n width: '100%',\n boxSizing: 'border-box',\n padding: 'var(--sc-content-item-padding, 10px 14px)',\n borderRadius: 'var(--sc-content-item-border-radius, 10px)',\n border: 'var(--sc-content-border, 1px solid rgba(0, 0, 0, 0.08))',\n background: 'var(--sc-content-background, rgba(255, 255, 255, 0.5))',\n textAlign: 'left',\n cursor: 'pointer',\n color: 'var(--sc-content-text-color, inherit)',\n font: 'inherit',\n };\n }\n\n private _oneLinerStyles(): Record<string, string> {\n return {\n flex: '1 1 auto',\n minWidth: '0',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n fontSize: '14px',\n lineHeight: '20px',\n };\n }\n\n private _starsStyles(): Record<string, string> {\n return {\n // Brand accent, the SAME token the FAQ uses for its primary marks, so\n // the rating follows the workspace brand (not a hardcoded blue). On a\n // host card the accent is ink on the CHAT APP's ground, where it is only\n // a contrast risk, so the host's own ink wins when it sent one.\n color: 'var(--sc-host-link-color, var(--sc-color-primary, #723b32))',\n fontSize: '13px',\n lineHeight: '18px',\n whiteSpace: 'nowrap',\n };\n }\n\n private _chevronStyles(): Record<string, string> {\n return { color: 'var(--sc-content-chevron-color, currentColor)', flex: '0 0 auto' };\n }\n\n // The flipped-open review is a padded card in the same --sc-content-* language\n // as the front-face row cards \u2014 otherwise the review title + body sit cramped\n // against the tile edge with no padding.\n private _detailCardStyles(): Record<string, string> {\n return {\n display: 'grid',\n gap: '8px',\n boxSizing: 'border-box',\n padding: 'var(--sc-content-item-padding, 10px 14px)',\n borderRadius: 'var(--sc-content-item-border-radius, 10px)',\n border: 'var(--sc-content-border, 1px solid rgba(0, 0, 0, 0.08))',\n background: 'var(--sc-content-background, rgba(255, 255, 255, 0.5))',\n };\n }\n\n private _backStyles(): Record<string, string> {\n return {\n justifySelf: 'start',\n padding: '4px 0',\n background: 'transparent',\n border: 'none',\n cursor: 'pointer',\n color: 'var(--sc-content-text-secondary-color, #64748b)',\n font: 'inherit',\n fontSize: '13px',\n fontWeight: '600',\n };\n }\n\n private _detailHeaderStyles(): Record<string, string> {\n return {\n display: 'flex',\n alignItems: 'baseline',\n justifyContent: 'space-between',\n gap: '10px',\n };\n }\n\n private _bodyStyles(): Record<string, string> {\n return {\n margin: '0',\n fontSize: 'var(--sc-content-body-font-size, 14px)',\n lineHeight: '20px',\n color: 'var(--sc-content-text-secondary-color, inherit)',\n };\n }\n\n private _metaStyles(): Record<string, string> {\n return {\n color: 'var(--sc-content-text-secondary-color, #64748b)',\n fontSize: '12px',\n lineHeight: '18px',\n };\n }\n\n private _stateStyles(): Record<string, string> {\n return {\n margin: '0',\n color: 'var(--sc-content-text-secondary-color, #64748b)',\n fontSize: '14px',\n lineHeight: '20px',\n };\n }\n}\n\n/** Escape a value for safe use inside a double-quoted attribute selector. */\nfunction cssAttrEscape(value: string): string {\n return value.replace(/[\"\\\\]/g, '\\\\$&');\n}\n\nif (!customElements.get('syntro-reviews-flip')) {\n customElements.define('syntro-reviews-flip', SyntroReviewsFlip);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'syntro-reviews-flip': SyntroReviewsFlip;\n }\n}\n", "/**\n * adaptive-reviews \u2014 Runtime manifest (flat shape)\n *\n * Exports the runtime descriptor consumed by the SDK's AppLoader. Importing\n * this module registers the <syntro-reviews> and <syntro-reviews-flip> custom\n * elements as a side effect. This is a lazy CDN adaptive (see\n * `syntrologie.bundlingMode: cdn-only`), so it must NOT import runtime-sdk\n * internals \u2014 the flat shape here is wrapped into a manifest by `cdn.ts` before\n * self-registering.\n */\n\nimport './reviewDisplay.js';\nimport type { ReviewDisplayResponse, SyntroReviews } from './reviewDisplay.js';\nimport './reviewsFlip.js';\nimport type { LoadReviewsByIds } from './reviewDisplay.js';\nimport type { SyntroReviewsFlip } from './reviewsFlip.js';\n\nexport const REVIEWS_ADAPTIVE_APP_ID = 'adaptive-reviews';\nexport const REVIEWS_ADAPTIVE_WIDGET_ID = 'adaptive-reviews:reviews';\n\n/** The id-driven FAQ-style flip tile \u2014 the concierge mounts this with a list of\n * review ids + a grounded heading (see `get_reviews` + `mount_adaptive_reviews`). */\nimport { REVIEWS_FLIP_WIDGET_ID } from './reviewsFlip.js';\n\nexport { REVIEWS_FLIP_WIDGET_ID };\n\ntype ReviewLoader = (limit: number, productId?: string | null) => Promise<ReviewDisplayResponse>;\n\nfunction asPositiveLimit(value: unknown): number | undefined {\n const numeric =\n typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : NaN;\n if (!Number.isFinite(numeric)) return undefined;\n return Math.max(1, Math.floor(numeric));\n}\n\nfunction applyReviewsConfig(el: SyntroReviews, config?: Record<string, unknown> | null): void {\n if (!config) return;\n\n if (typeof config.heading === 'string') {\n el.heading = config.heading;\n }\n\n const limit = asPositiveLimit(config.limit);\n if (limit !== undefined) {\n el.limit = limit;\n }\n\n if (typeof config.productId === 'string') {\n el.productId = config.productId;\n }\n\n if (typeof config.productIdSelector === 'string') {\n el.productIdSelector = config.productIdSelector;\n }\n\n if (typeof config.loadReviews === 'function') {\n el.loadReviews = config.loadReviews as ReviewLoader;\n }\n}\n\n/**\n * Mountable widget: receives the tile container + validated props, returns an\n * unmount fn. Same contract as the other adaptives' mountables.\n */\nexport const reviewsWidget = {\n mount(container: HTMLElement, config?: Record<string, unknown> | null) {\n const el = document.createElement('syntro-reviews') as SyntroReviews;\n applyReviewsConfig(el, config);\n container.appendChild(el);\n return () => el.remove();\n },\n\n update(container: HTMLElement, config?: Record<string, unknown> | null) {\n const el = container.querySelector('syntro-reviews') as SyntroReviews | null;\n if (el) applyReviewsConfig(el, config);\n },\n};\n\n/** Normalize `review_ids` to a string[] \u2014 accepts an array or a comma string\n * (SDK config / tool payloads have arrived both ways). */\nfunction asIdList(value: unknown): string[] | undefined {\n if (Array.isArray(value)) {\n return value.map((v) => String(v).trim()).filter((v) => v.length > 0);\n }\n if (typeof value === 'string') {\n return value\n .split(',')\n .map((v) => v.trim())\n .filter((v) => v.length > 0);\n }\n return undefined;\n}\n\nfunction applyReviewsFlipConfig(\n el: SyntroReviewsFlip,\n config?: Record<string, unknown> | null\n): void {\n if (!config) return;\n // The mount authors `title` (lifted to the SyntroTileCard header like every\n // other tile). Accept legacy `heading` too. This only feeds the section\n // aria-label now \u2014 the visible title is the card chrome's job, not ours.\n if (typeof config.title === 'string') el.heading = config.title;\n else if (typeof config.heading === 'string') el.heading = config.heading;\n // Accept both `review_ids` (mount payload / schema) and `reviewIds` (JS prop).\n const ids = asIdList(config.review_ids) ?? asIdList(config.reviewIds);\n if (ids !== undefined) el.reviewIds = ids;\n const max = asPositiveLimit(config.max);\n if (max !== undefined) el.max = max;\n if (typeof config.instanceId === 'string') el.instanceId = config.instanceId;\n if (typeof config.loadReviewsByIds === 'function') {\n el.loadReviewsByIds = config.loadReviewsByIds as LoadReviewsByIds;\n }\n}\n\n/**\n * Mountable for the id-driven flip tile. `review_ids` in \u2192 hydrate by id \u2192\n * render the FAQ-style flip. No query/selection here; the tile is dumb.\n */\nexport const reviewsFlipWidget = {\n mount(container: HTMLElement, config?: Record<string, unknown> | null) {\n const el = document.createElement('syntro-reviews-flip') as SyntroReviewsFlip;\n applyReviewsFlipConfig(el, config);\n container.appendChild(el);\n return () => el.remove();\n },\n\n update(container: HTMLElement, config?: Record<string, unknown> | null) {\n const el = container.querySelector('syntro-reviews-flip') as SyntroReviewsFlip | null;\n if (el) applyReviewsFlipConfig(el, config);\n },\n};\n\nexport const runtime = {\n id: REVIEWS_ADAPTIVE_APP_ID,\n version: '0.1.0',\n name: 'Reviews',\n description: 'Display imported and submitted customer reviews through the runtime SDK.',\n\n /** No DOM-mutation executors \u2014 this widget renders only. */\n executors: [],\n\n widgets: [\n {\n id: REVIEWS_ADAPTIVE_WIDGET_ID,\n component: reviewsWidget,\n metadata: {\n name: 'Reviews',\n description: 'Display customer reviews from the adaptive review endpoint.',\n icon: '\\u{1F4AC}',\n },\n },\n {\n id: REVIEWS_FLIP_WIDGET_ID,\n component: reviewsFlipWidget,\n metadata: {\n name: 'Reviews (flip)',\n description:\n 'Id-driven reviews tile: supply review_ids from get_reviews under a grounded heading; it hydrates and renders the real review bodies by id.',\n icon: '\\u{1F4AC}',\n subtitle: 'From verified buyers.',\n },\n },\n ],\n};\n\n// Back-compat alias for existing importers/tests.\nexport const reviewsAdaptiveRuntime = runtime;\n\nexport default runtime;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["e", "createContext", "key", "e", "i", "c", "s", "html", "LitElement", "nothing", "styleMap", "createdAtLabel", "LitElement", "html", "styleMap", "nothing"]
|
|
7
|
+
}
|
package/dist/reviewsFlip.d.ts
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { LitElement } from 'lit';
|
|
18
18
|
import { type LoadReviewsByIds } from './reviewDisplay.js';
|
|
19
|
+
/** The registered widget id for this tile (re-exported by `runtime.ts`). */
|
|
20
|
+
export declare const REVIEWS_FLIP_WIDGET_ID = "adaptive-reviews:reviews-flip";
|
|
19
21
|
export declare class SyntroReviewsFlip extends LitElement {
|
|
20
22
|
static properties: {
|
|
21
23
|
heading: {
|
|
@@ -68,6 +70,14 @@ export declare class SyntroReviewsFlip extends LitElement {
|
|
|
68
70
|
/** The ids to actually render (capped by `max` when set). */
|
|
69
71
|
private _effectiveIds;
|
|
70
72
|
private _load;
|
|
73
|
+
/**
|
|
74
|
+
* Report how long this load took. The reviews fetch is the network wait
|
|
75
|
+
* between the concierge mounting this tile and the shopper seeing it
|
|
76
|
+
* (BUG-1784767817 was a 30 s hang here), so it goes to telemetry on every
|
|
77
|
+
* load rather than living only in the promotion playbook. Superseded loads
|
|
78
|
+
* report nothing: their result is never shown.
|
|
79
|
+
*/
|
|
80
|
+
private _reportLoad;
|
|
71
81
|
private _applyPendingFocus;
|
|
72
82
|
private _open;
|
|
73
83
|
private _back;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviewsFlip.d.ts","sourceRoot":"","sources":["../src/reviewsFlip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"reviewsFlip.d.ts","sourceRoot":"","sources":["../src/reviewsFlip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,EAAQ,UAAU,EAAW,MAAM,KAAK,CAAC;AAEhD,OAAO,EAGL,KAAK,gBAAgB,EAGtB,MAAM,oBAAoB,CAAC;AA4B5B,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,kCAAkC,CAAC;AAWtE,qBAAa,iBAAkB,SAAQ,UAAU;IAC/C,OAAgB,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;MASxB;IAEF;iEAC6D;IAC7D,OAAO,SAAsB;IAC7B,0EAA0E;IAC1E,SAAS,EAAE,MAAM,EAAE,CAAM;IACzB,yDAAyD;IACzD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAuB;IACzC,kFAAkF;IAClF,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAEpC,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,aAAa,CAAuB;IAK5C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA+D;IAIrF,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,cAAc,CAAuB;IAEpC,gBAAgB;IAWhB,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAM/C,OAAO,IAAI,IAAI;IAIxB,OAAO,KAAK,QAAQ,GAOnB;IAED,6DAA6D;IAC7D,OAAO,CAAC,aAAa;YAMP,KAAK;IA+BnB;;;;;;OAMG;IACH,OAAO,CAAC,WAAW;IAyBnB,OAAO,CAAC,kBAAkB;IAc1B,OAAO,CAAC,KAAK;IAMb,OAAO,CAAC,KAAK;IAKJ,MAAM;IAqBf,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,YAAY;IAMpB;;;;;;;OAOG;IACH,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,WAAW;IAiCnB,OAAO,CAAC,eAAe;IAkBvB,OAAO,CAAC,UAAU;IAclB,OAAO,CAAC,aAAa;IAkCrB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,UAAU;IAkBlB,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,mBAAmB;IAS3B,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,YAAY;CAQrB;AAWD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,qBAAqB,EAAE,iBAAiB,CAAC;KAC1C;CACF"}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -14,7 +14,8 @@ export declare const REVIEWS_ADAPTIVE_APP_ID = "adaptive-reviews";
|
|
|
14
14
|
export declare const REVIEWS_ADAPTIVE_WIDGET_ID = "adaptive-reviews:reviews";
|
|
15
15
|
/** The id-driven FAQ-style flip tile — the concierge mounts this with a list of
|
|
16
16
|
* review ids + a grounded heading (see `get_reviews` + `mount_adaptive_reviews`). */
|
|
17
|
-
|
|
17
|
+
import { REVIEWS_FLIP_WIDGET_ID } from './reviewsFlip.js';
|
|
18
|
+
export { REVIEWS_FLIP_WIDGET_ID };
|
|
18
19
|
/**
|
|
19
20
|
* Mountable widget: receives the tile container + validated props, returns an
|
|
20
21
|
* unmount fn. Same contract as the other adaptives' mountables.
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,oBAAoB,CAAC;AAE5B,OAAO,kBAAkB,CAAC;AAI1B,eAAO,MAAM,uBAAuB,qBAAqB,CAAC;AAC1D,eAAO,MAAM,0BAA0B,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,oBAAoB,CAAC;AAE5B,OAAO,kBAAkB,CAAC;AAI1B,eAAO,MAAM,uBAAuB,qBAAqB,CAAC;AAC1D,eAAO,MAAM,0BAA0B,6BAA6B,CAAC;AAErE;sFACsF;AACtF,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,EAAE,sBAAsB,EAAE,CAAC;AAoClC;;;GAGG;AACH,eAAO,MAAM,aAAa;qBACP,WAAW,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;sBAOnD,WAAW,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;CAIvE,CAAC;AAsCF;;;GAGG;AACH,eAAO,MAAM,iBAAiB;qBACX,WAAW,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;sBAOnD,WAAW,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;CAIvE,CAAC;AAEF,eAAO,MAAM,OAAO;;;;;IAMlB,4DAA4D;;;;;6BAzE3C,WAAW,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;8BAOnD,WAAW,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;;;;;;;;;;;6BA+CrD,WAAW,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;8BAOnD,WAAW,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;;;;;;;;;CAqCvE,CAAC;AAGF,eAAO,MAAM,sBAAsB;;;;;IA5BjC,4DAA4D;;;;;6BAzE3C,WAAW,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;8BAOnD,WAAW,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;;;;;;;;;;;6BA+CrD,WAAW,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;8BAOnD,WAAW,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;;;;;;;;;CAwC3B,CAAC;AAE9C,eAAe,OAAO,CAAC"}
|
package/dist/runtime.js
CHANGED
package/dist/schema.js
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
external_exports
|
|
3
|
+
} from "./chunk-IGIU6S7P.js";
|
|
4
|
+
|
|
1
5
|
// src/schema.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
productIdSelector: z.string().optional().describe(
|
|
6
|
+
var reviewsPropsSchema = external_exports.object({
|
|
7
|
+
heading: external_exports.string().optional().describe("Section title shown above the review cards."),
|
|
8
|
+
limit: external_exports.number().int().min(1).max(50).optional().describe("Maximum number of reviews to display."),
|
|
9
|
+
productId: external_exports.string().optional().describe("Explicit product id to scope reviews to. Overridden by productIdSelector."),
|
|
10
|
+
productIdSelector: external_exports.string().optional().describe(
|
|
8
11
|
"CSS selector for a host-page element carrying data-syntro-product-id. When it resolves, reviews are scoped to that product \u2014 lets one config drive per-product reviews across many product pages. Omit for workspace-wide reviews."
|
|
9
12
|
)
|
|
10
13
|
}).strict();
|
|
11
|
-
var reviewsFlipPropsSchema =
|
|
12
|
-
title:
|
|
14
|
+
var reviewsFlipPropsSchema = external_exports.object({
|
|
15
|
+
title: external_exports.string().describe(
|
|
13
16
|
'REQUIRED. The tile title, shown in the card header. Keep it SHORT (max ~6 words / 48 chars) so it never truncates. A grounded framing of these reviews like a product-card headline, not the bare word "Reviews" (e.g. "Squat-proof, per real buyers"). Never a fabricated pull-quote or a verbatim lift of a review body.'
|
|
14
17
|
),
|
|
15
|
-
review_ids:
|
|
18
|
+
review_ids: external_exports.array(external_exports.string().min(1)).min(1).describe(
|
|
16
19
|
"The ordered ids of the real reviews to show, from get_reviews. The tile hydrates and renders exactly these \u2014 you never write review text. Order is your ranking (best first)."
|
|
17
20
|
),
|
|
18
|
-
max:
|
|
21
|
+
max: external_exports.number().int().min(1).max(20).optional().describe("Optional cap on how many of review_ids to render.")
|
|
19
22
|
}).strict();
|
|
20
23
|
var tileWidgets = [
|
|
21
24
|
{
|
package/dist/schema.js.map
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/schema.ts"],
|
|
4
4
|
"sourcesContent": ["/**\n * adaptive-reviews \u2014 Zod schema + capabilities (source of truth for tile\n * config validation and LLM authoring guidance).\n *\n * `generate-unified-schema.mjs::discoverAdaptives()` reads `tileWidgets` and\n * `CAPABILITIES_DOCUMENTATION` from this package's `dist/schema.js` and folds\n * them into the unified canvas-config schema \u2014 the same path every other\n * adaptive uses. (These used to live hand-coded in the generator's\n * BUILTIN_* blocks while reviews was a runtime-sdk built-in.)\n */\n\nimport { z } from 'zod';\n\nexport const reviewsPropsSchema = z\n .object({\n heading: z.string().optional().describe('Section title shown above the review cards.'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(50)\n .optional()\n .describe('Maximum number of reviews to display.'),\n productId: z\n .string()\n .optional()\n .describe('Explicit product id to scope reviews to. Overridden by productIdSelector.'),\n productIdSelector: z\n .string()\n .optional()\n .describe(\n 'CSS selector for a host-page element carrying data-syntro-product-id. When it resolves, reviews are scoped to that product \u2014 lets one config drive per-product reviews across many product pages. Omit for workspace-wide reviews.'\n ),\n })\n .strict();\n\nexport const reviewsFlipPropsSchema = z\n .object({\n title: z\n .string()\n .describe(\n 'REQUIRED. The tile title, shown in the card header. Keep it SHORT (max ~6 words / 48 chars) so it never truncates. A grounded framing of these reviews like a product-card headline, not the bare word \"Reviews\" (e.g. \"Squat-proof, per real buyers\"). Never a fabricated pull-quote or a verbatim lift of a review body.'\n ),\n review_ids: z\n .array(z.string().min(1))\n .min(1)\n .describe(\n 'The ordered ids of the real reviews to show, from get_reviews. The tile hydrates and renders exactly these \u2014 you never write review text. Order is your ranking (best first).'\n ),\n max: z\n .number()\n .int()\n .min(1)\n .max(20)\n .optional()\n .describe('Optional cap on how many of review_ids to render.'),\n })\n .strict();\n\nexport const tileWidgets = [\n {\n widget: 'adaptive-reviews:reviews',\n defName: 'reviewsAdaptiveProps',\n propsSchema: reviewsPropsSchema,\n },\n {\n widget: 'adaptive-reviews:reviews-flip',\n defName: 'reviewsFlipAdaptiveProps',\n propsSchema: reviewsFlipPropsSchema,\n },\n];\n\n// FLAT shape (like every other adaptive's schema.ts) \u2014 discoverAdaptives keys\n// this under the package dir name itself (`docs[dir] = capsDocs`), so exporting\n// it keyed by 'adaptive-reviews' here would double-nest it and blank the\n// planner's reviews guidance.\nexport const CAPABILITIES_DOCUMENTATION = {\n packageId: 'adaptive-reviews',\n plannerSummary:\n 'Customer review display for imported and submitted reviews. Reach for this when the strategy needs social proof or review content from the reviews system.',\n description: 'Displays imported and submitted customer reviews from the reviews system.',\n whenToUse: [\n {\n goal: 'Show review content on a customer site',\n action:\n 'Add a tile with widget: \"adaptive-reviews:reviews\" and configure heading and limit props.',\n },\n {\n goal: 'Surface social proof from imported review rows',\n action:\n 'Use the existing reviews import flow, then include the reviews adaptive tile in the published SDK config.',\n },\n ],\n conventions: [\n {\n name: 'Review data source',\n description:\n 'Review rows are managed by the reviews system and read from GET /api/adaptive/reviews; do not place review row data in SDK config.',\n },\n {\n name: 'Display props',\n description:\n 'Use heading for the section title and limit for the maximum number of reviews to display.',\n },\n {\n name: 'Per-product scoping',\n description:\n 'Set productIdSelector to a host-page selector whose element carries data-syntro-product-id to scope reviews to the current product; omit for workspace-wide reviews.',\n },\n ],\n events: [],\n};\n"],
|
|
5
|
-
"mappings": "
|
|
5
|
+
"mappings": ";;;;;AAaO,IAAM,qBAAqB,iBAC/B,OAAO;AAAA,EACN,SAAS,iBAAE,OAAO,EAAE,SAAS,EAAE,SAAS,6CAA6C;AAAA,EACrF,OAAO,iBACJ,OAAO,EACP,IAAI,EACJ,IAAI,CAAC,EACL,IAAI,EAAE,EACN,SAAS,EACT,SAAS,uCAAuC;AAAA,EACnD,WAAW,iBACR,OAAO,EACP,SAAS,EACT,SAAS,2EAA2E;AAAA,EACvF,mBAAmB,iBAChB,OAAO,EACP,SAAS,EACT;AAAA,IACC;AAAA,EACF;AACJ,CAAC,EACA,OAAO;AAEH,IAAM,yBAAyB,iBACnC,OAAO;AAAA,EACN,OAAO,iBACJ,OAAO,EACP;AAAA,IACC;AAAA,EACF;AAAA,EACF,YAAY,iBACT,MAAM,iBAAE,OAAO,EAAE,IAAI,CAAC,CAAC,EACvB,IAAI,CAAC,EACL;AAAA,IACC;AAAA,EACF;AAAA,EACF,KAAK,iBACF,OAAO,EACP,IAAI,EACJ,IAAI,CAAC,EACL,IAAI,EAAE,EACN,SAAS,EACT,SAAS,mDAAmD;AACjE,CAAC,EACA,OAAO;AAEH,IAAM,cAAc;AAAA,EACzB;AAAA,IACE,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AACF;AAMO,IAAM,6BAA6B;AAAA,EACxC,WAAW;AAAA,EACX,gBACE;AAAA,EACF,aAAa;AAAA,EACb,WAAW;AAAA,IACT;AAAA,MACE,MAAM;AAAA,MACN,QACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,QACE;AAAA,IACJ;AAAA,EACF;AAAA,EACA,aAAa;AAAA,IACX;AAAA,MACE,MAAM;AAAA,MACN,aACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,aACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,aACE;AAAA,IACJ;AAAA,EACF;AAAA,EACA,QAAQ,CAAC;AACX;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syntrologie/adapt-reviews",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.56.0",
|
|
4
4
|
"description": "Adaptive Reviews — display tile for imported and submitted customer reviews, scoped per product.",
|
|
5
5
|
"license": "Proprietary",
|
|
6
6
|
"private": false,
|
|
@@ -43,8 +43,7 @@
|
|
|
43
43
|
"lint": "biome check ."
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"lit": "^3.0.0"
|
|
47
|
-
"zod": "^3.0.0"
|
|
46
|
+
"lit": "^3.0.0"
|
|
48
47
|
},
|
|
49
48
|
"devDependencies": {
|
|
50
49
|
"@open-wc/testing-helpers": "3.0.1",
|