@goflowstate/widget-sdk 0.3.2 → 0.3.5
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/README.md +12 -2
- package/dist/canvas-widget-sdk.js +274 -39
- package/dist/canvas-widget-sdk.js.map +2 -2
- package/dist/feedback.d.ts +13 -4
- package/dist/feedback.d.ts.map +1 -1
- package/dist/index.mjs +274 -39
- package/dist/index.mjs.map +2 -2
- package/package.json +1 -1
package/dist/index.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/service.ts", "../src/feedback.ts", "../src/session.ts", "../src/sdk.ts", "../src/theme.ts", "../src/ingest.ts"],
|
|
4
|
-
"sourcesContent": ["// packages/widget-sdk/src/service.ts\n\nimport type { WidgetServiceClient } from './types';\n\nexport interface CreateServiceClientOptions {\n baseUrl: string;\n getJwt: (forceRefresh?: boolean) => Promise<string>;\n fetchImpl?: typeof fetch;\n}\n\nexport function createServiceClient(opts: CreateServiceClientOptions): WidgetServiceClient {\n const fetchImpl = opts.fetchImpl ?? globalThis.fetch.bind(globalThis);\n const baseUrl = opts.baseUrl.replace(/\\/+$/, '');\n\n async function call(path: string, init: RequestInit, token: string): Promise<Response> {\n const url = `${baseUrl}${path.startsWith('/') ? path : `/${path}`}`;\n const headers = new Headers(init.headers);\n headers.set('authorization', `Bearer ${token}`);\n if (init.body && !headers.has('content-type')) {\n headers.set('content-type', 'application/json');\n }\n return fetchImpl(url, { ...init, headers });\n }\n\n return {\n async fetch<T = unknown>(path: string, init: RequestInit = {}): Promise<T> {\n let token = await opts.getJwt(false);\n let res = await call(path, init, token);\n\n if (res.status === 401) {\n token = await opts.getJwt(true);\n res = await call(path, init, token);\n }\n\n if (!res.ok) {\n let body: { message?: string } = {};\n try {\n body = (await res.json()) as { message?: string };\n } catch {\n // non-JSON body \u2014 ignore\n }\n throw new Error(`Widget service request failed (${res.status}): ${body.message ?? ''}`);\n }\n return (await res.json()) as T;\n },\n };\n}\n", "/**\n * Flow feedback: the bug icon every flow carries (flowstate-docs plan 2026-11-09-flow-feedback).\n *\n * The session client mounts this when the session state says `feedbackEnabled`. A round button\n * sits in the corner of the flow's own page. A tap takes a screenshot of the page as the person\n * sees it, then opens a sheet: the thumbnail, one field, one button. The note goes out through\n * the flow's own session (`session.request('feedback', \u2026)`).\n *\n * It lives in the flow's document, in a shadow root, so it takes the flow's look instead of\n * bringing one: the flow's fonts by inheritance, and its ground, ink, main button and corner\n * radius read from the page (`readFlowTheme`). A flow pins any role with a `--fs-feedback-*`\n * custom property on `:root`, and opts a page out with `<html data-fs-feedback=\"off\">`.\n */\n\nexport interface FlowFeedbackTheme {\n ground: string;\n surface: string;\n ink: string;\n inkDim: string;\n line: string;\n fill: string;\n cta: string;\n ctaInk: string;\n fontBody: string;\n fontDisplay: string;\n displayWeight: string;\n radius: string;\n buttonRadius: string;\n dark: boolean;\n}\n\nexport interface FlowFeedbackReport {\n message: string;\n screenshot?: string;\n pageKey?: string;\n pagePath: string;\n viewport: { width: number; height: number };\n}\n\nexport interface FlowFeedbackOptions {\n /** Sends the report. The session client passes `session.request('feedback', report)`. */\n submit: (report: FlowFeedbackReport) => Promise<unknown>;\n /** Renders the page to a JPEG data URL, or null when it cannot. Defaults to the built-in\n * rasterizer; tests and unusual hosts pass their own. */\n capture?: () => Promise<string | null>;\n /** Called once the icon is on the page, so a host page can hide its own fallback. */\n onReady?: () => void;\n}\n\nexport interface FlowFeedbackHandle {\n /** The element the shadow root hangs from. */\n readonly host: HTMLElement;\n /** Re-read the flow's look (the page under the icon may have changed). */\n refresh(): void;\n open(): Promise<void>;\n close(): void;\n unmount(): void;\n}\n\nexport const FEEDBACK_HOST_ATTR = 'data-fs-feedback-host';\n/** The validator on the service accepts 160 KB of JPEG; stay under it with room for headers. */\nexport const SCREENSHOT_MAX_BYTES = 150_000;\nexport const SCREENSHOT_MAX_SIDE = 1280;\nconst CAPTURE_TIMEOUT_MS = 4000;\n\n// \u2500\u2500 colour \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype Rgba = [number, number, number, number];\n\nexport function parseColor(raw: string | null | undefined): Rgba | null {\n const value = (raw ?? '').trim().toLowerCase();\n if (!value || value === 'transparent') return null;\n const hex = /^#([0-9a-f]{3,8})$/.exec(value);\n if (hex) {\n const h = hex[1];\n const full =\n h.length <= 4\n ? h\n .split('')\n .map((c) => c + c)\n .join('')\n : h;\n if (full.length !== 6 && full.length !== 8) return null;\n const n = (i: number) => parseInt(full.slice(i, i + 2), 16);\n return [n(0), n(2), n(4), full.length === 8 ? n(6) / 255 : 1];\n }\n const fn = /^rgba?\\(([^)]+)\\)$/.exec(value);\n if (!fn) return null;\n const parts = fn[1].split(/[\\s,/]+/).filter(Boolean);\n if (parts.length < 3) return null;\n const channel = (p: string) => (p.endsWith('%') ? (parseFloat(p) * 255) / 100 : parseFloat(p));\n const alpha =\n parts[3] === undefined\n ? 1\n : parts[3].endsWith('%')\n ? parseFloat(parts[3]) / 100\n : parseFloat(parts[3]);\n const out: Rgba = [channel(parts[0]), channel(parts[1]), channel(parts[2]), alpha];\n return out.some((v) => Number.isNaN(v)) ? null : out;\n}\n\nconst opaque = (c: Rgba | null): c is Rgba => c !== null && c[3] >= 0.9;\nconst css = (c: Rgba, alpha = c[3]): string =>\n alpha >= 1\n ? `rgb(${Math.round(c[0])}, ${Math.round(c[1])}, ${Math.round(c[2])})`\n : `rgba(${Math.round(c[0])}, ${Math.round(c[1])}, ${Math.round(c[2])}, ${alpha})`;\nconst mix = (a: Rgba, b: Rgba, t: number): Rgba => [\n a[0] + (b[0] - a[0]) * t,\n a[1] + (b[1] - a[1]) * t,\n a[2] + (b[2] - a[2]) * t,\n 1,\n];\n/** WCAG relative luminance. */\nexport const luminance = (c: Rgba): number => {\n const lin = (v: number) => {\n const s = v / 255;\n return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n };\n return 0.2126 * lin(c[0]) + 0.7152 * lin(c[1]) + 0.0722 * lin(c[2]);\n};\nconst contrast = (a: Rgba, b: Rgba): number => {\n const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);\n return (hi + 0.05) / (lo + 0.05);\n};\nconst WHITE: Rgba = [255, 255, 255, 1];\nconst BLACK: Rgba = [17, 17, 20, 1];\n\n// \u2500\u2500 reading the flow's look \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst styleOf = (el: Element): CSSStyleDeclaration => getComputedStyle(el);\nconst rootVar = (doc: Document, names: string[]): string => {\n for (const el of [doc.documentElement, doc.body]) {\n if (!el) continue;\n const style = styleOf(el);\n for (const name of names) {\n const value = style.getPropertyValue(name).trim();\n if (value) return value;\n }\n }\n return '';\n};\nconst visible = (el: Element): boolean => {\n const rect = el.getBoundingClientRect();\n const style = styleOf(el);\n return style.display !== 'none' && style.visibility !== 'hidden' && rect.width * rect.height >= 0;\n};\nconst inOurs = (el: Element): boolean => el.closest(`[${FEEDBACK_HOST_ATTR}]`) !== null;\n\n/** The flow's main button: the largest visible button whose fill is solid and is not the ground. */\nconst primaryButton = (doc: Document, ground: Rgba): Element | null => {\n let best: Element | null = null;\n let bestArea = -1;\n doc\n .querySelectorAll(\n 'button, [role=\"button\"], input[type=\"submit\"], a[class*=\"btn\"], a[class*=\"cta\"]'\n )\n .forEach((el) => {\n if (inOurs(el) || !visible(el)) return;\n const fill = parseColor(styleOf(el).backgroundColor);\n if (!opaque(fill) || contrast(fill, ground) < 1.25) return;\n const rect = el.getBoundingClientRect();\n const area = rect.width * rect.height;\n if (area > bestArea) {\n best = el;\n bestArea = area;\n }\n });\n return best;\n};\n\n/**\n * Read the look of the page the icon sits on. Order per role: the flow's own pin\n * (`--fs-feedback-*`), what the page actually renders (computed styles), the flow's design\n * tokens (`--sb-*`, then the Flowstate widget tokens), a neutral default.\n */\nexport function readFlowTheme(doc: Document = document): FlowFeedbackTheme {\n const body = doc.body;\n const stage = body.querySelector(':scope > :not(script):not(style):not([data-fs-feedback-host])');\n const bodyStyle = styleOf(body);\n\n const sampledGround = [stage, body, doc.documentElement]\n .map((el) => (el ? parseColor(styleOf(el).backgroundColor) : null))\n .find(opaque);\n const sampledInk =\n parseColor((stage ? styleOf(stage) : bodyStyle).color) ?? parseColor(bodyStyle.color);\n const tokenGround = parseColor(rootVar(doc, ['--sb-ground', '--sb-bg', '--color-ink-050']));\n\n const ink0 = parseColor(rootVar(doc, ['--fs-feedback-ink'])) ?? sampledInk;\n let ground =\n parseColor(rootVar(doc, ['--fs-feedback-ground'])) ??\n sampledGround ??\n (tokenGround && opaque(tokenGround) ? tokenGround : null) ??\n (ink0 && luminance(ink0) > 0.5 ? BLACK : WHITE);\n ground = [ground[0], ground[1], ground[2], 1];\n const dark = luminance(ground) < 0.35;\n let ink =\n ink0 && contrast([ink0[0], ink0[1], ink0[2], 1], ground) >= 4.5 ? ink0 : dark ? WHITE : BLACK;\n ink = [ink[0], ink[1], ink[2], 1];\n\n const button = primaryButton(doc, ground);\n const buttonStyle = button ? styleOf(button) : null;\n const pinnedCta = parseColor(rootVar(doc, ['--fs-feedback-cta']));\n const tokenCta = parseColor(rootVar(doc, ['--sb-cta', '--sb-accent', '--color-cream']));\n const sampledCta = buttonStyle ? parseColor(buttonStyle.backgroundColor) : null;\n const cta =\n pinnedCta ??\n sampledCta ??\n (tokenCta && contrast(tokenCta, ground) >= 1.25 ? tokenCta : null) ??\n ink;\n const pinnedCtaInk = parseColor(rootVar(doc, ['--fs-feedback-cta-ink']));\n const sampledCtaInk =\n !pinnedCta && sampledCta && buttonStyle ? parseColor(buttonStyle.color) : null;\n const ctaInk =\n pinnedCtaInk ??\n (sampledCtaInk && contrast(sampledCtaInk, cta) >= 3 ? sampledCtaInk : null) ??\n (luminance(cta) > 0.4 ? BLACK : WHITE);\n\n const heading = Array.from(doc.querySelectorAll('h1, h2, [class*=\"title\"]')).find(\n (el) => !inOurs(el) && visible(el)\n );\n const headingStyle = heading ? styleOf(heading) : null;\n const fontBody =\n rootVar(doc, ['--fs-feedback-font']) ||\n (stage ? styleOf(stage).fontFamily : '') ||\n bodyStyle.fontFamily ||\n rootVar(doc, ['--sb-font-body', '--font-body']) ||\n 'system-ui, sans-serif';\n const fontDisplay =\n rootVar(doc, ['--fs-feedback-font-display']) ||\n headingStyle?.fontFamily ||\n rootVar(doc, ['--sb-font-display', '--sb-font-title', '--font-display']) ||\n fontBody;\n\n const field = Array.from(\n doc.querySelectorAll('textarea, input[type=\"text\"], input:not([type])')\n ).find((el) => !inOurs(el) && visible(el));\n const px = (value: string | undefined): number | null => {\n const n = parseFloat(value ?? '');\n return Number.isFinite(n) ? n : null;\n };\n const cornerOf = (style: CSSStyleDeclaration | null): number | null =>\n style ? px(style.borderTopLeftRadius || style.borderRadius) : null;\n const buttonRadius = cornerOf(buttonStyle);\n const fieldRadius = cornerOf(field ? styleOf(field) : null);\n const radius =\n rootVar(doc, ['--fs-feedback-radius']) ||\n `${Math.min(fieldRadius ?? Math.min(buttonRadius ?? 16, 22), 28)}px`;\n\n return {\n ground: css(ground),\n surface: css(dark ? mix(ground, WHITE, 0.07) : mix(ground, WHITE, 0.6)),\n ink: css(ink),\n inkDim: css(mix(ink, ground, 0.32)),\n line: css(ink, 0.16),\n fill: css(ink, 0.05),\n cta: css([cta[0], cta[1], cta[2], 1]),\n ctaInk: css([ctaInk[0], ctaInk[1], ctaInk[2], 1]),\n fontBody,\n fontDisplay,\n displayWeight: headingStyle?.fontWeight || '500',\n radius,\n buttonRadius: buttonRadius === null ? '999px' : `${Math.min(buttonRadius, 999)}px`,\n dark,\n };\n}\n\n// \u2500\u2500 the screenshot \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst dataUrlBytes = (url: string): number =>\n Math.floor(((url.length - url.indexOf(',') - 1) * 3) / 4);\n\n/** Step the JPEG quality, then the size, until the picture fits the service's cap. */\nexport function encodeScreenshot(canvas: HTMLCanvasElement): string | null {\n let source = canvas;\n for (let pass = 0; pass < 3; pass += 1) {\n for (const quality of [0.82, 0.7, 0.58, 0.46, 0.34]) {\n const url = source.toDataURL('image/jpeg', quality);\n if (url.startsWith('data:image/jpeg') && dataUrlBytes(url) <= SCREENSHOT_MAX_BYTES)\n return url;\n }\n const smaller = document.createElement('canvas');\n smaller.width = Math.max(1, Math.round(source.width * 0.75));\n smaller.height = Math.max(1, Math.round(source.height * 0.75));\n const ctx = smaller.getContext('2d');\n if (!ctx) return null;\n ctx.drawImage(source, 0, 0, smaller.width, smaller.height);\n source = smaller;\n }\n return null;\n}\n\n/** The page as the person sees it right now: the viewport, without the feedback icon. */\nasync function captureViewport(ground: string): Promise<string | null> {\n const { domToCanvas } = await import('modern-screenshot');\n const width = window.innerWidth;\n const height = window.innerHeight;\n const scale = Math.min(\n window.devicePixelRatio || 1,\n SCREENSHOT_MAX_SIDE / Math.max(width, height)\n );\n const canvas = await domToCanvas(document.documentElement, {\n width,\n height,\n scale,\n backgroundColor: ground,\n style: { transform: `translate(${-window.scrollX}px, ${-window.scrollY}px)` },\n filter: (node) => !(node instanceof Element && node.hasAttribute(FEEDBACK_HOST_ATTR)),\n });\n return encodeScreenshot(canvas);\n}\n\nconst withTimeout = <T>(work: Promise<T>, ms: number): Promise<T | null> =>\n new Promise((resolve) => {\n const timer = setTimeout(() => resolve(null), ms);\n work.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n () => {\n clearTimeout(timer);\n resolve(null);\n }\n );\n });\n\n// \u2500\u2500 where the icon sits \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// The icon must not cover what a person taps, and should not cover what they read. No fixed\n// corner is safe across flows: a bottom-anchored primary button on one page sits exactly where\n// the icon cleared it on another (0.3.1 put the icon over The Pulse's \"Share my card\"). So the\n// icon places itself. It looks up the right edge from the bottom corner for the lowest spot that\n// is empty; it takes a spot over a line of text or a picture only when empty space is far above\n// it; and it sits on a control only when every spot does (a full screen of rows), where the\n// bottom corner costs the least. A flow that pins `--fs-feedback-bottom` keeps its own position\n// and none of this runs.\n\nexport const PLACEMENT_BASE = 16;\nexport const PLACEMENT_STEP = 8;\n/** Clear space kept around the icon, so it does not crowd what it sits beside. */\nexport const PLACEMENT_PAD = 8;\n/** The icon never climbs past this share of the viewport. */\nexport const PLACEMENT_MAX_SHARE = 0.6;\n/** How far up empty space is still worth more than sitting on a line of text lower down. */\nexport const PLACEMENT_READ_COST = 240;\n\n/** What is at a point: nothing, something to read or look at, or something to tap. */\nexport const EMPTY = 0;\nexport const READ = 1;\nexport const TAP = 2;\nexport type Occupancy = typeof EMPTY | typeof READ | typeof TAP;\n\nexport interface PlacementBox {\n /** The icon's box as measured now, in viewport pixels. */\n left: number;\n top: number;\n size: number;\n /** The bottom offset that box was measured at. */\n bottom: number;\n}\n\n/** Sample points across the padded box, close enough that a line of small text is not missed. */\nconst SAMPLES = 5;\n\n/**\n * The bottom offset to sit at. `at(x, y)` says what is at a viewport point. An empty spot costs\n * its height; a spot over text or a picture costs its height plus PLACEMENT_READ_COST; a spot\n * over a control is taken only when there is no other, and then it is the base.\n */\nexport function choosePlacement(\n box: PlacementBox,\n viewportHeight: number,\n at: (x: number, y: number) => Occupancy\n): number {\n const max = Math.max(PLACEMENT_BASE, viewportHeight * PLACEMENT_MAX_SHARE);\n const span = box.size + PLACEMENT_PAD * 2;\n const offsets = Array.from({ length: SAMPLES }, (_, i) => (span * i) / (SAMPLES - 1));\n const x0 = box.left - PLACEMENT_PAD;\n let best = PLACEMENT_BASE;\n let bestCost = Infinity;\n for (let bottom = PLACEMENT_BASE; bottom <= max; bottom += PLACEMENT_STEP) {\n // Costs only grow with height: nothing above this can beat what is already found.\n if (bottom >= bestCost) break;\n const y0 = box.top - (bottom - box.bottom) - PLACEMENT_PAD;\n let worst: Occupancy = EMPTY;\n for (const dy of offsets) {\n for (const dx of offsets) {\n const found = at(x0 + dx, y0 + dy);\n if (found > worst) worst = found;\n if (worst === TAP) break;\n }\n if (worst === TAP) break;\n }\n if (worst === TAP) continue;\n const cost = bottom + (worst === READ ? PLACEMENT_READ_COST : 0);\n if (cost < bestCost) {\n bestCost = cost;\n best = bottom;\n }\n }\n return best;\n}\n\nconst TAPPABLE =\n 'a[href],button,input,select,textarea,summary,label,[role=\"button\"],[role=\"link\"],[role=\"tab\"],[role=\"switch\"],[role=\"checkbox\"],[role=\"radio\"],[role=\"slider\"],[role=\"option\"],[tabindex]:not([tabindex=\"-1\"]),[contenteditable=\"\"],[contenteditable=\"true\"],[onclick]';\nconst PICTURE = 'img,svg,canvas,video,picture';\n/** A control that fills this much of the viewport is a \"tap anywhere\" surface (a tap-through\n * story, a sealed pack): the icon has to sit somewhere on it, so it does not count. */\nconst SURFACE_SHARE = 0.8;\n\ninterface Reading {\n tap: boolean;\n picture: boolean;\n /** Boxes of the element's own text, in viewport pixels. */\n text: DOMRect[];\n /** It paints a ground of its own, so what lies under it cannot be seen. */\n opaque: boolean;\n}\n\n/**\n * Reads what is at viewport points, the feedback host aside. One reader serves one placement\n * pass: what it learns about an element is kept for the pass and dropped with it.\n *\n * A tap lands on the topmost element and travels up its ancestors, so only those can make a\n * point a control: a button under the flow's own bottom sheet is not one. What a person reads\n * is whatever shows through, down to the first element that paints its own ground.\n */\nexport function occupancyReader(\n host: Element,\n doc: Document = document\n): (x: number, y: number) => Occupancy {\n if (typeof doc.elementsFromPoint !== 'function') return () => EMPTY;\n const view = doc.defaultView;\n const width = view?.innerWidth ?? 1;\n const height = view?.innerHeight ?? 1;\n const seen = new Map<Element, Reading>();\n const read = (el: Element): Reading => {\n const known = seen.get(el);\n if (known) return known;\n const style = view ? view.getComputedStyle(el) : null;\n // A div with a click handler says so only through its cursor.\n let tap = el.matches(TAPPABLE) || style?.cursor === 'pointer';\n if (tap) {\n const r = el.getBoundingClientRect();\n if (r.width * r.height >= width * height * SURFACE_SHARE) tap = false;\n }\n const text: DOMRect[] = [];\n if (typeof doc.createRange === 'function') {\n for (const node of Array.from(el.childNodes)) {\n if (node.nodeType !== 3 || !node.nodeValue || !node.nodeValue.trim()) continue;\n const range = doc.createRange();\n range.selectNodeContents(node);\n if (typeof range.getClientRects === 'function')\n text.push(...Array.from(range.getClientRects()));\n }\n }\n const ground = parseColor(style?.backgroundColor);\n const image = style?.backgroundImage ?? '';\n const reading = {\n tap,\n picture: el.matches(PICTURE) || el.closest('svg') !== null,\n text,\n opaque: (ground !== null && ground[3] >= 0.5) || (image !== '' && image !== 'none'),\n };\n seen.set(el, reading);\n return reading;\n };\n return (x, y) => {\n const cx = Math.min(Math.max(x, 0), width - 1);\n const cy = Math.min(Math.max(y, 0), height - 1);\n const stack = doc\n .elementsFromPoint(cx, cy)\n .filter((el) => el !== host && el !== doc.documentElement && el !== doc.body);\n for (let el: Element | null = stack[0] ?? null; el && el !== doc.body; el = el.parentElement) {\n if (read(el).tap) return TAP;\n }\n for (const el of stack) {\n const r = read(el);\n if (\n r.picture ||\n r.text.some((t) => cx >= t.left && cx <= t.right && cy >= t.top && cy <= t.bottom)\n )\n return READ;\n if (r.opaque) break;\n }\n return EMPTY;\n };\n}\n\n// \u2500\u2500 floating and docked \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// The button floats over the flow and belongs to the person, not to the page (FB-478, founder\n// ruling 2026-11-11). They can drag it anywhere; let go and it settles on the nearer side. Flick\n// it at an edge, or drag it into one, and it docks there as a slim tab with the bug still\n// showing; a tap on the tab still opens the sheet, and pulling the tab inward floats it again.\n// Where they leave it is remembered. Until they move it, it keeps placing itself (above).\n\nexport type DockState = 'float' | 'dock-left' | 'dock-right';\nexport const BUTTON_SIZE = 44;\nexport const TAB_WIDTH = 26;\nexport const TAB_HEIGHT = 54;\nexport const EDGE_MARGIN = 12;\n/** How far past an edge a release has to be heading before the button docks there. */\nexport const DOCK_PAST = 14;\n/** Storage key for where the person left the button. One origin serves every flow, so the\n * choice follows them from flow to flow. */\nexport const SPOT_KEY = 'fs-feedback-spot';\n\n/** The part of the viewport the button may rest in: its size, less the safe-area insets. */\nexport interface FeedbackViewport {\n width: number;\n height: number;\n top: number;\n bottom: number;\n}\n\nexport interface FeedbackSpot {\n state: DockState;\n x: number;\n y: number;\n}\n\n/** Where a release at velocity `v` (px per second) comes to rest: scroll deceleration. */\nexport const projectMomentum = (v: number, deceleration = 0.992): number =>\n ((v / 1000) * deceleration) / (1 - deceleration);\n\n/** Past a boundary the button follows less and less, so the edge reads as soft, not stuck. */\nexport const rubberband = (overshoot: number, dimension: number, constant = 0.55): number =>\n (overshoot * dimension * constant) / (dimension + constant * Math.abs(overshoot));\n\nconst clampY = (y: number, height: number, view: FeedbackViewport): number =>\n Math.min(\n Math.max(y, view.top + EDGE_MARGIN),\n Math.max(view.top + EDGE_MARGIN, view.height - view.bottom - EDGE_MARGIN - height)\n );\n\n/** The resting spot for a state on one side, at a height. */\nexport function restingSpot(\n state: DockState,\n left: boolean,\n y: number,\n view: FeedbackViewport\n): FeedbackSpot {\n if (state === 'float') {\n return {\n state,\n x: left ? EDGE_MARGIN : view.width - BUTTON_SIZE - EDGE_MARGIN,\n y: clampY(y, BUTTON_SIZE, view),\n };\n }\n return { state, x: left ? 0 : view.width - TAB_WIDTH, y: clampY(y, TAB_HEIGHT, view) };\n}\n\n/**\n * Where a drag that ends at (x, y) with velocity (vx, vy) settles. A floating button heading\n * past an edge, or let go against one, docks on that side; otherwise it floats on the side its\n * momentum carries it to. A docked tab stays docked and only slides along its edge.\n */\nexport function settleRelease(\n from: FeedbackSpot,\n velocity: { x: number; y: number },\n view: FeedbackViewport\n): FeedbackSpot {\n const py = from.y + projectMomentum(velocity.y);\n if (from.state !== 'float') return restingSpot(from.state, from.state === 'dock-left', py, view);\n const px = from.x + projectMomentum(velocity.x);\n const maxX = view.width - BUTTON_SIZE;\n const tabY = py + (BUTTON_SIZE - TAB_HEIGHT) / 2;\n if (px < -DOCK_PAST || from.x < 3) return restingSpot('dock-left', true, tabY, view);\n if (px > maxX + DOCK_PAST || from.x > maxX - 3)\n return restingSpot('dock-right', false, tabY, view);\n return restingSpot('float', px + BUTTON_SIZE / 2 < view.width / 2, py, view);\n}\n\n// \u2500\u2500 the icon and the sheet \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst BUG_ICON =\n '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"8\" y=\"7\" width=\"8\" height=\"12\" rx=\"4\"/><path d=\"M9.5 7a2.5 2.5 0 0 1 5 0M12 11v8M4 13h4M16 13h4M5 7l3.2 2.4M19 7l-3.2 2.4M5 20l3.2-2.6M19 20l-3.2-2.6\"/></svg>';\n\nconst STYLE = `\n:host { all: initial; }\n* { box-sizing: border-box; }\n.bug {\n position: fixed; z-index: 2147483000; left: 0; top: 0;\n width: 44px; height: 44px; border-radius: 22px; padding: 0; margin: 0;\n display: var(--fs-feedback-display, grid); place-items: center; cursor: grab;\n background: var(--surface); color: var(--ink); border: 1px solid var(--line);\n box-shadow: 0 10px 24px rgba(0, 0, 0, 0.2), 0 2px 5px rgba(0, 0, 0, 0.12);\n -webkit-tap-highlight-color: transparent; touch-action: none; user-select: none; -webkit-user-select: none;\n will-change: transform;\n}\n@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {\n .bug {\n background: color-mix(in srgb, var(--surface) 74%, transparent);\n -webkit-backdrop-filter: blur(14px) saturate(160%); backdrop-filter: blur(14px) saturate(160%);\n }\n}\n.bug svg { width: 20px; height: 20px; pointer-events: none; }\n.bug.down svg { transform: scale(0.9); }\n.bug.dragging { cursor: grabbing; box-shadow: 0 18px 36px rgba(0, 0, 0, 0.26), 0 3px 8px rgba(0, 0, 0, 0.14); }\n.bug[data-state^=\"dock\"] { width: 26px; height: 54px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.14); opacity: 0.92; }\n.bug[data-state=\"dock-right\"] { border-radius: 16px 0 0 16px; border-right-width: 0; }\n.bug[data-state=\"dock-left\"] { border-radius: 0 16px 16px 0; border-left-width: 0; }\n.bug[data-state^=\"dock\"] svg { width: 16px; height: 16px; }\n.probe {\n position: fixed; visibility: hidden; pointer-events: none; width: 0; height: 0;\n padding: env(safe-area-inset-top, 0px) 0 env(safe-area-inset-bottom, 0px) 0;\n}\n.tuck { align-self: center; font-size: 13px; color: var(--ink-dim); }\n@media (prefers-reduced-transparency: reduce) {\n .bug { background: var(--surface); -webkit-backdrop-filter: none; backdrop-filter: none; }\n}\n@media (prefers-contrast: more) {\n .bug { background: var(--surface); border-color: var(--ink); -webkit-backdrop-filter: none; backdrop-filter: none; }\n}\n.bug:focus-visible, .send:focus-visible, .link:focus-visible, textarea:focus-visible {\n outline: 2px solid var(--ink); outline-offset: 2px;\n}\n.scrim { position: fixed; inset: 0; z-index: 2147483001; background: rgba(0, 0, 0, 0.42); }\n.sheet {\n position: fixed; left: 0; right: 0; bottom: 0; z-index: 2147483002;\n margin: 0 auto; max-width: 480px;\n background: var(--surface); color: var(--ink); border: 1px solid var(--line); border-bottom: 0;\n border-radius: calc(var(--radius) + 6px) calc(var(--radius) + 6px) 0 0;\n padding: 12px 18px calc(20px + env(safe-area-inset-bottom, 0px));\n display: flex; flex-direction: column; gap: 12px;\n font-family: var(--font-body); font-size: 15px; line-height: 1.4;\n}\n.grab { width: 36px; height: 4px; border-radius: 2px; background: var(--line); align-self: center; }\nh2 { margin: 0; font-family: var(--font-display); font-weight: var(--display-weight); font-size: 19px; line-height: 1.2; }\n.shot { display: flex; gap: 12px; align-items: center; font-size: 13px; color: var(--ink-dim); }\n.shot img { width: 50px; max-height: 108px; object-fit: cover; object-position: top; border-radius: 8px; border: 1px solid var(--line); }\n.link { background: none; border: 0; padding: 0; font: inherit; color: var(--ink); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }\ntextarea {\n width: 100%; min-height: 84px; resize: none; font: inherit; font-size: 16px; color: var(--ink);\n background: var(--fill); border: 1px solid var(--line); border-radius: var(--radius); padding: 13px 15px;\n}\ntextarea::placeholder { color: var(--ink-dim); }\n.send {\n font: inherit; font-weight: 500; cursor: pointer; border: 0; padding: 14px;\n background: var(--cta); color: var(--cta-ink); border-radius: var(--button-radius);\n}\n.send[disabled] { opacity: 0.5; cursor: default; }\n.status { margin: 0; font-size: 13px; color: var(--ink-dim); min-height: 1.4em; }\n[hidden] { display: none !important; }\n@media (prefers-reduced-motion: no-preference) {\n .bug {\n transition: width 0.22s cubic-bezier(0.2, 0.8, 0.2, 1), height 0.22s cubic-bezier(0.2, 0.8, 0.2, 1),\n border-radius 0.22s cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 0.22s ease;\n }\n .bug svg { transition: transform 0.1s ease-out, width 0.22s ease, height 0.22s ease; }\n .sheet { animation: rise 0.22s ease-out; }\n @keyframes rise { from { transform: translateY(24px); opacity: 0; } to { transform: none; opacity: 1; } }\n}\n`;\n\nlet mounted: FlowFeedbackHandle | null = null;\n\n/** Whether the page asked for no feedback icon (`<html data-fs-feedback=\"off\">`). */\nexport const feedbackOptedOut = (doc: Document = document): boolean =>\n doc.documentElement.getAttribute('data-fs-feedback') === 'off';\n\nconst SCROLL_OPTIONS = { capture: true, passive: true } as const;\n\nexport function mountFlowFeedback(options: FlowFeedbackOptions): FlowFeedbackHandle | null {\n if (typeof document === 'undefined' || !document.body) return null;\n if (mounted) return mounted;\n if (feedbackOptedOut()) return null;\n\n const host = document.createElement('div');\n host.setAttribute(FEEDBACK_HOST_ATTR, '');\n const root = host.attachShadow({ mode: 'open' });\n root.innerHTML = `<style>${STYLE}</style>\n<button type=\"button\" class=\"bug\" data-state=\"float\" aria-label=\"Send feedback on this page\">${BUG_ICON}</button>\n<div class=\"probe\" aria-hidden=\"true\"></div>\n<div class=\"scrim\" hidden></div>\n<div class=\"sheet\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"fsf-title\" hidden>\n <div class=\"grab\" aria-hidden=\"true\"></div>\n <h2 id=\"fsf-title\">Feedback on this page</h2>\n <div class=\"shot\" hidden><img alt=\"Screenshot of this page\" /><span><span>Screenshot attached</span><br /><button type=\"button\" class=\"link remove\">Remove screenshot</button></span></div>\n <textarea aria-labelledby=\"fsf-title\" placeholder=\"What should change?\" maxlength=\"2000\"></textarea>\n <p class=\"status\" role=\"status\" aria-live=\"polite\"></p>\n <button type=\"button\" class=\"send\" disabled>Send feedback</button>\n <button type=\"button\" class=\"link tuck\">Tuck the button to the side</button>\n</div>`;\n const q = <T extends Element>(selector: string): T => root.querySelector(selector) as T;\n const bug = q<HTMLButtonElement>('.bug');\n const scrim = q<HTMLDivElement>('.scrim');\n const sheet = q<HTMLDivElement>('.sheet');\n const shot = q<HTMLDivElement>('.shot');\n const img = q<HTMLImageElement>('.shot img');\n const remove = q<HTMLButtonElement>('.remove');\n const text = q<HTMLTextAreaElement>('textarea');\n const status = q<HTMLParagraphElement>('.status');\n const send = q<HTMLButtonElement>('.send');\n const tuck = q<HTMLButtonElement>('.tuck');\n const probe = q<HTMLDivElement>('.probe');\n\n let theme = readFlowTheme();\n let screenshot: string | null = null;\n let busy = false;\n let closeTimer: ReturnType<typeof setTimeout> | null = null;\n let placeTimer: ReturnType<typeof setTimeout> | null = null;\n let autoBottom = PLACEMENT_BASE;\n let spot: FeedbackSpot = { state: 'float', x: 0, y: 0 };\n /** The person moved or docked it: from then on the button stays where they put it. */\n let placedByPerson = false;\n let velocity = { x: 0, y: 0 };\n let frame = 0;\n\n const px = (name: string): number | null => {\n const raw = getComputedStyle(host).getPropertyValue(name).trim();\n const n = raw ? parseFloat(raw) : NaN;\n return Number.isFinite(n) ? n : null;\n };\n const viewport = (): FeedbackViewport => {\n const style = getComputedStyle(probe);\n return {\n width: window.innerWidth,\n height: window.innerHeight,\n top: parseFloat(style.paddingTop) || 0,\n bottom: parseFloat(style.paddingBottom) || 0,\n };\n };\n const draw = (): void => {\n bug.style.transform = `translate3d(${spot.x}px, ${spot.y}px, 0)`;\n };\n const setState = (next: DockState): void => {\n spot.state = next;\n bug.dataset.state = next;\n };\n /** Keep where the person is putting the button: the spot it is heading for, saved when the\n * move starts, so a page that closes mid-motion still remembers. */\n const remember = (spot: FeedbackSpot): void => {\n if (!placedByPerson) return;\n try {\n const view = viewport();\n const width = spot.state === 'float' ? BUTTON_SIZE : TAB_WIDTH;\n localStorage.setItem(\n SPOT_KEY,\n JSON.stringify({\n state: spot.state,\n left: spot.x + width / 2 < view.width / 2,\n y: view.height > 0 ? spot.y / view.height : 0,\n })\n );\n } catch {\n // Storage is a convenience; a private window keeps the spot for this page only.\n }\n };\n const still = (): boolean =>\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n\n /** Spring to a spot from wherever the button is now, at whatever speed it has. Damping 1\n * (no overshoot) unless a flick carried it there; response 0.4 s. */\n const springTo = (to: FeedbackSpot, flicked: boolean): void => {\n cancelAnimationFrame(frame);\n frame = 0;\n setState(to.state);\n remember(to);\n if (still() || typeof requestAnimationFrame !== 'function') {\n spot = { ...to };\n velocity = { x: 0, y: 0 };\n draw();\n return;\n }\n const omega = (2 * Math.PI) / 0.4;\n const k = omega * omega;\n const c = 2 * (flicked ? 0.8 : 1) * omega;\n let last = performance.now();\n const tick = (now: number): void => {\n const dt = Math.min(0.032, (now - last) / 1000);\n last = now;\n velocity.x += (-k * (spot.x - to.x) - c * velocity.x) * dt;\n velocity.y += (-k * (spot.y - to.y) - c * velocity.y) * dt;\n spot.x += velocity.x * dt;\n spot.y += velocity.y * dt;\n const close =\n Math.abs(spot.x - to.x) < 0.3 &&\n Math.abs(spot.y - to.y) < 0.3 &&\n Math.hypot(velocity.x, velocity.y) < 6;\n if (close) {\n spot = { ...to };\n velocity = { x: 0, y: 0 };\n frame = 0;\n draw();\n return;\n }\n draw();\n frame = requestAnimationFrame(tick);\n };\n frame = requestAnimationFrame(tick);\n };\n\n /** Until the person moves it, the button places itself clear of the page's own controls and\n * text; a flow's pin (`--fs-feedback-bottom`) holds it instead. Once they have moved or\n * docked it, it only stays inside the viewport. Skipped while the sheet is up. */\n const place = (): void => {\n placeTimer = null;\n if (!sheet.hidden || bug.hidden || drag || frame) return;\n const view = viewport();\n if (placedByPerson || spot.state !== 'float') {\n const width = spot.state === 'float' ? BUTTON_SIZE : TAB_WIDTH;\n spot = restingSpot(spot.state, spot.x + width / 2 < view.width / 2, spot.y, view);\n draw();\n return;\n }\n const right = px('--fs-feedback-right') ?? 14;\n const left = view.width - right - BUTTON_SIZE;\n const pinned = px('--fs-feedback-bottom');\n if (pinned === null && typeof document.elementsFromPoint === 'function') {\n autoBottom = choosePlacement(\n {\n left,\n top: view.height - view.bottom - autoBottom - BUTTON_SIZE,\n size: BUTTON_SIZE,\n bottom: autoBottom,\n },\n view.height,\n occupancyReader(host)\n );\n }\n const bottom = pinned ?? autoBottom;\n spot = { state: 'float', x: left, y: view.height - view.bottom - bottom - BUTTON_SIZE };\n draw();\n };\n const schedulePlace = (): void => {\n if (placeTimer) clearTimeout(placeTimer);\n placeTimer = setTimeout(place, 150);\n };\n\n // Dragging: the button stays under the finger from where it was grabbed, a short history of\n // the pointer gives the release its velocity, and the release is handed to the spring.\n let drag: {\n id: number;\n ox: number;\n oy: number;\n sx: number;\n sy: number;\n moved: boolean;\n history: { t: number; x: number; y: number }[];\n } | null = null;\n let suppressClick = false;\n const onDown = (event: PointerEvent): void => {\n if (!sheet.hidden || busy) return;\n bug.setPointerCapture?.(event.pointerId);\n cancelAnimationFrame(frame);\n frame = 0;\n velocity = { x: 0, y: 0 };\n drag = {\n id: event.pointerId,\n ox: event.clientX - spot.x,\n oy: event.clientY - spot.y,\n sx: event.clientX,\n sy: event.clientY,\n moved: false,\n history: [{ t: event.timeStamp, x: event.clientX, y: event.clientY }],\n };\n bug.classList.add('down');\n };\n const onMove = (event: PointerEvent): void => {\n if (!drag || event.pointerId !== drag.id) return;\n const dx = event.clientX - drag.sx;\n const dy = event.clientY - drag.sy;\n if (!drag.moved && Math.hypot(dx, dy) < 8) return;\n if (!drag.moved) {\n drag.moved = true;\n bug.classList.add('dragging');\n }\n drag.history.push({ t: event.timeStamp, x: event.clientX, y: event.clientY });\n if (drag.history.length > 6) drag.history.shift();\n const view = viewport();\n if (spot.state !== 'float') {\n // A docked tab slides along its edge, and comes out once it is pulled inward far enough.\n const inward = spot.state === 'dock-left' ? dx : -dx;\n if (inward <= 18) {\n spot = restingSpot(spot.state, spot.state === 'dock-left', event.clientY - drag.oy, view);\n draw();\n return;\n }\n setState('float');\n drag.ox = BUTTON_SIZE / 2;\n drag.oy = BUTTON_SIZE / 2;\n }\n let x = event.clientX - drag.ox;\n let y = event.clientY - drag.oy;\n const maxX = view.width - BUTTON_SIZE;\n const maxY = view.height - BUTTON_SIZE;\n if (x < 0) x = rubberband(x, 60);\n else if (x > maxX) x = maxX + rubberband(x - maxX, 60);\n if (y < 0) y = rubberband(y, 80);\n else if (y > maxY) y = maxY + rubberband(y - maxY, 80);\n spot.x = x;\n spot.y = y;\n draw();\n };\n const onUp = (event: PointerEvent): void => {\n if (!drag || event.pointerId !== drag.id) return;\n const ended = drag;\n drag = null;\n bug.classList.remove('down', 'dragging');\n if (!ended.moved) return; // a tap: the click that follows opens the sheet\n suppressClick = true;\n setTimeout(() => {\n suppressClick = false;\n }, 0);\n const first = ended.history[0]!;\n const last = ended.history[ended.history.length - 1]!;\n const dt = Math.max(1, last.t - first.t);\n const paused = event.timeStamp - last.t > 90;\n velocity = paused\n ? { x: 0, y: 0 }\n : { x: ((last.x - first.x) / dt) * 1000, y: ((last.y - first.y) / dt) * 1000 };\n placedByPerson = true;\n springTo(settleRelease(spot, velocity, viewport()), Math.hypot(velocity.x, velocity.y) > 350);\n };\n\n const observer =\n typeof MutationObserver === 'function'\n ? new MutationObserver((records) => {\n if (records.some((r) => r.target !== host)) schedulePlace();\n })\n : null;\n\n const paint = (): void => {\n theme = readFlowTheme();\n const vars: Record<string, string> = {\n '--surface': theme.surface,\n '--ink': theme.ink,\n '--ink-dim': theme.inkDim,\n '--line': theme.line,\n '--fill': theme.fill,\n '--cta': theme.cta,\n '--cta-ink': theme.ctaInk,\n '--font-body': theme.fontBody,\n '--font-display': theme.fontDisplay,\n '--display-weight': theme.displayWeight,\n '--radius': theme.radius,\n '--button-radius': theme.buttonRadius,\n };\n for (const [name, value] of Object.entries(vars)) host.style.setProperty(name, value);\n host.style.colorScheme = theme.dark ? 'dark' : 'light';\n };\n const sync = (): void => {\n send.disabled = busy || text.value.trim().length === 0;\n };\n const setShot = (url: string | null): void => {\n screenshot = url;\n shot.hidden = url === null;\n if (url) img.src = url;\n else img.removeAttribute('src');\n };\n const close = (): void => {\n if (closeTimer) clearTimeout(closeTimer);\n closeTimer = null;\n sheet.hidden = true;\n scrim.hidden = true;\n bug.hidden = false;\n setShot(null);\n text.value = '';\n status.textContent = '';\n busy = false;\n sync();\n schedulePlace();\n };\n const open = async (): Promise<void> => {\n if (!sheet.hidden || busy) return;\n busy = true;\n paint();\n // The picture is taken before the sheet shows, so the sheet is never in it.\n bug.hidden = true;\n const capture = options.capture ?? (() => captureViewport(theme.ground));\n const url = await withTimeout(Promise.resolve().then(capture), CAPTURE_TIMEOUT_MS);\n busy = false;\n setShot(url);\n tuck.hidden = spot.state !== 'float';\n scrim.hidden = false;\n sheet.hidden = false;\n sync();\n text.focus();\n };\n const submit = async (): Promise<void> => {\n const message = text.value.trim();\n if (!message || busy) return;\n busy = true;\n sync();\n status.textContent = '';\n try {\n await options.submit({\n message,\n ...(screenshot ? { screenshot } : {}),\n ...(document.body.dataset.pageKey ? { pageKey: document.body.dataset.pageKey } : {}),\n pagePath: window.location.pathname.slice(-200),\n viewport: { width: window.innerWidth, height: window.innerHeight },\n });\n status.textContent = 'Sent.';\n closeTimer = setTimeout(() => {\n close();\n bug.focus();\n }, 1200);\n } catch {\n busy = false;\n status.textContent = 'That did not send. Try again.';\n sync();\n }\n };\n\n bug.addEventListener('click', () => {\n if (!suppressClick) void open();\n });\n bug.addEventListener('pointerdown', onDown);\n bug.addEventListener('pointermove', onMove);\n bug.addEventListener('pointerup', onUp);\n bug.addEventListener('pointercancel', onUp);\n tuck.addEventListener('click', () => {\n const view = viewport();\n const left = spot.x + BUTTON_SIZE / 2 < view.width / 2;\n close();\n placedByPerson = true;\n springTo(\n restingSpot(\n left ? 'dock-left' : 'dock-right',\n left,\n spot.y + (BUTTON_SIZE - TAB_HEIGHT) / 2,\n view\n ),\n false\n );\n });\n scrim.addEventListener('click', close);\n remove.addEventListener('click', () => setShot(null));\n text.addEventListener('input', sync);\n send.addEventListener('click', () => void submit());\n sheet.addEventListener('keydown', (event) => {\n if (event.key === 'Escape') {\n close();\n bug.focus();\n }\n event.stopPropagation();\n });\n\n document.body.appendChild(host);\n paint();\n try {\n const saved = JSON.parse(localStorage.getItem(SPOT_KEY) ?? 'null') as {\n state?: unknown;\n left?: unknown;\n y?: unknown;\n } | null;\n if (\n saved &&\n typeof saved.y === 'number' &&\n (saved.state === 'float' || saved.state === 'dock-left' || saved.state === 'dock-right')\n ) {\n const view = viewport();\n placedByPerson = true;\n setState(saved.state);\n spot = restingSpot(saved.state, saved.left === true, saved.y * view.height, view);\n }\n } catch {\n // No saved spot, or storage is closed: the button places itself.\n }\n // The page is often still drawing when the icon mounts: place now, and again as it settles.\n place();\n schedulePlace();\n observer?.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['class', 'style', 'hidden'],\n });\n window.addEventListener('resize', schedulePlace);\n window.addEventListener('load', schedulePlace);\n // A button at the end of a scrolling page slides under an icon that only looked once. Capture,\n // so a scroll inside the flow's own container counts; the debounce places after it stops.\n window.addEventListener('scroll', schedulePlace, SCROLL_OPTIONS);\n // A screen that slides or fades in is still moving when the DOM change is seen: look again\n // when its motion ends.\n document.addEventListener('transitionend', schedulePlace, SCROLL_OPTIONS);\n document.addEventListener('animationend', schedulePlace, SCROLL_OPTIONS);\n options.onReady?.();\n\n mounted = {\n host,\n refresh: () => {\n if (sheet.hidden) paint();\n },\n open,\n close,\n unmount: () => {\n if (closeTimer) clearTimeout(closeTimer);\n if (placeTimer) clearTimeout(placeTimer);\n cancelAnimationFrame(frame);\n observer?.disconnect();\n window.removeEventListener('resize', schedulePlace);\n window.removeEventListener('load', schedulePlace);\n window.removeEventListener('scroll', schedulePlace, SCROLL_OPTIONS);\n document.removeEventListener('transitionend', schedulePlace, SCROLL_OPTIONS);\n document.removeEventListener('animationend', schedulePlace, SCROLL_OPTIONS);\n host.remove();\n mounted = null;\n },\n };\n return mounted;\n}\n\nexport const unmountFlowFeedback = (): void => mounted?.unmount();\n", "// Session module \u2014 the official client for Flowstate widget-session rails.\n//\n// Group-session widgets (decision sprint, and any future multi-contributor\n// widget) run inside the app's /w/[slug] page. The page hands the iframe a\n// single-use auth ticket over postMessage (ADR-025: never in the URL on the\n// production path), the ticket is redeemed for a session token, and every\n// session verb is a REST call against /api/w/:slug on the widget gateway.\n//\n// This module wraps that whole contract so a widget never hand-rolls the\n// bridge:\n//\n// ```ts\n// import { CanvasWidget } from '@goflowstate/widget-sdk';\n// const session = await CanvasWidget.initSession();\n// if (session) {\n// const state = await session.join();\n// const stop = session.poll((s) => render(s));\n// await session.contribute({ evidence: { pins } });\n// } else {\n// // no launch params \u2014 standalone dev; use createMockSession()\n// }\n// ```\n//\n// The wire vocabulary is the generic session shape the rails speak\n// (historically named after the expedition: `tripId`, `myWishlist`, `plan`).\n// This module maps it onto neutral names once, here, so widgets never see the\n// legacy keys.\n\n// ============================================\n// State view\n// ============================================\n\nimport { mountFlowFeedback, unmountFlowFeedback } from './feedback';\n\nexport interface SessionMember {\n userId: string;\n name: string;\n /** Deterministic per-user accent color (hex), stable across sessions. */\n color: string;\n /** True once this member has contributed (sealed) in the current round. */\n sealed: boolean;\n isMe: boolean;\n isHost: boolean;\n}\n\n/** Gate policy surfaced to widgets (ADR-047). Optional server-side \u2014 links\n * minted without a gate report null. */\nexport interface SessionGateView {\n deadlineAt: string | null;\n quorum?: { mode: 'all' | 'count' | 'pct'; min?: number };\n}\n\n/** Flow context when this session is a node in a running flow (ADR-047). */\nexport interface SessionFlowView {\n runId: string;\n nodeIndex: number;\n nodeCount: number;\n nodes: Array<{\n key: string;\n title: string;\n status: 'pending' | 'active' | 'resolved' | 'expired';\n artifactId?: string;\n linkSlug?: string;\n }>;\n}\n\n/** One verified (or user-entered) fact about the viewer, from a unishell\n * shell. Read-only context: never use it to authorize anything. */\nexport interface SessionClaim {\n type: 'name' | 'email' | 'phone';\n value: string;\n /** 'user-entered', 'otp', or a surface id such as 'discord'. */\n source: string;\n /** Present only when the value was proven. */\n verifiedAt?: string;\n}\n\nexport interface SessionClaimSet {\n name?: SessionClaim;\n email?: SessionClaim;\n phone?: SessionClaim;\n}\n\n/** Who this state was read for. `claims` is set only for a shell guest\n * reading their own state; it is never another participant's. */\nexport interface SessionViewer {\n userId: string;\n guest: boolean;\n claims: SessionClaimSet | null;\n}\n\n/** The rails' phase model: `setup` until the host commits framing, `open`\n * while contributions are being collected, `sealed` once quorum is met and\n * the session awaits close, `resolved` after synthesis produced the\n * artifact. (Wire values `wishlist`/`plan` are mapped to `open`/`resolved`.) */\nexport type SessionPhase = 'setup' | 'open' | 'sealed' | 'resolved';\n\nexport interface SessionStateView<\n TSetup = Record<string, unknown>,\n TContribution = unknown,\n TDoc = Record<string, unknown>,\n> {\n /** The widget-link slug \u2014 the session's identity and its /w/:slug URL key. */\n slug: string;\n phase: SessionPhase;\n status: string;\n setup: TSetup | null;\n members: SessionMember[];\n sealedCount: number;\n memberCount: number;\n /** True when every member has sealed (legacy all-hands quorum). */\n allSealed: boolean;\n /** True when the configured quorum policy is satisfied (falls back to\n * allSealed when the link carries no gate). */\n quorumMet: boolean;\n gate: SessionGateView | null;\n flow: SessionFlowView | null;\n /** Own contribution only \u2014 others stay blind until synthesis. */\n myContribution: TContribution | null;\n /** The synthesized document, present once the session resolved. */\n doc: TDoc | null;\n artifactId: string | null;\n /** Widget-specific state-view extension (e.g. the decision sprint's stage\n * + ballot), passed through verbatim when the server provides one. */\n extension: Record<string, unknown> | null;\n /** The reader of this state; null when the server sent none (servers\n * before the shell endpoints). Optional so hand-built views keep compiling. */\n viewer?: SessionViewer | null;\n}\n\n// ============================================\n// Client interface\n// ============================================\n\nexport interface SessionPollOptions {\n /** Poll cadence in ms. Default 4000 (matches the historical widgets). */\n intervalMs?: number;\n}\n\nexport interface SessionClient<\n TSetup = Record<string, unknown>,\n TContribution = unknown,\n TDoc = Record<string, unknown>,\n> {\n readonly slug: string;\n /** Base URL of the web app (for reveal links and parent navigation). */\n readonly webBase: string;\n /** Base URL of the mobile PWA, when the host provided one via the optional\n * `mobile` iframe param. Participant-facing join links (e.g. a projected\n * QR) should prefer it and fall back to `webBase` when absent. Optional so\n * hand-rolled SessionClient implementations keep compiling. */\n readonly mobileBase?: string | null;\n /** The viewer's own shell claims from the most recent state read; null\n * until a state arrives or when there are none. Optional so hand-rolled\n * SessionClient implementations keep compiling. */\n readonly claims?: SessionClaimSet | null;\n\n /** Enter the session: the first identified enter creates the group. */\n join(): Promise<SessionStateView<TSetup, TContribution, TDoc>>;\n /** Fetch the current blind-aware state. */\n state(): Promise<SessionStateView<TSetup, TContribution, TDoc>>;\n /** Poll state on a cadence. Returns a stop function. Errors are passed to\n * onError (or swallowed) so a transient failure never kills the loop. */\n poll(\n onState: (s: SessionStateView<TSetup, TContribution, TDoc>) => void,\n opts?: SessionPollOptions & { onError?: (err: unknown) => void }\n ): () => void;\n /** Host-only: commit the session framing. */\n commitSetup(setup: TSetup): Promise<SessionStateView<TSetup, TContribution, TDoc>>;\n /** Upsert my contribution (seal). One contribution row per member; widgets\n * with multi-stage payloads post the merged object every time. */\n contribute(payload: TContribution): Promise<SessionStateView<TSetup, TContribution, TDoc>>;\n /** Host-only close: synthesis runs, the artifact is born. Idempotent. */\n close(): Promise<SessionStateView<TSetup, TContribution, TDoc>>;\n /** Widget-specific verb that returns a session STATE (e.g. the sprint's\n * /advance). Body is JSON-encoded; the response's state is returned. */\n action(verb: string, body?: unknown): Promise<SessionStateView<TSetup, TContribution, TDoc>>;\n /** Widget-specific verb with a NON-state response (e.g. /assist). Returns\n * the response's raw `data` value. Optional cancellation stops this request,\n * including its wait for the shared ticket; it does not cancel ticket redemption. */\n request<T = unknown>(\n verb: string,\n body?: unknown,\n options?: { signal?: AbortSignal }\n ): Promise<T>;\n /** Open a Flowstate URL outside the widget (parent navigation preferred \u2014\n * the /w page owns the session cookie). */\n openExternal(url: string): void;\n /** Deep link to an artifact reveal on the web app. */\n revealUrl(artifactId: string): string;\n}\n\n// ============================================\n// Launch params + ticket handshake (ADR-025)\n// ============================================\n\nexport interface SessionLaunchParams {\n slug: string;\n /** Dev-only fallback: a ticket in the URL. Production receives the ticket\n * via the parent's `flowstate:init` postMessage \u2014 URLs leak into\n * history/referrers/logs. */\n ticket: string | null;\n apiBase: string;\n webBase: string;\n /** Base URL of the mobile PWA (the optional `mobile` iframe param). Null\n * when the host didn't send one \u2014 legacy hosts never do \u2014 so consumers\n * fall back to `webBase` links. Optional so hand-built params keep\n * compiling. */\n mobileBase?: string | null;\n /** Origin of the page that FRAMES the widget \u2014 the `host` iframe param.\n * The ready/init ticket handshake and `flowstate:navigate` are addressed\n * to this origin (0.2.2). Defaults to `webBase` when absent, which is\n * what every pre-0.2.2 host is: the web /w page framing the widget. The\n * mobile PWA frames the widget itself (its native lobby) and names itself\n * here \u2014 without it the browser drops every postMessage between them\n * (target-origin mismatch) and the widget never boots. */\n hostBase?: string;\n}\n\nexport function readSessionLaunchParams(): SessionLaunchParams | null {\n if (typeof window === 'undefined') return null;\n const q = new URLSearchParams(window.location.search);\n // `tripId` is the historical param name the /w page passes; keep reading it\n // so hosts don't have to change. `slug` is accepted as the modern alias.\n const slug = q.get('slug') ?? q.get('tripId') ?? '';\n if (!slug) return null;\n // `mobile` is optional and has NO default: absent means the host predates\n // the PWA doors (or has none configured) and consumers must keep minting\n // legacy webBase links.\n const mobile = q.get('mobile');\n const webBase = (q.get('web') ?? 'http://localhost:14321').replace(/\\/$/, '');\n const host = q.get('host');\n return {\n slug,\n ticket: q.get('ticket'),\n apiBase: (q.get('api') ?? 'http://localhost:13002').replace(/\\/$/, ''),\n webBase,\n mobileBase: mobile ? mobile.replace(/\\/$/, '') : null,\n hostBase: host ? host.replace(/\\/$/, '') : webBase,\n };\n}\n\n/** The origin the handshake is addressed to: the framing page. */\nexport const hostOriginOf = (params: Pick<SessionLaunchParams, 'webBase' | 'hostBase'>): string =>\n new URL(params.hostBase ?? params.webBase).origin;\n\nconst SESSION_INIT_TIMEOUT_MS = 10_000;\nconst DEFAULT_POLL_INTERVAL_MS = 4_000;\n\n/** Announce readiness to the parent (host) page and wait for it to hand over\n * the single-use session ticket. `hostOrigin` is the framing page's origin\n * (`hostOriginOf`) \u2014 the web /w page or the mobile PWA's native lobby. */\nfunction awaitInitTicket(hostOrigin: string): Promise<string> {\n return new Promise((resolve, reject) => {\n if (typeof window === 'undefined' || window.parent === window) {\n reject(new Error('no parent page to receive a ticket from'));\n return;\n }\n const webOrigin = hostOrigin;\n const timer = setTimeout(() => {\n window.removeEventListener('message', onMessage);\n reject(new Error('timed out waiting for the session handoff'));\n }, SESSION_INIT_TIMEOUT_MS);\n const onMessage = (e: MessageEvent) => {\n if (e.origin !== webOrigin) return;\n const data = e.data as { type?: string; ticket?: string } | null;\n if (data?.type !== 'flowstate:init' || typeof data.ticket !== 'string') return;\n clearTimeout(timer);\n window.removeEventListener('message', onMessage);\n resolve(data.ticket);\n };\n window.addEventListener('message', onMessage);\n window.parent.postMessage({ type: 'flowstate:ready' }, webOrigin);\n });\n}\n\n// ============================================\n// Wire mapping\n// ============================================\n\ninterface WireMember {\n userId: string;\n name: string;\n color: string;\n sealed: boolean;\n isMe: boolean;\n isHost: boolean;\n}\n\ninterface WireSessionState {\n tripId: string;\n phase: 'setup' | 'wishlist' | 'sealed' | 'plan';\n status: string;\n setup: Record<string, unknown> | null;\n members: WireMember[];\n sealedCount: number;\n memberCount: number;\n allSealed: boolean;\n quorumMet?: boolean;\n gate?: SessionGateView | null;\n flow?: SessionFlowView | null;\n myWishlist: unknown;\n plan: Record<string, unknown> | null;\n artifactId: string | null;\n extension?: Record<string, unknown> | null;\n viewer?: { userId: string; guest: boolean; claims?: SessionClaimSet };\n /** Flow feedback: true only while the ring has the bug icon switched on. */\n feedbackEnabled?: boolean;\n}\n\nconst WIRE_PHASE: Record<WireSessionState['phase'], SessionPhase> = {\n setup: 'setup',\n wishlist: 'open',\n sealed: 'sealed',\n plan: 'resolved',\n};\n\n/** Exported for tests; not part of the package entry point. */\nexport function toStateView<TSetup, TContribution, TDoc>(\n s: WireSessionState\n): SessionStateView<TSetup, TContribution, TDoc> {\n return {\n slug: s.tripId,\n phase: WIRE_PHASE[s.phase] ?? 'open',\n status: s.status,\n setup: (s.setup as TSetup | null) ?? null,\n members: s.members,\n sealedCount: s.sealedCount,\n memberCount: s.memberCount,\n allSealed: s.allSealed,\n quorumMet: s.quorumMet ?? s.allSealed,\n gate: s.gate ?? null,\n flow: s.flow ?? null,\n myContribution: (s.myWishlist as TContribution | null) ?? null,\n doc: (s.plan as TDoc | null) ?? null,\n artifactId: s.artifactId,\n extension: s.extension ?? null,\n viewer: s.viewer\n ? { userId: s.viewer.userId, guest: s.viewer.guest, claims: s.viewer.claims ?? null }\n : null,\n };\n}\n\n// ============================================\n// HTTP client over the session rails\n// ============================================\n\ntype ExternalOpener = (url: string) => void;\n\nconst parentOpener = (hostOrigin: string): ExternalOpener => {\n return (url) => {\n if (typeof window !== 'undefined' && window.parent !== window) {\n // Ask the host page to navigate itself (the web /w page shares the\n // session cookie; the PWA lobby routes reveals natively).\n window.parent.postMessage({ type: 'flowstate:navigate', url }, hostOrigin);\n return;\n }\n if (typeof window !== 'undefined') window.open(url, '_blank', 'noopener');\n };\n};\n\nclass HttpSessionClient<TSetup, TContribution, TDoc> implements SessionClient<\n TSetup,\n TContribution,\n TDoc\n> {\n readonly slug: string;\n readonly webBase: string;\n readonly mobileBase: string | null;\n private latestClaims: SessionClaimSet | null = null;\n private readonly apiBase: string;\n private readonly tokenPromise: Promise<string>;\n private readonly opener: ExternalOpener;\n private readonly hostOrigin: string;\n\n constructor(params: SessionLaunchParams, opener?: ExternalOpener) {\n this.slug = params.slug;\n this.webBase = params.webBase;\n this.mobileBase = params.mobileBase ?? null;\n this.apiBase = params.apiBase;\n this.hostOrigin = hostOriginOf(params);\n this.opener = opener ?? parentOpener(hostOriginOf(params));\n // Redeem eagerly: the ticket is single-use + short-lived; trade it for a\n // session token before the user spends time in the widget.\n this.tokenPromise = this.acquireTicket(params).then((t) => this.redeem(t));\n }\n\n private async acquireTicket(params: SessionLaunchParams): Promise<string> {\n if (params.ticket) return params.ticket; // dev fallback\n return awaitInitTicket(hostOriginOf(params));\n }\n\n private async redeem(ticket: string): Promise<string> {\n const res = await fetch(`${this.apiBase}/api/auth/ticket/redeem`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ ticket }),\n });\n if (!res.ok) throw new Error(`ticket redeem failed (${res.status})`);\n const json = (await res.json()) as { data: { token: string } };\n return json.data.token;\n }\n\n private async call(\n path: string,\n init: RequestInit = {}\n ): Promise<SessionStateView<TSetup, TContribution, TDoc>> {\n const token = await this.tokenPromise;\n const res = await fetch(`${this.apiBase}/api/w/${this.slug}${path}`, {\n ...init,\n headers: {\n authorization: `Bearer ${token}`,\n ...(init.body ? { 'content-type': 'application/json' } : {}),\n ...(init.headers ?? {}),\n },\n });\n if (!res.ok) {\n const body = (await res.json().catch(() => ({}))) as { message?: string };\n throw new Error(body.message ?? `session service ${res.status}`);\n }\n const json = (await res.json()) as {\n data: WireSessionState | { state: WireSessionState };\n };\n const data = json.data as Record<string, unknown>;\n const wire = (data.state ?? data) as WireSessionState;\n const view = toStateView<TSetup, TContribution, TDoc>(wire);\n this.latestClaims = view.viewer?.claims ?? null;\n this.syncFeedback(wire.feedbackEnabled === true);\n return view;\n }\n\n get claims(): SessionClaimSet | null {\n return this.latestClaims;\n }\n\n /** Flow feedback: every state read says whether the bug icon is on for this ring. On, the\n * icon mounts once and re-reads the page's look on each read; off, it leaves. */\n private syncFeedback(enabled: boolean): void {\n if (typeof document === 'undefined') return;\n if (!enabled) {\n unmountFlowFeedback();\n return;\n }\n const handle = mountFlowFeedback({\n submit: (report) => this.request('feedback', report),\n onReady: () => {\n if (window.parent !== window)\n window.parent.postMessage({ type: 'flowstate:feedback-ready' }, this.hostOrigin);\n },\n });\n handle?.refresh();\n }\n\n join() {\n return this.call('/enter', { method: 'POST' });\n }\n\n state() {\n return this.call('/state');\n }\n\n poll(\n onState: (s: SessionStateView<TSetup, TContribution, TDoc>) => void,\n opts?: SessionPollOptions & { onError?: (err: unknown) => void }\n ): () => void {\n const interval = opts?.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n const tick = async () => {\n try {\n const s = await this.state();\n if (!stopped) onState(s);\n } catch (err) {\n opts?.onError?.(err);\n }\n if (!stopped) timer = setTimeout(tick, interval);\n };\n void tick();\n return () => {\n stopped = true;\n if (timer) clearTimeout(timer);\n };\n }\n\n commitSetup(setup: TSetup) {\n return this.call('/setup', { method: 'POST', body: JSON.stringify({ setup }) });\n }\n\n contribute(payload: TContribution) {\n return this.call('/contribute', { method: 'POST', body: JSON.stringify({ payload }) });\n }\n\n close() {\n return this.call('/close', { method: 'POST' });\n }\n\n action(verb: string, body?: unknown) {\n return this.call(`/${verb.replace(/^\\//, '')}`, {\n method: 'POST',\n body: JSON.stringify(body ?? {}),\n });\n }\n\n async request<T = unknown>(\n verb: string,\n body?: unknown,\n options?: { signal?: AbortSignal }\n ): Promise<T> {\n const signal = options?.signal;\n if (signal?.aborted) {\n // Observe eager redemption even when this caller never waits for it.\n // Retain the original promise so other callers still receive its failure.\n void this.tokenPromise.catch(() => {});\n signal.throwIfAborted();\n }\n let onAbort: (() => void) | undefined;\n let token: string;\n try {\n token = await (signal\n ? Promise.race([\n this.tokenPromise,\n new Promise<never>((_resolve, reject) => {\n onAbort = () => reject(signal.reason);\n signal.addEventListener('abort', onAbort, { once: true });\n }),\n ])\n : this.tokenPromise);\n } finally {\n if (signal && onAbort) signal.removeEventListener('abort', onAbort);\n }\n const res = await fetch(`${this.apiBase}/api/w/${this.slug}/${verb.replace(/^\\//, '')}`, {\n signal,\n method: 'POST',\n headers: { authorization: `Bearer ${token}`, 'content-type': 'application/json' },\n body: JSON.stringify(body ?? {}),\n });\n if (!res.ok) {\n const errBody = (await res.json().catch(() => {\n signal?.throwIfAborted();\n return {};\n })) as { message?: string };\n throw new Error(errBody.message ?? `session service ${res.status}`);\n }\n const json = (await res.json()) as { data: T };\n return json.data;\n }\n\n openExternal(url: string): void {\n this.opener(url);\n }\n\n revealUrl(artifactId: string): string {\n // FB-133: honour the PWA base like the wall's take-home QR does \u2014 a\n // phone-born session's reveal opens in the PWA, not the legacy web page.\n return this.mobileBase\n ? `${this.mobileBase}/?a=${artifactId}`\n : `${this.webBase}/a/${artifactId}`;\n }\n}\n\n// ============================================\n// Standalone mock (widget dev outside the canvas)\n// ============================================\n\nexport interface MockSessionOptions<TSetup, TDoc> {\n /** Display name for the single mock member. Default \"You\". */\n me?: string;\n /** Extra fake members (all unsealed initially). */\n others?: string[];\n setup?: TSetup | null;\n /** Claims the mock viewer carries (shell-guest dev). Default null. */\n claims?: SessionClaimSet | null;\n /** Called on close() to synthesize the mock doc. */\n synthesize?: (contributions: unknown[]) => TDoc;\n}\n\n/** In-memory SessionClient for standalone dev \u2014 same interface, no host, no\n * network. The mock's \"host\" is always you. */\nexport function createMockSession<\n TSetup = Record<string, unknown>,\n TContribution = unknown,\n TDoc = Record<string, unknown>,\n>(opts: MockSessionOptions<TSetup, TDoc> = {}): SessionClient<TSetup, TContribution, TDoc> {\n const palette = ['#8b7ef8', '#f8a34d', '#4dc9a4', '#f26d8f', '#5aa9e6'];\n const members: SessionMember[] = [\n {\n userId: 'mock-me',\n name: opts.me ?? 'You',\n color: palette[0],\n sealed: false,\n isMe: true,\n isHost: true,\n },\n ...(opts.others ?? []).map((name, i) => ({\n userId: `mock-${i + 1}`,\n name,\n color: palette[(i + 1) % palette.length],\n sealed: false,\n isMe: false,\n isHost: false,\n })),\n ];\n let setup: TSetup | null = opts.setup ?? null;\n let myContribution: TContribution | null = null;\n let doc: TDoc | null = null;\n let status = 'open';\n\n const view = (): SessionStateView<TSetup, TContribution, TDoc> => {\n const sealedCount = members.filter((m) => m.sealed).length;\n const allSealed = members.length > 0 && sealedCount >= members.length;\n const phase: SessionPhase =\n status === 'closed' && doc ? 'resolved' : !setup ? 'setup' : allSealed ? 'sealed' : 'open';\n return {\n slug: 'mock-session',\n phase,\n status,\n setup,\n members: members.map((m) => ({ ...m })),\n sealedCount,\n memberCount: members.length,\n allSealed,\n quorumMet: allSealed,\n gate: null,\n flow: null,\n myContribution,\n doc,\n artifactId: doc ? 'mock-artifact' : null,\n extension: null,\n viewer: { userId: 'mock-me', guest: false, claims: opts.claims ?? null },\n };\n };\n\n return {\n slug: 'mock-session',\n webBase: 'http://localhost:14321',\n claims: opts.claims ?? null,\n async join() {\n return view();\n },\n async state() {\n return view();\n },\n poll(onState, pollOpts) {\n const interval = pollOpts?.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n const timer = setInterval(() => onState(view()), interval);\n onState(view());\n return () => clearInterval(timer);\n },\n async commitSetup(s: TSetup) {\n setup = s;\n return view();\n },\n async contribute(payload: TContribution) {\n myContribution = payload;\n const me = members.find((m) => m.isMe);\n if (me) me.sealed = true;\n return view();\n },\n async close() {\n status = 'closed';\n doc = opts.synthesize\n ? opts.synthesize([myContribution])\n : ({ mock: true } as unknown as TDoc);\n return view();\n },\n async action() {\n return view();\n },\n async request<T = unknown>() {\n return {} as T;\n },\n openExternal(url: string) {\n if (typeof window !== 'undefined') window.open(url, '_blank', 'noopener');\n },\n revealUrl(artifactId: string) {\n return `http://localhost:14321/a/${artifactId}`;\n },\n };\n}\n\n// ============================================\n// Entry point\n// ============================================\n\n/**\n * Initialize a widget-session client from the launch context.\n *\n * Returns `null` when the page carries no session launch params (standalone\n * dev, or a canvas-hosted widget that has no group session) \u2014 callers fall\n * back to {@link createMockSession} or their own local state.\n */\nexport function initSession<\n TSetup = Record<string, unknown>,\n TContribution = unknown,\n TDoc = Record<string, unknown>,\n>(): SessionClient<TSetup, TContribution, TDoc> | null {\n const params = readSessionLaunchParams();\n if (!params) return null;\n return new HttpSessionClient<TSetup, TContribution, TDoc>(params);\n}\n", "// ============================================\n// Canvas Widget SDK \u2014 runs inside widget iframes\n// ============================================\n//\n// Usage in a widget:\n// <script src=\"canvas-widget-sdk.js\"></script>\n// <script>\n// const widget = await CanvasWidget.init();\n// widget.onStateChange(state => { ... });\n// </script>\n\nimport type {\n CanvasWidgetSDK,\n WidgetDisplayState,\n WidgetEvent,\n InitPayload,\n StateChangePayload,\n EventBroadcastPayload,\n ConsentResult,\n ConsentResultPayload,\n UserContext,\n StorageAPI,\n SettingsAPI,\n WidgetServiceClient,\n AiAPI,\n AiCallOptions,\n AiCompleteResult,\n AiStreamEvent,\n DbAPI,\n DbCollection,\n DbRecord,\n DbListOptions,\n} from './types';\nimport { createServiceClient } from './service';\nimport { initSession } from './session';\n\nconst PROTOCOL = 'canvas-widget-v1';\nconst DEV_MODE_TOKEN = 'dev-mode-skip';\nconst INIT_TIMEOUT_MS = 5000;\n\n// crypto.randomUUID only exists in SECURE contexts (HTTPS or localhost). When\n// a widget iframe is served over plain http on a LAN IP (e.g. dev-on-phone at\n// http://10.0.0.x), it's undefined and init() throws \u2014 the widget never\n// hands shakes and the host times it out (\"failed to load\"). Fall back to a\n// getRandomValues-based v4 UUID (also secure-context-independent).\nfunction safeRandomUUID(): string {\n const c = globalThis.crypto;\n if (c && typeof c.randomUUID === 'function') return c.randomUUID();\n const b = new Uint8Array(16);\n if (c && typeof c.getRandomValues === 'function') c.getRandomValues(b);\n else for (let i = 0; i < 16; i++) b[i] = Math.floor(Math.random() * 256);\n b[6] = (b[6] & 0x0f) | 0x40;\n b[8] = (b[8] & 0x3f) | 0x80;\n const h = Array.from(b, (x) => x.toString(16).padStart(2, '0'));\n return `${h[0]}${h[1]}${h[2]}${h[3]}-${h[4]}${h[5]}-${h[6]}${h[7]}-${h[8]}${h[9]}-${h[10]}${h[11]}${h[12]}${h[13]}${h[14]}${h[15]}`;\n}\n\ninterface CanvasMessage {\n protocol: string;\n type: string;\n widgetId: string;\n nonce: string;\n timestamp: number;\n token: string;\n payload: Record<string, unknown>;\n}\n\n// ============================================\n// HMAC utilities (mirrored from host side)\n// ============================================\n\nlet cachedKey: { secret: string; key: CryptoKey } | null = null;\n\nasync function getHmacKey(secret: string): Promise<CryptoKey> {\n if (cachedKey && cachedKey.secret === secret) return cachedKey.key;\n\n const encoded = new TextEncoder().encode(secret);\n const buf = new ArrayBuffer(encoded.byteLength);\n new Uint8Array(buf).set(encoded);\n\n const key = await crypto.subtle.importKey('raw', buf, { name: 'HMAC', hash: 'SHA-256' }, false, [\n 'sign',\n ]);\n\n cachedKey = { secret, key };\n return key;\n}\n\nasync function signToken(\n secret: string,\n type: string,\n widgetId: string,\n nonce: string,\n timestamp: number\n): Promise<string> {\n const key = await getHmacKey(secret);\n const data = new TextEncoder().encode(`${type}:${widgetId}:${nonce}:${timestamp}`);\n const buf = new ArrayBuffer(data.byteLength);\n new Uint8Array(buf).set(data);\n const sig = await crypto.subtle.sign('HMAC', key, buf);\n return Array.from(new Uint8Array(sig))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\n// ============================================\n// SDK Implementation\n// ============================================\n\n/** Internal SDK implementation \u2014 not exposed directly to widget authors. */\nexport class CanvasWidgetSDKImpl implements CanvasWidgetSDK {\n private _widgetId: string;\n private _state: WidgetDisplayState;\n private _hmacSecret: string | null;\n private _user: UserContext | null;\n private _namespace: { officeId: string; memberCount: number } | null;\n private _widgetApiUrl: string | null;\n private _widgetApiToken: string | null;\n private _installationId: string;\n private _pendingConsents = new Map<\n string,\n { resolve: (r: ConsentResult) => void; reject: (e: Error) => void }\n >();\n private _cachedJwt: { token: string; expiresAt: number } | null = null;\n private _stateListeners: Array<(state: WidgetDisplayState) => void> = [];\n private _displayState: WidgetDisplayState = 'collapsed';\n private _displayStateCallbacks: Array<(state: WidgetDisplayState) => void> = [];\n private _focused = false;\n private _focusCallbacks: Array<(focused: boolean) => void> = [];\n private _settingsRemoteListeners: Array<(settings: Record<string, unknown>) => void> = [];\n private _eventListeners = new Map<string, Array<(event: WidgetEvent) => void>>();\n private _settings: Record<string, unknown>;\n private _messageHandler: (event: MessageEvent) => void;\n\n /** Authenticated HTTP client scoped to the widget's declared service URL. Undefined when no service is declared. */\n readonly service: WidgetServiceClient | undefined;\n\n constructor(init: InitPayload) {\n this._widgetId = init.widgetId;\n this._state = init.state;\n this._hmacSecret = init.hmacSecret;\n this._user = init.user;\n this._namespace = init.namespace ?? null;\n this._widgetApiUrl = init.widgetApiUrl;\n this._widgetApiToken = init.widgetApiToken ?? null;\n this._installationId = init.installationId;\n this._settings = init.settings ?? {};\n\n // Wire sdk.service when the host provides a service URL from the manifest\n if (init.serviceUrl) {\n this.service = createServiceClient({\n baseUrl: init.serviceUrl,\n getJwt: (forceRefresh?: boolean) => this.getJwt(forceRefresh),\n });\n }\n\n this._messageHandler = this.handleMessage.bind(this);\n window.addEventListener('message', this._messageHandler);\n }\n\n /** The unique identifier for this widget instance on the canvas. */\n get widgetId(): string {\n return this._widgetId;\n }\n\n /** The current display state of the widget (collapsed, expanding, expanded, collapsing). */\n get state(): WidgetDisplayState {\n return this._state;\n }\n\n /** The authenticated user context, or null when user data access has not been granted. */\n get user(): UserContext | null {\n return this._user;\n }\n\n /** Namespace (office) context \u2014 id and owner-inclusive member count \u2014 or\n * null on a personal canvas. What the `canvas.context` permission grants. */\n get namespace(): { officeId: string; memberCount: number } | null {\n return this._namespace;\n }\n\n /** Subscribe to display state changes; returns an unsubscribe function. */\n onStateChange(cb: (state: WidgetDisplayState) => void): () => void {\n this._stateListeners.push(cb);\n return () => {\n const idx = this._stateListeners.indexOf(cb);\n if (idx !== -1) this._stateListeners.splice(idx, 1);\n };\n }\n\n /** Ask the canvas host to expand this widget into its modal view. */\n requestExpand(): void {\n this.send('EXPAND_REQUEST', {});\n }\n\n /** Ask the canvas host to collapse this widget back to its tile view. */\n requestCollapse(): void {\n this.send('COLLAPSE_REQUEST', {});\n }\n\n /** Get the current display state */\n getDisplayState(): WidgetDisplayState {\n return this._displayState;\n }\n\n /** Register a callback for display state changes */\n onDisplayStateChange(callback: (state: WidgetDisplayState) => void): () => void {\n this._displayStateCallbacks.push(callback);\n return () => {\n this._displayStateCallbacks = this._displayStateCallbacks.filter((cb) => cb !== callback);\n };\n }\n\n /** Is this widget the user's current focus? Updates via FOCUS_CHANGE. */\n isFocused(): boolean {\n return this._focused;\n }\n\n /** Subscribe to focus transitions. Returns an unsubscribe function. */\n onFocusChange(callback: (focused: boolean) => void): () => void {\n this._focusCallbacks.push(callback);\n return () => {\n this._focusCallbacks = this._focusCallbacks.filter((cb) => cb !== callback);\n };\n }\n\n /** Request promotion to fullscreen from partial */\n requestFullscreen(): void {\n this.send('FULLSCREEN_REQUEST', {});\n }\n\n /** Broadcast an event on the given topic to all subscribed widgets on the canvas. */\n emit(topic: string, payload: unknown): void {\n this.send('EVENT_EMIT', { topic, payload });\n }\n\n /** Subscribe to events on the given topic; returns an unsubscribe function. */\n subscribe(topic: string, cb: (event: WidgetEvent) => void): () => void {\n const list = this._eventListeners.get(topic) ?? [];\n list.push(cb);\n this._eventListeners.set(topic, list);\n return () => {\n const current = this._eventListeners.get(topic);\n if (!current) return;\n const idx = current.indexOf(cb);\n if (idx !== -1) current.splice(idx, 1);\n };\n }\n\n /** Send a structured log entry to the canvas host for centralized debugging. */\n log(level: 'info' | 'warn' | 'error', message: string, data?: unknown): void {\n this.send('LOG', { level, message, data: data ?? null });\n }\n\n /** Request user consent for a named data scope; resolves with the consent decision. */\n async requestConsent(scope: string, reason: string): Promise<ConsentResult> {\n return new Promise<ConsentResult>((resolve, reject) => {\n const timeout = setTimeout(() => {\n this._pendingConsents.delete(scope);\n reject(new Error('Consent request timed out'));\n }, 60000);\n\n this._pendingConsents.set(scope, {\n resolve: (r: ConsentResult) => {\n clearTimeout(timeout);\n resolve(r);\n },\n reject: (e: Error) => {\n clearTimeout(timeout);\n reject(e);\n },\n });\n\n this.send('CONSENT_REQUEST', { scope, reason });\n });\n }\n\n /** Return the cached JWT for authenticating requests to external APIs; throws if no token is available. Pass forceRefresh=true to bypass the cache. */\n async getJwt(forceRefresh?: boolean): Promise<string> {\n if (!forceRefresh && this._cachedJwt && Date.now() < this._cachedJwt.expiresAt - 30000) {\n return this._cachedJwt.token;\n }\n throw new Error('No cached JWT \u2014 widget must use OAuth flow for token refresh');\n }\n\n /** Persistent key-value storage scoped to this widget installation. */\n get storage(): StorageAPI {\n return {\n get: (key: string) => this.storageRequest('GET', key),\n set: (key: string, value: unknown) => this.storageRequest('PUT', key, value).then(() => {}),\n delete: (key: string) => this.storageRequest('DELETE', key).then(() => {}),\n };\n }\n\n /** Ad-hoc collections/records backend scoped to the canvas/room. */\n get db(): DbAPI {\n const req = (path: string, method: string, body?: unknown) => this.widgetApiRequest(path, method, body);\n return {\n collection(name: string): DbCollection {\n const base = `/widget-api/db/${encodeURIComponent(name)}`;\n return {\n create: (data) => req(base, 'POST', data) as Promise<DbRecord>,\n get: (id) => req(`${base}/${encodeURIComponent(id)}`, 'GET') as Promise<DbRecord>,\n list: (opts?: DbListOptions) => {\n const qs = new URLSearchParams();\n if (opts?.limit != null) qs.set('limit', String(opts.limit));\n if (opts?.cursor) qs.set('cursor', opts.cursor);\n if (opts?.sort) qs.set('sort', opts.sort);\n if (opts?.mine) qs.set('mine', 'true');\n const suffix = qs.toString() ? `?${qs.toString()}` : '';\n return req(`${base}${suffix}`, 'GET') as Promise<DbRecord[]>;\n },\n update: (id, patch) => req(`${base}/${encodeURIComponent(id)}`, 'PATCH', patch) as Promise<DbRecord>,\n delete: (id) => (req(`${base}/${encodeURIComponent(id)}`, 'DELETE') as Promise<unknown>).then(() => {}),\n };\n },\n };\n }\n\n /** Platform LLM access (widget.* aliases only). Requests ride the same\n * widget-gateway auth as db/storage; the platform meters every call and\n * attributes it to this widget's publishing org. */\n get ai(): AiAPI {\n const complete = (opts: AiCallOptions): Promise<AiCompleteResult> =>\n this.widgetApiRequest('/widget-api/ai/complete', 'POST', opts) as Promise<AiCompleteResult>;\n const streamRequest = (opts: AiCallOptions): Promise<Response> => this.widgetApiStream(opts);\n return {\n complete,\n // Async generator over the gateway's SSE frames. Ends on [DONE] or\n // stream end; returning early (break) cancels the fetch body, which\n // the gateway sees as a disconnect and aborts the upstream call, so\n // an abandoned stream stops generating and billing.\n async *stream(opts: AiCallOptions): AsyncGenerator<AiStreamEvent> {\n const response = await streamRequest(opts);\n const reader = response.body?.getReader();\n if (!reader) throw new Error('ai.stream: response has no body');\n const decoder = new TextDecoder();\n let buffer = '';\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const frames = buffer.split('\\n\\n');\n buffer = frames.pop() ?? '';\n for (const frame of frames) {\n const dataLine = frame.split('\\n').find((l) => l.startsWith('data:'));\n if (!dataLine) continue;\n const payload = dataLine.slice(5).trim();\n if (!payload || payload === '[DONE]') continue;\n let parsed: AiStreamEvent;\n try {\n parsed = JSON.parse(payload) as AiStreamEvent;\n } catch {\n continue;\n }\n yield parsed;\n }\n }\n } finally {\n // Break/return path: releases the connection so the gateway's\n // req.on('close') fires and the upstream call aborts.\n await reader.cancel().catch(() => {});\n }\n },\n };\n }\n\n /** Widget configuration settings persisted by the canvas host. */\n get settings(): SettingsAPI {\n return {\n get: () => ({ ...this._settings }),\n set: (newSettings: Record<string, unknown>) => {\n this._settings = { ...this._settings, ...newSettings };\n this.send('SETTINGS_WRITE', { settings: this._settings });\n },\n onRemoteChange: (cb: (settings: Record<string, unknown>) => void) => {\n this._settingsRemoteListeners.push(cb);\n return () => {\n this._settingsRemoteListeners = this._settingsRemoteListeners.filter((fn) => fn !== cb);\n };\n },\n };\n }\n\n /** Display a toast notification in the canvas host UI. */\n async notify(message: string, opts?: { level?: string; duration?: number }): Promise<void> {\n this.send('NOTIFICATION', {\n message,\n level: opts?.level ?? 'info',\n duration: opts?.duration ?? 5000,\n });\n }\n\n // \u2500\u2500 Private \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Raw streaming POST against the widget gateway \u2014 same auth as\n * widgetApiRequest, but returns the Response for SSE consumption\n * instead of parsing a JSON envelope. */\n private async widgetApiStream(body: unknown): Promise<Response> {\n if (!this._widgetApiUrl) {\n throw new Error('Widget API Gateway URL not available');\n }\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (this._widgetApiToken) {\n headers['Authorization'] = `Bearer ${this._widgetApiToken}`;\n }\n const response = await fetch(`${this._widgetApiUrl}/widget-api/ai/stream`, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n const err = await response.json().catch(() => ({}));\n throw new Error((err as Record<string, string>).message ?? 'ai.stream request failed');\n }\n return response;\n }\n\n private async widgetApiRequest(path: string, method: string, body?: unknown): Promise<unknown> {\n if (!this._widgetApiUrl) {\n throw new Error('Widget API Gateway URL not available');\n }\n const url = `${this._widgetApiUrl}${path}`;\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (this._widgetApiToken) {\n headers['Authorization'] = `Bearer ${this._widgetApiToken}`;\n }\n const opts: RequestInit = { method, headers };\n if (body !== undefined && method !== 'GET' && method !== 'DELETE') {\n opts.body = JSON.stringify(body);\n }\n const response = await fetch(url, opts);\n if (!response.ok) {\n const err = await response.json().catch(() => ({}));\n throw new Error((err as Record<string, string>).message ?? `Request ${method} ${path} failed`);\n }\n if (method === 'GET' || method === 'POST' || method === 'PATCH') {\n const result = await response.json();\n return (result as Record<string, unknown>).data;\n }\n return undefined;\n }\n\n private storageRequest(method: string, key: string, value?: unknown): Promise<unknown> {\n return this.widgetApiRequest(`/widget-api/storage/${encodeURIComponent(key)}`, method, value);\n }\n\n private handleMessage(event: MessageEvent): void {\n const msg = event.data;\n if (!msg || typeof msg !== 'object' || msg.protocol !== PROTOCOL) return;\n\n switch (msg.type) {\n case 'STATE_CHANGE': {\n const payload = msg.payload as StateChangePayload;\n this._state = payload.state;\n for (const listener of this._stateListeners) {\n listener(payload.state);\n }\n break;\n }\n case 'EVENT_BROADCAST': {\n const payload = msg.payload as EventBroadcastPayload;\n const listeners = this._eventListeners.get(payload.topic);\n if (listeners) {\n const evt: WidgetEvent = {\n topic: payload.topic,\n payload: payload.payload,\n emittedAt: payload.emittedAt,\n };\n for (const listener of listeners) {\n listener(evt);\n }\n }\n break;\n }\n case 'CONSENT_RESULT': {\n const payload = msg.payload as ConsentResultPayload;\n const pending = this._pendingConsents.get(payload.scope);\n if (pending) {\n this._pendingConsents.delete(payload.scope);\n pending.resolve({\n scope: payload.scope,\n granted: payload.granted,\n grantToken: payload.grantToken,\n expiresIn: payload.expiresIn,\n });\n }\n break;\n }\n case 'AUTH_TOKEN_REFRESH': {\n const payload = msg.payload as { token: string; expiresIn: number };\n this._cachedJwt = {\n token: payload.token,\n expiresAt: Date.now() + payload.expiresIn * 1000,\n };\n break;\n }\n case 'DISPLAY_STATE_CHANGE': {\n const newState = msg.payload?.displayState as WidgetDisplayState;\n if (newState) {\n this._displayState = newState;\n this._displayStateCallbacks.forEach((cb) => cb(newState));\n }\n break;\n }\n case 'FOCUS_CHANGE': {\n const next = !!msg.payload?.focused;\n if (next !== this._focused) {\n this._focused = next;\n this._focusCallbacks.forEach((cb) => cb(next));\n }\n break;\n }\n case 'SETTINGS_REMOTE_UPDATE': {\n const p = msg.payload as { settings: Record<string, unknown> };\n this._settings = { ...p.settings };\n for (const fn of this._settingsRemoteListeners) {\n try {\n fn({ ...this._settings });\n } catch {\n // a listener throwing must not break message handling\n }\n }\n break;\n }\n case 'USER_CONTEXT':\n case 'CANVAS_CONTEXT':\n // Phase 3+ \u2014 reserved for future use\n break;\n }\n }\n\n private send(type: string, payload: Record<string, unknown>): void {\n // Fire and forget \u2014 sign is async but we don't need to await delivery\n this.buildMessage(type, payload).then((msg) => {\n window.parent.postMessage(msg, '*');\n });\n }\n\n private async buildMessage(\n type: string,\n payload: Record<string, unknown>\n ): Promise<CanvasMessage> {\n const nonce = safeRandomUUID();\n const timestamp = Date.now();\n\n let token: string;\n if (this._hmacSecret) {\n token = await signToken(this._hmacSecret, type, this._widgetId, nonce, timestamp);\n } else {\n token = DEV_MODE_TOKEN;\n }\n\n return {\n protocol: PROTOCOL,\n type,\n widgetId: this._widgetId,\n nonce,\n timestamp,\n token,\n payload,\n };\n }\n\n /** Tear down the SDK instance, remove all listeners, and reject pending consent requests. */\n destroy(): void {\n window.removeEventListener('message', this._messageHandler);\n this._stateListeners.length = 0;\n this._eventListeners.clear();\n for (const [, pending] of this._pendingConsents) {\n pending.reject(new Error('Widget destroyed'));\n }\n this._pendingConsents.clear();\n }\n}\n\n// ============================================\n// Public API: CanvasWidget.init()\n// ============================================\n\n/**\n * Entry point for the Flowstate Canvas Widget SDK.\n *\n * Usage:\n * ```ts\n * import { CanvasWidget } from '@goflowstate/widget-sdk';\n * const widget = await CanvasWidget.init();\n * ```\n */\nexport const CanvasWidget = {\n /**\n * Initialize a group-session client for widgets hosted on the /w/[slug]\n * session page (ticket handshake + session REST rails). Returns null when\n * the launch context carries no session params \u2014 see `initSession` in\n * ./session for details and `createMockSession` for standalone dev.\n */\n initSession,\n\n /**\n * Initialize the SDK and establish communication with the canvas host.\n *\n * Sends a READY message to the host and waits for an INIT response.\n * Resolves with a fully initialized {@link CanvasWidgetSDK} instance.\n * Rejects after 5 seconds if no INIT is received from the host.\n */\n init(): Promise<CanvasWidgetSDK> {\n return new Promise<CanvasWidgetSDK>((resolve, reject) => {\n const timeout = setTimeout(() => {\n window.removeEventListener('message', handler);\n reject(new Error('CanvasWidget.init() timed out \u2014 no INIT received from host within 5s'));\n }, INIT_TIMEOUT_MS);\n\n const handler = (event: MessageEvent) => {\n const msg = event.data;\n if (!msg || typeof msg !== 'object' || msg.protocol !== PROTOCOL) return;\n if (msg.type !== 'INIT') return;\n\n clearTimeout(timeout);\n window.removeEventListener('message', handler);\n\n const payload = msg.payload as InitPayload;\n const sdk = new CanvasWidgetSDKImpl(payload);\n resolve(sdk);\n };\n\n window.addEventListener('message', handler);\n\n // Send READY to host \u2014 the host will respond with INIT\n const readyMsg: CanvasMessage = {\n protocol: PROTOCOL,\n type: 'READY',\n widgetId: '', // Not yet known \u2014 host identifies us by iframe source\n nonce: safeRandomUUID(),\n timestamp: Date.now(),\n token: DEV_MODE_TOKEN, // READY is always allowed, token doesn't matter\n payload: { manifestVersion: '1' },\n };\n\n window.parent.postMessage(readyMsg, '*');\n });\n },\n};\n", "// ============================================\n// Widget theme utilities \u2014 the Flowstate Theme Contract (ADR-048)\n// ============================================\n//\n// Flowstate has exactly three themes: 'light' | 'dark' | 'glass'.\n// Glass is the signature default (the liquid-glass look) \u2014 widgets treat it\n// as the unthemed plane. The host hands the active theme to external iframe\n// widgets in two ways:\n//\n// 1. Boot: a `?theme=` query param appended to the iframe src.\n// 2. Live: a `postMessage` of `{ type: 'flowstate:theme', theme }`.\n//\n// Widgets map the theme onto their own tokens by setting `data-theme` on\n// their <html> element ('glass' removes the attribute, so widget CSS keeps\n// the glass plane as its default styles).\n//\n// Usage in a widget entry:\n// import { initWidgetTheme } from '@goflowstate/widget-sdk';\n// initWidgetTheme(); // apply boot theme + follow live host updates\n\nexport type WidgetTheme = 'light' | 'dark' | 'glass';\n\nconst THEME_MESSAGE_TYPE = 'flowstate:theme';\nconst DEFAULT_THEME: WidgetTheme = 'glass';\n\nfunction isWidgetTheme(value: unknown): value is WidgetTheme {\n return value === 'light' || value === 'dark' || value === 'glass';\n}\n\n/**\n * Read the theme the host handed the widget at boot via the `?theme=` URL\n * param. Invalid or missing values fall back to 'glass' (the default plane).\n */\nexport function getInitialTheme(): WidgetTheme {\n if (typeof window === 'undefined') return DEFAULT_THEME;\n try {\n const param = new URLSearchParams(window.location.search).get('theme');\n return isWidgetTheme(param) ? param : DEFAULT_THEME;\n } catch {\n return DEFAULT_THEME;\n }\n}\n\n/**\n * Subscribe to live theme updates pushed by the host\n * (`postMessage({ type: 'flowstate:theme', theme })`). Non-theme messages\n * and invalid theme values are ignored. Returns an unsubscribe function.\n *\n * Only messages from the embedding host page (`window.parent`) are accepted,\n * mirroring the ingest channel; a hostile sibling frame cannot restyle the\n * widget. Pass `allowedOrigin` to additionally pin the host's origin.\n */\nexport function onThemeChange(\n cb: (theme: WidgetTheme) => void,\n opts?: { allowedOrigin?: string }\n): () => void {\n if (typeof window === 'undefined') return () => {};\n const listener = (event: MessageEvent): void => {\n if (event.source !== window.parent) return;\n if (opts?.allowedOrigin && event.origin !== opts.allowedOrigin) return;\n const data: unknown = event.data;\n if (typeof data !== 'object' || data === null) return;\n const message = data as { type?: unknown; theme?: unknown };\n if (message.type !== THEME_MESSAGE_TYPE) return;\n if (!isWidgetTheme(message.theme)) return;\n cb(message.theme);\n };\n window.addEventListener('message', listener);\n return () => window.removeEventListener('message', listener);\n}\n\n/**\n * Apply a theme to the widget document by setting `data-theme` on <html>.\n * 'glass' REMOVES the attribute \u2014 widget CSS treats glass as the default\n * plane, with `[data-theme='light']` / `[data-theme='dark']` overrides.\n */\nexport function applyWidgetTheme(theme: WidgetTheme): void {\n if (typeof document === 'undefined') return;\n if (theme === 'glass') {\n delete document.documentElement.dataset.theme;\n } else {\n document.documentElement.dataset.theme = theme;\n }\n}\n\n/**\n * Convenience boot helper: apply the initial (`?theme=`) theme, then follow\n * live host updates, applying each. Returns the unsubscribe function.\n */\nexport function initWidgetTheme(): () => void {\n applyWidgetTheme(getInitialTheme());\n return onThemeChange(applyWidgetTheme);\n}\n", "// ============================================\n// Widget ingest channel \u2014 ADR-043 in-place delivery\n// ============================================\n//\n// When an artifact is dropped on (or sent to) a widget and the ingestion\n// transform produces a result, the HOST pushes the produced artifact into\n// any open iframe of the target widget:\n//\n// postMessage({ type: 'flowstate:ingest', artifactId, kind?, payload? })\n//\n// `payload` is the produced artifact's parsed JSON content when the host\n// could fetch it (small structured artifacts like kanban boards); widgets\n// without a payload can show an affordance around `artifactId` instead.\n// Same channel shape as the theme handoff (`flowstate:theme`).\n//\n// Usage in a widget entry:\n// import { onArtifactIngested } from '@goflowstate/widget-sdk';\n// onArtifactIngested(({ payload }) => mergeBoard(payload));\n\nexport const INGEST_MESSAGE_TYPE = 'flowstate:ingest';\n\nexport interface IngestedArtifact {\n /** The produced artifact's id (host route: /a/:id). */\n artifactId: string;\n /** The produced artifact kind (e.g. 'kanban-board'), when known. */\n kind?: string;\n /** Parsed JSON content of the produced artifact, when the host fetched it. */\n payload?: unknown;\n}\n\n/**\n * Subscribe to ingestion results pushed by the host. Non-ingest messages and\n * malformed payloads are ignored. Returns an unsubscribe function.\n *\n * Only messages from the embedding host page (`window.parent`) are accepted \u2014\n * a hostile sibling frame or opener cannot inject into the channel. Pass\n * `allowedOrigin` to additionally pin the host's origin (the session module\n * derives the same value from `webBase`).\n */\nexport function onArtifactIngested(\n cb: (artifact: IngestedArtifact) => void,\n opts?: { allowedOrigin?: string }\n): () => void {\n if (typeof window === 'undefined') return () => {};\n const listener = (event: MessageEvent): void => {\n if (event.source !== window.parent) return;\n if (opts?.allowedOrigin && event.origin !== opts.allowedOrigin) return;\n const data: unknown = event.data;\n if (typeof data !== 'object' || data === null) return;\n const message = data as { type?: unknown; artifactId?: unknown; kind?: unknown; payload?: unknown };\n if (message.type !== INGEST_MESSAGE_TYPE) return;\n if (typeof message.artifactId !== 'string' || !message.artifactId) return;\n cb({\n artifactId: message.artifactId,\n kind: typeof message.kind === 'string' ? message.kind : undefined,\n payload: message.payload,\n });\n };\n window.addEventListener('message', listener);\n return () => window.removeEventListener('message', listener);\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;AAUO,SAAS,oBAAoB,MAAuD;AACzF,QAAM,YAAY,KAAK,aAAa,WAAW,MAAM,KAAK,UAAU;AACpE,QAAM,UAAU,KAAK,QAAQ,QAAQ,QAAQ,EAAE;AAE/C,iBAAe,KAAK,MAAc,MAAmB,OAAkC;AACrF,UAAM,MAAM,GAAG,OAAO,GAAG,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI,IAAI,EAAE;AACjE,UAAM,UAAU,IAAI,QAAQ,KAAK,OAAO;AACxC,YAAQ,IAAI,iBAAiB,UAAU,KAAK,EAAE;AAC9C,QAAI,KAAK,QAAQ,CAAC,QAAQ,IAAI,cAAc,GAAG;AAC7C,cAAQ,IAAI,gBAAgB,kBAAkB;AAAA,IAChD;AACA,WAAO,UAAU,KAAK,EAAE,GAAG,MAAM,QAAQ,CAAC;AAAA,EAC5C;AAEA,SAAO;AAAA,IACL,MAAM,MAAmB,MAAc,OAAoB,CAAC,GAAe;AACzE,UAAI,QAAQ,MAAM,KAAK,OAAO,KAAK;AACnC,UAAI,MAAM,MAAM,KAAK,MAAM,MAAM,KAAK;AAEtC,UAAI,IAAI,WAAW,KAAK;AACtB,gBAAQ,MAAM,KAAK,OAAO,IAAI;AAC9B,cAAM,MAAM,KAAK,MAAM,MAAM,KAAK;AAAA,MACpC;AAEA,UAAI,CAAC,IAAI,IAAI;AACX,YAAI,OAA6B,CAAC;AAClC,YAAI;AACF,iBAAQ,MAAM,IAAI,KAAK;AAAA,QACzB,QAAQ;AAAA,QAER;AACA,cAAM,IAAI,MAAM,kCAAkC,IAAI,MAAM,MAAM,KAAK,WAAW,EAAE,EAAE;AAAA,MACxF;AACA,aAAQ,MAAM,IAAI,KAAK;AAAA,IACzB;AAAA,EACF;AACF;;;ACaO,IAAM,qBAAqB;AAE3B,IAAM,uBAAuB;AAC7B,IAAM,sBAAsB;AACnC,IAAM,qBAAqB;AAMpB,SAAS,WAAW,KAA6C;AACtE,QAAM,SAAS,OAAO,IAAI,KAAK,EAAE,YAAY;AAC7C,MAAI,CAAC,SAAS,UAAU,cAAe,QAAO;AAC9C,QAAM,MAAM,qBAAqB,KAAK,KAAK;AAC3C,MAAI,KAAK;AACP,UAAM,IAAI,IAAI,CAAC;AACf,UAAM,OACJ,EAAE,UAAU,IACR,EACG,MAAM,EAAE,EACR,IAAI,CAAC,MAAM,IAAI,CAAC,EAChB,KAAK,EAAE,IACV;AACN,QAAI,KAAK,WAAW,KAAK,KAAK,WAAW,EAAG,QAAO;AACnD,UAAM,IAAI,CAAC,MAAc,SAAS,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE;AAC1D,WAAO,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,KAAK,WAAW,IAAI,EAAE,CAAC,IAAI,MAAM,CAAC;AAAA,EAC9D;AACA,QAAM,KAAK,qBAAqB,KAAK,KAAK;AAC1C,MAAI,CAAC,GAAI,QAAO;AAChB,QAAM,QAAQ,GAAG,CAAC,EAAE,MAAM,SAAS,EAAE,OAAO,OAAO;AACnD,MAAI,MAAM,SAAS,EAAG,QAAO;AAC7B,QAAM,UAAU,CAAC,MAAe,EAAE,SAAS,GAAG,IAAK,WAAW,CAAC,IAAI,MAAO,MAAM,WAAW,CAAC;AAC5F,QAAM,QACJ,MAAM,CAAC,MAAM,SACT,IACA,MAAM,CAAC,EAAE,SAAS,GAAG,IACnB,WAAW,MAAM,CAAC,CAAC,IAAI,MACvB,WAAW,MAAM,CAAC,CAAC;AAC3B,QAAM,MAAY,CAAC,QAAQ,MAAM,CAAC,CAAC,GAAG,QAAQ,MAAM,CAAC,CAAC,GAAG,QAAQ,MAAM,CAAC,CAAC,GAAG,KAAK;AACjF,SAAO,IAAI,KAAK,CAAC,MAAM,OAAO,MAAM,CAAC,CAAC,IAAI,OAAO;AACnD;AAEA,IAAM,SAAS,CAAC,MAA8B,MAAM,QAAQ,EAAE,CAAC,KAAK;AACpE,IAAM,MAAM,CAAC,GAAS,QAAQ,EAAE,CAAC,MAC/B,SAAS,IACL,OAAO,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,MACjE,QAAQ,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK;AAClF,IAAM,MAAM,CAAC,GAAS,GAAS,MAAoB;AAAA,EACjD,EAAE,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK;AAAA,EACvB,EAAE,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK;AAAA,EACvB,EAAE,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK;AAAA,EACvB;AACF;AAEO,IAAM,YAAY,CAAC,MAAoB;AAC5C,QAAM,MAAM,CAAC,MAAc;AACzB,UAAM,IAAI,IAAI;AACd,WAAO,KAAK,UAAU,IAAI,UAAU,IAAI,SAAS,UAAU;AAAA,EAC7D;AACA,SAAO,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,SAAS,IAAI,EAAE,CAAC,CAAC;AACpE;AACA,IAAM,WAAW,CAAC,GAAS,MAAoB;AAC7C,QAAM,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAClE,UAAQ,KAAK,SAAS,KAAK;AAC7B;AACA,IAAM,QAAc,CAAC,KAAK,KAAK,KAAK,CAAC;AACrC,IAAM,QAAc,CAAC,IAAI,IAAI,IAAI,CAAC;AAIlC,IAAM,UAAU,CAAC,OAAqC,iBAAiB,EAAE;AACzE,IAAM,UAAU,CAAC,KAAe,UAA4B;AAC1D,aAAW,MAAM,CAAC,IAAI,iBAAiB,IAAI,IAAI,GAAG;AAChD,QAAI,CAAC,GAAI;AACT,UAAM,QAAQ,QAAQ,EAAE;AACxB,eAAW,QAAQ,OAAO;AACxB,YAAM,QAAQ,MAAM,iBAAiB,IAAI,EAAE,KAAK;AAChD,UAAI,MAAO,QAAO;AAAA,IACpB;AAAA,EACF;AACA,SAAO;AACT;AACA,IAAM,UAAU,CAAC,OAAyB;AACxC,QAAM,OAAO,GAAG,sBAAsB;AACtC,QAAM,QAAQ,QAAQ,EAAE;AACxB,SAAO,MAAM,YAAY,UAAU,MAAM,eAAe,YAAY,KAAK,QAAQ,KAAK,UAAU;AAClG;AACA,IAAM,SAAS,CAAC,OAAyB,GAAG,QAAQ,IAAI,kBAAkB,GAAG,MAAM;AAGnF,IAAM,gBAAgB,CAAC,KAAe,WAAiC;AACrE,MAAI,OAAuB;AAC3B,MAAI,WAAW;AACf,MACG;AAAA,IACC;AAAA,EACF,EACC,QAAQ,CAAC,OAAO;AACf,QAAI,OAAO,EAAE,KAAK,CAAC,QAAQ,EAAE,EAAG;AAChC,UAAM,OAAO,WAAW,QAAQ,EAAE,EAAE,eAAe;AACnD,QAAI,CAAC,OAAO,IAAI,KAAK,SAAS,MAAM,MAAM,IAAI,KAAM;AACpD,UAAM,OAAO,GAAG,sBAAsB;AACtC,UAAM,OAAO,KAAK,QAAQ,KAAK;AAC/B,QAAI,OAAO,UAAU;AACnB,aAAO;AACP,iBAAW;AAAA,IACb;AAAA,EACF,CAAC;AACH,SAAO;AACT;AAOO,SAAS,cAAc,MAAgB,UAA6B;AACzE,QAAM,OAAO,IAAI;AACjB,QAAM,QAAQ,KAAK,cAAc,+DAA+D;AAChG,QAAM,YAAY,QAAQ,IAAI;AAE9B,QAAM,gBAAgB,CAAC,OAAO,MAAM,IAAI,eAAe,EACpD,IAAI,CAAC,OAAQ,KAAK,WAAW,QAAQ,EAAE,EAAE,eAAe,IAAI,IAAK,EACjE,KAAK,MAAM;AACd,QAAM,aACJ,YAAY,QAAQ,QAAQ,KAAK,IAAI,WAAW,KAAK,KAAK,WAAW,UAAU,KAAK;AACtF,QAAM,cAAc,WAAW,QAAQ,KAAK,CAAC,eAAe,WAAW,iBAAiB,CAAC,CAAC;AAE1F,QAAM,OAAO,WAAW,QAAQ,KAAK,CAAC,mBAAmB,CAAC,CAAC,KAAK;AAChE,MAAI,SACF,WAAW,QAAQ,KAAK,CAAC,sBAAsB,CAAC,CAAC,KACjD,kBACC,eAAe,OAAO,WAAW,IAAI,cAAc,UACnD,QAAQ,UAAU,IAAI,IAAI,MAAM,QAAQ;AAC3C,WAAS,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC;AAC5C,QAAM,OAAO,UAAU,MAAM,IAAI;AACjC,MAAI,MACF,QAAQ,SAAS,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,KAAK,MAAM,OAAO,OAAO,QAAQ;AAC1F,QAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC;AAEhC,QAAM,SAAS,cAAc,KAAK,MAAM;AACxC,QAAM,cAAc,SAAS,QAAQ,MAAM,IAAI;AAC/C,QAAM,YAAY,WAAW,QAAQ,KAAK,CAAC,mBAAmB,CAAC,CAAC;AAChE,QAAM,WAAW,WAAW,QAAQ,KAAK,CAAC,YAAY,eAAe,eAAe,CAAC,CAAC;AACtF,QAAM,aAAa,cAAc,WAAW,YAAY,eAAe,IAAI;AAC3E,QAAM,MACJ,aACA,eACC,YAAY,SAAS,UAAU,MAAM,KAAK,OAAO,WAAW,SAC7D;AACF,QAAM,eAAe,WAAW,QAAQ,KAAK,CAAC,uBAAuB,CAAC,CAAC;AACvE,QAAM,gBACJ,CAAC,aAAa,cAAc,cAAc,WAAW,YAAY,KAAK,IAAI;AAC5E,QAAM,SACJ,iBACC,iBAAiB,SAAS,eAAe,GAAG,KAAK,IAAI,gBAAgB,UACrE,UAAU,GAAG,IAAI,MAAM,QAAQ;AAElC,QAAM,UAAU,MAAM,KAAK,IAAI,iBAAiB,0BAA0B,CAAC,EAAE;AAAA,IAC3E,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,QAAQ,EAAE;AAAA,EACnC;AACA,QAAM,eAAe,UAAU,QAAQ,OAAO,IAAI;AAClD,QAAM,WACJ,QAAQ,KAAK,CAAC,oBAAoB,CAAC,MAClC,QAAQ,QAAQ,KAAK,EAAE,aAAa,OACrC,UAAU,cACV,QAAQ,KAAK,CAAC,kBAAkB,aAAa,CAAC,KAC9C;AACF,QAAM,cACJ,QAAQ,KAAK,CAAC,4BAA4B,CAAC,KAC3C,cAAc,cACd,QAAQ,KAAK,CAAC,qBAAqB,mBAAmB,gBAAgB,CAAC,KACvE;AAEF,QAAM,QAAQ,MAAM;AAAA,IAClB,IAAI,iBAAiB,iDAAiD;AAAA,EACxE,EAAE,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC;AACzC,QAAM,KAAK,CAAC,UAA6C;AACvD,UAAM,IAAI,WAAW,SAAS,EAAE;AAChC,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,QAAM,WAAW,CAAC,UAChB,QAAQ,GAAG,MAAM,uBAAuB,MAAM,YAAY,IAAI;AAChE,QAAM,eAAe,SAAS,WAAW;AACzC,QAAM,cAAc,SAAS,QAAQ,QAAQ,KAAK,IAAI,IAAI;AAC1D,QAAM,SACJ,QAAQ,KAAK,CAAC,sBAAsB,CAAC,KACrC,GAAG,KAAK,IAAI,eAAe,KAAK,IAAI,gBAAgB,IAAI,EAAE,GAAG,EAAE,CAAC;AAElE,SAAO;AAAA,IACL,QAAQ,IAAI,MAAM;AAAA,IAClB,SAAS,IAAI,OAAO,IAAI,QAAQ,OAAO,IAAI,IAAI,IAAI,QAAQ,OAAO,GAAG,CAAC;AAAA,IACtE,KAAK,IAAI,GAAG;AAAA,IACZ,QAAQ,IAAI,IAAI,KAAK,QAAQ,IAAI,CAAC;AAAA,IAClC,MAAM,IAAI,KAAK,IAAI;AAAA,IACnB,MAAM,IAAI,KAAK,IAAI;AAAA,IACnB,KAAK,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;AAAA,IACpC,QAAQ,IAAI,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;AAAA,IAChD;AAAA,IACA;AAAA,IACA,eAAe,cAAc,cAAc;AAAA,IAC3C;AAAA,IACA,cAAc,iBAAiB,OAAO,UAAU,GAAG,KAAK,IAAI,cAAc,GAAG,CAAC;AAAA,IAC9E;AAAA,EACF;AACF;AAIA,IAAM,eAAe,CAAC,QACpB,KAAK,OAAQ,IAAI,SAAS,IAAI,QAAQ,GAAG,IAAI,KAAK,IAAK,CAAC;AAGnD,SAAS,iBAAiB,QAA0C;AACzE,MAAI,SAAS;AACb,WAAS,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG;AACtC,eAAW,WAAW,CAAC,MAAM,KAAK,MAAM,MAAM,IAAI,GAAG;AACnD,YAAM,MAAM,OAAO,UAAU,cAAc,OAAO;AAClD,UAAI,IAAI,WAAW,iBAAiB,KAAK,aAAa,GAAG,KAAK;AAC5D,eAAO;AAAA,IACX;AACA,UAAM,UAAU,SAAS,cAAc,QAAQ;AAC/C,YAAQ,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,QAAQ,IAAI,CAAC;AAC3D,YAAQ,SAAS,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,SAAS,IAAI,CAAC;AAC7D,UAAM,MAAM,QAAQ,WAAW,IAAI;AACnC,QAAI,CAAC,IAAK,QAAO;AACjB,QAAI,UAAU,QAAQ,GAAG,GAAG,QAAQ,OAAO,QAAQ,MAAM;AACzD,aAAS;AAAA,EACX;AACA,SAAO;AACT;AAGA,eAAe,gBAAgB,QAAwC;AACrE,QAAM,EAAE,YAAY,IAAI,MAAM,OAAO,mBAAmB;AACxD,QAAM,QAAQ,OAAO;AACrB,QAAM,SAAS,OAAO;AACtB,QAAM,QAAQ,KAAK;AAAA,IACjB,OAAO,oBAAoB;AAAA,IAC3B,sBAAsB,KAAK,IAAI,OAAO,MAAM;AAAA,EAC9C;AACA,QAAM,SAAS,MAAM,YAAY,SAAS,iBAAiB;AAAA,IACzD;AAAA,IACA;AAAA,IACA;AAAA,IACA,iBAAiB;AAAA,IACjB,OAAO,EAAE,WAAW,aAAa,CAAC,OAAO,OAAO,OAAO,CAAC,OAAO,OAAO,MAAM;AAAA,IAC5E,QAAQ,CAAC,SAAS,EAAE,gBAAgB,WAAW,KAAK,aAAa,kBAAkB;AAAA,EACrF,CAAC;AACD,SAAO,iBAAiB,MAAM;AAChC;AAEA,IAAM,cAAc,CAAI,MAAkB,OACxC,IAAI,QAAQ,CAAC,YAAY;AACvB,QAAM,QAAQ,WAAW,MAAM,QAAQ,IAAI,GAAG,EAAE;AAChD,OAAK;AAAA,IACH,CAAC,UAAU;AACT,mBAAa,KAAK;AAClB,cAAQ,KAAK;AAAA,IACf;AAAA,IACA,MAAM;AACJ,mBAAa,KAAK;AAClB,cAAQ,IAAI;AAAA,IACd;AAAA,EACF;AACF,CAAC;AAaI,IAAM,iBAAiB;AACvB,IAAM,iBAAiB;AAEvB,IAAM,gBAAgB;AAEtB,IAAM,sBAAsB;AAE5B,IAAM,sBAAsB;AAG5B,IAAM,QAAQ;AACd,IAAM,OAAO;AACb,IAAM,MAAM;AAanB,IAAM,UAAU;AAOT,SAAS,gBACd,KACA,gBACA,IACQ;AACR,QAAM,MAAM,KAAK,IAAI,gBAAgB,iBAAiB,mBAAmB;AACzE,QAAM,OAAO,IAAI,OAAO,gBAAgB;AACxC,QAAM,UAAU,MAAM,KAAK,EAAE,QAAQ,QAAQ,GAAG,CAAC,GAAG,MAAO,OAAO,KAAM,UAAU,EAAE;AACpF,QAAM,KAAK,IAAI,OAAO;AACtB,MAAI,OAAO;AACX,MAAI,WAAW;AACf,WAAS,SAAS,gBAAgB,UAAU,KAAK,UAAU,gBAAgB;AAEzE,QAAI,UAAU,SAAU;AACxB,UAAM,KAAK,IAAI,OAAO,SAAS,IAAI,UAAU;AAC7C,QAAI,QAAmB;AACvB,eAAW,MAAM,SAAS;AACxB,iBAAW,MAAM,SAAS;AACxB,cAAM,QAAQ,GAAG,KAAK,IAAI,KAAK,EAAE;AACjC,YAAI,QAAQ,MAAO,SAAQ;AAC3B,YAAI,UAAU,IAAK;AAAA,MACrB;AACA,UAAI,UAAU,IAAK;AAAA,IACrB;AACA,QAAI,UAAU,IAAK;AACnB,UAAM,OAAO,UAAU,UAAU,OAAO,sBAAsB;AAC9D,QAAI,OAAO,UAAU;AACnB,iBAAW;AACX,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,IAAM,WACJ;AACF,IAAM,UAAU;AAGhB,IAAM,gBAAgB;AAmBf,SAAS,gBACd,MACA,MAAgB,UACqB;AACrC,MAAI,OAAO,IAAI,sBAAsB,WAAY,QAAO,MAAM;AAC9D,QAAM,OAAO,IAAI;AACjB,QAAM,QAAQ,MAAM,cAAc;AAClC,QAAM,SAAS,MAAM,eAAe;AACpC,QAAM,OAAO,oBAAI,IAAsB;AACvC,QAAM,OAAO,CAAC,OAAyB;AACrC,UAAM,QAAQ,KAAK,IAAI,EAAE;AACzB,QAAI,MAAO,QAAO;AAClB,UAAM,QAAQ,OAAO,KAAK,iBAAiB,EAAE,IAAI;AAEjD,QAAI,MAAM,GAAG,QAAQ,QAAQ,KAAK,OAAO,WAAW;AACpD,QAAI,KAAK;AACP,YAAM,IAAI,GAAG,sBAAsB;AACnC,UAAI,EAAE,QAAQ,EAAE,UAAU,QAAQ,SAAS,cAAe,OAAM;AAAA,IAClE;AACA,UAAM,OAAkB,CAAC;AACzB,QAAI,OAAO,IAAI,gBAAgB,YAAY;AACzC,iBAAW,QAAQ,MAAM,KAAK,GAAG,UAAU,GAAG;AAC5C,YAAI,KAAK,aAAa,KAAK,CAAC,KAAK,aAAa,CAAC,KAAK,UAAU,KAAK,EAAG;AACtE,cAAM,QAAQ,IAAI,YAAY;AAC9B,cAAM,mBAAmB,IAAI;AAC7B,YAAI,OAAO,MAAM,mBAAmB;AAClC,eAAK,KAAK,GAAG,MAAM,KAAK,MAAM,eAAe,CAAC,CAAC;AAAA,MACnD;AAAA,IACF;AACA,UAAM,SAAS,WAAW,OAAO,eAAe;AAChD,UAAM,QAAQ,OAAO,mBAAmB;AACxC,UAAM,UAAU;AAAA,MACd;AAAA,MACA,SAAS,GAAG,QAAQ,OAAO,KAAK,GAAG,QAAQ,KAAK,MAAM;AAAA,MACtD;AAAA,MACA,QAAS,WAAW,QAAQ,OAAO,CAAC,KAAK,OAAS,UAAU,MAAM,UAAU;AAAA,IAC9E;AACA,SAAK,IAAI,IAAI,OAAO;AACpB,WAAO;AAAA,EACT;AACA,SAAO,CAAC,GAAG,MAAM;AACf,UAAM,KAAK,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC,GAAG,QAAQ,CAAC;AAC7C,UAAM,KAAK,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC,GAAG,SAAS,CAAC;AAC9C,UAAM,QAAQ,IACX,kBAAkB,IAAI,EAAE,EACxB,OAAO,CAAC,OAAO,OAAO,QAAQ,OAAO,IAAI,mBAAmB,OAAO,IAAI,IAAI;AAC9E,aAAS,KAAqB,MAAM,CAAC,KAAK,MAAM,MAAM,OAAO,IAAI,MAAM,KAAK,GAAG,eAAe;AAC5F,UAAI,KAAK,EAAE,EAAE,IAAK,QAAO;AAAA,IAC3B;AACA,eAAW,MAAM,OAAO;AACtB,YAAM,IAAI,KAAK,EAAE;AACjB,UACE,EAAE,WACF,EAAE,KAAK,KAAK,CAAC,MAAM,MAAM,EAAE,QAAQ,MAAM,EAAE,SAAS,MAAM,EAAE,OAAO,MAAM,EAAE,MAAM;AAEjF,eAAO;AACT,UAAI,EAAE,OAAQ;AAAA,IAChB;AACA,WAAO;AAAA,EACT;AACF;AAWO,IAAM,cAAc;AACpB,IAAM,YAAY;AAClB,IAAM,aAAa;AACnB,IAAM,cAAc;AAEpB,IAAM,YAAY;AAGlB,IAAM,WAAW;AAiBjB,IAAM,kBAAkB,CAAC,GAAW,eAAe,UACtD,IAAI,MAAQ,gBAAiB,IAAI;AAG9B,IAAM,aAAa,CAAC,WAAmB,WAAmB,WAAW,SACzE,YAAY,YAAY,YAAa,YAAY,WAAW,KAAK,IAAI,SAAS;AAEjF,IAAM,SAAS,CAAC,GAAW,QAAgB,SACzC,KAAK;AAAA,EACH,KAAK,IAAI,GAAG,KAAK,MAAM,WAAW;AAAA,EAClC,KAAK,IAAI,KAAK,MAAM,aAAa,KAAK,SAAS,KAAK,SAAS,cAAc,MAAM;AACnF;AAGK,SAAS,YACd,OACA,MACA,GACA,MACc;AACd,MAAI,UAAU,SAAS;AACrB,WAAO;AAAA,MACL;AAAA,MACA,GAAG,OAAO,cAAc,KAAK,QAAQ,cAAc;AAAA,MACnD,GAAG,OAAO,GAAG,aAAa,IAAI;AAAA,IAChC;AAAA,EACF;AACA,SAAO,EAAE,OAAO,GAAG,OAAO,IAAI,KAAK,QAAQ,WAAW,GAAG,OAAO,GAAG,YAAY,IAAI,EAAE;AACvF;AAOO,SAAS,cACd,MACA,UACA,MACc;AACd,QAAM,KAAK,KAAK,IAAI,gBAAgB,SAAS,CAAC;AAC9C,MAAI,KAAK,UAAU,QAAS,QAAO,YAAY,KAAK,OAAO,KAAK,UAAU,aAAa,IAAI,IAAI;AAC/F,QAAM,KAAK,KAAK,IAAI,gBAAgB,SAAS,CAAC;AAC9C,QAAM,OAAO,KAAK,QAAQ;AAC1B,QAAM,OAAO,MAAM,cAAc,cAAc;AAC/C,MAAI,KAAK,CAAC,aAAa,KAAK,IAAI,EAAG,QAAO,YAAY,aAAa,MAAM,MAAM,IAAI;AACnF,MAAI,KAAK,OAAO,aAAa,KAAK,IAAI,OAAO;AAC3C,WAAO,YAAY,cAAc,OAAO,MAAM,IAAI;AACpD,SAAO,YAAY,SAAS,KAAK,cAAc,IAAI,KAAK,QAAQ,GAAG,IAAI,IAAI;AAC7E;AAIA,IAAM,WACJ;AAEF,IAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6Ed,IAAI,UAAqC;AAGlC,IAAM,mBAAmB,CAAC,MAAgB,aAC/C,IAAI,gBAAgB,aAAa,kBAAkB,MAAM;AAE3D,IAAM,iBAAiB,EAAE,SAAS,MAAM,SAAS,KAAK;AAE/C,SAAS,kBAAkB,SAAyD;AACzF,MAAI,OAAO,aAAa,eAAe,CAAC,SAAS,KAAM,QAAO;AAC9D,MAAI,QAAS,QAAO;AACpB,MAAI,iBAAiB,EAAG,QAAO;AAE/B,QAAM,OAAO,SAAS,cAAc,KAAK;AACzC,OAAK,aAAa,oBAAoB,EAAE;AACxC,QAAM,OAAO,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAK,YAAY,UAAU,KAAK;AAAA,+FAC6D,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYrG,QAAM,IAAI,CAAoB,aAAwB,KAAK,cAAc,QAAQ;AACjF,QAAM,MAAM,EAAqB,MAAM;AACvC,QAAM,QAAQ,EAAkB,QAAQ;AACxC,QAAM,QAAQ,EAAkB,QAAQ;AACxC,QAAM,OAAO,EAAkB,OAAO;AACtC,QAAM,MAAM,EAAoB,WAAW;AAC3C,QAAM,SAAS,EAAqB,SAAS;AAC7C,QAAM,OAAO,EAAuB,UAAU;AAC9C,QAAM,SAAS,EAAwB,SAAS;AAChD,QAAM,OAAO,EAAqB,OAAO;AACzC,QAAM,OAAO,EAAqB,OAAO;AACzC,QAAM,QAAQ,EAAkB,QAAQ;AAExC,MAAI,QAAQ,cAAc;AAC1B,MAAI,aAA4B;AAChC,MAAI,OAAO;AACX,MAAI,aAAmD;AACvD,MAAI,aAAmD;AACvD,MAAI,aAAa;AACjB,MAAI,OAAqB,EAAE,OAAO,SAAS,GAAG,GAAG,GAAG,EAAE;AAEtD,MAAI,iBAAiB;AACrB,MAAI,WAAW,EAAE,GAAG,GAAG,GAAG,EAAE;AAC5B,MAAI,QAAQ;AAEZ,QAAM,KAAK,CAAC,SAAgC;AAC1C,UAAM,MAAM,iBAAiB,IAAI,EAAE,iBAAiB,IAAI,EAAE,KAAK;AAC/D,UAAM,IAAI,MAAM,WAAW,GAAG,IAAI;AAClC,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,QAAM,WAAW,MAAwB;AACvC,UAAM,QAAQ,iBAAiB,KAAK;AACpC,WAAO;AAAA,MACL,OAAO,OAAO;AAAA,MACd,QAAQ,OAAO;AAAA,MACf,KAAK,WAAW,MAAM,UAAU,KAAK;AAAA,MACrC,QAAQ,WAAW,MAAM,aAAa,KAAK;AAAA,IAC7C;AAAA,EACF;AACA,QAAM,OAAO,MAAY;AACvB,QAAI,MAAM,YAAY,eAAe,KAAK,CAAC,OAAO,KAAK,CAAC;AAAA,EAC1D;AACA,QAAM,WAAW,CAAC,SAA0B;AAC1C,SAAK,QAAQ;AACb,QAAI,QAAQ,QAAQ;AAAA,EACtB;AAGA,QAAM,WAAW,CAACA,UAA6B;AAC7C,QAAI,CAAC,eAAgB;AACrB,QAAI;AACF,YAAM,OAAO,SAAS;AACtB,YAAM,QAAQA,MAAK,UAAU,UAAU,cAAc;AACrD,mBAAa;AAAA,QACX;AAAA,QACA,KAAK,UAAU;AAAA,UACb,OAAOA,MAAK;AAAA,UACZ,MAAMA,MAAK,IAAI,QAAQ,IAAI,KAAK,QAAQ;AAAA,UACxC,GAAG,KAAK,SAAS,IAAIA,MAAK,IAAI,KAAK,SAAS;AAAA,QAC9C,CAAC;AAAA,MACH;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACA,QAAM,QAAQ,MACZ,OAAO,OAAO,eAAe,cAC7B,OAAO,WAAW,kCAAkC,EAAE;AAIxD,QAAM,WAAW,CAAC,IAAkB,YAA2B;AAC7D,yBAAqB,KAAK;AAC1B,YAAQ;AACR,aAAS,GAAG,KAAK;AACjB,aAAS,EAAE;AACX,QAAI,MAAM,KAAK,OAAO,0BAA0B,YAAY;AAC1D,aAAO,EAAE,GAAG,GAAG;AACf,iBAAW,EAAE,GAAG,GAAG,GAAG,EAAE;AACxB,WAAK;AACL;AAAA,IACF;AACA,UAAM,QAAS,IAAI,KAAK,KAAM;AAC9B,UAAM,IAAI,QAAQ;AAClB,UAAM,IAAI,KAAK,UAAU,MAAM,KAAK;AACpC,QAAI,OAAO,YAAY,IAAI;AAC3B,UAAM,OAAO,CAAC,QAAsB;AAClC,YAAM,KAAK,KAAK,IAAI,QAAQ,MAAM,QAAQ,GAAI;AAC9C,aAAO;AACP,eAAS,MAAM,CAAC,KAAK,KAAK,IAAI,GAAG,KAAK,IAAI,SAAS,KAAK;AACxD,eAAS,MAAM,CAAC,KAAK,KAAK,IAAI,GAAG,KAAK,IAAI,SAAS,KAAK;AACxD,WAAK,KAAK,SAAS,IAAI;AACvB,WAAK,KAAK,SAAS,IAAI;AACvB,YAAMC,SACJ,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC,IAAI,OAC1B,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC,IAAI,OAC1B,KAAK,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI;AACvC,UAAIA,QAAO;AACT,eAAO,EAAE,GAAG,GAAG;AACf,mBAAW,EAAE,GAAG,GAAG,GAAG,EAAE;AACxB,gBAAQ;AACR,aAAK;AACL;AAAA,MACF;AACA,WAAK;AACL,cAAQ,sBAAsB,IAAI;AAAA,IACpC;AACA,YAAQ,sBAAsB,IAAI;AAAA,EACpC;AAKA,QAAM,QAAQ,MAAY;AACxB,iBAAa;AACb,QAAI,CAAC,MAAM,UAAU,IAAI,UAAU,QAAQ,MAAO;AAClD,UAAM,OAAO,SAAS;AACtB,QAAI,kBAAkB,KAAK,UAAU,SAAS;AAC5C,YAAM,QAAQ,KAAK,UAAU,UAAU,cAAc;AACrD,aAAO,YAAY,KAAK,OAAO,KAAK,IAAI,QAAQ,IAAI,KAAK,QAAQ,GAAG,KAAK,GAAG,IAAI;AAChF,WAAK;AACL;AAAA,IACF;AACA,UAAM,QAAQ,GAAG,qBAAqB,KAAK;AAC3C,UAAM,OAAO,KAAK,QAAQ,QAAQ;AAClC,UAAM,SAAS,GAAG,sBAAsB;AACxC,QAAI,WAAW,QAAQ,OAAO,SAAS,sBAAsB,YAAY;AACvE,mBAAa;AAAA,QACX;AAAA,UACE;AAAA,UACA,KAAK,KAAK,SAAS,KAAK,SAAS,aAAa;AAAA,UAC9C,MAAM;AAAA,UACN,QAAQ;AAAA,QACV;AAAA,QACA,KAAK;AAAA,QACL,gBAAgB,IAAI;AAAA,MACtB;AAAA,IACF;AACA,UAAM,SAAS,UAAU;AACzB,WAAO,EAAE,OAAO,SAAS,GAAG,MAAM,GAAG,KAAK,SAAS,KAAK,SAAS,SAAS,YAAY;AACtF,SAAK;AAAA,EACP;AACA,QAAM,gBAAgB,MAAY;AAChC,QAAI,WAAY,cAAa,UAAU;AACvC,iBAAa,WAAW,OAAO,GAAG;AAAA,EACpC;AAIA,MAAI,OAQO;AACX,MAAI,gBAAgB;AACpB,QAAM,SAAS,CAAC,UAA8B;AAC5C,QAAI,CAAC,MAAM,UAAU,KAAM;AAC3B,QAAI,oBAAoB,MAAM,SAAS;AACvC,yBAAqB,KAAK;AAC1B,YAAQ;AACR,eAAW,EAAE,GAAG,GAAG,GAAG,EAAE;AACxB,WAAO;AAAA,MACL,IAAI,MAAM;AAAA,MACV,IAAI,MAAM,UAAU,KAAK;AAAA,MACzB,IAAI,MAAM,UAAU,KAAK;AAAA,MACzB,IAAI,MAAM;AAAA,MACV,IAAI,MAAM;AAAA,MACV,OAAO;AAAA,MACP,SAAS,CAAC,EAAE,GAAG,MAAM,WAAW,GAAG,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC;AAAA,IACtE;AACA,QAAI,UAAU,IAAI,MAAM;AAAA,EAC1B;AACA,QAAM,SAAS,CAAC,UAA8B;AAC5C,QAAI,CAAC,QAAQ,MAAM,cAAc,KAAK,GAAI;AAC1C,UAAM,KAAK,MAAM,UAAU,KAAK;AAChC,UAAM,KAAK,MAAM,UAAU,KAAK;AAChC,QAAI,CAAC,KAAK,SAAS,KAAK,MAAM,IAAI,EAAE,IAAI,EAAG;AAC3C,QAAI,CAAC,KAAK,OAAO;AACf,WAAK,QAAQ;AACb,UAAI,UAAU,IAAI,UAAU;AAAA,IAC9B;AACA,SAAK,QAAQ,KAAK,EAAE,GAAG,MAAM,WAAW,GAAG,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC;AAC5E,QAAI,KAAK,QAAQ,SAAS,EAAG,MAAK,QAAQ,MAAM;AAChD,UAAM,OAAO,SAAS;AACtB,QAAI,KAAK,UAAU,SAAS;AAE1B,YAAM,SAAS,KAAK,UAAU,cAAc,KAAK,CAAC;AAClD,UAAI,UAAU,IAAI;AAChB,eAAO,YAAY,KAAK,OAAO,KAAK,UAAU,aAAa,MAAM,UAAU,KAAK,IAAI,IAAI;AACxF,aAAK;AACL;AAAA,MACF;AACA,eAAS,OAAO;AAChB,WAAK,KAAK,cAAc;AACxB,WAAK,KAAK,cAAc;AAAA,IAC1B;AACA,QAAI,IAAI,MAAM,UAAU,KAAK;AAC7B,QAAI,IAAI,MAAM,UAAU,KAAK;AAC7B,UAAM,OAAO,KAAK,QAAQ;AAC1B,UAAM,OAAO,KAAK,SAAS;AAC3B,QAAI,IAAI,EAAG,KAAI,WAAW,GAAG,EAAE;AAAA,aACtB,IAAI,KAAM,KAAI,OAAO,WAAW,IAAI,MAAM,EAAE;AACrD,QAAI,IAAI,EAAG,KAAI,WAAW,GAAG,EAAE;AAAA,aACtB,IAAI,KAAM,KAAI,OAAO,WAAW,IAAI,MAAM,EAAE;AACrD,SAAK,IAAI;AACT,SAAK,IAAI;AACT,SAAK;AAAA,EACP;AACA,QAAM,OAAO,CAAC,UAA8B;AAC1C,QAAI,CAAC,QAAQ,MAAM,cAAc,KAAK,GAAI;AAC1C,UAAM,QAAQ;AACd,WAAO;AACP,QAAI,UAAU,OAAO,QAAQ,UAAU;AACvC,QAAI,CAAC,MAAM,MAAO;AAClB,oBAAgB;AAChB,eAAW,MAAM;AACf,sBAAgB;AAAA,IAClB,GAAG,CAAC;AACJ,UAAM,QAAQ,MAAM,QAAQ,CAAC;AAC7B,UAAM,OAAO,MAAM,QAAQ,MAAM,QAAQ,SAAS,CAAC;AACnD,UAAM,KAAK,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,CAAC;AACvC,UAAM,SAAS,MAAM,YAAY,KAAK,IAAI;AAC1C,eAAW,SACP,EAAE,GAAG,GAAG,GAAG,EAAE,IACb,EAAE,IAAK,KAAK,IAAI,MAAM,KAAK,KAAM,KAAM,IAAK,KAAK,IAAI,MAAM,KAAK,KAAM,IAAK;AAC/E,qBAAiB;AACjB,aAAS,cAAc,MAAM,UAAU,SAAS,CAAC,GAAG,KAAK,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,GAAG;AAAA,EAC9F;AAEA,QAAM,WACJ,OAAO,qBAAqB,aACxB,IAAI,iBAAiB,CAAC,YAAY;AAChC,QAAI,QAAQ,KAAK,CAAC,MAAM,EAAE,WAAW,IAAI,EAAG,eAAc;AAAA,EAC5D,CAAC,IACD;AAEN,QAAM,QAAQ,MAAY;AACxB,YAAQ,cAAc;AACtB,UAAM,OAA+B;AAAA,MACnC,aAAa,MAAM;AAAA,MACnB,SAAS,MAAM;AAAA,MACf,aAAa,MAAM;AAAA,MACnB,UAAU,MAAM;AAAA,MAChB,UAAU,MAAM;AAAA,MAChB,SAAS,MAAM;AAAA,MACf,aAAa,MAAM;AAAA,MACnB,eAAe,MAAM;AAAA,MACrB,kBAAkB,MAAM;AAAA,MACxB,oBAAoB,MAAM;AAAA,MAC1B,YAAY,MAAM;AAAA,MAClB,mBAAmB,MAAM;AAAA,IAC3B;AACA,eAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,IAAI,EAAG,MAAK,MAAM,YAAY,MAAM,KAAK;AACpF,SAAK,MAAM,cAAc,MAAM,OAAO,SAAS;AAAA,EACjD;AACA,QAAM,OAAO,MAAY;AACvB,SAAK,WAAW,QAAQ,KAAK,MAAM,KAAK,EAAE,WAAW;AAAA,EACvD;AACA,QAAM,UAAU,CAAC,QAA6B;AAC5C,iBAAa;AACb,SAAK,SAAS,QAAQ;AACtB,QAAI,IAAK,KAAI,MAAM;AAAA,QACd,KAAI,gBAAgB,KAAK;AAAA,EAChC;AACA,QAAM,QAAQ,MAAY;AACxB,QAAI,WAAY,cAAa,UAAU;AACvC,iBAAa;AACb,UAAM,SAAS;AACf,UAAM,SAAS;AACf,QAAI,SAAS;AACb,YAAQ,IAAI;AACZ,SAAK,QAAQ;AACb,WAAO,cAAc;AACrB,WAAO;AACP,SAAK;AACL,kBAAc;AAAA,EAChB;AACA,QAAM,OAAO,YAA2B;AACtC,QAAI,CAAC,MAAM,UAAU,KAAM;AAC3B,WAAO;AACP,UAAM;AAEN,QAAI,SAAS;AACb,UAAM,UAAU,QAAQ,YAAY,MAAM,gBAAgB,MAAM,MAAM;AACtE,UAAM,MAAM,MAAM,YAAY,QAAQ,QAAQ,EAAE,KAAK,OAAO,GAAG,kBAAkB;AACjF,WAAO;AACP,YAAQ,GAAG;AACX,SAAK,SAAS,KAAK,UAAU;AAC7B,UAAM,SAAS;AACf,UAAM,SAAS;AACf,SAAK;AACL,SAAK,MAAM;AAAA,EACb;AACA,QAAM,SAAS,YAA2B;AACxC,UAAM,UAAU,KAAK,MAAM,KAAK;AAChC,QAAI,CAAC,WAAW,KAAM;AACtB,WAAO;AACP,SAAK;AACL,WAAO,cAAc;AACrB,QAAI;AACF,YAAM,QAAQ,OAAO;AAAA,QACnB;AAAA,QACA,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,QACnC,GAAI,SAAS,KAAK,QAAQ,UAAU,EAAE,SAAS,SAAS,KAAK,QAAQ,QAAQ,IAAI,CAAC;AAAA,QAClF,UAAU,OAAO,SAAS,SAAS,MAAM,IAAI;AAAA,QAC7C,UAAU,EAAE,OAAO,OAAO,YAAY,QAAQ,OAAO,YAAY;AAAA,MACnE,CAAC;AACD,aAAO,cAAc;AACrB,mBAAa,WAAW,MAAM;AAC5B,cAAM;AACN,YAAI,MAAM;AAAA,MACZ,GAAG,IAAI;AAAA,IACT,QAAQ;AACN,aAAO;AACP,aAAO,cAAc;AACrB,WAAK;AAAA,IACP;AAAA,EACF;AAEA,MAAI,iBAAiB,SAAS,MAAM;AAClC,QAAI,CAAC,cAAe,MAAK,KAAK;AAAA,EAChC,CAAC;AACD,MAAI,iBAAiB,eAAe,MAAM;AAC1C,MAAI,iBAAiB,eAAe,MAAM;AAC1C,MAAI,iBAAiB,aAAa,IAAI;AACtC,MAAI,iBAAiB,iBAAiB,IAAI;AAC1C,OAAK,iBAAiB,SAAS,MAAM;AACnC,UAAM,OAAO,SAAS;AACtB,UAAM,OAAO,KAAK,IAAI,cAAc,IAAI,KAAK,QAAQ;AACrD,UAAM;AACN,qBAAiB;AACjB;AAAA,MACE;AAAA,QACE,OAAO,cAAc;AAAA,QACrB;AAAA,QACA,KAAK,KAAK,cAAc,cAAc;AAAA,QACtC;AAAA,MACF;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AACD,QAAM,iBAAiB,SAAS,KAAK;AACrC,SAAO,iBAAiB,SAAS,MAAM,QAAQ,IAAI,CAAC;AACpD,OAAK,iBAAiB,SAAS,IAAI;AACnC,OAAK,iBAAiB,SAAS,MAAM,KAAK,OAAO,CAAC;AAClD,QAAM,iBAAiB,WAAW,CAAC,UAAU;AAC3C,QAAI,MAAM,QAAQ,UAAU;AAC1B,YAAM;AACN,UAAI,MAAM;AAAA,IACZ;AACA,UAAM,gBAAgB;AAAA,EACxB,CAAC;AAED,WAAS,KAAK,YAAY,IAAI;AAC9B,QAAM;AACN,MAAI;AACF,UAAM,QAAQ,KAAK,MAAM,aAAa,QAAQ,QAAQ,KAAK,MAAM;AAKjE,QACE,SACA,OAAO,MAAM,MAAM,aAClB,MAAM,UAAU,WAAW,MAAM,UAAU,eAAe,MAAM,UAAU,eAC3E;AACA,YAAM,OAAO,SAAS;AACtB,uBAAiB;AACjB,eAAS,MAAM,KAAK;AACpB,aAAO,YAAY,MAAM,OAAO,MAAM,SAAS,MAAM,MAAM,IAAI,KAAK,QAAQ,IAAI;AAAA,IAClF;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,QAAM;AACN,gBAAc;AACd,YAAU,QAAQ,SAAS,MAAM;AAAA,IAC/B,WAAW;AAAA,IACX,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,iBAAiB,CAAC,SAAS,SAAS,QAAQ;AAAA,EAC9C,CAAC;AACD,SAAO,iBAAiB,UAAU,aAAa;AAC/C,SAAO,iBAAiB,QAAQ,aAAa;AAG7C,SAAO,iBAAiB,UAAU,eAAe,cAAc;AAG/D,WAAS,iBAAiB,iBAAiB,eAAe,cAAc;AACxE,WAAS,iBAAiB,gBAAgB,eAAe,cAAc;AACvE,UAAQ,UAAU;AAElB,YAAU;AAAA,IACR;AAAA,IACA,SAAS,MAAM;AACb,UAAI,MAAM,OAAQ,OAAM;AAAA,IAC1B;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,MAAM;AACb,UAAI,WAAY,cAAa,UAAU;AACvC,UAAI,WAAY,cAAa,UAAU;AACvC,2BAAqB,KAAK;AAC1B,gBAAU,WAAW;AACrB,aAAO,oBAAoB,UAAU,aAAa;AAClD,aAAO,oBAAoB,QAAQ,aAAa;AAChD,aAAO,oBAAoB,UAAU,eAAe,cAAc;AAClE,eAAS,oBAAoB,iBAAiB,eAAe,cAAc;AAC3E,eAAS,oBAAoB,gBAAgB,eAAe,cAAc;AAC1E,WAAK,OAAO;AACZ,gBAAU;AAAA,IACZ;AAAA,EACF;AACA,SAAO;AACT;AAEO,IAAM,sBAAsB,MAAY,SAAS,QAAQ;;;ACx3BzD,SAAS,0BAAsD;AACpE,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,QAAM,IAAI,IAAI,gBAAgB,OAAO,SAAS,MAAM;AAGpD,QAAM,OAAO,EAAE,IAAI,MAAM,KAAK,EAAE,IAAI,QAAQ,KAAK;AACjD,MAAI,CAAC,KAAM,QAAO;AAIlB,QAAM,SAAS,EAAE,IAAI,QAAQ;AAC7B,QAAM,WAAW,EAAE,IAAI,KAAK,KAAK,0BAA0B,QAAQ,OAAO,EAAE;AAC5E,QAAM,OAAO,EAAE,IAAI,MAAM;AACzB,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,EAAE,IAAI,QAAQ;AAAA,IACtB,UAAU,EAAE,IAAI,KAAK,KAAK,0BAA0B,QAAQ,OAAO,EAAE;AAAA,IACrE;AAAA,IACA,YAAY,SAAS,OAAO,QAAQ,OAAO,EAAE,IAAI;AAAA,IACjD,UAAU,OAAO,KAAK,QAAQ,OAAO,EAAE,IAAI;AAAA,EAC7C;AACF;AAGO,IAAM,eAAe,CAAC,WAC3B,IAAI,IAAI,OAAO,YAAY,OAAO,OAAO,EAAE;AAE7C,IAAM,0BAA0B;AAChC,IAAM,2BAA2B;AAKjC,SAAS,gBAAgB,YAAqC;AAC5D,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,QAAI,OAAO,WAAW,eAAe,OAAO,WAAW,QAAQ;AAC7D,aAAO,IAAI,MAAM,yCAAyC,CAAC;AAC3D;AAAA,IACF;AACA,UAAM,YAAY;AAClB,UAAM,QAAQ,WAAW,MAAM;AAC7B,aAAO,oBAAoB,WAAW,SAAS;AAC/C,aAAO,IAAI,MAAM,2CAA2C,CAAC;AAAA,IAC/D,GAAG,uBAAuB;AAC1B,UAAM,YAAY,CAAC,MAAoB;AACrC,UAAI,EAAE,WAAW,UAAW;AAC5B,YAAM,OAAO,EAAE;AACf,UAAI,MAAM,SAAS,oBAAoB,OAAO,KAAK,WAAW,SAAU;AACxE,mBAAa,KAAK;AAClB,aAAO,oBAAoB,WAAW,SAAS;AAC/C,cAAQ,KAAK,MAAM;AAAA,IACrB;AACA,WAAO,iBAAiB,WAAW,SAAS;AAC5C,WAAO,OAAO,YAAY,EAAE,MAAM,kBAAkB,GAAG,SAAS;AAAA,EAClE,CAAC;AACH;AAoCA,IAAM,aAA8D;AAAA,EAClE,OAAO;AAAA,EACP,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,MAAM;AACR;AAGO,SAAS,YACd,GAC+C;AAC/C,SAAO;AAAA,IACL,MAAM,EAAE;AAAA,IACR,OAAO,WAAW,EAAE,KAAK,KAAK;AAAA,IAC9B,QAAQ,EAAE;AAAA,IACV,OAAQ,EAAE,SAA2B;AAAA,IACrC,SAAS,EAAE;AAAA,IACX,aAAa,EAAE;AAAA,IACf,aAAa,EAAE;AAAA,IACf,WAAW,EAAE;AAAA,IACb,WAAW,EAAE,aAAa,EAAE;AAAA,IAC5B,MAAM,EAAE,QAAQ;AAAA,IAChB,MAAM,EAAE,QAAQ;AAAA,IAChB,gBAAiB,EAAE,cAAuC;AAAA,IAC1D,KAAM,EAAE,QAAwB;AAAA,IAChC,YAAY,EAAE;AAAA,IACd,WAAW,EAAE,aAAa;AAAA,IAC1B,QAAQ,EAAE,SACN,EAAE,QAAQ,EAAE,OAAO,QAAQ,OAAO,EAAE,OAAO,OAAO,QAAQ,EAAE,OAAO,UAAU,KAAK,IAClF;AAAA,EACN;AACF;AAQA,IAAM,eAAe,CAAC,eAAuC;AAC3D,SAAO,CAAC,QAAQ;AACd,QAAI,OAAO,WAAW,eAAe,OAAO,WAAW,QAAQ;AAG7D,aAAO,OAAO,YAAY,EAAE,MAAM,sBAAsB,IAAI,GAAG,UAAU;AACzE;AAAA,IACF;AACA,QAAI,OAAO,WAAW,YAAa,QAAO,KAAK,KAAK,UAAU,UAAU;AAAA,EAC1E;AACF;AAEA,IAAM,oBAAN,MAIE;AAAA,EAUA,YAAY,QAA6B,QAAyB;AATlE,wBAAS;AACT,wBAAS;AACT,wBAAS;AACT,wBAAQ,gBAAuC;AAC/C,wBAAiB;AACjB,wBAAiB;AACjB,wBAAiB;AACjB,wBAAiB;AAGf,SAAK,OAAO,OAAO;AACnB,SAAK,UAAU,OAAO;AACtB,SAAK,aAAa,OAAO,cAAc;AACvC,SAAK,UAAU,OAAO;AACtB,SAAK,aAAa,aAAa,MAAM;AACrC,SAAK,SAAS,UAAU,aAAa,aAAa,MAAM,CAAC;AAGzD,SAAK,eAAe,KAAK,cAAc,MAAM,EAAE,KAAK,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC;AAAA,EAC3E;AAAA,EAEA,MAAc,cAAc,QAA8C;AACxE,QAAI,OAAO,OAAQ,QAAO,OAAO;AACjC,WAAO,gBAAgB,aAAa,MAAM,CAAC;AAAA,EAC7C;AAAA,EAEA,MAAc,OAAO,QAAiC;AACpD,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,2BAA2B;AAAA,MAChE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,CAAC;AAAA,IACjC,CAAC;AACD,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,yBAAyB,IAAI,MAAM,GAAG;AACnE,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,WAAO,KAAK,KAAK;AAAA,EACnB;AAAA,EAEA,MAAc,KACZ,MACA,OAAoB,CAAC,GACmC;AACxD,UAAM,QAAQ,MAAM,KAAK;AACzB,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,UAAU,KAAK,IAAI,GAAG,IAAI,IAAI;AAAA,MACnE,GAAG;AAAA,MACH,SAAS;AAAA,QACP,eAAe,UAAU,KAAK;AAAA,QAC9B,GAAI,KAAK,OAAO,EAAE,gBAAgB,mBAAmB,IAAI,CAAC;AAAA,QAC1D,GAAI,KAAK,WAAW,CAAC;AAAA,MACvB;AAAA,IACF,CAAC;AACD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,OAAQ,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC/C,YAAM,IAAI,MAAM,KAAK,WAAW,mBAAmB,IAAI,MAAM,EAAE;AAAA,IACjE;AACA,UAAM,OAAQ,MAAM,IAAI,KAAK;AAG7B,UAAM,OAAO,KAAK;AAClB,UAAM,OAAQ,KAAK,SAAS;AAC5B,UAAM,OAAO,YAAyC,IAAI;AAC1D,SAAK,eAAe,KAAK,QAAQ,UAAU;AAC3C,SAAK,aAAa,KAAK,oBAAoB,IAAI;AAC/C,WAAO;AAAA,EACT;AAAA,EAEA,IAAI,SAAiC;AACnC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA,EAIQ,aAAa,SAAwB;AAC3C,QAAI,OAAO,aAAa,YAAa;AACrC,QAAI,CAAC,SAAS;AACZ,0BAAoB;AACpB;AAAA,IACF;AACA,UAAM,SAAS,kBAAkB;AAAA,MAC/B,QAAQ,CAAC,WAAW,KAAK,QAAQ,YAAY,MAAM;AAAA,MACnD,SAAS,MAAM;AACb,YAAI,OAAO,WAAW;AACpB,iBAAO,OAAO,YAAY,EAAE,MAAM,2BAA2B,GAAG,KAAK,UAAU;AAAA,MACnF;AAAA,IACF,CAAC;AACD,YAAQ,QAAQ;AAAA,EAClB;AAAA,EAEA,OAAO;AACL,WAAO,KAAK,KAAK,UAAU,EAAE,QAAQ,OAAO,CAAC;AAAA,EAC/C;AAAA,EAEA,QAAQ;AACN,WAAO,KAAK,KAAK,QAAQ;AAAA,EAC3B;AAAA,EAEA,KACE,SACA,MACY;AACZ,UAAM,WAAW,MAAM,cAAc;AACrC,QAAI,UAAU;AACd,QAAI,QAA8C;AAClD,UAAM,OAAO,YAAY;AACvB,UAAI;AACF,cAAM,IAAI,MAAM,KAAK,MAAM;AAC3B,YAAI,CAAC,QAAS,SAAQ,CAAC;AAAA,MACzB,SAAS,KAAK;AACZ,cAAM,UAAU,GAAG;AAAA,MACrB;AACA,UAAI,CAAC,QAAS,SAAQ,WAAW,MAAM,QAAQ;AAAA,IACjD;AACA,SAAK,KAAK;AACV,WAAO,MAAM;AACX,gBAAU;AACV,UAAI,MAAO,cAAa,KAAK;AAAA,IAC/B;AAAA,EACF;AAAA,EAEA,YAAY,OAAe;AACzB,WAAO,KAAK,KAAK,UAAU,EAAE,QAAQ,QAAQ,MAAM,KAAK,UAAU,EAAE,MAAM,CAAC,EAAE,CAAC;AAAA,EAChF;AAAA,EAEA,WAAW,SAAwB;AACjC,WAAO,KAAK,KAAK,eAAe,EAAE,QAAQ,QAAQ,MAAM,KAAK,UAAU,EAAE,QAAQ,CAAC,EAAE,CAAC;AAAA,EACvF;AAAA,EAEA,QAAQ;AACN,WAAO,KAAK,KAAK,UAAU,EAAE,QAAQ,OAAO,CAAC;AAAA,EAC/C;AAAA,EAEA,OAAO,MAAc,MAAgB;AACnC,WAAO,KAAK,KAAK,IAAI,KAAK,QAAQ,OAAO,EAAE,CAAC,IAAI;AAAA,MAC9C,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,QAAQ,CAAC,CAAC;AAAA,IACjC,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QACJ,MACA,MACA,SACY;AACZ,UAAM,SAAS,SAAS;AACxB,QAAI,QAAQ,SAAS;AAGnB,WAAK,KAAK,aAAa,MAAM,MAAM;AAAA,MAAC,CAAC;AACrC,aAAO,eAAe;AAAA,IACxB;AACA,QAAI;AACJ,QAAI;AACJ,QAAI;AACF,cAAQ,OAAO,SACX,QAAQ,KAAK;AAAA,QACX,KAAK;AAAA,QACL,IAAI,QAAe,CAAC,UAAU,WAAW;AACvC,oBAAU,MAAM,OAAO,OAAO,MAAM;AACpC,iBAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAAA,QAC1D,CAAC;AAAA,MACH,CAAC,IACD,KAAK;AAAA,IACX,UAAE;AACA,UAAI,UAAU,QAAS,QAAO,oBAAoB,SAAS,OAAO;AAAA,IACpE;AACA,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,UAAU,KAAK,IAAI,IAAI,KAAK,QAAQ,OAAO,EAAE,CAAC,IAAI;AAAA,MACvF;AAAA,MACA,QAAQ;AAAA,MACR,SAAS,EAAE,eAAe,UAAU,KAAK,IAAI,gBAAgB,mBAAmB;AAAA,MAChF,MAAM,KAAK,UAAU,QAAQ,CAAC,CAAC;AAAA,IACjC,CAAC;AACD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,UAAW,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM;AAC5C,gBAAQ,eAAe;AACvB,eAAO,CAAC;AAAA,MACV,CAAC;AACD,YAAM,IAAI,MAAM,QAAQ,WAAW,mBAAmB,IAAI,MAAM,EAAE;AAAA,IACpE;AACA,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,aAAa,KAAmB;AAC9B,SAAK,OAAO,GAAG;AAAA,EACjB;AAAA,EAEA,UAAU,YAA4B;AAGpC,WAAO,KAAK,aACR,GAAG,KAAK,UAAU,OAAO,UAAU,KACnC,GAAG,KAAK,OAAO,MAAM,UAAU;AAAA,EACrC;AACF;AAoBO,SAAS,kBAId,OAAyC,CAAC,GAA+C;AACzF,QAAM,UAAU,CAAC,WAAW,WAAW,WAAW,WAAW,SAAS;AACtE,QAAM,UAA2B;AAAA,IAC/B;AAAA,MACE,QAAQ;AAAA,MACR,MAAM,KAAK,MAAM;AAAA,MACjB,OAAO,QAAQ,CAAC;AAAA,MAChB,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,QAAQ;AAAA,IACV;AAAA,IACA,IAAI,KAAK,UAAU,CAAC,GAAG,IAAI,CAAC,MAAM,OAAO;AAAA,MACvC,QAAQ,QAAQ,IAAI,CAAC;AAAA,MACrB;AAAA,MACA,OAAO,SAAS,IAAI,KAAK,QAAQ,MAAM;AAAA,MACvC,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,QAAQ;AAAA,IACV,EAAE;AAAA,EACJ;AACA,MAAI,QAAuB,KAAK,SAAS;AACzC,MAAI,iBAAuC;AAC3C,MAAI,MAAmB;AACvB,MAAI,SAAS;AAEb,QAAM,OAAO,MAAqD;AAChE,UAAM,cAAc,QAAQ,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE;AACpD,UAAM,YAAY,QAAQ,SAAS,KAAK,eAAe,QAAQ;AAC/D,UAAM,QACJ,WAAW,YAAY,MAAM,aAAa,CAAC,QAAQ,UAAU,YAAY,WAAW;AACtF,WAAO;AAAA,MACL,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,QAAQ,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE;AAAA,MACtC;AAAA,MACA,aAAa,QAAQ;AAAA,MACrB;AAAA,MACA,WAAW;AAAA,MACX,MAAM;AAAA,MACN,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,YAAY,MAAM,kBAAkB;AAAA,MACpC,WAAW;AAAA,MACX,QAAQ,EAAE,QAAQ,WAAW,OAAO,OAAO,QAAQ,KAAK,UAAU,KAAK;AAAA,IACzE;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,QAAQ,KAAK,UAAU;AAAA,IACvB,MAAM,OAAO;AACX,aAAO,KAAK;AAAA,IACd;AAAA,IACA,MAAM,QAAQ;AACZ,aAAO,KAAK;AAAA,IACd;AAAA,IACA,KAAK,SAAS,UAAU;AACtB,YAAM,WAAW,UAAU,cAAc;AACzC,YAAM,QAAQ,YAAY,MAAM,QAAQ,KAAK,CAAC,GAAG,QAAQ;AACzD,cAAQ,KAAK,CAAC;AACd,aAAO,MAAM,cAAc,KAAK;AAAA,IAClC;AAAA,IACA,MAAM,YAAY,GAAW;AAC3B,cAAQ;AACR,aAAO,KAAK;AAAA,IACd;AAAA,IACA,MAAM,WAAW,SAAwB;AACvC,uBAAiB;AACjB,YAAM,KAAK,QAAQ,KAAK,CAAC,MAAM,EAAE,IAAI;AACrC,UAAI,GAAI,IAAG,SAAS;AACpB,aAAO,KAAK;AAAA,IACd;AAAA,IACA,MAAM,QAAQ;AACZ,eAAS;AACT,YAAM,KAAK,aACP,KAAK,WAAW,CAAC,cAAc,CAAC,IAC/B,EAAE,MAAM,KAAK;AAClB,aAAO,KAAK;AAAA,IACd;AAAA,IACA,MAAM,SAAS;AACb,aAAO,KAAK;AAAA,IACd;AAAA,IACA,MAAM,UAAuB;AAC3B,aAAO,CAAC;AAAA,IACV;AAAA,IACA,aAAa,KAAa;AACxB,UAAI,OAAO,WAAW,YAAa,QAAO,KAAK,KAAK,UAAU,UAAU;AAAA,IAC1E;AAAA,IACA,UAAU,YAAoB;AAC5B,aAAO,4BAA4B,UAAU;AAAA,IAC/C;AAAA,EACF;AACF;AAaO,SAAS,cAIuC;AACrD,QAAM,SAAS,wBAAwB;AACvC,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,IAAI,kBAA+C,MAAM;AAClE;;;ACvpBA,IAAM,WAAW;AACjB,IAAM,iBAAiB;AACvB,IAAM,kBAAkB;AAOxB,SAAS,iBAAyB;AAChC,QAAM,IAAI,WAAW;AACrB,MAAI,KAAK,OAAO,EAAE,eAAe,WAAY,QAAO,EAAE,WAAW;AACjE,QAAM,IAAI,IAAI,WAAW,EAAE;AAC3B,MAAI,KAAK,OAAO,EAAE,oBAAoB,WAAY,GAAE,gBAAgB,CAAC;AAAA,MAChE,UAAS,IAAI,GAAG,IAAI,IAAI,IAAK,GAAE,CAAC,IAAI,KAAK,MAAM,KAAK,OAAO,IAAI,GAAG;AACvE,IAAE,CAAC,IAAK,EAAE,CAAC,IAAI,KAAQ;AACvB,IAAE,CAAC,IAAK,EAAE,CAAC,IAAI,KAAQ;AACvB,QAAM,IAAI,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC;AAC9D,SAAO,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC;AACnI;AAgBA,IAAI,YAAuD;AAE3D,eAAe,WAAW,QAAoC;AAC5D,MAAI,aAAa,UAAU,WAAW,OAAQ,QAAO,UAAU;AAE/D,QAAM,UAAU,IAAI,YAAY,EAAE,OAAO,MAAM;AAC/C,QAAM,MAAM,IAAI,YAAY,QAAQ,UAAU;AAC9C,MAAI,WAAW,GAAG,EAAE,IAAI,OAAO;AAE/B,QAAM,MAAM,MAAM,OAAO,OAAO,UAAU,OAAO,KAAK,EAAE,MAAM,QAAQ,MAAM,UAAU,GAAG,OAAO;AAAA,IAC9F;AAAA,EACF,CAAC;AAED,cAAY,EAAE,QAAQ,IAAI;AAC1B,SAAO;AACT;AAEA,eAAe,UACb,QACA,MACA,UACA,OACA,WACiB;AACjB,QAAM,MAAM,MAAM,WAAW,MAAM;AACnC,QAAM,OAAO,IAAI,YAAY,EAAE,OAAO,GAAG,IAAI,IAAI,QAAQ,IAAI,KAAK,IAAI,SAAS,EAAE;AACjF,QAAM,MAAM,IAAI,YAAY,KAAK,UAAU;AAC3C,MAAI,WAAW,GAAG,EAAE,IAAI,IAAI;AAC5B,QAAM,MAAM,MAAM,OAAO,OAAO,KAAK,QAAQ,KAAK,GAAG;AACrD,SAAO,MAAM,KAAK,IAAI,WAAW,GAAG,CAAC,EAClC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAC1C,KAAK,EAAE;AACZ;AAOO,IAAM,sBAAN,MAAqD;AAAA,EA2B1D,YAAY,MAAmB;AA1B/B,wBAAQ;AACR,wBAAQ;AACR,wBAAQ;AACR,wBAAQ;AACR,wBAAQ;AACR,wBAAQ;AACR,wBAAQ;AACR,wBAAQ;AACR,wBAAQ,oBAAmB,oBAAI,IAG7B;AACF,wBAAQ,cAA0D;AAClE,wBAAQ,mBAA8D,CAAC;AACvE,wBAAQ,iBAAoC;AAC5C,wBAAQ,0BAAqE,CAAC;AAC9E,wBAAQ,YAAW;AACnB,wBAAQ,mBAAqD,CAAC;AAC9D,wBAAQ,4BAA+E,CAAC;AACxF,wBAAQ,mBAAkB,oBAAI,IAAiD;AAC/E,wBAAQ;AACR,wBAAQ;AAGR;AAAA,wBAAS;AAGP,SAAK,YAAY,KAAK;AACtB,SAAK,SAAS,KAAK;AACnB,SAAK,cAAc,KAAK;AACxB,SAAK,QAAQ,KAAK;AAClB,SAAK,aAAa,KAAK,aAAa;AACpC,SAAK,gBAAgB,KAAK;AAC1B,SAAK,kBAAkB,KAAK,kBAAkB;AAC9C,SAAK,kBAAkB,KAAK;AAC5B,SAAK,YAAY,KAAK,YAAY,CAAC;AAGnC,QAAI,KAAK,YAAY;AACnB,WAAK,UAAU,oBAAoB;AAAA,QACjC,SAAS,KAAK;AAAA,QACd,QAAQ,CAAC,iBAA2B,KAAK,OAAO,YAAY;AAAA,MAC9D,CAAC;AAAA,IACH;AAEA,SAAK,kBAAkB,KAAK,cAAc,KAAK,IAAI;AACnD,WAAO,iBAAiB,WAAW,KAAK,eAAe;AAAA,EACzD;AAAA;AAAA,EAGA,IAAI,WAAmB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,QAA4B;AAC9B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,OAA2B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA,EAIA,IAAI,YAA8D;AAChE,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,cAAc,IAAqD;AACjE,SAAK,gBAAgB,KAAK,EAAE;AAC5B,WAAO,MAAM;AACX,YAAM,MAAM,KAAK,gBAAgB,QAAQ,EAAE;AAC3C,UAAI,QAAQ,GAAI,MAAK,gBAAgB,OAAO,KAAK,CAAC;AAAA,IACpD;AAAA,EACF;AAAA;AAAA,EAGA,gBAAsB;AACpB,SAAK,KAAK,kBAAkB,CAAC,CAAC;AAAA,EAChC;AAAA;AAAA,EAGA,kBAAwB;AACtB,SAAK,KAAK,oBAAoB,CAAC,CAAC;AAAA,EAClC;AAAA;AAAA,EAGA,kBAAsC;AACpC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,qBAAqB,UAA2D;AAC9E,SAAK,uBAAuB,KAAK,QAAQ;AACzC,WAAO,MAAM;AACX,WAAK,yBAAyB,KAAK,uBAAuB,OAAO,CAAC,OAAO,OAAO,QAAQ;AAAA,IAC1F;AAAA,EACF;AAAA;AAAA,EAGA,YAAqB;AACnB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,cAAc,UAAkD;AAC9D,SAAK,gBAAgB,KAAK,QAAQ;AAClC,WAAO,MAAM;AACX,WAAK,kBAAkB,KAAK,gBAAgB,OAAO,CAAC,OAAO,OAAO,QAAQ;AAAA,IAC5E;AAAA,EACF;AAAA;AAAA,EAGA,oBAA0B;AACxB,SAAK,KAAK,sBAAsB,CAAC,CAAC;AAAA,EACpC;AAAA;AAAA,EAGA,KAAK,OAAe,SAAwB;AAC1C,SAAK,KAAK,cAAc,EAAE,OAAO,QAAQ,CAAC;AAAA,EAC5C;AAAA;AAAA,EAGA,UAAU,OAAe,IAA8C;AACrE,UAAM,OAAO,KAAK,gBAAgB,IAAI,KAAK,KAAK,CAAC;AACjD,SAAK,KAAK,EAAE;AACZ,SAAK,gBAAgB,IAAI,OAAO,IAAI;AACpC,WAAO,MAAM;AACX,YAAM,UAAU,KAAK,gBAAgB,IAAI,KAAK;AAC9C,UAAI,CAAC,QAAS;AACd,YAAM,MAAM,QAAQ,QAAQ,EAAE;AAC9B,UAAI,QAAQ,GAAI,SAAQ,OAAO,KAAK,CAAC;AAAA,IACvC;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,OAAkC,SAAiB,MAAsB;AAC3E,SAAK,KAAK,OAAO,EAAE,OAAO,SAAS,MAAM,QAAQ,KAAK,CAAC;AAAA,EACzD;AAAA;AAAA,EAGA,MAAM,eAAe,OAAe,QAAwC;AAC1E,WAAO,IAAI,QAAuB,CAAC,SAAS,WAAW;AACrD,YAAM,UAAU,WAAW,MAAM;AAC/B,aAAK,iBAAiB,OAAO,KAAK;AAClC,eAAO,IAAI,MAAM,2BAA2B,CAAC;AAAA,MAC/C,GAAG,GAAK;AAER,WAAK,iBAAiB,IAAI,OAAO;AAAA,QAC/B,SAAS,CAAC,MAAqB;AAC7B,uBAAa,OAAO;AACpB,kBAAQ,CAAC;AAAA,QACX;AAAA,QACA,QAAQ,CAAC,MAAa;AACpB,uBAAa,OAAO;AACpB,iBAAO,CAAC;AAAA,QACV;AAAA,MACF,CAAC;AAED,WAAK,KAAK,mBAAmB,EAAE,OAAO,OAAO,CAAC;AAAA,IAChD,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,OAAO,cAAyC;AACpD,QAAI,CAAC,gBAAgB,KAAK,cAAc,KAAK,IAAI,IAAI,KAAK,WAAW,YAAY,KAAO;AACtF,aAAO,KAAK,WAAW;AAAA,IACzB;AACA,UAAM,IAAI,MAAM,mEAA8D;AAAA,EAChF;AAAA;AAAA,EAGA,IAAI,UAAsB;AACxB,WAAO;AAAA,MACL,KAAK,CAAC,QAAgB,KAAK,eAAe,OAAO,GAAG;AAAA,MACpD,KAAK,CAAC,KAAa,UAAmB,KAAK,eAAe,OAAO,KAAK,KAAK,EAAE,KAAK,MAAM;AAAA,MAAC,CAAC;AAAA,MAC1F,QAAQ,CAAC,QAAgB,KAAK,eAAe,UAAU,GAAG,EAAE,KAAK,MAAM;AAAA,MAAC,CAAC;AAAA,IAC3E;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,KAAY;AACd,UAAM,MAAM,CAAC,MAAc,QAAgB,SAAmB,KAAK,iBAAiB,MAAM,QAAQ,IAAI;AACtG,WAAO;AAAA,MACL,WAAW,MAA4B;AACrC,cAAM,OAAO,kBAAkB,mBAAmB,IAAI,CAAC;AACvD,eAAO;AAAA,UACL,QAAQ,CAAC,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,UACxC,KAAK,CAAC,OAAO,IAAI,GAAG,IAAI,IAAI,mBAAmB,EAAE,CAAC,IAAI,KAAK;AAAA,UAC3D,MAAM,CAAC,SAAyB;AAC9B,kBAAM,KAAK,IAAI,gBAAgB;AAC/B,gBAAI,MAAM,SAAS,KAAM,IAAG,IAAI,SAAS,OAAO,KAAK,KAAK,CAAC;AAC3D,gBAAI,MAAM,OAAQ,IAAG,IAAI,UAAU,KAAK,MAAM;AAC9C,gBAAI,MAAM,KAAM,IAAG,IAAI,QAAQ,KAAK,IAAI;AACxC,gBAAI,MAAM,KAAM,IAAG,IAAI,QAAQ,MAAM;AACrC,kBAAM,SAAS,GAAG,SAAS,IAAI,IAAI,GAAG,SAAS,CAAC,KAAK;AACrD,mBAAO,IAAI,GAAG,IAAI,GAAG,MAAM,IAAI,KAAK;AAAA,UACtC;AAAA,UACA,QAAQ,CAAC,IAAI,UAAU,IAAI,GAAG,IAAI,IAAI,mBAAmB,EAAE,CAAC,IAAI,SAAS,KAAK;AAAA,UAC9E,QAAQ,CAAC,OAAQ,IAAI,GAAG,IAAI,IAAI,mBAAmB,EAAE,CAAC,IAAI,QAAQ,EAAuB,KAAK,MAAM;AAAA,UAAC,CAAC;AAAA,QACxG;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,KAAY;AACd,UAAM,WAAW,CAAC,SAChB,KAAK,iBAAiB,2BAA2B,QAAQ,IAAI;AAC/D,UAAM,gBAAgB,CAAC,SAA2C,KAAK,gBAAgB,IAAI;AAC3F,WAAO;AAAA,MACL;AAAA;AAAA;AAAA;AAAA;AAAA,MAKA,OAAO,OAAO,MAAoD;AAChE,cAAM,WAAW,MAAM,cAAc,IAAI;AACzC,cAAM,SAAS,SAAS,MAAM,UAAU;AACxC,YAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,iCAAiC;AAC9D,cAAM,UAAU,IAAI,YAAY;AAChC,YAAI,SAAS;AACb,YAAI;AACF,qBAAS;AACP,kBAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,gBAAI,KAAM;AACV,sBAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AAChD,kBAAM,SAAS,OAAO,MAAM,MAAM;AAClC,qBAAS,OAAO,IAAI,KAAK;AACzB,uBAAW,SAAS,QAAQ;AAC1B,oBAAM,WAAW,MAAM,MAAM,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,WAAW,OAAO,CAAC;AACpE,kBAAI,CAAC,SAAU;AACf,oBAAM,UAAU,SAAS,MAAM,CAAC,EAAE,KAAK;AACvC,kBAAI,CAAC,WAAW,YAAY,SAAU;AACtC,kBAAI;AACJ,kBAAI;AACF,yBAAS,KAAK,MAAM,OAAO;AAAA,cAC7B,QAAQ;AACN;AAAA,cACF;AACA,oBAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF,UAAE;AAGA,gBAAM,OAAO,OAAO,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,WAAwB;AAC1B,WAAO;AAAA,MACL,KAAK,OAAO,EAAE,GAAG,KAAK,UAAU;AAAA,MAChC,KAAK,CAAC,gBAAyC;AAC7C,aAAK,YAAY,EAAE,GAAG,KAAK,WAAW,GAAG,YAAY;AACrD,aAAK,KAAK,kBAAkB,EAAE,UAAU,KAAK,UAAU,CAAC;AAAA,MAC1D;AAAA,MACA,gBAAgB,CAAC,OAAoD;AACnE,aAAK,yBAAyB,KAAK,EAAE;AACrC,eAAO,MAAM;AACX,eAAK,2BAA2B,KAAK,yBAAyB,OAAO,CAAC,OAAO,OAAO,EAAE;AAAA,QACxF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,OAAO,SAAiB,MAA6D;AACzF,SAAK,KAAK,gBAAgB;AAAA,MACxB;AAAA,MACA,OAAO,MAAM,SAAS;AAAA,MACtB,UAAU,MAAM,YAAY;AAAA,IAC9B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,gBAAgB,MAAkC;AAC9D,QAAI,CAAC,KAAK,eAAe;AACvB,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AACA,UAAM,UAAkC,EAAE,gBAAgB,mBAAmB;AAC7E,QAAI,KAAK,iBAAiB;AACxB,cAAQ,eAAe,IAAI,UAAU,KAAK,eAAe;AAAA,IAC3D;AACA,UAAM,WAAW,MAAM,MAAM,GAAG,KAAK,aAAa,yBAAyB;AAAA,MACzE,QAAQ;AAAA,MACR;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AACD,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,MAAM,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAClD,YAAM,IAAI,MAAO,IAA+B,WAAW,0BAA0B;AAAA,IACvF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,iBAAiB,MAAc,QAAgB,MAAkC;AAC7F,QAAI,CAAC,KAAK,eAAe;AACvB,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AACA,UAAM,MAAM,GAAG,KAAK,aAAa,GAAG,IAAI;AACxC,UAAM,UAAkC,EAAE,gBAAgB,mBAAmB;AAC7E,QAAI,KAAK,iBAAiB;AACxB,cAAQ,eAAe,IAAI,UAAU,KAAK,eAAe;AAAA,IAC3D;AACA,UAAM,OAAoB,EAAE,QAAQ,QAAQ;AAC5C,QAAI,SAAS,UAAa,WAAW,SAAS,WAAW,UAAU;AACjE,WAAK,OAAO,KAAK,UAAU,IAAI;AAAA,IACjC;AACA,UAAM,WAAW,MAAM,MAAM,KAAK,IAAI;AACtC,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,MAAM,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAClD,YAAM,IAAI,MAAO,IAA+B,WAAW,WAAW,MAAM,IAAI,IAAI,SAAS;AAAA,IAC/F;AACA,QAAI,WAAW,SAAS,WAAW,UAAU,WAAW,SAAS;AAC/D,YAAM,SAAS,MAAM,SAAS,KAAK;AACnC,aAAQ,OAAmC;AAAA,IAC7C;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,eAAe,QAAgB,KAAa,OAAmC;AACrF,WAAO,KAAK,iBAAiB,uBAAuB,mBAAmB,GAAG,CAAC,IAAI,QAAQ,KAAK;AAAA,EAC9F;AAAA,EAEQ,cAAc,OAA2B;AAC/C,UAAM,MAAM,MAAM;AAClB,QAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,IAAI,aAAa,SAAU;AAElE,YAAQ,IAAI,MAAM;AAAA,MAChB,KAAK,gBAAgB;AACnB,cAAM,UAAU,IAAI;AACpB,aAAK,SAAS,QAAQ;AACtB,mBAAW,YAAY,KAAK,iBAAiB;AAC3C,mBAAS,QAAQ,KAAK;AAAA,QACxB;AACA;AAAA,MACF;AAAA,MACA,KAAK,mBAAmB;AACtB,cAAM,UAAU,IAAI;AACpB,cAAM,YAAY,KAAK,gBAAgB,IAAI,QAAQ,KAAK;AACxD,YAAI,WAAW;AACb,gBAAM,MAAmB;AAAA,YACvB,OAAO,QAAQ;AAAA,YACf,SAAS,QAAQ;AAAA,YACjB,WAAW,QAAQ;AAAA,UACrB;AACA,qBAAW,YAAY,WAAW;AAChC,qBAAS,GAAG;AAAA,UACd;AAAA,QACF;AACA;AAAA,MACF;AAAA,MACA,KAAK,kBAAkB;AACrB,cAAM,UAAU,IAAI;AACpB,cAAM,UAAU,KAAK,iBAAiB,IAAI,QAAQ,KAAK;AACvD,YAAI,SAAS;AACX,eAAK,iBAAiB,OAAO,QAAQ,KAAK;AAC1C,kBAAQ,QAAQ;AAAA,YACd,OAAO,QAAQ;AAAA,YACf,SAAS,QAAQ;AAAA,YACjB,YAAY,QAAQ;AAAA,YACpB,WAAW,QAAQ;AAAA,UACrB,CAAC;AAAA,QACH;AACA;AAAA,MACF;AAAA,MACA,KAAK,sBAAsB;AACzB,cAAM,UAAU,IAAI;AACpB,aAAK,aAAa;AAAA,UAChB,OAAO,QAAQ;AAAA,UACf,WAAW,KAAK,IAAI,IAAI,QAAQ,YAAY;AAAA,QAC9C;AACA;AAAA,MACF;AAAA,MACA,KAAK,wBAAwB;AAC3B,cAAM,WAAW,IAAI,SAAS;AAC9B,YAAI,UAAU;AACZ,eAAK,gBAAgB;AACrB,eAAK,uBAAuB,QAAQ,CAAC,OAAO,GAAG,QAAQ,CAAC;AAAA,QAC1D;AACA;AAAA,MACF;AAAA,MACA,KAAK,gBAAgB;AACnB,cAAM,OAAO,CAAC,CAAC,IAAI,SAAS;AAC5B,YAAI,SAAS,KAAK,UAAU;AAC1B,eAAK,WAAW;AAChB,eAAK,gBAAgB,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;AAAA,QAC/C;AACA;AAAA,MACF;AAAA,MACA,KAAK,0BAA0B;AAC7B,cAAM,IAAI,IAAI;AACd,aAAK,YAAY,EAAE,GAAG,EAAE,SAAS;AACjC,mBAAW,MAAM,KAAK,0BAA0B;AAC9C,cAAI;AACF,eAAG,EAAE,GAAG,KAAK,UAAU,CAAC;AAAA,UAC1B,QAAQ;AAAA,UAER;AAAA,QACF;AACA;AAAA,MACF;AAAA,MACA,KAAK;AAAA,MACL,KAAK;AAEH;AAAA,IACJ;AAAA,EACF;AAAA,EAEQ,KAAK,MAAc,SAAwC;AAEjE,SAAK,aAAa,MAAM,OAAO,EAAE,KAAK,CAAC,QAAQ;AAC7C,aAAO,OAAO,YAAY,KAAK,GAAG;AAAA,IACpC,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,aACZ,MACA,SACwB;AACxB,UAAM,QAAQ,eAAe;AAC7B,UAAM,YAAY,KAAK,IAAI;AAE3B,QAAI;AACJ,QAAI,KAAK,aAAa;AACpB,cAAQ,MAAM,UAAU,KAAK,aAAa,MAAM,KAAK,WAAW,OAAO,SAAS;AAAA,IAClF,OAAO;AACL,cAAQ;AAAA,IACV;AAEA,WAAO;AAAA,MACL,UAAU;AAAA,MACV;AAAA,MACA,UAAU,KAAK;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,UAAgB;AACd,WAAO,oBAAoB,WAAW,KAAK,eAAe;AAC1D,SAAK,gBAAgB,SAAS;AAC9B,SAAK,gBAAgB,MAAM;AAC3B,eAAW,CAAC,EAAE,OAAO,KAAK,KAAK,kBAAkB;AAC/C,cAAQ,OAAO,IAAI,MAAM,kBAAkB,CAAC;AAAA,IAC9C;AACA,SAAK,iBAAiB,MAAM;AAAA,EAC9B;AACF;AAeO,IAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAiC;AAC/B,WAAO,IAAI,QAAyB,CAAC,SAAS,WAAW;AACvD,YAAM,UAAU,WAAW,MAAM;AAC/B,eAAO,oBAAoB,WAAW,OAAO;AAC7C,eAAO,IAAI,MAAM,2EAAsE,CAAC;AAAA,MAC1F,GAAG,eAAe;AAElB,YAAM,UAAU,CAAC,UAAwB;AACvC,cAAM,MAAM,MAAM;AAClB,YAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,IAAI,aAAa,SAAU;AAClE,YAAI,IAAI,SAAS,OAAQ;AAEzB,qBAAa,OAAO;AACpB,eAAO,oBAAoB,WAAW,OAAO;AAE7C,cAAM,UAAU,IAAI;AACpB,cAAM,MAAM,IAAI,oBAAoB,OAAO;AAC3C,gBAAQ,GAAG;AAAA,MACb;AAEA,aAAO,iBAAiB,WAAW,OAAO;AAG1C,YAAM,WAA0B;AAAA,QAC9B,UAAU;AAAA,QACV,MAAM;AAAA,QACN,UAAU;AAAA;AAAA,QACV,OAAO,eAAe;AAAA,QACtB,WAAW,KAAK,IAAI;AAAA,QACpB,OAAO;AAAA;AAAA,QACP,SAAS,EAAE,iBAAiB,IAAI;AAAA,MAClC;AAEA,aAAO,OAAO,YAAY,UAAU,GAAG;AAAA,IACzC,CAAC;AAAA,EACH;AACF;;;AC5mBA,IAAM,qBAAqB;AAC3B,IAAM,gBAA6B;AAEnC,SAAS,cAAc,OAAsC;AAC3D,SAAO,UAAU,WAAW,UAAU,UAAU,UAAU;AAC5D;AAMO,SAAS,kBAA+B;AAC7C,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,MAAI;AACF,UAAM,QAAQ,IAAI,gBAAgB,OAAO,SAAS,MAAM,EAAE,IAAI,OAAO;AACrE,WAAO,cAAc,KAAK,IAAI,QAAQ;AAAA,EACxC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAWO,SAAS,cACd,IACA,MACY;AACZ,MAAI,OAAO,WAAW,YAAa,QAAO,MAAM;AAAA,EAAC;AACjD,QAAM,WAAW,CAAC,UAA8B;AAC9C,QAAI,MAAM,WAAW,OAAO,OAAQ;AACpC,QAAI,MAAM,iBAAiB,MAAM,WAAW,KAAK,cAAe;AAChE,UAAM,OAAgB,MAAM;AAC5B,QAAI,OAAO,SAAS,YAAY,SAAS,KAAM;AAC/C,UAAM,UAAU;AAChB,QAAI,QAAQ,SAAS,mBAAoB;AACzC,QAAI,CAAC,cAAc,QAAQ,KAAK,EAAG;AACnC,OAAG,QAAQ,KAAK;AAAA,EAClB;AACA,SAAO,iBAAiB,WAAW,QAAQ;AAC3C,SAAO,MAAM,OAAO,oBAAoB,WAAW,QAAQ;AAC7D;AAOO,SAAS,iBAAiB,OAA0B;AACzD,MAAI,OAAO,aAAa,YAAa;AACrC,MAAI,UAAU,SAAS;AACrB,WAAO,SAAS,gBAAgB,QAAQ;AAAA,EAC1C,OAAO;AACL,aAAS,gBAAgB,QAAQ,QAAQ;AAAA,EAC3C;AACF;AAMO,SAAS,kBAA8B;AAC5C,mBAAiB,gBAAgB,CAAC;AAClC,SAAO,cAAc,gBAAgB;AACvC;;;ACzEO,IAAM,sBAAsB;AAoB5B,SAAS,mBACd,IACA,MACY;AACZ,MAAI,OAAO,WAAW,YAAa,QAAO,MAAM;AAAA,EAAC;AACjD,QAAM,WAAW,CAAC,UAA8B;AAC9C,QAAI,MAAM,WAAW,OAAO,OAAQ;AACpC,QAAI,MAAM,iBAAiB,MAAM,WAAW,KAAK,cAAe;AAChE,UAAM,OAAgB,MAAM;AAC5B,QAAI,OAAO,SAAS,YAAY,SAAS,KAAM;AAC/C,UAAM,UAAU;AAChB,QAAI,QAAQ,SAAS,oBAAqB;AAC1C,QAAI,OAAO,QAAQ,eAAe,YAAY,CAAC,QAAQ,WAAY;AACnE,OAAG;AAAA,MACD,YAAY,QAAQ;AAAA,MACpB,MAAM,OAAO,QAAQ,SAAS,WAAW,QAAQ,OAAO;AAAA,MACxD,SAAS,QAAQ;AAAA,IACnB,CAAC;AAAA,EACH;AACA,SAAO,iBAAiB,WAAW,QAAQ;AAC3C,SAAO,MAAM,OAAO,oBAAoB,WAAW,QAAQ;AAC7D;",
|
|
4
|
+
"sourcesContent": ["// packages/widget-sdk/src/service.ts\n\nimport type { WidgetServiceClient } from './types';\n\nexport interface CreateServiceClientOptions {\n baseUrl: string;\n getJwt: (forceRefresh?: boolean) => Promise<string>;\n fetchImpl?: typeof fetch;\n}\n\nexport function createServiceClient(opts: CreateServiceClientOptions): WidgetServiceClient {\n const fetchImpl = opts.fetchImpl ?? globalThis.fetch.bind(globalThis);\n const baseUrl = opts.baseUrl.replace(/\\/+$/, '');\n\n async function call(path: string, init: RequestInit, token: string): Promise<Response> {\n const url = `${baseUrl}${path.startsWith('/') ? path : `/${path}`}`;\n const headers = new Headers(init.headers);\n headers.set('authorization', `Bearer ${token}`);\n if (init.body && !headers.has('content-type')) {\n headers.set('content-type', 'application/json');\n }\n return fetchImpl(url, { ...init, headers });\n }\n\n return {\n async fetch<T = unknown>(path: string, init: RequestInit = {}): Promise<T> {\n let token = await opts.getJwt(false);\n let res = await call(path, init, token);\n\n if (res.status === 401) {\n token = await opts.getJwt(true);\n res = await call(path, init, token);\n }\n\n if (!res.ok) {\n let body: { message?: string } = {};\n try {\n body = (await res.json()) as { message?: string };\n } catch {\n // non-JSON body \u2014 ignore\n }\n throw new Error(`Widget service request failed (${res.status}): ${body.message ?? ''}`);\n }\n return (await res.json()) as T;\n },\n };\n}\n", "/**\n * Flow feedback: the bug icon every flow carries (flowstate-docs plan 2026-11-09-flow-feedback).\n *\n * The session client mounts this when the session state says `feedbackEnabled`. A round button\n * sits in the corner of the flow's own page. A tap opens a sheet (one field, one button) and\n * takes a screenshot of the page as the person sees it. The sheet says each step: the picture is\n * being taken, it is attached (or there is none, with a way to take it again), the note is\n * sending, it is sent. The note goes out through the flow's own session\n * (`session.request('feedback', \u2026)`).\n *\n * It lives in the flow's document, in a shadow root, so it takes the flow's look instead of\n * bringing one: the flow's fonts by inheritance, and its ground, ink, main button and corner\n * radius read from the page (`readFlowTheme`). A flow pins any role with a `--fs-feedback-*`\n * custom property on `:root`, and opts a page out with `<html data-fs-feedback=\"off\">`.\n */\n\nexport interface FlowFeedbackTheme {\n ground: string;\n surface: string;\n ink: string;\n inkDim: string;\n line: string;\n fill: string;\n cta: string;\n ctaInk: string;\n fontBody: string;\n fontDisplay: string;\n displayWeight: string;\n /** Letter spacing of the flow's titles, in em, so it scales to the sheet's title size. */\n displayTracking: string;\n fontButton: string;\n buttonWeight: string;\n buttonTracking: string;\n buttonTransform: string;\n radius: string;\n buttonRadius: string;\n dark: boolean;\n}\n\nexport interface FlowFeedbackReport {\n message: string;\n screenshot?: string;\n pageKey?: string;\n pagePath: string;\n viewport: { width: number; height: number };\n}\n\nexport interface FlowFeedbackOptions {\n /** Sends the report. The session client passes `session.request('feedback', report)`. */\n submit: (report: FlowFeedbackReport) => Promise<unknown>;\n /** Renders the page to a JPEG data URL, or null when it cannot. Defaults to the built-in\n * rasterizer; tests and unusual hosts pass their own. It runs while the sheet is already\n * open, so a host's own capture must leave the feedback host element out of the picture. */\n capture?: () => Promise<string | null>;\n /** Called once the icon is on the page, so a host page can hide its own fallback. */\n onReady?: () => void;\n}\n\nexport interface FlowFeedbackHandle {\n /** The element the shadow root hangs from. */\n readonly host: HTMLElement;\n /** Re-read the flow's look (the page under the icon may have changed). */\n refresh(): void;\n open(): Promise<void>;\n close(): void;\n unmount(): void;\n}\n\nexport const FEEDBACK_HOST_ATTR = 'data-fs-feedback-host';\n/** The validator on the service accepts 160 KB of JPEG; stay under it with room for headers. */\nexport const SCREENSHOT_MAX_BYTES = 150_000;\nexport const SCREENSHOT_MAX_SIDE = 1280;\n/** The sheet is open and the person is typing while the picture is taken, so a slow phone gets\n * time (FB-517: at 4 s the picture was lost on a real phone). */\nconst CAPTURE_TIMEOUT_MS = 15_000;\n/** A note sent while the picture is still being taken waits this long for it, then goes alone. */\nconst SEND_WAIT_MS = 4000;\n/** How long \"Feedback sent\" stays before the sheet closes on its own. */\nconst SENT_HOLD_MS = 1600;\n\n// \u2500\u2500 colour \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype Rgba = [number, number, number, number];\n\nexport function parseColor(raw: string | null | undefined): Rgba | null {\n const value = (raw ?? '').trim().toLowerCase();\n if (!value || value === 'transparent') return null;\n const hex = /^#([0-9a-f]{3,8})$/.exec(value);\n if (hex) {\n const h = hex[1];\n const full =\n h.length <= 4\n ? h\n .split('')\n .map((c) => c + c)\n .join('')\n : h;\n if (full.length !== 6 && full.length !== 8) return null;\n const n = (i: number) => parseInt(full.slice(i, i + 2), 16);\n return [n(0), n(2), n(4), full.length === 8 ? n(6) / 255 : 1];\n }\n const fn = /^rgba?\\(([^)]+)\\)$/.exec(value);\n if (!fn) return null;\n const parts = fn[1].split(/[\\s,/]+/).filter(Boolean);\n if (parts.length < 3) return null;\n const channel = (p: string) => (p.endsWith('%') ? (parseFloat(p) * 255) / 100 : parseFloat(p));\n const alpha =\n parts[3] === undefined\n ? 1\n : parts[3].endsWith('%')\n ? parseFloat(parts[3]) / 100\n : parseFloat(parts[3]);\n const out: Rgba = [channel(parts[0]), channel(parts[1]), channel(parts[2]), alpha];\n return out.some((v) => Number.isNaN(v)) ? null : out;\n}\n\nconst opaque = (c: Rgba | null): c is Rgba => c !== null && c[3] >= 0.9;\nconst css = (c: Rgba, alpha = c[3]): string =>\n alpha >= 1\n ? `rgb(${Math.round(c[0])}, ${Math.round(c[1])}, ${Math.round(c[2])})`\n : `rgba(${Math.round(c[0])}, ${Math.round(c[1])}, ${Math.round(c[2])}, ${alpha})`;\nconst mix = (a: Rgba, b: Rgba, t: number): Rgba => [\n a[0] + (b[0] - a[0]) * t,\n a[1] + (b[1] - a[1]) * t,\n a[2] + (b[2] - a[2]) * t,\n 1,\n];\n/** WCAG relative luminance. */\nexport const luminance = (c: Rgba): number => {\n const lin = (v: number) => {\n const s = v / 255;\n return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n };\n return 0.2126 * lin(c[0]) + 0.7152 * lin(c[1]) + 0.0722 * lin(c[2]);\n};\nconst contrast = (a: Rgba, b: Rgba): number => {\n const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);\n return (hi + 0.05) / (lo + 0.05);\n};\nconst WHITE: Rgba = [255, 255, 255, 1];\nconst BLACK: Rgba = [17, 17, 20, 1];\n\n// \u2500\u2500 reading the flow's look \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst styleOf = (el: Element): CSSStyleDeclaration => getComputedStyle(el);\nconst rootVar = (doc: Document, names: string[]): string => {\n for (const el of [doc.documentElement, doc.body]) {\n if (!el) continue;\n const style = styleOf(el);\n for (const name of names) {\n const value = style.getPropertyValue(name).trim();\n if (value) return value;\n }\n }\n return '';\n};\nconst visible = (el: Element): boolean => {\n const rect = el.getBoundingClientRect();\n const style = styleOf(el);\n return style.display !== 'none' && style.visibility !== 'hidden' && rect.width * rect.height >= 0;\n};\nconst inOurs = (el: Element): boolean => el.closest(`[${FEEDBACK_HOST_ATTR}]`) !== null;\n\n/** The flow's main button: the largest visible button whose fill is solid and is not the ground. */\nconst primaryButton = (doc: Document, ground: Rgba): Element | null => {\n let best: Element | null = null;\n let bestArea = -1;\n doc\n .querySelectorAll(\n 'button, [role=\"button\"], input[type=\"submit\"], a[class*=\"btn\"], a[class*=\"cta\"]'\n )\n .forEach((el) => {\n if (inOurs(el) || !visible(el)) return;\n const fill = parseColor(styleOf(el).backgroundColor);\n if (!opaque(fill) || contrast(fill, ground) < 1.25) return;\n const rect = el.getBoundingClientRect();\n const area = rect.width * rect.height;\n if (area > bestArea) {\n best = el;\n bestArea = area;\n }\n });\n return best;\n};\n\ninterface TextSample {\n family: string;\n size: number;\n chars: number;\n style: CSSStyleDeclaration;\n heading: boolean;\n}\nconst TEXT_SAMPLE_LIMIT = 600;\nconst DISPLAY_MIN_PX = 22;\nconst sizeOf = (style: CSSStyleDeclaration): number => {\n const n = parseFloat(style.fontSize);\n return Number.isFinite(n) && n > 0 ? n : 16;\n};\n/**\n * The text the flow is drawing right now: every element that holds words of its own, with its\n * font. Flows set their fonts on inner elements more often than on <body>, so the body's own\n * font is the browser default on half of them; the words on the page are the reliable source.\n */\nconst textSamples = (doc: Document): TextSample[] => {\n const out: TextSample[] = [];\n const all = doc.body.querySelectorAll('*');\n for (let i = 0; i < all.length && out.length < TEXT_SAMPLE_LIMIT; i += 1) {\n const el = all[i];\n if (el.tagName === 'SCRIPT' || el.tagName === 'STYLE' || inOurs(el)) continue;\n let chars = 0;\n for (const node of Array.from(el.childNodes)) {\n if (node.nodeType === 3) chars += (node.textContent ?? '').trim().length;\n }\n if (chars === 0 || !visible(el)) continue;\n const style = styleOf(el);\n if (!style.fontFamily) continue;\n out.push({\n family: style.fontFamily,\n size: sizeOf(style),\n chars,\n style,\n heading: /^H[1-3]$/.test(el.tagName),\n });\n }\n return out;\n};\n/** A letter spacing as em of its own font size, held inside a range that stays legible. */\nconst trackingEm = (\n style: CSSStyleDeclaration | null,\n min: number,\n max: number,\n fallback: number\n): string => {\n const px = style ? parseFloat(style.letterSpacing) : NaN;\n const em = style && Number.isFinite(px) ? px / sizeOf(style) : fallback;\n const held = Math.min(max, Math.max(min, em));\n return held === 0 ? '0em' : `${Math.round(held * 1000) / 1000}em`;\n};\nconst weightIn = (\n style: CSSStyleDeclaration | null,\n min: number,\n max: number,\n fallback: string\n): string => {\n const raw = style?.fontWeight ?? '';\n const n = raw === 'bold' ? 700 : raw === 'normal' ? 400 : parseFloat(raw);\n return Number.isFinite(n) ? String(Math.min(max, Math.max(min, n))) : fallback;\n};\n\n/**\n * Read the look of the page the icon sits on. Order per role: the flow's own pin\n * (`--fs-feedback-*`), what the page actually renders (computed styles), the flow's design\n * tokens (`--sb-*`, then the Flowstate widget tokens), a neutral default.\n */\nexport function readFlowTheme(doc: Document = document): FlowFeedbackTheme {\n const body = doc.body;\n const stage = body.querySelector(':scope > :not(script):not(style):not([data-fs-feedback-host])');\n const bodyStyle = styleOf(body);\n\n const sampledGround = [stage, body, doc.documentElement]\n .map((el) => (el ? parseColor(styleOf(el).backgroundColor) : null))\n .find(opaque);\n const sampledInk =\n parseColor((stage ? styleOf(stage) : bodyStyle).color) ?? parseColor(bodyStyle.color);\n const tokenGround = parseColor(rootVar(doc, ['--sb-ground', '--sb-bg', '--color-ink-050']));\n\n const ink0 = parseColor(rootVar(doc, ['--fs-feedback-ink'])) ?? sampledInk;\n let ground =\n parseColor(rootVar(doc, ['--fs-feedback-ground'])) ??\n sampledGround ??\n (tokenGround && opaque(tokenGround) ? tokenGround : null) ??\n (ink0 && luminance(ink0) > 0.5 ? BLACK : WHITE);\n ground = [ground[0], ground[1], ground[2], 1];\n const dark = luminance(ground) < 0.35;\n let ink =\n ink0 && contrast([ink0[0], ink0[1], ink0[2], 1], ground) >= 4.5 ? ink0 : dark ? WHITE : BLACK;\n ink = [ink[0], ink[1], ink[2], 1];\n\n const button = primaryButton(doc, ground);\n const buttonStyle = button ? styleOf(button) : null;\n const pinnedCta = parseColor(rootVar(doc, ['--fs-feedback-cta']));\n const tokenCta = parseColor(rootVar(doc, ['--sb-cta', '--sb-accent', '--color-cream']));\n const sampledCta = buttonStyle ? parseColor(buttonStyle.backgroundColor) : null;\n const cta =\n pinnedCta ??\n sampledCta ??\n (tokenCta && contrast(tokenCta, ground) >= 1.25 ? tokenCta : null) ??\n ink;\n const pinnedCtaInk = parseColor(rootVar(doc, ['--fs-feedback-cta-ink']));\n const sampledCtaInk =\n !pinnedCta && sampledCta && buttonStyle ? parseColor(buttonStyle.color) : null;\n const ctaInk =\n pinnedCtaInk ??\n (sampledCtaInk && contrast(sampledCtaInk, cta) >= 3 ? sampledCtaInk : null) ??\n (luminance(cta) > 0.4 ? BLACK : WHITE);\n\n // Type. The title face is the flow's largest heading, or its largest line when it has no\n // heading element; the body face is the one most of its smaller words are set in.\n const samples = textSamples(doc);\n const headings = Array.from(doc.querySelectorAll('h1, h2, [class*=\"title\"]')).filter(\n (el) => !inOurs(el) && visible(el)\n );\n const largest = <T>(items: T[], size: (item: T) => number): T | null =>\n items.reduce<T | null>(\n (best, item) => (best === null || size(item) > size(best) ? item : best),\n null\n );\n const bigHeading = largest(\n headings.filter((el) => sizeOf(styleOf(el)) >= DISPLAY_MIN_PX),\n (el) => sizeOf(styleOf(el))\n );\n const bigLine = largest(\n samples.filter((t) => t.size >= DISPLAY_MIN_PX && t.chars >= 3),\n (t) => t.size\n );\n const headingStyle = bigHeading\n ? styleOf(bigHeading)\n : (bigLine?.style ?? (headings[0] ? styleOf(headings[0]) : null));\n const byFamily = new Map<string, number>();\n for (const t of samples) {\n if (t.size < DISPLAY_MIN_PX && !t.heading)\n byFamily.set(t.family, (byFamily.get(t.family) ?? 0) + t.chars);\n }\n const readBody = largest(Array.from(byFamily), ([, chars]) => chars)?.[0] ?? '';\n const fontBody =\n rootVar(doc, ['--fs-feedback-font']) ||\n readBody ||\n (stage ? styleOf(stage).fontFamily : '') ||\n bodyStyle.fontFamily ||\n rootVar(doc, ['--sb-font-body', '--font-body']) ||\n 'system-ui, sans-serif';\n const fontDisplay =\n rootVar(doc, ['--fs-feedback-font-display']) ||\n headingStyle?.fontFamily ||\n rootVar(doc, ['--sb-font-display', '--sb-font-title', '--font-display']) ||\n fontBody;\n const buttonType = buttonStyle?.fontFamily ? buttonStyle : null;\n\n const field = Array.from(\n doc.querySelectorAll('textarea, input[type=\"text\"], input:not([type])')\n ).find((el) => !inOurs(el) && visible(el));\n const px = (value: string | undefined): number | null => {\n const n = parseFloat(value ?? '');\n return Number.isFinite(n) ? n : null;\n };\n const cornerOf = (style: CSSStyleDeclaration | null): number | null =>\n style ? px(style.borderTopLeftRadius || style.borderRadius) : null;\n const buttonRadius = cornerOf(buttonStyle);\n const fieldRadius = cornerOf(field ? styleOf(field) : null);\n const radius =\n rootVar(doc, ['--fs-feedback-radius']) ||\n `${Math.min(fieldRadius ?? Math.min(buttonRadius ?? 16, 22), 28)}px`;\n\n return {\n ground: css(ground),\n surface: css(dark ? mix(ground, WHITE, 0.07) : mix(ground, WHITE, 0.6)),\n ink: css(ink),\n inkDim: css(mix(ink, ground, 0.32)),\n line: css(ink, 0.16),\n fill: css(ink, 0.05),\n cta: css([cta[0], cta[1], cta[2], 1]),\n ctaInk: css([ctaInk[0], ctaInk[1], ctaInk[2], 1]),\n fontBody,\n fontDisplay,\n displayWeight: headingStyle?.fontWeight || '500',\n displayTracking: trackingEm(headingStyle, -0.04, 0.02, -0.01),\n fontButton: buttonType?.fontFamily || fontBody,\n buttonWeight: weightIn(buttonType, 500, 800, '600'),\n buttonTracking: trackingEm(buttonType, -0.02, 0.08, 0),\n buttonTransform: buttonType?.textTransform === 'uppercase' ? 'uppercase' : 'none',\n radius,\n buttonRadius: buttonRadius === null ? '999px' : `${Math.min(buttonRadius, 999)}px`,\n dark,\n };\n}\n\n// \u2500\u2500 the screenshot \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst dataUrlBytes = (url: string): number =>\n Math.floor(((url.length - url.indexOf(',') - 1) * 3) / 4);\n\n/** Step the JPEG quality, then the size, until the picture fits the service's cap. */\nexport function encodeScreenshot(canvas: HTMLCanvasElement): string | null {\n let source = canvas;\n for (let pass = 0; pass < 3; pass += 1) {\n for (const quality of [0.82, 0.7, 0.58, 0.46, 0.34]) {\n const url = source.toDataURL('image/jpeg', quality);\n if (url.startsWith('data:image/jpeg') && dataUrlBytes(url) <= SCREENSHOT_MAX_BYTES)\n return url;\n }\n const smaller = document.createElement('canvas');\n smaller.width = Math.max(1, Math.round(source.width * 0.75));\n smaller.height = Math.max(1, Math.round(source.height * 0.75));\n const ctx = smaller.getContext('2d');\n if (!ctx) return null;\n ctx.drawImage(source, 0, 0, smaller.width, smaller.height);\n source = smaller;\n }\n return null;\n}\n\n/** The page as the person sees it right now: the viewport, without the feedback icon. */\nasync function captureViewport(ground: string): Promise<string | null> {\n // Read the page as it is at the tap, before the rasterizer loads.\n const width = window.innerWidth;\n const height = window.innerHeight;\n const { scrollX, scrollY } = window;\n const { domToCanvas } = await import('modern-screenshot');\n const scale = Math.min(\n window.devicePixelRatio || 1,\n SCREENSHOT_MAX_SIDE / Math.max(width, height)\n );\n const canvas = await domToCanvas(document.documentElement, {\n width,\n height,\n scale,\n backgroundColor: ground,\n style: { transform: `translate(${-scrollX}px, ${-scrollY}px)` },\n filter: (node) => !(node instanceof Element && node.hasAttribute(FEEDBACK_HOST_ATTR)),\n });\n return encodeScreenshot(canvas);\n}\n\nconst withTimeout = <T>(work: Promise<T>, ms: number): Promise<T | null> =>\n new Promise((resolve) => {\n const timer = setTimeout(() => resolve(null), ms);\n work.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n () => {\n clearTimeout(timer);\n resolve(null);\n }\n );\n });\n\n// \u2500\u2500 where the icon sits \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// The icon must not cover what a person taps, and should not cover what they read. No fixed\n// corner is safe across flows: a bottom-anchored primary button on one page sits exactly where\n// the icon cleared it on another (0.3.1 put the icon over The Pulse's \"Share my card\"). So the\n// icon places itself. It looks up the right edge from the bottom corner for the lowest spot that\n// is empty; it takes a spot over a line of text or a picture only when empty space is far above\n// it; and it sits on a control only when every spot does (a full screen of rows), where the\n// bottom corner costs the least. A flow that pins `--fs-feedback-bottom` keeps its own position\n// and none of this runs.\n\nexport const PLACEMENT_BASE = 16;\nexport const PLACEMENT_STEP = 8;\n/** Clear space kept around the icon, so it does not crowd what it sits beside. */\nexport const PLACEMENT_PAD = 8;\n/** The icon never climbs past this share of the viewport. */\nexport const PLACEMENT_MAX_SHARE = 0.6;\n/** How far up empty space is still worth more than sitting on a line of text lower down. */\nexport const PLACEMENT_READ_COST = 240;\n\n/** What is at a point: nothing, something to read or look at, or something to tap. */\nexport const EMPTY = 0;\nexport const READ = 1;\nexport const TAP = 2;\nexport type Occupancy = typeof EMPTY | typeof READ | typeof TAP;\n\nexport interface PlacementBox {\n /** The icon's box as measured now, in viewport pixels. */\n left: number;\n top: number;\n size: number;\n /** The bottom offset that box was measured at. */\n bottom: number;\n}\n\n/** Sample points across the padded box, close enough that a line of small text is not missed. */\nconst SAMPLES = 5;\n\n/**\n * The bottom offset to sit at. `at(x, y)` says what is at a viewport point. An empty spot costs\n * its height; a spot over text or a picture costs its height plus PLACEMENT_READ_COST; a spot\n * over a control is taken only when there is no other, and then it is the base.\n */\nexport function choosePlacement(\n box: PlacementBox,\n viewportHeight: number,\n at: (x: number, y: number) => Occupancy\n): number {\n const max = Math.max(PLACEMENT_BASE, viewportHeight * PLACEMENT_MAX_SHARE);\n const span = box.size + PLACEMENT_PAD * 2;\n const offsets = Array.from({ length: SAMPLES }, (_, i) => (span * i) / (SAMPLES - 1));\n const x0 = box.left - PLACEMENT_PAD;\n let best = PLACEMENT_BASE;\n let bestCost = Infinity;\n for (let bottom = PLACEMENT_BASE; bottom <= max; bottom += PLACEMENT_STEP) {\n // Costs only grow with height: nothing above this can beat what is already found.\n if (bottom >= bestCost) break;\n const y0 = box.top - (bottom - box.bottom) - PLACEMENT_PAD;\n let worst: Occupancy = EMPTY;\n for (const dy of offsets) {\n for (const dx of offsets) {\n const found = at(x0 + dx, y0 + dy);\n if (found > worst) worst = found;\n if (worst === TAP) break;\n }\n if (worst === TAP) break;\n }\n if (worst === TAP) continue;\n const cost = bottom + (worst === READ ? PLACEMENT_READ_COST : 0);\n if (cost < bestCost) {\n bestCost = cost;\n best = bottom;\n }\n }\n return best;\n}\n\nconst TAPPABLE =\n 'a[href],button,input,select,textarea,summary,label,[role=\"button\"],[role=\"link\"],[role=\"tab\"],[role=\"switch\"],[role=\"checkbox\"],[role=\"radio\"],[role=\"slider\"],[role=\"option\"],[tabindex]:not([tabindex=\"-1\"]),[contenteditable=\"\"],[contenteditable=\"true\"],[onclick]';\nconst PICTURE = 'img,svg,canvas,video,picture';\n/** A control that fills this much of the viewport is a \"tap anywhere\" surface (a tap-through\n * story, a sealed pack): the icon has to sit somewhere on it, so it does not count. */\nconst SURFACE_SHARE = 0.8;\n\ninterface Reading {\n tap: boolean;\n picture: boolean;\n /** Boxes of the element's own text, in viewport pixels. */\n text: DOMRect[];\n /** It paints a ground of its own, so what lies under it cannot be seen. */\n opaque: boolean;\n}\n\n/**\n * Reads what is at viewport points, the feedback host aside. One reader serves one placement\n * pass: what it learns about an element is kept for the pass and dropped with it.\n *\n * A tap lands on the topmost element and travels up its ancestors, so only those can make a\n * point a control: a button under the flow's own bottom sheet is not one. What a person reads\n * is whatever shows through, down to the first element that paints its own ground.\n */\nexport function occupancyReader(\n host: Element,\n doc: Document = document\n): (x: number, y: number) => Occupancy {\n if (typeof doc.elementsFromPoint !== 'function') return () => EMPTY;\n const view = doc.defaultView;\n const width = view?.innerWidth ?? 1;\n const height = view?.innerHeight ?? 1;\n const seen = new Map<Element, Reading>();\n const read = (el: Element): Reading => {\n const known = seen.get(el);\n if (known) return known;\n const style = view ? view.getComputedStyle(el) : null;\n // A div with a click handler says so only through its cursor.\n let tap = el.matches(TAPPABLE) || style?.cursor === 'pointer';\n if (tap) {\n const r = el.getBoundingClientRect();\n if (r.width * r.height >= width * height * SURFACE_SHARE) tap = false;\n }\n const text: DOMRect[] = [];\n if (typeof doc.createRange === 'function') {\n for (const node of Array.from(el.childNodes)) {\n if (node.nodeType !== 3 || !node.nodeValue || !node.nodeValue.trim()) continue;\n const range = doc.createRange();\n range.selectNodeContents(node);\n if (typeof range.getClientRects === 'function')\n text.push(...Array.from(range.getClientRects()));\n }\n }\n const ground = parseColor(style?.backgroundColor);\n const image = style?.backgroundImage ?? '';\n const reading = {\n tap,\n picture: el.matches(PICTURE) || el.closest('svg') !== null,\n text,\n opaque: (ground !== null && ground[3] >= 0.5) || (image !== '' && image !== 'none'),\n };\n seen.set(el, reading);\n return reading;\n };\n return (x, y) => {\n const cx = Math.min(Math.max(x, 0), width - 1);\n const cy = Math.min(Math.max(y, 0), height - 1);\n const stack = doc\n .elementsFromPoint(cx, cy)\n .filter((el) => el !== host && el !== doc.documentElement && el !== doc.body);\n for (let el: Element | null = stack[0] ?? null; el && el !== doc.body; el = el.parentElement) {\n if (read(el).tap) return TAP;\n }\n for (const el of stack) {\n const r = read(el);\n if (\n r.picture ||\n r.text.some((t) => cx >= t.left && cx <= t.right && cy >= t.top && cy <= t.bottom)\n )\n return READ;\n if (r.opaque) break;\n }\n return EMPTY;\n };\n}\n\n// \u2500\u2500 floating and docked \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// The button floats over the flow and belongs to the person, not to the page (FB-478, founder\n// ruling 2026-11-11). They can drag it anywhere; let go and it settles on the nearer side. Flick\n// it at an edge, or drag it into one, and it docks there as a slim tab with the bug still\n// showing; a tap on the tab still opens the sheet, and pulling the tab inward floats it again.\n// Where they leave it is remembered. Until they move it, it keeps placing itself (above).\n\nexport type DockState = 'float' | 'dock-left' | 'dock-right';\nexport const BUTTON_SIZE = 44;\nexport const TAB_WIDTH = 26;\nexport const TAB_HEIGHT = 54;\nexport const EDGE_MARGIN = 12;\n/** How far past an edge a release has to be heading before the button docks there. */\nexport const DOCK_PAST = 14;\n/** Storage key for where the person left the button. One origin serves every flow, so the\n * choice follows them from flow to flow. */\nexport const SPOT_KEY = 'fs-feedback-spot';\n\n/** The part of the viewport the button may rest in: its size, less the safe-area insets. */\nexport interface FeedbackViewport {\n width: number;\n height: number;\n top: number;\n bottom: number;\n}\n\nexport interface FeedbackSpot {\n state: DockState;\n x: number;\n y: number;\n}\n\n/** Where a release at velocity `v` (px per second) comes to rest: scroll deceleration. */\nexport const projectMomentum = (v: number, deceleration = 0.992): number =>\n ((v / 1000) * deceleration) / (1 - deceleration);\n\n/** Past a boundary the button follows less and less, so the edge reads as soft, not stuck. */\nexport const rubberband = (overshoot: number, dimension: number, constant = 0.55): number =>\n (overshoot * dimension * constant) / (dimension + constant * Math.abs(overshoot));\n\nconst clampY = (y: number, height: number, view: FeedbackViewport): number =>\n Math.min(\n Math.max(y, view.top + EDGE_MARGIN),\n Math.max(view.top + EDGE_MARGIN, view.height - view.bottom - EDGE_MARGIN - height)\n );\n\n/** The resting spot for a state on one side, at a height. */\nexport function restingSpot(\n state: DockState,\n left: boolean,\n y: number,\n view: FeedbackViewport\n): FeedbackSpot {\n if (state === 'float') {\n return {\n state,\n x: left ? EDGE_MARGIN : view.width - BUTTON_SIZE - EDGE_MARGIN,\n y: clampY(y, BUTTON_SIZE, view),\n };\n }\n return { state, x: left ? 0 : view.width - TAB_WIDTH, y: clampY(y, TAB_HEIGHT, view) };\n}\n\n/**\n * Where a drag that ends at (x, y) with velocity (vx, vy) settles. A floating button heading\n * past an edge, or let go against one, docks on that side; otherwise it floats on the side its\n * momentum carries it to. A docked tab stays docked and only slides along its edge.\n */\nexport function settleRelease(\n from: FeedbackSpot,\n velocity: { x: number; y: number },\n view: FeedbackViewport\n): FeedbackSpot {\n const py = from.y + projectMomentum(velocity.y);\n if (from.state !== 'float') return restingSpot(from.state, from.state === 'dock-left', py, view);\n const px = from.x + projectMomentum(velocity.x);\n const maxX = view.width - BUTTON_SIZE;\n const tabY = py + (BUTTON_SIZE - TAB_HEIGHT) / 2;\n if (px < -DOCK_PAST || from.x < 3) return restingSpot('dock-left', true, tabY, view);\n if (px > maxX + DOCK_PAST || from.x > maxX - 3)\n return restingSpot('dock-right', false, tabY, view);\n return restingSpot('float', px + BUTTON_SIZE / 2 < view.width / 2, py, view);\n}\n\n// \u2500\u2500 the icon and the sheet \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst BUG_ICON =\n '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"8\" y=\"7\" width=\"8\" height=\"12\" rx=\"4\"/><path d=\"M9.5 7a2.5 2.5 0 0 1 5 0M12 11v8M4 13h4M16 13h4M5 7l3.2 2.4M19 7l-3.2 2.4M5 20l3.2-2.6M19 20l-3.2-2.6\"/></svg>';\n\nconst STYLE = `\n:host { all: initial; }\n* { box-sizing: border-box; }\n.bug {\n position: fixed; z-index: 2147483000; left: 0; top: 0;\n width: 44px; height: 44px; border-radius: 22px; padding: 0; margin: 0;\n display: var(--fs-feedback-display, grid); place-items: center; cursor: grab;\n background: var(--surface); color: var(--ink); border: 1px solid var(--line);\n box-shadow: 0 10px 24px rgba(0, 0, 0, 0.2), 0 2px 5px rgba(0, 0, 0, 0.12);\n -webkit-tap-highlight-color: transparent; touch-action: none; user-select: none; -webkit-user-select: none;\n will-change: transform;\n}\n@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {\n .bug {\n background: color-mix(in srgb, var(--surface) 74%, transparent);\n -webkit-backdrop-filter: blur(14px) saturate(160%); backdrop-filter: blur(14px) saturate(160%);\n }\n}\n.bug svg { width: 20px; height: 20px; pointer-events: none; }\n.bug.down svg { transform: scale(0.9); }\n.bug.dragging { cursor: grabbing; box-shadow: 0 18px 36px rgba(0, 0, 0, 0.26), 0 3px 8px rgba(0, 0, 0, 0.14); }\n.bug[data-state^=\"dock\"] { width: 26px; height: 54px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.14); opacity: 0.92; }\n.bug[data-state=\"dock-right\"] { border-radius: 16px 0 0 16px; border-right-width: 0; }\n.bug[data-state=\"dock-left\"] { border-radius: 0 16px 16px 0; border-left-width: 0; }\n.bug[data-state^=\"dock\"] svg { width: 16px; height: 16px; }\n.probe {\n position: fixed; visibility: hidden; pointer-events: none; width: 0; height: 0;\n padding: env(safe-area-inset-top, 0px) 0 env(safe-area-inset-bottom, 0px) 0;\n}\n.tuck { align-self: center; font-size: 13px; color: var(--ink-dim); }\n@media (prefers-reduced-transparency: reduce) {\n .bug { background: var(--surface); -webkit-backdrop-filter: none; backdrop-filter: none; }\n}\n@media (prefers-contrast: more) {\n .bug { background: var(--surface); border-color: var(--ink); -webkit-backdrop-filter: none; backdrop-filter: none; }\n}\n.bug:focus-visible, .send:focus-visible, .link:focus-visible, textarea:focus-visible {\n outline: 2px solid var(--ink); outline-offset: 2px;\n}\n.scrim { position: fixed; inset: 0; z-index: 2147483001; background: rgba(0, 0, 0, 0.42); }\n.sheet {\n position: fixed; left: 0; right: 0; bottom: 0; z-index: 2147483002;\n margin: 0 auto; max-width: 480px;\n background: var(--surface); color: var(--ink); border: 1px solid var(--line); border-bottom: 0;\n border-radius: calc(var(--radius) + 6px) calc(var(--radius) + 6px) 0 0;\n padding: 12px 18px calc(20px + env(safe-area-inset-bottom, 0px));\n display: flex; flex-direction: column; gap: 12px;\n font-family: var(--font-body); font-size: 15px; line-height: 1.4;\n}\n.grab { width: 36px; height: 4px; border-radius: 2px; background: var(--line); align-self: center; }\nh2 {\n margin: 2px 0 0; font-family: var(--font-display); font-weight: var(--display-weight);\n font-size: 22px; line-height: 1.15; letter-spacing: var(--display-tracking); text-wrap: balance;\n}\n.form { display: contents; }\n.shot { display: flex; gap: 14px; align-items: center; font-size: 14px; }\n.thumb {\n position: relative; flex: none; width: 60px; aspect-ratio: var(--shot-ratio, 0.46); max-height: 124px;\n border-radius: 9px; border: 1px solid var(--line); background: var(--fill);\n}\n.thumb img { display: none; width: 100%; height: 100%; object-fit: cover; object-position: top; border-radius: 8px; }\n.scan, .empty, .badge { display: none; }\n.shot[data-shot=\"taking\"] .thumb { overflow: hidden; }\n.shot[data-shot=\"taking\"] .scan {\n display: block; position: absolute; left: 0; right: 0; top: 0; height: 34%;\n background: linear-gradient(to bottom, transparent, var(--line)); border-bottom: 2px solid var(--ink);\n}\n.shot[data-shot=\"attached\"] .thumb { background: var(--surface); }\n.shot[data-shot=\"attached\"] img { display: block; }\n.shot[data-shot=\"attached\"] .badge {\n display: grid; place-items: center; position: absolute; right: -8px; top: -8px; width: 22px; height: 22px;\n border-radius: 50%; background: #1f7a4d; color: #fff; border: 2px solid var(--surface);\n}\n.badge svg { width: 12px; height: 12px; }\n.shot[data-shot=\"none\"] .thumb { border-style: dashed; background: none; }\n.shot[data-shot=\"none\"] .empty { display: grid; place-items: center; position: absolute; inset: 0; color: var(--ink-dim); }\n.empty svg { width: 22px; height: 22px; }\n.shot-text { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; min-width: 0; }\n.shot-label { font-weight: 600; color: var(--ink); }\n.shot[data-shot=\"none\"] .shot-label { color: var(--ink-dim); font-weight: inherit; }\n.finder {\n position: fixed; z-index: 2147483002; pointer-events: none;\n top: calc(10px + env(safe-area-inset-top, 0px)); left: 10px; right: 10px; bottom: var(--finder-bottom, 55%);\n}\n.finder i { position: absolute; width: 22px; height: 22px; border: 3px solid #fff; filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.5)); }\n.finder i:nth-child(1) { left: 0; top: 0; border-right: 0; border-bottom: 0; border-top-left-radius: 9px; }\n.finder i:nth-child(2) { right: 0; top: 0; border-left: 0; border-bottom: 0; border-top-right-radius: 9px; }\n.finder i:nth-child(3) { left: 0; bottom: 0; border-right: 0; border-top: 0; border-bottom-left-radius: 9px; }\n.finder i:nth-child(4) { right: 0; bottom: 0; border-left: 0; border-top: 0; border-bottom-right-radius: 9px; }\n.link { background: none; border: 0; padding: 0; font: inherit; color: var(--ink); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }\ntextarea {\n width: 100%; min-height: 84px; resize: none; font: inherit; font-size: 16px; color: var(--ink);\n background: var(--fill); border: 1px solid var(--line); border-radius: var(--radius); padding: 13px 15px;\n}\ntextarea::placeholder { color: var(--ink-dim); }\n.send {\n font-family: var(--font-button); font-weight: var(--button-weight); font-size: 17px; line-height: 1.2;\n letter-spacing: var(--button-tracking); text-transform: var(--button-transform);\n cursor: pointer; border: 0; padding: 15px 14px;\n background: var(--cta); color: var(--cta-ink); border-radius: var(--button-radius);\n}\n.send { display: flex; align-items: center; justify-content: center; gap: 9px; }\n.send[disabled] { opacity: 0.5; cursor: default; }\n.sheet[data-busy] .send { opacity: 1; }\n.dots { display: inline-flex; gap: 4px; }\n.dots i { width: 5px; height: 5px; border-radius: 50%; background: currentColor; opacity: 0.4; }\n.done { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 14px 0 10px; }\n.ring { display: grid; place-items: center; width: 52px; height: 52px; border-radius: 50%; background: #1f7a4d; color: #fff; }\n.ring svg { width: 26px; height: 26px; }\n.done-title {\n margin: 0; font-family: var(--font-display); font-weight: var(--display-weight);\n font-size: 22px; line-height: 1.15; letter-spacing: var(--display-tracking);\n}\n.said { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }\n.status { margin: 0; font-size: 13px; color: var(--ink-dim); min-height: 1.4em; }\n[hidden] { display: none !important; }\n@media (prefers-reduced-motion: no-preference) {\n .bug {\n transition: width 0.22s cubic-bezier(0.2, 0.8, 0.2, 1), height 0.22s cubic-bezier(0.2, 0.8, 0.2, 1),\n border-radius 0.22s cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 0.22s ease;\n }\n .bug svg { transition: transform 0.1s ease-out, width 0.22s ease, height 0.22s ease; }\n .sheet { animation: rise 0.22s ease-out; }\n .shot[data-shot=\"taking\"] .scan { animation: fsf-scan 1.3s cubic-bezier(0.45, 0, 0.55, 1) infinite; }\n @keyframes fsf-scan { from { transform: translateY(-100%); } to { transform: translateY(300%); } }\n .finder i { animation: fsf-frame 0.9s ease-in-out infinite alternate; }\n @keyframes fsf-frame { from { opacity: 0.55; } to { opacity: 1; } }\n .shot[data-shot=\"attached\"] img { animation: fsf-land 0.28s cubic-bezier(0.2, 0.8, 0.2, 1); }\n @keyframes fsf-land { from { transform: scale(1.14); opacity: 0; } to { transform: none; opacity: 1; } }\n .shot[data-shot=\"attached\"] .badge { animation: fsf-pop 0.24s cubic-bezier(0.2, 0.8, 0.2, 1) 0.14s backwards; }\n .ring { animation: fsf-pop 0.26s cubic-bezier(0.2, 0.8, 0.2, 1); }\n @keyframes fsf-pop { from { transform: scale(0.4); opacity: 0; } to { transform: none; opacity: 1; } }\n .dots i { animation: fsf-dot 1s ease-in-out infinite; }\n .dots i:nth-child(2) { animation-delay: 0.15s; }\n .dots i:nth-child(3) { animation-delay: 0.3s; }\n @keyframes fsf-dot { 30% { opacity: 1; transform: translateY(-2px); } 60% { opacity: 0.4; transform: none; } }\n @keyframes rise { from { transform: translateY(24px); opacity: 0; } to { transform: none; opacity: 1; } }\n}\n`;\n\nlet mounted: FlowFeedbackHandle | null = null;\n\n/** Whether the page asked for no feedback icon (`<html data-fs-feedback=\"off\">`). */\nexport const feedbackOptedOut = (doc: Document = document): boolean =>\n doc.documentElement.getAttribute('data-fs-feedback') === 'off';\n\nconst CHECK_ICON =\n '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M5 12.5l4.5 4.5L19 7.5\"/></svg>';\nconst CAMERA_ICON =\n '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"3.5\" y=\"6.5\" width=\"17\" height=\"12.5\" rx=\"3\"/><circle cx=\"12\" cy=\"12.8\" r=\"3.2\"/><path d=\"M9 6.5l1-2h4l1 2\"/></svg>';\ntype ShotState = 'taking' | 'attached' | 'none';\nconst SHOT_LABEL: Record<ShotState, string> = {\n taking: 'Taking a screenshot',\n attached: 'Screenshot attached',\n none: 'No screenshot',\n};\nconst SCROLL_OPTIONS = { capture: true, passive: true } as const;\n\nexport function mountFlowFeedback(options: FlowFeedbackOptions): FlowFeedbackHandle | null {\n if (typeof document === 'undefined' || !document.body) return null;\n if (mounted) return mounted;\n if (feedbackOptedOut()) return null;\n\n const host = document.createElement('div');\n host.setAttribute(FEEDBACK_HOST_ATTR, '');\n const root = host.attachShadow({ mode: 'open' });\n root.innerHTML = `<style>${STYLE}</style>\n<button type=\"button\" class=\"bug\" data-state=\"float\" aria-label=\"Send feedback on this page\">${BUG_ICON}</button>\n<div class=\"probe\" aria-hidden=\"true\"></div>\n<div class=\"scrim\" hidden></div>\n<div class=\"finder\" aria-hidden=\"true\" hidden><i></i><i></i><i></i><i></i></div>\n<div class=\"sheet\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"fsf-title\" data-view=\"form\" hidden>\n <div class=\"grab\" aria-hidden=\"true\"></div>\n <div class=\"form\">\n <h2 id=\"fsf-title\">Feedback on this page</h2>\n <div class=\"shot\" data-shot=\"none\">\n <span class=\"thumb\"><span class=\"scan\" aria-hidden=\"true\"></span><img alt=\"Screenshot of this page\" /><span class=\"empty\" aria-hidden=\"true\">${CAMERA_ICON}</span><span class=\"badge\" aria-hidden=\"true\">${CHECK_ICON}</span></span>\n <span class=\"shot-text\"><span class=\"shot-label\" role=\"status\" aria-live=\"polite\">No screenshot</span><button type=\"button\" class=\"link remove\" hidden>Remove</button><button type=\"button\" class=\"link retake\" hidden>Take screenshot</button></span>\n </div>\n <textarea aria-labelledby=\"fsf-title\" placeholder=\"What should change?\" maxlength=\"2000\"></textarea>\n <p class=\"status\" role=\"status\" aria-live=\"polite\"></p>\n <button type=\"button\" class=\"send\" disabled><span class=\"send-label\">Send feedback</span><span class=\"dots\" aria-hidden=\"true\" hidden><i></i><i></i><i></i></span></button>\n <button type=\"button\" class=\"link tuck\">Tuck the button to the side</button>\n </div>\n <div class=\"done\" hidden><span class=\"ring\">${CHECK_ICON}</span><p class=\"done-title\" aria-hidden=\"true\">Feedback sent</p></div>\n <p class=\"said\" role=\"status\" aria-live=\"polite\"></p>\n</div>`;\n const q = <T extends Element>(selector: string): T => root.querySelector(selector) as T;\n const bug = q<HTMLButtonElement>('.bug');\n const scrim = q<HTMLDivElement>('.scrim');\n const sheet = q<HTMLDivElement>('.sheet');\n const shot = q<HTMLDivElement>('.shot');\n const img = q<HTMLImageElement>('.shot img');\n const shotLabel = q<HTMLSpanElement>('.shot-label');\n const remove = q<HTMLButtonElement>('.remove');\n const retake = q<HTMLButtonElement>('.retake');\n const finder = q<HTMLDivElement>('.finder');\n const form = q<HTMLDivElement>('.form');\n const done = q<HTMLDivElement>('.done');\n const said = q<HTMLParagraphElement>('.said');\n const sendLabel = q<HTMLSpanElement>('.send-label');\n const dots = q<HTMLSpanElement>('.dots');\n const text = q<HTMLTextAreaElement>('textarea');\n const status = q<HTMLParagraphElement>('.status');\n const send = q<HTMLButtonElement>('.send');\n const tuck = q<HTMLButtonElement>('.tuck');\n const probe = q<HTMLDivElement>('.probe');\n\n let theme = readFlowTheme();\n let screenshot: string | null = null;\n let busy = false;\n let closeTimer: ReturnType<typeof setTimeout> | null = null;\n let placeTimer: ReturnType<typeof setTimeout> | null = null;\n let autoBottom = PLACEMENT_BASE;\n let spot: FeedbackSpot = { state: 'float', x: 0, y: 0 };\n /** The person moved or docked it: from then on the button stays where they put it. */\n let placedByPerson = false;\n let velocity = { x: 0, y: 0 };\n let frame = 0;\n\n const px = (name: string): number | null => {\n const raw = getComputedStyle(host).getPropertyValue(name).trim();\n const n = raw ? parseFloat(raw) : NaN;\n return Number.isFinite(n) ? n : null;\n };\n const viewport = (): FeedbackViewport => {\n const style = getComputedStyle(probe);\n return {\n width: window.innerWidth,\n height: window.innerHeight,\n top: parseFloat(style.paddingTop) || 0,\n bottom: parseFloat(style.paddingBottom) || 0,\n };\n };\n const draw = (): void => {\n bug.style.transform = `translate3d(${spot.x}px, ${spot.y}px, 0)`;\n };\n const setState = (next: DockState): void => {\n spot.state = next;\n bug.dataset.state = next;\n };\n /** Keep where the person is putting the button: the spot it is heading for, saved when the\n * move starts, so a page that closes mid-motion still remembers. */\n const remember = (spot: FeedbackSpot): void => {\n if (!placedByPerson) return;\n try {\n const view = viewport();\n const width = spot.state === 'float' ? BUTTON_SIZE : TAB_WIDTH;\n localStorage.setItem(\n SPOT_KEY,\n JSON.stringify({\n state: spot.state,\n left: spot.x + width / 2 < view.width / 2,\n y: view.height > 0 ? spot.y / view.height : 0,\n })\n );\n } catch {\n // Storage is a convenience; a private window keeps the spot for this page only.\n }\n };\n const still = (): boolean =>\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n\n /** Spring to a spot from wherever the button is now, at whatever speed it has. Damping 1\n * (no overshoot) unless a flick carried it there; response 0.4 s. */\n const springTo = (to: FeedbackSpot, flicked: boolean): void => {\n cancelAnimationFrame(frame);\n frame = 0;\n setState(to.state);\n remember(to);\n if (still() || typeof requestAnimationFrame !== 'function') {\n spot = { ...to };\n velocity = { x: 0, y: 0 };\n draw();\n return;\n }\n const omega = (2 * Math.PI) / 0.4;\n const k = omega * omega;\n const c = 2 * (flicked ? 0.8 : 1) * omega;\n let last = performance.now();\n const tick = (now: number): void => {\n const dt = Math.min(0.032, (now - last) / 1000);\n last = now;\n velocity.x += (-k * (spot.x - to.x) - c * velocity.x) * dt;\n velocity.y += (-k * (spot.y - to.y) - c * velocity.y) * dt;\n spot.x += velocity.x * dt;\n spot.y += velocity.y * dt;\n const close =\n Math.abs(spot.x - to.x) < 0.3 &&\n Math.abs(spot.y - to.y) < 0.3 &&\n Math.hypot(velocity.x, velocity.y) < 6;\n if (close) {\n spot = { ...to };\n velocity = { x: 0, y: 0 };\n frame = 0;\n draw();\n return;\n }\n draw();\n frame = requestAnimationFrame(tick);\n };\n frame = requestAnimationFrame(tick);\n };\n\n /** Until the person moves it, the button places itself clear of the page's own controls and\n * text; a flow's pin (`--fs-feedback-bottom`) holds it instead. Once they have moved or\n * docked it, it only stays inside the viewport. Skipped while the sheet is up. */\n const place = (): void => {\n placeTimer = null;\n if (!sheet.hidden || bug.hidden || drag || frame) return;\n const view = viewport();\n if (placedByPerson || spot.state !== 'float') {\n const width = spot.state === 'float' ? BUTTON_SIZE : TAB_WIDTH;\n spot = restingSpot(spot.state, spot.x + width / 2 < view.width / 2, spot.y, view);\n draw();\n return;\n }\n const right = px('--fs-feedback-right') ?? 14;\n const left = view.width - right - BUTTON_SIZE;\n const pinned = px('--fs-feedback-bottom');\n if (pinned === null && typeof document.elementsFromPoint === 'function') {\n autoBottom = choosePlacement(\n {\n left,\n top: view.height - view.bottom - autoBottom - BUTTON_SIZE,\n size: BUTTON_SIZE,\n bottom: autoBottom,\n },\n view.height,\n occupancyReader(host)\n );\n }\n const bottom = pinned ?? autoBottom;\n spot = { state: 'float', x: left, y: view.height - view.bottom - bottom - BUTTON_SIZE };\n draw();\n };\n const schedulePlace = (): void => {\n if (placeTimer) clearTimeout(placeTimer);\n placeTimer = setTimeout(place, 150);\n };\n\n // Dragging: the button stays under the finger from where it was grabbed, a short history of\n // the pointer gives the release its velocity, and the release is handed to the spring.\n let drag: {\n id: number;\n ox: number;\n oy: number;\n sx: number;\n sy: number;\n moved: boolean;\n history: { t: number; x: number; y: number }[];\n } | null = null;\n let suppressClick = false;\n const onDown = (event: PointerEvent): void => {\n if (!sheet.hidden || busy) return;\n bug.setPointerCapture?.(event.pointerId);\n cancelAnimationFrame(frame);\n frame = 0;\n velocity = { x: 0, y: 0 };\n drag = {\n id: event.pointerId,\n ox: event.clientX - spot.x,\n oy: event.clientY - spot.y,\n sx: event.clientX,\n sy: event.clientY,\n moved: false,\n history: [{ t: event.timeStamp, x: event.clientX, y: event.clientY }],\n };\n bug.classList.add('down');\n };\n const onMove = (event: PointerEvent): void => {\n if (!drag || event.pointerId !== drag.id) return;\n const dx = event.clientX - drag.sx;\n const dy = event.clientY - drag.sy;\n if (!drag.moved && Math.hypot(dx, dy) < 8) return;\n if (!drag.moved) {\n drag.moved = true;\n bug.classList.add('dragging');\n }\n drag.history.push({ t: event.timeStamp, x: event.clientX, y: event.clientY });\n if (drag.history.length > 6) drag.history.shift();\n const view = viewport();\n if (spot.state !== 'float') {\n // A docked tab slides along its edge, and comes out once it is pulled inward far enough.\n const inward = spot.state === 'dock-left' ? dx : -dx;\n if (inward <= 18) {\n spot = restingSpot(spot.state, spot.state === 'dock-left', event.clientY - drag.oy, view);\n draw();\n return;\n }\n setState('float');\n drag.ox = BUTTON_SIZE / 2;\n drag.oy = BUTTON_SIZE / 2;\n }\n let x = event.clientX - drag.ox;\n let y = event.clientY - drag.oy;\n const maxX = view.width - BUTTON_SIZE;\n const maxY = view.height - BUTTON_SIZE;\n if (x < 0) x = rubberband(x, 60);\n else if (x > maxX) x = maxX + rubberband(x - maxX, 60);\n if (y < 0) y = rubberband(y, 80);\n else if (y > maxY) y = maxY + rubberband(y - maxY, 80);\n spot.x = x;\n spot.y = y;\n draw();\n };\n const onUp = (event: PointerEvent): void => {\n if (!drag || event.pointerId !== drag.id) return;\n const ended = drag;\n drag = null;\n bug.classList.remove('down', 'dragging');\n if (!ended.moved) return; // a tap: the click that follows opens the sheet\n suppressClick = true;\n setTimeout(() => {\n suppressClick = false;\n }, 0);\n const first = ended.history[0]!;\n const last = ended.history[ended.history.length - 1]!;\n const dt = Math.max(1, last.t - first.t);\n const paused = event.timeStamp - last.t > 90;\n velocity = paused\n ? { x: 0, y: 0 }\n : { x: ((last.x - first.x) / dt) * 1000, y: ((last.y - first.y) / dt) * 1000 };\n placedByPerson = true;\n springTo(settleRelease(spot, velocity, viewport()), Math.hypot(velocity.x, velocity.y) > 350);\n };\n\n const observer =\n typeof MutationObserver === 'function'\n ? new MutationObserver((records) => {\n if (records.some((r) => r.target !== host)) schedulePlace();\n })\n : null;\n\n const paint = (): void => {\n theme = readFlowTheme();\n const vars: Record<string, string> = {\n '--surface': theme.surface,\n '--ink': theme.ink,\n '--ink-dim': theme.inkDim,\n '--line': theme.line,\n '--fill': theme.fill,\n '--cta': theme.cta,\n '--cta-ink': theme.ctaInk,\n '--font-body': theme.fontBody,\n '--font-display': theme.fontDisplay,\n '--display-weight': theme.displayWeight,\n '--display-tracking': theme.displayTracking,\n '--font-button': theme.fontButton,\n '--button-weight': theme.buttonWeight,\n '--button-tracking': theme.buttonTracking,\n '--button-transform': theme.buttonTransform,\n '--radius': theme.radius,\n '--button-radius': theme.buttonRadius,\n };\n for (const [name, value] of Object.entries(vars)) host.style.setProperty(name, value);\n host.style.colorScheme = theme.dark ? 'dark' : 'light';\n };\n const sync = (): void => {\n send.disabled = busy || text.value.trim().length === 0;\n };\n /** The picture is taken while the sheet is already up. Each opening is a round; a picture\n * from a round that closed, or that the person removed, is dropped. */\n let round = 0;\n let pending: Promise<void> | null = null;\n /** The slot never leaves the sheet: it says the picture is being taken, is attached, or is\n * not there (with a way to take it again). The corner marks sit on the page while it is taken. */\n const setShot = (state: ShotState, url: string | null = null): void => {\n screenshot = state === 'attached' ? url : null;\n shot.dataset.shot = state;\n shotLabel.textContent = SHOT_LABEL[state];\n remove.hidden = state !== 'attached' || busy;\n retake.hidden = state !== 'none' || busy;\n finder.hidden = state !== 'taking';\n if (screenshot) img.src = screenshot;\n else img.removeAttribute('src');\n };\n const setBusy = (next: boolean): void => {\n busy = next;\n if (busy) sheet.setAttribute('data-busy', '');\n else sheet.removeAttribute('data-busy');\n text.readOnly = busy;\n sendLabel.textContent = busy ? 'Sending' : 'Send feedback';\n dots.hidden = !busy;\n const state = shot.dataset.shot as ShotState;\n remove.hidden = state !== 'attached' || busy;\n retake.hidden = state !== 'none' || busy;\n sync();\n };\n const setView = (view: 'form' | 'sent'): void => {\n sheet.dataset.view = view;\n form.hidden = view !== 'form';\n done.hidden = view !== 'sent';\n said.textContent = view === 'sent' ? 'Feedback sent' : '';\n };\n const take = (): Promise<void> => {\n const mine = (round += 1);\n setShot('taking');\n // The marks frame the part of the page the sheet leaves in view.\n host.style.setProperty('--finder-bottom', `${sheet.offsetHeight + 10}px`);\n host.style.setProperty(\n '--shot-ratio',\n String(Math.max(0.3, Math.min(1, window.innerWidth / Math.max(1, window.innerHeight))))\n );\n const capture = options.capture ?? (() => captureViewport(theme.ground));\n const work = withTimeout(Promise.resolve().then(capture), CAPTURE_TIMEOUT_MS).then((url) => {\n if (mine !== round) return;\n pending = null;\n setShot(url ? 'attached' : 'none', url);\n // Focus waits for the picture, so a keyboard that resizes the page is not in it.\n const active = root.activeElement;\n if (!busy && (!active || active === bug || active === retake || active === remove))\n text.focus();\n });\n pending = work;\n return work;\n };\n const close = (): void => {\n if (closeTimer) clearTimeout(closeTimer);\n closeTimer = null;\n round += 1;\n pending = null;\n sheet.hidden = true;\n scrim.hidden = true;\n bug.hidden = false;\n setBusy(false);\n setShot('none');\n setView('form');\n text.value = '';\n status.textContent = '';\n sync();\n schedulePlace();\n };\n const open = (): Promise<void> => {\n if (!sheet.hidden) return pending ?? Promise.resolve();\n if (busy) return Promise.resolve();\n paint();\n // The tap is answered at once: the sheet rises and says the picture is being taken.\n // The rasterizer skips this host, so neither the icon nor the sheet is in the picture.\n bug.hidden = true;\n setView('form');\n tuck.hidden = spot.state !== 'float';\n scrim.hidden = false;\n sheet.hidden = false;\n sync();\n return take();\n };\n const submit = async (): Promise<void> => {\n const message = text.value.trim();\n if (!message || busy) return;\n setBusy(true);\n status.textContent = '';\n let mine = round;\n if (pending) {\n // Wait a short while for a picture that is still being taken, then send the note alone.\n let timer: ReturnType<typeof setTimeout> | undefined;\n await Promise.race([\n pending,\n new Promise<void>((resolve) => (timer = setTimeout(resolve, SEND_WAIT_MS))),\n ]);\n clearTimeout(timer);\n if (mine !== round) return;\n if (pending) {\n mine = round += 1;\n pending = null;\n setShot('none');\n }\n }\n try {\n await options.submit({\n message,\n ...(screenshot ? { screenshot } : {}),\n ...(document.body.dataset.pageKey ? { pageKey: document.body.dataset.pageKey } : {}),\n pagePath: window.location.pathname.slice(-200),\n viewport: { width: window.innerWidth, height: window.innerHeight },\n });\n if (mine !== round) return;\n // One clear end: the form gives way to a check and a title, then the sheet closes.\n setView('sent');\n closeTimer = setTimeout(() => {\n close();\n bug.focus();\n }, SENT_HOLD_MS);\n } catch {\n if (mine !== round) return;\n setBusy(false);\n status.textContent = 'That did not send. Try again.';\n }\n };\n\n bug.addEventListener('click', () => {\n if (!suppressClick) void open();\n });\n bug.addEventListener('pointerdown', onDown);\n // Load the rasterizer on the press, so the picture is not waiting on a download at the tap.\n if (!options.capture)\n bug.addEventListener('pointerdown', () => void import('modern-screenshot').catch(() => {}), {\n once: true,\n });\n bug.addEventListener('pointermove', onMove);\n bug.addEventListener('pointerup', onUp);\n bug.addEventListener('pointercancel', onUp);\n tuck.addEventListener('click', () => {\n const view = viewport();\n const left = spot.x + BUTTON_SIZE / 2 < view.width / 2;\n close();\n placedByPerson = true;\n springTo(\n restingSpot(\n left ? 'dock-left' : 'dock-right',\n left,\n spot.y + (BUTTON_SIZE - TAB_HEIGHT) / 2,\n view\n ),\n false\n );\n });\n scrim.addEventListener('click', close);\n remove.addEventListener('click', () => {\n round += 1;\n pending = null;\n setShot('none');\n retake.focus();\n });\n retake.addEventListener('click', () => void take());\n // Once sent, a tap anywhere on the sheet closes it early.\n sheet.addEventListener('click', () => {\n if (sheet.dataset.view === 'sent') {\n close();\n bug.focus();\n }\n });\n text.addEventListener('input', sync);\n send.addEventListener('click', () => void submit());\n sheet.addEventListener('keydown', (event) => {\n if (event.key === 'Escape') {\n close();\n bug.focus();\n }\n event.stopPropagation();\n });\n\n document.body.appendChild(host);\n paint();\n try {\n const saved = JSON.parse(localStorage.getItem(SPOT_KEY) ?? 'null') as {\n state?: unknown;\n left?: unknown;\n y?: unknown;\n } | null;\n if (\n saved &&\n typeof saved.y === 'number' &&\n (saved.state === 'float' || saved.state === 'dock-left' || saved.state === 'dock-right')\n ) {\n const view = viewport();\n placedByPerson = true;\n setState(saved.state);\n spot = restingSpot(saved.state, saved.left === true, saved.y * view.height, view);\n }\n } catch {\n // No saved spot, or storage is closed: the button places itself.\n }\n // The page is often still drawing when the icon mounts: place now, and again as it settles.\n place();\n schedulePlace();\n observer?.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['class', 'style', 'hidden'],\n });\n window.addEventListener('resize', schedulePlace);\n window.addEventListener('load', schedulePlace);\n // A button at the end of a scrolling page slides under an icon that only looked once. Capture,\n // so a scroll inside the flow's own container counts; the debounce places after it stops.\n window.addEventListener('scroll', schedulePlace, SCROLL_OPTIONS);\n // A screen that slides or fades in is still moving when the DOM change is seen: look again\n // when its motion ends.\n document.addEventListener('transitionend', schedulePlace, SCROLL_OPTIONS);\n document.addEventListener('animationend', schedulePlace, SCROLL_OPTIONS);\n options.onReady?.();\n\n mounted = {\n host,\n refresh: () => {\n if (sheet.hidden) paint();\n },\n open,\n close,\n unmount: () => {\n if (closeTimer) clearTimeout(closeTimer);\n if (placeTimer) clearTimeout(placeTimer);\n cancelAnimationFrame(frame);\n observer?.disconnect();\n window.removeEventListener('resize', schedulePlace);\n window.removeEventListener('load', schedulePlace);\n window.removeEventListener('scroll', schedulePlace, SCROLL_OPTIONS);\n document.removeEventListener('transitionend', schedulePlace, SCROLL_OPTIONS);\n document.removeEventListener('animationend', schedulePlace, SCROLL_OPTIONS);\n host.remove();\n mounted = null;\n },\n };\n return mounted;\n}\n\nexport const unmountFlowFeedback = (): void => mounted?.unmount();\n", "// Session module \u2014 the official client for Flowstate widget-session rails.\n//\n// Group-session widgets (decision sprint, and any future multi-contributor\n// widget) run inside the app's /w/[slug] page. The page hands the iframe a\n// single-use auth ticket over postMessage (ADR-025: never in the URL on the\n// production path), the ticket is redeemed for a session token, and every\n// session verb is a REST call against /api/w/:slug on the widget gateway.\n//\n// This module wraps that whole contract so a widget never hand-rolls the\n// bridge:\n//\n// ```ts\n// import { CanvasWidget } from '@goflowstate/widget-sdk';\n// const session = await CanvasWidget.initSession();\n// if (session) {\n// const state = await session.join();\n// const stop = session.poll((s) => render(s));\n// await session.contribute({ evidence: { pins } });\n// } else {\n// // no launch params \u2014 standalone dev; use createMockSession()\n// }\n// ```\n//\n// The wire vocabulary is the generic session shape the rails speak\n// (historically named after the expedition: `tripId`, `myWishlist`, `plan`).\n// This module maps it onto neutral names once, here, so widgets never see the\n// legacy keys.\n\n// ============================================\n// State view\n// ============================================\n\nimport { mountFlowFeedback, unmountFlowFeedback } from './feedback';\n\nexport interface SessionMember {\n userId: string;\n name: string;\n /** Deterministic per-user accent color (hex), stable across sessions. */\n color: string;\n /** True once this member has contributed (sealed) in the current round. */\n sealed: boolean;\n isMe: boolean;\n isHost: boolean;\n}\n\n/** Gate policy surfaced to widgets (ADR-047). Optional server-side \u2014 links\n * minted without a gate report null. */\nexport interface SessionGateView {\n deadlineAt: string | null;\n quorum?: { mode: 'all' | 'count' | 'pct'; min?: number };\n}\n\n/** Flow context when this session is a node in a running flow (ADR-047). */\nexport interface SessionFlowView {\n runId: string;\n nodeIndex: number;\n nodeCount: number;\n nodes: Array<{\n key: string;\n title: string;\n status: 'pending' | 'active' | 'resolved' | 'expired';\n artifactId?: string;\n linkSlug?: string;\n }>;\n}\n\n/** One verified (or user-entered) fact about the viewer, from a unishell\n * shell. Read-only context: never use it to authorize anything. */\nexport interface SessionClaim {\n type: 'name' | 'email' | 'phone';\n value: string;\n /** 'user-entered', 'otp', or a surface id such as 'discord'. */\n source: string;\n /** Present only when the value was proven. */\n verifiedAt?: string;\n}\n\nexport interface SessionClaimSet {\n name?: SessionClaim;\n email?: SessionClaim;\n phone?: SessionClaim;\n}\n\n/** Who this state was read for. `claims` is set only for a shell guest\n * reading their own state; it is never another participant's. */\nexport interface SessionViewer {\n userId: string;\n guest: boolean;\n claims: SessionClaimSet | null;\n}\n\n/** The rails' phase model: `setup` until the host commits framing, `open`\n * while contributions are being collected, `sealed` once quorum is met and\n * the session awaits close, `resolved` after synthesis produced the\n * artifact. (Wire values `wishlist`/`plan` are mapped to `open`/`resolved`.) */\nexport type SessionPhase = 'setup' | 'open' | 'sealed' | 'resolved';\n\nexport interface SessionStateView<\n TSetup = Record<string, unknown>,\n TContribution = unknown,\n TDoc = Record<string, unknown>,\n> {\n /** The widget-link slug \u2014 the session's identity and its /w/:slug URL key. */\n slug: string;\n phase: SessionPhase;\n status: string;\n setup: TSetup | null;\n members: SessionMember[];\n sealedCount: number;\n memberCount: number;\n /** True when every member has sealed (legacy all-hands quorum). */\n allSealed: boolean;\n /** True when the configured quorum policy is satisfied (falls back to\n * allSealed when the link carries no gate). */\n quorumMet: boolean;\n gate: SessionGateView | null;\n flow: SessionFlowView | null;\n /** Own contribution only \u2014 others stay blind until synthesis. */\n myContribution: TContribution | null;\n /** The synthesized document, present once the session resolved. */\n doc: TDoc | null;\n artifactId: string | null;\n /** Widget-specific state-view extension (e.g. the decision sprint's stage\n * + ballot), passed through verbatim when the server provides one. */\n extension: Record<string, unknown> | null;\n /** The reader of this state; null when the server sent none (servers\n * before the shell endpoints). Optional so hand-built views keep compiling. */\n viewer?: SessionViewer | null;\n}\n\n// ============================================\n// Client interface\n// ============================================\n\nexport interface SessionPollOptions {\n /** Poll cadence in ms. Default 4000 (matches the historical widgets). */\n intervalMs?: number;\n}\n\nexport interface SessionClient<\n TSetup = Record<string, unknown>,\n TContribution = unknown,\n TDoc = Record<string, unknown>,\n> {\n readonly slug: string;\n /** Base URL of the web app (for reveal links and parent navigation). */\n readonly webBase: string;\n /** Base URL of the mobile PWA, when the host provided one via the optional\n * `mobile` iframe param. Participant-facing join links (e.g. a projected\n * QR) should prefer it and fall back to `webBase` when absent. Optional so\n * hand-rolled SessionClient implementations keep compiling. */\n readonly mobileBase?: string | null;\n /** The viewer's own shell claims from the most recent state read; null\n * until a state arrives or when there are none. Optional so hand-rolled\n * SessionClient implementations keep compiling. */\n readonly claims?: SessionClaimSet | null;\n\n /** Enter the session: the first identified enter creates the group. */\n join(): Promise<SessionStateView<TSetup, TContribution, TDoc>>;\n /** Fetch the current blind-aware state. */\n state(): Promise<SessionStateView<TSetup, TContribution, TDoc>>;\n /** Poll state on a cadence. Returns a stop function. Errors are passed to\n * onError (or swallowed) so a transient failure never kills the loop. */\n poll(\n onState: (s: SessionStateView<TSetup, TContribution, TDoc>) => void,\n opts?: SessionPollOptions & { onError?: (err: unknown) => void }\n ): () => void;\n /** Host-only: commit the session framing. */\n commitSetup(setup: TSetup): Promise<SessionStateView<TSetup, TContribution, TDoc>>;\n /** Upsert my contribution (seal). One contribution row per member; widgets\n * with multi-stage payloads post the merged object every time. */\n contribute(payload: TContribution): Promise<SessionStateView<TSetup, TContribution, TDoc>>;\n /** Host-only close: synthesis runs, the artifact is born. Idempotent. */\n close(): Promise<SessionStateView<TSetup, TContribution, TDoc>>;\n /** Widget-specific verb that returns a session STATE (e.g. the sprint's\n * /advance). Body is JSON-encoded; the response's state is returned. */\n action(verb: string, body?: unknown): Promise<SessionStateView<TSetup, TContribution, TDoc>>;\n /** Widget-specific verb with a NON-state response (e.g. /assist). Returns\n * the response's raw `data` value. Optional cancellation stops this request,\n * including its wait for the shared ticket; it does not cancel ticket redemption. */\n request<T = unknown>(\n verb: string,\n body?: unknown,\n options?: { signal?: AbortSignal }\n ): Promise<T>;\n /** Open a Flowstate URL outside the widget (parent navigation preferred \u2014\n * the /w page owns the session cookie). */\n openExternal(url: string): void;\n /** Deep link to an artifact reveal on the web app. */\n revealUrl(artifactId: string): string;\n}\n\n// ============================================\n// Launch params + ticket handshake (ADR-025)\n// ============================================\n\nexport interface SessionLaunchParams {\n slug: string;\n /** Dev-only fallback: a ticket in the URL. Production receives the ticket\n * via the parent's `flowstate:init` postMessage \u2014 URLs leak into\n * history/referrers/logs. */\n ticket: string | null;\n apiBase: string;\n webBase: string;\n /** Base URL of the mobile PWA (the optional `mobile` iframe param). Null\n * when the host didn't send one \u2014 legacy hosts never do \u2014 so consumers\n * fall back to `webBase` links. Optional so hand-built params keep\n * compiling. */\n mobileBase?: string | null;\n /** Origin of the page that FRAMES the widget \u2014 the `host` iframe param.\n * The ready/init ticket handshake and `flowstate:navigate` are addressed\n * to this origin (0.2.2). Defaults to `webBase` when absent, which is\n * what every pre-0.2.2 host is: the web /w page framing the widget. The\n * mobile PWA frames the widget itself (its native lobby) and names itself\n * here \u2014 without it the browser drops every postMessage between them\n * (target-origin mismatch) and the widget never boots. */\n hostBase?: string;\n}\n\nexport function readSessionLaunchParams(): SessionLaunchParams | null {\n if (typeof window === 'undefined') return null;\n const q = new URLSearchParams(window.location.search);\n // `tripId` is the historical param name the /w page passes; keep reading it\n // so hosts don't have to change. `slug` is accepted as the modern alias.\n const slug = q.get('slug') ?? q.get('tripId') ?? '';\n if (!slug) return null;\n // `mobile` is optional and has NO default: absent means the host predates\n // the PWA doors (or has none configured) and consumers must keep minting\n // legacy webBase links.\n const mobile = q.get('mobile');\n const webBase = (q.get('web') ?? 'http://localhost:14321').replace(/\\/$/, '');\n const host = q.get('host');\n return {\n slug,\n ticket: q.get('ticket'),\n apiBase: (q.get('api') ?? 'http://localhost:13002').replace(/\\/$/, ''),\n webBase,\n mobileBase: mobile ? mobile.replace(/\\/$/, '') : null,\n hostBase: host ? host.replace(/\\/$/, '') : webBase,\n };\n}\n\n/** The origin the handshake is addressed to: the framing page. */\nexport const hostOriginOf = (params: Pick<SessionLaunchParams, 'webBase' | 'hostBase'>): string =>\n new URL(params.hostBase ?? params.webBase).origin;\n\nconst SESSION_INIT_TIMEOUT_MS = 10_000;\nconst DEFAULT_POLL_INTERVAL_MS = 4_000;\n\n/** Announce readiness to the parent (host) page and wait for it to hand over\n * the single-use session ticket. `hostOrigin` is the framing page's origin\n * (`hostOriginOf`) \u2014 the web /w page or the mobile PWA's native lobby. */\nfunction awaitInitTicket(hostOrigin: string): Promise<string> {\n return new Promise((resolve, reject) => {\n if (typeof window === 'undefined' || window.parent === window) {\n reject(new Error('no parent page to receive a ticket from'));\n return;\n }\n const webOrigin = hostOrigin;\n const timer = setTimeout(() => {\n window.removeEventListener('message', onMessage);\n reject(new Error('timed out waiting for the session handoff'));\n }, SESSION_INIT_TIMEOUT_MS);\n const onMessage = (e: MessageEvent) => {\n if (e.origin !== webOrigin) return;\n const data = e.data as { type?: string; ticket?: string } | null;\n if (data?.type !== 'flowstate:init' || typeof data.ticket !== 'string') return;\n clearTimeout(timer);\n window.removeEventListener('message', onMessage);\n resolve(data.ticket);\n };\n window.addEventListener('message', onMessage);\n window.parent.postMessage({ type: 'flowstate:ready' }, webOrigin);\n });\n}\n\n// ============================================\n// Wire mapping\n// ============================================\n\ninterface WireMember {\n userId: string;\n name: string;\n color: string;\n sealed: boolean;\n isMe: boolean;\n isHost: boolean;\n}\n\ninterface WireSessionState {\n tripId: string;\n phase: 'setup' | 'wishlist' | 'sealed' | 'plan';\n status: string;\n setup: Record<string, unknown> | null;\n members: WireMember[];\n sealedCount: number;\n memberCount: number;\n allSealed: boolean;\n quorumMet?: boolean;\n gate?: SessionGateView | null;\n flow?: SessionFlowView | null;\n myWishlist: unknown;\n plan: Record<string, unknown> | null;\n artifactId: string | null;\n extension?: Record<string, unknown> | null;\n viewer?: { userId: string; guest: boolean; claims?: SessionClaimSet };\n /** Flow feedback: true only while the ring has the bug icon switched on. */\n feedbackEnabled?: boolean;\n}\n\nconst WIRE_PHASE: Record<WireSessionState['phase'], SessionPhase> = {\n setup: 'setup',\n wishlist: 'open',\n sealed: 'sealed',\n plan: 'resolved',\n};\n\n/** Exported for tests; not part of the package entry point. */\nexport function toStateView<TSetup, TContribution, TDoc>(\n s: WireSessionState\n): SessionStateView<TSetup, TContribution, TDoc> {\n return {\n slug: s.tripId,\n phase: WIRE_PHASE[s.phase] ?? 'open',\n status: s.status,\n setup: (s.setup as TSetup | null) ?? null,\n members: s.members,\n sealedCount: s.sealedCount,\n memberCount: s.memberCount,\n allSealed: s.allSealed,\n quorumMet: s.quorumMet ?? s.allSealed,\n gate: s.gate ?? null,\n flow: s.flow ?? null,\n myContribution: (s.myWishlist as TContribution | null) ?? null,\n doc: (s.plan as TDoc | null) ?? null,\n artifactId: s.artifactId,\n extension: s.extension ?? null,\n viewer: s.viewer\n ? { userId: s.viewer.userId, guest: s.viewer.guest, claims: s.viewer.claims ?? null }\n : null,\n };\n}\n\n// ============================================\n// HTTP client over the session rails\n// ============================================\n\ntype ExternalOpener = (url: string) => void;\n\nconst parentOpener = (hostOrigin: string): ExternalOpener => {\n return (url) => {\n if (typeof window !== 'undefined' && window.parent !== window) {\n // Ask the host page to navigate itself (the web /w page shares the\n // session cookie; the PWA lobby routes reveals natively).\n window.parent.postMessage({ type: 'flowstate:navigate', url }, hostOrigin);\n return;\n }\n if (typeof window !== 'undefined') window.open(url, '_blank', 'noopener');\n };\n};\n\nclass HttpSessionClient<TSetup, TContribution, TDoc> implements SessionClient<\n TSetup,\n TContribution,\n TDoc\n> {\n readonly slug: string;\n readonly webBase: string;\n readonly mobileBase: string | null;\n private latestClaims: SessionClaimSet | null = null;\n private readonly apiBase: string;\n private readonly tokenPromise: Promise<string>;\n private readonly opener: ExternalOpener;\n private readonly hostOrigin: string;\n\n constructor(params: SessionLaunchParams, opener?: ExternalOpener) {\n this.slug = params.slug;\n this.webBase = params.webBase;\n this.mobileBase = params.mobileBase ?? null;\n this.apiBase = params.apiBase;\n this.hostOrigin = hostOriginOf(params);\n this.opener = opener ?? parentOpener(hostOriginOf(params));\n // Redeem eagerly: the ticket is single-use + short-lived; trade it for a\n // session token before the user spends time in the widget.\n this.tokenPromise = this.acquireTicket(params).then((t) => this.redeem(t));\n }\n\n private async acquireTicket(params: SessionLaunchParams): Promise<string> {\n if (params.ticket) return params.ticket; // dev fallback\n return awaitInitTicket(hostOriginOf(params));\n }\n\n private async redeem(ticket: string): Promise<string> {\n const res = await fetch(`${this.apiBase}/api/auth/ticket/redeem`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ ticket }),\n });\n if (!res.ok) throw new Error(`ticket redeem failed (${res.status})`);\n const json = (await res.json()) as { data: { token: string } };\n return json.data.token;\n }\n\n private async call(\n path: string,\n init: RequestInit = {}\n ): Promise<SessionStateView<TSetup, TContribution, TDoc>> {\n const token = await this.tokenPromise;\n const res = await fetch(`${this.apiBase}/api/w/${this.slug}${path}`, {\n ...init,\n headers: {\n authorization: `Bearer ${token}`,\n ...(init.body ? { 'content-type': 'application/json' } : {}),\n ...(init.headers ?? {}),\n },\n });\n if (!res.ok) {\n const body = (await res.json().catch(() => ({}))) as { message?: string };\n throw new Error(body.message ?? `session service ${res.status}`);\n }\n const json = (await res.json()) as {\n data: WireSessionState | { state: WireSessionState };\n };\n const data = json.data as Record<string, unknown>;\n const wire = (data.state ?? data) as WireSessionState;\n const view = toStateView<TSetup, TContribution, TDoc>(wire);\n this.latestClaims = view.viewer?.claims ?? null;\n this.syncFeedback(wire.feedbackEnabled === true);\n return view;\n }\n\n get claims(): SessionClaimSet | null {\n return this.latestClaims;\n }\n\n /** Flow feedback: every state read says whether the bug icon is on for this ring. On, the\n * icon mounts once and re-reads the page's look on each read; off, it leaves. */\n private syncFeedback(enabled: boolean): void {\n if (typeof document === 'undefined') return;\n if (!enabled) {\n unmountFlowFeedback();\n return;\n }\n const handle = mountFlowFeedback({\n submit: (report) => this.request('feedback', report),\n onReady: () => {\n if (window.parent !== window)\n window.parent.postMessage({ type: 'flowstate:feedback-ready' }, this.hostOrigin);\n },\n });\n handle?.refresh();\n }\n\n join() {\n return this.call('/enter', { method: 'POST' });\n }\n\n state() {\n return this.call('/state');\n }\n\n poll(\n onState: (s: SessionStateView<TSetup, TContribution, TDoc>) => void,\n opts?: SessionPollOptions & { onError?: (err: unknown) => void }\n ): () => void {\n const interval = opts?.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n const tick = async () => {\n try {\n const s = await this.state();\n if (!stopped) onState(s);\n } catch (err) {\n opts?.onError?.(err);\n }\n if (!stopped) timer = setTimeout(tick, interval);\n };\n void tick();\n return () => {\n stopped = true;\n if (timer) clearTimeout(timer);\n };\n }\n\n commitSetup(setup: TSetup) {\n return this.call('/setup', { method: 'POST', body: JSON.stringify({ setup }) });\n }\n\n contribute(payload: TContribution) {\n return this.call('/contribute', { method: 'POST', body: JSON.stringify({ payload }) });\n }\n\n close() {\n return this.call('/close', { method: 'POST' });\n }\n\n action(verb: string, body?: unknown) {\n return this.call(`/${verb.replace(/^\\//, '')}`, {\n method: 'POST',\n body: JSON.stringify(body ?? {}),\n });\n }\n\n async request<T = unknown>(\n verb: string,\n body?: unknown,\n options?: { signal?: AbortSignal }\n ): Promise<T> {\n const signal = options?.signal;\n if (signal?.aborted) {\n // Observe eager redemption even when this caller never waits for it.\n // Retain the original promise so other callers still receive its failure.\n void this.tokenPromise.catch(() => {});\n signal.throwIfAborted();\n }\n let onAbort: (() => void) | undefined;\n let token: string;\n try {\n token = await (signal\n ? Promise.race([\n this.tokenPromise,\n new Promise<never>((_resolve, reject) => {\n onAbort = () => reject(signal.reason);\n signal.addEventListener('abort', onAbort, { once: true });\n }),\n ])\n : this.tokenPromise);\n } finally {\n if (signal && onAbort) signal.removeEventListener('abort', onAbort);\n }\n const res = await fetch(`${this.apiBase}/api/w/${this.slug}/${verb.replace(/^\\//, '')}`, {\n signal,\n method: 'POST',\n headers: { authorization: `Bearer ${token}`, 'content-type': 'application/json' },\n body: JSON.stringify(body ?? {}),\n });\n if (!res.ok) {\n const errBody = (await res.json().catch(() => {\n signal?.throwIfAborted();\n return {};\n })) as { message?: string };\n throw new Error(errBody.message ?? `session service ${res.status}`);\n }\n const json = (await res.json()) as { data: T };\n return json.data;\n }\n\n openExternal(url: string): void {\n this.opener(url);\n }\n\n revealUrl(artifactId: string): string {\n // FB-133: honour the PWA base like the wall's take-home QR does \u2014 a\n // phone-born session's reveal opens in the PWA, not the legacy web page.\n return this.mobileBase\n ? `${this.mobileBase}/?a=${artifactId}`\n : `${this.webBase}/a/${artifactId}`;\n }\n}\n\n// ============================================\n// Standalone mock (widget dev outside the canvas)\n// ============================================\n\nexport interface MockSessionOptions<TSetup, TDoc> {\n /** Display name for the single mock member. Default \"You\". */\n me?: string;\n /** Extra fake members (all unsealed initially). */\n others?: string[];\n setup?: TSetup | null;\n /** Claims the mock viewer carries (shell-guest dev). Default null. */\n claims?: SessionClaimSet | null;\n /** Called on close() to synthesize the mock doc. */\n synthesize?: (contributions: unknown[]) => TDoc;\n}\n\n/** In-memory SessionClient for standalone dev \u2014 same interface, no host, no\n * network. The mock's \"host\" is always you. */\nexport function createMockSession<\n TSetup = Record<string, unknown>,\n TContribution = unknown,\n TDoc = Record<string, unknown>,\n>(opts: MockSessionOptions<TSetup, TDoc> = {}): SessionClient<TSetup, TContribution, TDoc> {\n const palette = ['#8b7ef8', '#f8a34d', '#4dc9a4', '#f26d8f', '#5aa9e6'];\n const members: SessionMember[] = [\n {\n userId: 'mock-me',\n name: opts.me ?? 'You',\n color: palette[0],\n sealed: false,\n isMe: true,\n isHost: true,\n },\n ...(opts.others ?? []).map((name, i) => ({\n userId: `mock-${i + 1}`,\n name,\n color: palette[(i + 1) % palette.length],\n sealed: false,\n isMe: false,\n isHost: false,\n })),\n ];\n let setup: TSetup | null = opts.setup ?? null;\n let myContribution: TContribution | null = null;\n let doc: TDoc | null = null;\n let status = 'open';\n\n const view = (): SessionStateView<TSetup, TContribution, TDoc> => {\n const sealedCount = members.filter((m) => m.sealed).length;\n const allSealed = members.length > 0 && sealedCount >= members.length;\n const phase: SessionPhase =\n status === 'closed' && doc ? 'resolved' : !setup ? 'setup' : allSealed ? 'sealed' : 'open';\n return {\n slug: 'mock-session',\n phase,\n status,\n setup,\n members: members.map((m) => ({ ...m })),\n sealedCount,\n memberCount: members.length,\n allSealed,\n quorumMet: allSealed,\n gate: null,\n flow: null,\n myContribution,\n doc,\n artifactId: doc ? 'mock-artifact' : null,\n extension: null,\n viewer: { userId: 'mock-me', guest: false, claims: opts.claims ?? null },\n };\n };\n\n return {\n slug: 'mock-session',\n webBase: 'http://localhost:14321',\n claims: opts.claims ?? null,\n async join() {\n return view();\n },\n async state() {\n return view();\n },\n poll(onState, pollOpts) {\n const interval = pollOpts?.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n const timer = setInterval(() => onState(view()), interval);\n onState(view());\n return () => clearInterval(timer);\n },\n async commitSetup(s: TSetup) {\n setup = s;\n return view();\n },\n async contribute(payload: TContribution) {\n myContribution = payload;\n const me = members.find((m) => m.isMe);\n if (me) me.sealed = true;\n return view();\n },\n async close() {\n status = 'closed';\n doc = opts.synthesize\n ? opts.synthesize([myContribution])\n : ({ mock: true } as unknown as TDoc);\n return view();\n },\n async action() {\n return view();\n },\n async request<T = unknown>() {\n return {} as T;\n },\n openExternal(url: string) {\n if (typeof window !== 'undefined') window.open(url, '_blank', 'noopener');\n },\n revealUrl(artifactId: string) {\n return `http://localhost:14321/a/${artifactId}`;\n },\n };\n}\n\n// ============================================\n// Entry point\n// ============================================\n\n/**\n * Initialize a widget-session client from the launch context.\n *\n * Returns `null` when the page carries no session launch params (standalone\n * dev, or a canvas-hosted widget that has no group session) \u2014 callers fall\n * back to {@link createMockSession} or their own local state.\n */\nexport function initSession<\n TSetup = Record<string, unknown>,\n TContribution = unknown,\n TDoc = Record<string, unknown>,\n>(): SessionClient<TSetup, TContribution, TDoc> | null {\n const params = readSessionLaunchParams();\n if (!params) return null;\n return new HttpSessionClient<TSetup, TContribution, TDoc>(params);\n}\n", "// ============================================\n// Canvas Widget SDK \u2014 runs inside widget iframes\n// ============================================\n//\n// Usage in a widget:\n// <script src=\"canvas-widget-sdk.js\"></script>\n// <script>\n// const widget = await CanvasWidget.init();\n// widget.onStateChange(state => { ... });\n// </script>\n\nimport type {\n CanvasWidgetSDK,\n WidgetDisplayState,\n WidgetEvent,\n InitPayload,\n StateChangePayload,\n EventBroadcastPayload,\n ConsentResult,\n ConsentResultPayload,\n UserContext,\n StorageAPI,\n SettingsAPI,\n WidgetServiceClient,\n AiAPI,\n AiCallOptions,\n AiCompleteResult,\n AiStreamEvent,\n DbAPI,\n DbCollection,\n DbRecord,\n DbListOptions,\n} from './types';\nimport { createServiceClient } from './service';\nimport { initSession } from './session';\n\nconst PROTOCOL = 'canvas-widget-v1';\nconst DEV_MODE_TOKEN = 'dev-mode-skip';\nconst INIT_TIMEOUT_MS = 5000;\n\n// crypto.randomUUID only exists in SECURE contexts (HTTPS or localhost). When\n// a widget iframe is served over plain http on a LAN IP (e.g. dev-on-phone at\n// http://10.0.0.x), it's undefined and init() throws \u2014 the widget never\n// hands shakes and the host times it out (\"failed to load\"). Fall back to a\n// getRandomValues-based v4 UUID (also secure-context-independent).\nfunction safeRandomUUID(): string {\n const c = globalThis.crypto;\n if (c && typeof c.randomUUID === 'function') return c.randomUUID();\n const b = new Uint8Array(16);\n if (c && typeof c.getRandomValues === 'function') c.getRandomValues(b);\n else for (let i = 0; i < 16; i++) b[i] = Math.floor(Math.random() * 256);\n b[6] = (b[6] & 0x0f) | 0x40;\n b[8] = (b[8] & 0x3f) | 0x80;\n const h = Array.from(b, (x) => x.toString(16).padStart(2, '0'));\n return `${h[0]}${h[1]}${h[2]}${h[3]}-${h[4]}${h[5]}-${h[6]}${h[7]}-${h[8]}${h[9]}-${h[10]}${h[11]}${h[12]}${h[13]}${h[14]}${h[15]}`;\n}\n\ninterface CanvasMessage {\n protocol: string;\n type: string;\n widgetId: string;\n nonce: string;\n timestamp: number;\n token: string;\n payload: Record<string, unknown>;\n}\n\n// ============================================\n// HMAC utilities (mirrored from host side)\n// ============================================\n\nlet cachedKey: { secret: string; key: CryptoKey } | null = null;\n\nasync function getHmacKey(secret: string): Promise<CryptoKey> {\n if (cachedKey && cachedKey.secret === secret) return cachedKey.key;\n\n const encoded = new TextEncoder().encode(secret);\n const buf = new ArrayBuffer(encoded.byteLength);\n new Uint8Array(buf).set(encoded);\n\n const key = await crypto.subtle.importKey('raw', buf, { name: 'HMAC', hash: 'SHA-256' }, false, [\n 'sign',\n ]);\n\n cachedKey = { secret, key };\n return key;\n}\n\nasync function signToken(\n secret: string,\n type: string,\n widgetId: string,\n nonce: string,\n timestamp: number\n): Promise<string> {\n const key = await getHmacKey(secret);\n const data = new TextEncoder().encode(`${type}:${widgetId}:${nonce}:${timestamp}`);\n const buf = new ArrayBuffer(data.byteLength);\n new Uint8Array(buf).set(data);\n const sig = await crypto.subtle.sign('HMAC', key, buf);\n return Array.from(new Uint8Array(sig))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\n// ============================================\n// SDK Implementation\n// ============================================\n\n/** Internal SDK implementation \u2014 not exposed directly to widget authors. */\nexport class CanvasWidgetSDKImpl implements CanvasWidgetSDK {\n private _widgetId: string;\n private _state: WidgetDisplayState;\n private _hmacSecret: string | null;\n private _user: UserContext | null;\n private _namespace: { officeId: string; memberCount: number } | null;\n private _widgetApiUrl: string | null;\n private _widgetApiToken: string | null;\n private _installationId: string;\n private _pendingConsents = new Map<\n string,\n { resolve: (r: ConsentResult) => void; reject: (e: Error) => void }\n >();\n private _cachedJwt: { token: string; expiresAt: number } | null = null;\n private _stateListeners: Array<(state: WidgetDisplayState) => void> = [];\n private _displayState: WidgetDisplayState = 'collapsed';\n private _displayStateCallbacks: Array<(state: WidgetDisplayState) => void> = [];\n private _focused = false;\n private _focusCallbacks: Array<(focused: boolean) => void> = [];\n private _settingsRemoteListeners: Array<(settings: Record<string, unknown>) => void> = [];\n private _eventListeners = new Map<string, Array<(event: WidgetEvent) => void>>();\n private _settings: Record<string, unknown>;\n private _messageHandler: (event: MessageEvent) => void;\n\n /** Authenticated HTTP client scoped to the widget's declared service URL. Undefined when no service is declared. */\n readonly service: WidgetServiceClient | undefined;\n\n constructor(init: InitPayload) {\n this._widgetId = init.widgetId;\n this._state = init.state;\n this._hmacSecret = init.hmacSecret;\n this._user = init.user;\n this._namespace = init.namespace ?? null;\n this._widgetApiUrl = init.widgetApiUrl;\n this._widgetApiToken = init.widgetApiToken ?? null;\n this._installationId = init.installationId;\n this._settings = init.settings ?? {};\n\n // Wire sdk.service when the host provides a service URL from the manifest\n if (init.serviceUrl) {\n this.service = createServiceClient({\n baseUrl: init.serviceUrl,\n getJwt: (forceRefresh?: boolean) => this.getJwt(forceRefresh),\n });\n }\n\n this._messageHandler = this.handleMessage.bind(this);\n window.addEventListener('message', this._messageHandler);\n }\n\n /** The unique identifier for this widget instance on the canvas. */\n get widgetId(): string {\n return this._widgetId;\n }\n\n /** The current display state of the widget (collapsed, expanding, expanded, collapsing). */\n get state(): WidgetDisplayState {\n return this._state;\n }\n\n /** The authenticated user context, or null when user data access has not been granted. */\n get user(): UserContext | null {\n return this._user;\n }\n\n /** Namespace (office) context \u2014 id and owner-inclusive member count \u2014 or\n * null on a personal canvas. What the `canvas.context` permission grants. */\n get namespace(): { officeId: string; memberCount: number } | null {\n return this._namespace;\n }\n\n /** Subscribe to display state changes; returns an unsubscribe function. */\n onStateChange(cb: (state: WidgetDisplayState) => void): () => void {\n this._stateListeners.push(cb);\n return () => {\n const idx = this._stateListeners.indexOf(cb);\n if (idx !== -1) this._stateListeners.splice(idx, 1);\n };\n }\n\n /** Ask the canvas host to expand this widget into its modal view. */\n requestExpand(): void {\n this.send('EXPAND_REQUEST', {});\n }\n\n /** Ask the canvas host to collapse this widget back to its tile view. */\n requestCollapse(): void {\n this.send('COLLAPSE_REQUEST', {});\n }\n\n /** Get the current display state */\n getDisplayState(): WidgetDisplayState {\n return this._displayState;\n }\n\n /** Register a callback for display state changes */\n onDisplayStateChange(callback: (state: WidgetDisplayState) => void): () => void {\n this._displayStateCallbacks.push(callback);\n return () => {\n this._displayStateCallbacks = this._displayStateCallbacks.filter((cb) => cb !== callback);\n };\n }\n\n /** Is this widget the user's current focus? Updates via FOCUS_CHANGE. */\n isFocused(): boolean {\n return this._focused;\n }\n\n /** Subscribe to focus transitions. Returns an unsubscribe function. */\n onFocusChange(callback: (focused: boolean) => void): () => void {\n this._focusCallbacks.push(callback);\n return () => {\n this._focusCallbacks = this._focusCallbacks.filter((cb) => cb !== callback);\n };\n }\n\n /** Request promotion to fullscreen from partial */\n requestFullscreen(): void {\n this.send('FULLSCREEN_REQUEST', {});\n }\n\n /** Broadcast an event on the given topic to all subscribed widgets on the canvas. */\n emit(topic: string, payload: unknown): void {\n this.send('EVENT_EMIT', { topic, payload });\n }\n\n /** Subscribe to events on the given topic; returns an unsubscribe function. */\n subscribe(topic: string, cb: (event: WidgetEvent) => void): () => void {\n const list = this._eventListeners.get(topic) ?? [];\n list.push(cb);\n this._eventListeners.set(topic, list);\n return () => {\n const current = this._eventListeners.get(topic);\n if (!current) return;\n const idx = current.indexOf(cb);\n if (idx !== -1) current.splice(idx, 1);\n };\n }\n\n /** Send a structured log entry to the canvas host for centralized debugging. */\n log(level: 'info' | 'warn' | 'error', message: string, data?: unknown): void {\n this.send('LOG', { level, message, data: data ?? null });\n }\n\n /** Request user consent for a named data scope; resolves with the consent decision. */\n async requestConsent(scope: string, reason: string): Promise<ConsentResult> {\n return new Promise<ConsentResult>((resolve, reject) => {\n const timeout = setTimeout(() => {\n this._pendingConsents.delete(scope);\n reject(new Error('Consent request timed out'));\n }, 60000);\n\n this._pendingConsents.set(scope, {\n resolve: (r: ConsentResult) => {\n clearTimeout(timeout);\n resolve(r);\n },\n reject: (e: Error) => {\n clearTimeout(timeout);\n reject(e);\n },\n });\n\n this.send('CONSENT_REQUEST', { scope, reason });\n });\n }\n\n /** Return the cached JWT for authenticating requests to external APIs; throws if no token is available. Pass forceRefresh=true to bypass the cache. */\n async getJwt(forceRefresh?: boolean): Promise<string> {\n if (!forceRefresh && this._cachedJwt && Date.now() < this._cachedJwt.expiresAt - 30000) {\n return this._cachedJwt.token;\n }\n throw new Error('No cached JWT \u2014 widget must use OAuth flow for token refresh');\n }\n\n /** Persistent key-value storage scoped to this widget installation. */\n get storage(): StorageAPI {\n return {\n get: (key: string) => this.storageRequest('GET', key),\n set: (key: string, value: unknown) => this.storageRequest('PUT', key, value).then(() => {}),\n delete: (key: string) => this.storageRequest('DELETE', key).then(() => {}),\n };\n }\n\n /** Ad-hoc collections/records backend scoped to the canvas/room. */\n get db(): DbAPI {\n const req = (path: string, method: string, body?: unknown) => this.widgetApiRequest(path, method, body);\n return {\n collection(name: string): DbCollection {\n const base = `/widget-api/db/${encodeURIComponent(name)}`;\n return {\n create: (data) => req(base, 'POST', data) as Promise<DbRecord>,\n get: (id) => req(`${base}/${encodeURIComponent(id)}`, 'GET') as Promise<DbRecord>,\n list: (opts?: DbListOptions) => {\n const qs = new URLSearchParams();\n if (opts?.limit != null) qs.set('limit', String(opts.limit));\n if (opts?.cursor) qs.set('cursor', opts.cursor);\n if (opts?.sort) qs.set('sort', opts.sort);\n if (opts?.mine) qs.set('mine', 'true');\n const suffix = qs.toString() ? `?${qs.toString()}` : '';\n return req(`${base}${suffix}`, 'GET') as Promise<DbRecord[]>;\n },\n update: (id, patch) => req(`${base}/${encodeURIComponent(id)}`, 'PATCH', patch) as Promise<DbRecord>,\n delete: (id) => (req(`${base}/${encodeURIComponent(id)}`, 'DELETE') as Promise<unknown>).then(() => {}),\n };\n },\n };\n }\n\n /** Platform LLM access (widget.* aliases only). Requests ride the same\n * widget-gateway auth as db/storage; the platform meters every call and\n * attributes it to this widget's publishing org. */\n get ai(): AiAPI {\n const complete = (opts: AiCallOptions): Promise<AiCompleteResult> =>\n this.widgetApiRequest('/widget-api/ai/complete', 'POST', opts) as Promise<AiCompleteResult>;\n const streamRequest = (opts: AiCallOptions): Promise<Response> => this.widgetApiStream(opts);\n return {\n complete,\n // Async generator over the gateway's SSE frames. Ends on [DONE] or\n // stream end; returning early (break) cancels the fetch body, which\n // the gateway sees as a disconnect and aborts the upstream call, so\n // an abandoned stream stops generating and billing.\n async *stream(opts: AiCallOptions): AsyncGenerator<AiStreamEvent> {\n const response = await streamRequest(opts);\n const reader = response.body?.getReader();\n if (!reader) throw new Error('ai.stream: response has no body');\n const decoder = new TextDecoder();\n let buffer = '';\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const frames = buffer.split('\\n\\n');\n buffer = frames.pop() ?? '';\n for (const frame of frames) {\n const dataLine = frame.split('\\n').find((l) => l.startsWith('data:'));\n if (!dataLine) continue;\n const payload = dataLine.slice(5).trim();\n if (!payload || payload === '[DONE]') continue;\n let parsed: AiStreamEvent;\n try {\n parsed = JSON.parse(payload) as AiStreamEvent;\n } catch {\n continue;\n }\n yield parsed;\n }\n }\n } finally {\n // Break/return path: releases the connection so the gateway's\n // req.on('close') fires and the upstream call aborts.\n await reader.cancel().catch(() => {});\n }\n },\n };\n }\n\n /** Widget configuration settings persisted by the canvas host. */\n get settings(): SettingsAPI {\n return {\n get: () => ({ ...this._settings }),\n set: (newSettings: Record<string, unknown>) => {\n this._settings = { ...this._settings, ...newSettings };\n this.send('SETTINGS_WRITE', { settings: this._settings });\n },\n onRemoteChange: (cb: (settings: Record<string, unknown>) => void) => {\n this._settingsRemoteListeners.push(cb);\n return () => {\n this._settingsRemoteListeners = this._settingsRemoteListeners.filter((fn) => fn !== cb);\n };\n },\n };\n }\n\n /** Display a toast notification in the canvas host UI. */\n async notify(message: string, opts?: { level?: string; duration?: number }): Promise<void> {\n this.send('NOTIFICATION', {\n message,\n level: opts?.level ?? 'info',\n duration: opts?.duration ?? 5000,\n });\n }\n\n // \u2500\u2500 Private \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Raw streaming POST against the widget gateway \u2014 same auth as\n * widgetApiRequest, but returns the Response for SSE consumption\n * instead of parsing a JSON envelope. */\n private async widgetApiStream(body: unknown): Promise<Response> {\n if (!this._widgetApiUrl) {\n throw new Error('Widget API Gateway URL not available');\n }\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (this._widgetApiToken) {\n headers['Authorization'] = `Bearer ${this._widgetApiToken}`;\n }\n const response = await fetch(`${this._widgetApiUrl}/widget-api/ai/stream`, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n const err = await response.json().catch(() => ({}));\n throw new Error((err as Record<string, string>).message ?? 'ai.stream request failed');\n }\n return response;\n }\n\n private async widgetApiRequest(path: string, method: string, body?: unknown): Promise<unknown> {\n if (!this._widgetApiUrl) {\n throw new Error('Widget API Gateway URL not available');\n }\n const url = `${this._widgetApiUrl}${path}`;\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (this._widgetApiToken) {\n headers['Authorization'] = `Bearer ${this._widgetApiToken}`;\n }\n const opts: RequestInit = { method, headers };\n if (body !== undefined && method !== 'GET' && method !== 'DELETE') {\n opts.body = JSON.stringify(body);\n }\n const response = await fetch(url, opts);\n if (!response.ok) {\n const err = await response.json().catch(() => ({}));\n throw new Error((err as Record<string, string>).message ?? `Request ${method} ${path} failed`);\n }\n if (method === 'GET' || method === 'POST' || method === 'PATCH') {\n const result = await response.json();\n return (result as Record<string, unknown>).data;\n }\n return undefined;\n }\n\n private storageRequest(method: string, key: string, value?: unknown): Promise<unknown> {\n return this.widgetApiRequest(`/widget-api/storage/${encodeURIComponent(key)}`, method, value);\n }\n\n private handleMessage(event: MessageEvent): void {\n const msg = event.data;\n if (!msg || typeof msg !== 'object' || msg.protocol !== PROTOCOL) return;\n\n switch (msg.type) {\n case 'STATE_CHANGE': {\n const payload = msg.payload as StateChangePayload;\n this._state = payload.state;\n for (const listener of this._stateListeners) {\n listener(payload.state);\n }\n break;\n }\n case 'EVENT_BROADCAST': {\n const payload = msg.payload as EventBroadcastPayload;\n const listeners = this._eventListeners.get(payload.topic);\n if (listeners) {\n const evt: WidgetEvent = {\n topic: payload.topic,\n payload: payload.payload,\n emittedAt: payload.emittedAt,\n };\n for (const listener of listeners) {\n listener(evt);\n }\n }\n break;\n }\n case 'CONSENT_RESULT': {\n const payload = msg.payload as ConsentResultPayload;\n const pending = this._pendingConsents.get(payload.scope);\n if (pending) {\n this._pendingConsents.delete(payload.scope);\n pending.resolve({\n scope: payload.scope,\n granted: payload.granted,\n grantToken: payload.grantToken,\n expiresIn: payload.expiresIn,\n });\n }\n break;\n }\n case 'AUTH_TOKEN_REFRESH': {\n const payload = msg.payload as { token: string; expiresIn: number };\n this._cachedJwt = {\n token: payload.token,\n expiresAt: Date.now() + payload.expiresIn * 1000,\n };\n break;\n }\n case 'DISPLAY_STATE_CHANGE': {\n const newState = msg.payload?.displayState as WidgetDisplayState;\n if (newState) {\n this._displayState = newState;\n this._displayStateCallbacks.forEach((cb) => cb(newState));\n }\n break;\n }\n case 'FOCUS_CHANGE': {\n const next = !!msg.payload?.focused;\n if (next !== this._focused) {\n this._focused = next;\n this._focusCallbacks.forEach((cb) => cb(next));\n }\n break;\n }\n case 'SETTINGS_REMOTE_UPDATE': {\n const p = msg.payload as { settings: Record<string, unknown> };\n this._settings = { ...p.settings };\n for (const fn of this._settingsRemoteListeners) {\n try {\n fn({ ...this._settings });\n } catch {\n // a listener throwing must not break message handling\n }\n }\n break;\n }\n case 'USER_CONTEXT':\n case 'CANVAS_CONTEXT':\n // Phase 3+ \u2014 reserved for future use\n break;\n }\n }\n\n private send(type: string, payload: Record<string, unknown>): void {\n // Fire and forget \u2014 sign is async but we don't need to await delivery\n this.buildMessage(type, payload).then((msg) => {\n window.parent.postMessage(msg, '*');\n });\n }\n\n private async buildMessage(\n type: string,\n payload: Record<string, unknown>\n ): Promise<CanvasMessage> {\n const nonce = safeRandomUUID();\n const timestamp = Date.now();\n\n let token: string;\n if (this._hmacSecret) {\n token = await signToken(this._hmacSecret, type, this._widgetId, nonce, timestamp);\n } else {\n token = DEV_MODE_TOKEN;\n }\n\n return {\n protocol: PROTOCOL,\n type,\n widgetId: this._widgetId,\n nonce,\n timestamp,\n token,\n payload,\n };\n }\n\n /** Tear down the SDK instance, remove all listeners, and reject pending consent requests. */\n destroy(): void {\n window.removeEventListener('message', this._messageHandler);\n this._stateListeners.length = 0;\n this._eventListeners.clear();\n for (const [, pending] of this._pendingConsents) {\n pending.reject(new Error('Widget destroyed'));\n }\n this._pendingConsents.clear();\n }\n}\n\n// ============================================\n// Public API: CanvasWidget.init()\n// ============================================\n\n/**\n * Entry point for the Flowstate Canvas Widget SDK.\n *\n * Usage:\n * ```ts\n * import { CanvasWidget } from '@goflowstate/widget-sdk';\n * const widget = await CanvasWidget.init();\n * ```\n */\nexport const CanvasWidget = {\n /**\n * Initialize a group-session client for widgets hosted on the /w/[slug]\n * session page (ticket handshake + session REST rails). Returns null when\n * the launch context carries no session params \u2014 see `initSession` in\n * ./session for details and `createMockSession` for standalone dev.\n */\n initSession,\n\n /**\n * Initialize the SDK and establish communication with the canvas host.\n *\n * Sends a READY message to the host and waits for an INIT response.\n * Resolves with a fully initialized {@link CanvasWidgetSDK} instance.\n * Rejects after 5 seconds if no INIT is received from the host.\n */\n init(): Promise<CanvasWidgetSDK> {\n return new Promise<CanvasWidgetSDK>((resolve, reject) => {\n const timeout = setTimeout(() => {\n window.removeEventListener('message', handler);\n reject(new Error('CanvasWidget.init() timed out \u2014 no INIT received from host within 5s'));\n }, INIT_TIMEOUT_MS);\n\n const handler = (event: MessageEvent) => {\n const msg = event.data;\n if (!msg || typeof msg !== 'object' || msg.protocol !== PROTOCOL) return;\n if (msg.type !== 'INIT') return;\n\n clearTimeout(timeout);\n window.removeEventListener('message', handler);\n\n const payload = msg.payload as InitPayload;\n const sdk = new CanvasWidgetSDKImpl(payload);\n resolve(sdk);\n };\n\n window.addEventListener('message', handler);\n\n // Send READY to host \u2014 the host will respond with INIT\n const readyMsg: CanvasMessage = {\n protocol: PROTOCOL,\n type: 'READY',\n widgetId: '', // Not yet known \u2014 host identifies us by iframe source\n nonce: safeRandomUUID(),\n timestamp: Date.now(),\n token: DEV_MODE_TOKEN, // READY is always allowed, token doesn't matter\n payload: { manifestVersion: '1' },\n };\n\n window.parent.postMessage(readyMsg, '*');\n });\n },\n};\n", "// ============================================\n// Widget theme utilities \u2014 the Flowstate Theme Contract (ADR-048)\n// ============================================\n//\n// Flowstate has exactly three themes: 'light' | 'dark' | 'glass'.\n// Glass is the signature default (the liquid-glass look) \u2014 widgets treat it\n// as the unthemed plane. The host hands the active theme to external iframe\n// widgets in two ways:\n//\n// 1. Boot: a `?theme=` query param appended to the iframe src.\n// 2. Live: a `postMessage` of `{ type: 'flowstate:theme', theme }`.\n//\n// Widgets map the theme onto their own tokens by setting `data-theme` on\n// their <html> element ('glass' removes the attribute, so widget CSS keeps\n// the glass plane as its default styles).\n//\n// Usage in a widget entry:\n// import { initWidgetTheme } from '@goflowstate/widget-sdk';\n// initWidgetTheme(); // apply boot theme + follow live host updates\n\nexport type WidgetTheme = 'light' | 'dark' | 'glass';\n\nconst THEME_MESSAGE_TYPE = 'flowstate:theme';\nconst DEFAULT_THEME: WidgetTheme = 'glass';\n\nfunction isWidgetTheme(value: unknown): value is WidgetTheme {\n return value === 'light' || value === 'dark' || value === 'glass';\n}\n\n/**\n * Read the theme the host handed the widget at boot via the `?theme=` URL\n * param. Invalid or missing values fall back to 'glass' (the default plane).\n */\nexport function getInitialTheme(): WidgetTheme {\n if (typeof window === 'undefined') return DEFAULT_THEME;\n try {\n const param = new URLSearchParams(window.location.search).get('theme');\n return isWidgetTheme(param) ? param : DEFAULT_THEME;\n } catch {\n return DEFAULT_THEME;\n }\n}\n\n/**\n * Subscribe to live theme updates pushed by the host\n * (`postMessage({ type: 'flowstate:theme', theme })`). Non-theme messages\n * and invalid theme values are ignored. Returns an unsubscribe function.\n *\n * Only messages from the embedding host page (`window.parent`) are accepted,\n * mirroring the ingest channel; a hostile sibling frame cannot restyle the\n * widget. Pass `allowedOrigin` to additionally pin the host's origin.\n */\nexport function onThemeChange(\n cb: (theme: WidgetTheme) => void,\n opts?: { allowedOrigin?: string }\n): () => void {\n if (typeof window === 'undefined') return () => {};\n const listener = (event: MessageEvent): void => {\n if (event.source !== window.parent) return;\n if (opts?.allowedOrigin && event.origin !== opts.allowedOrigin) return;\n const data: unknown = event.data;\n if (typeof data !== 'object' || data === null) return;\n const message = data as { type?: unknown; theme?: unknown };\n if (message.type !== THEME_MESSAGE_TYPE) return;\n if (!isWidgetTheme(message.theme)) return;\n cb(message.theme);\n };\n window.addEventListener('message', listener);\n return () => window.removeEventListener('message', listener);\n}\n\n/**\n * Apply a theme to the widget document by setting `data-theme` on <html>.\n * 'glass' REMOVES the attribute \u2014 widget CSS treats glass as the default\n * plane, with `[data-theme='light']` / `[data-theme='dark']` overrides.\n */\nexport function applyWidgetTheme(theme: WidgetTheme): void {\n if (typeof document === 'undefined') return;\n if (theme === 'glass') {\n delete document.documentElement.dataset.theme;\n } else {\n document.documentElement.dataset.theme = theme;\n }\n}\n\n/**\n * Convenience boot helper: apply the initial (`?theme=`) theme, then follow\n * live host updates, applying each. Returns the unsubscribe function.\n */\nexport function initWidgetTheme(): () => void {\n applyWidgetTheme(getInitialTheme());\n return onThemeChange(applyWidgetTheme);\n}\n", "// ============================================\n// Widget ingest channel \u2014 ADR-043 in-place delivery\n// ============================================\n//\n// When an artifact is dropped on (or sent to) a widget and the ingestion\n// transform produces a result, the HOST pushes the produced artifact into\n// any open iframe of the target widget:\n//\n// postMessage({ type: 'flowstate:ingest', artifactId, kind?, payload? })\n//\n// `payload` is the produced artifact's parsed JSON content when the host\n// could fetch it (small structured artifacts like kanban boards); widgets\n// without a payload can show an affordance around `artifactId` instead.\n// Same channel shape as the theme handoff (`flowstate:theme`).\n//\n// Usage in a widget entry:\n// import { onArtifactIngested } from '@goflowstate/widget-sdk';\n// onArtifactIngested(({ payload }) => mergeBoard(payload));\n\nexport const INGEST_MESSAGE_TYPE = 'flowstate:ingest';\n\nexport interface IngestedArtifact {\n /** The produced artifact's id (host route: /a/:id). */\n artifactId: string;\n /** The produced artifact kind (e.g. 'kanban-board'), when known. */\n kind?: string;\n /** Parsed JSON content of the produced artifact, when the host fetched it. */\n payload?: unknown;\n}\n\n/**\n * Subscribe to ingestion results pushed by the host. Non-ingest messages and\n * malformed payloads are ignored. Returns an unsubscribe function.\n *\n * Only messages from the embedding host page (`window.parent`) are accepted \u2014\n * a hostile sibling frame or opener cannot inject into the channel. Pass\n * `allowedOrigin` to additionally pin the host's origin (the session module\n * derives the same value from `webBase`).\n */\nexport function onArtifactIngested(\n cb: (artifact: IngestedArtifact) => void,\n opts?: { allowedOrigin?: string }\n): () => void {\n if (typeof window === 'undefined') return () => {};\n const listener = (event: MessageEvent): void => {\n if (event.source !== window.parent) return;\n if (opts?.allowedOrigin && event.origin !== opts.allowedOrigin) return;\n const data: unknown = event.data;\n if (typeof data !== 'object' || data === null) return;\n const message = data as { type?: unknown; artifactId?: unknown; kind?: unknown; payload?: unknown };\n if (message.type !== INGEST_MESSAGE_TYPE) return;\n if (typeof message.artifactId !== 'string' || !message.artifactId) return;\n cb({\n artifactId: message.artifactId,\n kind: typeof message.kind === 'string' ? message.kind : undefined,\n payload: message.payload,\n });\n };\n window.addEventListener('message', listener);\n return () => window.removeEventListener('message', listener);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["spot", "close"]
|
|
7
7
|
}
|