sgiant-ai-widget 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +155 -0
  3. package/dist/ai-invalidation.d.ts +29 -0
  4. package/dist/ai-invalidation.d.ts.map +1 -0
  5. package/dist/ai-invalidation.js +121 -0
  6. package/dist/ai-invalidation.js.map +1 -0
  7. package/dist/contrast.d.ts +87 -0
  8. package/dist/contrast.d.ts.map +1 -0
  9. package/dist/contrast.js +151 -0
  10. package/dist/contrast.js.map +1 -0
  11. package/dist/directive.d.ts +37 -0
  12. package/dist/directive.d.ts.map +1 -0
  13. package/dist/directive.js +87 -0
  14. package/dist/directive.js.map +1 -0
  15. package/dist/dom.d.ts +83 -0
  16. package/dist/dom.d.ts.map +1 -0
  17. package/dist/dom.js +146 -0
  18. package/dist/dom.js.map +1 -0
  19. package/dist/host-actions.d.ts +192 -0
  20. package/dist/host-actions.d.ts.map +1 -0
  21. package/dist/host-actions.js +443 -0
  22. package/dist/host-actions.js.map +1 -0
  23. package/dist/icons.d.ts +21 -0
  24. package/dist/icons.d.ts.map +1 -0
  25. package/dist/icons.js +84 -0
  26. package/dist/icons.js.map +1 -0
  27. package/dist/index.d.ts +600 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +4833 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/labels.d.ts +187 -0
  32. package/dist/labels.d.ts.map +1 -0
  33. package/dist/labels.js +232 -0
  34. package/dist/labels.js.map +1 -0
  35. package/dist/limits.d.ts +43 -0
  36. package/dist/limits.d.ts.map +1 -0
  37. package/dist/limits.js +48 -0
  38. package/dist/limits.js.map +1 -0
  39. package/dist/markdown.d.ts +23 -0
  40. package/dist/markdown.d.ts.map +1 -0
  41. package/dist/markdown.js +0 -0
  42. package/dist/markdown.js.map +1 -0
  43. package/dist/message-chrome.d.ts +45 -0
  44. package/dist/message-chrome.d.ts.map +1 -0
  45. package/dist/message-chrome.js +101 -0
  46. package/dist/message-chrome.js.map +1 -0
  47. package/dist/pane-follow.d.ts +67 -0
  48. package/dist/pane-follow.d.ts.map +1 -0
  49. package/dist/pane-follow.js +61 -0
  50. package/dist/pane-follow.js.map +1 -0
  51. package/dist/prefix.d.ts +9 -0
  52. package/dist/prefix.d.ts.map +1 -0
  53. package/dist/prefix.js +9 -0
  54. package/dist/prefix.js.map +1 -0
  55. package/dist/proposal-summary.d.ts +19 -0
  56. package/dist/proposal-summary.d.ts.map +1 -0
  57. package/dist/proposal-summary.js +73 -0
  58. package/dist/proposal-summary.js.map +1 -0
  59. package/dist/replay.d.ts +125 -0
  60. package/dist/replay.d.ts.map +1 -0
  61. package/dist/replay.js +172 -0
  62. package/dist/replay.js.map +1 -0
  63. package/dist/safe-url.d.ts +47 -0
  64. package/dist/safe-url.d.ts.map +1 -0
  65. package/dist/safe-url.js +80 -0
  66. package/dist/safe-url.js.map +1 -0
  67. package/dist/sgiant-ai-widget.global.js +778 -0
  68. package/dist/sgiant-ai-widget.global.js.map +7 -0
  69. package/dist/specs.d.ts +173 -0
  70. package/dist/specs.d.ts.map +1 -0
  71. package/dist/specs.js +231 -0
  72. package/dist/specs.js.map +1 -0
  73. package/dist/storage.d.ts +77 -0
  74. package/dist/storage.d.ts.map +1 -0
  75. package/dist/storage.js +124 -0
  76. package/dist/storage.js.map +1 -0
  77. package/dist/styles.d.ts +29 -0
  78. package/dist/styles.d.ts.map +1 -0
  79. package/dist/styles.js +876 -0
  80. package/dist/styles.js.map +1 -0
  81. package/dist/ui-control.d.ts +8 -0
  82. package/dist/ui-control.d.ts.map +1 -0
  83. package/dist/ui-control.js +8 -0
  84. package/dist/ui-control.js.map +1 -0
  85. package/dist/ui-render.d.ts +75 -0
  86. package/dist/ui-render.d.ts.map +1 -0
  87. package/dist/ui-render.js +617 -0
  88. package/dist/ui-render.js.map +1 -0
  89. package/dist/ui-spec.d.ts +143 -0
  90. package/dist/ui-spec.d.ts.map +1 -0
  91. package/dist/ui-spec.js +288 -0
  92. package/dist/ui-spec.js.map +1 -0
  93. package/package.json +61 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts", "../node_modules/sgiant-ai-agent-bridge/src/dom-control.ts", "../node_modules/sgiant-ai-agent-bridge/src/protocol.ts", "../node_modules/sgiant-ai-agent-bridge/src/transport.ts", "../src/host-actions.ts", "../src/storage.ts", "../src/limits.ts", "../src/contrast.ts", "../src/pane-follow.ts", "../src/directive.ts", "../src/ui-spec.ts", "../src/ai-invalidation.ts", "../src/markdown.ts", "../src/prefix.ts", "../src/dom.ts", "../src/icons.ts", "../src/message-chrome.ts", "../src/ui-render.ts", "../src/proposal-summary.ts", "../src/styles.ts", "../src/labels.ts", "../src/replay.ts", "../src/specs.ts", "../src/safe-url.ts"],
4
+ "sourcesContent": ["/**\n * Embeddable AI chatbox widget \u2014 framework-agnostic vanilla DOM, so it drops\n * into the analytics app OR an external site with one call. It POSTs to a\n * streaming chat endpoint and renders the reply live.\n *\n * Transport is intentionally tolerant: it reads a streamed body and accepts\n * BOTH shapes this platform emits \u2014\n * - SSE : `data: {\"type\":\"assistant_delta\",\"text\":\"\u2026\"}`\n * - NDJSON : `{\"d\":\"\u2026\"}`\n * plus `{type:\"thread\",threadId}` / `{type:\"error\"}` / `{type:\"done\"}`.\n *\n * SECURITY: the widget never holds long-lived secrets \u2014 the host supplies a\n * token (Clerk session for in-app, or a short-lived embed token for external\n * use). The backend's OBO scoping remains the real ceiling; this is just UI.\n * See [[project_unified_managed_ai]].\n */\n\n// Shared host protocol \u2014 standard page context + in-app action map every\n// surface (org/admin/marketing/onboarding) wires the same way.\nexport {\n makePageContext,\n matchManifest,\n isKnownNavTarget,\n createHostActions,\n STANDARD_ACTION_PATHS,\n isNavigationAction,\n type PageContext,\n type PageManifestEntry,\n type NavTarget,\n type AppSurface,\n type HostActionsConfig,\n type HostActionHandler,\n} from \"./host-actions\";\n// Used locally too (the block above only RE-exports for consumers): the\n// auto-navigate gate needs to know what counts as navigation.\nimport {\n readFlag,\n readItem,\n readJson,\n writeFlag,\n writeItem,\n writeJson,\n} from \"./storage\";\nimport {\n CHAT_ATTACHMENT_MAX_AV_BYTES,\n CHAT_ATTACHMENT_MAX_BYTES,\n CHAT_ATTACHMENT_MAX_COUNT,\n} from \"./limits\";\n// From `./limits`, not `@sgiant/shared` \u2014 see the note in that file. The\n// history is worth keeping: this import was first written as\n// `@sgiant/ui/tokens`, which made a vanilla-DOM widget declare a React library\n// carrying three.js, recharts and Clerk to read one function. It was moved to\n// `@sgiant/tokens` to fix that, and then the same mistake arrived by a\n// different door as `@sgiant/assets/actions` (#306). Now it is local, and a\n// drift test holds the copies together instead of a manifest entry.\nimport { resolveAccentContrast, mixSrgb, accentInk } from \"./contrast\";\nimport { isNavigationAction } from \"./host-actions\";\nimport { shouldAutoNavigate, shouldCollapseNarration } from \"./pane-follow\";\n// The component vocabulary composed UI cards are drawn from. Re-exported below\n// for hosts that want to validate a spec before handing it over.\nimport { parseJsonDirective } from \"./directive\";\nexport { parseJsonDirective, type ParsedDirective } from \"./directive\";\nexport {\n normalizeUiSpec,\n uiSpecMediaIds,\n KNOWN_UI_STATUSES,\n UI_SAY_ACTION,\n type UiSpec,\n type UiItem,\n type UiAction,\n type UiLayout,\n type UiStatus,\n} from \"./ui-spec\";\n// Same reason: the block below re-exports these for consumers, but the widget\n// itself subscribes so a finished background job can refresh the transcript.\nimport { subscribeAiChange } from \"./ai-invalidation\";\n// Read-only UI control \u2014 point the Copilot at on-page controls by their stable\n// `data-ai-target` id. `scanAiTargets` feeds the current page's controls into\n// the turn context so the model knows what it may highlight/scroll-to/focus.\nexport {\n scanAiTargets,\n clearHighlight,\n isUiControlAction,\n isOperateAction,\n UI_CONTROL_ACTIONS,\n OPERATE_ACTIONS,\n type AiTargetInfo,\n type UiControlAction,\n type OperateAction,\n} from \"./ui-control\";\nimport { isOperateAction, isUiControlAction } from \"./ui-control\";\nimport {\n createFrameTransport,\n type FrameTransport,\n type BridgeAction,\n} from \"sgiant-ai-agent-bridge\";\n// The generic half only. AI_DOMAIN_KEYS / AI_ACTION_DOMAINS / invalidateAiTouched\n// moved to @sgiant/ai-apply on 2026-09-02: they name sgiant's tools and sgiant's\n// React Query keys, which is a description of one product rather than of this\n// widget. What stays carries opaque domain STRINGS this package never interprets.\nexport {\n broadcastAiChange,\n subscribeAiChange,\n subscribeLiveSync,\n type AiChangeEvent,\n type LiveSyncOptions,\n} from \"./ai-invalidation\";\n// THE single tool-name \u2192 endpoint mapping for applying a confirm-gated write\n// proposal \u2014 every host (org/admin widget adapters, the full-page panel\n// client) calls this instead of keeping its own copy.\n// applyProposal is not here, and is no longer in this PACKAGE at all (#306).\n// It maps ~15 sgiant tool names onto sgiant's own REST endpoints \u2014 the\n// opposite of embeddable \u2014 and was the only reason this package's import graph\n// reached @sgiant/asset-actions, which is GitLab-restricted and so cannot\n// travel into a public repo.\n//\n// It moved to `@sgiant/ai-apply`, platform-side. It briefly lived at a\n// `./sgiant` subpath of this package, which got it out of the CORE but not out\n// of the published tarball \u2014 a distinction that only matters once the tarball\n// is public, which is the whole point of #306.\nimport type { PageContext } from \"./host-actions\";\nimport { renderMarkdown } from \"./markdown\";\nimport { createMessageChrome } from \"./message-chrome\";\nimport { PREFIX } from \"./prefix\";\nimport { createUiRenderers } from \"./ui-render\";\nimport { genericProposalSummary } from \"./proposal-summary\";\n// Exported so a host can fall back to it deliberately, and so the sgiant\n// phrasing in @sgiant/ai-apply can end every branch with the same floor.\nexport { genericProposalSummary } from \"./proposal-summary\";\nimport {\n addMsg,\n attIcon,\n el,\n escapeHtml,\n maskMarkdown,\n parseLine,\n relBucket,\n relTime,\n} from \"./dom\";\nimport {\n hostDefinesPlatformTokens,\n injectStyles,\n USER_BUBBLE_INK,\n} from \"./styles\";\nimport { WIDGET_LABELS, type WidgetLabels } from \"./labels\";\nexport { WIDGET_LABELS, resolveWidgetLabels } from \"./labels\";\nexport type { WidgetLabels };\nexport { PREFIX };\n\n/**\n * The window event that opens the assistant panel. Owned here, next to the\n * `openEventName` option that consumes it, so a nav link and the widget host\n * can never drift onto different strings and silently produce a dead button.\n */\nexport const OPEN_ASSISTANT_EVENT = \"sgiant:open-assistant\";\n\n// Thread replay + branch navigation moved to `./replay.ts` (#320): a pure\n// transform, server payload in and render items out, with no DOM and no\n// host context \u2014 the shape #306 needs more of.\nimport { type LoadedThreadItem, type ReplayMessageItem } from \"./replay\";\nexport { buildThreadReplay } from \"./replay\";\nexport type { LoadedThreadItem } from \"./replay\";\n\n/**\n * A background job as the CHAT needs to draw it.\n *\n * Deliberately the GENERIC job shape (`JobSummary`'s status + done-of-total\n * progress) rather than anything a site import or a report build owns: the\n * platform is heading toward AI-defined job types, and a card that switched on\n * the type would have to be rewritten for each of them. The host maps the API's\n * `JobRecord` onto this \u2014 which is a structural widening, not a translation.\n *\n * Every field but `status` is optional so a job type that has no answer for one\n * simply omits it, and the card draws what it was given.\n */\nexport interface WidgetJobView {\n /**\n * The job's id. Optional because a card fetched BY id already knows it \u2014 but\n * required in practice for `listThreadJobs`, where the whole point is\n * learning which jobs exist. A view without one simply cannot be re-attached.\n */\n id?: string;\n /** The generic job type, used ONLY to pick nicer copy for the types we happen\n * to know. An unrecognised type renders with the neutral fallback title \u2014\n * which is the test that this stays type-agnostic. */\n type?: string;\n /** The coarse, type-agnostic lifecycle \u2014 `JobStatus` on the wire. */\n status: \"queued\" | \"running\" | \"done\" | \"failed\" | \"cancelled\";\n /** Done-of-total, with a short label for what is happening right now (the\n * report section being built). `total` is null while it is not yet known. */\n progress?: { done?: number; total?: number | null; label?: string | null };\n /** Overrides the type-derived title when the host knows better. */\n title?: string | null;\n /** Why it failed / what went wrong inside it. Shown verbatim. */\n error?: string | null;\n /** Where the finished work landed \u2014 an in-app, root-relative path the card\n * links to (e.g. \"/assets\"). Omit when the job produced nothing to open. */\n resultPath?: string | null;\n /**\n * Where this work can be WATCHED while it runs \u2014 an in-app, root-relative\n * path, available from the moment the work exists rather than when it ends.\n *\n * Distinct from `resultPath`, which is the finished artefact and is null\n * until then. A report is the case that needs it: its whole premise is a\n * page you watch build, so \"come and look\" is useful at second one and\n * useless at the end. In advanced view the widget points the app pane here\n * itself (see `autoNavigateFrame`), which is the difference between telling\n * someone to watch it build and showing them.\n *\n * Omit for work with nothing to watch \u2014 a job whose only observable state is\n * the card itself.\n */\n livePath?: string | null;\n /**\n * The MEDIA this job produced, by id \u2014 what lets a `[[ui:\u2026]]` tile that was\n * drawn empty fill itself in with the actual asset the moment the job\n * finishes, instead of asking the client to go and look.\n *\n * An id and not a URL, for the same reason everywhere else in this file: ours\n * are signed and short-lived, and a job runs for minutes. The widget\n * resolves it through the same batched `resolveMediaUrls` every other tile\n * uses. Omitted by every job type that produces no single asset.\n */\n resultMediaId?: string | null;\n /**\n * The job's FLOW \u2014 the runner's own narration (which pages the AI picked,\n * how it grouped them, what each step yielded), oldest first. Server text,\n * escaped like every other untrusted string. Rides the same poll as\n * progress, so the card is live while the job runs \u2014 and because it comes\n * from the server, a page refresh replays it instead of losing it.\n */\n events?: Array<{\n /** \"status\" | \"decision\" | \"step\" | \"problem\" \u2014 coarse render hint. */\n kind?: string;\n message: string;\n at?: string;\n /** The runner's MACHINE-readable note on this step. `code` is the one key\n * read here: it lets a step be shown in the reader's own language instead\n * of in the runner's English, which has no request locale to work from.\n *\n * Nullable, not merely optional: the wire type sends an explicit `null`\n * for a step that carries no payload, and the host forwards the API's\n * events verbatim rather than mapping them one by one. */\n data?: Record<string, unknown> | null;\n }>;\n}\n\nexport interface AiChatWidgetOptions {\n /** Streaming chat endpoint (POST). e.g. https://api.example.com/accounts/:id/ai/chat */\n endpoint: string;\n /** Media-upload endpoint (POST multipart) for chat attachments \u2014 e.g.\n * https://api.example.com/accounts/:id/assets/media. When set (authed\n * surfaces), the composer shows a paperclip so the user can attach files the\n * assistant reads. Uploads are sent SESSION-scoped (`session=1` + the\n * current threadId): reachable from the chat history, never filed into the\n * asset library. Omit on the anonymous surface (nowhere to store). */\n uploadEndpoint?: string;\n /** Account the chat is scoped to. Omit for the public/anonymous endpoint. */\n accountId?: string;\n /**\n * DYNAMIC account scope, read at call time and preferred over `accountId`.\n * For hosts whose page context changes while the widget lives on (the admin\n * backoffice follows staff across accounts): re-creating the widget per\n * account aborts an in-flight reply and visibly resets the chat, so such a\n * host mounts ONE instance and feeds the current account through here.\n */\n getAccountScope?: () => string | undefined;\n /** Dynamic twin of `uploadEndpoint`, same reason. The attach button exists\n * when either is provided. */\n getUploadEndpoint?: () => string | undefined;\n /**\n * Extra fields merged into every POST body \u2014 e.g. `{ visitorId, source }` for\n * the anonymous marketing endpoint. Never put secrets here; it's plain JSON.\n */\n extraBody?: Record<string, unknown>;\n /**\n * Dynamic per-send context (current page, recent navigation, last actions).\n * Merged into each POST body under `pageContext` so the assistant can answer\n * about what the user is looking at. Called right before every send.\n */\n getContext?: () =>\n | PageContext\n | Record<string, unknown>\n | undefined\n | Promise<PageContext | Record<string, unknown> | undefined>;\n /**\n * Window event name that opens the panel \u2014 pass `OPEN_ASSISTANT_EVENT`, so a\n * nav/sidebar link elsewhere in the app can summon the widget.\n */\n openEventName?: string;\n /**\n * How the host phrases a confirm card for its OWN write tools.\n *\n * The card is the security boundary \u2014 the user approves the sentence, not the\n * tool call \u2014 so a host that has write tools should supply both of these.\n * Anything not covered falls through to a generic renderer that describes the\n * call from its arguments, which is what makes an unrecognised tool safe\n * rather than blank.\n *\n * sgiant's own fifteen branches live in `@sgiant/ai-apply`; they were in here\n * until 2026-09-02, naming sgiant tools to people embedding a chat bubble.\n */\n /** Tool name -> card TITLE. Falls back to the `proposalGeneric` label. */\n proposalTitles?: Record<string, string>;\n /** Tool name + args -> card BODY. Return null to use the generic renderer. */\n proposalSummary?: (\n name: string,\n args: Record<string, unknown>\n ) => string | null;\n\n /**\n * The status badge's vocabulary, all host-supplied and all optional.\n *\n * These were hard-coded to sgiant's own tools until 2026-09-02 \u2014 the map\n * literally listed `run_stats_query` and `list_hotels`. That is host\n * configuration sitting in a package meant to be generic, the same mistake\n * `applyProposal` was: harmless while there is one host, nonsense the moment\n * a stranger installs it and the badge flips to \"Analytics\" for a tool their\n * product does not have.\n *\n * Omit them all and the badge simply never changes role, which is the right\n * behaviour for a host that has no such concept.\n */\n /** Role id -> display name, e.g. `{ analytics: \"Analytics\" }`. Unknown ids\n * fall back to the id itself, so a missing entry degrades to something\n * readable rather than blank. */\n roleNames?: Record<string, string>;\n /** Tool name -> role id. Flips the badge as the agent runs that tool. */\n toolRoles?: Record<string, string>;\n /** Host action name -> role id. Flips the badge when the action is invoked. */\n actionRoles?: Record<string, string>;\n /** Bearer token (Clerk session or embed token). Use getToken for refresh. */\n token?: string;\n /** Async token provider \u2014 called before each send (overrides `token`). */\n getToken?: () => string | Promise<string>;\n /** Send cookies (same-origin in-app embedding). Default false. */\n withCredentials?: boolean;\n /**\n * Authed surfaces (org + admin): a persistent status bar shows the remaining\n * AI credits and which Copilot role is acting. Called on open + after each turn;\n * return the current credit balance (null = unknown/hidden).\n */\n getBalance?: () => Promise<number | null> | number | null;\n /** Assistant name shown in the header + bubble aria-label. Default \"Copilot\". */\n title?: string;\n /** Small line under the name. Default \"Growth assistant\". */\n subtitle?: string;\n /** Avatar image URL (the brand logo mark). Falls back to a crescent glyph. */\n avatarUrl?: string;\n /** CSS gradient for the chrome (header/bubble) for a premium look. */\n gradient?: string;\n greeting?: string;\n /**\n * Page-aware shortcuts. Returns a few suggested questions/actions for the\n * CURRENT page (the host derives them from the path) \u2014 rendered as clickable\n * chips under the greeting whenever the conversation is empty. Clicking a chip\n * sends it as the user's message. Called on open + new chat, so the shortcuts\n * follow the user around the app. Return [] (or omit) to show none.\n */\n getSuggestions?: () => string[] | Promise<string[]>;\n accent?: string;\n /**\n * Theme token overrides. The widget's entire stylesheet reads from\n * `--aiw-*` CSS custom properties set on its roots; pass any subset here to\n * restyle it (e.g. a dark theme: `{ surface:\"#16161e\", bg:\"#101016\",\n * text:\"#e7e7ec\", \"text-2\":\"#b6b6c2\", muted:\"#8a8a96\", border:\"#2b2b36\",\n * \"border-strong\":\"#3a3a48\", \"border-soft\":\"#22222c\" }`). Known tokens:\n * accent, accent-contrast, gradient, surface, surface-2, bg, text, text-2,\n * muted, border, border-strong, border-soft. `accent`/`gradient` options\n * remain the shorthand for the two most common ones.\n */\n theme?: Record<string, string>;\n /**\n * Custom directive renderers \u2014 the OPEN extension point. The assistant can\n * emit `[[<tag>:{json}]]` for any tag registered here; the widget strips the\n * directive, mounts a host element in the log, and calls your renderer with\n * the parsed JSON. Return a disposer to be called when the message clears.\n * Built-in tags (widget/preview/navigate/action/chips/form) are reserved and\n * cannot be overridden. Also available post-mount via\n * `handle.registerRenderer(tag, fn)`.\n */\n renderers?: Record<string, DirectiveRenderer>;\n /**\n * Fallback chart renderer for embeds WITHOUT `renderDataWidget` (no React\n * host). When the assistant streams a `render_chart` frame and no host\n * renderer is wired, this is consulted before the built-in degrade\n * (kpi \u2192 stat, else a plain table) \u2014 plug a lightweight chart lib here to\n * get real charts in a standalone embed. Same contract as renderDataWidget.\n */\n renderChartFallback?: (\n host: HTMLElement,\n spec: unknown,\n rows: unknown,\n comparisonRows?: unknown\n ) => (() => void) | void;\n position?: \"bottom-right\" | \"bottom-left\";\n /** Mount target; defaults to document.body. */\n container?: HTMLElement;\n /**\n * When set, the conversation (thread id + messages) is persisted to\n * localStorage under this key and restored on reload \u2014 so a page refresh\n * doesn't lose the chat. Use a per-visitor/per-user key (e.g. the visitorId).\n */\n persistKey?: string;\n /**\n * Scope for LAYOUT preferences \u2014 the panel's dragged position and its\n * open/closed state. Distinct from `persistKey` (which scopes the CONVERSATION)\n * on purpose: a host that changes persistKey per account (so each account keeps\n * its own thread) would otherwise reset the panel's position and re-close it on\n * every account switch \u2014 the user drags it once and it \"jumps\" as they navigate.\n * Pass a stable app-level value (e.g. \"backoffice\" / \"org\") so position + open\n * state follow the user across accounts and pages. Falls back to `persistKey`.\n */\n layoutKey?: string;\n /**\n * Prefix for every key this widget writes to `localStorage`. Defaults to\n * `\"aiw\"`, which is deliberately NEUTRAL \u2014 it matches the `--aiw-*` custom\n * properties the stylesheet already uses and names no product.\n *\n * It exists because a published widget writes into a stranger's browser\n * (#306). Keys read `aiw:draft:<persistKey>`, `aiw:sound`, `aiw:opened`; a\n * host that embeds more than one widget, or wants its own namespace, sets\n * this.\n *\n * CHANGING IT ORPHANS EXISTING DATA rather than migrating it: the old keys\n * are simply not read any more, so a returning user gets an empty draft and a\n * default position. sgiant's own surfaces pass `\"ayca\"` for exactly that\n * reason \u2014 it is what their keys were called before this option existed.\n */\n storageNamespace?: string;\n /**\n * Called when the user clicks \"Report issue\" on an error state \u2014 wire it to\n * your Backoffice/contact endpoint so the admin team gets the failed turn.\n */\n onReportIssue?: (details: {\n error: string;\n lastUserMessage: string;\n threadId?: string;\n }) => void | Promise<void>;\n /**\n * Flag the CURRENT conversation for oversight (agent/admin surfaces only). When\n * provided, a flag control appears in the header; clicking it asks for a reason\n * and calls this with the reason + the live thread id. Wire it to the flags API\n * (e.g. POST /admin/ai/conversations/:kind/:threadId/flag). Omit to hide the\n * control (e.g. the public visitor widget). Throw to surface a failure.\n */\n onFlag?: (details: {\n reason: string;\n threadId?: string;\n }) => void | Promise<void>;\n /**\n * Lead capture. When the assistant emits the sentinel `[[collect-email]]` in a\n * reply, the widget strips it and renders an inline email form; submitting it\n * calls this with the email (+ the last user message as context). Wire it to\n * your contact/lead API. Omit to disable lead capture entirely.\n */\n onLead?: (lead: { email: string; context?: string }) => Promise<void>;\n /**\n * Generic AI-rendered input forms. When the assistant emits a\n * `[[form:{...}]]` directive, the widget renders the described form inline and,\n * on submit, calls this with the directive's `action` name + the collected\n * field values. SECURITY: the AI only names an `action`; the HOST maps action\n * names to real API calls here \u2014 the AI can never call an arbitrary endpoint.\n * Throw to surface a retry. Return a string to show as the success message.\n */\n onWidgetAction?: (\n action: string,\n data: Record<string, string>\n ) => Promise<string | void>;\n /**\n * Show an expand/restore control in the header to grow the panel to a larger\n * reading size (and back). Default true. Set false for tight embeds.\n */\n expandable?: boolean;\n /**\n * Offer an \"auto-navigate\" toggle in the header. When the user turns it ON,\n * the assistant's `[[navigate:\u2026]]` suggestions are followed AUTOMATICALLY (no\n * confirm button) \u2014 a hands-free \"drive me there\" mode. State is BROWSER-LOCAL\n * (localStorage), off by default, and per-user controlled. In-app ACTIONS that\n * carry a `confirm` (anything that changes state or costs credits) are NEVER\n * auto-run \u2014 only pure page navigation is. Set true on surfaces where\n * navigation is enabled (org/admin/marketing).\n */\n autoNavOption?: boolean;\n /**\n * ADVANCED VIEW. When `getAdvancedUrl` is provided, a header toggle opens a\n * full-screen split: the chat on the left and the app in an <iframe> on the\n * right that Copilot can DRIVE (highlight / fill / click) via the postMessage\n * agent bridge (`sgiant-ai-agent-bridge`). The framed page must mount the\n * agent (`mountAiAgent`). `getAdvancedUrl()` returns the embed URL for the\n * CURRENT page; `getAdvancedUrl(path)` returns it for an account-relative path\n * (used to navigate the frame). Omit to disable advanced view.\n */\n getAdvancedUrl?: (path?: string) => string;\n /**\n * Map a navigation-class action (`navigate` / `open-*`) to the account-relative\n * path it opens, so advanced view can route it to the FRAME instead of the\n * parent app. Return null for non-navigation actions (they run host-side). Only\n * consulted in advanced view.\n */\n resolveActionPath?: (\n name: string,\n data: Record<string, string>\n ) => string | null;\n /** Origin the embedded app is served from, for the agent bridge. Defaults to\n * the current origin (same-origin embedding). */\n advancedOrigin?: string;\n /** Labels for the advanced-view controls (i18n; host supplies translations). */\n advancedLabel?: string;\n exitAdvancedLabel?: string;\n collapsePaneLabel?: string;\n expandPaneLabel?: string;\n /** Labels for the advanced-view fullscreen toggle (edge-to-edge on/off). */\n fullscreenLabel?: string;\n exitFullscreenLabel?: string;\n /** Label for the draggable chat\u21C6app divider (advanced view; drag to resize,\n * double-click to reset). Width persists to localStorage per host. */\n resizeLabel?: string;\n /**\n * Past-conversation history. When provided, a history control appears in the\n * header; opening it lists the user's prior threads. Picking one calls\n * `loadThread` and replays its messages. Wire these to the authed endpoints\n * (e.g. GET /accounts/:id/ai/threads and \u2026/threads/:threadId/messages). Omit\n * to disable history (the widget still restores the last thread via persistKey).\n */\n listThreads?: () => Promise<\n Array<{\n id: string;\n title?: string | null;\n updatedAt?: string;\n starred?: boolean;\n }>\n >;\n /** Toggle the SHARED star (team bookmark) on a past thread from the history\n * panel. Resolves to the new starred state. Omit to hide the star toggle. */\n starThread?: (threadId: string) => Promise<{ starred: boolean }>;\n /** Load one past thread (oldest\u2192newest) for replay in the log. Items are\n * messages OR inline data widgets, so reopening a conversation restores its\n * charts/tables \u2014 not just text (matching the full-page assistant). */\n loadThread?: (threadId: string) => Promise<LoadedThreadItem[]>;\n /** Switch the active branch of a conversation tree (the \u2039n/m\u203A sibling\n * switcher, ChatGPT/Claude-style edit-and-continue). POSTs the chosen leaf so\n * the server recomputes the active path; the widget then reloads the thread\n * via `loadThread`. Omit on hosts without branching \u2192 the edit/regenerate/\n * switcher controls stay hidden. */\n setActiveLeaf?: (threadId: string, messageId: string) => Promise<void>;\n /** UI labels for the branching controls. The host feeds translated strings\n * (per the repo i18n rule); the widget uses the English fallbacks below only\n * when an override is absent \u2014 it never hardcodes a user-visible string. */\n editLabel?: string;\n regenerateLabel?: string;\n saveLabel?: string;\n cancelLabel?: string;\n /** Record a thumbs up/down on an assistant reply (the chat quality signal).\n * value: 1 = up, -1 = down, 0 = clear. When provided, vote buttons appear on\n * replayed assistant turns; omit on hosts without the vote endpoint \u2192 buttons\n * hidden. Wire to POST /ai/vote (reuses the AiMessageVote backend), mirroring\n * the React panel's `vote`. */\n vote?: (input: {\n threadId: string;\n messageId: string;\n value: 1 | -1 | 0;\n model?: string;\n /** One line of free text from the quality prompt (#299). The reason is\n * where the signal actually is \u2014 \"it answered the wrong question\" is\n * actionable in a way a finer number is not. */\n reason?: string;\n }) => Promise<void>;\n voteUpLabel?: string;\n voteDownLabel?: string;\n /** Translated copy for the widget's OWN chrome \u2014 header, More menu, composer,\n * status bar, activity chips, proposal cards, history panel, error state. The\n * widget is vanilla DOM and can't call i18next, so the host builds this from\n * its own t() via `resolveWidgetLabels(t)`. Any omitted key falls back to the\n * English default in WIDGET_LABELS \u2014 the widget never hardcodes visible copy. */\n labels?: Partial<WidgetLabels>;\n /** Where \"Sign up\" sends the visitor when the free token allowance runs out\n * (and from the meter's CTA). When set, the widget shows the token meter. */\n signupUrl?: string;\n /**\n * Rich-content render hooks (in-app React hosts). When provided, the widget\n * hands the assistant's final reply markdown / data-widget frames to the host\n * to render with the REAL @sgiant/ui components (<Markdown>, <AiDataWidget>) \u2014\n * pixel-identical to the full-page assistant. Each returns an optional\n * disposer the widget calls when it clears that message (new chat / history\n * switch / destroy) so the host can unmount its React root. Omit on external\n * embeds (no React) \u2192 the widget uses its built-in lightweight renderers.\n */\n renderMarkdown?: (host: HTMLElement, markdown: string) => (() => void) | void;\n /** Render an assistant `render_chart` frame (spec + rows) as a real chart. */\n renderDataWidget?: (\n host: HTMLElement,\n spec: unknown,\n rows: unknown,\n comparisonRows?: unknown\n ) => (() => void) | void; /**\n * Resolve library media ids to URLs the browser can load, for `[[ui:\u2026]]`\n * cards. Called ONCE per card with every id it needs; return a map (omit an\n * id that can't be resolved \u2014 the tile then says so rather than sitting\n * blank).\n *\n * This is the reason a composed card can show the account's own pictures at\n * all: the widget is embeddable and has no session of its own, so only the\n * host can mint a signed URL. It is also why the spec names media by ID and\n * never by URL \u2014 an id resolves against media the ACCOUNT owns, so a model\n * that invents one gets an empty tile instead of the client's browser\n * fetching a URL the model chose.\n */\n resolveMediaUrls?: (ids: string[]) => Promise<Record<string, string>>;\n /**\n * Apply a confirm-gated WRITE-tool proposal (e.g. `organize_assets`). The AI\n * never mutates directly \u2014 it proposes; the widget shows a card and THIS runs\n * only when the user clicks Apply (a Clerk-authed action in the host). Map the\n * tool name \u2192 a real, access-checked API call. Return a string to show as the\n * success note; throw to let the user retry. Omit to hide write proposals.\n */\n /**\n * Where a report lives in THIS app. The widget runs in three shells with\n * different route shapes, so it cannot build the URL itself \u2014 and guessing\n * one would produce a link that 404s in two of them.\n */\n reportHref?: (reportId: string) => string;\n onApplyProposal?: (\n name: string,\n args: Record<string, unknown>,\n /** The account this proposal is FOR, when the worker said. The host applies\n * on its own session and would otherwise aim at whatever account its page\n * is scoped to \u2014 which is not always the account the assistant reasoned\n * about, and on a platform page is not an account at all. */\n opts?: { accountId?: string; artifactId?: string }\n ) => Promise<\n string | void | { message?: string; jobId?: string; reportId?: string }\n >;\n /**\n * OBSERVE the human's answer to a `question` frame \u2014 analytics, or resolving\n * the same question on another surface (a critical one also pushed to\n * Telegram). NOT the delivery path: the ask_user turn has already ENDED\n * server-side, so the widget itself delivers the answer as the next user\n * message. A host that omits this loses nothing but the notification.\n */\n onAnswer?: (answer: {\n questionId: string;\n optionIds?: string[];\n text?: string;\n }) => void;\n /**\n * Read ONE background job, GENERICALLY. Wire it to the type-agnostic job read\n * (`GET /accounts/:id/jobs/:jobId`) \u2014 never to a per-type endpoint, because\n * the whole point of the generic row is that a fourth job type must not need a\n * new poller here. Whenever `onApplyProposal` answers with a `jobId`, the\n * widget draws a live card in the conversation and polls this until the job\n * reaches a terminal state.\n *\n * Omit it and nothing changes: an apply that returns a job id is reported\n * exactly as it is today (a one-line \"started \u2713\" and no card).\n */\n getJob?: (jobId: string) => Promise<WidgetJobView | null>;\n /**\n * Read ONE report. The single deliberate exception to `getJob`'s \"never a\n * per-type endpoint\" rule, and it is worth stating why.\n *\n * A report stopped being a `job` row: it owns its lifecycle, its narration\n * and its stage vocabulary outright, and after the contract migration there\n * is no generic row to read. That is not a fourth job type wanting its own\n * poller \u2014 it is a second SOURCE of tracked work, which is why the fan-out\n * below is untouched and only the READ dispatches. One poll loop still\n * serves every card and every tile.\n *\n * Omit it and a report card simply never polls.\n */\n getReport?: (reportId: string) => Promise<WidgetJobView | null>;\n /**\n * Jobs still RUNNING for a thread, asked of the server on reopen.\n *\n * The widget also remembers its own jobs in browser storage, which is fast\n * and enough for the device that started them \u2014 and wrong everywhere else. A\n * report begun on a phone, or before the cache was cleared, left the desktop\n * showing a conversation with nothing happening in it while it ran. This is\n * the authoritative answer; storage is the instant one.\n *\n * Omit it and re-attach stays per-browser, exactly as before.\n */\n listThreadJobs?: (threadId: string) => Promise<WidgetJobView[]>;\n /**\n * Cancel a running/queued job the chat is watching (the card's Cancel\n * button). Cooperative on the server \u2014 a report stops at the next step\n * boundary \u2014 so the card keeps polling and flips to \"Cancelled\" when the\n * runner actually stops. Omit to hide the button.\n */\n cancelJob?: (jobId: string) => Promise<void>;\n /** Cancel a running/queued REPORT \u2014 the same cooperative contract as\n * `cancelJob`, against the report's own route. Omit to hide the button on\n * report cards. */\n cancelReport?: (reportId: string) => Promise<void>;\n /** This thread's UNSAVED session artifacts (media scraped or imported in the\n * conversation, hidden from the library until saved) \u2014 drives the artifact\n * strip above the composer. Omit on hosts without asset storage. */\n listSessionArtifacts?: (threadId: string) => Promise<\n {\n id: string;\n filename: string;\n contentType: string;\n label?: string | null;\n }[]\n >;\n}\n\n// The directive vocabulary moved to `./specs.ts` (#320): what the model may\n// emit inside `[[tag:{json}]]`, and the parsing of it. #306 calls this the\n// widget\u2019s real extension point, so it needs to be findable.\nimport {\n type WidgetSpec,\n type NavigateSpec,\n type PreviewSpec,\n type ActionSpec,\n type ChipsSpec,\n LEAD_TOKEN,\n type FormSpec,\n type ProposalField,\n buildField,\n isTruthyValue,\n isPrimitiveArg,\n proposalFields,\n parseFormDirective,\n stripDirectivesForReplay,\n} from \"./specs\";\n\n// URL safety moved to `./safe-url.ts` (#320): what the widget will navigate\n// to, given that model output is untrusted. A public surface once the\n// widget publishes.\nimport {\n isSafeRelPath,\n gateNavigationTarget,\n isSafeFrameUrl,\n} from \"./safe-url\";\nexport { gateNavigationTarget } from \"./safe-url\";\n\n/**\n * Renders one custom `[[<tag>:{json}]]` directive into `host` (already\n * mounted in the chat log). `spec` is the directive's parsed JSON \u2014 validate\n * it yourself, the assistant authored it. Optionally return a disposer; the\n * widget calls it when the surrounding message clears (new chat / history\n * switch / destroy).\n */\nexport type DirectiveRenderer = (\n host: HTMLElement,\n spec: unknown\n) => (() => void) | void;\n\n/**\n * What the collapsed launcher is saying (#305).\n *\n * It had exactly ONE appearance, which is why an unread reply and an idle\n * corner animated about equally. Only `unread` and `working` move; the rest are\n * static differences, and that is what makes movement mean something again.\n */\nexport interface LauncherState {\n state?: \"resting\" | \"unread\" | \"working\" | \"offline\" | \"parked\";\n /** `unread` only \u2014 shown as a count badge. */\n count?: number;\n /** `pill` on a first visit (a word, not a puzzle \u2014 #88); `pebble` after. */\n variant?: \"pebble\" | \"pill\";\n /** Which way the mark and disc invert. \"auto\" reads the host's own surface. */\n ground?: \"light\" | \"ink\" | \"auto\";\n}\n\nexport interface AiChatWidgetHandle {\n open(): void;\n close(): void;\n toggle(): void;\n destroy(): void;\n /** Register a custom directive renderer at runtime (see `renderers`).\n * Reserved built-in tags are rejected with an Error. */\n registerRenderer(tag: string, renderer: DirectiveRenderer): void;\n /** Drive the collapsed launcher. Partial \u2014 omitted fields keep their value. */\n setLauncher(next: LauncherState): void;\n}\n\n// Every SVG this file draws now lives in `./icons.ts` (#320) \u2014 pure data,\n// and the file an embedder must point at to replace the mark (#306:\n// `avatarUrl` only accepts a bitmap today).\nimport {\n AVATAR_DEFS,\n AVATAR_SVG,\n ICON_HISTORY,\n ICON_EXPAND,\n ICON_COLLAPSE,\n ICON_COMPASS,\n ICON_ADVANCED,\n ICON_CHEVRON_R,\n ICON_DOWNLOAD,\n ICON_FLAG,\n ICON_BELL,\n ICON_BELL_OFF,\n ICON_MORE,\n ICON_ATTACH,\n ICON_EDIT,\n ICON_REGEN,\n ICON_THUMB_UP,\n ICON_THUMB_DOWN,\n} from \"./icons\";\n\nexport function createAiChatWidget(\n opts: AiChatWidgetOptions\n): AiChatWidgetHandle {\n const accent = opts.accent ?? \"#6d28d9\";\n const gradient =\n opts.gradient ?? `linear-gradient(135deg,${accent},${accent})`;\n const side = opts.position === \"bottom-left\" ? \"left\" : \"right\";\n const root = opts.container ?? document.body;\n const name = opts.title ?? \"Copilot\";\n // Resolve a widget-chrome label: host-provided translation (opts.labels) \u2192\n // English default (WIDGET_LABELS). `{token}` placeholders are filled from\n // params so runtime values (name, filename, error) interpolate in any language.\n const L = (\n key: keyof WidgetLabels,\n params?: Record<string, string | number>\n ): string => {\n let s: string = opts.labels?.[key] ?? WIDGET_LABELS[key];\n if (params)\n for (const p of Object.keys(params))\n s = s.split(`{${p}}`).join(String(params[p]));\n return s;\n };\n // Avatar markup: the brand logo mark (img) when given, else a crescent glyph.\n const avatarInner = opts.avatarUrl\n ? `<img src=\"${opts.avatarUrl}\" alt=\"${name}\" class=\"${PREFIX}-av-img\"/>`\n : AVATAR_SVG;\n /** Aborted by `destroy()`. Every in-flight turn is tied to it, so a widget\n * that has been torn down stops writing to its own dead DOM and storage. */\n const alive = new AbortController();\n let threadId: string | undefined;\n /** True when the VIEW on screen has a turn in flight (per-thread, not\n * global): it blocks double-sending into the same conversation, while other\n * threads stay free to receive a new message. Derived from\n * `pendingThreads` by `syncBusy()` \u2014 never written directly elsewhere. */\n let busy = false;\n /**\n * Cross-thread stream fence. Every send() captures the view generation;\n * switching threads / starting a new chat bumps it. A stale turn keeps\n * CONSUMING its stream so the server completes + persists normally, but\n * stops touching the visible log / `history` / localStorage \u2014 the reply\n * belongs to ITS thread, not whichever conversation is now on screen (the\n * exact bleed users reported when switching mid-answer).\n */\n let viewGen = 0;\n /** Placeholder key for a turn whose brand-new thread id hasn't arrived yet\n * (the server names the thread in the first frame). */\n const NEW_TURN_KEY = \"\u00B7new\u00B7\";\n /** How long a single assistant turn must run before the widget asks whether\n * the wait was worth it (#299). 20s is comfortably past a normal tool-using\n * answer and far short of the 300s ceiling. */\n const QUALITY_SLOW_MS = 20_000;\n /** Threads with a response in flight \u2014 drives the History list's\n * \"answering\u2026\" spinner and the per-thread busy state. */\n const pendingThreads = new Set<string>();\n const isThreadPending = (key: string | undefined): boolean =>\n pendingThreads.has(key ?? NEW_TURN_KEY);\n /** Recompute `busy` for the CURRENT view (call after any switch or any\n * pending-set change). Function-declared so early code can reference it;\n * it only runs after the composer exists. */\n function syncBusy(): void {\n busy = isThreadPending(threadId);\n sendBtn.disabled = busy;\n // The launcher's `working` state, from the real in-flight turn rather than\n // a separate flag that could drift from it (#305). Only while the panel is\n // CLOSED \u2014 a breathing launcher next to an open panel that is already\n // streaming says nothing the panel is not saying louder.\n if (typeof applyLauncher === \"function\" && panel.style.display === \"none\") {\n setLauncher({ state: busy ? \"working\" : \"resting\" });\n }\n }\n let lastUserContent = \"\";\n // The navigable pages from the most recent turn's pageContext, cached so the\n // #111 prose-nav fallback (linkifyProseNav) can match a model's \"Open <page>\"\n // prose against a real path even after the turn's getContext() has gone \u2014\n // and, since S4, the allow-list `dispatchAction` checks a navigation against.\n let knownNavTargets: Array<{\n path?: string;\n title?: string;\n action?: string;\n }> = [];\n /** Read the catalogue straight from the host, for a navigation dispatched\n * before this session has sent a turn (a chip in a restored transcript). */\n async function refreshNavTargets(): Promise<void> {\n if (!opts.getContext) return;\n try {\n const ctx = await opts.getContext();\n const targets = (ctx as { navTargets?: unknown } | undefined)?.navTargets;\n if (Array.isArray(targets)) knownNavTargets = targets;\n } catch {\n // A host that cannot say what its pages are has not declared any.\n }\n }\n\n // Conversation memory across page reloads (opt-in via persistKey). Kept in\n // localStorage so a refresh restores the thread + messages.\n type WidgetAtt = {\n mediaId: string;\n kind: string;\n filename: string;\n contentType: string;\n };\n type StoredMsg = {\n role: \"user\" | \"assistant\";\n content: string;\n attachments?: WidgetAtt[];\n };\n // Every persisted key hangs off this one prefix (#306). Neutral by default:\n // a published widget must not write a product name into a stranger's browser.\n const ns = opts.storageNamespace || \"aiw\";\n const storeKey = opts.persistKey ? `${ns}:v1:${opts.persistKey}` : null;\n // Remember whether the panel was left open, so a page refresh restores it\n // (in-app surfaces only \u2014 external embeds shouldn't auto-pop for visitors).\n // Layout prefs (open state + position) use layoutKey so they follow the user\n // across accounts, not persistKey which re-scopes per account (see layoutKey).\n const layoutScope = opts.layoutKey ?? opts.persistKey;\n const openStateKey = layoutScope ? `${ns}:open:${layoutScope}` : null;\n // Keep the UNSENT composer draft across refreshes so typing isn't lost.\n const draftKey = opts.persistKey ? `${ns}:draft:${opts.persistKey}` : null;\n function saveDraft(v: string): void {\n writeItem(draftKey, v);\n }\n function loadDraft(): string {\n return readItem(draftKey);\n }\n function rememberOpen(isOpen: boolean): void {\n writeFlag(openStateKey, isOpen);\n }\n const history: StoredMsg[] = [];\n /**\n * How long a conversation keeps auto-restoring after its last activity.\n *\n * Restore exists so a refresh or a page change mid-task doesn't lose the\n * chat \u2014 a WORKING-SESSION convenience, not an archive. Without a cutoff the\n * widget reopened on whatever was said last, however long ago: days later\n * the first thing on screen was a stale test message instead of the\n * greeting. Older conversations are one click away in History; only a\n * recent one comes back on its own.\n */\n const RESTORE_MAX_AGE_MS = 12 * 60 * 60 * 1000;\n function loadState(): void {\n type Saved = {\n threadId?: string;\n messages?: StoredMsg[];\n savedAt?: number;\n };\n const s = readJson<Saved>(\n storeKey,\n (v): v is Saved => Boolean(v) && typeof v === \"object\"\n );\n if (!s) return;\n // No stamp (a pre-cutoff save) counts as stale \u2014 the greeting wins.\n if (\n typeof s.savedAt !== \"number\" ||\n Date.now() - s.savedAt > RESTORE_MAX_AGE_MS\n ) {\n return;\n }\n threadId = s.threadId;\n if (Array.isArray(s.messages)) history.push(...s.messages);\n }\n function saveState(): void {\n writeJson(storeKey, {\n threadId,\n messages: history.slice(-40),\n savedAt: Date.now(),\n });\n }\n loadState();\n\n injectStyles(side);\n\n const bubble = el(\"button\", `${PREFIX}-bubble`);\n bubble.setAttribute(\"aria-label\", L(\"openBubble\", { name }));\n bubble.innerHTML =\n `<span class=\"${PREFIX}-bubble-av\">${avatarInner}</span>` +\n `<span class=\"${PREFIX}-bubble-label\"></span>` +\n `<span class=\"${PREFIX}-bubble-dot\" hidden></span>`;\n\n /**\n * The launcher's six states (#305).\n *\n * Held as one object and applied wholesale so a partial update cannot leave\n * two states on at once \u2014 the reason the old launcher had one appearance is\n * that nothing owned this.\n *\n * `ground: \"auto\"` reads the HOST's own background rather than guessing: the\n * same widget mounts on a white app, a cream report and a near-black\n * marketing page, and the mark has to straddle navy on all three.\n */\n // DECLARED ABOVE `launcher`, and that is a fix rather than a tidy-up.\n //\n // `launcher` calls hasOpenedBefore() in its initialiser. While this const sat\n // BELOW that call, the function read OPENED_KEY inside its temporal dead zone\n // and threw a ReferenceError on every single construction \u2014 and the old\n // implementation's own `try { \u2026 } catch { return true }` swallowed it, because\n // a TDZ violation IS a ReferenceError. So the catch that exists to survive\n // blocked storage was silently absorbing a crash, and `hasOpenedBefore()`\n // answered \"true\" for everyone: the first-visit pill has never rendered for\n // any visitor, ever.\n //\n // It surfaced only when #320 moved that try/catch into ./storage's readFlag,\n // which let the error escape to the caller instead \u2014 and then only in a\n // browser, since nothing type-checks or unit-tests initialiser ORDER.\n const OPENED_KEY = `${ns}:opened`;\n function hasOpenedBefore(): boolean {\n // TRUE when storage is unreadable, unlike every other flag in this file:\n // show the pebble rather than greeting a returning reader with the\n // first-visit pill on every page load.\n return readFlag(OPENED_KEY, true);\n }\n function markOpened(): void {\n writeFlag(OPENED_KEY, true);\n }\n\n const launcher: Required<LauncherState> = {\n state: \"resting\",\n count: 0,\n // A reader who has opened it once does not need the word again. Its OWN\n // key, not the thread cache: that expires after 12 hours by design, and\n // \"has this person ever met the widget\" is not a fact that should expire.\n variant: hasOpenedBefore() ? \"pebble\" : \"pill\",\n ground: \"auto\",\n };\n\n function resolveGround(): \"light\" | \"ink\" {\n if (launcher.ground !== \"auto\") return launcher.ground;\n try {\n // SAMPLE WHERE THE LAUNCHER ACTUALLY SITS, not up its parent chain.\n // Walking parents reads <body>, and on a marketing page body is\n // near-white while the section under the corner is near-black \u2014 measured\n // on our own home page, which returned \"light\" over a dark hero. The\n // launcher is position:fixed, so its ancestors say nothing about what is\n // behind it.\n const r = bubble.getBoundingClientRect();\n const stack = document.elementsFromPoint(\n r.left + r.width / 2,\n r.top + r.height / 2\n );\n for (const node of stack) {\n // Skip the widget's own chrome, or it reads its own disc.\n if (node === bubble || bubble.contains(node) || panel.contains(node)) {\n continue;\n }\n const bg = getComputedStyle(node).backgroundColor;\n const m = /rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)(?:,\\s*([\\d.]+))?\\)/.exec(bg);\n // `transparent` tells us nothing \u2014 keep looking behind it.\n if (!m || (m[4] !== undefined && Number(m[4]) <= 0.5)) continue;\n const [r8, g8, b8] = [Number(m[1]), Number(m[2]), Number(m[3])];\n // Perceived lightness, not the average \u2014 green dominates.\n return (0.2126 * r8 + 0.7152 * g8 + 0.0722 * b8) / 255 < 0.5\n ? \"ink\"\n : \"light\";\n }\n } catch {\n /* no DOM answer \u2014 the light default is the common case */\n }\n return \"light\";\n }\n\n function applyLauncher(): void {\n const dot = bubble.querySelector(\n `.${PREFIX}-bubble-dot`\n ) as HTMLElement | null;\n const label = bubble.querySelector(\n `.${PREFIX}-bubble-label`\n ) as HTMLElement | null;\n for (const s of [\"unread\", \"working\", \"offline\", \"parked\"]) {\n bubble.classList.toggle(`${PREFIX}-bubble-${s}`, launcher.state === s);\n }\n bubble.classList.toggle(\n `${PREFIX}-bubble-pill`,\n launcher.variant === \"pill\"\n );\n bubble.classList.toggle(`${PREFIX}-on-ink`, resolveGround() === \"ink\");\n if (label) label.textContent = launcher.variant === \"pill\" ? name : \"\";\n if (dot) {\n const showCount = launcher.state === \"unread\" && launcher.count > 0;\n dot.hidden = !showCount && launcher.state !== \"offline\";\n dot.textContent = showCount ? String(launcher.count) : \"\";\n }\n // The label carries the state for anyone not looking at it.\n bubble.setAttribute(\n \"aria-label\",\n launcher.state === \"unread\" && launcher.count > 0\n ? L(\"openBubbleUnread\", { name, count: launcher.count })\n : L(\"openBubble\", { name })\n );\n }\n\n function setLauncher(next: LauncherState): void {\n Object.assign(launcher, next);\n applyLauncher();\n }\n const panel = el(\"div\", `${PREFIX}-panel`);\n // Theme: the stylesheet is written entirely against --aiw-* custom\n // properties, with light defaults + a prefers-color-scheme:dark token block\n // baked into the sheet. Only what the host EXPLICITLY passed goes inline on\n // the roots \u2014 inline wins over both scheme blocks, so a themed host is\n // deterministic while an untouched widget still follows the OS scheme.\n // Form controls additionally borrow the HOST app's control tokens when the\n // page actually has them \u2014 see `hostDefinesPlatformTokens`. Resolved once at\n // mount (the answer is a property of which page we're on, not of the moment),\n // and expressed as a class so the CSS keeps reading live `var()`s: the app's\n // own light/dark switch still repaints the chat's inputs with it.\n if (hostDefinesPlatformTokens()) {\n bubble.classList.add(`${PREFIX}-host-tokens`);\n panel.classList.add(`${PREFIX}-host-tokens`);\n }\n const themeTokens: Record<string, string> = { ...(opts.theme ?? {}) };\n if (opts.accent && !themeTokens.accent) themeTokens.accent = opts.accent;\n if ((opts.gradient || opts.accent) && !themeTokens.gradient) {\n themeTokens.gradient = gradient;\n }\n // DERIVE the foreground for accent-filled controls (#307). The sheet's\n // default is `#fff`, right against the widget's own violet (7.10:1) and wrong\n // against every accent a host actually passes \u2014 all three of ours send teal\n // `#60C7C8`, where white measures 2.00:1 across thirteen controls. A default\n // could not fix that, because the failing value WAS the default.\n //\n // An explicit `accent-contrast` still wins: a host that has measured its own\n // pairing is not overruled. And an accent we cannot parse (a `color-mix()`,\n // a var(), a named colour) leaves the token untouched rather than guessing.\n if (themeTokens.accent && !themeTokens[\"accent-contrast\"]) {\n const fg = resolveAccentContrast(themeTokens.accent);\n if (fg) themeTokens[\"accent-contrast\"] = fg;\n }\n // The user's own message bubble needs its OWN pairing, for the reason #307\n // exists and one level deeper. That bubble is not accent-filled: it is the\n // accent mixed 76% into a near-black navy, which lands somewhere the raw\n // accent's foreground was never measured against. Over the teal all three\n // hosts pass it resolves to `#4a9d9e`, where the sheet's literal `#fff` is\n // **3.18:1** \u2014 under the floor, on every message the user types, which is the\n // most-drawn element in the widget.\n //\n // Deriving against the MIX rather than the accent is the whole point; the mix\n // is computed once, in JS, and handed to CSS as a resolved value so the two\n // cannot disagree about what the background actually is. An explicit\n // `user-bg` / `user-contrast` from the host still wins, and an unparseable\n // accent leaves both untouched so the sheet's own defaults apply.\n // The accent as INK, per scheme (#326). `--aiw-accent` is a FILL colour, and\n // twenty rules also use it as text \u2014 links, hover states, the focused input,\n // chips, KPI deltas, the menu icons, the active vote. A fill is not an ink:\n //\n // teal #60C7C8 (all three hosts) 2.00:1 on the light surface\n // violet #6d28d9 (our own default) 2.55:1 on the dark one\n // amber #FBAA34 1.93:1 on the light one\n //\n // So this is not \"the teal is a bad accent\" \u2014 EVERY accent is illegible as\n // ink on one of the two schemes, and which one depends only on where the\n // colour sits between the surfaces. The widget's own default is the one that\n // fails in the dark.\n //\n // Two values, because the surface flips and an inline token would not: the\n // sheet picks between them (`--aiw-accent-ink` reads `-light` in the base\n // block and `-dark` in the dark one), so the app's own switch still drives it.\n // Both fall back to the raw accent when the accent cannot be parsed.\n if (themeTokens.accent) {\n for (const [suffix, surface] of [\n [\"light\", \"#ffffff\"],\n [\"dark\", \"#161616\"],\n ] as const) {\n const key = `accent-ink-${suffix}`;\n if (themeTokens[key]) continue;\n const ink = accentInk(themeTokens.accent, surface);\n if (ink) themeTokens[key] = ink;\n }\n }\n if (themeTokens.accent && !themeTokens[\"user-bg\"]) {\n const bg = mixSrgb(themeTokens.accent, USER_BUBBLE_INK, 0.76);\n if (bg) {\n themeTokens[\"user-bg\"] = bg;\n if (!themeTokens[\"user-contrast\"]) {\n const fg = resolveAccentContrast(bg);\n if (fg) themeTokens[\"user-contrast\"] = fg;\n }\n }\n }\n for (const [k, v] of Object.entries(themeTokens)) {\n bubble.style.setProperty(`--aiw-${k}`, v);\n panel.style.setProperty(`--aiw-${k}`, v);\n }\n panel.style.display = \"none\";\n panel.setAttribute(\"role\", \"dialog\");\n panel.setAttribute(\"aria-label\", L(\"panelAria\", { name }));\n\n // \u2500\u2500 Drag-to-reposition (floating mode only) \u2500\u2500\u2500\u2500\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 panel is anchored to a fixed corner, where it covers whatever the user\n // is trying to read. Let them drag it by the header, double-click to reset,\n // and remember where they put it.\n //\n // Pointer Events (not mouse events) so it works with touch and pen as well as\n // a mouse; the header sets `touch-action:none` so a drag doesn't scroll the\n // page underneath on a touchscreen.\n const posKey = layoutScope ? `${ns}:pos:${layoutScope}` : null;\n\n /**\n * Below this width the panel is a FULL-SCREEN bottom sheet (see the\n * sheet media query below \u2014 see SHEET_QUERY). Declared up here because applyPos\n * needs it and runs at mount \u2014 a `const` defined later would be in the\n * temporal dead zone and throw.\n */\n // MUST match the media query below, character for character in meaning:\n // the JS decides where the panel is positioned and the CSS decides what it\n // looks like, and a phone in landscape used to fall between them (#309).\n const SHEET_QUERY =\n \"(max-width:640px),(max-height:520px) and (pointer:coarse)\";\n const isSheet = (): boolean => window.matchMedia(SHEET_QUERY).matches;\n\n /**\n * Keep the panel fully on screen.\n *\n * Runs on drop AND on window resize \u2014 without the resize pass, a position\n * saved on a large screen leaves the panel off-canvas when the same user\n * opens a smaller window later, with no way back except clearing storage.\n */\n const clampPos = (x: number, y: number): { x: number; y: number } => {\n const w = panel.offsetWidth || 368;\n const h = panel.offsetHeight || 540;\n const maxX = Math.max(0, window.innerWidth - w);\n const maxY = Math.max(0, window.innerHeight - h);\n return {\n x: Math.min(Math.max(0, x), maxX),\n y: Math.min(Math.max(0, y), maxY),\n };\n };\n\n const toCorner = (): void => {\n // Back to the CSS-anchored corner: clear the inline overrides rather than\n // hard-coding the default, so the corner stays whatever CSS says.\n panel.style.left = panel.style.top = \"\";\n panel.style.right = panel.style.bottom = \"\";\n };\n\n const applyPos = (p: { x: number; y: number } | null): void => {\n // The mobile sheet is full-screen via CSS `inset:0`, and an INLINE\n // left/top beats a stylesheet rule. So a position saved by dragging the\n // panel on desktop used to survive onto the phone: the sheet never went\n // full-screen, it floated at the desktop coordinates with the host app's\n // header showing through \u2014 its logo and top-right actions colliding with\n // the widget's own. Below the breakpoint the saved position simply does\n // not apply. It stays in storage, so going back to desktop restores it.\n if (isSheet()) return toCorner();\n if (!p) return toCorner();\n const { x, y } = clampPos(p.x, p.y);\n // Atomic: a non-finite result (a NaN slipping in from a drag/resize edge\n // case) must NEVER leave the panel half-positioned \u2014 `left` set but `top`\n // empty makes `top` resolve to `auto`, which drops the panel to its static\n // flow position off-screen, and a refresh restores it there (stranded). Fall\n // back to the visible corner instead.\n if (!Number.isFinite(x) || !Number.isFinite(y)) return toCorner();\n panel.style.left = `${x}px`;\n panel.style.top = `${y}px`;\n panel.style.right = \"auto\";\n panel.style.bottom = \"auto\";\n };\n\n const readPos = (): { x: number; y: number } | null =>\n readJson(\n posKey,\n (v): v is { x: number; y: number } =>\n typeof (v as { x?: unknown })?.x === \"number\" &&\n typeof (v as { y?: unknown })?.y === \"number\"\n );\n\n const savePos = (p: { x: number; y: number } | null): void => {\n writeJson(posKey, p);\n };\n\n const header = el(\"div\", `${PREFIX}-header`);\n header.classList.add(`${PREFIX}-draggable`);\n\n let panelDrag: { dx: number; dy: number; id: number } | null = null;\n\n header.addEventListener(\"pointerdown\", (ev) => {\n // Never start a drag from a control in the header (menu, close, model chip)\n // \u2014 the buttons must keep working.\n if ((ev.target as HTMLElement)?.closest(\"button,a,input,select\")) return;\n // Advanced/full-screen mode owns its own layout; only the floating panel moves.\n if (panel.classList.contains(`${PREFIX}-advanced`)) return;\n const r = panel.getBoundingClientRect();\n panelDrag = {\n dx: ev.clientX - r.left,\n dy: ev.clientY - r.top,\n id: ev.pointerId,\n };\n header.setPointerCapture(ev.pointerId);\n panel.style.transition = \"none\"; // no easing while it follows the finger\n });\n\n header.addEventListener(\"pointermove\", (ev) => {\n if (!panelDrag || ev.pointerId !== panelDrag.id) return;\n ev.preventDefault();\n applyPos({ x: ev.clientX - panelDrag.dx, y: ev.clientY - panelDrag.dy });\n });\n\n const endPanelDrag = (ev: PointerEvent): void => {\n if (!panelDrag || ev.pointerId !== panelDrag.id) return;\n panelDrag = null;\n panel.style.transition = \"\";\n const r = panel.getBoundingClientRect();\n const p = clampPos(r.left, r.top);\n applyPos(p);\n savePos(p);\n };\n header.addEventListener(\"pointerup\", endPanelDrag);\n header.addEventListener(\"pointercancel\", endPanelDrag);\n\n // Double-click the header to snap back to the default corner \u2014 the same\n // gesture the advanced view's resize divider already uses, so there is one\n // thing to learn rather than two.\n header.addEventListener(\"dblclick\", (ev) => {\n if ((ev.target as HTMLElement)?.closest(\"button,a,input,select\")) return;\n savePos(null);\n applyPos(null);\n });\n\n // Re-clamp when the viewport changes so a saved position can never strand the\n // panel off-screen on a smaller window. Named so `destroy()` can remove it \u2014\n // an anonymous listener here leaked the entire widget graph (panel, log,\n // history, disposers) on every mount/unmount cycle, and admin remounts on\n // account and language change.\n const onResize = (): void => {\n const p = readPos();\n if (p) applyPos(p);\n };\n window.addEventListener(\"resize\", onResize);\n\n applyPos(readPos());\n\n const avatar = el(\"div\", `${PREFIX}-avatar`);\n avatar.innerHTML = avatarInner;\n const hName = el(\"div\", `${PREFIX}-hname`);\n const titleEl = el(\"span\", `${PREFIX}-title`);\n titleEl.textContent = name;\n const subEl = el(\"span\", `${PREFIX}-sub`);\n subEl.textContent = opts.subtitle ?? \"Growth assistant\";\n // Staff-only \"model\" chip \u2014 set from the server `meta` frame (which model/agent\n // runs the turn). Hidden until a staff turn reports it, so customers never see\n // a model id (isStaff is server-authoritative).\n const metaChip = el(\"span\", `${PREFIX}-metachip`);\n metaChip.hidden = true;\n hName.append(titleEl, subEl, metaChip);\n const hActions = el(\"div\", `${PREFIX}-hactions`);\n // New chat \u2014 start a fresh conversation (keeps the prior one in history).\n const newChatBtn = el(\"button\", `${PREFIX}-icon`);\n newChatBtn.setAttribute(\"aria-label\", L(\"newChat\"));\n newChatBtn.title = L(\"newChat\");\n newChatBtn.innerHTML =\n '<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><path d=\"M12 5v14M5 12h14\"/></svg>';\n hActions.appendChild(newChatBtn);\n // History (past conversations) \u2014 only when the host wired a thread lister.\n let historyBtn: HTMLElement | null = null;\n if (opts.listThreads) {\n historyBtn = el(\"button\", `${PREFIX}-icon`);\n historyBtn.setAttribute(\"aria-label\", L(\"pastConversations\"));\n historyBtn.title = L(\"history\");\n historyBtn.innerHTML = ICON_HISTORY;\n hActions.appendChild(historyBtn);\n }\n // \u2500\u2500 \"More\" overflow menu \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 // The secondary/toggle controls (download, flag, sound, auto-nav)\n // used to sit side-by-side in the header. As their number grew the title bar\n // became a cramped, unreadable strip of look-alike icons. They now live in a\n // labeled dropdown behind a single kebab button, so the header keeps only the\n // primary actions (new chat, history, expand, close). Each menu row carries a\n // text label (+ an On/Off pill for toggles) \u2014 far clearer than bare icons.\n const moreWrap = el(\"div\", `${PREFIX}-morewrap`);\n const moreBtn = el(\"button\", `${PREFIX}-icon`) as HTMLButtonElement;\n moreBtn.setAttribute(\"aria-label\", L(\"moreOptions\"));\n moreBtn.setAttribute(\"aria-haspopup\", \"menu\");\n moreBtn.setAttribute(\"aria-expanded\", \"false\");\n moreBtn.title = L(\"more\");\n moreBtn.innerHTML = ICON_MORE;\n const moreMenu = el(\"div\", `${PREFIX}-menu`);\n moreMenu.setAttribute(\"role\", \"menu\");\n // Class, not `display` (#309): the menu transitions now, and display is not\n // animatable. See the -menu / -menu-open rules.\n moreMenu.classList.remove(`${PREFIX}-menu-open`);\n moreWrap.append(moreBtn, moreMenu);\n let menuOpen = false;\n const setMenu = (open: boolean): void => {\n menuOpen = open;\n moreMenu.classList.toggle(`${PREFIX}-menu-open`, open);\n moreBtn.classList.toggle(`${PREFIX}-icon-on`, open);\n moreBtn.setAttribute(\"aria-expanded\", open ? \"true\" : \"false\");\n };\n moreBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n setMenu(!menuOpen);\n });\n // Dismiss on any click outside the menu, and on Escape. Both listeners live on\n // `panel` (removed on destroy) so nothing leaks when the widget is torn down.\n panel.addEventListener(\"click\", (e) => {\n if (menuOpen && !moreWrap.contains(e.target as Node)) setMenu(false);\n });\n panel.addEventListener(\"keydown\", (e) => {\n if (menuOpen && e.key === \"Escape\") {\n e.stopPropagation();\n setMenu(false);\n }\n });\n // Star state per thread, learned from the History list (which already returns\n // it) and updated on every toggle. Without this the menu row would have to\n // guess, and showing \"not starred\" for a starred conversation is worse than\n // showing nothing.\n const starredThreads = new Map<string, boolean>();\n let paintStarItem = (): void => {};\n\n // Build one labeled menu row. Returns handles so toggle syncs can update just\n // the icon / state pill without clobbering the label text.\n const menuItem = (\n label: string\n ): {\n btn: HTMLButtonElement;\n ico: HTMLElement;\n setState: (on: boolean) => void;\n } => {\n const btn = el(\"button\", `${PREFIX}-menu-item`) as HTMLButtonElement;\n btn.type = \"button\";\n btn.setAttribute(\"role\", \"menuitem\");\n const ico = el(\"span\", `${PREFIX}-menu-ico`);\n const lab = el(\"span\", `${PREFIX}-menu-label`);\n lab.textContent = label;\n const state = el(\"span\", `${PREFIX}-menu-state`);\n btn.append(ico, lab, state);\n return {\n btn,\n ico,\n setState: (on: boolean) => {\n state.textContent = on ? L(\"on\") : L(\"off\");\n btn.classList.toggle(`${PREFIX}-menu-item-on`, on);\n },\n };\n };\n\n // Download \u2014 export the current conversation as a plain-text transcript.\n const downloadItem = menuItem(L(\"downloadChat\"));\n downloadItem.ico.innerHTML = ICON_DOWNLOAD;\n downloadItem.btn.addEventListener(\"click\", () => {\n setMenu(false);\n exportConversation();\n });\n moreMenu.appendChild(downloadItem.btn);\n // Star THIS conversation (#299).\n //\n // The star was already reachable \u2014 but only from the History panel, i.e. only\n // on conversations you are NOT in. To bookmark the one you are reading you had\n // to leave it, find it in a list, and star it there. That is the same\n // reachability failure as the vote buttons, one surface over.\n //\n // It goes in the menu rather than the header on purpose: the header comment\n // above records that these controls used to sit side-by-side and became \"a\n // cramped, unreadable strip of look-alike icons\". A star is secondary.\n const starItem = opts.starThread ? menuItem(L(\"star\")) : null;\n if (starItem) {\n starItem.ico.textContent = \"\u2606\";\n // Repainted from the cache below whenever the thread changes, so the row\n // states what IS rather than what was last clicked.\n starItem.btn.addEventListener(\"click\", () => {\n if (!threadId || !opts.starThread) return;\n const id = threadId;\n starItem.btn.disabled = true;\n void opts\n .starThread(id)\n .then((r) => {\n starredThreads.set(id, r.starred);\n paintStarItem();\n })\n .finally(() => {\n starItem.btn.disabled = false;\n });\n });\n moreMenu.appendChild(starItem.btn);\n paintStarItem = (): void => {\n // No thread yet (a brand-new chat) means there is nothing to bookmark.\n const known = threadId ? starredThreads.get(threadId) : undefined;\n starItem.btn.hidden = !threadId;\n const on = known === true;\n starItem.ico.textContent = on ? \"\u2605\" : \"\u2606\";\n starItem.ico.style.color = on ? \"#f59e0b\" : \"\";\n const lab = starItem.btn.querySelector(`.${PREFIX}-menu-label`);\n if (lab) lab.textContent = on ? L(\"unstar\") : L(\"star\");\n };\n paintStarItem();\n }\n // Flag \u2014 oversight control (agent/admin surfaces). Asks for a reason, then\n // hands it to the host's onFlag with the live thread id. Hidden when unwired.\n if (opts.onFlag) {\n const flagItem = menuItem(L(\"flagConversation\"));\n flagItem.ico.innerHTML = ICON_FLAG;\n flagItem.btn.title = L(\"flagConversationHint\");\n flagItem.btn.addEventListener(\"click\", () => {\n setMenu(false);\n // A flag needs a live conversation (the thread id is created on the first\n // turn). Tell the user plainly instead of silently doing nothing.\n if (!threadId) {\n flagNote(L(\"flagSendFirst\"), false);\n return;\n }\n const reason = window.prompt(L(\"flagPrompt\"));\n if (!reason || !reason.trim()) return;\n flagItem.btn.disabled = true;\n Promise.resolve(opts.onFlag!({ reason: reason.trim(), threadId }))\n .then(() => {\n // Visible confirmation \u2014 a tooltip change alone is invisible, so the\n // user couldn't tell the flag worked.\n flagNote(L(\"flagged\"), true);\n })\n .catch((err) => {\n flagNote(\n L(\"flagFailed\", {\n msg: (err as Error)?.message || L(\"flagFailedGeneric\"),\n }),\n false\n );\n })\n .finally(() => {\n flagItem.btn.disabled = false;\n });\n });\n moreMenu.appendChild(flagItem.btn);\n }\n // Notification sound \u2014 a soft chime when a reply arrives (browser-local toggle).\n const SOUND_KEY = `${ns}:sound`;\n let soundOn = readFlag(SOUND_KEY);\n const soundItem = menuItem(L(\"notificationSound\"));\n const syncSound = (): void => {\n soundItem.ico.innerHTML = soundOn ? ICON_BELL : ICON_BELL_OFF;\n soundItem.setState(soundOn);\n soundItem.btn.title = soundOn ? L(\"soundOnHint\") : L(\"soundOffHint\");\n };\n syncSound();\n soundItem.btn.addEventListener(\"click\", () => {\n soundOn = !soundOn;\n writeFlag(SOUND_KEY, soundOn);\n syncSound();\n if (soundOn) maybeDing(true); // preview on enable\n });\n moreMenu.appendChild(soundItem.btn);\n // Soft two-note chime via WebAudio (no asset). Plays only when enabled.\n function maybeDing(force?: boolean): void {\n if (!soundOn && !force) return;\n try {\n const AudioCtx =\n window.AudioContext ||\n (window as unknown as { webkitAudioContext: typeof AudioContext })\n .webkitAudioContext;\n const ctx = new AudioCtx();\n const now = ctx.currentTime;\n [\n [880, 0],\n [1175, 0.12],\n ].forEach(([freq, at]) => {\n const o = ctx.createOscillator();\n const g = ctx.createGain();\n o.type = \"sine\";\n o.frequency.value = freq;\n g.gain.setValueAtTime(0.0001, now + at);\n g.gain.exponentialRampToValueAtTime(0.12, now + at + 0.02);\n g.gain.exponentialRampToValueAtTime(0.0001, now + at + 0.18);\n o.connect(g).connect(ctx.destination);\n o.start(now + at);\n o.stop(now + at + 0.2);\n });\n window.setTimeout(() => void ctx.close().catch(() => {}), 500);\n } catch {\n /* audio blocked \u2014 non-fatal */\n }\n }\n // Auto-navigate toggle \u2014 a BROWSER-LOCAL setting: when on, Copilot follows its\n // own navigation suggestions automatically (no confirm button). Off by default.\n const AUTONAV_KEY = `${ns}:autonav`;\n let autoNav = readFlag(AUTONAV_KEY);\n if (opts.autoNavOption) {\n const autoNavItem = menuItem(L(\"autoNavigate\"));\n autoNavItem.ico.innerHTML = ICON_COMPASS;\n const syncAutoNav = (): void => {\n autoNavItem.setState(autoNav);\n autoNavItem.btn.title = autoNav\n ? L(\"autoNavOnHint\")\n : L(\"autoNavOffHint\");\n };\n syncAutoNav();\n autoNavItem.btn.addEventListener(\"click\", () => {\n autoNav = !autoNav;\n writeFlag(AUTONAV_KEY, autoNav);\n syncAutoNav();\n });\n moreMenu.appendChild(autoNavItem.btn);\n }\n // The \"More\" menu holds the secondary controls; drop it into the header after\n // the primary actions (new chat / history), before expand + close.\n hActions.appendChild(moreWrap);\n // Expand / restore \u2014 grows the panel for easier reading (default on).\n let expandBtn: HTMLElement | null = null;\n const expandable = opts.expandable !== false;\n if (expandable) {\n expandBtn = el(\"button\", `${PREFIX}-icon ${PREFIX}-expand`);\n expandBtn.setAttribute(\"aria-label\", L(\"expandChat\"));\n expandBtn.title = L(\"expand\");\n expandBtn.innerHTML = ICON_EXPAND;\n hActions.appendChild(expandBtn);\n }\n // Advanced view \u2014 full-screen split with the app in a drivable iframe. Only\n // shown when the host wired `getAdvancedUrl`.\n let advancedBtn: HTMLElement | null = null;\n if (opts.getAdvancedUrl) {\n advancedBtn = el(\"button\", `${PREFIX}-icon ${PREFIX}-advbtn`);\n advancedBtn.setAttribute(\n \"aria-label\",\n opts.advancedLabel || \"Advanced view\"\n );\n advancedBtn.title = opts.advancedLabel || \"Advanced view\";\n advancedBtn.innerHTML = ICON_ADVANCED;\n hActions.appendChild(advancedBtn);\n }\n // Advanced view always opens edge-to-edge \u2014 no separate fullscreen toggle\n // button; the exit-advanced control (advancedBtn) is the way back out.\n const closeBtn = el(\"button\", `${PREFIX}-close`);\n closeBtn.innerHTML = \"&times;\";\n closeBtn.setAttribute(\"aria-label\", L(\"closeChat\"));\n hActions.appendChild(closeBtn);\n header.append(avatar, hName, hActions);\n\n const log = el(\"div\", `${PREFIX}-log`);\n // Accessible, scrollable conversation region. role=log + aria-live announces\n // streamed replies to screen readers; tabindex makes it keyboard-scrollable.\n log.setAttribute(\"role\", \"log\");\n log.setAttribute(\"aria-live\", \"polite\");\n log.setAttribute(\"aria-label\", L(\"conversation\"));\n log.setAttribute(\"tabindex\", \"0\");\n // Scroll state + scrollDown MUST be defined here, BEFORE the history restore\n // below: restoring a persisted assistant message runs addAssistantMessage \u2192\n // renderNavigate (for a `[[navigate]]` directive), which calls scrollDown.\n // Defining these later put them in the temporal dead zone during restore \u2014\n // \"Cannot access 'scrollDown' (minified: 'He') before initialization\" \u2014 which\n // crashed the widget on load for any thread whose history contained a nav.\n let pinned = true;\n let scrollQueued = false;\n const scrollDown = (force?: boolean): void => {\n if (force) pinned = true;\n if (!pinned || scrollQueued) return;\n scrollQueued = true;\n requestAnimationFrame(() => {\n scrollQueued = false;\n log.scrollTop = log.scrollHeight;\n });\n };\n // Rich-content lifecycle. Host-mounted React roots (markdown / data widgets)\n // hand back a disposer; we keep them so they can be unmounted when the log is\n // cleared (new chat / history switch / destroy), avoiding leaked roots.\n const richDisposers: Array<() => void> = [];\n // Custom directive renderers (the OSS extension point). Seeded from\n // opts.renderers; extendable at runtime via handle.registerRenderer.\n // Built-in tags stay ours so a plugin can't hijack action/form security.\n const RESERVED_TAGS = new Set([\n \"widget\",\n \"preview\",\n \"navigate\",\n \"action\",\n \"chips\",\n \"form\",\n ]);\n const customRenderers: Record<string, DirectiveRenderer> = {};\n const registerRenderer = (tag: string, renderer: DirectiveRenderer): void => {\n if (!/^[a-z][a-z0-9_-]*$/i.test(tag) || RESERVED_TAGS.has(tag)) {\n throw new Error(`invalid or reserved directive tag: ${tag}`);\n }\n customRenderers[tag] = renderer;\n };\n for (const [tag, renderer] of Object.entries(opts.renderers ?? {})) {\n registerRenderer(tag, renderer);\n }\n // Live agent-activity chips, keyed by tool callId so a `running` chip can be\n // updated to `ok`/`error` in place. Cleared whenever the log is replaced.\n const liveActivity = new Map<string, HTMLElement>();\n function clearRich(): void {\n liveActivity.clear();\n for (const dispose of richDisposers.splice(0)) {\n try {\n dispose();\n } catch {\n /* host cleanup best-effort */\n }\n }\n }\n // One process-step chip (spinner while running \u2192 check / cross when done).\n // `agent` is the AI role that ran the step (Vega/AYCA/\u2026) \u2014 shown as a small\n // badge so the user sees WHICH agent did WHAT.\n // A model id \u2192 a compact label for the flow chip (\"claude-sonnet-4-6\" \u2192\n // \"sonnet-4-6\"; \"managed\" stays). Keeps the badge short.\n function shortModel(model?: string): string {\n if (!model) return \"\";\n return model.replace(/^claude-/, \"\").replace(/-\\d{8}$/, \"\");\n }\n /** Chips draw their own \u2713/\u2715 glyph, but many host apply messages (and a few\n * labels) historically END in a textual \"\u2713\" too \u2014 rendered together they\n * read as a double checkmark (\"\u2713 Brand updated \u2026 \u2713\"). The glyph wins; the\n * textual tick is stripped wherever text sits next to a glyph. */\n function stripTick(s: string): string {\n return s.replace(/\\s*\u2713\\s*$/u, \"\");\n }\n function paintActivity(\n chip: HTMLElement,\n label: string,\n status: string,\n agent?: string,\n model?: string\n ): void {\n label = stripTick(label);\n const icon =\n status === \"running\"\n ? `<span class=\"${PREFIX}-act-spin\" aria-hidden=\"true\"></span>`\n : status === \"error\"\n ? `<span class=\"${PREFIX}-act-x\" aria-hidden=\"true\">\u2715</span>`\n : `<span class=\"${PREFIX}-act-ok\" aria-hidden=\"true\">\u2713</span>`;\n const badge = agent\n ? `<span class=\"${PREFIX}-act-agent\">${escapeHtml(agent)}</span>`\n : \"\";\n // The MODEL acting on this step \u2014 shown so the user sees model selection live.\n const m = shortModel(model);\n const modelBadge = m\n ? `<span class=\"${PREFIX}-act-model\">${escapeHtml(m)}</span>`\n : \"\";\n chip.className =\n `${PREFIX}-activity` +\n (status !== \"running\" ? ` ${PREFIX}-activity-done` : \"\");\n chip.innerHTML = `${icon}${badge}${modelBadge}<span class=\"${PREFIX}-act-label\">${escapeHtml(label)}</span>`;\n }\n function addActivityChip(\n label: string,\n status: string,\n agent?: string,\n model?: string,\n href?: string\n ): HTMLElement {\n const chip = el(\"div\", `${PREFIX}-activity`);\n paintActivity(chip, label, status, agent, model);\n // A produced REPORT gets a way in. Through the same dispatchAction +\n // isSafeRelPath guard the job card uses \u2014 the path came from a host\n // callback, but it still crosses into a navigation, and there is exactly\n // one gate for that in this file.\n if (isSafeRelPath(href)) {\n const path = href;\n const btn = el(\"button\", `${PREFIX}-act-open`) as HTMLButtonElement;\n btn.type = \"button\";\n btn.innerHTML = `<span>${escapeHtml(L(\"openReport\"))}</span> <span aria-hidden=\"true\">\u2192</span>`;\n btn.addEventListener(\"click\", async () => {\n btn.disabled = true;\n try {\n await dispatchAction(\"navigate\", { path });\n } catch {\n btn.disabled = false;\n }\n });\n chip.appendChild(btn);\n }\n log.appendChild(chip);\n return chip;\n }\n // Live frame: create the chip on `running`, update the same one on ok/error.\n function liveActivityFrame(\n callId: string,\n label: string,\n status: string,\n agent?: string,\n model?: string\n ): void {\n const existing = liveActivity.get(callId);\n if (existing) {\n paintActivity(existing, label, status, agent, model);\n return;\n }\n const chip = addActivityChip(label, status, agent, model);\n liveActivity.set(callId, chip);\n scrollDown();\n }\n /**\n * Sleep, but tied to the widget's lifetime: resolves `true` after `ms`, or\n * `false` the moment `destroy()` aborts. Every polling loop waits through this\n * so a torn-down widget leaves no pending timer behind and no loop that wakes\n * up to paint into DOM that is no longer on the page.\n */\n function waitAlive(ms: number): Promise<boolean> {\n return new Promise<boolean>((resolve) => {\n if (alive.signal.aborted) {\n resolve(false);\n return;\n }\n const timer = setTimeout(() => {\n alive.signal.removeEventListener(\"abort\", onAbort);\n resolve(true);\n }, ms);\n const onAbort = (): void => {\n clearTimeout(timer);\n resolve(false);\n };\n alive.signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n }\n // \u2500\u2500 Live background jobs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 // An apply that ENQUEUES (a whole-site import, a render) answers in a second\n // and then works for minutes. The chat used to say \"I'll notify you when it's\n // ready \u2713\" and then show nothing at all until the completion artifact appeared\n // on a reload \u2014 the one place the user was watching was the one place the work\n // was invisible. So the apply's job id is kept, a card is drawn in the log, and\n // the GENERIC job read is polled until the job is terminal.\n //\n // Nothing here knows what a report is. It renders a status, a\n // done-of-total, a label and a terminal outcome \u2014 which is all `JobSummary`\n // promises for a job of ANY type, including one this build has never seen.\n\n /** How often a live job is re-read. Slow enough not to be a poll storm, fast\n * enough that \"3 of 12\" visibly moves while a job runs. */\n const JOB_POLL_MS = 3000;\n /** Stop babysitting a job that never lands \u2014 the card says so rather than\n * spinning forever. */\n const JOB_POLL_MAX_MS = 30 * 60 * 1000;\n\n /**\n * WHICH SOURCE a piece of tracked work lives in.\n *\n * Everything below used to key on a bare id, which was correct while `job`\n * was the only source. It is not any more: a report is its own row in its own\n * table, so a report id and a job id are two namespaces, and one map keyed on\n * the raw string quietly assumes they cannot collide. They are both UUIDs, so\n * a collision is vanishingly unlikely and would be almost impossible to\n * diagnose \u2014 the cheap fix is to stop assuming.\n */\n type WorkKind = \"job\" | \"report\";\n interface WorkRef {\n kind: WorkKind;\n id: string;\n }\n /** The map key for one piece of tracked work. The ONLY place the two\n * namespaces are joined, so they can never be conflated by accident. */\n const refKey = (ref: WorkRef): string => `${ref.kind}:${ref.id}`;\n /** A view carries its generic `type`; that is what tells re-attach which\n * source a listed row came from. */\n const refOf = (view: { id?: string; type?: string }): WorkRef | null =>\n view.id\n ? { kind: view.type === \"report\" ? \"report\" : \"job\", id: view.id }\n : null;\n\n /** One piece of work this conversation is watching, as remembered across a\n * refresh. */\n interface TrackedJob {\n jobId: string;\n /** Which source `jobId` belongs to. OPTIONAL on read: entries written\n * before reports left the job model have no `kind`, and they are all\n * generic jobs \u2014 so a missing value reads as \"job\" rather than dropping a\n * card someone is watching. */\n kind?: WorkKind;\n /** The thread the job was started FROM \u2014 a job only re-attaches to its own\n * conversation, never to whichever thread happens to be open. */\n threadId: string;\n /** When we started watching (ms). Old entries are pruned, so a job whose\n * completion we never saw cannot haunt the chat forever. */\n at: number;\n }\n /** Tracked jobs outlive the page, so they live beside the conversation in\n * localStorage (same opt-in: no persistKey \u2192 in-memory for this session only,\n * which is exactly today's behaviour minus the refresh). */\n const jobsKey = opts.persistKey ? `${ns}:jobs:${opts.persistKey}` : null;\n const TRACKED_JOB_TTL_MS = 24 * 60 * 60 * 1000;\n /** The card currently drawn for a job id. Re-pointed (not re-created) whenever\n * the transcript is re-rendered, so a poll always paints the LIVE node. */\n const jobCards = new Map<string, HTMLElement>();\n /** Job ids with a poll loop already running \u2014 a re-attach must never start a\n * second one. */\n const jobPolling = new Set<string>();\n /**\n * Everything ELSE that wants to know how a job is going.\n *\n * The job card was the only watcher for a long time, so the poll painted it\n * directly. It is not any more: a `[[ui:\u2026]]` tile can name a jobId and has to\n * repaint itself as that render finishes. Two pollers for one job would be\n * two answers to the same question, drifting apart and costing double, so\n * there is ONE loop per job id and it fans out to whoever is listening.\n *\n * Each subscriber carries the node it paints, because the transcript is wiped\n * and redrawn on every turn (`reattachTrackedJobs`): the node a subscriber\n * closed over is detached moments later, and a subscriber list that never\n * forgot them would grow without bound and paint into nothing.\n */\n const jobWatchers = new Map<\n string,\n Set<{\n node: HTMLElement;\n paint: (view: WidgetJobView) => void;\n /** Has this node ever actually been in the document? A tile subscribes\n * while it is still being BUILT \u2014 the card it belongs to is appended a\n * few statements later \u2014 so \"not connected\" only means \"gone\" once it\n * has been seen connected at least once. Pruning on the first poll\n * instead would silently unsubscribe every tile whose job answered\n * faster than the card was assembled. */\n seen: boolean;\n }>\n >();\n\n /**\n * Hand one poll result to everyone watching that job: the card, and any tile\n * that named it. Subscribers whose node has left the document are dropped\n * here rather than on a timer \u2014 a detached node is the definition of a\n * subscriber with nothing left to paint.\n */\n function emitJob(ref: WorkRef, view: WidgetJobView): void {\n const key = refKey(ref);\n const card = jobCards.get(key);\n if (card) paintJob(card, view);\n const subs = jobWatchers.get(key);\n if (!subs) return;\n for (const sub of subs) {\n if (!sub.node.isConnected) {\n // Detached AFTER having been on screen \u2014 the transcript that held it\n // was wiped, so there is nothing left to paint.\n if (sub.seen) subs.delete(sub);\n continue;\n }\n sub.seen = true;\n try {\n sub.paint(view);\n } catch {\n // One bad subscriber must not stop the others (or the poll) \u2014 a tile\n // that throws is a drawing bug, not a reason to stall the job.\n }\n }\n if (!subs.size) jobWatchers.delete(key);\n }\n\n /**\n * Poll one job to a terminal state, fanning every result out through\n * `emitJob`. Idempotent per id: a second call while a loop is running simply\n * returns, which is what lets a tile and a card share one poll.\n *\n * Stops on: a terminal status, the safety ceiling, or `destroy()` (every wait\n * goes through `waitAlive`, so an aborted widget never wakes up again).\n */\n function watchJob(ref: WorkRef): void {\n // THE ONLY DISPATCH. A report reads from its own endpoint because it no\n // longer has a generic row; everything after this line is source-agnostic,\n // which is what keeps one loop serving both.\n const read = ref.kind === \"report\" ? opts.getReport : opts.getJob;\n if (!read || !ref.id) return;\n const key = refKey(ref);\n if (jobPolling.has(key)) return;\n jobPolling.add(key);\n void (async () => {\n const started = Date.now();\n try {\n for (;;) {\n if (Date.now() - started >= JOB_POLL_MAX_MS) {\n // Give up WATCHING, not on the job: it is still running server-side,\n // and saying so is more honest than a spinner that never resolves.\n const stalled = jobCards.get(key);\n if (stalled) {\n stalled.className = `${PREFIX}-job ${PREFIX}-job-done`;\n stalled.textContent = L(\"jobUnreachable\");\n }\n forgetJob(ref);\n return;\n }\n let view: WidgetJobView | null;\n try {\n view = await read(ref.id);\n } catch {\n // A transient read failure is not a failed job \u2014 keep the spinner\n // and try again on the next tick.\n if (!(await waitAlive(JOB_POLL_MS))) return;\n continue;\n }\n // A job the account can no longer read (deleted, or never existed) is\n // not something to keep asking about.\n if (!view) {\n jobCards.get(key)?.remove();\n jobCards.delete(key);\n jobWatchers.delete(key);\n forgetJob(ref);\n return;\n }\n emitJob(ref, view);\n if (\n view.status === \"done\" ||\n view.status === \"failed\" ||\n view.status === \"cancelled\"\n ) {\n forgetJob(ref);\n jobWatchers.delete(key);\n scrollDown();\n return;\n }\n if (!(await waitAlive(JOB_POLL_MS))) return;\n }\n } finally {\n jobPolling.delete(key);\n }\n })();\n }\n\n /**\n * Subscribe a node to a job's progress and make sure the job is being polled.\n *\n * The node is what a tile passes so it can be dropped once the transcript\n * that held it is gone.\n */\n function subscribeJob(\n ref: WorkRef,\n node: HTMLElement,\n paint: (view: WidgetJobView) => void\n ): void {\n const read = ref.kind === \"report\" ? opts.getReport : opts.getJob;\n if (!read || !ref.id) return;\n const key = refKey(ref);\n let subs = jobWatchers.get(key);\n if (!subs) {\n subs = new Set();\n jobWatchers.set(key, subs);\n }\n subs.add({ node, paint, seen: false });\n watchJob(ref);\n }\n\n function loadTrackedJobs(): TrackedJob[] {\n const parsed = readJson<unknown[]>(jobsKey, (v): v is unknown[] =>\n Array.isArray(v)\n );\n if (!parsed) return [];\n const cutoff = Date.now() - TRACKED_JOB_TTL_MS;\n // NOTE the absent `kind` check: an entry written by the previous build has\n // none, and rejecting it would drop the card for work that is still running\n // across the deploy. `trackedRef` below defaults it.\n return parsed.filter(\n (j): j is TrackedJob =>\n Boolean(j) &&\n typeof j === \"object\" &&\n typeof (j as TrackedJob).jobId === \"string\" &&\n typeof (j as TrackedJob).threadId === \"string\" &&\n typeof (j as TrackedJob).at === \"number\" &&\n (j as TrackedJob).at > cutoff\n );\n }\n function saveTrackedJobs(jobs: TrackedJob[]): void {\n writeJson(jobsKey, jobs.length ? jobs : null);\n }\n /** The ref a stored entry refers to \u2014 `kind` defaults to \"job\" for entries\n * written before reports had their own source. */\n function trackedRef(j: TrackedJob): WorkRef {\n return { kind: j.kind ?? \"job\", id: j.jobId };\n }\n function rememberJob(ref: WorkRef, tid: string): void {\n const jobs = loadTrackedJobs().filter(\n (j) => refKey(trackedRef(j)) !== refKey(ref)\n );\n jobs.push({ jobId: ref.id, kind: ref.kind, threadId: tid, at: Date.now() });\n saveTrackedJobs(jobs);\n }\n /** A finished job is not a live job: forgetting it here is what stops the card\n * from being redrawn on the next refresh (the transcript's own completion\n * artifact is the lasting record). */\n function forgetJob(ref: WorkRef): void {\n saveTrackedJobs(\n loadTrackedJobs().filter((j) => refKey(trackedRef(j)) !== refKey(ref))\n );\n }\n\n /** The card's title: the host's own wording, else the copy for a type we know,\n * else a neutral one \u2014 an unknown type must still read as something. */\n function jobTitle(view: WidgetJobView): string {\n if (view.title) return view.title;\n if (view.type === \"coder\") return L(\"jobTitleCoder\");\n if (view.type === \"report\") return L(\"jobTitleReport\");\n return L(\"jobTitleFallback\");\n }\n\n /** \"3 of 12\" / \"3 so far\" / nothing at all \u2014 `total` is null until the runner\n * has planned its work, and \"3 of 0\" would be a lie. */\n function jobCounts(view: WidgetJobView): string {\n const done = view.progress?.done ?? 0;\n const total = view.progress?.total ?? null;\n if (total && total > 0) return L(\"jobProgress\", { done, total });\n return done > 0 ? L(\"jobProgressOpen\", { done }) : \"\";\n }\n\n /** (Re)paint a job card from the latest read. Same element throughout the\n * job's life, so the card the user is looking at is the one that updates. */\n function paintJob(card: HTMLElement, view: WidgetJobView): void {\n const terminal =\n view.status === \"done\" ||\n view.status === \"failed\" ||\n view.status === \"cancelled\";\n const failed = view.status === \"failed\";\n card.className =\n `${PREFIX}-job` +\n (terminal ? ` ${PREFIX}-job-done` : \"\") +\n (failed ? ` ${PREFIX}-job-failed` : \"\");\n const icon = failed\n ? `<span class=\"${PREFIX}-act-x\" aria-hidden=\"true\">\u2715</span>`\n : view.status === \"done\"\n ? `<span class=\"${PREFIX}-act-ok\" aria-hidden=\"true\">\u2713</span>`\n : view.status === \"cancelled\"\n ? `<span class=\"${PREFIX}-act-x\" aria-hidden=\"true\">\u2013</span>`\n : `<span class=\"${PREFIX}-act-spin\" aria-hidden=\"true\"></span>`;\n const state = failed\n ? L(\"jobFailed\")\n : view.status === \"done\"\n ? L(\"jobDone\")\n : view.status === \"cancelled\"\n ? L(\"jobCancelled\")\n : view.status === \"queued\"\n ? L(\"jobQueued\")\n : L(\"jobRunning\");\n const counts = jobCounts(view);\n // The progress LABEL is server text (for a report, the section being\n // built) \u2014 escaped like every other untrusted string the widget draws.\n const detail = failed ? (view.error ?? \"\") : (view.progress?.label ?? \"\");\n const total = view.progress?.total ?? 0;\n const done = view.progress?.done ?? 0;\n // A bar only where there is a real ratio to show; otherwise the counts line\n // carries the progress on its own.\n const pct =\n total > 0\n ? Math.max(0, Math.min(100, Math.round((done / total) * 100)))\n : 0;\n // The FLOW: the last few narration lines while running (the live feel),\n // the whole story behind a toggle once it is long. All server text \u2014\n // escaped per line.\n //\n // A step is shown in the READER'S language when the runner tagged it with a\n // code we have a label for, and in the runner's own English otherwise. That\n // fallback is the point: a new step can be emitted by the server without a\n // widget release, and it degrades to an English sentence rather than to a\n // blank line or a raw key.\n // FOLLOW IT INTO THE PANE, and stop narrating twice.\n //\n // Owner's decision (S4): once the app pane is showing the same run, the\n // chat column collapses to ONE line. The report page owns the detail \u2014 it\n // has the stage rail and it is the authority on stage \u2014 and two renderings\n // of one job competing on one screen is the divergence advanced view makes\n // absurdly visible. When the pane is NOT showing it, the chat narrates in\n // full, because then it is the only account of what is happening.\n //\n // `livePath` rather than `resultPath`: this has to work while the thing is\n // RUNNING, which is the whole point of a page you watch build.\n // Keyed through `refOf`/`refKey`, the same way `jobCards` and\n // `userConfirmedWork` are: `type` is free-form copy (\"import\", \"render\"),\n // so composing the key by hand here gave the follow decision a key that\n // could not match the one the confirmation was recorded under.\n const followRef = !terminal && view.livePath ? refOf(view) : null;\n if (followRef && view.livePath) {\n autoNavigateFrame(refKey(followRef), view.livePath);\n }\n const mirrored = frameShowing(view.livePath);\n const events = mirrored ? [] : (view.events ?? []);\n const eventText = (e: {\n message: string;\n data?: Record<string, unknown> | null;\n }): string => {\n const code = e.data?.code;\n if (typeof code !== \"string\" || !code) return e.message;\n const key = `jobEv${code.charAt(0).toUpperCase()}${code.slice(1)}`;\n // Membership is checked against the label BAG rather than by calling `L`\n // and testing its answer: `L` indexes a record typed as total, so an\n // unknown key yields `undefined` rather than anything recognisable, and a\n // step we have no wording for must fall through to the server's sentence.\n if (!(key in WIDGET_LABELS)) return e.message;\n return L(key as keyof WidgetLabels) || e.message;\n };\n const tailStart = terminal\n ? Math.max(0, events.length - 3)\n : Math.max(0, events.length - 5);\n const flowLine = (e: {\n kind?: string;\n message: string;\n data?: Record<string, unknown> | null;\n }) =>\n `<li class=\"${PREFIX}-job-ev${\n e.kind === \"problem\"\n ? ` ${PREFIX}-job-ev-problem`\n : e.kind === \"decision\"\n ? ` ${PREFIX}-job-ev-decision`\n : \"\"\n }\">${escapeHtml(eventText(e))}</li>`;\n const flow = events.length\n ? `<ul class=\"${PREFIX}-job-flow\">${events\n .slice(tailStart)\n .map(flowLine)\n .join(\"\")}</ul>` +\n (tailStart > 0\n ? `<details class=\"${PREFIX}-job-flow-all\"><summary>${escapeHtml(\n L(\"jobFlowAll\", { count: events.length })\n )}</summary><ul class=\"${PREFIX}-job-flow\">${events\n .map(flowLine)\n .join(\"\")}</ul></details>`\n : \"\")\n : \"\";\n card.innerHTML =\n `<div class=\"${PREFIX}-job-head\">${icon}` +\n `<span class=\"${PREFIX}-job-title\">${escapeHtml(jobTitle(view))}</span>` +\n `<span class=\"${PREFIX}-job-state\">${escapeHtml(state)}</span></div>` +\n (counts\n ? `<div class=\"${PREFIX}-job-counts\">${escapeHtml(counts)}</div>`\n : \"\") +\n (total > 0 && !terminal\n ? `<div class=\"${PREFIX}-job-bar\"><i style=\"transform:scaleX(${pct / 100})\"></i></div>`\n : \"\") +\n (detail\n ? `<div class=\"${PREFIX}-job-detail\">${escapeHtml(detail)}</div>`\n : \"\") +\n // The one line that replaces the narration when the pane is showing the\n // same run. Not silence: the card still has to say WHY it went quiet, or\n // it reads as the feed having stopped.\n (mirrored\n ? `<div class=\"${PREFIX}-job-mirrored\">${escapeHtml(L(\"jobWatchingInPane\"))}</div>`\n : \"\") +\n flow;\n // Finished WITH something to look at \u2014 the notification's \"here's the\n // folder\" without leaving the conversation.\n if (view.status === \"done\" && isSafeRelPath(view.resultPath)) {\n const path = view.resultPath;\n const btn = el(\"button\", `${PREFIX}-nav-btn`) as HTMLButtonElement;\n btn.type = \"button\";\n btn.innerHTML = `<span>${escapeHtml(L(\"jobOpenResult\"))}</span> <span aria-hidden=\"true\">\u2192</span>`;\n btn.addEventListener(\"click\", async () => {\n btn.disabled = true;\n try {\n await dispatchAction(\"navigate\", { path });\n } catch {\n btn.disabled = false;\n }\n });\n card.appendChild(btn);\n }\n // Still in flight and the host can stop it \u2192 Cancel. The server cancel is\n // cooperative, so the button just asks: the card keeps polling and flips\n // to \"Cancelled\" when the runner actually stops at its next boundary.\n // The cancel goes to the route that OWNS the work: a report is cancelled\n // on its own row, and after the contract migration there is no generic job\n // to cancel it through.\n const cancelFor =\n view.type === \"report\" ? opts.cancelReport : opts.cancelJob;\n if (!terminal && view.id && cancelFor) {\n const jobId = view.id;\n const cancel = cancelFor;\n const btn = el(\"button\", `${PREFIX}-nav-btn`) as HTMLButtonElement;\n btn.type = \"button\";\n btn.innerHTML = `<span>${escapeHtml(L(\"jobCancel\"))}</span>`;\n btn.addEventListener(\"click\", async () => {\n btn.disabled = true;\n btn.textContent = L(\"jobCancelling\");\n try {\n await cancel(jobId);\n } catch {\n // Couldn't reach the server \u2014 the button is usable again.\n btn.disabled = false;\n btn.innerHTML = `<span>${escapeHtml(L(\"jobCancel\"))}</span>`;\n }\n });\n card.appendChild(btn);\n }\n }\n\n /** A placeholder card for a job we have not read yet (the moment after Apply,\n * and the moment after a refresh) \u2014 the chat is never blank about it. */\n function pendingJobView(): WidgetJobView {\n return { status: \"queued\" };\n }\n\n /**\n * Give one job a CARD in the log, and make sure it is being watched.\n *\n * The polling itself lives in `watchJob` \u2014 this is the card half, kept apart\n * because a tile wants the second half without the first.\n */\n function trackJob(ref: WorkRef, tid: string | undefined): void {\n const read = ref.kind === \"report\" ? opts.getReport : opts.getJob;\n if (!read || !ref.id) return;\n if (tid) rememberJob(ref, tid);\n // The poll is started BEFORE the card guard below, deliberately: that guard\n // protects the CARD from being drawn twice, and a job whose card is already\n // up still needs its loop running (and its tiles fed) after a re-attach.\n watchJob(ref);\n // Idempotent per id, and it has to be: `reattachTrackedJobs` calls this\n // TWICE for the same job (once from browser storage, once when the server\n // listing resolves). The dedupe used to sit below the append, so the second\n // call drew a second card and overwrote the map entry \u2014 leaving the first\n // one orphaned in the log, repainted by nobody and frozen on \"Queued\"\n // forever. Bail before touching the DOM when the card is already on screen;\n // a stale entry left detached by a transcript wipe is rebuilt instead.\n const existing = jobCards.get(refKey(ref));\n if (existing?.isConnected) return;\n const card = el(\"div\", `${PREFIX}-job`);\n paintJob(card, pendingJobView());\n log.appendChild(card);\n jobCards.set(refKey(ref), card);\n scrollDown(true);\n }\n\n /**\n * Work THIS session's user started by pressing a button, keyed like `jobCards`.\n *\n * The only thing that separates \"the report you just asked me to write\" from\n * \"an import some other tab left running\" \u2014 and the difference matters because\n * the first earns the app pane and the second must never take it. `trackJob`\n * cannot tell them apart on its own: it is called with a threadId from the\n * apply below AND from the server-listing re-attach, so the presence of a\n * thread proves nothing about who asked. The fact is recorded where the\n * confirmation actually happens instead.\n */\n const userConfirmedWork = new Set<string>();\n function trackConfirmedJob(ref: WorkRef): void {\n userConfirmedWork.add(refKey(ref));\n trackJob(ref, threadId);\n }\n\n /**\n * Re-attach the live cards for jobs started FROM this thread.\n *\n * This is what makes a running import survive a refresh \u2014 and also what\n * restores the card after any transcript re-render (the end-of-turn reload and\n * the completion broadcast both wipe the log). Cards are always rebuilt from\n * the tracked list rather than moved, so there is one path, not two.\n */\n function reattachTrackedJobs(tid: string | undefined): void {\n if (!(opts.getJob || opts.getReport) || !tid) return;\n jobCards.clear();\n // Browser storage FIRST so the card is back on the same tick as the\n // transcript \u2014 a job this device started is already known here, and waiting\n // on a round-trip would flash an empty conversation.\n for (const j of loadTrackedJobs()) {\n if (j.threadId === tid) trackJob(trackedRef(j), undefined);\n }\n // Then ask the SERVER what this thread actually has running. Storage only\n // knows what this browser started, so a job begun on a phone, or before\n // the cache was cleared, was invisible on every other device \u2014 the card\n // said nothing was happening while it ran. `trackJob` is idempotent\n // per id, so a job both sources know about is drawn once.\n const listRunning = opts.listThreadJobs;\n if (!listRunning) return;\n void listRunning(tid)\n .then((running) => {\n // The thread may have been switched while the request was in flight.\n if (threadId !== tid) return;\n // The listing is MULTI-SOURCE: it carries reports as well as generic\n // jobs, and each row's `type` is what says which. Deriving the ref\n // here \u2014 rather than assuming \"job\" \u2014 is what makes a report started\n // on another device re-attach to the right poller.\n for (const j of running) {\n const ref = refOf(j);\n if (ref) trackJob(ref, tid);\n }\n })\n .catch(() => {\n /* offline or unauthorised \u2014 storage already covered this device */\n });\n }\n\n // Turn a FINAL assistant bubble's text into rich content: the host's real\n // <Markdown> when wired, else the built-in safe markdown\u2192HTML, else plain.\n function applyAssistantRich(\n bubble: HTMLElement,\n text: string,\n animate?: boolean\n ): void {\n // Cool swap: when a streamed reply finishes, the raw markdown text is\n // replaced by the rendered markdown \u2014 fade it in so it doesn't pop.\n if (animate) {\n bubble.classList.remove(`${PREFIX}-rich-in`);\n // reflow so re-adding the class restarts the animation\n void bubble.offsetWidth;\n bubble.classList.add(`${PREFIX}-rich-in`);\n }\n if (opts.renderMarkdown) {\n bubble.textContent = \"\";\n const dispose = opts.renderMarkdown(bubble, text);\n if (dispose) richDisposers.push(dispose);\n } else {\n bubble.innerHTML = renderMarkdown(text);\n }\n }\n // Append a complete assistant message rendered as markdown (history restore,\n // greeting, \"no response\"). The streaming path stays plain until finalize.\n function addAssistantMessage(text: string): HTMLElement {\n // Replay: render clean prose (directives the live turn already handled are\n // stripped) + an inert note per directive \u2014 never raw [[\u2026]] code.\n const { clean, notes, navs, uis } = stripDirectivesForReplay(text);\n const bubble = addMsg(log, \"assistant\", \"\");\n applyAssistantRich(bubble, clean);\n for (const n of notes) {\n const note = el(\"div\", `${PREFIX}-replay-note`);\n note.textContent = n;\n log.appendChild(note);\n }\n // Composed cards are re-drawn in full \u2014 see the note in\n // stripDirectivesForReplay for why they are not flattened to a note.\n for (const u of uis) renderUiCard(u);\n // Re-render navigation as a REAL chip so \"Open <page>\" stays clickable after\n // a reload/restore (#111) \u2014 in replay mode so it never auto-navigates on open.\n if (opts.onWidgetAction)\n for (const nav of navs)\n if (isSafeRelPath(nav.path)) renderNavigate(nav, true);\n return bubble;\n }\n // Re-render a full thread (messages + inline DATA WIDGETS) into the log,\n // replacing whatever is shown. Shared by history-reopen + refresh-restore.\n // `tid` is the thread being rendered, which is NOT always the active one yet:\n // reopening from history renders first and adopts the thread after. The live\n // job cards belong to a THREAD, so they have to be re-attached for the one on\n // screen, not the one that was open a moment ago.\n /** The \u2191in / \u2193out tokens caption under an assistant reply \u2014 one builder for\n * the live stream and the transcript replay so the two can never drift. */\n function usageBadge(inTok: number, outTok: number): HTMLElement {\n const cap = el(\"div\", `${PREFIX}-usage`);\n cap.innerHTML =\n `<span class=\"${PREFIX}-usage-pill\">\u2191 ${inTok.toLocaleString()}</span>` +\n `<span class=\"${PREFIX}-usage-pill\">\u2193 ${outTok.toLocaleString()}</span>` +\n `<span class=\"${PREFIX}-usage-sep\">\u00B7</span>` +\n `<span>${(inTok + outTok).toLocaleString()} tokens</span>`;\n return cap;\n }\n\n function renderThreadItems(\n items: LoadedThreadItem[],\n tid: string | undefined = threadId\n ): void {\n // A `question` card is NOT persisted server-side (unlike a data widget),\n // and this re-render runs at the END of every turn \u2014 which is\n // exactly when `ask_user` has just drawn one. Rebuilding purely from the\n // transcript would erase the decision the assistant is waiting on: the card\n // flashed in and vanished a moment later, so ask_user was dead on the\n // floating widget. Carry the UNANSWERED cards across the wipe (they keep\n // their listeners while detached, so they stay clickable). ANSWERED ones are\n // dropped on purpose \u2014 the answer is a real user message in the reloaded\n // transcript by now, and keeping the card would print the same decision\n // twice. Only for the thread already on screen: switching conversations must\n // not drag another one's question along. Same semantics as the React panel.\n const carriedQuestions =\n tid === threadId\n ? Array.from(\n log.querySelectorAll<HTMLElement>(\n `.${PREFIX}-question:not(.${PREFIX}-question-done)`\n )\n )\n : [];\n clearRich();\n log.innerHTML = \"\";\n history.length = 0;\n for (const it of items) {\n if (\"kind\" in it && it.kind === \"widget\") {\n renderServerWidget(\n it.spec as { title?: string; chartType?: string } | undefined,\n it.rows,\n it.comparisonRows\n );\n } else if (\"kind\" in it && it.kind === \"activity\") {\n // Replay a persisted process step (static, already finished) + its agent.\n addActivityChip(\n it.label,\n it.status,\n it.agent,\n it.model,\n it.reportId ? opts.reportHref?.(it.reportId) : undefined\n );\n } else if (\"role\" in it) {\n // The message's first DOM node \u2014 the anchor an edit/regenerate rewrites\n // the transcript FROM (bubble, or attachment row before it).\n const before = log.lastChild;\n if (it.role === \"assistant\") addAssistantMessage(it.content);\n else addMsg(log, it.role, it.content);\n // Persisted per-turn token caption \u2014 the live stream draws this under\n // the reply, but this reload runs right after the turn and used to\n // wipe it. Rendered from the stored counts so it also survives reopen\n // (and shows on the free staff lane, where it's insight, not billing).\n if (it.role === \"assistant\" && (it.inputTokens || it.outputTokens)) {\n log.appendChild(\n usageBadge(it.inputTokens ?? 0, it.outputTokens ?? 0)\n );\n }\n history.push({ role: it.role, content: it.content });\n // Branch controls (edit / regenerate / \u2039n/m\u203A) on branching hosts, and/or\n // the per-message vote when a vote endpoint is wired.\n if (opts.setActiveLeaf || opts.vote) {\n const anchor = before ? before.nextSibling : log.firstChild;\n addMessageActions(it, anchor);\n }\n }\n }\n // The transcript we just drew is the finished record; anything this thread\n // started that is STILL running goes back underneath it, live \u2014 and so does\n // the question the assistant is still blocked on.\n for (const q of carriedQuestions) log.appendChild(q);\n reattachTrackedJobs(tid);\n scrollDown(true);\n }\n\n /**\n * \u2039n/m\u203A sibling-branch switcher (ChatGPT/Claude-style, the arrows jump to the\n * deepest leaf of the previous/next sibling branch) and the up/down vote pair.\n *\n * Both bodies live in `./message-chrome` (`#320`) \u2014 they are pure DOM builders\n * that read no widget state. `switchBranch` is declared below and hoists.\n *\n * `getThreadId` is a GETTER on purpose: a vote is cast on CLICK, long after\n * the button is built, and `threadId` is reassigned when the server names the\n * thread. Passing the value here would post the vote against whatever the id\n * was at render time \u2014 silently, since the request still succeeds.\n */\n const { buildBranchNav, buildVoteButtons } = createMessageChrome({\n switchBranch: (leaf) => switchBranch(leaf),\n getThreadId: () => threadId,\n ...(opts.vote ? { vote: opts.vote } : {}),\n ...(opts.voteUpLabel ? { voteUpLabel: opts.voteUpLabel } : {}),\n ...(opts.voteDownLabel ? { voteDownLabel: opts.voteDownLabel } : {}),\n });\n\n /** Remove `anchor` and every DOM node after it from the log \u2014 the \"rewrite from\n * here onward\" a fork (edit / regenerate) does before streaming its branch. */\n function removeFrom(anchor: ChildNode | null): void {\n let node: ChildNode | null = anchor;\n while (node) {\n const next = node.nextSibling;\n node.remove();\n node = next;\n }\n }\n\n // Swap a replayed user bubble for an inline editor (prefilled text + Save /\n // Cancel). Save forks a new branch off this turn's parent and re-runs from\n // there; Cancel restores the original message. `nodes` are the message's DOM\n // rows (hidden while editing), `anchor` its first node (the rewrite point).\n function beginEdit(\n item: ReplayMessageItem,\n anchor: ChildNode | null,\n nodes: HTMLElement[]\n ): void {\n if (busy) return;\n const editor = el(\"div\", `${PREFIX}-edit ${PREFIX}-user`);\n const ta = el(\"textarea\", `${PREFIX}-edit-input`) as HTMLTextAreaElement;\n ta.value = item.content;\n ta.rows = Math.min(8, Math.max(2, item.content.split(\"\\n\").length));\n const row = el(\"div\", `${PREFIX}-edit-actions`);\n const cancel = el(\"button\", `${PREFIX}-edit-cancel`) as HTMLButtonElement;\n cancel.type = \"button\";\n cancel.textContent = opts.cancelLabel ?? \"Cancel\";\n const save = el(\"button\", `${PREFIX}-edit-save`) as HTMLButtonElement;\n save.type = \"button\";\n save.textContent = opts.saveLabel ?? \"Save & send\";\n row.appendChild(cancel);\n row.appendChild(save);\n editor.appendChild(ta);\n editor.appendChild(row);\n log.insertBefore(editor, anchor);\n for (const n of nodes) n.style.display = \"none\";\n ta.focus();\n const restore = (): void => {\n editor.remove();\n for (const n of nodes) n.style.display = \"\";\n };\n const submit = (): void => {\n const text = ta.value.trim();\n if (!text || busy) return;\n editor.remove();\n // Drop the old (now off-branch) DOM, then stream the edited turn; the\n // reload in `send` brings back the canonical ids + \u2039n/m\u203A switchers.\n removeFrom(anchor);\n void send(text, { parentId: item.parentId ?? null });\n };\n cancel.addEventListener(\"click\", restore);\n save.addEventListener(\"click\", submit);\n ta.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Escape\") restore();\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n submit();\n }\n });\n }\n\n /** Thumbs up/down for one assistant message (mirrors the panel's VoteButtons).\n * Clicking the active vote clears it; the chosen vote stays lit. Optimistic \u2014\n * reverts on failure. Wrapped so both buttons share the toggle state. */\n /**\n * The widget ASKING for a quality signal (#299).\n *\n * Everything else about this signal is passive, and passive on surfaces where\n * the control was invisible or absent \u2014 which is most of why the vote table is\n * near-empty. This is the one place the widget raises the question itself.\n *\n * TWO TRIGGERS, TWO QUESTIONS, on purpose. Prompting a slow-but-excellent\n * answer with \"was this helpful?\" teaches us \"slow = bad\", which we already\n * knew. So a long wait asks about the WAIT and offers thumbs; a failed turn\n * asks about the GOAL and takes words, because \"what were you trying to do\"\n * is the thing a failure destroys and no thumb can carry.\n *\n * It needs a messageId \u2014 a vote is a row against a message. A turn that\n * persisted nothing has none, and gets no prompt: the error card's existing\n * Report is the path there, and stacking a second ask on a failure is exactly\n * the noise this is supposed to avoid.\n */\n function renderQualityPrompt(\n variant: \"slow\" | \"failed\",\n messageId: string,\n model?: string\n ): void {\n if (!opts.vote) return;\n const card = el(\"div\", `${PREFIX}-quality`);\n const title = el(\"div\", `${PREFIX}-quality-title`);\n title.textContent = L(\n variant === \"slow\" ? \"qualitySlowTitle\" : \"qualityFailedTitle\"\n );\n card.appendChild(title);\n\n // A slow turn asks for a verdict; a failed one asks what was wanted, and\n // the words ARE the answer, so no thumbs on that variant.\n let picked: 1 | -1 | null = variant === \"failed\" ? -1 : null;\n const row = el(\"div\", `${PREFIX}-quality-row`);\n if (variant === \"slow\") {\n const mk = (\n v: 1 | -1,\n label: string,\n icon: string\n ): HTMLButtonElement => {\n const b = el(\n \"button\",\n `${PREFIX}-msgact ${PREFIX}-vote`\n ) as HTMLButtonElement;\n b.type = \"button\";\n b.setAttribute(\"aria-label\", label);\n b.title = label;\n b.innerHTML = icon;\n b.addEventListener(\"click\", () => {\n picked = v;\n row\n .querySelectorAll(`.${PREFIX}-vote`)\n .forEach((n) => n.classList.remove(`${PREFIX}-vote-on`));\n b.classList.add(`${PREFIX}-vote-on`);\n });\n return b;\n };\n row.appendChild(mk(1, opts.voteUpLabel ?? \"Helpful\", ICON_THUMB_UP));\n row.appendChild(\n mk(-1, opts.voteDownLabel ?? \"Not helpful\", ICON_THUMB_DOWN)\n );\n }\n const reason = el(\"input\", `${PREFIX}-quality-reason`) as HTMLInputElement;\n reason.type = \"text\";\n reason.maxLength = 500;\n reason.placeholder = L(\n variant === \"slow\"\n ? \"qualityReasonPlaceholder\"\n : \"qualityFailedPlaceholder\"\n );\n reason.setAttribute(\"aria-label\", title.textContent);\n row.appendChild(reason);\n card.appendChild(row);\n\n const actions = el(\"div\", `${PREFIX}-quality-actions`);\n const send = el(\"button\", `${PREFIX}-quality-send`) as HTMLButtonElement;\n send.type = \"button\";\n send.textContent = L(\"qualitySend\");\n const dismiss = el(\n \"button\",\n `${PREFIX}-quality-dismiss`\n ) as HTMLButtonElement;\n dismiss.type = \"button\";\n dismiss.textContent = L(\"qualityDismiss\");\n dismiss.addEventListener(\"click\", () => card.remove());\n send.addEventListener(\"click\", () => {\n const text = reason.value.trim();\n // Nothing said and nothing picked is a dismissal, not a datapoint.\n if (picked === null && !text) {\n card.remove();\n return;\n }\n send.disabled = true;\n void opts.vote!({\n threadId: threadId ?? \"\",\n messageId,\n value: picked ?? -1,\n ...(model ? { model } : {}),\n ...(text ? { reason: text } : {}),\n })\n .then(() => {\n card.textContent = L(\"qualityThanks\");\n card.classList.add(`${PREFIX}-quality-done`);\n })\n .catch(() => {\n send.disabled = false;\n });\n });\n actions.append(send, dismiss);\n card.appendChild(actions);\n log.appendChild(card);\n scrollDown();\n }\n\n // Threads already asked for a quality signal THIS SESSION. At most once per\n // thread per session (#299): a widget that asks after every slow turn is a\n // widget people learn to dismiss without reading.\n const qualityAsked = new Set<string>();\n /** Turns completed per thread this session \u2014 the \"never on the first turn\"\n * half of the rule above. */\n const threadTurns = new Map<string, number>();\n\n // Hover action row under a replayed message: EDIT (user) / REGENERATE + VOTE\n // (assistant) + the \u2039n/m\u203A switcher. Rendered on hosts that wired `setActiveLeaf`\n // and/or `vote`; appended right after the message's bubble.\n function addMessageActions(\n item: ReplayMessageItem,\n anchor: ChildNode | null\n ): void {\n if (!opts.setActiveLeaf && !opts.vote) return;\n const isUser = item.role === \"user\";\n // A regenerate needs the user turn it answered (an assistant's parentId).\n const canRegen =\n !!opts.setActiveLeaf && !isUser && typeof item.parentId === \"string\";\n // A vote needs a stable message id + a wired endpoint (assistant turns only).\n const canVote = !isUser && !!opts.vote && typeof item.id === \"string\";\n if (!item.branch && !isUser && !canRegen && !canVote) return;\n const rowEl = el(\"div\", `${PREFIX}-msgactions ${PREFIX}-${item.role}`);\n if (item.branch && opts.setActiveLeaf)\n rowEl.appendChild(buildBranchNav(item.branch));\n if (isUser) {\n // The message's DOM rows (attachment chips + bubble) to hide while editing.\n const btn = el(\n \"button\",\n `${PREFIX}-msgact ${PREFIX}-msgact-icon`\n ) as HTMLButtonElement;\n btn.type = \"button\";\n const label = opts.editLabel ?? \"Edit\";\n btn.setAttribute(\"aria-label\", label);\n btn.title = label;\n btn.innerHTML = ICON_EDIT;\n btn.addEventListener(\"click\", () => {\n const nodes: HTMLElement[] = [];\n for (let n: ChildNode | null = anchor; n; n = n.nextSibling) {\n if (n instanceof HTMLElement) nodes.push(n);\n if (n === rowEl) break;\n }\n beginEdit(item, anchor, nodes);\n });\n rowEl.appendChild(btn);\n } else {\n if (canRegen) {\n const parentId = item.parentId;\n const btn = el(\"button\", `${PREFIX}-msgact`) as HTMLButtonElement;\n btn.type = \"button\";\n const label = opts.regenerateLabel ?? \"Regenerate\";\n btn.setAttribute(\"aria-label\", label);\n btn.innerHTML = `${ICON_REGEN}<span>${escapeHtml(label)}</span>`;\n btn.addEventListener(\"click\", () => {\n if (busy || typeof parentId !== \"string\") return;\n // Drop the current assistant turn's DOM, then stream a fresh sibling\n // answer to the same user turn (empty content + regenerate flag).\n removeFrom(anchor);\n void send(\"\", { regenerate: true, parentId });\n });\n rowEl.appendChild(btn);\n }\n if (canVote)\n rowEl.appendChild(\n buildVoteButtons(item.id as string, item.model ?? undefined)\n );\n }\n log.appendChild(rowEl);\n }\n\n // Switch which branch is active (the \u2039n/m\u203A arrows): persist the chosen leaf\n // server-side, then reload so the transcript follows that path.\n async function switchBranch(leaf: string): Promise<void> {\n if (busy || !threadId || !opts.setActiveLeaf || !opts.loadThread) return;\n try {\n await opts.setActiveLeaf(threadId, leaf);\n const items = await opts.loadThread(threadId);\n renderThreadItems(items);\n saveState();\n } catch {\n /* leave the current branch visible on failure */\n }\n }\n\n /**\n * Paint the opening view: a restored conversation, or the greeting.\n *\n * DEFINED HERE, CALLED AT THE END OF SETUP \u2014 and that is the whole point.\n * Restoring runs the ordinary render path, and those renderers read widget\n * state (`advanced`, and whatever a future card reaches for) declared with\n * `let` further down this same scope. Running the restore inline, where it\n * used to be, therefore read a binding in its temporal dead zone the moment a\n * restored message carried a `[[navigate]]` directive: renderNavigate touched\n * `advanced` ~700 lines before its declaration executed.\n *\n * The blast radius was the whole app, not the widget. The throw escaped the\n * factory, React's error boundary caught it, and ui-admin rendered \"Something\n * went wrong\" on EVERY route. Worse, the trigger was persisted: the offending\n * transcript sat in localStorage, so the crash survived every reload and the\n * only way out was clearing site data. Using the assistant broke the app.\n *\n * A render is not startup work. Do it once the state it renders against\n * exists.\n */\n function paintOpeningView(): void {\n if (history.length) {\n // Restore a prior conversation (survives refresh) \u2014 text only at first.\n for (const m of history)\n if (m.role === \"assistant\") addAssistantMessage(m.content);\n else addMsg(log, m.role, m.content, m.attachments);\n } else if (opts.greeting) {\n addAssistantMessage(opts.greeting);\n }\n // A background job this thread started may still be running from BEFORE the\n // refresh \u2014 put its live card back immediately, off the restored thread id,\n // without waiting for the (optional, async) full thread fetch below.\n reattachTrackedJobs(threadId);\n // Persisted history is TEXT-ONLY (data widgets aren't stored in\n // localStorage), so on refresh the charts/tables were lost. If we have the\n // thread id + a loader, re-fetch the full thread and re-render WITH its\n // widgets \u2014 matching reopen-from-history. Async + best-effort; the text\n // restore above is the instant fallback.\n if (threadId && opts.loadThread) {\n const tid = threadId;\n void opts\n .loadThread(tid)\n .then((items) => {\n if (items.length) renderThreadItems(items);\n })\n .catch(() => {\n /* keep the text-only restore */\n });\n }\n }\n\n // Smooth auto-scroll: stay pinned to the newest message ONLY while the user is\n // already near the bottom \u2014 so streaming text doesn't yank the view when they\n // scrolled up to read. rAF-batched to avoid per-token layout thrash (the\n // \"glitch\"). Pinning resets whenever they scroll back down.\n log.addEventListener(\"scroll\", () => {\n pinned = log.scrollHeight - log.scrollTop - log.clientHeight < 90;\n });\n // Defensive wheel scrolling. The widget lives in the host's light DOM, so a\n // host that installs a global wheel handler \u2014 scroll-lock from an open\n // drawer/dialog (react-remove-scroll/Radix), or a smooth-scroll library \u2014\n // can preventDefault our wheel events and a scroll region won't move. We\n // drive the scroll ourselves and only claim the gesture when the region\n // actually moves, so page overscroll still hands off cleanly at the edges.\n // Applied to EVERY internal scroll area (the log AND the history list \u2014 the\n // latter previously relied on native scrolling and was dead on such hosts).\n const attachWheelScroll = (elm: HTMLElement): void => {\n elm.addEventListener(\n \"wheel\",\n (e) => {\n const step =\n e.deltaMode === 1\n ? e.deltaY * 16 // lines \u2192 px\n : e.deltaMode === 2\n ? e.deltaY * elm.clientHeight // pages \u2192 px\n : e.deltaY;\n const before = elm.scrollTop;\n elm.scrollTop = before + step;\n if (elm.scrollTop !== before) {\n e.preventDefault();\n e.stopPropagation();\n }\n },\n { passive: false }\n );\n };\n attachWheelScroll(log);\n\n /** Show a brief, VISIBLE system line in the transcript (flag result, guidance)\n * \u2014 a tooltip/title change alone is invisible, so users can't tell it worked. */\n function flagNote(text: string, ok: boolean): void {\n const note = el(\n \"div\",\n `${PREFIX}-replay-note ${PREFIX}-flag-note${ok ? ` ${PREFIX}-flag-ok` : \"\"}`\n );\n note.textContent = text;\n log.appendChild(note);\n scrollDown(true);\n // Auto-dismiss the ephemeral notice so it doesn't clutter the transcript.\n setTimeout(() => note.remove(), 6000);\n }\n\n // Token meter \u2014 shown only for the free visitor preview (when signupUrl is\n // set). Surfaces remaining allowance + tokens used this browser session.\n const meterEl = el(\"div\", `${PREFIX}-meter`);\n meterEl.style.display = \"none\";\n let quotaGranted = 0;\n let quotaRemaining: number | null = null;\n let sessionUsed = 0;\n function renderMeter(): void {\n if (!opts.signupUrl || (quotaRemaining === null && sessionUsed === 0)) {\n meterEl.style.display = \"none\";\n return;\n }\n meterEl.style.display = \"block\";\n const pct =\n quotaGranted > 0 && quotaRemaining !== null\n ? Math.max(0, Math.min(100, (quotaRemaining / quotaGranted) * 100))\n : 100;\n const remTxt =\n quotaRemaining !== null\n ? L(\"meterTokensLeft\", { count: quotaRemaining.toLocaleString() })\n : L(\"meterFreePreview\");\n const usedTxt = L(\"meterUsedThisSession\", {\n count: sessionUsed.toLocaleString(),\n });\n meterEl.innerHTML = `<div class=\"${PREFIX}-meter-bar\"><span style=\"transform:scaleX(${pct / 100})\"></span></div><div class=\"${PREFIX}-meter-row\"><span>${escapeHtml(remTxt)}</span><span>${escapeHtml(usedTxt)}</span></div>`;\n }\n\n // Authed status bar \u2014 remaining credits + the active Copilot role. Shown only\n // when a balance provider is wired (org + admin), independent of the visitor\n // token meter above.\n // Host-supplied, empty by default. See the option docs above for why these\n // are no longer a list of sgiant's own tool names.\n const ROLE_NAMES: Record<string, string> = opts.roleNames ?? {};\n const ACTION_ROLE: Record<string, string> = opts.actionRoles ?? {};\n const TOOL_ROLE: Record<string, string> = opts.toolRoles ?? {};\n // Persistent status structure so the credits number can ANIMATE (a live\n // count-down after a turn / count-up after a top-up) instead of snapping.\n const statusEl = el(\"div\", `${PREFIX}-status`);\n statusEl.style.display = \"none\";\n const statusRoleEl = el(\"span\", `${PREFIX}-status-role`);\n const statusCreditsEl = el(\"span\", `${PREFIX}-status-credits`);\n const statusCreditsVal = el(\"span\", `${PREFIX}-status-credits-val`);\n statusCreditsEl.append(\n document.createTextNode(\"\u2605 \"),\n statusCreditsVal,\n document.createTextNode(L(\"creditsSuffix\"))\n );\n statusEl.append(statusRoleEl, statusCreditsEl);\n let activeRole = \"talk\";\n let creditBalance: number | null = null;\n let displayedCredits = 0; // number currently on screen (drives the tween)\n let creditInit = false; // first value snaps; later changes animate\n let creditRaf = 0;\n function setCreditsText(n: number): void {\n statusCreditsVal.textContent = Math.round(n).toLocaleString();\n }\n function renderStatus(): void {\n if (!opts.getBalance) {\n statusEl.style.display = \"none\";\n return;\n }\n statusEl.style.display = \"flex\";\n statusRoleEl.textContent = ROLE_NAMES[activeRole] ?? activeRole;\n // null = \"not credit-driven here\" (the free staff lane): hide the credits\n // chip entirely \u2014 a dash read as a broken balance, and there is nothing\n // to show. The role indicator stays.\n statusCreditsEl.style.display = creditBalance === null ? \"none\" : \"\";\n if (creditBalance !== null && !creditRaf) {\n displayedCredits = creditBalance;\n setCreditsText(displayedCredits);\n }\n }\n // Tween the on-screen credits to `to` (easeOut), honoring reduced motion.\n function animateCredits(to: number): void {\n if (creditRaf) {\n cancelAnimationFrame(creditRaf);\n creditRaf = 0;\n }\n const from = displayedCredits;\n const reduce = window.matchMedia?.(\n \"(prefers-reduced-motion: reduce)\"\n ).matches;\n if (from === to || reduce) {\n displayedCredits = to;\n setCreditsText(to);\n return;\n }\n const dur = Math.min(900, 250 + Math.abs(to - from) * 1.2);\n statusCreditsVal.classList.add(`${PREFIX}-credits-live`);\n let start = 0;\n const step = (now: number): void => {\n if (!start) start = now;\n const p = Math.min(1, (now - start) / dur);\n const e = 1 - Math.pow(1 - p, 3); // easeOutCubic\n displayedCredits = from + (to - from) * e;\n setCreditsText(displayedCredits);\n if (p < 1) creditRaf = requestAnimationFrame(step);\n else {\n creditRaf = 0;\n displayedCredits = to;\n setCreditsText(to);\n statusCreditsVal.classList.remove(`${PREFIX}-credits-live`);\n }\n };\n creditRaf = requestAnimationFrame(step);\n }\n async function refreshBalance(): Promise<void> {\n if (!opts.getBalance) return;\n try {\n creditBalance = await opts.getBalance();\n } catch {\n return; // keep last\n }\n if (!opts.getBalance) return;\n statusEl.style.display = \"flex\";\n statusRoleEl.textContent = ROLE_NAMES[activeRole] ?? activeRole;\n // Same null-hides-the-chip rule as renderStatus (free staff lane).\n statusCreditsEl.style.display = creditBalance === null ? \"none\" : \"\";\n if (creditBalance === null) {\n /* chip hidden */\n } else if (!creditInit) {\n creditInit = true; // first read snaps (no count-up from zero on open)\n displayedCredits = creditBalance;\n setCreditsText(creditBalance);\n } else {\n animateCredits(creditBalance); // subsequent changes count to the new value\n }\n }\n function setRole(role: string): void {\n activeRole = role;\n renderStatus();\n }\n\n // Page-aware shortcut chips (filled by renderSuggestions); sits just above the\n // composer and only while the conversation is empty.\n const suggestionsEl = el(\"div\", `${PREFIX}-suggestions`);\n suggestionsEl.style.display = \"none\";\n\n const form = el(\"form\", `${PREFIX}-form`) as HTMLFormElement;\n const input = el(\"input\", `${PREFIX}-input`) as HTMLInputElement;\n // \"Always ready\" cue \u2014 an inviting prompt the assistant is waiting for input.\n input.placeholder = L(\"askAnything\", { name });\n input.setAttribute(\"aria-label\", L(\"messageAria\", { name }));\n input.autocomplete = \"off\";\n // Restore an unsent draft from a prior session; keep it in sync as they type.\n input.value = loadDraft();\n input.addEventListener(\"input\", () => saveDraft(input.value));\n const sendBtn = el(\"button\", `${PREFIX}-send`) as HTMLButtonElement;\n sendBtn.type = \"submit\";\n sendBtn.textContent = L(\"send\");\n\n // Session artifacts \u2014 media scraped or imported in THIS conversation, hidden\n // from the library until saved. A slim chip strip above the composer with a\n // per-item Save action (promotes via save_artifact_to_assets). Best-effort:\n // hosts without the lister never see it.\n const artifactsEl = el(\"div\", `${PREFIX}-artifacts`);\n artifactsEl.style.display = \"none\";\n let savingArtifact = false;\n async function refreshArtifacts(): Promise<void> {\n if (!opts.listSessionArtifacts || !threadId) {\n artifactsEl.style.display = \"none\";\n artifactsEl.innerHTML = \"\";\n return;\n }\n let items: {\n id: string;\n filename: string;\n contentType: string;\n label?: string | null;\n }[] = [];\n try {\n items = await opts.listSessionArtifacts(threadId);\n } catch {\n return; // strip is best-effort\n }\n artifactsEl.innerHTML = \"\";\n if (!items.length) {\n artifactsEl.style.display = \"none\";\n return;\n }\n artifactsEl.style.display = \"flex\";\n const title = el(\"span\", `${PREFIX}-artifacts-title`);\n title.textContent = L(\"artifactsTitle\");\n artifactsEl.appendChild(title);\n for (const a of items) {\n const chip = el(\"span\", `${PREFIX}-artifact`);\n const label = el(\"span\", `${PREFIX}-artifact-name`);\n label.textContent = a.label || a.filename;\n label.title = a.filename;\n chip.appendChild(label);\n if (opts.onApplyProposal) {\n const save = el(\n \"button\",\n `${PREFIX}-artifact-save`\n ) as HTMLButtonElement;\n save.type = \"button\";\n save.textContent = L(\"artifactSave\");\n save.addEventListener(\"click\", async () => {\n if (savingArtifact) return;\n savingArtifact = true;\n save.textContent = L(\"artifactSaving\");\n save.disabled = true;\n try {\n await opts.onApplyProposal!(\"save_artifact_to_assets\", {\n mediaId: a.id,\n });\n chip.remove();\n if (!artifactsEl.querySelector(`.${PREFIX}-artifact`))\n artifactsEl.style.display = \"none\";\n } catch {\n save.textContent = L(\"tryAgain\");\n save.disabled = false;\n } finally {\n savingArtifact = false;\n }\n });\n chip.appendChild(save);\n }\n artifactsEl.appendChild(chip);\n }\n }\n\n // Attachments (authed surfaces only): a paperclip that opens a file picker,\n // uploads session-scoped (never into the asset library), and stages refs for\n // the next turn.\n const stagedAtts: WidgetAtt[] = [];\n const attBar = el(\"div\", `${PREFIX}-attbar`);\n attBar.style.display = \"none\";\n let attachBtn: HTMLButtonElement | null = null;\n let fileInput: HTMLInputElement | null = null;\n function renderStaged(): void {\n attBar.innerHTML = \"\";\n attBar.style.display = stagedAtts.length ? \"flex\" : \"none\";\n stagedAtts.forEach((a, i) => {\n const chip = el(\"span\", `${PREFIX}-att ${PREFIX}-att-staged`);\n chip.title = `${a.filename} (${a.contentType})`;\n const label = el(\"span\", \"\");\n label.textContent = `${attIcon(a.kind)} ${a.filename}`;\n const x = el(\"button\", `${PREFIX}-att-x`) as HTMLButtonElement;\n x.type = \"button\";\n x.textContent = \"\u00D7\";\n x.setAttribute(\"aria-label\", L(\"removeAttachment\", { file: a.filename }));\n x.addEventListener(\"click\", () => {\n stagedAtts.splice(i, 1);\n renderStaged();\n });\n chip.append(label, x);\n attBar.appendChild(chip);\n });\n }\n // Max size for one attachment \u2014 the shared transport cap. NOTE the api's\n // TURN-time caps are tighter per kind (5 MB image vision block, 12 MB native\n // PDF \u2014 see @sgiant/shared chat-attachment-limits); an oversize image still\n // uploads but resolves as a skipped note the model sees.\n const MAX_ATT_BYTES = CHAT_ATTACHMENT_MAX_BYTES;\n /** A clip or track gets a far bigger allowance than a document: a 30-second\n * 1080p phone video is already past the document cap, so the everyday case\n * would be refused for being ordinary. Video/audio is referenced by id and\n * never sent to the model inline, so this bounds an upload, not a prompt. */\n const attCap = (file: File): number =>\n /^(video|audio)\\//i.test(file.type)\n ? CHAT_ATTACHMENT_MAX_AV_BYTES\n : MAX_ATT_BYTES;\n const capLabel = (bytes: number): string =>\n `${Math.round(bytes / (1024 * 1024))} MB`;\n function attError(msg: string): void {\n const chip = el(\"span\", `${PREFIX}-att ${PREFIX}-att-err`);\n chip.textContent = `\u26A0 ${msg}`;\n attBar.appendChild(chip);\n attBar.style.display = \"flex\";\n setTimeout(() => {\n chip.remove();\n if (!attBar.children.length) attBar.style.display = \"none\";\n }, 4000);\n }\n async function uploadFiles(files: FileList | null): Promise<void> {\n const uploadEndpoint = opts.getUploadEndpoint?.() ?? opts.uploadEndpoint;\n if (!files || !uploadEndpoint) return;\n if (attachBtn) attachBtn.disabled = true;\n try {\n const token = opts.getToken ? await opts.getToken() : opts.token;\n for (const file of Array.from(files).slice(\n 0,\n CHAT_ATTACHMENT_MAX_COUNT\n )) {\n if (stagedAtts.length >= CHAT_ATTACHMENT_MAX_COUNT) break;\n const cap = attCap(file);\n if (file.size > cap) {\n attError(`\"${file.name}\" is too large (max ${capLabel(cap)})`);\n continue;\n }\n const fd = new FormData();\n fd.append(\"file\", file);\n // Chat attachments are SESSION-scoped: reachable from the chat history\n // by id, but never filed into the asset library.\n fd.append(\"session\", \"1\");\n if (threadId) fd.append(\"threadId\", threadId);\n const res = await fetch(uploadEndpoint, {\n method: \"POST\",\n headers: token ? { authorization: `Bearer ${token}` } : {},\n credentials: opts.withCredentials ? \"include\" : \"same-origin\",\n body: fd,\n });\n if (!res.ok) continue;\n const j = (await res.json()) as {\n media?: { id: string; filename: string; contentType: string };\n };\n if (!j.media) continue;\n const ct = j.media.contentType || file.type || \"\";\n stagedAtts.push({\n mediaId: j.media.id,\n kind: ct.startsWith(\"image/\")\n ? \"image\"\n : ct.includes(\"pdf\")\n ? \"pdf\"\n : \"doc\",\n filename: j.media.filename,\n contentType: ct,\n });\n renderStaged();\n }\n } catch {\n /* upload failed \u2014 silent; the user can retry */\n } finally {\n if (attachBtn) attachBtn.disabled = false;\n if (fileInput) fileInput.value = \"\";\n }\n }\n if (opts.uploadEndpoint || opts.getUploadEndpoint) {\n fileInput = el(\"input\", \"\") as HTMLInputElement;\n fileInput.type = \"file\";\n fileInput.multiple = true;\n fileInput.accept =\n \"image/*,video/*,audio/*,.svg,application/pdf,.doc,.docx,.xls,.xlsx,.csv,.txt,.md,.json,.yaml,.yml\";\n fileInput.style.display = \"none\";\n fileInput.addEventListener(\n \"change\",\n () => void uploadFiles(fileInput!.files)\n );\n attachBtn = el(\"button\", `${PREFIX}-attach`) as HTMLButtonElement;\n attachBtn.type = \"button\";\n attachBtn.innerHTML = ICON_ATTACH;\n attachBtn.setAttribute(\"aria-label\", L(\"attachFile\"));\n attachBtn.title = L(\"attachFileHint\");\n attachBtn.addEventListener(\"click\", () => fileInput!.click());\n // Paste-to-attach: a screenshot (or copied file) pasted into the composer\n // becomes a staged attachment \u2014 the same pipeline as the paperclip. Only\n // intercept when the clipboard actually carries files; plain text pastes\n // fall through untouched.\n input.addEventListener(\"paste\", (e) => {\n const files = (e as ClipboardEvent).clipboardData?.files;\n if (files && files.length) {\n e.preventDefault();\n void uploadFiles(files);\n }\n });\n form.append(attachBtn, input, sendBtn, fileInput);\n } else {\n form.append(input, sendBtn);\n }\n\n // The chat lives in its own column so advanced view can lay a drivable app\n // pane beside it. In normal mode `chatCol` fills the panel; `pane` is hidden.\n const chatCol = el(\"div\", `${PREFIX}-chatcol`);\n chatCol.append(\n header,\n log,\n meterEl,\n statusEl,\n suggestionsEl,\n artifactsEl,\n attBar,\n form\n );\n const pane = el(\"div\", `${PREFIX}-pane`);\n panel.append(chatCol, pane);\n renderStatus();\n // Shared avatar gradient/filter defs (once) \u2014 see AVATAR_DEFS.\n if (!document.getElementById(`${PREFIX}-av-g`)) {\n const defs = document.createElement(\"div\");\n defs.style.cssText = \"position:absolute;width:0;height:0;overflow:hidden\";\n defs.innerHTML = AVATAR_DEFS;\n root.append(defs);\n }\n root.append(bubble, panel);\n\n // Drop the active thread + transcript (the old one stays in history) and\n // start fresh with the greeting. Shared by the \"New chat\" button and any\n // caller that opens the widget on a fresh thread (see open's forceNew).\n const startNewChat = (): void => {\n viewGen++; // fence: any in-flight turn stops painting into this view\n threadId = undefined;\n paintStarItem();\n history.length = 0;\n saveState();\n clearRich();\n log.innerHTML = \"\";\n if (opts.greeting) addAssistantMessage(opts.greeting);\n syncBusy();\n void renderSuggestions();\n void refreshArtifacts();\n };\n\n const open = (prefill?: unknown, forceNew?: boolean): void => {\n // The first-visit pill has done its job the moment it is opened, and an\n // unread count is answered by reading the thread (#305).\n markOpened();\n setLauncher({ variant: \"pebble\", state: \"resting\", count: 0 });\n panel.style.display = \"flex\";\n panel.style.transform = \"\"; // clear any leftover drag offset\n // Re-apply the saved position NOW that the panel is visible and laid out, so\n // clampPos measures its REAL size and can never strand it off-screen. A saved\n // {0,0} / stale position combined with leftover inline anchors used to render\n // the panel below the fold \u2014 the \"invisible widget\" bug. No saved position \u2192\n // applyPos(null) restores the default CSS corner.\n applyPos(readPos());\n // Belt AND braces: whatever happened above, if the panel is not actually\n // on-screen now, discard the saved position and snap to the default corner.\n // A visible assistant on open is non-negotiable \u2014 a stranded panel reads as\n // \"the assistant is gone\", which is exactly the bug this closes.\n const pr = panel.getBoundingClientRect();\n if (\n pr.right < 40 ||\n pr.bottom < 40 ||\n pr.left > window.innerWidth - 40 ||\n pr.top > window.innerHeight - 40\n ) {\n savePos(null);\n applyPos(null);\n }\n bubble.style.display = \"none\";\n // Show the latest message + focus the composer (always-ready assistant feel).\n pinned = true;\n // A caller (e.g. \"Regenerate showcase\") can demand a FRESH chat so the\n // starter lands in a clean thread, not tacked onto whatever was open.\n if (forceNew) startNewChat();\n log.scrollTop = log.scrollHeight;\n // A caller (e.g. the \"Build with AI\" button) can open PREFILLED with a\n // starter via a CustomEvent detail \u2014 seed the composer, so the user just\n // hits send. Non-string args (a click MouseEvent) are ignored. Normally we\n // only seed an EMPTY composer, but forceNew (fresh thread) always seeds.\n if (\n typeof prefill === \"string\" &&\n prefill.trim() &&\n (forceNew || !input.value.trim())\n ) {\n input.value = prefill;\n saveDraft(input.value);\n }\n input.focus();\n bindKeyboard();\n void refreshBalance();\n // Refresh shortcuts for the page the user opened the widget on.\n void renderSuggestions();\n rememberOpen(true);\n };\n const close = (): void => {\n unbindKeyboard();\n panel.style.display = \"none\";\n panel.style.transform = \"\";\n panel.style.transition = \"\";\n panel.style.height = \"\";\n // Clear ALL position overrides (not just top) so the next open re-applies the\n // saved position from a clean slate. A lone leftover left/right/bottom used to\n // combine with a re-applied axis into an off-screen anchor.\n panel.style.left = panel.style.top = \"\";\n panel.style.right = panel.style.bottom = \"\";\n // Same reason: a leftover keyboard flag would suppress the safe-area\n // padding on the next open, with no keyboard to justify it.\n panel.classList.remove(`${PREFIX}-kb`);\n bubble.style.display = \"flex\";\n rememberOpen(false);\n };\n const toggle = (): void =>\n panel.style.display === \"none\" ? open() : close();\n\n bubble.addEventListener(\"click\", open);\n closeBtn.addEventListener(\"click\", close);\n\n // Swipe-down-to-close (mobile bottom sheet). Dragging the header/grab-handle\n // pulls the panel down with the finger; releasing past a threshold dismisses\n // it, otherwise it springs back. No-op on desktop widths \u2014 `isSheet` is\n // declared next to `applyPos`, which needs it at mount.\n\n // iOS keyboard handling. On the mobile full-screen sheet the soft keyboard\n // shrinks the VISUAL viewport but NOT 100dvh, so a focused composer would sit\n // behind the keyboard / off-screen. Size the sheet to window.visualViewport\n // (which DOES shrink for the keyboard) while it's open, and follow its offset.\n let vvCleanup: (() => void) | null = null;\n const bindKeyboard = (): void => {\n const vv = window.visualViewport;\n if (!vv || vvCleanup) return;\n const apply = (): void => {\n if (!isSheet() || panel.style.display === \"none\") {\n panel.style.height = \"\";\n panel.style.bottom = \"\";\n panel.classList.remove(`${PREFIX}-kb`);\n // Desktop / non-sheet: the panel's `top` is owned by the drag-to-\n // reposition logic, NOT the keyboard handler. Clearing it here dropped a\n // dragged panel to `top:auto` \u2192 off-screen, and a refresh re-stranded it.\n // Restore the saved position (or the CSS corner) instead of clobbering it.\n applyPos(readPos());\n return;\n }\n panel.style.height = `${vv.height}px`;\n panel.style.top = `${vv.offsetTop}px`;\n // The sheet's CSS is `inset:0`, so `bottom:0` is still in play. top +\n // height + bottom is over-constrained; engines are entitled to resolve\n // that differently, and the one that honours `bottom` stretches the\n // panel back under the keyboard. Pin it to auto so the height we just\n // measured is the height that renders.\n panel.style.bottom = \"auto\";\n // Keyboard up \u21D2 the home indicator is covered, so drop the safe-area\n // padding that iOS still reports (see the -kb rules).\n panel.classList.toggle(\n `${PREFIX}-kb`,\n vv.height < window.innerHeight - 80\n );\n };\n apply();\n vv.addEventListener(\"resize\", apply);\n vv.addEventListener(\"scroll\", apply);\n vvCleanup = (): void => {\n vv.removeEventListener(\"resize\", apply);\n vv.removeEventListener(\"scroll\", apply);\n };\n };\n const unbindKeyboard = (): void => {\n vvCleanup?.();\n vvCleanup = null;\n };\n let dragStartY = 0;\n let dragging = false;\n header.addEventListener(\n \"touchstart\",\n (e: TouchEvent) => {\n if (!isSheet() || e.touches.length !== 1) return;\n dragStartY = e.touches[0].clientY;\n dragging = true;\n panel.style.transition = \"none\";\n },\n { passive: true }\n );\n header.addEventListener(\n \"touchmove\",\n (e: TouchEvent) => {\n if (!dragging) return;\n const dy = e.touches[0].clientY - dragStartY;\n if (dy > 0) panel.style.transform = `translateY(${dy}px)`;\n },\n { passive: true }\n );\n const endDrag = (e: TouchEvent): void => {\n if (!dragging) return;\n dragging = false;\n const dy = (e.changedTouches[0]?.clientY ?? dragStartY) - dragStartY;\n panel.style.transition = \"transform var(--duration-fast) var(--ease-out)\";\n if (dy > 110) {\n panel.style.transform = \"translateY(100%)\";\n window.setTimeout(close, 230);\n } else {\n panel.style.transform = \"\";\n }\n };\n header.addEventListener(\"touchend\", endDrag, { passive: true });\n header.addEventListener(\"touchcancel\", endDrag, { passive: true });\n\n // Expand / restore the panel (bigger reading area). Toggles a class + icon.\n // \"Wide view\" in the UI: the same button flips between grow (expand) and\n // shrink-back-to-widget (restore). Advanced view reuses `setExpanded(false)`\n // to drop the plain wide size, so the icon/label never drift out of sync.\n let expanded = false;\n const setExpanded = (v: boolean): void => {\n expanded = v;\n panel.classList.toggle(`${PREFIX}-expanded`, expanded);\n if (expandBtn) {\n expandBtn.innerHTML = expanded ? ICON_COLLAPSE : ICON_EXPAND;\n expandBtn.setAttribute(\n \"aria-label\",\n expanded ? L(\"restoreChat\") : L(\"expandChat\")\n );\n expandBtn.title = expanded ? L(\"restore\") : L(\"expand\");\n }\n };\n if (expandBtn) {\n expandBtn.addEventListener(\"click\", () => {\n setExpanded(!expanded);\n scrollDown(true);\n });\n }\n\n // --- Advanced view -----------------------------------------------------------\n // A full-screen split: chat on the left, the app in an <iframe> on the right\n // that Copilot drives via the agent bridge. Navigation moves the FRAME; on-page\n // actions (highlight/fill/click) run INSIDE the frame over postMessage, so they\n // hit the app the user is watching \u2014 not the parent shell behind the overlay.\n let advanced = false;\n let advancedFull = false;\n let paneCollapsed = false;\n let transport: FrameTransport | null = null;\n let advFrame: HTMLIFrameElement | null = null;\n let frameUrlLabel: HTMLElement | null = null;\n let collapseBtnEl: HTMLButtonElement | null = null;\n const advKey = opts.persistKey ? `${ns}:adv:${opts.persistKey}` : \"\";\n const rememberAdvanced = (): void => {\n writeFlag(advKey, advanced);\n };\n\n const setFrameUrlLabel = (url: string): void => {\n if (!frameUrlLabel) return;\n let text = url;\n try {\n const u = new URL(url, location.href);\n text = u.pathname + u.search;\n } catch {\n /* keep raw */\n }\n frameUrlLabel.textContent = text;\n };\n\n /**\n * THE PATH THE WIDGET LAST ASKED THE FRAME TO SHOW.\n *\n * Compared against where the frame ACTUALLY is on every load, which is how\n * \"the user has driven the frame themselves\" is detected: the pane hosts our\n * own app, so a click inside it navigates the iframe without going through\n * `navigateFrame` at all. Same-origin by construction (`isSafeFrameUrl`), so\n * the location is readable \u2014 but still guarded, because a frame mid-load has\n * no document to ask.\n */\n let frameRequestedPath: string | null = null;\n /** True once the frame has gone somewhere the widget did not send it. Blocks\n * auto-navigation, so a report finishing never yanks a reader off the page\n * they chose. */\n let frameUserDriven = false;\n\n const framePath = (): string | null => {\n try {\n return advFrame?.contentWindow?.location?.pathname ?? null;\n } catch {\n // Cross-origin should be impossible here, but a frame that has not\n // committed a document yet can also throw. Unknown, not \"user drove it\".\n return null;\n }\n };\n\n /** Point the frame at a full URL (a full document load \u2014 the embedded app boots\n * at that route and re-mounts its agent). */\n const navigateFrame = (url: string): void => {\n if (!advFrame) return;\n // Untrusted target (cross-origin, protocol-relative or a `javascript:`\n // payload smuggled through a model-authored path) \u2014 refuse silently.\n if (!isSafeFrameUrl(url)) return;\n try {\n frameRequestedPath = new URL(url, location.origin).pathname;\n } catch {\n frameRequestedPath = null;\n }\n advFrame.src = url;\n setFrameUrlLabel(url);\n };\n\n /**\n * Follow a long operation into the pane \u2014 ONCE, and never over the user.\n *\n * A report tells the reader to \"watch it build\" and then leaves the frame\n * wherever it was, so watching it meant clicking. This does the click. Four\n * conditions, and each one is a way this could be obnoxious instead of\n * helpful:\n *\n * - advanced view only. In floating mode there is no pane to move, and\n * navigating the whole PAGE out from under someone because a background\n * job started is not the same feature.\n * - only work the user confirmed in THIS session (`userConfirmedWork`).\n * Tracked jobs are restored from localStorage and re-discovered from the\n * server listing, so a card can appear for an import another tab (or\n * another device) started; `openAdvanced` clears `autoNavigated` for a\n * fresh session, and without this condition the first poll after opening\n * the pane would send it to that job's page.\n * - not if the user has driven the frame since (`frameUserDriven`). They\n * chose that page; a job finishing is not permission to leave it.\n * - once per operation (`autoNavigated`). A poll runs every couple of\n * seconds; re-navigating on each one would reload the page continuously\n * and make it unreadable \u2014 which is the exact opposite of watching it\n * build.\n */\n const autoNavigated = new Set<string>();\n const autoNavigateFrame = (key: string, path: string): void => {\n if (!advFrame || !opts.getAdvancedUrl) return;\n if (\n !shouldAutoNavigate({\n advanced,\n canNavigate: true,\n userConfirmedThisSession: userConfirmedWork.has(key),\n userDriven: frameUserDriven,\n alreadyFollowed: autoNavigated.has(key),\n path,\n currentPath: framePath() ?? frameRequestedPath,\n })\n ) {\n return;\n }\n autoNavigated.add(key);\n navigateFrame(opts.getAdvancedUrl(path));\n };\n\n /** Is the pane already showing this path? Drives the narration collapse \u2014\n * see `renderJobCard`. Compared on pathname: a query string is a filter\n * within the same page, not a different destination. */\n const frameShowing = (path: string | null | undefined): boolean =>\n shouldCollapseNarration({\n advanced,\n livePath: path,\n currentPath: framePath() ?? frameRequestedPath,\n });\n\n // Collapse the app pane to a thin strip (not display:none \u2014 the strip keeps the\n // toggle visible so it can be re-expanded). Also refreshes the toggle's label.\n const setPaneCollapsed = (v: boolean): void => {\n paneCollapsed = v;\n panel.classList.toggle(`${PREFIX}-pane-collapsed`, v);\n if (collapseBtnEl) {\n const label = v\n ? opts.expandPaneLabel || \"Show page\"\n : opts.collapsePaneLabel || \"Hide page\";\n collapseBtnEl.setAttribute(\"aria-label\", label);\n collapseBtnEl.title = label;\n }\n };\n\n const buildFrame = (): void => {\n pane.innerHTML = \"\";\n const bar = el(\"div\", `${PREFIX}-pane-bar`);\n const collapseBtn = el(\n \"button\",\n `${PREFIX}-icon ${PREFIX}-pane-collapse`\n ) as HTMLButtonElement;\n collapseBtn.type = \"button\";\n collapseBtn.setAttribute(\n \"aria-label\",\n opts.collapsePaneLabel || \"Hide page\"\n );\n collapseBtn.title = opts.collapsePaneLabel || \"Hide page\";\n collapseBtn.innerHTML = ICON_CHEVRON_R;\n collapseBtn.addEventListener(\"click\", () =>\n setPaneCollapsed(!paneCollapsed)\n );\n collapseBtnEl = collapseBtn;\n frameUrlLabel = el(\"div\", `${PREFIX}-pane-url`);\n bar.append(collapseBtn, frameUrlLabel);\n const frame = el(\"iframe\", `${PREFIX}-pane-frame`) as HTMLIFrameElement;\n frame.setAttribute(\"title\", opts.advancedLabel || \"App preview\");\n // No sandbox: the framed page is our OWN app and needs full capability\n // (same-origin session cookies, storage, popups). For untrusted third-party\n // targets an embedder would add a sandbox with explicit allows.\n // The frame sits in a padded body so the driven app reads as an inset,\n // rounded \"screen\" with breathing room rather than a flush edge-to-edge slab.\n // THE USER CAN DRIVE THIS FRAME WITHOUT US. It hosts our own app, so a\n // click inside it navigates the iframe directly \u2014 `navigateFrame` never\n // hears about it. Comparing where it LANDED against where we last asked it\n // to go is the only honest signal, and it is what stops a finishing job\n // from yanking someone off a page they chose to open.\n frame.addEventListener(\"load\", () => {\n const here = framePath();\n if (!here) return;\n setFrameUrlLabel(here);\n if (frameRequestedPath === null) {\n // We never asked for anything and it moved: the user did.\n frameUserDriven = true;\n return;\n }\n if (here !== frameRequestedPath) frameUserDriven = true;\n });\n const body = el(\"div\", `${PREFIX}-pane-body`);\n body.append(frame);\n pane.append(bar, body);\n advFrame = frame;\n transport = createFrameTransport(frame, {\n targetOrigin: opts.advancedOrigin || location.origin,\n });\n };\n\n const teardownFrame = (): void => {\n transport?.destroy();\n transport = null;\n advFrame = null;\n frameUrlLabel = null;\n collapseBtnEl = null;\n disposePaneWidget();\n pane.innerHTML = \"\";\n };\n\n /* ------------------------------------------------------------------ *\n * THE PANE IS A UNION: a ROUTE, or a WIDGET.\n *\n * It could only ever be `<iframe src>`, so anything the assistant computed \u2014\n * a chart, a proposed dashboard, a preview \u2014 had to become a real route\n * before it could be shown at full size. The one that mattered most is the\n * ordinary case: \"chart my revenue by channel\" produced a picture squeezed\n * into a 368px chat column while a whole half-screen sat next to it showing\n * a page nobody was reading.\n *\n * EITHER/OR, not a layering. The frame is torn down when a widget takes the\n * pane and rebuilt when the route comes back \u2014 leaving a live iframe behind\n * an overlay would keep its app mounted, its polls running and its agent\n * bridge connected, invisibly.\n *\n * The widget half reuses `renderDataWidget` \u2014 the SAME host renderer the\n * chat log uses \u2014 so there is one rendering path, one validation story and no\n * second widget vocabulary. The host draws a real `<WidgetRender>` with its\n * actual data; the pane is not a picture of one.\n * ------------------------------------------------------------------ */\n let paneWidgetDispose: (() => void) | null = null;\n const disposePaneWidget = (): void => {\n const d = paneWidgetDispose;\n paneWidgetDispose = null;\n if (!d) return;\n try {\n d();\n } catch {\n /* a renderer that throws on unmount must not wedge the pane */\n }\n };\n\n /** True while the pane is showing a computed widget rather than a route. */\n let paneShowsWidget = false;\n\n const showPaneRoute = (): void => {\n if (!paneShowsWidget) return;\n paneShowsWidget = false;\n disposePaneWidget();\n buildFrame();\n if (opts.getAdvancedUrl) navigateFrame(opts.getAdvancedUrl());\n };\n\n /**\n * Put a computed widget in the pane. Returns false when the host wired no\n * renderer \u2014 the caller then keeps it in the chat log, which is where it went\n * before this existed.\n */\n const showPaneWidget = (\n spec: unknown,\n rows: unknown,\n comparisonRows?: unknown\n ): boolean => {\n if (!advanced || !opts.renderDataWidget) return false;\n teardownFrame();\n paneShowsWidget = true;\n const bar = el(\"div\", `${PREFIX}-pane-bar`);\n const back = el(\"button\", `${PREFIX}-icon`) as HTMLButtonElement;\n back.type = \"button\";\n // The way BACK to the page. Without it the pane is a dead end and the only\n // exit is leaving advanced view entirely.\n back.setAttribute(\"aria-label\", L(\"paneBackToPage\"));\n back.title = L(\"paneBackToPage\");\n back.innerHTML = ICON_CHEVRON_R;\n back.addEventListener(\"click\", () => showPaneRoute());\n const label = el(\"div\", `${PREFIX}-pane-url`);\n label.textContent = L(\"paneShowingWidget\");\n bar.append(back, label);\n const body = el(\"div\", `${PREFIX}-pane-body`);\n const host = el(\"div\", `${PREFIX}-pane-widget`);\n body.append(host);\n pane.append(bar, body);\n try {\n const dispose = opts.renderDataWidget(host, spec, rows, comparisonRows);\n if (dispose) paneWidgetDispose = dispose;\n } catch {\n // A renderer that throws must not leave an empty half-screen: fall back\n // to the route and let the caller put it in the log.\n showPaneRoute();\n return false;\n }\n return true;\n };\n\n const updateAdvancedBtn = (): void => {\n if (!advancedBtn) return;\n advancedBtn.classList.toggle(`${PREFIX}-advbtn-on`, advanced);\n const label = advanced\n ? opts.exitAdvancedLabel || \"Exit advanced view\"\n : opts.advancedLabel || \"Advanced view\";\n advancedBtn.setAttribute(\"aria-label\", label);\n advancedBtn.title = label;\n };\n\n // Fullscreen \u2014 advanced view is edge-to-edge by default (the `-advanced-full`\n // class drops the max-width + margins + radius). openAdvanced sets it on;\n // closeAdvanced resets it. There is no user-facing toggle button.\n const setAdvancedFull = (v: boolean): void => {\n advancedFull = v;\n panel.classList.toggle(`${PREFIX}-advanced-full`, v);\n };\n\n // Draggable divider between the chat column and the app pane (advanced view).\n // Drag to resize; the width persists per host so it survives reloads; a\n // double-click resets to the default. Only active in advanced, non-collapsed.\n const advWidthKey = opts.persistKey ? `${ns}:advw:${opts.persistKey}` : \"\";\n const resizer = el(\"div\", `${PREFIX}-resizer`);\n resizer.setAttribute(\"role\", \"separator\");\n resizer.setAttribute(\"aria-orientation\", \"vertical\");\n const resizeLabel = opts.resizeLabel || \"Resize panels\";\n resizer.setAttribute(\"aria-label\", resizeLabel);\n resizer.title = resizeLabel;\n panel.insertBefore(resizer, pane);\n\n const clampChatWidth = (px: number): number => {\n const total = panel.getBoundingClientRect().width || 900;\n const max = Math.max(360, total - 360); // keep the app pane usable\n return Math.round(Math.min(Math.max(px, 320), max));\n };\n const setChatWidth = (px: number, persist: boolean): void => {\n const w = clampChatWidth(px);\n panel.style.setProperty(\"--aiw-chatw\", `${w}px`);\n if (persist) writeItem(advWidthKey, String(w));\n };\n const restoreChatWidth = (): void => {\n const raw = readItem(advWidthKey);\n if (raw) setChatWidth(parseInt(raw, 10), false);\n };\n let resizing = false;\n const onResizeMove = (e: PointerEvent): void => {\n if (!resizing) return;\n setChatWidth(e.clientX - panel.getBoundingClientRect().left, true);\n };\n const stopResize = (): void => {\n if (!resizing) return;\n resizing = false;\n panel.classList.remove(`${PREFIX}-resizing`);\n window.removeEventListener(\"pointermove\", onResizeMove);\n window.removeEventListener(\"pointerup\", stopResize);\n };\n resizer.addEventListener(\"pointerdown\", (e) => {\n if (!advanced || paneCollapsed) return;\n e.preventDefault();\n resizing = true;\n panel.classList.add(`${PREFIX}-resizing`);\n window.addEventListener(\"pointermove\", onResizeMove);\n window.addEventListener(\"pointerup\", stopResize);\n });\n // Double-click the divider \u2192 reset to the default width.\n resizer.addEventListener(\"dblclick\", () => {\n panel.style.removeProperty(\"--aiw-chatw\");\n writeItem(advWidthKey, null);\n });\n\n const openAdvanced = (): void => {\n if (advanced || !opts.getAdvancedUrl) return;\n advanced = true;\n setPaneCollapsed(false);\n panel.classList.add(`${PREFIX}-advanced`);\n // Advanced view is a CSS-driven layout. A leftover inline drag position\n // (left/top/right:auto) would OVERRIDE that CSS, so drop the floating\n // position here; closeAdvanced restores it. Advanced always opens\n // edge-to-edge (there is no separate fullscreen toggle anymore).\n toCorner();\n setAdvancedFull(true);\n restoreChatWidth();\n // Advanced owns the full screen and IS the wide view, so the \"wide\" button\n // is redundant here (hidden via CSS). Drop the plain expanded size + reset\n // the button so it shows the right icon/label when advanced is exited.\n if (expanded) setExpanded(false);\n buildFrame();\n // A fresh advanced session: the user has not driven anything yet, and any\n // operation that auto-navigated in a PREVIOUS session may do so again.\n frameUserDriven = false;\n autoNavigated.clear();\n navigateFrame(opts.getAdvancedUrl());\n updateAdvancedBtn();\n rememberAdvanced();\n scrollDown(true);\n };\n\n const closeAdvanced = (): void => {\n if (!advanced) return;\n advanced = false;\n if (advancedFull) setAdvancedFull(false);\n panel.classList.remove(`${PREFIX}-advanced`, `${PREFIX}-pane-collapsed`);\n // Restore the floating drag position cleared on enter.\n applyPos(readPos());\n teardownFrame();\n updateAdvancedBtn();\n rememberAdvanced();\n scrollDown(true);\n };\n\n if (advancedBtn) {\n advancedBtn.addEventListener(\"click\", () =>\n advanced ? closeAdvanced() : openAdvanced()\n );\n }\n\n /**\n * The single funnel for every in-app action Copilot requests. In advanced view\n * it retargets: on-page control/operate \u2192 the iframe (via the bridge); a\n * navigation-class action \u2192 the iframe's URL. Otherwise (and for any\n * app-specific handlers) it falls back to the host's `onWidgetAction`.\n */\n const dispatchAction = async (\n name: string,\n data: Record<string, string>\n ): Promise<string | void> => {\n // Every navigation \u2014 frame or host router \u2014 goes through the one gate.\n // It THROWS on refusal, which is what makes the chip go red instead of\n // ticking green over a path we declined.\n if (name === \"navigate\")\n await gateNavigationTarget(\n data.path,\n () => knownNavTargets,\n refreshNavTargets\n );\n if (advanced && transport) {\n if (isUiControlAction(name) || isOperateAction(name)) {\n const r = await transport.act(name as BridgeAction, {\n target: data.target ?? \"\",\n ...(data.value !== undefined ? { value: data.value } : {}),\n });\n return r.ok\n ? L(\"actionShownOnPage\")\n : r.message || L(\"actionPageFailed\");\n }\n const candidate =\n name === \"navigate\"\n ? (data.path ?? null)\n : (opts.resolveActionPath?.(name, data) ?? null);\n // Only root-relative in-app routes may be handed to `getAdvancedUrl`.\n const relPath = isSafeRelPath(candidate) ? candidate : null;\n if (relPath != null && opts.getAdvancedUrl) {\n navigateFrame(opts.getAdvancedUrl(relPath));\n return L(\"actionOpened\");\n }\n }\n return opts.onWidgetAction ? opts.onWidgetAction(name, data) : undefined;\n };\n\n // History (past conversations) \u2014 fetch the thread list, show a picker, and on\n // select replay that thread's messages into the log (sets it as the active\n // thread so the next turn continues it).\n if (historyBtn && opts.listThreads) {\n historyBtn.addEventListener(\"click\", () => void openHistory());\n }\n\n // New chat \u2014 reset to a fresh thread + greeting, then focus the composer.\n newChatBtn.addEventListener(\"click\", () => {\n startNewChat();\n input.focus();\n });\n\n // Page-aware shortcut chips \u2014 rendered only while the conversation is empty\n // (no user turn yet). The host returns suggestions for the current path, so\n // the chips change as the user moves around the app. A click sends the chip.\n let suggestToken = 0;\n async function renderSuggestions(): Promise<void> {\n const empty = !busy && !history.some((m) => m.role === \"user\");\n if (!opts.getSuggestions || !empty) {\n suggestionsEl.style.display = \"none\";\n suggestionsEl.innerHTML = \"\";\n return;\n }\n const my = ++suggestToken;\n let items: string[] = [];\n try {\n items = (await opts.getSuggestions()) ?? [];\n } catch {\n items = [];\n }\n // Superseded by a newer call (navigated again) or conversation started.\n if (my !== suggestToken) return;\n const stillEmpty = !busy && !history.some((m) => m.role === \"user\");\n items = items.filter((s) => s && s.trim()).slice(0, 4);\n suggestionsEl.innerHTML = \"\";\n if (!items.length || !stillEmpty) {\n suggestionsEl.style.display = \"none\";\n return;\n }\n for (const text of items) {\n const chip = el(\"button\", `${PREFIX}-suggestion`) as HTMLButtonElement;\n chip.type = \"button\";\n chip.textContent = text;\n chip.addEventListener(\"click\", () => {\n suggestionsEl.style.display = \"none\";\n suggestionsEl.innerHTML = \"\";\n void send(text);\n });\n suggestionsEl.appendChild(chip);\n }\n suggestionsEl.style.display = \"flex\";\n scrollDown(true);\n }\n\n /** Append a \"sign up to continue\" call-to-action when the free allowance is\n * spent (only when the host provided a signupUrl). */\n function showSignupCta(): void {\n if (!opts.signupUrl) return;\n const wrap = el(\"div\", `${PREFIX}-cta`);\n const a = document.createElement(\"a\");\n a.className = `${PREFIX}-cta-btn`;\n a.href = opts.signupUrl;\n a.textContent = L(\"signupCta\");\n wrap.appendChild(a);\n log.appendChild(wrap);\n scrollDown(true);\n }\n /** Export the current conversation as a plain-text .txt download (client-side;\n * reads the in-memory transcript). Data widgets aren't textual, so the\n * transcript is the messages \u2014 same as the full-page assistant's export. */\n function exportConversation(): void {\n const msgs = history.filter((m) => m.content.trim().length > 0);\n if (msgs.length === 0) return;\n const body = msgs.map(\n (m) => `${m.role === \"user\" ? \"You\" : name}:\\n${m.content}\\n`\n );\n // BOM so editors read the UTF-8 (incl. Turkish characters) correctly.\n const text =\n \"\uFEFF\" + [`${name} \u2014 conversation`, \"=\".repeat(40), \"\", ...body].join(\"\\n\");\n const blob = new Blob([text], { type: \"text/plain;charset=utf-8\" });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = \"copilot-conversation.txt\";\n document.body.appendChild(a);\n a.click();\n a.remove();\n URL.revokeObjectURL(url);\n }\n async function openHistory(): Promise<void> {\n if (!opts.listThreads) return;\n const overlay = el(\"div\", `${PREFIX}-history`);\n const head = el(\"div\", `${PREFIX}-history-head`);\n head.textContent = L(\"pastConversations\");\n const back = el(\"button\", `${PREFIX}-history-back`);\n back.textContent = L(\"close\");\n back.addEventListener(\"click\", () => overlay.remove());\n head.appendChild(back);\n const listEl = el(\"div\", `${PREFIX}-history-list`);\n listEl.textContent = L(\"loading\");\n // Same defensive wheel handling as the log, so the past-conversations list\n // scrolls even on hosts that globally intercept wheel events.\n attachWheelScroll(listEl);\n overlay.append(head, listEl);\n panel.appendChild(overlay);\n try {\n const threads = await opts.listThreads();\n listEl.innerHTML = \"\";\n if (!threads.length) {\n listEl.textContent = L(\"noConversations\");\n return;\n }\n let lastBucket = \"\";\n for (const th of threads.slice(0, 50)) {\n // Relative-time group separators (Today / Yesterday / This month / \u2026).\n if (th.updatedAt) {\n const bucket = relBucket(th.updatedAt);\n if (bucket !== lastBucket) {\n lastBucket = bucket;\n const sep = el(\"div\", `${PREFIX}-history-sep`);\n sep.textContent = L(bucket);\n listEl.appendChild(sep);\n }\n }\n const item = el(\"button\", `${PREFIX}-history-item`);\n const ti = el(\"span\", `${PREFIX}-history-title`);\n ti.textContent = th.title || L(\"untitledConversation\");\n item.appendChild(ti);\n // A response is in flight for this conversation \u2014 show it working.\n if (isThreadPending(th.id)) {\n const spin = el(\"span\", `${PREFIX}-act-spin`);\n spin.title = L(\"threadAnswering\");\n spin.setAttribute(\"aria-label\", L(\"threadAnswering\"));\n item.appendChild(spin);\n }\n if (th.updatedAt) {\n const dt = el(\"span\", `${PREFIX}-history-date`);\n dt.textContent = relTime(th.updatedAt);\n item.appendChild(dt);\n }\n // Shared star toggle \u2014 team-visible bookmark on the conversation.\n if (opts.starThread) {\n const star = el(\"span\", `${PREFIX}-history-star`);\n let on = Boolean(th.starred);\n // The list is the only place star state is READ, so it is also where\n // the menu row learns it.\n starredThreads.set(th.id, on);\n const paint = () => {\n star.textContent = on ? \"\u2605\" : \"\u2606\";\n star.style.color = on ? \"#f59e0b\" : \"\";\n star.title = on ? L(\"unstar\") : L(\"star\");\n };\n paint();\n star.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const prev = on;\n on = !on;\n paint(); // optimistic\n opts.starThread!(th.id).then(\n (r) => {\n on = r.starred;\n starredThreads.set(th.id, on);\n paintStarItem();\n paint();\n },\n () => {\n on = prev;\n paint();\n }\n );\n });\n item.appendChild(star);\n }\n item.addEventListener(\n \"click\",\n () => void loadPastThread(th.id, overlay)\n );\n listEl.appendChild(item);\n }\n } catch {\n listEl.textContent = L(\"historyLoadFailed\");\n }\n }\n async function loadPastThread(\n id: string,\n /** The History overlay when opened from the list; absent when a HOST\n * surface re-enters a conversation directly, which has no overlay to\n * close and no list to write errors to. */\n overlay?: HTMLElement\n ): Promise<void> {\n if (!opts.loadThread) return;\n try {\n const items = await opts.loadThread(id);\n viewGen++; // fence: an in-flight turn from the previous view goes silent\n // Replace the visible conversation with the chosen thread (messages +\n // inline data widgets). renderThreadItems clears rich roots + the log.\n renderThreadItems(items, id);\n threadId = id;\n paintStarItem();\n syncBusy();\n saveState();\n void refreshArtifacts();\n overlay?.remove();\n } catch {\n if (overlay) {\n overlay.querySelector(`.${PREFIX}-history-list`)!.textContent =\n L(\"threadOpenFailed\");\n } else {\n // No overlay to report into \u2014 say it in the conversation itself.\n addActivityChip(L(\"historyLoadFailed\"), \"error\");\n }\n }\n }\n\n // Let a nav/sidebar link anywhere in the host app open the panel \u2014 optionally\n // PREFILLED via `new CustomEvent(name, { detail: { prompt } })`.\n const onOpenEvent = (e: Event): void => {\n const detail = (\n e as CustomEvent<{\n prompt?: string;\n newChat?: boolean;\n advanced?: boolean;\n /** Re-enter an EXISTING conversation \u2014 a host surface lands the\n * client back in the thread it came from. Wins over prompt/newChat:\n * the point is the conversation that already exists. */\n threadId?: string;\n }>\n ).detail;\n if (detail?.threadId) {\n open();\n void loadPastThread(detail.threadId);\n if (detail?.advanced) openAdvanced();\n return;\n }\n open(detail?.prompt, detail?.newChat);\n // Let a host entry point (e.g. the AI Hub \"Open assistant\" button) jump\n // straight into advanced view.\n if (detail?.advanced) openAdvanced();\n };\n if (opts.openEventName) {\n window.addEventListener(opts.openEventName, onOpenEvent);\n }\n\n // Initial page-aware shortcuts. Done HERE (not at greeting time) so every\n // const it reads \u2014 suggestionsEl, busy, send \u2014 is already initialized; calling\n // it earlier hits the temporal dead zone (Cannot access before init).\n void renderSuggestions();\n\n // Restore the open/closed state across reloads \u2014 a user who left the assistant\n // open finds it open again (done after open() + bindKeyboard are defined).\n //\n // ADVANCED VIEW RESTORES TOO, and did not for its whole life: every toggle\n // called `rememberAdvanced()` and wrote `ayca:adv:<persistKey>`, but nothing\n // ever read that key back \u2014 `advanced` was initialised `false` and stayed\n // false. A write with no reader, so the flag was perfectly maintained and\n // never consulted. Anyone working in the split view lost it on every refresh\n // and landed back in the small floating widget: the transcript and the thread\n // came back (those keys ARE read), the working surface did not, which reads\n // as \"the chat didn't continue\".\n //\n // Order matters \u2014 advanced is a layout of the OPEN panel, so it can only be\n // entered after open(). openAdvanced() no-ops without `getAdvancedUrl`, so a\n // host that doesn't offer the view is unaffected.\n if (readFlag(openStateKey)) {\n open();\n if (readFlag(advKey)) openAdvanced();\n }\n\n form.addEventListener(\"submit\", (e) => {\n e.preventDefault();\n const content = input.value.trim();\n // A turn needs text OR at least one staged attachment.\n if ((!content && stagedAtts.length === 0) || busy) return;\n input.value = \"\";\n saveDraft(\"\"); // sent \u2014 drop the persisted draft\n void send(content);\n });\n\n /** Parse + render any inline directives in a finished reply, returning the\n * text with them stripped. [[widget]]/[[navigate]]/[[action]]/[[form]]/lead. */\n function renderDirectives(text: string): string {\n let t = text;\n // Custom renderers first (the open extension point) \u2014 each registered tag\n // gets its own host card in the log; a throwing renderer removes its host\n // instead of wedging the message.\n for (const [tag, renderer] of Object.entries(customRenderers)) {\n for (let i = 0; i < 4; i++) {\n const d = parseJsonDirective<unknown>(t, tag);\n if (!d) break;\n t = d.stripped;\n const host = el(\"div\", `${PREFIX}-widget ${PREFIX}-custom`);\n log.appendChild(host);\n try {\n const dispose = renderer(host, d.spec);\n if (dispose) richDisposers.push(dispose);\n } catch {\n host.remove();\n }\n scrollDown(true);\n }\n }\n for (let i = 0; i < 6; i++) {\n const w = parseJsonDirective<WidgetSpec>(t, \"widget\");\n if (!w) break;\n t = w.stripped;\n renderWidget(w.spec);\n }\n // Composed UI cards \u2014 the assistant's own layout, drawn from primitives.\n for (let i = 0; i < 4; i++) {\n const u = parseJsonDirective<unknown>(t, \"ui\");\n if (!u) break;\n t = u.stripped;\n renderUiCard(u.spec);\n }\n // Dynamic HTML previews \u2014 the AI draws a mockup/preview and we paint it in a\n // FULLY sandboxed iframe (no scripts, no same-origin) right in the chat.\n for (let i = 0; i < 3; i++) {\n const p = parseJsonDirective<PreviewSpec>(t, \"preview\");\n if (!p || typeof p.spec.html !== \"string\") break;\n t = p.stripped;\n renderPreview(p.spec);\n }\n // STRIP FIRST, RENDER SECOND \u2014 and strip even when nothing can render it.\n //\n // These two used to be gated on `opts.onWidgetAction`, so a host that wires\n // no actions never reached the strip and the raw `[[navigate:{...}]]` was\n // printed to the reader as text. Seen live on the public demo, whose whole\n // job is to impress a prospect. The comment below already said \"strip the\n // directive either way\"; the condition above it prevented exactly that.\n //\n // The model emits these because the system prompt offers them, which no\n // host can switch off \u2014 so \"no handler\" is a normal state, not a misuse.\n const nav = parseJsonDirective<NavigateSpec>(t, \"navigate\");\n if (nav) {\n t = nav.stripped;\n // Only offer/auto-follow a target that is a plain root-relative in-app\n // route, and only when a host can actually act on it.\n if (opts.onWidgetAction && nav.spec.path && isSafeRelPath(nav.spec.path))\n renderNavigate(nav.spec);\n }\n for (let i = 0; i < 4; i++) {\n const act = parseJsonDirective<ActionSpec>(t, \"action\");\n if (!act) break;\n t = act.stripped;\n if (opts.onWidgetAction && act.spec.name) renderAction(act.spec);\n }\n // Quick-reply chips \u2014 tappable answer options for a choice question.\n const chips = parseJsonDirective<ChipsSpec>(t, \"chips\");\n if (chips && Array.isArray(chips.spec.options)) {\n t = chips.stripped;\n renderChips(chips.spec);\n }\n const form = parseFormDirective(t);\n if (form) {\n // Same rule: the markup never survives to the reader, with or without a\n // host that can submit it.\n t = form.stripped;\n if (opts.onWidgetAction || opts.onLead) renderForm(form.spec);\n } else if (t.includes(LEAD_TOKEN)) {\n t = t.replace(LEAD_TOKEN, \"\").trim();\n if (!(opts.onLead || opts.onWidgetAction)) return linkifyProseNav(t);\n renderForm({\n action: \"lead\",\n fields: [\n {\n name: \"email\",\n type: \"email\",\n placeholder: \"you@company.com\",\n required: true,\n },\n ],\n submit: L(\"send\"),\n });\n }\n t = linkifyProseNav(t);\n return t;\n }\n\n /**\n * #111 fallback \u2014 turn a model's PROSE navigation into a real chip.\n *\n * The prompt tells the model to emit [[navigate:{\u2026}]] for in-app destinations,\n * but some lanes (notably the codebase-agent, whose coding framing deprioritises\n * the directive syntax) still narrate \"\u2197 Open Products & apps\" as a plain bullet\n * \u2014 which renders as dead text the user can't click. This is the deterministic\n * safety net: after the real directives are parsed, if a SHORT standalone line\n * names a page from THIS turn's \"Pages you can open\" catalog, drop the prose and\n * render the same nav chip renderNavigate would have. Lane- and model-agnostic:\n * it fixes the symptom no matter which model produced the prose.\n *\n * Deliberately conservative to avoid a wrong chip: only navigable targets (a\n * path, no named action), only lines \u2264 60 chars whose text \u2014 after stripping a\n * leading bullet/arrow and an open-verb \u2014 EQUALS a known page title, at most 2\n * per reply.\n */\n function linkifyProseNav(text: string): string {\n if (!opts.onWidgetAction || !knownNavTargets.length) return text;\n const targets = knownNavTargets.filter(\n (n): n is { path: string; title: string } =>\n typeof n.path === \"string\" &&\n !!n.path &&\n typeof n.title === \"string\" &&\n !!n.title &&\n !n.action\n );\n if (!targets.length) return text;\n // Leading bullet/arrow glyphs + an optional open-verb (en + tr).\n const lead =\n /^[\\s>*+\\-\u2022\u00B7\u2013\u2014\u2197\u2192\u25B6\u25BA]*\\s*(?:open|go to|goto|visit|view|see|a\u00E7|git|g\u00F6r\u00FCnt\u00FCle)?\\s+/i;\n const kept: string[] = [];\n let chips = 0;\n for (const line of text.split(\"\\n\")) {\n const raw = line.trim();\n if (chips < 2 && raw && raw.length <= 60) {\n const bare = raw\n .replace(lead, \"\")\n .replace(/\\s+(?:page|sayfas\u0131|sayfas\u0131na)$/i, \"\")\n .replace(/[.:\uFF1B;\u2192\u2197\\s]+$/u, \"\")\n .trim()\n .toLowerCase();\n const hit = targets.find((n) => {\n const title = n.title.toLowerCase();\n return bare === title || bare === `the ${title}`;\n });\n if (hit) {\n renderNavigate({ path: hit.path, label: hit.title });\n chips += 1;\n continue; // drop the now-redundant prose line\n }\n }\n kept.push(line);\n }\n return kept.join(\"\\n\");\n }\n\n // Confirm-card copy for write-tool proposals lives in ./proposal-summary\n // (#320). Pure text formatting, so it is testable without a widget.\n // Host-supplied, with the generic renderer as the floor. `|| ` not `?? ` on\n // purpose: a host returning \"\" means \"nothing worth saying\", and an empty\n // card would be a confirm gate that confirms nothing.\n const PROPOSAL_LABELS: Record<string, string> = opts.proposalTitles ?? {};\n const proposalSummary = (\n name: string,\n args: Record<string, unknown>\n ): string =>\n opts.proposalSummary?.(name, args) || genericProposalSummary(args);\n /**\n * Render a `question` frame \u2014 the assistant asking the human to DECIDE.\n *\n * Distinct from a proposal card: a proposal asks permission for an action the\n * model already chose, a question asks for something the model cannot decide\n * itself. Answering resolves it; the card then collapses to the chosen answer\n * so the transcript reads as a conversation rather than a dead form.\n *\n * Options render as buttons and free-text as an input, because the SAME\n * question may also be answered from Telegram, where buttons work and prose\n * does not. Keeping both shapes here means one question definition serves\n * every surface.\n */\n function renderQuestion(q: {\n questionId: string;\n prompt: string;\n context?: string;\n options?: Array<{ id: string; label: string; description?: string }>;\n multi?: boolean;\n critical?: boolean;\n }): void {\n const wrap = el(\"div\", `${PREFIX}-question`);\n if (q.critical) wrap.classList.add(`${PREFIX}-question-critical`);\n\n const title = el(\"div\", `${PREFIX}-question-title`);\n title.textContent = q.prompt;\n wrap.appendChild(title);\n\n if (q.context) {\n const ctx = el(\"div\", `${PREFIX}-question-ctx`);\n ctx.textContent = q.context;\n wrap.appendChild(ctx);\n }\n\n const answered = (summary: string): void => {\n // Collapse to what was decided. Leaving live controls after an answer\n // invites a second, conflicting reply to a question already resolved.\n wrap.innerHTML = \"\";\n wrap.classList.add(`${PREFIX}-question-done`);\n const done = el(\"div\", `${PREFIX}-question-answer`);\n done.textContent = summary;\n wrap.appendChild(done);\n };\n\n // Shown in place of collapsing the card when the answer could NOT leave the\n // browser. Collapsing anyway would tell the user their choice was accepted\n // while the assistant never heard it.\n const undelivered = el(\"div\", `${PREFIX}-question-err`);\n undelivered.textContent = L(\"questionSendFailed\");\n undelivered.style.display = \"none\";\n\n /**\n * Deliver the answer as an ordinary user message.\n *\n * The ask_user turn ENDED server-side the moment the question was emitted\n * (see runAgentTurn) \u2014 there is no mid-turn channel to reply on, and \"the\n * answer arrives as the next message\" is the design. `onAnswer` is only an\n * observer hook, so it can never be what makes the answer land.\n */\n const deliver = (optionIds: string[], text?: string): void => {\n const chosen = optionIds\n .map((id) => q.options?.find((o) => o.id === id)?.label ?? id)\n .filter(Boolean);\n const answer = chosen.length ? chosen.join(\", \") : (text ?? \"\").trim();\n if (!answer) return;\n // A turn is already streaming \u2014 sending now would be dropped by the\n // composer guard, so say so and leave the controls live to retry.\n if (busy) {\n undelivered.style.display = \"\";\n return;\n }\n opts.onAnswer?.({ questionId: q.questionId, optionIds, text });\n void send(answer);\n answered(answer);\n };\n\n if (q.options?.length) {\n const list = el(\"div\", `${PREFIX}-question-opts`);\n const picked = new Set<string>();\n // Built before the options so toggling one can enable it. A multi-select\n // confirm with nothing picked has nothing to send: leaving it enabled made\n // the click a silent no-op (`deliver` bails on an empty answer), which\n // reads as a broken button. The React panel disables it; so does this.\n const confirm = q.multi\n ? (el(\"button\", `${PREFIX}-question-send`) as HTMLButtonElement)\n : null;\n if (confirm) {\n confirm.type = \"button\";\n confirm.textContent = L(\"questionConfirm\");\n confirm.disabled = true;\n confirm.addEventListener(\"click\", () => deliver([...picked]));\n }\n for (const o of q.options) {\n const b = el(\"button\", `${PREFIX}-question-opt`) as HTMLButtonElement;\n b.type = \"button\";\n const lab = el(\"span\", `${PREFIX}-question-opt-label`);\n lab.textContent = o.label;\n b.appendChild(lab);\n if (o.description) {\n const d = el(\"span\", `${PREFIX}-question-opt-desc`);\n d.textContent = o.description;\n b.appendChild(d);\n }\n b.addEventListener(\"click\", () => {\n if (!q.multi) {\n deliver([o.id]);\n return;\n }\n // Multi-select: toggle, and confirm explicitly \u2014 otherwise the first\n // click would submit and the user could never pick a second option.\n if (picked.has(o.id)) picked.delete(o.id);\n else picked.add(o.id);\n b.classList.toggle(`${PREFIX}-question-opt-on`, picked.has(o.id));\n if (confirm) confirm.disabled = picked.size === 0;\n });\n list.appendChild(b);\n }\n wrap.appendChild(list);\n if (confirm) wrap.appendChild(confirm);\n } else {\n // Free text.\n const row = el(\"div\", `${PREFIX}-question-free`);\n const input = el(\"input\", `${PREFIX}-question-input`) as HTMLInputElement;\n input.type = \"text\";\n input.placeholder = L(\"questionPlaceholder\");\n const go = el(\"button\", `${PREFIX}-question-send`) as HTMLButtonElement;\n go.type = \"button\";\n go.textContent = L(\"questionConfirm\");\n const submit = (): void => {\n const v = input.value.trim();\n if (v) deliver([], v);\n };\n go.addEventListener(\"click\", submit);\n input.addEventListener(\"keydown\", (ev) => {\n if ((ev as KeyboardEvent).key === \"Enter\") submit();\n });\n row.appendChild(input);\n row.appendChild(go);\n wrap.appendChild(row);\n }\n\n wrap.appendChild(undelivered);\n log.appendChild(wrap);\n scrollDown(true);\n }\n\n function renderProposal(\n name: string,\n args: Record<string, unknown>,\n agent?: string,\n /** Inputs the ASSISTANT asked for on this card \u2014 see ProposalField. */\n fields: ProposalField[] = [],\n /** The account this write targets, per the worker. */\n proposalAccountId?: string,\n /** The persisted artifact a dashboard/template apply needs. */\n proposalArtifactId?: string\n ): void {\n // `-pending` marks a card that is still AWAITING the user, and it is what\n // the end-of-turn reload checks. The reload used to look for `-proposal`,\n // which also matches an already-applied card (the success path empties the\n // node but keeps the class) \u2014 so one Apply disabled the reload for the\n // whole session and with it every edit/regenerate/branch control.\n const wrap = el(\"div\", `${PREFIX}-proposal ${PREFIX}-proposal-pending`);\n const title = el(\"div\", `${PREFIX}-proposal-title`);\n /**\n * Args the user may correct before applying \u2014 see EDITABLE_ARGS.\n *\n * Each entry keeps the field's TYPE and its node, not just a reader: the\n * apply path has to post a boolean as a boolean (not the string \"false\"),\n * and has to be able to point at the control it is refusing to send without.\n */\n const edits: Array<{\n arg: string;\n type: string;\n required: boolean;\n read: () => string;\n node: HTMLElement;\n }> = [];\n // Show the acting agent (e.g. Vega) so the user sees WHO proposed this.\n if (agent) {\n const badge = el(\"span\", `${PREFIX}-act-agent`);\n badge.textContent = agent;\n badge.style.marginRight = \"6px\";\n title.appendChild(badge);\n }\n title.appendChild(\n document.createTextNode(PROPOSAL_LABELS[name] ?? L(\"proposalGeneric\"))\n );\n wrap.appendChild(title);\n let disposePreview: (() => void) | undefined;\n // Scraped-media import \u2014 PREVIEW the actual image/video (loaded from its\n // source URL, NOT saved) before the user clicks Apply.\n if (name === \"add_scraped_media\") {\n const src = typeof args.url === \"string\" ? args.url : \"\";\n if (src && args.type === \"video\") {\n const video = el(\n \"video\",\n `${PREFIX}-proposal-media`\n ) as HTMLVideoElement;\n video.src = src;\n video.controls = true;\n video.preload = \"metadata\";\n video.playsInline = true;\n if (typeof args.poster === \"string\" && args.poster)\n video.poster = String(args.poster);\n wrap.appendChild(video);\n } else if (src) {\n const img = el(\"img\", `${PREFIX}-proposal-media`) as HTMLImageElement;\n img.src = src;\n img.loading = \"lazy\";\n img.alt =\n typeof args.alt === \"string\" && args.alt\n ? String(args.alt)\n : \"Web image preview\";\n wrap.appendChild(img);\n }\n }\n // Editable args: the assistant's suggestion is a STARTING POINT, not a\n // decision the user has to accept whole. Importing a site into a folder\n // called \"Website\" and then renaming it afterwards is a worse experience\n // than being handed the name with a cursor in it.\n for (const field of fields) {\n const row = el(\"label\", `${PREFIX}-proposal-edit`);\n const built = buildField({\n name: field.arg,\n ...(field.type ? { type: field.type } : {}),\n ...(field.label ? { label: field.label } : {}),\n ...(field.options ? { options: field.options } : {}),\n ...(field.required ? { required: field.required } : {}),\n // Prefilled with what the assistant proposed: the user confirms or\n // corrects, rather than typing from nothing. Any PRIMITIVE counts \u2014\n // reading only strings meant `withReadme: true` drew an UNTICKED box and\n // `maxPages: 20` drew an empty number field, so the card contradicted\n // the very proposal it was there to confirm.\n value: isPrimitiveArg(args[field.arg]) ? String(args[field.arg]) : \"\",\n ...(field.placeholder ? { placeholder: field.placeholder } : {}),\n });\n // Caption first \u2014 unless the control already draws one (a checkbox), in\n // which case the card adding its own would say the same thing twice.\n if (field.label && !built.selfLabelled) {\n const cap = el(\"span\", `${PREFIX}-proposal-edit-label`);\n cap.textContent = field.label;\n row.appendChild(cap);\n }\n row.appendChild(built.node);\n wrap.appendChild(row);\n edits.push({\n arg: field.arg,\n type: field.type ?? \"text\",\n required: Boolean(field.required),\n read: built.read,\n node: built.node,\n });\n }\n const summary = proposalSummary(name, args);\n if (summary) {\n const s = el(\"div\", `${PREFIX}-proposal-summary`);\n s.textContent = summary;\n wrap.appendChild(s);\n }\n // Shown when Apply is REFUSED because a field the assistant marked required\n // was left blank. Applying anyway (which is what dropping the empty value\n // did) sent an incomplete write; doing nothing at all reads as a dead\n // button. Say which way it went.\n const editErr = el(\"div\", `${PREFIX}-proposal-err`);\n editErr.textContent = L(\"requiredFields\");\n editErr.style.display = \"none\";\n if (edits.length) wrap.appendChild(editErr);\n const row = el(\"div\", `${PREFIX}-confirm-row`);\n const apply = el(\"button\", `${PREFIX}-nav-btn`) as HTMLButtonElement;\n apply.type = \"button\";\n apply.textContent = L(\"apply\");\n const cancel = el(\"button\", `${PREFIX}-confirm-no`) as HTMLButtonElement;\n cancel.type = \"button\";\n cancel.textContent = L(\"dismiss\");\n cancel.addEventListener(\"click\", () => {\n disposePreview?.();\n wrap.remove();\n });\n apply.addEventListener(\"click\", async () => {\n // Collect the edits BEFORE anything is disabled or dispatched: a required\n // field left blank has to stop the apply, and a blocked card must stay\n // usable so the user can fill it in and click again.\n // What the user typed WINS over what the assistant proposed \u2014 that is the\n // entire point of showing them the field. An emptied OPTIONAL field means\n // \"you choose\", so it is dropped rather than sent as \"\".\n const edited: Record<string, unknown> = { ...args };\n const missing: HTMLElement[] = [];\n for (const f of edits) {\n f.node.classList.remove(`${PREFIX}-field-invalid`);\n const raw = f.read();\n // Post the type the ARG actually is. Every control reads back a string,\n // and the string \"false\" is TRUTHY server-side (`withReadme !== false`\n // passes), so an unticked box used to change nothing at all.\n if (f.type === \"checkbox\") {\n const on = isTruthyValue(raw);\n if (f.required && !on) {\n missing.push(f.node);\n continue;\n }\n edited[f.arg] = on;\n continue;\n }\n if (!raw) {\n if (f.required) {\n missing.push(f.node);\n continue;\n }\n delete edited[f.arg];\n continue;\n }\n if (f.type === \"number\") {\n const n = Number(raw);\n // Non-numeric text in a number field is the user's answer, not a\n // NaN to post \u2014 hand it over as typed and let the API judge it.\n edited[f.arg] = Number.isFinite(n) ? n : raw;\n continue;\n }\n edited[f.arg] = raw;\n }\n if (missing.length) {\n for (const n of missing) n.classList.add(`${PREFIX}-field-invalid`);\n editErr.style.display = \"\";\n return;\n }\n editErr.style.display = \"none\";\n apply.disabled = true;\n apply.textContent = L(\"applying\");\n try {\n // Carry the originating thread on EVERY apply \u2014 session-scoped asset\n // writes (scraped imports) use it to keep chat debris out of the\n // library until explicitly saved. Tools that don't care simply\n // ignore the extra key.\n const res = await opts.onApplyProposal!(\n name,\n threadId ? { ...edited, threadId } : edited,\n // `artifactId` travels beside the account: the dashboard/template\n // applies are the two that cannot be reconstructed from `args` alone.\n proposalAccountId || proposalArtifactId\n ? {\n ...(proposalAccountId ? { accountId: proposalAccountId } : {}),\n ...(proposalArtifactId\n ? { artifactId: proposalArtifactId }\n : {}),\n }\n : undefined\n );\n const msg = typeof res === \"string\" ? res : res?.message;\n // An apply that ENQUEUED answers with a job id. Keeping it is the whole\n // difference between \"I'll notify you when it's ready \u2713\" followed by\n // minutes of silence, and a card that shows the work happening.\n const jobId = res && typeof res === \"object\" ? res.jobId : undefined;\n disposePreview?.();\n // Resolved: it no longer blocks the end-of-turn reload.\n wrap.classList.remove(`${PREFIX}-proposal-pending`);\n wrap.innerHTML = \"\";\n const ok = el(\"div\", `${PREFIX}-proposal-ok`);\n ok.innerHTML =\n `<span class=\"${PREFIX}-act-ok\" aria-hidden=\"true\">\u2713</span>` +\n `<span>${escapeHtml(stripTick(msg || L(\"applied\")))}</span>`;\n wrap.appendChild(ok);\n // A REPORT is a document, not just a task: once it exists it has a\n // page, and the useful thing to hand the user is a way in.\n const reportId =\n res && typeof res === \"object\" ? res.reportId : undefined;\n // TRACK THE REPORT, NOT ITS JOB ROW. Both ids come back today, but the\n // generic row is being retired \u2014 a card polling it would go blind the\n // moment the contract migration lands, and it would go blind SILENTLY,\n // stuck on \"Queued\" forever rather than erroring. Prefer the id that\n // will still exist; fall back to the job id for work that has no\n // report (an import, a render).\n if (reportId) trackConfirmedJob({ kind: \"report\", id: reportId });\n else if (jobId) trackConfirmedJob({ kind: \"job\", id: jobId });\n const href = reportId ? opts.reportHref?.(reportId) : undefined;\n if (href) {\n const open = el(\"a\", `${PREFIX}-proposal-link`) as HTMLAnchorElement;\n open.href = href;\n open.textContent = L(\"openReport\");\n ok.appendChild(open);\n }\n } catch {\n apply.disabled = false;\n apply.textContent = L(\"tryAgain\");\n }\n });\n row.append(apply, cancel);\n wrap.appendChild(row);\n log.appendChild(wrap);\n scrollDown(true);\n }\n\n /**\n * Stream-loss recovery. The api runs a turn to completion and persists the\n * transcript even when the browser's stream dies (QUIC/proxy drops \u2014 seen\n * live: the reply landed 8 minutes after the client lost the connection).\n * So a transport failure is NOT a failed turn: keep the thread marked\n * \"answering\u2026\" (which also blocks double-sending into it) and poll until\n * THIS turn's exchange lands, then show it. The transcript is persisted at\n * turn END (the user message and the reply arrive together), so the signal\n * is \"the last user message is the one we sent, followed by an assistant\n * reply\" \u2014 merely seeing a trailing assistant message would false-positive\n * on the PREVIOUS exchange in an existing thread. `sentContent` is null for\n * a regenerate (no new user message; any trailing reply counts). Bounded at\n * 15 minutes; on timeout the thread simply unlocks and the reply \u2014 if any \u2014\n * shows on its next open.\n */\n async function recoverLostTurn(\n turnThread: string,\n sentContent: string | null\n ): Promise<void> {\n if (!opts.loadThread) return;\n pendingThreads.add(turnThread);\n syncBusy();\n try {\n for (let i = 0; i < 90 && !alive.signal.aborted; i++) {\n await new Promise((r) => setTimeout(r, 10_000));\n if (alive.signal.aborted) return;\n let items: LoadedThreadItem[];\n try {\n items = await opts.loadThread(turnThread);\n } catch {\n continue; // transient \u2014 the same outage that killed the stream\n }\n const msgs = items.filter(\n (it): it is Extract<LoadedThreadItem, { role: string }> =>\n \"role\" in it\n );\n const last = msgs[msgs.length - 1];\n if (!last || last.role !== \"assistant\") continue;\n if (sentContent !== null) {\n const lastUser = [...msgs].reverse().find((m) => m.role === \"user\");\n if (!lastUser || lastUser.content.trim() !== sentContent.trim())\n continue; // still the PREVIOUS exchange \u2014 our turn hasn't landed\n }\n pendingThreads.delete(turnThread);\n syncBusy();\n if (threadId === turnThread) {\n renderThreadItems(items, turnThread);\n maybeDing();\n scrollDown();\n saveState();\n }\n return;\n }\n } finally {\n pendingThreads.delete(turnThread);\n syncBusy();\n }\n }\n\n async function send(\n content: string,\n fork?: { parentId?: string | null; regenerate?: boolean }\n ): Promise<void> {\n // A regenerate re-answers an existing user turn \u2014 it carries no new user\n // message, so it must NOT push a user bubble to the DOM / history.\n const isRegen = fork?.regenerate === true;\n // Turn fence: this turn renders only while the user is still on the view\n // it started from. `turnThread` is the thread the reply BELONGS to (fixed\n // once the server names it); `live()` gates every visible side effect.\n const myGen = viewGen;\n let turnThread: string | undefined = threadId;\n const live = (): boolean => viewGen === myGen && !alive.signal.aborted;\n pendingThreads.add(turnThread ?? NEW_TURN_KEY);\n syncBusy();\n if (!isRegen) lastUserContent = content;\n // Take + clear any staged attachments for THIS turn.\n const atts = stagedAtts.splice(0);\n renderStaged();\n // The conversation is starting \u2014 page shortcuts give way to the thread.\n suggestionsEl.style.display = \"none\";\n suggestionsEl.innerHTML = \"\";\n sendBtn.disabled = true;\n setRole(\"talk\"); // each turn starts as the conversational copilot\n if (!isRegen) {\n addMsg(log, \"user\", content, atts.length ? atts : undefined);\n history.push({\n role: \"user\",\n content,\n ...(atts.length ? { attachments: atts } : {}),\n });\n }\n saveState();\n // Animated typing indicator until the first token lands.\n const typing = el(\"div\", `${PREFIX}-typing`);\n typing.innerHTML = \"<span></span><span></span><span></span>\";\n log.appendChild(typing);\n scrollDown(true);\n let assistant: HTMLElement | null = null;\n let assistantRaw = \"\";\n let producedAny = false;\n /**\n * Write proposals seen this turn, rendered only AFTER the reply text.\n *\n * The model almost always emits a tool call BEFORE it writes the sentence\n * explaining it, so rendering each card where it arrives put \"Apply this\n * change?\" above the reasoning for it \u2014 the user was asked to approve\n * something before being told what it was. Buffering and flushing at the\n * end of the turn makes the order match how a person reads: the\n * explanation, then the decision.\n */\n const deferredProposals: Array<{\n name: string;\n args: Record<string, unknown>;\n agent?: string;\n fields: ProposalField[];\n /** The account the worker said this write is for. */\n accountId?: string;\n /** The artifact a dashboard/template apply needs \u2014 the runner emits it,\n * and until now nothing on this side carried it. */\n artifactId?: string;\n }> = [];\n let turnIn = 0;\n let turnOut = 0;\n let failure: string | null = null;\n // Stamped by the turn's `done` frame \u2014 the handle for rating the answer\n // just streamed, on surfaces where no canonical reload paints one (#299).\n let liveMessageId: string | undefined;\n let liveModel: string | undefined;\n // Wall-clock for the quality prompt's 20s trigger \u2014 comfortably past a\n // normal tool-using answer and far short of the 300s ceiling.\n const turnStartedAt = Date.now();\n let transportLost = false;\n // Chars of assistantRaw already painted into the streaming bubble. The\n // display can lag behind assistantRaw: directive bytes ([[tag:{json}]])\n // are withheld from view while they stream (see processStreamDisplay).\n let shownLen = 0;\n // Close the current text bubble and render its markdown, so the NEXT thing\n // (a widget/activity, or more text) lands AFTER it \u2014 keeping the reply in\n // true order instead of dumping widgets below all the text. `final` also\n // parses inline directives. Returns the rendered bubble (for the token tag).\n const flushSegment = (final: boolean): HTMLElement | null => {\n const bubble = assistant;\n // On a NON-final flush, bake only what the user has SEEN and carry the\n // withheld tail (a directive still streaming) into the next segment \u2014\n // baking half a [[widget:{\u2026}]] would show raw JSON AND orphan its tail.\n let raw = final ? assistantRaw : assistantRaw.slice(0, shownLen);\n assistantRaw = final ? \"\" : assistantRaw.slice(shownLen);\n shownLen = 0;\n if (!bubble) return null;\n assistant = null;\n bubble.classList.remove(`${PREFIX}-streaming`);\n if (!raw.trim()) {\n bubble.remove();\n return null;\n }\n if (final) raw = renderDirectives(raw);\n history.push({ role: \"assistant\", content: raw });\n // A reply that arrived while the panel was CLOSED is the one thing the\n // launcher has ever needed to say and could not (#305). Counted here, at\n // the moment a reply is recorded, rather than from a transcript diff \u2014\n // the diff would also count the replies the reader has already seen.\n if (panel.style.display === \"none\") {\n setLauncher({ state: \"unread\", count: launcher.count + 1 });\n }\n applyAssistantRich(bubble, raw, true);\n return bubble;\n };\n // ---- Directive-aware streaming display --------------------------------\n // Directives ([[tag:{json}]]) arrive INSIDE the text stream. They used to\n // scroll by as raw JSON and only become cards when the turn finished. Now\n // the stream painter WITHHOLDS directive bytes as they arrive, and purely\n // visual cards (widget / preview / custom tags) render the moment their\n // JSON completes \u2014 mid-reply, in true order. Side-effecting directives\n // (navigate / action / chips / form) stay hidden in assistantRaw for the\n // end-of-turn renderDirectives pass, unchanged.\n const streamDirectiveTag = (tag: string): \"now\" | \"later\" | null => {\n if (\n tag === \"widget\" ||\n tag === \"ui\" ||\n tag === \"preview\" ||\n customRenderers[tag]\n )\n return \"now\";\n if (\n tag === \"navigate\" ||\n tag === \"action\" ||\n tag === \"chips\" ||\n tag === \"form\"\n )\n return \"later\";\n return null;\n };\n const appendStreamText = (s: string): void => {\n if (!s) return;\n if (!assistant) {\n typing.remove();\n assistant = addMsg(log, \"assistant\", \"\");\n assistant.classList.add(`${PREFIX}-streaming`);\n }\n const masked = maskMarkdown(s);\n if (masked) {\n const tok = el(\"span\", `${PREFIX}-tok`);\n tok.textContent = masked;\n assistant.appendChild(tok);\n }\n };\n const renderStreamDirective = (tag: string, spec: unknown): void => {\n const custom = customRenderers[tag];\n if (custom) {\n const host = el(\"div\", `${PREFIX}-widget ${PREFIX}-custom`);\n log.appendChild(host);\n try {\n const dispose = custom(host, spec);\n if (dispose) richDisposers.push(dispose);\n } catch {\n host.remove();\n }\n scrollDown(true);\n return;\n }\n if (tag === \"widget\") {\n renderWidget(spec as WidgetSpec);\n return;\n }\n if (tag === \"ui\") {\n renderUiCard(spec);\n return;\n }\n const p = spec as PreviewSpec;\n if (typeof p.html === \"string\") renderPreview(p);\n };\n const processStreamDisplay = (): void => {\n for (;;) {\n const pending = assistantRaw.slice(shownLen);\n if (!pending) return;\n const open = pending.indexOf(\"[[\");\n if (open < 0) {\n // Withhold a lone trailing \"[\" \u2014 it may grow into an opener.\n const cut = pending.endsWith(\"[\")\n ? pending.length - 1\n : pending.length;\n appendStreamText(pending.slice(0, cut));\n shownLen += cut;\n return;\n }\n if (open > 0) {\n appendStreamText(pending.slice(0, open));\n shownLen += open;\n continue;\n }\n // pending starts with \"[[\" \u2014 read the tag.\n const m = /^\\[\\[([a-z-]{1,24})(:|\\]\\])?/i.exec(pending);\n if (!m) {\n if (pending.length <= 2) return; // bare \"[[\" \u2014 tag may follow\n appendStreamText(\"[[\"); // \"[[\" + a non-tag char \u2014 literal text\n shownLen += 2;\n continue;\n }\n if (!m[2] && pending.length === 2 + m[1].length) return; // tag typing\n if (m[2] === \"]]\") {\n if (`[[${m[1]}]]` === LEAD_TOKEN) {\n shownLen += m[0].length; // hide the token; final pass renders it\n continue;\n }\n appendStreamText(\"[[\");\n shownLen += 2;\n continue;\n }\n if (!m[2]) {\n // Tag chars ended in something that is not \":\" \u2014 literal text.\n appendStreamText(\"[[\");\n shownLen += 2;\n continue;\n }\n const mode = streamDirectiveTag(m[1]);\n if (!mode) {\n appendStreamText(\"[[\");\n shownLen += 2;\n continue;\n }\n const d = parseJsonDirective<unknown>(pending, m[1]);\n if (!d || d.start !== 0) {\n // Incomplete (still streaming) \u2014 hold. Give up on a pathological\n // never-closing directive so the rest of the reply isn't hostage.\n if (pending.length > 20000) {\n appendStreamText(\"[[\");\n shownLen += 2;\n continue;\n }\n return;\n }\n if (mode === \"later\") {\n shownLen += d.end; // keep in raw (final pass acts on it), hide only\n continue;\n }\n // Renderable NOW: bake the text seen so far, paint the card, resume.\n const rest = pending.slice(d.end);\n assistantRaw = assistantRaw.slice(0, shownLen);\n flushSegment(false);\n renderStreamDirective(m[1], d.spec);\n assistantRaw = rest;\n shownLen = 0;\n }\n };\n try {\n const token = opts.getToken ? await opts.getToken() : opts.token;\n const baseCtx = opts.getContext ? await opts.getContext() : undefined;\n // In advanced view the controllable page is the FRAME, not the parent\n // shell \u2014 so the on-page targets (and the current path) come from the\n // frame's agent, overriding whatever the host scanned locally.\n const pageContext =\n advanced && transport && baseCtx && typeof baseCtx === \"object\"\n ? {\n ...(baseCtx as Record<string, unknown>),\n path: transport.getPath() ?? (baseCtx as { path?: string }).path,\n uiTargets: transport.getTargets(),\n }\n : baseCtx;\n // Cache this turn's navigable pages for the #111 prose-nav fallback below.\n const ctxTargets = (pageContext as { navTargets?: unknown } | undefined)\n ?.navTargets;\n if (Array.isArray(ctxTargets)) knownNavTargets = ctxTargets;\n const res = await fetch(opts.endpoint, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...(token ? { authorization: `Bearer ${token}` } : {}),\n },\n credentials: opts.withCredentials ? \"include\" : \"same-origin\",\n // Tied to the widget's lifetime. Without this the reader loop outlived\n // `destroy()`: it kept appending to a detached log, kept fetching, and\n // kept calling saveState() \u2014 into the SAME storage key the replacement\n // widget was already writing. Admin re-keys the widget on account and\n // language change, so a mid-stream account switch could clobber the new\n // account's transcript with the old one's.\n signal: alive.signal,\n body: JSON.stringify({\n ...(opts.extraBody ?? {}),\n ...(pageContext ? { pageContext } : {}),\n accountId: opts.getAccountScope?.() ?? opts.accountId ?? \"\",\n threadId,\n content,\n ...(atts.length ? { attachments: atts.map((a) => a.mediaId) } : {}),\n ...(fork?.parentId !== undefined ? { parentId: fork.parentId } : {}),\n ...(fork?.regenerate ? { regenerate: true } : {}),\n }),\n });\n if (!res.ok || !res.body) {\n failure = `Server error (${res.status}).`;\n } else {\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buf = \"\";\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buf += decoder.decode(value, { stream: true });\n let idx: number;\n while ((idx = buf.indexOf(\"\\n\")) >= 0) {\n const line = buf.slice(0, idx).trim();\n buf = buf.slice(idx + 1);\n const frame = parseLine(line);\n if (!frame) continue;\n if (frame.threadId) {\n // The server named this turn's thread. Re-key the pending entry;\n // update the GLOBAL threadId only while the user is still here \u2014\n // after a switch it must not clobber the view they moved to.\n if (turnThread !== frame.threadId) {\n pendingThreads.delete(turnThread ?? NEW_TURN_KEY);\n turnThread = frame.threadId;\n pendingThreads.add(turnThread);\n }\n if (live()) {\n threadId = frame.threadId;\n // A brand-new conversation just got its id \u2014 the star row can\n // stop hiding itself.\n paintStarItem();\n }\n }\n const piece = frame.text ?? frame.d;\n if (piece && live()) {\n assistantRaw += piece;\n producedAny = true;\n // Stream MASKED plain text (markdown marks hidden) with a\n // per-token fade+blur reveal \u2014 directive bytes are withheld and\n // visual cards paint mid-stream (see processStreamDisplay). The\n // real markdown still renders once at the end of each segment.\n processStreamDisplay();\n scrollDown();\n }\n // Inline data widget (render_chart). Flush the current text first so\n // the chart lands AFTER it, in order \u2014 not below the whole reply.\n if (frame.type === \"widget\" && live()) {\n typing.remove();\n flushSegment(false);\n renderServerWidget(frame.spec, frame.rows, frame.comparisonRows);\n producedAny = true;\n }\n // Live agent-activity step \u2014 a process chip (running \u2192 ok/error). On\n // start, flush text so the chip sits AFTER it (true order) and flip\n // the role badge to match the tool (Talk \u2192 Analytics, etc.).\n if (\n frame.type === \"activity\" &&\n frame.label &&\n frame.status &&\n live()\n ) {\n typing.remove();\n if (frame.status === \"running\") {\n flushSegment(false);\n if (frame.name && TOOL_ROLE[frame.name])\n setRole(TOOL_ROLE[frame.name]);\n }\n liveActivityFrame(\n frame.callId ?? frame.label,\n frame.label,\n frame.status,\n (frame as { agent?: string }).agent,\n (frame as { model?: string }).model\n );\n producedAny = true;\n }\n // The assistant is ASKING the human to decide something.\n if (\n frame.type === \"question\" &&\n frame.questionId &&\n frame.prompt &&\n live()\n ) {\n typing.remove();\n flushSegment(false);\n renderQuestion(\n frame as unknown as Parameters<typeof renderQuestion>[0]\n );\n producedAny = true;\n }\n // Confirm-gated WRITE tool \u2014 the AI PROPOSES; show a card the user\n // applies (the actual mutation is a host, Clerk-authed action).\n if (\n frame.type === \"tool_proposal\" &&\n frame.name &&\n opts.onApplyProposal\n ) {\n // Every write proposal shows a confirm card \u2014 the human applies it.\n // (Saving a generated image/video/PDF into the library is just this:\n // the AI proposes the save, the user clicks Apply, or asks for it.)\n // HELD until the turn's text has been rendered \u2014 see\n // deferredProposals. Do NOT flush the segment here: splitting the\n // bubble at the tool call is what let the card jump the queue.\n deferredProposals.push({\n name: frame.name,\n args: (frame.args ?? {}) as Record<string, unknown>,\n agent: (frame as { agent?: string }).agent,\n // Inputs the assistant wants filled in before this is applied.\n // Absent on every proposal that needs no decision, which is\n // most of them \u2014 the card stays a plain confirmation.\n fields: proposalFields((frame as { fields?: unknown }).fields),\n accountId: (frame as { accountId?: string }).accountId,\n artifactId: (frame as { artifactId?: string }).artifactId,\n });\n producedAny = true;\n }\n // Free-allowance meter (visitor preview). `quota` is the server's\n // snapshot; `usage` is this turn's tokens (count toward the session).\n if (frame.type === \"quota\") {\n if (typeof frame.granted === \"number\")\n quotaGranted = frame.granted;\n if (typeof frame.remaining === \"number\")\n quotaRemaining = frame.remaining;\n renderMeter();\n if (frame.exhausted) showSignupCta();\n }\n if (frame.type === \"usage\") {\n turnIn += frame.inputTokens ?? 0;\n turnOut += frame.outputTokens ?? 0;\n const turn = (frame.inputTokens ?? 0) + (frame.outputTokens ?? 0);\n sessionUsed += turn;\n if (quotaRemaining !== null)\n quotaRemaining = Math.max(0, quotaRemaining - turn);\n renderMeter();\n }\n if (frame.type === \"meta\" && frame.isStaff && frame.modelLabel) {\n metaChip.textContent = frame.modelLabel;\n metaChip.hidden = false;\n }\n // The turn's own message id + model (#299). This is what lets the\n // LIVE turn be rated on a surface that has no id-bearing replay to\n // fall back on \u2014 the visitor lane, whose transcript read carried no\n // ids at all until this change.\n if (frame.type === \"done\") {\n if (frame.messageId) liveMessageId = frame.messageId;\n if (frame.model) liveModel = frame.model;\n }\n if (frame.type === \"error\" && frame.message)\n failure = frame.message;\n }\n }\n }\n } catch (err) {\n // TRANSPORT loss (QUIC/network drop, proxy hiccup) \u2014 distinct from a\n // server-sent error frame: the api keeps running the turn server-side\n // and persists the transcript at the end, so the reply is NOT lost,\n // the browser just can't see the rest of the stream. Recover by\n // polling the thread until the reply lands (see below).\n transportLost = true;\n failure = (err as Error).message || \"Network error.\";\n } finally {\n pendingThreads.delete(turnThread ?? NEW_TURN_KEY);\n syncBusy();\n if (live()) input.focus();\n // A turn just consumed credits \u2014 refresh the remaining-credits readout.\n void refreshBalance();\n // A generation/import may have just landed \u2014 refresh the artifact strip\n // (thread-scoped, so only when this turn's thread is the one on screen).\n if (threadId === turnThread) void refreshArtifacts();\n }\n\n // Stale turn finished in the background (the user switched away while it\n // streamed). Nothing here may touch the visible view UNLESS the user has\n // switched BACK to this turn's thread \u2014 then reload the canonical\n // transcript (the reply is persisted server-side) and surface any pending\n // proposal cards (they are ephemeral, never persisted, so this is their\n // only way to reach the user). Otherwise leave everything for the thread\n // to show on next open; the History spinner has just cleared.\n // Transport died mid-stream but the api runs the turn to completion and\n // persists the transcript at the end \u2014 recover instead of giving up: keep\n // the thread marked \"answering\u2026\" and poll until the reply lands (seen live\n // with QUIC drops through the edge; the reply arrived minutes later).\n if (transportLost && turnThread && opts.loadThread) {\n typing.remove();\n if (live()) showError(L(\"streamLostRecovering\"));\n void recoverLostTurn(turnThread, isRegen ? null : content);\n return;\n }\n\n if (!live()) {\n typing.remove(); // detached or not \u2014 harmless either way\n if (turnThread && threadId === turnThread && opts.loadThread) {\n try {\n renderThreadItems(await opts.loadThread(turnThread), turnThread);\n } catch {\n /* the thread shows the reply on its next open */\n }\n for (const p of deferredProposals)\n renderProposal(\n p.name,\n p.args,\n p.agent,\n p.fields,\n p.accountId,\n p.artifactId\n );\n maybeDing();\n scrollDown();\n saveState();\n }\n return;\n }\n\n // Clear the typing indicator + render the FINAL text segment (with\n // directives). Earlier segments were already flushed around widgets/chips.\n typing.remove();\n const lastBubble = flushSegment(true);\n // The reply is on screen; NOW ask for the decisions, in the order the model\n // proposed them.\n for (const p of deferredProposals)\n renderProposal(\n p.name,\n p.args,\n p.agent,\n p.fields,\n p.accountId,\n p.artifactId\n );\n if (deferredProposals.length) scrollDown();\n if (!producedAny) {\n if (failure) showError(failure);\n else {\n addMsg(log, \"assistant\", \"(no response)\");\n scrollDown(true);\n }\n } else {\n // A reply did stream; surface a late error inline (partial + error).\n if (failure) showError(failure);\n // Per-message token badge under the reply (UI-friendly tokens caption).\n if (turnIn + turnOut > 0) {\n const cap = usageBadge(turnIn, turnOut);\n if (lastBubble) lastBubble.insertAdjacentElement(\"afterend\", cap);\n else log.appendChild(cap);\n }\n maybeDing();\n scrollDown();\n }\n saveState();\n // Reload the canonical thread after the turn so every message (incl. the one\n // just sent) carries its persisted id + \u2039n/m\u203A switcher \u2014 that's what makes\n // the edit / regenerate / branch controls appear WITHOUT a manual reopen. A\n // fork turn (edit / regenerate) also needs this to land the rewritten tree.\n // Only when branching is wired (setActiveLeaf) \u2014 otherwise the streamed view\n // is already canonical and reloading would just cause a needless re-render.\n // A live, un-applied WRITE PROPOSAL card is ephemeral \u2014 it is NOT\n // persisted as a thread artifact, so a full renderThreadItems()\n // reload (which does `log.innerHTML = \"\"`) would silently WIPE it: the\n // agent proposes, the card flashes in, the end-of-turn reload deletes it,\n // and the admin never gets to click Apply. When a proposal card is on\n // screen, keep the streamed view instead. The only thing the reload adds is\n // the \u2039n/m\u203A branch switcher + persisted ids on this turn's messages, which\n // self-heal on the next send or when the thread is reopened \u2014 a fair trade\n // to avoid destroying a pending Apply card.\n const hasLiveProposal = !!log.querySelector(`.${PREFIX}-proposal-pending`);\n // Same wipe hazard for a FAILED turn: the error card (Retry/Report) is\n // ephemeral UI and the failed turn persisted nothing, so the canonical\n // reload would repaint an EMPTY thread \u2014 deleting the user's own message\n // AND the error they were meant to read (the \"error flashes then the chat\n // goes blank\" report). Keep the streamed view whenever an error card is on\n // screen; the next successful send reloads and self-heals the ids.\n const hasErrorCard = !!log.querySelector(`.${PREFIX}-error`);\n // Reload THIS TURN's thread (never whatever the view has become) \u2014 and only\n // while the user is still on it; the fence above already handled the\n // switched-away cases.\n if (\n turnThread &&\n opts.loadThread &&\n opts.setActiveLeaf &&\n !hasLiveProposal &&\n !hasErrorCard\n ) {\n try {\n const reloaded = await opts.loadThread(turnThread);\n if (live()) renderThreadItems(reloaded, turnThread);\n } catch {\n /* keep the streamed view if the reload fails */\n }\n } else if (liveMessageId && opts.vote && producedAny && live()) {\n // NO canonical reload happened, so nothing is going to paint the vote\n // control for us (#299).\n //\n // Hosts that wire `setActiveLeaf` (org, admin) reload the thread after\n // every turn, and that reload re-renders each message WITH its persisted\n // id \u2014 which is why thumbs already appear on a live turn there. The issue\n // read that as \"votes only on replayed turns\"; measured, it is narrower\n // and the gap is here:\n //\n // \u2022 the visitor lane wires no setActiveLeaf at all, so it never reloads\n // \u2022 a live proposal card or an error card SUPPRESSES the reload, to\n // avoid wiping ephemeral UI \u2014 so those turns lose the control too\n //\n // In all three the id now arrives on the `done` frame, so the answer just\n // read is ratable without reopening the thread.\n const rowEl = el(\"div\", `${PREFIX}-msgactions ${PREFIX}-assistant`);\n rowEl.appendChild(buildVoteButtons(liveMessageId, liveModel));\n log.appendChild(rowEl);\n scrollDown();\n }\n\n // ASK, when the turn gave us a reason to (#299). Every other part of this\n // signal waits to be volunteered, on controls that are easy to miss.\n const threadKey = turnThread ?? NEW_TURN_KEY;\n const turns = (threadTurns.get(threadKey) ?? 0) + 1;\n threadTurns.set(threadKey, turns);\n const elapsed = Date.now() - turnStartedAt;\n if (\n live() &&\n liveMessageId &&\n opts.vote &&\n // Never on a thread's FIRST turn \u2014 asking someone to rate a conversation\n // they have not had yet is noise.\n turns > 1 &&\n // At most once per thread per session.\n !qualityAsked.has(threadKey) &&\n (failure || elapsed >= QUALITY_SLOW_MS)\n ) {\n qualityAsked.add(threadKey);\n renderQualityPrompt(\n failure ? \"failed\" : \"slow\",\n liveMessageId,\n liveModel\n );\n }\n }\n\n /**\n * Build ONE input from a field spec \u2014 the widget's only place that turns a\n * described field into a DOM control.\n *\n * Extracted because the editable proposal card needs exactly this and was\n * about to grow its own copy. A second implementation would drift: the same\n * `select` would look one way inside a form and another inside a card, and a\n * later fix to one would quietly miss the other.\n *\n * Returns a node + a reader rather than the raw element, because not every\n * control IS one element: a checkbox needs its label beside it to mean\n * anything, and a radio needs one input per option. Handing the caller a\n * `<select>` and letting it read `.value` worked only as long as every field\n * happened to be a single text-ish box.\n */\n\n /** Render an AI-described input form inline; submit \u2192 host action. */\n function renderForm(spec: FormSpec): void {\n const wrap = el(\"div\", `${PREFIX}-lead`);\n if (spec.title) {\n const t = el(\"div\", `${PREFIX}-form-title`);\n t.textContent = spec.title;\n wrap.appendChild(t);\n }\n const f = el(\"form\", `${PREFIX}-lead-form`) as HTMLFormElement;\n const controls: {\n name: string;\n get: () => string;\n required: boolean;\n /** A checkbox answers \"false\" when unticked \u2014 a real answer for a normal\n * field, and \"not filled in\" for this one. */\n boolish: boolean;\n /** Highlighted when the form is held back on this control. */\n node: HTMLElement;\n }[] = [];\n // Fields are built by ONE function, shared with the editable proposal card\n // \u2014 see `buildField`. Two implementations of \"render an input from a spec\"\n // is how a select ends up styled differently depending on which part of the\n // chat drew it.\n for (const field of spec.fields.slice(0, 8)) {\n const built = buildField(field);\n f.appendChild(built.node);\n controls.push({\n name: field.name,\n get: built.read,\n required: Boolean(field.required),\n boolish: field.type === \"checkbox\",\n node: built.node,\n });\n }\n // Why Send did nothing. A silent `return` on a missing required field left\n // the user clicking a button that never reacted \u2014 with no way to guess which\n // field was at fault.\n const err = el(\"div\", `${PREFIX}-form-err`);\n err.textContent = L(\"requiredFields\");\n err.style.display = \"none\";\n f.appendChild(err);\n const submit = el(\"button\", `${PREFIX}-lead-btn`) as HTMLButtonElement;\n submit.type = \"submit\";\n submit.textContent = spec.submit ?? L(\"submit\");\n f.appendChild(submit);\n wrap.appendChild(f);\n log.appendChild(wrap);\n scrollDown(true);\n f.addEventListener(\"submit\", async (e) => {\n e.preventDefault();\n const data: Record<string, string> = {};\n for (const c of controls) data[c.name] = c.get();\n // Check the REQUIRED fields, each on its own. This used to ask \"is any\n // control empty AND is any field required\", which failed both ways: an\n // untouched OPTIONAL field blocked a form whose required ones were all\n // filled, and a required checkbox passed while unticked because it reads\n // the string \"false\", which is not empty.\n const missing = controls.filter((c) => {\n if (!c.required) return false;\n const v = data[c.name];\n return c.boolish ? !isTruthyValue(v) : !v;\n });\n for (const c of controls)\n c.node.classList.toggle(`${PREFIX}-field-invalid`, missing.includes(c));\n if (missing.length) {\n err.style.display = \"\";\n // Put the cursor on the offending control \u2014 its node is the input\n // itself for a text field, and a wrapper for a checkbox/radio group.\n const first = missing[0]?.node;\n (first?.matches(\"input,select,textarea\")\n ? first\n : first?.querySelector<HTMLElement>(\"input,select,textarea\")\n )?.focus();\n return;\n }\n err.style.display = \"none\";\n submit.disabled = true;\n submit.textContent = L(\"sending\");\n try {\n let msg: string | void = undefined;\n if (opts.onWidgetAction) {\n msg = await opts.onWidgetAction(spec.action, data);\n } else if (opts.onLead && spec.action === \"lead\") {\n await opts.onLead({\n email: data.email ?? \"\",\n context: lastUserContent,\n });\n }\n wrap.innerHTML = \"\";\n const ok = el(\"div\", `${PREFIX}-lead-ok`);\n ok.textContent = (typeof msg === \"string\" && msg) || L(\"done\");\n wrap.appendChild(ok);\n } catch {\n submit.disabled = false;\n submit.textContent = L(\"tryAgain\");\n }\n });\n }\n\n // Server-driven UI rendering lives in ./ui-render (#320). Built HERE, after\n // every dependency above exists: `paintOpeningView()` at the end of setup\n // reaches `renderUiCard` through `addAssistantMessage`, so this cannot move\n // later in the file without a temporal-dead-zone crash on first paint.\n const {\n renderWidget,\n renderUiCard,\n renderServerWidget,\n renderChips,\n renderPreview,\n operateConfirmText,\n } = createUiRenderers({\n L,\n log,\n scrollDown,\n send,\n richDisposers,\n dispatchAction,\n subscribeJob,\n showPaneWidget,\n input,\n opts,\n });\n\n /** Render a navigation suggestion. With auto-navigate ON it follows the link\n * immediately (showing a \"Opening \u2026\" chip); OFF it offers a confirm button.\n * ADVANCED mode always auto-follows: navigation there only retargets the\n * embedded iframe (the user is WATCHING the assistant drive the app \u2014 a\n * confirm button per page defeats the mode), while the browser-local toggle\n * keeps governing normal widget mode, where navigation moves the real app. */\n function renderNavigate(spec: NavigateSpec, replay = false): void {\n const wrap = el(\"div\", `${PREFIX}-nav`);\n const label = spec.label || \"Open page\";\n // On replay (history / post-turn thread restore) always render the button,\n // never auto-follow \u2014 re-opening a conversation must not navigate the app.\n if ((autoNav || advanced) && !replay) {\n const chip = el(\"div\", `${PREFIX}-autonav`);\n chip.innerHTML = `${ICON_COMPASS}<span>${escapeHtml(L(\"actionOpening\", { label }))}</span>`;\n wrap.appendChild(chip);\n log.appendChild(wrap);\n scrollDown(true);\n // The chip must always land on an OUTCOME. It used to be written once as\n // \"Opening \u2026\" and only rewritten on failure, so a successful navigation\n // left it saying \"Opening Open Assets\u2026\" for the rest of the conversation\n // \u2014 which reads as a click that never finished, and was reported as\n // exactly that. The handler's own words win when it has any (\"Already on\n // assets\" is more use than a tick).\n void Promise.resolve(\n dispatchAction(\"navigate\", { path: spec.path })\n ).then(\n (msg) => {\n chip.querySelector(\"span\")!.textContent =\n (typeof msg === \"string\" && msg) || L(\"actionDone\", { label });\n },\n () => {\n chip.querySelector(\"span\")!.textContent = L(\"actionOpenFailed\", {\n label,\n });\n }\n );\n return;\n }\n const btn = el(\"button\", `${PREFIX}-nav-btn`) as HTMLButtonElement;\n btn.type = \"button\";\n btn.innerHTML = `<span>${escapeHtml(label)}</span> <span aria-hidden=\"true\">\u2192</span>`;\n btn.addEventListener(\"click\", async () => {\n btn.disabled = true;\n try {\n const msg = await dispatchAction(\"navigate\", { path: spec.path });\n // Say what actually happened. A blanket tick hid the difference\n // between \"opened\" and \"you are already there\", which is the whole\n // reason a click on the page you are on looked broken.\n btn.innerHTML = `<span>${escapeHtml(\n (typeof msg === \"string\" && msg) || L(\"actionDone\", { label })\n )}</span>`;\n } catch {\n // Re-enabling in silence left the user to guess. Refusing an off-app\n // path is a decision worth stating.\n btn.disabled = false;\n btn.innerHTML = `<span>${escapeHtml(L(\"actionRetry\", { label }))}</span>`;\n }\n });\n wrap.appendChild(btn);\n log.appendChild(wrap);\n scrollDown(true);\n }\n\n /** Render an AI-proposed in-app action as a button (with optional confirm\n * step), dispatched to the host via onWidgetAction(name, data). */\n\n function renderAction(spec: ActionSpec): void {\n // Reflect the acting capability in the status badge (e.g. open-dashboards\n // \u2192 Analytics); plain navigation stays Talk.\n if (ACTION_ROLE[spec.name]) setRole(ACTION_ROLE[spec.name]);\n const wrap = el(\"div\", `${PREFIX}-nav`);\n // State-changing UI control (fill / click) is ALWAYS confirm-gated: force a\n // confirm prompt even if the model forgot one, and never auto-run it \u2014 the\n // user must approve typing/clicking on their behalf. Read-only nudges\n // (highlight/scroll/focus) never reach renderAction (they carry no button).\n if (isOperateAction(spec.name) && !spec.confirm) {\n spec = { ...spec, confirm: operateConfirmText(spec) };\n }\n // Auto-navigate: a confirm-LESS action is pure navigation\n // (open-dashboards, \u2026) \u2014 run it immediately. Anything with `confirm` (changes\n // state / costs credits) ALWAYS asks, even in auto mode. Advanced mode\n // auto-runs pure navigation unconditionally (it only moves the iframe).\n if (\n (autoNav || advanced) &&\n isNavigationAction(spec.name) &&\n !spec.confirm\n ) {\n const label = spec.label || \"Opening\u2026\";\n const chip = el(\"div\", `${PREFIX}-autonav`);\n chip.innerHTML = `${ICON_COMPASS}<span>${escapeHtml(L(\"actionOpening\", { label }))}</span>`;\n wrap.appendChild(chip);\n log.appendChild(wrap);\n scrollDown(true);\n void Promise.resolve(dispatchAction(spec.name, spec.data ?? {})).then(\n (msg) => {\n chip.querySelector(\"span\")!.textContent =\n (typeof msg === \"string\" && msg) || L(\"actionDone\", { label });\n },\n () => {\n chip.querySelector(\"span\")!.textContent = L(\"actionRunFailed\", {\n label,\n });\n }\n );\n return;\n }\n const btn = el(\"button\", `${PREFIX}-nav-btn`) as HTMLButtonElement;\n btn.type = \"button\";\n const label = spec.label || \"Run\";\n btn.innerHTML = `<span>${escapeHtml(label)}</span>`;\n const run = async (): Promise<void> => {\n btn.disabled = true;\n try {\n const msg = await dispatchAction(spec.name, spec.data ?? {});\n btn.innerHTML = `<span>${escapeHtml(\n (typeof msg === \"string\" && msg) || L(\"actionDone\", { label })\n )}</span>`;\n } catch {\n btn.disabled = false;\n btn.innerHTML = `<span>${escapeHtml(L(\"actionRetry\", { label }))}</span>`;\n }\n };\n btn.addEventListener(\"click\", () => {\n // \"Critic permission\": ask the user to confirm before acting.\n if (spec.confirm) {\n wrap.innerHTML = \"\";\n const q = el(\"div\", `${PREFIX}-confirm-q`);\n q.textContent = spec.confirm;\n const row = el(\"div\", `${PREFIX}-confirm-row`);\n const yes = el(\"button\", `${PREFIX}-nav-btn`) as HTMLButtonElement;\n yes.type = \"button\";\n yes.textContent = L(\"confirm\");\n const no = el(\"button\", `${PREFIX}-confirm-no`) as HTMLButtonElement;\n no.type = \"button\";\n no.textContent = L(\"cancel\");\n yes.addEventListener(\"click\", () => {\n wrap.innerHTML = \"\";\n wrap.appendChild(btn);\n btn.innerHTML = `<span>${escapeHtml(label)}</span>`;\n void run();\n });\n no.addEventListener(\"click\", () => wrap.remove());\n row.append(yes, no);\n wrap.append(q, row);\n scrollDown(true);\n } else {\n void run();\n }\n });\n wrap.appendChild(btn);\n log.appendChild(wrap);\n scrollDown(true);\n }\n\n /** Render a recoverable error card: friendly copy + retry + (optional) report. */\n function showError(raw: string): void {\n const wrap = el(\"div\", `${PREFIX}-error`);\n const txt = el(\"div\", `${PREFIX}-error-text`);\n txt.textContent = L(\"errorSnag\", { name });\n const detail = el(\"div\", `${PREFIX}-error-detail`);\n detail.textContent = raw;\n const actions = el(\"div\", `${PREFIX}-error-actions`);\n\n const retry = el(\"button\", `${PREFIX}-error-btn ${PREFIX}-error-retry`);\n retry.setAttribute(\"type\", \"button\");\n retry.textContent = L(\"tryAgain\");\n retry.addEventListener(\"click\", () => {\n wrap.remove();\n if (lastUserContent) void send(lastUserContent);\n });\n actions.appendChild(retry);\n\n if (opts.onReportIssue) {\n const report = el(\"button\", `${PREFIX}-error-btn`);\n report.setAttribute(\"type\", \"button\");\n report.textContent = L(\"reportIssue\");\n report.addEventListener(\"click\", async () => {\n (report as HTMLButtonElement).disabled = true;\n report.textContent = L(\"reporting\");\n try {\n await opts.onReportIssue!({\n error: raw,\n lastUserMessage: lastUserContent,\n threadId,\n });\n report.textContent = L(\"reported\");\n } catch {\n report.textContent = L(\"reportFailed\");\n (report as HTMLButtonElement).disabled = false;\n }\n });\n actions.appendChild(report);\n }\n\n wrap.append(txt, detail, actions);\n log.appendChild(wrap);\n scrollDown(true);\n }\n\n /**\n * A background job this conversation started has finished.\n *\n * `generate_report` and friends return immediately and run for minutes, then\n * report back onto the THREAD \u2014 which the chat only ever saw on a reload, so\n * \"I'll notify you when the folder is ready\" was followed, in the chat, by\n * nothing at all. The api announces the change (`publishLiveChange`) and the\n * host forwards it here.\n *\n * Guarded: never reload mid-turn (it would wipe the streaming view), never\n * over a card still awaiting the user, and only for the `ai` domain.\n */\n const offAiChange = subscribeAiChange((e) => {\n const scope = opts.getAccountScope?.() ?? opts.accountId;\n if (scope && e.accountId && e.accountId !== scope) return;\n if (!e.domains?.includes(\"ai\")) return;\n if (busy || !threadId || !opts.loadThread) return;\n if (log.querySelector(`.${PREFIX}-proposal-pending`)) return;\n void opts\n .loadThread(threadId)\n .then((t) => renderThreadItems(t))\n .catch(() => {\n /* a missed refresh just means the old manual reload */\n });\n });\n\n // Every `let` in this scope is now initialized, so the opening view can be\n // painted against real state rather than a temporal dead zone. Keep this the\n // LAST thing setup does.\n paintOpeningView();\n\n /**\n * `offline` from the browser, not from a failed request (#305).\n *\n * A launcher that says so up front is better than one that looks ready and\n * fails on click \u2014 which is what the widget did, since every turn's failure\n * arrived only after the reader had committed to asking. `navigator.onLine`\n * is a floor, not a guarantee (it can be true behind a captive portal), so it\n * is used to say \"definitely not\" and never to promise the opposite.\n */\n const syncOnline = (): void => {\n if (typeof navigator === \"undefined\") return;\n if (!navigator.onLine) setLauncher({ state: \"offline\" });\n else if (launcher.state === \"offline\") setLauncher({ state: \"resting\" });\n };\n window.addEventListener(\"online\", syncOnline);\n window.addEventListener(\"offline\", syncOnline);\n syncOnline();\n\n /**\n * The ground under a fixed launcher CHANGES as the page scrolls \u2014 a dark hero\n * gives way to a light body \u2014 so \"auto\" has to be re-read, not resolved once\n * at mount. Debounced to one rAF and applied only when the answer actually\n * flips, so scrolling costs one elementsFromPoint per frame at most and the\n * mark never strobes between two fills.\n */\n let groundTimer = 0;\n let lastGround: \"light\" | \"ink\" | null = null;\n const watchGround = (): void => {\n // TRAILING, not leading, and not one rAF. A single rAF sampled while the\n // scroll was still in flight and read the OLD stack \u2014 measured: the class\n // stayed `on-ink` over a light section until a scroll event was dispatched\n // by hand, which proved the logic right and the timing wrong. Same shape as\n // the router-vs-fragment race in #303. The ground does not need to update\n // mid-scroll; it needs to be correct when the reader stops.\n window.clearTimeout(groundTimer);\n groundTimer = window.setTimeout(() => {\n if (launcher.ground !== \"auto\") return;\n const now = resolveGround();\n if (now === lastGround) return;\n lastGround = now;\n applyLauncher();\n }, 140);\n };\n window.addEventListener(\"scroll\", watchGround, { passive: true });\n window.addEventListener(\"resize\", watchGround, { passive: true });\n\n applyLauncher();\n\n return {\n open,\n close,\n toggle,\n registerRenderer,\n setLauncher,\n destroy() {\n // FIRST: cut the in-flight turn loose. Everything below tears down the\n // DOM it would otherwise keep writing to.\n alive.abort();\n offAiChange();\n if (opts.openEventName) {\n window.removeEventListener(opts.openEventName, onOpenEvent);\n }\n window.removeEventListener(\"resize\", onResize);\n window.removeEventListener(\"online\", syncOnline);\n window.removeEventListener(\"offline\", syncOnline);\n window.removeEventListener(\"scroll\", watchGround);\n window.removeEventListener(\"resize\", watchGround);\n window.clearTimeout(groundTimer);\n unbindKeyboard();\n clearRich();\n teardownFrame();\n bubble.remove();\n panel.remove();\n },\n };\n}\n", null, null, null, "/**\n * Shared host protocol for Copilot \u2014 the ONE definition of the page context the\n * assistant receives and the in-app actions it may take, reused by every\n * surface (org, admin, marketing, onboarding). Keeping this in one place means\n * adding a surface is a few lines, and the action allow-list can't drift between\n * apps.\n *\n * SECURITY: the AI only ever NAMES an action; the host owns the name\u2192effect\n * mapping below. The model never receives a raw URL or endpoint.\n */\n\nimport {\n isUiControlAction,\n runUiControl,\n isOperateAction,\n runOperateAction,\n} from \"./ui-control\";\n// Type-only: erased at build, so this stays a zero-runtime-dependency module.\nimport type { Locale } from \"./limits\";\n\n/** Which product surface the user is on. */\nexport type AppSurface = \"org\" | \"admin\" | \"marketing\" | \"onboarding\";\n\n/**\n * A structured descriptor of ONE page/route \u2014 the \"page manifest\". It lives next\n * to the app's routes (one source of truth) and teaches Copilot what a page IS:\n * its purpose, the sections on it, and the actions available there. Feeding\n * these into the turn context turns Copilot from \"knows the route name\" into a\n * page-structure-aware UI assistant.\n */\nexport interface PageManifestEntry {\n /** Language-/account-neutral path the model uses to navigate (e.g. \"/network\",\n * or an org suffix like \"/assets\"). */\n path: string;\n /** Human title shown to the user / used by the model. */\n title: string;\n /** One-line \"what this page is for\". */\n purpose: string;\n /** Key sections/areas on the page (so Copilot can describe its structure). */\n sections?: string[];\n /** Allow-listed action name(s) that open/operate this page, when the host\n * prefers a named action over a raw path (e.g. org's \"open-dashboards\"). */\n action?: string;\n /** A few page-aware shortcut questions surfaced as clickable chips in the\n * assistant widget while the conversation is empty \u2014 the smart \"where do I\n * start on THIS page\" prompts. Clicking one sends it to Copilot. */\n suggestions?: string[];\n}\n\n/** A compact navigable target the model is offered (derived from the manifest). */\nexport interface NavTarget {\n path: string;\n title: string;\n purpose: string;\n /** Named action to use instead of a raw path, when set. */\n action?: string;\n}\n\n/** Per-turn page context sent to the assistant (merged into the POST as\n * `pageContext` by `getContext`). */\nexport interface PageContext {\n app: AppSurface;\n /** Current route/path. */\n path: string;\n /** Last path segment \u2014 a coarse \"page\" name the model can reason about. */\n page: string;\n /** Recent navigation breadcrumb (most-recent last, capped). */\n recentPages: string[];\n /** The current page's manifest descriptor (purpose + sections), when known. */\n pageInfo?: PageManifestEntry;\n /** The catalog of pages Copilot may open (from the manifest) \u2014 the single source\n * of truth for navigation, so the prompt never hardcodes a path list. */\n navTargets?: NavTarget[];\n /** On-page controls the Copilot may point at (highlight/scroll-to/focus-field)\n * \u2014 scanned from the live DOM via `scanAiTargets()`. Ids the host owns, never\n * selectors. Fed into the turn so the model knows what's on THIS page. */\n uiTargets?: Array<{ id: string; label: string }>;\n}\n\n/**\n * The locale segments the site actually serves.\n *\n * \"Any two lowercase letters\" is NOT a locale test: `/ai` is a real marketing\n * page, and treating its first segment as a locale rewrote that entry to `/`,\n * which then matched every path with the same segment count. Mirrors\n * a host's i18n config; the host's `Locale` union types it, so\n * a locale that is renamed there fails to compile here.\n */\nconst LOCALE_SEGMENTS = [\"en\", \"tr\"] as const satisfies readonly Locale[];\n\nfunction isLocaleSegment(seg: string): boolean {\n return (LOCALE_SEGMENTS as readonly string[]).includes(seg);\n}\n\n/** Normalize a path for manifest matching: drop a leading locale segment\n * (marketing \"/en/network\" \u2192 \"/network\") and any trailing slash. */\nfunction normalizePath(path: string): string {\n let p = path.replace(/\\/+$/, \"\") || \"/\";\n const m = p.match(/^\\/([a-z]{2})(\\/|$)/);\n if (m && isLocaleSegment(m[1])) p = p.slice(m[1].length + 1) || \"/\";\n return p;\n}\n\n/** Manifest entries are ours and locale-neutral by contract (see\n * `PageManifestEntry.path`), so they get a trailing-slash trim and nothing\n * else \u2014 no locale strip, no decoding. */\nfunction entrySegments(path: string): string[] {\n return path.replace(/\\/+$/, \"\").split(\"/\").filter(Boolean);\n}\n\n/**\n * Percent-decode until the result stops changing, so a doubly-encoded segment\n * (`%252e`) is judged by what it finally says rather than by its first layer.\n * Malformed encoding, and nesting past any plausible depth, are refusals \u2014\n * there is no legitimate route we would be rejecting.\n */\nfunction decodeFully(raw: string): string | null {\n let cur = raw;\n for (let i = 0; i < 6; i++) {\n if (!cur.includes(\"%\")) return cur;\n let next: string;\n try {\n next = decodeURIComponent(cur);\n } catch {\n return null;\n }\n if (next === cur) return cur;\n cur = next;\n }\n return null;\n}\n\n/**\n * Decode ONE raw path segment into the thing a browser will finally resolve it\n * to, or null when it is not a plain segment at all.\n *\n * This is the half of the gate that has to come FIRST. Judging the raw spelling\n * is a blocklist \u2014 it refuses `..` and admits `%2e%2e`, `%2E%2E`, `.%2e` and\n * `%252e%252e`, every one of which the URL parser resolves as a double-dot\n * segment (or resolves after one more decode hop on the server).\n */\nfunction decodeSegment(raw: string): string | null {\n const decoded = decodeFully(raw);\n if (decoded === null) return null;\n // Compatibility forms fold into ASCII: U+FF0E FULLWIDTH FULL STOP twice is\n // `..` after NFKC, and U+FF0F folds to a solidus.\n const seg = decoded.normalize(\"NFKC\");\n // A segment that decodes into a SEPARATOR smuggled structure past the split:\n // `%2f` and `%5c` are both \"/\" once the URL is parsed.\n if (/[/\\\\]/.test(seg)) return null;\n // C0 controls and DEL. Tab/LF/CR matter most \u2014 the URL parser STRIPS them, so\n // `.%09.` arrives as `..`.\n if (/[\\u0000-\\u001f\\u007f]/.test(seg)) return null;\n return seg;\n}\n\n/**\n * Decode an AI-supplied path into the segments the browser will actually\n * resolve, or null if it is not a root-relative in-app path at all.\n *\n * Decode first, then validate. The caller matches what comes back against the\n * manifest, which is an ALLOW-list; nothing downstream re-inspects spellings.\n */\nfunction navPathSegments(path: unknown): string[] | null {\n if (typeof path !== \"string\") return null;\n // The URL parser strips leading/trailing C0-and-space before it parses.\n if (path !== path.trim()) return null;\n if (!path.startsWith(\"/\")) return null;\n const bare = path.split(/[?#]/)[0] ?? \"\";\n // A backslash is a path separator to the URL parser, so `/\\evil.example.com`\n // is protocol-relative. No route needs one.\n if (bare.includes(\"\\\\\")) return null;\n // Protocol-relative (`//host`), or an empty interior segment.\n if (bare.includes(\"//\")) return null;\n const out: string[] = [];\n for (const raw of bare.split(\"/\")) {\n if (!raw) continue; // the leading \"/\" and one trailing \"/\"\n const seg = decodeSegment(raw);\n if (seg === null) return null;\n // Dot segments are relative MOVES, not names. The host prefixes the account\n // after this runs, so `/reports/../../accounts/<them>/x` climbs straight\n // back out of the account it was just given.\n if (seg === \".\" || seg === \"..\") return null;\n out.push(seg);\n }\n if (out.length && isLocaleSegment(out[0])) out.shift();\n return out;\n}\n\n/**\n * Find the manifest entry that best describes the current path \u2014 the entry\n * whose `path` is the longest suffix of the (normalized) current path.\n *\n * A segment written as `:name` matches ANY one segment, so a detail page can\n * be declared once (\"/reports/:reportId\") instead of being undescribable.\n * Without this a concrete `/reports/<uuid>` matched nothing at all \u2014 not even\n * the list page above it, since the uuid is a further segment and the\n * comparison was a plain suffix test \u2014 so the assistant silently lost all\n * knowledge of which page the user was standing on.\n *\n * Matching is done per SEGMENT rather than by string, because a suffix test\n * cannot tell a parameter from a literal. The longest match still wins, which\n * keeps a specific entry ahead of a general one.\n */\nexport function matchManifest(\n path: string,\n manifest: PageManifestEntry[]\n): PageManifestEntry | undefined {\n const segs = (p: string): string[] => p.split(\"/\").filter(Boolean);\n const pathSegs = segs(normalizePath(path));\n let best: PageManifestEntry | undefined;\n let bestLen = -1;\n for (const e of manifest) {\n const entrySegs = segs(e.path);\n // The root entry (\"/\") describes only the root.\n if (!entrySegs.length) {\n if (!pathSegs.length && bestLen < 0) {\n best = e;\n bestLen = 0;\n }\n continue;\n }\n if (entrySegs.length > pathSegs.length) continue;\n const off = pathSegs.length - entrySegs.length;\n const hit = entrySegs.every(\n (seg, i) => seg.startsWith(\":\") || seg === pathSegs[off + i]\n );\n if (hit && entrySegs.length > bestLen) {\n best = e;\n bestLen = entrySegs.length;\n }\n }\n return best;\n}\n\n/**\n * Is this AI-supplied path one of the pages the manifest actually DECLARES?\n *\n * The manifest is the whole security surface for navigation, and until now it\n * was only ever advice: it went into the prompt as \"Pages you can open (use\n * ONLY these to navigate)\" and nothing checked the answer. The only real gate\n * was a shape test \u2014 starts with one `/`, not `//` or `/\\` \u2014 which every\n * cross-tenant path also passes. `/accounts/<someone-else>/settings` is a\n * root-relative in-app route by that definition, and `orgPath` returns an\n * already-`/accounts/`-prefixed path untouched, so a model that emitted one got\n * it loaded into the deliberately un-sandboxed, same-origin advanced frame.\n *\n * Matched from the START, per segment, unlike `matchManifest` \u2014 which matches a\n * SUFFIX because it is handed a full SPA route and has to find the entry that\n * describes it. Here the input is the account-relative path the model wrote, so\n * the manifest's namespace has to be its PREFIX or the check is no gate at all:\n * a suffix test admits `/accounts/<someone-else>/dashboards` on the strength of\n * the `/dashboards` entry. Extra trailing segments ARE allowed \u2014 that is how a\n * detail page under a declared list (\"/reports/<uuid>\") stays reachable \u2014 and\n * `:param` segments match any one segment, as in the manifest itself.\n *\n * DECODE FIRST, THEN VALIDATE. The first cut of this refused a literal `..`\n * segment by comparing the raw spelling, which is a blocklist \u2014 and a blocklist\n * of shapes is exactly what produced the hole above. `%2e%2e`, `%2E%2E`, `.%2e`\n * and `%252e%252e` all survive a literal comparison and all mean `..` by the\n * time the URL is parsed, so `/reports/%2e%2e/%2e%2e/%2e%2e/accounts/<them>/x`\n * walked straight through it and into the frame. `navPathSegments` resolves the\n * path to what the browser will actually see \u2014 percent-decoded to a fixed\n * point, NFKC-folded, backslashes and protocol-relative forms refused \u2014 and\n * only then is each segment matched against the manifest.\n *\n * A dot segment is refused rather than resolved: the host prefixes the account\n * AFTER this runs, so resolving here would judge a different path than the one\n * the browser resolves, and no real route contains one.\n *\n * NO MANIFEST MEANS NO NAVIGATION. A host that declares no pages has declared\n * nothing the model may open, and falling back to \"any root-relative path\" here\n * would restore exactly the hole this closes.\n */\nexport function isKnownNavTarget(\n path: unknown,\n targets: readonly { path?: string }[] | undefined | null\n): path is string {\n const segs = navPathSegments(path);\n if (segs === null) return false;\n if (!targets?.length) return false;\n for (const t of targets) {\n if (!t.path) continue;\n const entry = entrySegments(t.path);\n // The root entry describes the root, never everything beneath it.\n if (!entry.length) {\n if (!segs.length) return true;\n continue;\n }\n if (entry.length > segs.length) continue;\n if (entry.every((s, i) => s.startsWith(\":\") || s === segs[i])) return true;\n }\n return false;\n}\n\n/** Build a standard PageContext from a path (derives `page`, caps the trail).\n * Pass the app's page `manifest` to also attach the current page's descriptor\n * and the navigable-page catalog. */\nexport function makePageContext(\n app: AppSurface,\n path: string,\n recentPages: string[] = [],\n manifest?: PageManifestEntry[]\n): PageContext {\n const page = path.split(\"/\").filter(Boolean).slice(-1)[0] || \"home\";\n const ctx: PageContext = {\n app,\n path,\n page,\n recentPages: recentPages.slice(-8),\n };\n if (manifest?.length) {\n ctx.pageInfo = matchManifest(path, manifest);\n ctx.navTargets = manifest.map((e) => ({\n path: e.path,\n title: e.title,\n purpose: e.purpose,\n ...(e.action ? { action: e.action } : {}),\n }));\n }\n return ctx;\n}\n\n// NOTE: `formatPageContext` and `STANDARD_ACTIONS` lived here and were deleted\n// 2026-08-05 with zero callers. Both were client-side twins of things that moved\n// server-side: prompt assembly runs on the SERVER (a formatPageContextHint in\n// lib/page-context.ts) and the permitted action set is stated in\n// the backend's prompt builder). Don't rebuild a client catalogue \u2014 it drifted\n// from the live one (12 actions vs 5) the whole time it sat unused.\n\n/**\n * Account-relative path each named standard action opens. Single source of truth\n * shared by `createHostActions` (which navigates the parent app there) and the\n * widget's advanced view (which navigates the iframe there via `resolveActionPath`),\n * so the two can't drift.\n */\nexport const STANDARD_ACTION_PATHS: Record<string, string> = {\n \"open-dashboards\": \"/dashboards\",\n \"open-dashboard-builder\": \"/dashboards/new/edit\",\n \"open-billing\": \"/billing\",\n \"open-assets\": \"/assets\",\n};\n\n/** Human confirmation text for each named standard action (post-navigation). */\nconst STANDARD_ACTION_DONE: Record<string, string> = {\n \"open-dashboards\": \"Opened dashboards\",\n \"open-dashboard-builder\": \"Opened the dashboard builder\",\n \"open-billing\": \"Opened billing\",\n \"open-assets\": \"Opened assets\",\n};\n\n/**\n * Is this action PURE NAVIGATION \u2014 moving the user to a page and nothing else?\n *\n * The auto-navigate option promises exactly this and nothing more. It used to\n * decide by asking whether the model had attached a `confirm` to its own action,\n * which put the safety decision in the model's hands: an action it emitted\n * without one ran instantly and unattended \u2014 and a confirm-less action can\n * spend metered credit, so \"auto-navigate\" could buy things. Navigation is a\n * closed set \u2014 enumerate it, and let everything else wait for the user to\n * press the button.\n */\nexport function isNavigationAction(name: string): boolean {\n // `show-assets` is navigation too \u2014 it opens a folder/file and nothing else,\n // buys nothing and changes nothing. It is listed explicitly rather than via\n // STANDARD_ACTION_PATHS because it carries a target, so it has no fixed path.\n return (\n name === \"navigate\" ||\n name === \"show-assets\" ||\n name in STANDARD_ACTION_PATHS\n );\n}\n\n/** Said instead of \"Opened \u2026\" when the user is ALREADY on that page. */\nconst STANDARD_ACTION_ALREADY: Record<string, string> = {\n \"open-dashboards\": \"Already on dashboards\",\n \"open-dashboard-builder\": \"Already in the dashboard builder\",\n \"open-billing\": \"Already on billing\",\n \"open-assets\": \"Already on assets\",\n};\n\n/**\n * Are we already looking at this route?\n *\n * Navigating to the page you are standing on is not a helpful action, it is\n * noise \u2014 and worse, it reads as a broken one, because nothing visibly happens.\n * Compared on pathname only: a query string or hash is a filter or an anchor\n * within the same page, not a different destination.\n */\n/** Where an account-relative path really lands, mirroring the hosts' own\n * `orgPath`/`adminPath`: already absolute stays put, anything else is prefixed\n * with the account. Kept in step with those two by construction \u2014 they are\n * idempotent for a path that already carries the prefix. */\nfunction resolveTarget(base: string, path: string): string {\n return path.startsWith(\"/accounts/\") ? path : `${base}${path}`;\n}\n\nfunction alreadyThere(path: string): boolean {\n if (typeof window === \"undefined\") return false;\n const strip = (p: string): string =>\n p.length > 1 && p.endsWith(\"/\") ? p.slice(0, -1) : p;\n // The QUERY has to count, not just the path (#134). \"Open the Rooms folder\"\n // is `/assets?folder=\u2026`: judged on pathname alone that is \"already there\"\n // whenever the user happens to be on /assets, so the one navigation most\n // likely to be requested was the one guaranteed to be refused.\n const [targetPath = \"\", targetQuery = \"\"] = path.split(\"?\");\n if (strip(window.location.pathname) !== strip(targetPath)) return false;\n const current = new URLSearchParams(window.location.search);\n const wanted = new URLSearchParams(targetQuery);\n for (const [k, v] of wanted) if (current.get(k) !== v) return false;\n return true;\n}\n\nexport type HostActionHandler = (\n data: Record<string, string>\n) => Promise<string | void> | string | void;\n\nexport interface HostActionsConfig {\n /** Account scope for the `/accounts/:id/...` standard routes (org/admin). */\n accountId?: string;\n /** The host's router push (in-app SPA navigation). */\n navigate: (path: string) => void;\n /** Point a MOUNTED asset library at a folder/asset. Supplied by the host as\n * `showAssetsAt` from @sgiant/assets \u2014 the router push alone cannot reach a\n * library that is already on screen. */\n showAssets?: (t: {\n folderId?: string | null;\n assetId?: string | null;\n }) => void;\n /** App-specific actions, merged over the standard set (can override). */\n handlers?: Record<string, HostActionHandler>;\n /**\n * The page manifest this host declares \u2014 the SAME catalogue the model is\n * offered as \"Pages you can open\". A model-authored `navigate` path is\n * checked against it (see `isKnownNavTarget`) before the router is touched,\n * so the list stops being advice and becomes the gate. A host that passes\n * none can navigate nowhere.\n */\n pages?: readonly { path?: string }[];\n /**\n * The host's translator, for the CHIP OUTCOMES below.\n *\n * Every visible string the widget itself renders goes through\n * `WIDGET_LABELS`, which is what lets a host translate the chrome. These\n * outcomes were plain English record literals returned straight into the chip\n * text, so a Turkish session read \"Opened dashboards\" and \"Already there\" in\n * the middle of a Turkish conversation. Optional so a host that passes none\n * keeps today's English.\n */\n t?: (key: string, defaultValue: string) => string;\n}\n\n/**\n * Build the `onWidgetAction(name, data)` handler from the standard allow-listed\n * actions + any app-specific handlers. Pass to `createAiChatWidget`.\n */\nexport function createHostActions(\n cfg: HostActionsConfig\n): (action: string, data: Record<string, string>) => Promise<string | void> {\n const base = cfg.accountId ? `/accounts/${cfg.accountId}` : \"\";\n const tr = cfg.t ?? ((_k: string, d: string) => d);\n const standard: Record<string, HostActionHandler> = {\n // Deep-link INTO the library: a folder and/or one file's preview.\n //\n // Two steps on purpose. If the user is elsewhere, the host router has to\n // open /assets first; if they are already there, a same-route push fires no\n // popstate and does not remount, so the library would never hear it \u2014 hence\n // showAssetsAt, which writes the query AND announces it (#134).\n \"show-assets\": (data) => {\n const folderId = data.folderId || null;\n const assetId = data.assetId || null;\n if (!folderId && !assetId) return;\n const query = new URLSearchParams();\n if (folderId) query.set(\"folder\", folderId);\n if (assetId) query.set(\"asset\", assetId);\n const path = `${STANDARD_ACTION_PATHS[\"open-assets\"]}?${query}`;\n if (!alreadyThere(resolveTarget(base, path))) cfg.navigate(path);\n cfg.showAssets?.({ folderId, assetId });\n return assetId\n ? tr(\"aiAssistant.action.actionOpenedFile\", \"Opened the file\")\n : tr(\"aiAssistant.action.actionOpenedFolder\", \"Opened the folder\");\n },\n navigate: (data) => {\n if (data.path) {\n // The manifest decides, not the path's shape. Floating mode hands the\n // path to the host's own router, which is the same session and the same\n // origin as the advanced frame \u2014 so it needs the same gate, refused out\n // loud rather than by returning undefined (an undefined return resolves\n // as success and the chip ticks green over a path we declined).\n if (!isKnownNavTarget(data.path, cfg.pages))\n return tr(\n \"aiAssistant.action.actionNotAPage\",\n \"That is not a page I can open\"\n );\n // Compare what the host will ACTUALLY open, not what the model wrote.\n // The AI emits account-relative paths (\"/assets\"); the host prefixes\n // the account before routing. Testing the bare path against the real\n // location never matched, so \"you are already here\" could not fire on\n // the one action most likely to be aimed at the current page.\n if (alreadyThere(resolveTarget(base, data.path)))\n return tr(\"aiAssistant.action.actionAlreadyThere\", \"Already there\");\n cfg.navigate(data.path);\n return tr(\"aiAssistant.action.actionOpened\", \"Opened\");\n }\n },\n };\n // The named \"open-*\" actions all resolve to a fixed account-relative path (see\n // STANDARD_ACTION_PATHS) \u2014 generate their handlers so the paths live in ONE\n // place the advanced-view resolver also reads.\n for (const [name, path] of Object.entries(STANDARD_ACTION_PATHS)) {\n standard[name] = () => {\n const target = `${base}${path}`;\n if (alreadyThere(target))\n return tr(\n `aiAssistant.action.actionAlready_${name.replace(/-/g, \"_\")}`,\n STANDARD_ACTION_ALREADY[name] ?? \"Already there\"\n );\n cfg.navigate(target);\n return tr(\n `aiAssistant.action.actionDone_${name.replace(/-/g, \"_\")}`,\n STANDARD_ACTION_DONE[name] ?? \"Opened\"\n );\n };\n }\n const map: Record<string, HostActionHandler> = {\n ...standard,\n ...(cfg.handlers ?? {}),\n };\n return async (action, data) => {\n // Read-only UI control (highlight / scroll-to / focus-field) works on EVERY\n // surface via the pure-DOM twin \u2014 no per-app wiring, no confirm (reversible).\n if (isUiControlAction(action)) {\n const ok = runUiControl(action, data.target ?? \"\");\n // A target that isn't on this page is a FAILURE. Returning undefined\n // resolved the widget's promise and rendered a tick, so \"show me where\"\n // ticked green while nothing was highlighted.\n if (!ok) throw new Error(`no such control on this page: ${data.target}`);\n return tr(\"aiAssistant.action.actionShownOnPage\", \"Shown on the page\");\n }\n // State-changing UI control (fill / click). The widget has already forced a\n // Confirm step before we get here (renderAction), so this runs post-approval.\n if (isOperateAction(action)) {\n const ok = runOperateAction(action, data.target ?? \"\", data.value);\n // Same as above, and worse here: the user APPROVED this one. Telling them\n // a fill or a click succeeded when the control was never found means they\n // walk away believing a value was entered.\n if (!ok) throw new Error(`no such control on this page: ${data.target}`);\n return tr(\"aiAssistant.action.actionDoneOnPage\", \"Done on the page\");\n }\n const fn = map[action];\n // THROW, don't shrug. Returning undefined here resolved the widget's\n // promise, and a resolved promise is rendered as \"<label> \u2713\" \u2014 so an action\n // this app cannot perform reported itself as done. That is how \"Opening\n // Open Assets\u2026\" turned into a tick next to a page that never opened. An\n // action we do not have is a failure, and the chip must say so.\n if (!fn) throw new Error(`unsupported action: ${action}`);\n return await fn(data);\n };\n}\n", "/**\n * Every persisted value the widget owns, and the two rules that govern all of\n * them.\n *\n * Extracted from index.ts (#320). Storage was not one region of that file \u2014 it\n * was 33 `localStorage` touches spread across 8 key-scoped concerns, from the\n * draft textarea to the launcher position to the advanced-pane width. Each site\n * re-implemented the same two rules, which is how they drifted:\n *\n * 1. AN EMPTY KEY MEANS PERSISTENCE IS OFF. Most keys are derived from\n * `opts.persistKey`, which is optional: no persistKey, no writing to a\n * visitor's browser. That was eight separate `if (!key) return` guards.\n * 2. BLOCKED STORAGE IS NON-FATAL. Private windows, cleared site data and\n * \"block third-party cookies\" all throw on access rather than returning\n * null, so every read and every write needs a try/catch. That was ten\n * of them, each with its own comment saying the same thing.\n *\n * THE ONE PLACE THOSE RULES ARE NOT UNIFORM is the point of collecting them.\n * `hasOpenedBefore` returns TRUE when storage is unreadable, while every other\n * flag defaults to false. That is deliberate \u2014 a returning reader should get\n * the quiet pebble, not the first-visit pill on every single page load \u2014 but in\n * index.ts it was one `return true` inside one of ten identical-looking catch\n * blocks, indistinguishable from a typo. Here it is an argument with a name.\n *\n * These helpers take the key rather than closing over it, so they are callable\n * from anywhere and testable without constructing a widget.\n */\n\n/**\n * A key that names a real slot.\n *\n * \"Persistence is off\" is spelled TWO ways in the caller \u2014 `null` for the five\n * keys derived from `opts.persistKey`, `\"\"` for the two derived from the\n * advanced pane. Neither spelling is wrong and unifying them in index.ts would\n * be a behaviour-free diff across a 5800-line file, so this is the one place\n * that has to know both, and the only place that should.\n */\ntype StorageKey = string | null | undefined;\nconst live = (key: StorageKey): key is string => Boolean(key);\n\n/**\n * Read a string. Returns `fallback` when the key is off, absent, or storage is\n * unreadable \u2014 the three cases callers have never needed to tell apart.\n */\nexport function readItem(key: StorageKey, fallback = \"\"): string {\n if (!live(key)) return fallback;\n try {\n return localStorage.getItem(key) ?? fallback;\n } catch {\n return fallback;\n }\n}\n\n/** Write a string, or REMOVE the key when given an empty value or null. */\nexport function writeItem(key: StorageKey, value: string | null): void {\n if (!live(key)) return;\n try {\n if (value) localStorage.setItem(key, value);\n else localStorage.removeItem(key);\n } catch {\n /* storage full or blocked \u2014 the value just does not persist */\n }\n}\n\n/**\n * Read a `\"1\"`/`\"0\"` flag.\n *\n * ABSENT AND UNREADABLE ARE DIFFERENT STATES, and collapsing them is a bug this\n * function was written with and had to have removed. A key that is simply not\n * set means \"false\" \u2014 the flag has never been turned on. `whenUnavailable` is\n * only for the case where storage THREW, i.e. the answer is unknowable rather\n * than no.\n *\n * The distinction is invisible in four of the five callers, whose answer is\n * false either way. In the fifth it inverts the first-visit experience: a\n * first-ever visitor has no `sg_ayca_opened` key, and answering \"unavailable\"\n * there would greet them as a returning reader and suppress the pill they are\n * supposed to see.\n */\nexport function readFlag(key: StorageKey, whenUnavailable = false): boolean {\n if (!live(key)) return false;\n try {\n return localStorage.getItem(key) === \"1\";\n } catch {\n return whenUnavailable;\n }\n}\n\n/** Write a `\"1\"`/`\"0\"` flag. */\nexport function writeFlag(key: StorageKey, on: boolean): void {\n writeItem(key, on ? \"1\" : \"0\");\n}\n\n/**\n * Read JSON and hand it to a validator before returning it.\n *\n * The validator is required rather than optional on purpose: everything in\n * localStorage was written by a PREVIOUS BUILD of this widget and may not match\n * the current shape. Two of the callers here already learned that \u2014 the tracked\n * jobs list deliberately accepts entries with no `kind` because a build that\n * predates the field wrote them, and dropping those would lose the card for\n * work still running across a deploy.\n */\nexport function readJson<T>(\n key: StorageKey,\n isValid: (value: unknown) => value is T\n): T | null {\n if (!live(key)) return null;\n try {\n const raw = localStorage.getItem(key);\n if (!raw) return null;\n const parsed: unknown = JSON.parse(raw);\n return isValid(parsed) ? parsed : null;\n } catch {\n return null; // corrupt, or unreadable \u2014 either way, start fresh\n }\n}\n\n/** Write JSON, or REMOVE the key when given null. */\nexport function writeJson(key: StorageKey, value: unknown | null): void {\n if (!live(key)) return;\n try {\n if (value === null) localStorage.removeItem(key);\n else localStorage.setItem(key, JSON.stringify(value));\n } catch {\n /* storage full or blocked \u2014 non-fatal */\n }\n}\n", "/**\n * The constants this widget used to import from `@sgiant/shared`, inlined.\n *\n * #306 requires the published core to have ZERO `@sgiant/*` runtime\n * dependencies \u2014 a stranger installing the widget from npm cannot resolve a\n * package that lives in our private registry, and three numbers are not a\n * reason to make them try.\n *\n * BUT inlining is the exact move that created the bug `@sgiant/shared`'s\n * `chat-attachment-limits.ts` was written to end: the client caps and the api's\n * turn-time caps were four independent literals, so a 10 MB PNG passed every\n * client check, uploaded fine, showed as a chip, and was silently dropped when\n * the turn resolved it.\n *\n * So the copy is gated instead of trusted: a drift test in the host repo\n * asserts every value here equals its `@sgiant/shared` original. The test is a\n * monorepo devDependency, so it costs the published package nothing and still\n * fails the build the moment the two drift.\n *\n * If you change a number here, change it in `@sgiant/shared` too \u2014 or rather,\n * change it there and let the test tell you about this file.\n */\n\n/** Attachments per message (composer slots AND turn-time resolution). */\nexport const CHAT_ATTACHMENT_MAX_COUNT = 6;\n\n/** Client-side transport cap per file. */\nexport const CHAT_ATTACHMENT_MAX_BYTES = 25 * 1024 * 1024;\n\n/** Transport cap for an attached VIDEO or AUDIO file \u2014 much larger, because\n * 25 MB is a document number and a 30-second 1080p phone clip is already past\n * it. A clip is stored as an asset and referenced BY ID, never sent as bytes. */\nexport const CHAT_ATTACHMENT_MAX_AV_BYTES = 100 * 1024 * 1024;\n\n/** The locale tags the host may pass. Was `import type { Locale }` from\n * `@sgiant/shared`; a type import erases at runtime but still puts the package\n * in the manifest, which is what publishing reads. */\nexport type Locale = \"en\" | \"tr\";\n\n/**\n * The six motion custom properties the stylesheet reads, previously\n * `motionCssVars()` from `@sgiant/tokens`.\n *\n * Kept as a function with the same shape so the call site is unchanged, and\n * gated by the same drift test.\n */\nexport function motionCssVars(): string {\n return [\n `--duration-fast: 150ms;`,\n `--duration-base: 300ms;`,\n `--duration-slow: 600ms;`,\n `--ease-out: cubic-bezier(0.22, 1, 0.36, 1);`,\n `--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);`,\n `--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);`,\n ].join(\"\\n\");\n}\n", "/**\n * Accent/foreground contrast, WCAG 2.1 relative luminance (#307).\n *\n * WHY THIS EXISTS: the widget shipped `--aiw-accent-contrast: #fff`, correct\n * against its own violet default (7.10:1) and never overridden by a single\n * host. All three of ours pass a brand accent and none passes a contrast\n * colour, so every accent-filled control rendered white on teal `#60C7C8` \u2014\n * **2.00:1**, against a 4.5:1 floor. Seventeen declarations, thirteen controls,\n * four surfaces.\n *\n * A default cannot fix that, because the failing value IS the default. The\n * pairing has to be DERIVED from whatever accent arrives \u2014 which also makes the\n * widget safe to publish (#306), where we will not control the accent at all.\n *\n * No dependency on @sgiant/tokens on purpose: this file has to survive being\n * extracted into a public package.\n */\n\n/** The two foregrounds we choose between. Both are brand values, and both are\n * near enough to black/white to behave sensibly for an arbitrary accent a\n * third-party host passes. */\nexport const CONTRAST_DARK = \"#151D2F\"; // brand navy\nexport const CONTRAST_LIGHT = \"#FCF7E3\"; // brand cream\n\n/** #rgb, #rrggbb, or #rrggbbaa \u2192 [r,g,b] 0-255. Null when unparseable \u2014 a\n * caller must then keep whatever it already had rather than guess. */\nfunction parseHex(hex: string): [number, number, number] | null {\n const h = hex.trim().replace(/^#/, \"\");\n if (h.length === 3) {\n const [r, g, b] = h.split(\"\");\n if (!/^[0-9a-f]{3}$/i.test(h)) return null;\n return [parseInt(r + r, 16), parseInt(g + g, 16), parseInt(b + b, 16)];\n }\n if ((h.length === 6 || h.length === 8) && /^[0-9a-f]+$/i.test(h)) {\n return [\n parseInt(h.slice(0, 2), 16),\n parseInt(h.slice(2, 4), 16),\n parseInt(h.slice(4, 6), 16),\n ];\n }\n return null;\n}\n\n/** WCAG 2.1 relative luminance. */\nexport function relativeLuminance(hex: string): number | null {\n const rgb = parseHex(hex);\n if (!rgb) return null;\n const [r, g, b] = rgb.map((c) => {\n const s = c / 255;\n return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);\n }) as [number, number, number];\n return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n}\n\n/** WCAG contrast ratio, 1\u201321. Null when either colour is unparseable. */\nexport function contrastRatio(a: string, b: string): number | null {\n const la = relativeLuminance(a);\n const lb = relativeLuminance(b);\n if (la === null || lb === null) return null;\n return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);\n}\n\n/**\n * The foreground to use on a given accent: whichever of dark/light contrasts\n * more. Returns null for anything it cannot parse \u2014 `color-mix()`, a CSS\n * variable, `rebeccapurple` \u2014 so the caller leaves the host's own value alone\n * instead of overwriting it with a guess.\n *\n * Measured on the accents this repo actually ships: teal \u2192 navy 8.41, amber \u2192\n * navy 8.73, orange \u2192 navy 5.98, and the violet default \u2192 cream 6.62. All four\n * clear 4.5:1, which the host repo's contrast test asserts, so the\n * next brand-colour change cannot quietly undo it.\n */\nexport function resolveAccentContrast(accent: string): string | null {\n const dark = contrastRatio(accent, CONTRAST_DARK);\n const light = contrastRatio(accent, CONTRAST_LIGHT);\n if (dark === null || light === null) return null;\n return dark >= light ? CONTRAST_DARK : CONTRAST_LIGHT;\n}\n\n/**\n * Mix two hex colours in sRGB, the same way CSS `color-mix(in srgb, \u2026)` does.\n *\n * Exists because the user's message bubble is not painted with the accent \u2014 it\n * is painted with the accent mixed 76% into a near-black navy, and a foreground\n * derived from the RAW accent is derived against the wrong background. Over the\n * teal all three hosts pass, that bubble resolves to `#4a9d9e` and the sheet's\n * literal `#fff` measures **3.18:1** on it: #307's defect, still live in the\n * most-used element in the widget, because the fix was applied to the token and\n * the bubble never read the token.\n *\n * The mix is computed HERE and handed to CSS as a resolved value, rather than\n * computed here AND written as a `color-mix()` next door. Two implementations of\n * one blend is how the foreground and the background drift apart, which is the\n * whole bug again one level down.\n *\n * Null when either colour is unparseable \u2014 the caller must then leave whatever\n * was there alone rather than guess, exactly as `resolveAccentContrast` does.\n */\nexport function mixSrgb(a: string, b: string, ratioA: number): string | null {\n const pa = parseHex(a);\n const pb = parseHex(b);\n if (!pa || !pb) return null;\n const p = Math.min(1, Math.max(0, ratioA));\n const chan = (i: number): string =>\n Math.round(pa[i] * p + pb[i] * (1 - p))\n .toString(16)\n .padStart(2, \"0\");\n return `#${chan(0)}${chan(1)}${chan(2)}`;\n}\n\n/**\n * The accent, adjusted until it is legible as TEXT on a given surface.\n *\n * `--aiw-accent` is a FILL colour. Twenty rules also use it as ink \u2014 links,\n * hover states, the focused input, chips, KPI deltas, the menu icons, the\n * active vote \u2014 and a fill is not an ink. Measured against the surfaces this\n * sheet actually paints:\n *\n * teal #60C7C8 (what all three hosts pass) 2.00:1 on white, 9.05 on dark\n * violet #6d28d9 (the widget's own default) 7.10:1 on white, 2.55 on dark\n * amber #FBAA34 1.93:1 on white, 9.39 on dark\n *\n * So it is not \"the teal is a bad accent\". Every accent is illegible as ink on\n * one of the two schemes; the widget's own default is the one that fails in the\n * dark. Which scheme breaks depends only on where the accent sits between the\n * two surfaces, and that is a property of the colour, not a mistake in it.\n *\n * The adjustment blends toward black or white \u2014 whichever the surface is\n * further from \u2014 and returns the FIRST step that clears the floor, so the ink\n * stays as close to the brand accent as legibility allows rather than jumping\n * to a safe navy. Hue survives; some chroma does not.\n *\n * 5% steps because that is finer than the eye reads as a colour change and\n * still terminates in twenty iterations. Null when the accent is unparseable \u2014\n * the caller leaves the token alone and the sheet's own default applies, which\n * is the same contract as `resolveAccentContrast`.\n */\nexport function accentInk(\n accent: string,\n surface: string,\n target = 4.5\n): string | null {\n const surfaceLum = relativeLuminance(surface);\n if (surfaceLum === null || parseHex(accent) === null) return null;\n const pole = surfaceLum > 0.5 ? \"#000000\" : \"#ffffff\";\n for (let p = 1; p >= 0; p -= 0.05) {\n const candidate = mixSrgb(accent, pole, p);\n if (!candidate) return null;\n const ratio = contrastRatio(candidate, surface);\n if (ratio !== null && ratio >= target) return candidate;\n }\n return pole;\n}\n", "/**\n * TWO DECISIONS THE APP PANE MAKES, extracted so they can be checked.\n *\n * Both live inside `createAiChatWidget`'s closure in the real widget, where\n * they are unreachable from a test \u2014 and both are the kind of rule that fails\n * politely: get them wrong and the pane either never follows a long operation\n * (the feature silently does nothing) or follows it too eagerly (it yanks a\n * reader off the page they chose, repeatedly, on every poll). Neither shows up\n * as an error.\n */\n\n/** Same-path test used for both decisions. A query string is a filter within a\n * page, not a different destination; a trailing slash is not a destination\n * either. */\nexport function samePanePath(\n a: string | null | undefined,\n b: string | null | undefined\n): boolean {\n if (!a || !b) return false;\n const norm = (p: string): string => {\n const path = p.split(\"?\")[0] ?? \"\";\n return path.length > 1 && path.endsWith(\"/\") ? path.slice(0, -1) : path;\n };\n return norm(a) === norm(b);\n}\n\nexport interface AutoNavigateInput {\n /** Advanced view is open. There is no pane to move otherwise, and moving the\n * whole PAGE because a background job started is a different feature. */\n advanced: boolean;\n /** The host wired `getAdvancedUrl` \u2014 without it there is no embed URL. */\n canNavigate: boolean;\n /**\n * The user CONFIRMED this operation in this session \u2014 they pressed the button\n * that started it, in this widget, just now.\n *\n * The other three conditions all ask whether following would be RUDE. This\n * one asks whether the operation is the user's at all. A tracked job outlives\n * the tab: it is written to localStorage so a running import survives a\n * refresh, and it is also re-discovered from the server listing so work\n * started on another device shows up here. Neither of those is a thing the\n * person in front of the screen just asked for, and \"advanced view was opened\"\n * is not consent to be sent to a page some other tab started building.\n */\n userConfirmedThisSession: boolean;\n /** The user has driven the frame themselves since this session began. */\n userDriven: boolean;\n /** This operation has already been followed once. */\n alreadyFollowed: boolean;\n /** The path to follow to, as the model/host supplied it. */\n path: string | null | undefined;\n /** Where the pane is now \u2014 skip the reload if it is already there. */\n currentPath?: string | null;\n}\n\n/**\n * Should the pane follow a long operation to its live page?\n *\n * Every `false` here is a way this becomes obnoxious rather than helpful, and\n * the third one is the one that bites hardest: a report card polls every couple\n * of seconds, so a decision that does not remember it already followed would\n * reload the page continuously \u2014 turning \"watch it build\" into a surface that\n * cannot be read at all.\n */\nexport function shouldAutoNavigate(i: AutoNavigateInput): boolean {\n if (!i.advanced || !i.canNavigate) return false;\n if (!i.userConfirmedThisSession) return false;\n if (i.userDriven || i.alreadyFollowed) return false;\n if (!i.path || !i.path.startsWith(\"/\") || i.path.startsWith(\"//\")) {\n return false;\n }\n // Already there: following again would be a pointless full document load.\n if (samePanePath(i.currentPath, i.path)) return false;\n return true;\n}\n\n/**\n * Should the chat column collapse its narration to one line?\n *\n * True exactly when the pane is showing the same run \u2014 the owner's decision for\n * advanced mode. The report page owns the detail (it has the stage rail and it\n * is the authority on stage), and two renderings of one job on one screen is\n * the divergence advanced view makes visible. When the pane is elsewhere the\n * chat is the ONLY account of what is happening, so it narrates in full.\n */\nexport function shouldCollapseNarration(i: {\n advanced: boolean;\n livePath: string | null | undefined;\n currentPath: string | null | undefined;\n}): boolean {\n if (!i.advanced) return false;\n return samePanePath(i.currentPath, i.livePath);\n}\n", "/**\n * The `[[tag:{json}]]` directive extractor the chat widget runs over every\n * assistant reply.\n *\n * Lives in its own module because it is pure and because getting it wrong is\n * VISIBLE: whatever this function fails to consume is shown to the user as raw\n * text in the middle of a sentence. That makes it worth pinning down with\n * tests, which the widget itself (imperative DOM, no harness) cannot be.\n */\n\nexport interface ParsedDirective<T> {\n spec: T;\n /** The message with this directive removed. */\n stripped: string;\n start: number;\n /** Index just past everything consumed \u2014 the directive leaves no residue. */\n end: number;\n}\n\n/**\n * Pull the FIRST `[[tag:{json}]]` out of `text`.\n *\n * Forgiving by design, because the delimiters are the part a model gets wrong\n * most often and every slip is shown to the user as raw punctuation:\n *\n * - the JSON object is BRACE-MATCHED, string- and escape-aware, so a `}` or\n * `]]` inside a quoted caption cannot end it early;\n * - 0\u20132 trailing `]` are consumed, so `\u2026}]]`, `\u2026}]` and `\u2026}` all parse;\n * - a surplus `}` before the brackets is consumed too. A model that closes one\n * brace too many is common, and it used to leave `}]]` sitting in the reply:\n * the matcher stopped at the REAL closing brace (correctly), then looked for\n * `]`, found `}`, and gave up \u2014 so the card rendered perfectly and the user\n * still saw `}]]` printed above it.\n *\n * Only ADJACENT residue is swallowed. Once a character is neither `}` nor `]`,\n * consumption stops, so nothing the assistant actually wrote is eaten.\n */\nexport function parseJsonDirective<T>(\n text: string,\n tag: string\n): ParsedDirective<T> | null {\n const open = `[[${tag}:`;\n const start = text.indexOf(open);\n if (start < 0) return null;\n const braceStart = text.indexOf(\"{\", start + open.length);\n if (braceStart < 0) return null;\n // Walk the JSON object, tracking string state, to find its true closing brace.\n let depth = 0;\n let inStr = false;\n let esc = false;\n let end = -1;\n for (let i = braceStart; i < text.length; i++) {\n const ch = text[i];\n if (inStr) {\n if (esc) esc = false;\n else if (ch === \"\\\\\") esc = true;\n else if (ch === '\"') inStr = false;\n } else if (ch === '\"') inStr = true;\n else if (ch === \"{\") depth++;\n else if (ch === \"}\") {\n depth--;\n if (depth === 0) {\n end = i;\n break;\n }\n }\n }\n if (end < 0) return null;\n try {\n const spec = JSON.parse(text.slice(braceStart, end + 1)) as T;\n let after = end + 1;\n // Surplus closing braces the model added past the object's real end.\n // Bounded so a genuine `}` in following prose is never swallowed wholesale.\n let extraBraces = 0;\n while (text[after] === \"}\" && extraBraces < 3) {\n after++;\n extraBraces++;\n }\n // Then the closing brackets \u2014 0, 1 or 2.\n if (text[after] === \"]\") after++;\n if (text[after] === \"]\") after++;\n const stripped = (text.slice(0, start) + text.slice(after)).trim();\n return { spec, stripped, start, end: after };\n } catch {\n return null;\n }\n}\n", "/**\n * The component vocabulary the assistant composes chat UI from \u2014 `[[ui:{\u2026}]]`.\n *\n * WHY THIS EXISTS. Every rich thing the chat could draw used to need frontend\n * code: a card kind here, a host renderer there, a release before the model\n * could show it. So the chat could only ever show what the UI had been built to\n * show, and each new scenario (a storyboard, a shortlist, a room-type picker)\n * meant the same work again. This is the substrate that ends that: a small set\n * of primitives the widget knows how to draw, composed BY THE MODEL per\n * situation. A new scenario becomes a spec, not a release.\n *\n * It is deliberately a VOCABULARY and not a language. The model cannot send\n * markup or code \u2014 it names primitives (a tile, a status, a button) and the\n * widget draws them in its own styles. That keeps every card looking like the\n * product rather than like whatever the model imagined this time, and it keeps\n * model output out of the DOM.\n *\n * Everything here is PURE so it can be tested without a browser: the widget is\n * imperative DOM with no test harness, and the rules below (what is dropped,\n * what is capped, what a missing field means) are exactly the part worth\n * pinning down.\n */\n\n/**\n * How `items` are laid out. Unknown values fall back to a strip.\n *\n * \"carousel\" shows ONE item at a time with paging, which is the right shape for\n * a set the client is judging rather than scanning: a reel's scenes want the\n * space to be looked at properly, and a strip of six thumbnails invites\n * approving what was never really seen. The other layouts remain right for\n * picking from many (grid) or reading down a short list (list).\n */\nexport type UiLayout = \"strip\" | \"grid\" | \"list\" | \"carousel\";\n\n/**\n * The one action name the WIDGET answers itself: send `data.text` (falling back\n * to the button's label) back into the conversation as if the user had typed\n * it. Everything else goes to the host by name.\n *\n * This is what makes the vocabulary self-sufficient. Without it, every button a\n * composed card wants \u2014 \"Redo scene 2\", \"Make it warmer\", \"Use the sunset one\"\n * \u2014 is a capability the HOST has to implement and name in advance, which is the\n * per-scenario frontend work this whole design exists to remove. With it, the\n * assistant's own tools are the implementation: the button says the thing, the\n * assistant does it, and a card can offer an action nobody wrote code for.\n *\n * Precedent, not novelty: `[[chips:\u2026]]` already answers by sending the chosen\n * text as an ordinary message, which keeps the transcript in order and honest \u2014\n * the click is visible in the conversation as the sentence it stood for.\n */\nexport const UI_SAY_ACTION = \"say\";\n\n/**\n * A button on a card or tile. Dispatched through the host's existing\n * `onWidgetAction(name, data)` \u2014 the same path `[[action:\u2026]]` already uses, so\n * a UI card can only ever do what the host already permits by name \u2014 except for\n * `say` (see UI_SAY_ACTION), which the widget answers itself.\n */\nexport interface UiAction {\n name: string;\n label?: string;\n /** When set, the user must approve before the host is called. */\n confirm?: string;\n data?: Record<string, string>;\n variant?: \"primary\" | \"default\" | \"danger\";\n}\n\n/** One thing in the card: a picture/clip with a caption, a state and buttons. */\nexport interface UiItem {\n /** Caller's own id for this item, echoed back in an action's `data.itemId`\n * so a handler knows WHICH tile was acted on without the model having to\n * repeat it in every button. */\n id?: string;\n title?: string;\n caption?: string;\n /** Library media to show. An ID, never a URL \u2014 see `sanitizeMediaId`. */\n mediaId?: string;\n /** Still to show for a video tile before it plays. */\n posterMediaId?: string;\n /** How to draw `mediaId`. Inferred by the widget when omitted \u2014 but a signed\n * URL carries no extension, so AUDIO in particular must be declared or it\n * is drawn as a broken picture. */\n media?: \"image\" | \"video\" | \"audio\";\n /** Drawn as a badge; a known one is also coloured. */\n status?: UiStatus;\n /**\n * A running job this tile is the view of \u2014 the card stops being a snapshot.\n *\n * Without it, a storyboard card drawn while six clips render is stale the\n * moment it paints: the client scrolls back an hour later to a rail of\n * \"rendering\" badges over empty boxes, with the finished clips sitting in the\n * library. The tile subscribes to the job's existing poll and fills in its\n * own media and status when the render lands.\n *\n * The GENERIC job id (what `/accounts/:id/jobs/:jobId` answers), not a\n * media_job detail id \u2014 the wrong one 404s, and a 404 makes the widget\n * conclude the job is gone.\n */\n jobId?: string;\n actions?: UiAction[];\n}\n\nexport interface UiSpec {\n title?: string;\n /** A sentence above the items \u2014 what the card is for. */\n caption?: string;\n layout: UiLayout;\n /** Shape of every tile's media box. */\n aspect: UiAspect;\n items: UiItem[];\n /** Card-level buttons, under the items. */\n actions: UiAction[];\n}\n\n/**\n * The shape tiles are drawn in \u2014 named format keywords (\"square\" 1:1,\n * \"portrait\" 4:5, \"story\" 9:16, \"landscape\" 4:3, \"wide\" 16:9) rather than\n * raw ratios, so the model describes a card in the same vocabulary the\n * social platforms use.\n *\n * Card-level rather than per-tile: the items of one card share a format, and\n * a strip whose tiles were each a different shape would read as a mistake. It\n * matters because portrait media previewed in square tiles shows the client a\n * crop of what they are looking at, and they approve it anyway.\n */\nexport type UiAspect = \"square\" | \"portrait\" | \"story\" | \"landscape\" | \"wide\";\n\nconst ASPECTS: readonly string[] = [\n \"square\",\n \"portrait\",\n \"story\",\n \"landscape\",\n \"wide\",\n];\n\n/**\n * States a tile can be in. Kept OPEN \u2014 an unrecognised status is still shown,\n * just without semantic colour, because the alternative is that the model\n * cannot describe a state until the frontend ships a name for it, which is the\n * exact coupling this whole file exists to remove.\n */\nexport type UiStatus = string;\n\n/** The four the widget colours. Everything else is drawn neutral. */\nexport const KNOWN_UI_STATUSES = [\n \"pending\",\n \"running\",\n \"ready\",\n \"failed\",\n] as const;\n\n/** Words the model reaches for that mean one of the four. Being forgiving here\n * is cheaper than a badge that silently loses its colour because the model\n * wrote \"rendering\" instead of \"running\". */\nconst STATUS_SYNONYMS: Record<string, string> = {\n queued: \"pending\",\n waiting: \"pending\",\n rendering: \"running\",\n generating: \"running\",\n processing: \"running\",\n in_progress: \"running\",\n done: \"ready\",\n complete: \"ready\",\n completed: \"ready\",\n ok: \"ready\",\n success: \"ready\",\n error: \"failed\",\n cancelled: \"failed\",\n canceled: \"failed\",\n};\n\n/**\n * Caps. A spec is model output, so it is not a promise of reasonable size: an\n * 800-item strip would lock the DOM, and a 4000-character caption would push\n * the composer off screen. Generous enough that no honest card hits them.\n */\nconst MAX_ITEMS = 24;\nconst MAX_ACTIONS = 4;\nconst MAX_TEXT = 240;\nconst MAX_DATA_KEYS = 12;\n\nconst clamp = (v: unknown, max = MAX_TEXT): string | undefined => {\n if (typeof v !== \"string\") return undefined;\n const s = v.trim();\n if (!s) return undefined;\n return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;\n};\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n Boolean(v) && typeof v === \"object\" && !Array.isArray(v);\n\n/**\n * A media id, or nothing.\n *\n * The model may name library media only BY ID \u2014 never by URL. Model output is\n * untrusted (it routinely carries text scraped from third-party pages), and a\n * URL it chose would be fetched by the client's browser the moment the card\n * painted: a tracking pixel, or a request to an attacker's host carrying the\n * user's IP, drawn inside the product's own chat. An id can only ever be\n * resolved by the host against media the ACCOUNT owns, so the worst case is a\n * tile that resolves to nothing.\n *\n * Shape-checked rather than trusted: ids are opaque to the widget, so anything\n * that isn't a plain id-looking token is refused.\n */\nfunction sanitizeMediaId(v: unknown): string | undefined {\n if (typeof v !== \"string\") return undefined;\n const s = v.trim();\n if (!s || s.length > 64) return undefined;\n return /^[A-Za-z0-9_-]+$/.test(s) ? s : undefined;\n}\n\n/**\n * Canonicalize a status we recognise; otherwise hand back the model's own\n * wording untouched.\n *\n * The match is done on a squashed key so \"In Progress\" and \"in-progress\" both\n * land on `running`, but an unrecognised status is DISPLAYED, and displaying\n * the squashed key would put `awaiting_client_sign_off` on a badge in front of\n * a client. The open half of this vocabulary is only useful if the words the\n * model chose survive it.\n */\nfunction normalizeStatus(v: unknown): string | undefined {\n const s = clamp(v, 32);\n if (!s) return undefined;\n const key = s.toLowerCase().replace(/[\\s-]+/g, \"_\");\n if (STATUS_SYNONYMS[key]) return STATUS_SYNONYMS[key];\n return (KNOWN_UI_STATUSES as readonly string[]).includes(key) ? key : s;\n}\n\n/** Action `data` is passed to the host verbatim, so it is flattened to plain\n * strings \u2014 a nested object would reach a handler expecting a value and fail\n * somewhere far from here. */\nfunction normalizeData(v: unknown): Record<string, string> | undefined {\n if (!isRecord(v)) return undefined;\n const out: Record<string, string> = {};\n for (const [k, raw] of Object.entries(v)) {\n if (Object.keys(out).length >= MAX_DATA_KEYS) break;\n if (\n typeof raw === \"string\" ||\n typeof raw === \"number\" ||\n typeof raw === \"boolean\"\n )\n out[k] = String(raw);\n }\n return Object.keys(out).length ? out : undefined;\n}\n\n/**\n * One button, or null.\n *\n * An action with no `name` is DROPPED rather than drawn inert: a button that\n * dispatches nothing is indistinguishable from one that works, so the user\n * clicks it, nothing happens, and they have no way to tell whether the product\n * is broken or their request was refused.\n */\nfunction normalizeAction(v: unknown): UiAction | null {\n if (!isRecord(v)) return null;\n const name = clamp(v.name, 64);\n if (!name) return null;\n const label = clamp(v.label, 48);\n const confirm = clamp(v.confirm);\n const data = normalizeData(v.data);\n const variant =\n v.variant === \"primary\" || v.variant === \"danger\" ? v.variant : undefined;\n // A `say` button with nothing to say is inert for the same reason a nameless\n // one is: it looks exactly like a button that works.\n if (name === UI_SAY_ACTION && !data?.text && !label) return null;\n return {\n name,\n ...(label ? { label } : {}),\n ...(confirm ? { confirm } : {}),\n ...(data ? { data } : {}),\n ...(variant ? { variant } : {}),\n };\n}\n\nfunction normalizeActions(v: unknown): UiAction[] {\n if (!Array.isArray(v)) return [];\n const out: UiAction[] = [];\n for (const raw of v) {\n if (out.length >= MAX_ACTIONS) break;\n const a = normalizeAction(raw);\n if (a) out.push(a);\n }\n return out;\n}\n\n/**\n * One tile, or null when there is nothing to show.\n *\n * \"Nothing to show\" is deliberate: an item with no media, no text and no state\n * would paint as an empty box, which reads as a broken card rather than as an\n * item the model chose not to describe.\n */\nfunction normalizeItem(v: unknown): UiItem | null {\n if (!isRecord(v)) return null;\n const id = clamp(v.id, 64);\n const title = clamp(v.title, 80);\n const caption = clamp(v.caption);\n const mediaId = sanitizeMediaId(v.mediaId);\n const posterMediaId = sanitizeMediaId(v.posterMediaId);\n const status = normalizeStatus(v.status);\n const media =\n v.media === \"video\" || v.media === \"image\" || v.media === \"audio\"\n ? v.media\n : undefined;\n // Same opaque-token shape as a media id, and checked for the same reason: it\n // is put straight into a URL the widget then polls.\n const jobId = sanitizeMediaId(v.jobId);\n const actions = normalizeActions(v.actions);\n // A tile that is NOTHING but a pending render is the whole point of `jobId` \u2014\n // \"scene 3 is being made, watch this space\" \u2014 so it must survive the\n // has-anything-to-show check that would otherwise drop it.\n if (!mediaId && !title && !caption && !status && !jobId && !actions.length)\n return null;\n return {\n ...(id ? { id } : {}),\n ...(title ? { title } : {}),\n ...(caption ? { caption } : {}),\n ...(mediaId ? { mediaId } : {}),\n ...(posterMediaId ? { posterMediaId } : {}),\n ...(media ? { media } : {}),\n ...(status ? { status } : {}),\n ...(jobId ? { jobId } : {}),\n ...(actions.length ? { actions } : {}),\n };\n}\n\n/**\n * Turn whatever the model emitted into a spec the widget can draw, or null if\n * there is nothing worth drawing.\n *\n * Null rather than an empty card on purpose: the caller leaves the reply's text\n * alone, so a malformed directive costs the user nothing. A card that renders\n * as an empty box would instead look like the assistant tried to show something\n * and the product lost it.\n */\nexport function normalizeUiSpec(input: unknown): UiSpec | null {\n if (!isRecord(input)) return null;\n const items: UiItem[] = [];\n if (Array.isArray(input.items)) {\n for (const raw of input.items) {\n if (items.length >= MAX_ITEMS) break;\n const it = normalizeItem(raw);\n if (it) items.push(it);\n }\n }\n const actions = normalizeActions(input.actions);\n const title = clamp(input.title, 80);\n const caption = clamp(input.caption);\n if (!items.length && !actions.length) return null;\n const layout: UiLayout =\n input.layout === \"grid\" ||\n input.layout === \"list\" ||\n input.layout === \"carousel\"\n ? input.layout\n : \"strip\";\n const aspect = (\n typeof input.aspect === \"string\" && ASPECTS.includes(input.aspect)\n ? input.aspect\n : \"square\"\n ) as UiAspect;\n return {\n ...(title ? { title } : {}),\n ...(caption ? { caption } : {}),\n layout,\n aspect,\n items,\n actions,\n };\n}\n\n/** Every media id a spec will need resolved, in one list \u2014 so the host makes\n * ONE batched round-trip instead of a request per tile. */\nexport function uiSpecMediaIds(spec: UiSpec): string[] {\n const ids = new Set<string>();\n for (const it of spec.items) {\n if (it.mediaId) ids.add(it.mediaId);\n if (it.posterMediaId) ids.add(it.posterMediaId);\n }\n return [...ids];\n}\n", "/**\n * A cross-tab \"the assistant changed something\" bus.\n *\n * Deliberately knows NOTHING about what changed: it carries an account id and a\n * list of opaque domain strings, and the host decides what they mean. sgiant's\n * mapping from those strings to query keys lives in `@sgiant/ai-apply`, because\n * it is a description of sgiant's product rather than of this widget.\n */\n\n// Neutral, like the bridge's channel: this ships to people whose product is\n// not sgiant. It is a WIRE value between tabs of one deployment, so a change\n// only costs an old tab and a new tab not seeing each other until reload.\nconst AI_LIVE_CHANNEL = \"ai-widget-live\";\n\nexport interface AiChangeEvent {\n accountId: string;\n domains?: string[];\n}\n\n/** Tell other tabs an AI change landed so they invalidate too. No-op where\n * BroadcastChannel is unavailable (old browsers / SSR). */\nexport function broadcastAiChange(accountId: string, domains?: string[]): void {\n // SAME-TAB listeners (e.g. the brand info bar's live \"updated by AI\" status)\n // get a synchronous window event \u2014 BroadcastChannel is meant for OTHER tabs and\n // is the less reliable path within one document, so this is the primary signal\n // for anything on the current page; BroadcastChannel below still covers tabs.\n if (typeof window !== \"undefined\") {\n try {\n window.dispatchEvent(\n new CustomEvent(AI_LIVE_CHANNEL, { detail: { accountId, domains } })\n );\n } catch {\n /* CustomEvent unavailable */\n }\n }\n if (typeof BroadcastChannel === \"undefined\") return;\n try {\n const ch = new BroadcastChannel(AI_LIVE_CHANNEL);\n ch.postMessage({ accountId, domains } satisfies AiChangeEvent);\n ch.close();\n } catch {\n /* channel unavailable */\n }\n}\n\n/** Subscribe to cross-tab AI change events; returns an unsubscribe fn. */\nexport function subscribeAiChange(cb: (e: AiChangeEvent) => void): () => void {\n if (typeof BroadcastChannel === \"undefined\") return () => {};\n const ch = new BroadcastChannel(AI_LIVE_CHANNEL);\n const handler = (ev: MessageEvent): void => {\n const d = ev.data as Partial<AiChangeEvent> | null;\n if (d && typeof d.accountId === \"string\")\n cb({\n accountId: d.accountId,\n domains: Array.isArray(d.domains) ? d.domains : undefined,\n });\n };\n ch.addEventListener(\"message\", handler);\n return () => {\n ch.removeEventListener(\"message\", handler);\n ch.close();\n };\n}\n\n// --- Cross-user real-time (SSE) ------------------------------------------------\n// Tier 3: a change made by ANOTHER user/device. The server pushes\n// `{accountId, domains}` over SSE (Postgres LISTEN/NOTIFY \u2192 see\n// docs/realtime-sync.md); the client refetches. Uses fetch (not EventSource) so\n// it can send the Clerk bearer token, and auto-reconnects with capped backoff.\n\nexport interface LiveSyncOptions {\n /** The SSE endpoint, e.g. `${API_BASE}/accounts/:id/live`. */\n url: string;\n /** Fresh auth token per (re)connect. */\n getToken: () => Promise<string | null>;\n /** Called for each change event the server pushes. */\n onChange: (accountId: string, domains?: string[]) => void;\n}\n\n/** Subscribe to the server's live-sync stream. Returns an unsubscribe fn. No-op\n * where fetch/streams are unavailable (SSR). */\nexport function subscribeLiveSync(opts: LiveSyncOptions): () => void {\n if (typeof fetch === \"undefined\") return () => {};\n let closed = false;\n let ctrl: AbortController | null = null;\n let backoff = 1000;\n\n const connect = async (): Promise<void> => {\n if (closed) return;\n ctrl = new AbortController();\n try {\n const token = await opts.getToken();\n const res = await fetch(opts.url, {\n headers: token ? { authorization: `Bearer ${token}` } : {},\n credentials: \"include\",\n signal: ctrl.signal,\n });\n if (!res.ok || !res.body) throw new Error(`live ${res.status}`);\n backoff = 1000; // connected \u2014 reset backoff\n const reader = res.body.getReader();\n const dec = new TextDecoder();\n let buf = \"\";\n for (;;) {\n const { value, done } = await reader.read();\n if (done || closed) break;\n buf += dec.decode(value, { stream: true });\n let nl: number;\n while ((nl = buf.indexOf(\"\\n\\n\")) >= 0) {\n const frame = buf.slice(0, nl);\n buf = buf.slice(nl + 2);\n const line = frame.split(\"\\n\").find((l) => l.startsWith(\"data:\"));\n if (!line) continue; // heartbeat / comment\n try {\n const d = JSON.parse(\n line.slice(5).trim()\n ) as Partial<AiChangeEvent>;\n if (d && typeof d.accountId === \"string\")\n opts.onChange(\n d.accountId,\n Array.isArray(d.domains) ? d.domains : undefined\n );\n } catch {\n /* non-json frame */\n }\n }\n }\n } catch {\n /* network error / abort */\n }\n if (closed) return;\n const wait = backoff;\n backoff = Math.min(backoff * 2, 30_000);\n setTimeout(connect, wait);\n };\n void connect();\n return () => {\n closed = true;\n ctrl?.abort();\n };\n}\n", "/**\n * Minimal, dependency-free, XSS-safe Markdown \u2192 HTML for the embeddable chat\n * widget's assistant bubbles \u2014 the FALLBACK renderer.\n *\n * In-app React hosts pass `renderMarkdown` (our real @sgiant/ui <Markdown>) and\n * this is never used there. But the widget also ships as ZERO-dependency vanilla\n * JS and embeds on third-party sites (the marketing onboarding bot), where React\n * isn't available \u2014 so this covers the subset the assistant actually emits:\n * headings, bold / italic / strikethrough, inline + fenced code, links, ordered\n * / unordered lists, blockquotes, GFM tables, horizontal rules and paragraphs.\n *\n * SECURITY: every input is HTML-escaped BEFORE any markup is added, code spans\n * are tokenised out (NUL-delimited sentinels, so they can't collide with text)\n * before inline processing, and link URLs are sanitised (http/https/mailto or\n * app-relative only). No raw HTML or `javascript:` URL can pass through \u2014 the\n * model's output is untrusted text, never markup.\n */\n\nconst HTML_ESC: Record<string, string> = {\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n '\"': \"&quot;\",\n \"'\": \"&#39;\",\n};\n\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => HTML_ESC[c] as string);\n}\n\n/** Allow only safe link targets; everything else renders as plain text. */\nfunction safeUrl(url: string): string | null {\n const u = url.trim();\n if (/^(https?:\\/\\/|mailto:)/i.test(u)) return u;\n if (/^[/#]/.test(u)) return u; // app-relative path or in-page anchor\n return null;\n}\n\n/**\n * Inline span markup. Runs on ALREADY-ESCAPED text, with code spans pulled out\n * first (so `*` / `_` inside `code` aren't treated as emphasis).\n */\nfunction inline(src: string): string {\n const codes: string[] = [];\n // 1) Protect inline code (NUL-delimited sentinel \u2192 no digit collisions).\n let s = src.replace(/`([^`]+)`/g, (_m, code: string) => {\n codes.push(`<code>${code}</code>`);\n return `\u0000C${codes.length - 1}\u0000`;\n });\n // 2) Links [text](url) \u2014 sanitise the target; drop the markup if unsafe.\n s = s.replace(\n /\\[([^\\]]+)\\]\\(([^)\\s]+)\\)/g,\n (_m, text: string, url: string) => {\n const safe = safeUrl(url);\n if (!safe) return text;\n return `<a href=\"${escapeHtml(safe)}\" target=\"_blank\" rel=\"noopener noreferrer\">${text}</a>`;\n }\n );\n // 3) Emphasis. Bold before italic; strikethrough last.\n s = s.replace(/(\\*\\*|__)(?=\\S)([\\s\\S]+?\\S)\\1/g, \"<strong>$2</strong>\");\n s = s.replace(\n /(^|[^*_])([*_])(?=\\S)([\\s\\S]+?\\S)\\2(?![*_])/g,\n \"$1<em>$3</em>\"\n );\n s = s.replace(/~~(?=\\S)([\\s\\S]+?\\S)~~/g, \"<del>$1</del>\");\n // 4) Restore protected code spans.\n s = s.replace(/\u0000C(\\d+)\u0000/g, (_m, i: string) => codes[Number(i)] ?? \"\");\n return s;\n}\n\n/** A `|`-delimited GFM table row \u2192 cell strings (trims the optional edge pipes). */\nfunction tableCells(line: string): string[] {\n return line\n .trim()\n .replace(/^\\|/, \"\")\n .replace(/\\|$/, \"\")\n .split(\"|\")\n .map((c) => c.trim());\n}\n\nconst SEP_RE = /^\\s*\\|?\\s*:?-{1,}:?\\s*(\\|\\s*:?-{1,}:?\\s*)*\\|?\\s*$/;\nconst FENCE_RE = /^\u0000F(\\d+)\u0000$/;\n\n/**\n * Render a small, safe subset of Markdown to an HTML string, to be injected into\n * a widget bubble whose descendant CSS styles the tags.\n */\nexport function renderMarkdown(src: string): string {\n if (!src) return \"\";\n\n // Pull fenced code blocks out first so nothing inside them is interpreted.\n const blocks: string[] = [];\n const text = src.replace(/```[^\\n]*\\n?([\\s\\S]*?)```/g, (_m, code: string) => {\n const body = escapeHtml(code.replace(/\\n$/, \"\"));\n blocks.push(`<pre class=\"md-pre\"><code>${body}</code></pre>`);\n return `\\n\u0000F${blocks.length - 1}\u0000\\n`;\n });\n\n const lines = text.split(/\\r?\\n/);\n const out: string[] = [];\n let i = 0;\n\n const isBlank = (l: string): boolean => l.trim().length === 0;\n const fenceRef = (l: string): number | null => {\n const m = l.trim().match(FENCE_RE);\n return m ? Number(m[1]) : null;\n };\n const startsBlock = (l: string, idx: number): boolean =>\n /^(#{1,6})\\s+/.test(l) ||\n /^\\s*>/.test(l) ||\n /^\\s*([-*+]|\\d+[.)])\\s+/.test(l) ||\n /^\\s*([-*_])(\\s*\\1){2,}\\s*$/.test(l) ||\n fenceRef(l) !== null ||\n (l.includes(\"|\") && idx + 1 < lines.length && SEP_RE.test(lines[idx + 1]));\n\n while (i < lines.length) {\n const line = lines[i];\n\n if (isBlank(line)) {\n i++;\n continue;\n }\n\n // Restored fenced code block.\n const fref = fenceRef(line);\n if (fref !== null) {\n out.push(blocks[fref] ?? \"\");\n i++;\n continue;\n }\n\n // Heading \u2014 # \u2026 ######\n const h = line.match(/^(#{1,6})\\s+(.*)$/);\n if (h) {\n const level = h[1].length;\n out.push(`<h${level}>${inline(escapeHtml(h[2].trim()))}</h${level}>`);\n i++;\n continue;\n }\n\n // Horizontal rule \u2014 ---, ***, ___\n if (/^\\s*([-*_])(\\s*\\1){2,}\\s*$/.test(line)) {\n out.push(\"<hr>\");\n i++;\n continue;\n }\n\n // GFM table \u2014 a header row immediately followed by a separator row.\n if (\n line.includes(\"|\") &&\n i + 1 < lines.length &&\n SEP_RE.test(lines[i + 1])\n ) {\n const header = tableCells(line);\n i += 2; // consume header + separator\n const body: string[][] = [];\n while (i < lines.length && lines[i].includes(\"|\") && !isBlank(lines[i])) {\n body.push(tableCells(lines[i]));\n i++;\n }\n const thead = header\n .map((c) => `<th>${inline(escapeHtml(c))}</th>`)\n .join(\"\");\n const rows = body\n .map(\n (r) =>\n `<tr>${r\n .map((c) => `<td>${inline(escapeHtml(c))}</td>`)\n .join(\"\")}</tr>`\n )\n .join(\"\");\n out.push(\n `<table class=\"md-table\"><thead><tr>${thead}</tr></thead><tbody>${rows}</tbody></table>`\n );\n continue;\n }\n\n // Blockquote \u2014 one or more leading `>` lines.\n if (/^\\s*>/.test(line)) {\n const quote: string[] = [];\n while (i < lines.length && /^\\s*>/.test(lines[i])) {\n quote.push(lines[i].replace(/^\\s*>\\s?/, \"\"));\n i++;\n }\n out.push(`<blockquote>${renderMarkdown(quote.join(\"\\n\"))}</blockquote>`);\n continue;\n }\n\n // List \u2014 contiguous `-`/`*`/`+` (unordered) or `1.` (ordered) items. A\n // blank line BETWEEN items (a \"loose\" list, which is what the model emits)\n // is tolerated so the items stay in ONE list instead of becoming separate\n // single-item lists with big margins between them.\n const itemRe = /^(\\s*)([-*+]|\\d+[.)])\\s+(.*)$/;\n const listItem = line.match(itemRe);\n if (listItem) {\n const ordered = /\\d/.test(listItem[2]);\n const items: string[] = [];\n while (i < lines.length) {\n const m = lines[i].match(itemRe);\n if (m) {\n items.push(`<li>${inline(escapeHtml(m[3]))}</li>`);\n i++;\n continue;\n }\n // Skip a single blank line if another item follows (loose list).\n if (\n isBlank(lines[i]) &&\n i + 1 < lines.length &&\n itemRe.test(lines[i + 1])\n ) {\n i++;\n continue;\n }\n break;\n }\n const tag = ordered ? \"ol\" : \"ul\";\n out.push(`<${tag}>${items.join(\"\")}</${tag}>`);\n continue;\n }\n\n // Paragraph \u2014 gather contiguous \"plain\" lines (soft-wrap \u2192 <br>).\n const para: string[] = [];\n while (i < lines.length && !isBlank(lines[i])) {\n if (startsBlock(lines[i], i)) break;\n para.push(lines[i].trim());\n i++;\n }\n if (para.length)\n out.push(\n `<p>${inline(escapeHtml(para.join(\"\\n\"))).replace(/\\n/g, \"<br>\")}</p>`\n );\n }\n\n return out.join(\"\");\n}\n", "/**\n * The one class prefix every rule and every element in this widget carries.\n *\n * Its own module because both `index.ts` and `styles.ts` need it and neither\n * should import the other: the stylesheet must not pull in the 5800-line\n * widget closure just to know what to call a class (#320).\n */\nexport const PREFIX = \"sgiant-aiw\";\n", "/**\n * DOM construction and the small formatters around it.\n *\n * These nine helpers sat at the bottom of index.ts, below the 6,143-line\n * `createAiChatWidget` closure and outside it \u2014 module-level all along, and\n * used from inside it 240+ times (`el` alone has 215 call sites). Being outside\n * the closure is what makes them the one seam in #320 that separates without a\n * design decision: they capture nothing, so moving them is a move and not a\n * refactor.\n *\n * Nothing here may reach back into the widget. If one of these ever needs\n * widget state, it belongs in the closure or behind an explicit parameter \u2014 a\n * helper that quietly grows a dependency on its caller is how the file got to\n * 7,000 lines in the first place.\n */\nimport { PREFIX } from \"./prefix\";\n\n/** One decoded SSE frame. Lives here because `parseLine` produces it and\n * nothing upstream of the parser has an opinion about its shape. */\nexport interface StreamFrame {\n type?: string;\n text?: string;\n d?: string;\n threadId?: string;\n message?: string;\n /** render_chart widget frame (analytics lane): spec = model args, rows = data. */\n spec?: { title?: string; chartType?: string };\n rows?: unknown;\n /** Optional prior-period rows for the same chart (comparison overlay). */\n comparisonRows?: unknown;\n /** question frame \u2014 the assistant asking the human to DECIDE (see WsQuestion). */\n questionId?: string;\n prompt?: string;\n context?: string;\n options?: Array<{ id: string; label: string; description?: string }>;\n multi?: boolean;\n critical?: boolean;\n /** activity frame \u2014 a live agent process step (running \u2192 ok/error). */\n callId?: string;\n name?: string;\n label?: string;\n status?: string;\n /** tool_proposal frame \u2014 a confirm-gated write tool's args. */\n args?: unknown;\n /** tool_proposal frame \u2014 the AiArtifact the runner persisted for a\n * dashboard/template write, which is the ONLY handle its REST apply takes.\n * The runner has always emitted this; nothing here read it, so\n * `apply_dashboard` and `save_template` rendered a confirm card whose Apply\n * could only reach `throw new Error(\"Unsupported action\")`. */\n artifactId?: string;\n /** usage frame \u2014 per-turn token counts (drives the session meter). */\n inputTokens?: number;\n outputTokens?: number;\n /** quota frame \u2014 the free visitor allowance snapshot. */\n granted?: number;\n used?: number;\n remaining?: number;\n exhausted?: boolean;\n /** meta frame \u2014 staff-only \"model\" chip: which model/agent runs the turn. */\n isStaff?: boolean;\n modelLabel?: string;\n /** done frame \u2014 the persisted id of the turn's final assistant message, and\n * the model that wrote it. Absent when the turn persisted nothing. */\n messageId?: string;\n model?: string;\n}\n\n/** Parse one stream line \u2014 tolerates `data: ` SSE prefix and bare NDJSON. */\nexport function parseLine(line: string): StreamFrame | null {\n if (!line) return null;\n let json = line;\n if (json.startsWith(\"data:\")) json = json.slice(5).trim();\n if (!json || json === \"[DONE]\") return null;\n try {\n return JSON.parse(json) as StreamFrame;\n } catch {\n return null;\n }\n}\n\n/** Chip glyph per attachment kind \u2014 a clip showing the document icon would read\n * as \"we did not understand your file\". */\nexport function attIcon(kind: string): string {\n if (kind === \"image\") return \"\uD83D\uDDBC\";\n if (kind === \"video\") return \"\uD83C\uDFAC\";\n if (kind === \"audio\") return \"\uD83C\uDFB5\";\n return \"\uD83D\uDCC4\";\n}\n\nexport function el(tag: string, cls: string): HTMLElement {\n const node = document.createElement(tag);\n node.className = cls;\n // Every button gets the focus base HERE rather than at its 40 call sites\n // (#308). Enumerating the call sites is the wrong shape \u2014 the list falls\n // behind the code the first time someone adds a control, which is exactly how\n // the widget ended up with forty buttons and no designed focus state. A\n // button created after this change is covered without anyone remembering.\n if (tag === \"button\") node.classList.add(`${PREFIX}-btn`);\n return node;\n}\n\n/** Coarse \"when\" bucket for grouping past conversations (Today / Yesterday / \u2026). */\n/** Relative-time group for a timestamp \u2014 returns a WIDGET_LABELS key so the\n * caller resolves the localized separator text via L(). */\nexport function relBucket(\n iso: string\n):\n | \"bucketToday\"\n | \"bucketYesterday\"\n | \"bucketWeek\"\n | \"bucketMonth\"\n | \"bucketOlder\" {\n const d = new Date(iso);\n if (isNaN(d.getTime())) return \"bucketOlder\";\n const now = new Date();\n const startToday = new Date(\n now.getFullYear(),\n now.getMonth(),\n now.getDate()\n ).getTime();\n const day = 86_400_000;\n const t = d.getTime();\n if (t >= startToday) return \"bucketToday\";\n if (t >= startToday - day) return \"bucketYesterday\";\n if (t >= startToday - 6 * day) return \"bucketWeek\";\n if (t >= startToday - 29 * day) return \"bucketMonth\";\n return \"bucketOlder\";\n}\n\n/** Short relative label, e.g. \"3h ago\", \"2w ago\" \u2014 falls back to a date. */\nexport function relTime(iso: string): string {\n const d = new Date(iso);\n if (isNaN(d.getTime())) return \"\";\n const diff = Date.now() - d.getTime();\n const min = 60_000;\n const hr = 3_600_000;\n const day = 86_400_000;\n const wk = 7 * day;\n if (diff < hr) return `${Math.max(1, Math.floor(diff / min))}m ago`;\n if (diff < day) return `${Math.floor(diff / hr)}h ago`;\n if (diff < wk) return `${Math.floor(diff / day)}d ago`;\n if (diff < 30 * day) return `${Math.floor(diff / wk)}w ago`;\n return d.toLocaleDateString();\n}\n\n/** Hide raw markdown control marks from a streamed chunk so the user reads clean\n * text WHILE the bot types (the real markdown renders once at the end). Light +\n * per-chunk \u2014 it only needs to look clean for the moment it streams. */\nexport function maskMarkdown(s: string): string {\n return s\n .replace(/`+/g, \"\") // code ticks\n .replace(/[*~]/g, \"\") // bold / italic / strike markers\n .replace(/^#{1,6}\\s*/gm, \"\") // heading hashes\n .replace(/^\\s{0,3}>\\s?/gm, \"\") // blockquote\n .replace(/\\|/g, \" \"); // table pipes\n}\n\n/** Escape text for the few places we build innerHTML (nav button label). */\nexport function escapeHtml(s: string): string {\n return s.replace(\n /[&<>\"']/g,\n (c) =>\n ({\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n '\"': \"&quot;\",\n \"'\": \"&#39;\",\n })[c] as string\n );\n}\n\n/** Wrap a bare HTML fragment in a minimal document so a preview always renders\n * sanely; a full document is used as-is. */\nexport function wrapPreviewHtml(html: string): string {\n const s = (html ?? \"\").trim();\n if (/<!doctype|<html[\\s>]/i.test(s)) return s;\n return `<!doctype html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><style>*{box-sizing:border-box}body{margin:0;padding:16px;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif;color:#0f172a}</style></head><body>${s}</body></html>`;\n}\n\nexport function addMsg(\n log: HTMLElement,\n role: \"user\" | \"assistant\",\n text: string,\n attachments?: { kind: string; filename: string; contentType: string }[]\n): HTMLElement {\n // Attachment chips render as their own row above the text bubble so a\n // file-only turn (no text) still shows what the user sent.\n if (attachments && attachments.length) {\n const chips = el(\"div\", `${PREFIX}-atts ${PREFIX}-${role}`);\n for (const a of attachments) {\n const chip = el(\"span\", `${PREFIX}-att`);\n chip.title = `${a.filename} (${a.contentType})`;\n chip.textContent = `${attIcon(a.kind)} ${a.filename}`;\n chips.appendChild(chip);\n }\n log.appendChild(chips);\n }\n const msg = el(\"div\", `${PREFIX}-msg ${PREFIX}-${role}`);\n msg.textContent = text;\n if (!text && attachments && attachments.length) msg.style.display = \"none\";\n log.appendChild(msg);\n return msg;\n}\n", "/**\n * Every SVG the widget draws: the Copilot mark, its gradient defs, and the\n * chrome icons.\n *\n * Extracted from index.ts (#320). Pure data \u00E2\u0080\u0094 one import and no behaviour \u00E2\u0080\u0094\n * which is what makes it the cheapest region to move, and the reason to move\n * it first.\n *\n * It also answers something #306 asks for and #305 warned about. The mark's\n * only escape hatch is `avatarUrl`, which swaps in an `<img>`: an embedder\n * either uses our moon or supplies a BITMAP, with no way to pass a vector.\n * #305 asked that the redraw land \"as something replaceable, not a second\n * constant\" \u00E2\u0080\u0094 and it landed as a constant, here. Making the mark themeable\n * starts by having a file to point at.\n */\nimport { PREFIX } from \"./prefix\";\n\n// Copilot \u2014 the CRESCENT (#305). \"Copilot = moonlight (tr)\" is already the\n// comment on the ASSISTANT design token, and this file's own avatar fallback\n// was documented as \"else a crescent glyph\" \u2014 describing a mark nobody had\n// drawn. This draws it.\n//\n// What it replaces: a morphing SVG blob, an feGaussianBlur+feColorMatrix goo\n// filter, two orbiting metaballs with three <animate> tracks each, a float\n// loop, a blink loop, an orange glow, a gradient ring and a two-layer shadow \u2014\n// EIGHT simultaneous effects to say one thing, \"there is a chat here\". Each was\n// added for a reason and none was ever taken away, so the launcher was loud at\n// rest and had nothing left to say when a reply was actually waiting.\n//\n// It is ONE path with no filter and no SMIL. That is not only simplicity: a CSS\n// `animation:none` cannot reach a SMIL <animate>, so a reader who asked their OS\n// for stillness got a permanently moving blob in the corner of every page (#308\n// recorded that as unreachable from CSS; this is the fix).\n//\n// THE GROUND RULE, measured rather than chosen. Contrast over the brand sweep:\n// white fails on every stop (teal 2.00, amber 1.93, orange 2.81) and navy\n// clears all of them (8.41 / 8.73 / 5.98); on cream, teal is 1.86 and navy is\n// 15.66. Navy is the only value that survives the whole sweep and cream is the\n// only value that survives navy \u2014 so THE MARK AND ITS DISC ALWAYS STRADDLE\n// NAVY. Light ground: navy disc, gradient mark. Ink ground: the whole object\n// inverts to a cream disc with a navy mark. The gradient never makes that\n// second trip, which is why there is no third variant.\nexport const AVATAR_DEFS = `<svg width=\"0\" height=\"0\" style=\"position:absolute\" aria-hidden=\"true\"><defs>\n<linearGradient id=\"${PREFIX}-av-sweep\" x1=\"0\" y1=\"1\" x2=\"1\" y2=\"0\">\n<stop offset=\"0%\" stop-color=\"#60C7C8\"/><stop offset=\"55%\" stop-color=\"#FBAA34\"/><stop offset=\"100%\" stop-color=\"#FA712D\"/>\n</linearGradient></defs></svg>`;\n\n// The mark, taken from the resolved specimen rather than re-derived: two arcs\n// of the SAME radius, which is what makes a crescent read as a MOON. My first\n// attempt used a shallower second arc (r19 against r15) with the tips at top\n// and bottom \u2014 geometrically a crescent, but standing straight up. It read as\n// 90 degrees, which is a shape and not a moon. This one is tilted, and the\n// tilt is the whole difference.\nexport const AVATAR_SVG = `<svg viewBox=\"0 0 24 24\" class=\"${PREFIX}-ayca\" aria-hidden=\"true\"><path class=\"${PREFIX}-av-mark\" d=\"M20.4 14.5A8.6 8.6 0 0 1 9.5 3.6 8.6 8.6 0 1 0 20.4 14.5Z\"/></svg>`;\n\n// Small line icons for the header controls (currentColor, 18px).\nexport const ICON_HISTORY = `<svg viewBox=\"0 0 24 24\" width=\"17\" height=\"17\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M3 3v5h5\"/><path d=\"M3.05 13A9 9 0 1 0 6 5.3L3 8\"/><path d=\"M12 7v5l3 2\"/></svg>`;\nexport const ICON_EXPAND = `<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M15 3h6v6\"/><path d=\"M9 21H3v-6\"/><path d=\"M21 3l-7 7\"/><path d=\"M3 21l7-7\"/></svg>`;\nexport const ICON_COLLAPSE = `<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M4 14h6v6\"/><path d=\"M20 10h-6V4\"/><path d=\"M14 10l7-7\"/><path d=\"M3 21l7-7\"/></svg>`;\n// Compass \u2014 the auto-navigate (\"drive me there\") toggle.\nexport const ICON_COMPASS = `<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><polygon points=\"16.2 7.8 13.4 13.4 7.8 16.2 10.6 10.6 16.2 7.8\"/></svg>`;\n// Advanced view: a panel split into a sidebar + main area (Copilot \u21C6 live app).\nexport const ICON_ADVANCED = `<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"2\"/><line x1=\"10\" y1=\"4\" x2=\"10\" y2=\"20\"/></svg>`;\nexport const ICON_CHEVRON_R = `<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"9 18 15 12 9 6\"/></svg>`;\n// Download \u2014 export the current conversation as a .txt transcript.\nexport const ICON_DOWNLOAD = `<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"7 10 12 15 17 10\"/><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"/></svg>`;\n// Flag \u2014 agent/admin oversight: flag the current conversation with a reason.\nexport const ICON_FLAG = `<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z\"/><line x1=\"4\" y1=\"22\" x2=\"4\" y2=\"15\"/></svg>`;\n// Bell \u2014 toggle a soft chime when a reply arrives.\nexport const ICON_BELL = `<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9\"/><path d=\"M10.3 21a1.94 1.94 0 0 0 3.4 0\"/></svg>`;\nexport const ICON_BELL_OFF = `<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/><path d=\"M18.63 13A17.9 17.9 0 0 1 18 8\"/><path d=\"M6.26 6.26A6 6 0 0 0 6 8c0 7-3 9-3 9h14\"/><path d=\"M18 8a6 6 0 0 0-9.33-5\"/><line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\"/></svg>`;\n// Kebab (vertical dots) \u2014 the \"More\" overflow menu that collects the secondary\n// header controls so they no longer crowd the title bar side-by-side.\nexport const ICON_MORE = `<svg viewBox=\"0 0 24 24\" width=\"17\" height=\"17\" fill=\"currentColor\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"5\" r=\"1.7\"/><circle cx=\"12\" cy=\"12\" r=\"1.7\"/><circle cx=\"12\" cy=\"19\" r=\"1.7\"/></svg>`;\n// Paperclip \u2014 the composer's attach-a-file control. A crisp line icon replaces\n// the flat \uD83D\uDCCE emoji so the button reads as part of the widget, not an OS glyph.\nexport const ICON_ATTACH = `<svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21.44 11.05l-9.19 9.19a5.5 5.5 0 0 1-7.78-7.78l9.19-9.19a3.5 3.5 0 0 1 4.95 4.95l-9.2 9.19a1.5 1.5 0 0 1-2.12-2.12l8.49-8.49\"/></svg>`;\n// Branching controls: edit a user turn, regenerate an assistant reply, and the\n// \u2039n/m\u203A sibling switcher arrows.\nexport const ICON_EDIT = `<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 20h9\"/><path d=\"M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z\"/></svg>`;\nexport const ICON_REGEN = `<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M3 2v6h6\"/><path d=\"M3.51 15a9 9 0 1 0 2.13-9.36L3 8\"/></svg>`;\nexport const ICON_CHEV_L = `<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"15 18 9 12 15 6\"/></svg>`;\nexport const ICON_CHEV_R = `<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"9 18 15 12 9 6\"/></svg>`;\n// Per-message quality vote (thumbs up/down) \u2014 mirrors the React panel's VoteButtons.\nexport const ICON_THUMB_UP = `<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M7 10v12\"/><path d=\"M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z\"/></svg>`;\nexport const ICON_THUMB_DOWN = `<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M17 14V2\"/><path d=\"M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z\"/></svg>`;\n", "import { el } from \"./dom\";\nimport {\n ICON_CHEV_L,\n ICON_CHEV_R,\n ICON_THUMB_DOWN,\n ICON_THUMB_UP,\n} from \"./icons\";\nimport { PREFIX } from \"./prefix\";\nimport type { BranchNav } from \"./replay\";\n\n/**\n * The two controls that hang off a single assistant message: the \u2039n/m\u203A branch\n * navigator and the up/down vote pair.\n *\n * Both are pure DOM builders \u2014 they read no widget state and write none. They\n * sat inside the 6,051-line closure only because that is where they were\n * written (`#320`), and they are the shape that actually moves out of it: the\n * measurement on that issue put them at two closure bindings each, against 56\n * for `send()` and 28 for the job cluster.\n *\n * THE THREAD ID IS A GETTER, AND THAT IS THE WHOLE REASON THIS IS A FACTORY.\n *\n * `buildVoteButtons` reads the thread id inside the CLICK handler, not while\n * building. In the closure that was automatic \u2014 `threadId` is a `let` the widget\n * reassigns when the server names a thread, and a vote cast later reads whatever\n * it holds then. Taking it as a plain parameter here would capture the value at\n * BUILD time, so a message rendered before the thread was named would post its\n * vote against the wrong id \u2014 or an empty one \u2014 and nothing would fail loudly:\n * the request succeeds, the vote lands on nothing, and the quality signal\n * `#299` exists to collect goes quietly missing.\n *\n * A getter keeps the read where it was. The rest of the dependencies are fixed\n * for the widget's lifetime and are captured once, which is what lets both\n * bodies move byte for byte.\n */\nexport function createMessageChrome(deps: {\n /** Navigate to a sibling version. Called on click, so the reference is enough. */\n switchBranch: (leaf: string) => void | Promise<void>;\n /** Read the CURRENT thread id \u2014 see the note above; do not pass a value. */\n getThreadId: () => string | undefined;\n vote?: (input: {\n threadId: string;\n messageId: string;\n value: 1 | -1 | 0;\n model?: string;\n reason?: string;\n }) => Promise<void>;\n voteUpLabel?: string;\n voteDownLabel?: string;\n}) {\n const { switchBranch, getThreadId, vote, voteUpLabel, voteDownLabel } = deps;\n\n function buildBranchNav(branch: BranchNav): HTMLElement {\n const nav = el(\"div\", `${PREFIX}-branchnav`);\n const prev = el(\"button\", `${PREFIX}-branch-btn`) as HTMLButtonElement;\n prev.type = \"button\";\n prev.setAttribute(\"aria-label\", \"Previous version\");\n prev.innerHTML = ICON_CHEV_L;\n prev.disabled = !branch.prevLeaf;\n if (branch.prevLeaf) {\n const leaf = branch.prevLeaf;\n prev.addEventListener(\"click\", () => void switchBranch(leaf));\n }\n const count = el(\"span\", `${PREFIX}-branch-count`);\n count.textContent = `${branch.index}/${branch.count}`;\n const next = el(\"button\", `${PREFIX}-branch-btn`) as HTMLButtonElement;\n next.type = \"button\";\n next.setAttribute(\"aria-label\", \"Next version\");\n next.innerHTML = ICON_CHEV_R;\n next.disabled = !branch.nextLeaf;\n if (branch.nextLeaf) {\n const leaf = branch.nextLeaf;\n next.addEventListener(\"click\", () => void switchBranch(leaf));\n }\n nav.appendChild(prev);\n nav.appendChild(count);\n nav.appendChild(next);\n return nav;\n }\n\n function buildVoteButtons(messageId: string, model?: string): HTMLElement {\n const wrap = el(\"div\", `${PREFIX}-votes`);\n let cur: 1 | -1 | 0 = 0;\n const up = el(\n \"button\",\n `${PREFIX}-msgact ${PREFIX}-vote`\n ) as HTMLButtonElement;\n const down = el(\n \"button\",\n `${PREFIX}-msgact ${PREFIX}-vote`\n ) as HTMLButtonElement;\n up.type = \"button\";\n down.type = \"button\";\n up.setAttribute(\"aria-label\", voteUpLabel ?? \"Helpful\");\n down.setAttribute(\"aria-label\", voteDownLabel ?? \"Not helpful\");\n up.title = voteUpLabel ?? \"Helpful\";\n down.title = voteDownLabel ?? \"Not helpful\";\n up.innerHTML = ICON_THUMB_UP;\n down.innerHTML = ICON_THUMB_DOWN;\n const paint = (): void => {\n up.classList.toggle(`${PREFIX}-vote-on`, cur === 1);\n down.classList.toggle(`${PREFIX}-vote-on`, cur === -1);\n };\n const cast = (next: 1 | -1): void => {\n if (!vote) return;\n const value = (cur === next ? 0 : next) as 1 | -1 | 0;\n const prev = cur;\n cur = value;\n paint();\n // messageId is globally unique; the current threadId scopes the vote.\n void vote({\n threadId: getThreadId() ?? \"\",\n messageId,\n value,\n ...(model ? { model } : {}),\n }).catch(() => {\n cur = prev;\n paint();\n });\n };\n up.addEventListener(\"click\", () => cast(1));\n down.addEventListener(\"click\", () => cast(-1));\n wrap.appendChild(up);\n wrap.appendChild(down);\n return wrap;\n }\n\n return { buildBranchNav, buildVoteButtons };\n}\n", "import { el, wrapPreviewHtml } from \"./dom\";\nimport { PREFIX } from \"./prefix\";\nimport type { ActionSpec, ChipsSpec, PreviewSpec, WidgetSpec } from \"./specs\";\nimport {\n UI_SAY_ACTION,\n normalizeUiSpec,\n uiSpecMediaIds,\n type UiAction,\n type UiItem,\n type UiStatus,\n} from \"./ui-spec\";\n// Type-only, so it is erased and there is no runtime cycle with ./index. This\n// interface belongs beside the other spec types in ./specs; moving it touches a\n// dozen call sites, which does not belong in a commit whose whole point is that\n// nothing changed.\nimport type { AiChatWidgetOptions, WidgetJobView } from \"./index\";\nimport type { WidgetLabels } from \"./labels\";\n\n/**\n * Rendering for server-driven UI: the `ui` spec and the inline data widgets.\n *\n * `./ui-spec` owns the DATA \u2014 the types and `normalizeUiSpec`. This owns the\n * DOM built from it. They were one 6,882-line file with the spec parsed at one\n * end and painted at the other (#320).\n *\n * ## Why a factory rather than exported functions\n *\n * These fifteen functions read nine things from the widget's closure. Threading\n * those through as a `ctx` parameter would have meant editing every signature\n * and every call site \u2014 and one consumer compiles this source with\n * `noUnusedParameters`, so the handful that do not need `ctx` could not have\n * taken it for uniformity anyway.\n *\n * A factory destructures the context once and lets the bodies close over it, so\n * they moved out of `index.ts` BYTE FOR BYTE and not one of the eleven call\n * sites changed. A move you can verify by diffing is worth more here than the\n * marginally purer shape.\n *\n * ## Nine of the fifteen are private\n *\n * Only six are called from outside. The other nine were in `index.ts`'s scope\n * for no reason other than living in the same file, and are now unreachable\n * from it \u2014 which is most of what this split is for.\n */\nexport interface UiRenderCtx {\n /** Label lookup; `L(\"prev\")`. Host translation falling back to English. */\n L: (\n key: keyof WidgetLabels,\n params?: Record<string, string | number>\n ) => string;\n /** The conversation log element every renderer appends into. */\n log: HTMLElement;\n /** Scroll the log to the bottom; `force` overrides the user's scroll-up pin. */\n scrollDown: (force?: boolean) => void;\n /** Send a turn, as the chip renderer does when a suggestion is clicked. */\n send: (\n content: string,\n fork?: { parentId?: string | null; regenerate?: boolean }\n ) => Promise<void>;\n /** Teardown callbacks for renderers holding subscriptions or timers. */\n richDisposers: Array<() => void>;\n /** Hand an action to the host app. */\n dispatchAction: (\n name: string,\n data: Record<string, string>\n ) => Promise<string | void>;\n /** Live job updates for a tile that is watching one. `kind` is the narrow\n * union rather than `string`: a report id and a job id are two namespaces,\n * and widening it here would let them be conflated at this boundary. */\n subscribeJob: (\n ref: { kind: \"job\" | \"report\"; id: string },\n node: HTMLElement,\n paint: (view: WidgetJobView) => void\n ) => void;\n /** Try to render into the host's pane; false when it cannot take it. */\n showPaneWidget: (\n spec: unknown,\n rows: unknown,\n comparisonRows?: unknown\n ) => boolean;\n /** The composer input, focused when a chip is dismissed. */\n input: HTMLInputElement;\n /** The host's options. Only three are read here \u2014 `renderDataWidget`,\n * `renderChartFallback` and `resolveMediaUrls` \u2014 but the whole object is\n * passed so the bodies keep saying `opts.x`, which is what makes this a move\n * rather than a rewrite. */\n opts: AiChatWidgetOptions;\n}\n\nexport type UiRenderers = ReturnType<typeof createUiRenderers>;\n\nexport function createUiRenderers(ctx: UiRenderCtx) {\n const {\n L,\n log,\n scrollDown,\n send,\n richDisposers,\n dispatchAction,\n subscribeJob,\n showPaneWidget,\n input,\n opts,\n } = ctx;\n\n /** Render an inline data widget (stat / kpis / list / table) in the log. */\n function renderWidget(spec: WidgetSpec): void {\n const card = el(\"div\", `${PREFIX}-widget`);\n if (spec.title) {\n const t = el(\"div\", `${PREFIX}-widget-title`);\n t.textContent = spec.title;\n card.appendChild(t);\n }\n const kind =\n spec.kind ?? (spec.rows ? \"table\" : spec.items ? \"kpis\" : \"list\");\n if (kind === \"stat\") {\n const v = el(\"div\", `${PREFIX}-widget-stat`);\n v.textContent = String(spec.value ?? \"\");\n card.appendChild(v);\n if (spec.caption) {\n const c = el(\"div\", `${PREFIX}-widget-cap`);\n c.textContent = spec.caption;\n card.appendChild(c);\n }\n if (spec.delta) {\n const d = el(\"div\", `${PREFIX}-widget-delta`);\n d.textContent = spec.delta;\n card.appendChild(d);\n }\n } else if (kind === \"kpis\") {\n const grid = el(\"div\", `${PREFIX}-widget-kpis`);\n for (const it of (spec.items ?? []).slice(0, 8)) {\n const tile = el(\"div\", `${PREFIX}-kpi`);\n const kv = el(\"div\", `${PREFIX}-kpi-v`);\n kv.textContent = String(it.value ?? \"\");\n const kl = el(\"div\", `${PREFIX}-kpi-l`);\n kl.textContent = it.label ?? \"\";\n tile.append(kv, kl);\n if (it.delta) {\n const kd = el(\"div\", `${PREFIX}-kpi-d`);\n kd.textContent = it.delta;\n tile.appendChild(kd);\n }\n grid.appendChild(tile);\n }\n card.appendChild(grid);\n } else if (kind === \"table\" && spec.rows) {\n const table = document.createElement(\"table\");\n table.className = `${PREFIX}-widget-table`;\n if (spec.columns?.length) {\n const thead = table.createTHead().insertRow();\n for (const c of spec.columns) {\n const th = document.createElement(\"th\");\n th.textContent = String(c);\n thead.appendChild(th);\n }\n }\n const tbody = table.createTBody();\n for (const row of spec.rows.slice(0, 30)) {\n const tr = tbody.insertRow();\n for (const cell of row) tr.insertCell().textContent = String(cell);\n }\n card.appendChild(table);\n } else {\n const ul = el(\"ul\", `${PREFIX}-widget-list`);\n for (const line of (spec.lines ?? []).slice(0, 30)) {\n const li = document.createElement(\"li\");\n li.textContent = line;\n ul.appendChild(li);\n }\n card.appendChild(ul);\n }\n log.appendChild(card);\n scrollDown(true);\n }\n\n // \u2500\u2500 Composed UI cards \u2014 `[[ui:{\u2026}]]` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 assistant composes a card out of primitives this widget knows how to\n // draw (see ui-spec.ts for the vocabulary and why it is a vocabulary rather\n // than markup). Nothing here knows what a storyboard is, or a shortlist, or a\n // room-type picker: it draws tiles with media, a state and buttons, and the\n // buttons go to the host by name through the SAME `dispatchAction` an\n // `[[action:\u2026]]` uses \u2014 so a composed card can never do anything the host\n // doesn't already allow.\n\n /** The label for a tile's state: ours to translate for the four we know,\n * the model's own words for anything else. */\n function uiStatusLabel(status: string): string {\n if (status === \"pending\") return L(\"uiStatusPending\");\n if (status === \"running\") return L(\"uiStatusRunning\");\n if (status === \"ready\") return L(\"uiStatusReady\");\n if (status === \"failed\") return L(\"uiStatusFailed\");\n return status;\n }\n\n /** One button on a card or tile. */\n function buildUiAction(a: UiAction, itemId?: string): HTMLElement {\n const wrap = el(\"span\", `${PREFIX}-ui-act`);\n const variant =\n a.variant === \"primary\"\n ? ` ${PREFIX}-ui-btn-primary`\n : a.variant === \"danger\"\n ? ` ${PREFIX}-ui-btn-danger`\n : \"\";\n const btn = el(\"button\", `${PREFIX}-ui-btn${variant}`) as HTMLButtonElement;\n btn.type = \"button\";\n const label = a.label || a.name;\n btn.textContent = label;\n // The tile's own id rides along, so a handler knows WHICH tile was acted on\n // without the model having to repeat it inside every button's data.\n const data = { ...(a.data ?? {}), ...(itemId ? { itemId } : {}) };\n const run = async (): Promise<void> => {\n btn.disabled = true;\n // `say` is answered here, not by the host: it puts its text into the\n // conversation as an ordinary user message (exactly as a chip does), so\n // the assistant's own tools carry it out and the transcript records the\n // click as the sentence it stood for.\n if (a.name === UI_SAY_ACTION) {\n void send((a.data?.text || label).trim());\n return;\n }\n try {\n const msg = await dispatchAction(a.name, data);\n btn.textContent =\n (typeof msg === \"string\" && msg) || L(\"actionDone\", { label });\n } catch {\n btn.disabled = false;\n btn.textContent = `${label} \u2014 ${L(\"tryAgain\")}`;\n }\n };\n btn.addEventListener(\"click\", () => {\n if (!a.confirm) {\n void run();\n return;\n }\n // The confirm replaces THIS BUTTON, not the card. A single [[action:\u2026]]\n // card can take itself over to ask, because it is the only thing there;\n // a composed card holds many actions, and hiding a whole storyboard\n // behind one question would lose the very thing being confirmed.\n wrap.textContent = \"\";\n const q = el(\"span\", `${PREFIX}-ui-confirm-q`);\n q.textContent = a.confirm;\n const yes = el(\"button\", `${PREFIX}-ui-btn`) as HTMLButtonElement;\n yes.type = \"button\";\n yes.textContent = L(\"confirm\");\n const no = el(\"button\", `${PREFIX}-ui-btn-ghost`) as HTMLButtonElement;\n no.type = \"button\";\n no.textContent = L(\"cancel\");\n const restore = (): void => {\n wrap.textContent = \"\";\n wrap.appendChild(btn);\n };\n yes.addEventListener(\"click\", () => {\n restore();\n void run();\n });\n no.addEventListener(\"click\", restore);\n wrap.append(q, yes, no);\n });\n wrap.appendChild(btn);\n return wrap;\n }\n\n /** One tile. The media box is drawn EMPTY-BUT-SIZED and filled in later, so\n * the card doesn't reflow (and scroll out from under a reading user) when\n * the pictures land. */\n /** The badge class for a status \u2014 the known four are coloured, anything the\n * model invented gets the neutral one. */\n function uiBadgeClass(status: string): string {\n const known =\n status === \"pending\" ||\n status === \"running\" ||\n status === \"ready\" ||\n status === \"failed\";\n return `${PREFIX}-ui-badge ${PREFIX}-ui-badge-${known ? status : \"other\"}`;\n }\n\n /**\n * A job's coarse lifecycle as the tile vocabulary's own status.\n *\n * `cancelled` deliberately lands on `failed` rather than on its own badge:\n * from the tile's point of view \u2014 \"is there a clip here?\" \u2014 a cancelled\n * render and a failed one are the same answer, and the card-level job view\n * is where the difference is spelled out.\n */\n function uiStatusForJob(status: WidgetJobView[\"status\"]): UiStatus {\n if (status === \"done\") return \"ready\";\n if (status === \"failed\" || status === \"cancelled\") return \"failed\";\n if (status === \"queued\") return \"pending\";\n return \"running\";\n }\n\n /**\n * Repaint one tile from a job poll \u2014 the thing that stops a composed card\n * being a snapshot of the moment it was written.\n *\n * Only ever ADDS what it learns: media it did not have, and a status it can\n * now state. It never clears a media id the model already gave, because a\n * tile can legitimately show a reference still while the render it names is\n * still running.\n */\n function paintUiTileLive(tile: HTMLElement, view: WidgetJobView): void {\n const box = tile.querySelector<HTMLElement>(`.${PREFIX}-ui-media`);\n if (!box) return;\n const badge = tile.querySelector<HTMLElement>(`.${PREFIX}-ui-badge`);\n if (badge) {\n const s = uiStatusForJob(view.status);\n badge.className = uiBadgeClass(s);\n badge.textContent = uiStatusLabel(s);\n }\n const produced = view.resultMediaId;\n if (!produced || box.dataset.mid === produced) return;\n box.dataset.mid = produced;\n // The clip has landed, so the box stops being a placeholder \u2014 one more\n // batched resolve, scoped to this tile, and `fillUiMedia` does the rest\n // (it queries `.\u2026-ui-media` inside whatever element it is handed).\n void fillUiMedia([produced], tile);\n }\n\n function buildUiTile(it: UiItem): HTMLElement {\n const tile = el(\"div\", `${PREFIX}-ui-tile`);\n const box = el(\"div\", `${PREFIX}-ui-media`);\n if (it.mediaId) {\n box.dataset.mid = it.mediaId;\n if (it.posterMediaId) box.dataset.poster = it.posterMediaId;\n if (it.media) box.dataset.kind = it.media;\n }\n const ph = el(\"span\", `${PREFIX}-ui-media-ph`);\n ph.textContent = it.mediaId ? \"\" : L(\"uiMediaPending\");\n box.appendChild(ph);\n // A tile watching a job always gets a badge, even when the model named no\n // status: the badge is where the job's progress is going to be written, and\n // one added later would make the tile jump as it filled in.\n const initialStatus =\n it.status ?? (it.jobId ? (\"running\" as UiStatus) : undefined);\n if (initialStatus) {\n const badge = el(\"span\", uiBadgeClass(initialStatus));\n badge.textContent = uiStatusLabel(initialStatus);\n box.appendChild(badge);\n }\n tile.appendChild(box);\n if (it.jobId) {\n subscribeJob({ kind: \"job\", id: it.jobId }, tile, (view) =>\n paintUiTileLive(tile, view)\n );\n }\n if (it.title) {\n const t = el(\"div\", `${PREFIX}-ui-tile-title`);\n t.textContent = it.title;\n tile.appendChild(t);\n }\n if (it.caption) {\n const c = el(\"div\", `${PREFIX}-ui-tile-cap`);\n c.textContent = it.caption;\n tile.appendChild(c);\n }\n if (it.actions?.length) {\n const row = el(\"div\", `${PREFIX}-ui-tile-actions`);\n for (const a of it.actions) row.appendChild(buildUiAction(a, it.id));\n tile.appendChild(row);\n }\n return tile;\n }\n\n /** Does this URL point at something to PLAY rather than show? The id alone\n * can't say, so the model's own `media` hint wins and the extension is the\n * fallback \u2014 a clip drawn as an <img> is a broken tile, which is worse than\n * a still drawn in a <video> (that still shows the frame). */\n function looksLikeVideo(url: string): boolean {\n return /\\.(mp4|webm|mov|m4v)(\\?|#|$)/i.test(url);\n }\n\n /** Resolve a card's media ids and paint them in. One batched call for the\n * whole card \u2014 a request per tile would be a burst of round-trips for what\n * is conceptually one picture set. */\n async function fillUiMedia(ids: string[], card: HTMLElement): Promise<void> {\n let urls: Record<string, string> = {};\n if (ids.length && opts.resolveMediaUrls) {\n try {\n urls = (await opts.resolveMediaUrls(ids)) ?? {};\n } catch {\n urls = {}; // every tile then says \"Unavailable\" \u2014 never a blank box\n }\n }\n const boxes = card.querySelectorAll<HTMLElement>(`.${PREFIX}-ui-media`);\n for (const box of Array.from(boxes)) {\n const mid = box.dataset.mid;\n if (!mid) continue;\n const url = urls[mid];\n const ph = box.querySelector<HTMLElement>(`.${PREFIX}-ui-media-ph`);\n if (!url) {\n if (ph) ph.textContent = L(\"uiMediaMissing\");\n continue;\n }\n // AUDIO must be declared, never guessed: our URLs are signed and carry no\n // extension, so an mp3 left to inference is drawn as an <img> and reads as\n // a broken tile.\n const kind = box.dataset.kind\n ? box.dataset.kind\n : looksLikeVideo(url)\n ? \"video\"\n : \"image\";\n const isVideo = kind === \"video\";\n const isAudio = kind === \"audio\";\n const node = el(\n isAudio ? \"audio\" : isVideo ? \"video\" : \"img\",\n `${PREFIX}-ui-media-el`\n ) as HTMLImageElement & HTMLVideoElement & HTMLAudioElement;\n node.src = url;\n if (isVideo || isAudio) {\n node.controls = true;\n node.preload = \"metadata\";\n if (isVideo) {\n node.playsInline = true;\n const poster = box.dataset.poster ? urls[box.dataset.poster] : \"\";\n if (poster) node.poster = poster;\n }\n } else {\n node.loading = \"lazy\";\n node.alt = \"\";\n }\n // A media id that resolves to a URL the browser then refuses (expired\n // signature, deleted object) must still read as a state, not a blank.\n node.addEventListener(\"error\", () => {\n node.remove();\n const p = el(\"span\", `${PREFIX}-ui-media-ph`);\n p.textContent = L(\"uiMediaMissing\");\n box.appendChild(p);\n });\n ph?.remove();\n box.insertBefore(node, box.firstChild);\n }\n }\n\n /**\n * Wrap a tile row in paging: one visible at a time, arrows and dots overlaid.\n *\n * Overlaid rather than stacked beneath \u2014 controls that are flex siblings\n * steal height from the media, so a square tile renders visibly smaller\n * than the space it was given and reads as a lesser thing than it is.\n */\n function buildUiCarousel(items: HTMLElement, count: number): HTMLElement {\n const wrap = el(\"div\", `${PREFIX}-ui-carousel`);\n const stage = el(\"div\", `${PREFIX}-ui-carousel-stage`);\n stage.appendChild(items);\n wrap.appendChild(stage);\n\n let index = 0;\n const dots: HTMLElement[] = [];\n const paint = (): void => {\n items.style.transform = `translateX(-${index * 100}%)`;\n dots.forEach((d, i) =>\n d.classList.toggle(`${PREFIX}-ui-dot-on`, i === index)\n );\n counter.textContent = `${index + 1}/${count}`;\n };\n const go = (delta: number): void => {\n index = (index + delta + count) % count;\n paint();\n };\n\n const arrow = (side: \"left\" | \"right\"): HTMLElement => {\n const b = el(\n \"button\",\n `${PREFIX}-ui-arrow ${PREFIX}-ui-arrow-${side}`\n ) as HTMLButtonElement;\n b.type = \"button\";\n b.setAttribute(\"aria-label\", side === \"left\" ? L(\"prev\") : L(\"next\"));\n b.textContent = side === \"left\" ? \"\u2039\" : \"\u203A\";\n b.addEventListener(\"click\", (e) => {\n // The card sits inside a scrollable log and may sit inside other\n // clickable chrome; paging must never reach either.\n e.preventDefault();\n e.stopPropagation();\n go(side === \"left\" ? -1 : 1);\n });\n return b;\n };\n stage.append(arrow(\"left\"), arrow(\"right\"));\n\n const counter = el(\"span\", `${PREFIX}-ui-counter`);\n stage.appendChild(counter);\n\n const dotRow = el(\"div\", `${PREFIX}-ui-dots`);\n for (let i = 0; i < count; i++) {\n const d = el(\"button\", `${PREFIX}-ui-dot`) as HTMLButtonElement;\n d.type = \"button\";\n d.setAttribute(\"aria-label\", `${i + 1}`);\n d.addEventListener(\"click\", (e) => {\n e.preventDefault();\n e.stopPropagation();\n index = i;\n paint();\n });\n dots.push(d);\n dotRow.appendChild(d);\n }\n stage.appendChild(dotRow);\n paint();\n return wrap;\n }\n\n /** Draw a composed UI card in the log. */\n function renderUiCard(raw: unknown): void {\n const spec = normalizeUiSpec(raw);\n if (!spec) return; // nothing worth drawing \u2014 the reply's text stands alone\n const card = el(\"div\", `${PREFIX}-ui`);\n if (spec.title) {\n const t = el(\"div\", `${PREFIX}-ui-title`);\n t.textContent = spec.title;\n card.appendChild(t);\n }\n if (spec.caption) {\n const c = el(\"div\", `${PREFIX}-ui-cap`);\n c.textContent = spec.caption;\n card.appendChild(c);\n }\n if (spec.items.length) {\n const items = el(\n \"div\",\n `${PREFIX}-ui-items ${PREFIX}-ui-${spec.layout} ${PREFIX}-ui-a-${spec.aspect}`\n );\n for (const it of spec.items) items.appendChild(buildUiTile(it));\n // A CAROUSEL shows one item at a time, because the client is judging each\n // one rather than scanning a set: six thumbnails in a row invites\n // approving what was never really looked at. Paging is added around the\n // same tiles, so nothing about how a tile is built changes.\n if (spec.layout === \"carousel\" && spec.items.length > 1) {\n card.appendChild(buildUiCarousel(items, spec.items.length));\n } else {\n card.appendChild(items);\n }\n }\n if (spec.actions.length) {\n const row = el(\"div\", `${PREFIX}-ui-actions`);\n for (const a of spec.actions) row.appendChild(buildUiAction(a));\n card.appendChild(row);\n }\n log.appendChild(card);\n scrollDown(true);\n void fillUiMedia(uiSpecMediaIds(spec), card);\n }\n\n /** Map an analytics render_chart frame (spec + StatsQueryRow[]) to an inline\n * widget. kpi \u2192 a stat; everything else \u2192 a table of the returned rows. */\n function renderServerWidget(\n spec: { title?: string; chartType?: string } | undefined,\n rows: unknown,\n comparisonRows?: unknown\n ): void {\n // ADVANCED VIEW: the big surface is the right place for a chart.\n //\n // A computed widget used to land in the 368px chat column while half the\n // screen next to it showed a page nobody was reading. Same principle as\n // the report narration collapsing when the pane shows the run: whichever\n // surface can render it properly gets it, and the chat says where it went\n // rather than drawing a second, smaller copy.\n //\n // Falls through to the log when the pane cannot take it (no host renderer,\n // or the renderer threw), so a failure here costs nothing.\n if (showPaneWidget(spec, rows, comparisonRows)) {\n const note = el(\"div\", `${PREFIX}-job-mirrored`);\n note.textContent = L(\"jobWatchingInPane\");\n log.appendChild(note);\n scrollDown(true);\n return;\n }\n // In-app: hand the frame to the host's REAL dashboard renderer (charts).\n if (opts.renderDataWidget) {\n const host = el(\"div\", `${PREFIX}-widget ${PREFIX}-widget-host`);\n log.appendChild(host);\n const dispose = opts.renderDataWidget(host, spec, rows, comparisonRows);\n if (dispose) richDisposers.push(dispose);\n scrollDown(true);\n return;\n }\n // Embedder-supplied chart fallback (no React host) \u2014 real charts in a\n // standalone embed via whatever lightweight lib the embedder plugs in.\n if (opts.renderChartFallback) {\n const host = el(\"div\", `${PREFIX}-widget ${PREFIX}-widget-host`);\n log.appendChild(host);\n try {\n const dispose = opts.renderChartFallback(\n host,\n spec,\n rows,\n comparisonRows\n );\n if (dispose) richDisposers.push(dispose);\n scrollDown(true);\n return;\n } catch {\n host.remove(); // fall through to the built-in degrade\n }\n }\n // External embed: lightweight fallback (kpi \u2192 stat, else a table).\n const title = spec?.title;\n const data = Array.isArray(rows)\n ? (rows as Array<Record<string, unknown>>)\n : [];\n if (spec?.chartType === \"kpi\" && data[0]) {\n const first = data[0];\n const keys = Object.keys(first);\n const valueKey = keys[keys.length - 1];\n renderWidget({\n kind: \"stat\",\n title,\n value: String(first[valueKey] ?? \"\"),\n caption: valueKey,\n });\n return;\n }\n if (!data.length) {\n renderWidget({ kind: \"list\", title, lines: [\"(no data)\"] });\n return;\n }\n const columns = Object.keys(data[0]);\n const tableRows = data\n .slice(0, 30)\n .map((r) => columns.map((c) => (r[c] == null ? \"\" : String(r[c]))));\n renderWidget({ kind: \"table\", title, columns, rows: tableRows });\n }\n\n function renderChips(spec: ChipsSpec): void {\n const options = (Array.isArray(spec.options) ? spec.options : [])\n .map((o) => String(o).trim())\n .filter(Boolean)\n .slice(0, 8);\n if (!options.length) return;\n const multi = spec.multi === true;\n const wrap = el(\"div\", `${PREFIX}-chips`);\n const chosen = new Set<string>();\n let answered = false;\n const answer = (text: string): void => {\n if (answered || !text.trim()) return;\n answered = true;\n wrap\n .querySelectorAll(\"button\")\n .forEach((b) => ((b as HTMLButtonElement).disabled = true));\n void send(text.trim());\n };\n for (const opt of options) {\n const b = el(\"button\", `${PREFIX}-chip`) as HTMLButtonElement;\n b.type = \"button\";\n b.textContent = opt;\n b.addEventListener(\"click\", () => {\n if (answered) return;\n if (!multi) {\n answer(opt);\n return;\n }\n if (chosen.has(opt)) {\n chosen.delete(opt);\n b.classList.remove(`${PREFIX}-chip-on`);\n } else {\n chosen.add(opt);\n b.classList.add(`${PREFIX}-chip-on`);\n }\n });\n wrap.appendChild(b);\n }\n // \"Other\" \u2014 let the user type a free answer instead of picking.\n if (spec.other !== false) {\n const other = el(\n \"button\",\n `${PREFIX}-chip ${PREFIX}-chip-other`\n ) as HTMLButtonElement;\n other.type = \"button\";\n other.textContent = L(\"otherOption\");\n other.addEventListener(\"click\", () => input.focus());\n wrap.appendChild(other);\n }\n // Multi-select needs an explicit Send (collect the toggled options).\n if (multi) {\n const go = el(\n \"button\",\n `${PREFIX}-chip ${PREFIX}-chip-send`\n ) as HTMLButtonElement;\n go.type = \"button\";\n go.textContent = L(\"send\");\n go.addEventListener(\"click\", () => {\n const picks = options.filter((o) => chosen.has(o));\n if (picks.length) answer(picks.join(\", \"));\n });\n wrap.appendChild(go);\n }\n log.appendChild(wrap);\n scrollDown(true);\n }\n\n /** Render an AI-authored HTML preview in a sandboxed iframe. Scripts stay\n * OFF (static HTML, can't execute), but allow-same-origin is granted so the\n * parent page's blob: URLs (resolved private media) can paint \u2014 an\n * opaque-origin frame refuses them and every resolved image breaks. */\n function renderPreview(spec: PreviewSpec): void {\n const wrap = el(\"div\", `${PREFIX}-preview`);\n const bar = el(\"div\", `${PREFIX}-preview-bar`);\n const dots = el(\"span\", `${PREFIX}-preview-dots`);\n dots.innerHTML = `<i></i><i></i><i></i>`;\n const title = el(\"span\", `${PREFIX}-preview-title`);\n title.textContent = spec.title || L(\"preview\");\n bar.append(dots, title);\n const frame = el(\"iframe\", `${PREFIX}-preview-frame`) as HTMLIFrameElement;\n frame.setAttribute(\"sandbox\", \"allow-same-origin\");\n frame.setAttribute(\"title\", spec.title || L(\"preview\"));\n frame.srcdoc = wrapPreviewHtml(spec.html);\n wrap.append(bar, frame);\n log.appendChild(wrap);\n scrollDown(true);\n }\n\n // Default confirm copy for a state-changing UI action the model didn't caption.\n function operateConfirmText(spec: ActionSpec): string {\n const target = spec.data?.target ?? \"this control\";\n if (spec.name === \"fill\") {\n const v = spec.data?.value ?? \"\";\n const short = v.length > 40 ? `${v.slice(0, 40)}\u2026` : v;\n return short\n ? `Type \u201C${short}\u201D into ${target}?`\n : `Fill ${target} for you?`;\n }\n return `Click ${target} for you?`;\n }\n\n return {\n renderWidget,\n renderUiCard,\n renderServerWidget,\n renderChips,\n renderPreview,\n operateConfirmText,\n };\n}\n", "/**\n * The GENERIC half of a confirm card: describe a tool call nobody has taught us\n * about, well enough that a person can approve or refuse it.\n *\n * The sgiant-specific half \u2014 fifteen branches phrasing sgiant's own tools, and\n * the titles that go with them \u2014 moved to `@sgiant/ai-apply` on 2026-09-02.\n * This package is published for anyone to embed, and a table of one company's\n * tool names is not something a stranger's users should be reading.\n *\n * What is left is what makes the widget SAFE to publish rather than merely safe\n * for sgiant: every host's own tools land here, and a blank card is a confirm\n * gate that confirms nothing. the confirm-card tests pin that.\n *\n * A host phrases its own tools by passing `proposalSummary` \u2014 return a string to\n * override, or null to fall through to this.\n */\n\n/** Render an unknown tool call from its arguments alone. */\nexport function genericProposalSummary(args: Record<string, unknown>): string {\n return Object.entries(args)\n .filter(([k]) => k !== \"id\")\n .map(([k, v]) => `${k}: ${describeArg(v)}`)\n .join(\"\\n\");\n}\n\n/**\n * One argument value, as a person can actually read it.\n *\n * `String(v)` on anything structured yields \"[object Object]\" \u2014 and on a list\n * of them, \"[object Object],[object Object],[object Object]\". A confirm card\n * that says that is asking the client to approve something they cannot see,\n * which is the one thing a confirm card must never do. Seen for real on a\n * three-scene storyboard: the whole point of the card is that the client\n * agrees the PLAN, and the plan was invisible.\n *\n * Deliberately a SUMMARY, not a JSON dump \u2014 a raw blob is technically honest\n * and just as unreadable. Objects are described by whichever of the handful\n * of naming fields they carry, so a scene reads as its title.\n */\nfunction describeArg(v: unknown, depth = 0): string {\n if (v === null || v === undefined) return \"\u2014\";\n if (Array.isArray(v)) {\n if (!v.length) return \"(none)\";\n // Nested lists are summarised by count rather than expanded: a card is a\n // glance, not a document.\n if (depth > 0) return `${v.length} items`;\n return v.map((x) => `\\n \u2022 ${describeArg(x, depth + 1)}`).join(\"\");\n }\n if (typeof v === \"object\") {\n const rec = v as Record<string, unknown>;\n // The fields something calls itself by, in the order a human would look.\n const named = [\"title\", \"name\", \"label\", \"key\", \"id\"]\n .map((k) => (typeof rec[k] === \"string\" ? (rec[k] as string) : \"\"))\n .find(Boolean);\n const detail =\n typeof rec.prompt === \"string\"\n ? rec.prompt\n : typeof rec.caption === \"string\"\n ? rec.caption\n : typeof rec.description === \"string\"\n ? rec.description\n : \"\";\n if (named || detail)\n return [named, detail && `\u2014 ${detail.slice(0, 90)}`]\n .filter(Boolean)\n .join(\" \");\n // Nothing self-describing: name its shape rather than lie about it.\n const keys = Object.keys(rec);\n return keys.length ? `{${keys.slice(0, 4).join(\", \")}}` : \"{}\";\n }\n return String(v);\n}\n", "/**\n * The widget's stylesheet, and the one decision that decides how it is themed.\n *\n * Extracted from index.ts (#320), which was 8278 lines with 5822 of them inside\n * a single function. This is the largest region that is NOT inside that\n * closure \u2014 730 lines that depend on exactly three things (the class prefix,\n * the motion tokens, and which side the launcher sits on) and are read by one\n * caller. Moving it is mechanical and changes no behaviour, which is the point:\n * the file it came from is the one #306 proposes publishing, and 8278 lines is\n * not something a third party can read.\n */\nimport { motionCssVars } from \"./limits\";\n\nimport { PREFIX } from \"./prefix\";\n\nlet stylesInjected = false;\n/**\n * Does the page around us define the PLATFORM's control variables?\n *\n * When the widget is mounted inside one of our own apps, `--input` / `--ring` /\n * `--card` / \u2026 are already on `<html>` (set by the host's own stylesheet), and\n * a text field in chat should be the same object as a text field on the page\n * behind it \u2014 same border, same focus ring, same radius, flipping together when\n * the app toggles `.dark`. When the widget is embedded on a customer's site,\n * none of that exists and it must fall back to its own `--aiw-*` palette.\n *\n * The check is deliberately shape-based, not merely presence-based: `--input`\n * is a plausible name for anyone to invent, but our tokens are HSL COMPONENTS\n * (\"220 9% 85%\") so they can be used with an alpha. A site whose `--input` is\n * `#fff` fails the test and we leave its page alone \u2014 the failure mode of\n * guessing wrong here is an unreadable chat form on someone else's website.\n */\n/**\n * The near-black the user bubble mixes the accent into.\n *\n * Exported because `index.ts` derives that bubble's foreground against the\n * RESULT of the mix, not against the accent (#307 one level down: over the teal\n * all three hosts pass, the bubble is `#4a9d9e` and the old literal `#fff` was\n * 3.18:1). Two copies of this number would put the derivation and the paint on\n * different backgrounds, which is the same bug again.\n */\nexport const USER_BUBBLE_INK = \"#04191b\";\n\nexport function hostDefinesPlatformTokens(): boolean {\n try {\n const cs = getComputedStyle(document.documentElement);\n const hslTriplet = /^-?[\\d.]+\\s+[\\d.]+%\\s+[\\d.]+%$/;\n const required = [\n \"--input\",\n \"--ring\",\n \"--card\",\n \"--foreground\",\n \"--muted-foreground\",\n \"--primary-foreground\",\n ];\n if (!required.every((v) => hslTriplet.test(cs.getPropertyValue(v).trim())))\n return false;\n return cs.getPropertyValue(\"--radius\").trim().length > 0;\n } catch {\n // No DOM/computed style (SSR, hostile sandbox) \u2014 own palette is always safe.\n return false;\n }\n}\n\n/**\n * DARK MODE: the host's own switch first, the OS only as a fallback.\n *\n * This sheet expressed dark ONLY as `@media (prefers-color-scheme:dark)`, which\n * is right for a widget dropped on a stranger's page and wrong inside our own\n * apps: they carry a light/dark/system switch of their own\n * (a host theme toggle flips `.dark` on the document\n * element). A user whose OS was dark and whose app was set to LIGHT got a dark\n * widget in a light app, and the reverse broke exactly as hard. Reported from\n * the product, and no gate could have caught it \u2014 nothing type-checks a media\n * query, and the widget looks correct in whichever mode the person who changed\n * it happened to be in.\n *\n * `-host-tokens` already answers \"are we inside one of our apps\": it is set at\n * mount by `hostDefinesPlatformTokens()`, which tests for the platform's own\n * HSL-COMPONENT control variables rather than merely for a name anyone could\n * invent. Where it is present the app owns the scheme in BOTH directions, so\n * the OS query must not apply. Where it is absent nothing else knows the answer\n * and the OS query is all there is.\n *\n' * The pane's colours are NOT in here any more. It used to carry a complete\n * parallel palette in literals \u2014 #f4f5f7 / #eef0f3 / #e7e8ec / #777 light and\n * #121212 / #1a1a1a / #262626 / #9b9b9b dark \u2014 which is what #306 measured as\n * \"the advanced pane does not participate in the token system at all\": a themed\n * host repainted the whole widget and this pane stayed grey. Every one of those\n * eight values was the light or dark reading of a token that already existed,\n * so the pane now reads the tokens and its dark half falls out for free. Only\n * the shadow differs by scheme, because that one genuinely is not a colour\n * token.\n *\n * It also lifts the URL strip off the floor: #777 on #eef0f3 measured 3.92:1,\n * under the 4.5:1 body-text minimum. --aiw-muted is documented above as a\n * CONTRAST FLOOR and clears it on both surfaces.\n *\n * ONE source string, interpolated under two selector scopes. A hand-copied\n * palette is a palette that drifts, and this one has three separate blocks.\n */\nconst darkRules = (bubble: string, panel: string): string => `\n${bubble},${panel}{--aiw-accent-ink:var(--aiw-accent-ink-dark,var(--aiw-accent));--aiw-surface:#161616;--aiw-surface-raised:#1d1d1d;--aiw-surface-2:#2c2c2c;--aiw-bg:#101010;--aiw-text:#eee;--aiw-text-2:#ddd;--aiw-muted:#9b9b9b;--aiw-border:#2a2a2a;--aiw-border-strong:#444;--aiw-border-soft:#262626;--aiw-danger-bg:#231613;--aiw-danger-border:#5a2c1d;--aiw-danger-text:#ff9b7a;--aiw-danger-text-2:#d3a08d;--aiw-ok-bg:#122017;--aiw-ok-border:#2c4d36;--aiw-ok-text:#7fd39a}\n${panel} .${PREFIX}-assistant code{background:rgba(255,255,255,.1)}\n${panel} .${PREFIX}-assistant blockquote{color:#aaa;border-left-color:color-mix(in srgb,var(--aiw-accent) 53%,transparent)}\n${panel} .${PREFIX}-assistant table.md-table th{color:#aaa;border-bottom-color:#2a2a2a}\n${panel} .${PREFIX}-assistant table.md-table td{border-bottom-color:#222}\n${panel} .${PREFIX}-assistant hr{border-top-color:#2a2a2a}\n${panel} .${PREFIX}-pane-frame{box-shadow:0 1px 4px rgba(0,0,0,.4)}\n${panel}.${PREFIX}-advanced .${PREFIX}-chatcol{border-right-color:#262626}\n${panel}.${PREFIX}-advanced.${PREFIX}-pane-collapsed .${PREFIX}-chatcol{border-right:0}\n`;\n\nexport function injectStyles(side: \"left\" | \"right\"): void {\n if (stylesInjected) return;\n stylesInjected = true;\n // WARNING, and it costs a round trip every time: this whole stylesheet is a\n // TEMPLATE LITERAL. A backtick anywhere below \u2014 including inside a CSS\n // comment, where it reads as harmless prose quoting a token name \u2014 closes the\n // JS string, and the file fails to compile with an error that points at the\n // wrong line and says only \"';' expected\". Do not quote identifiers with\n // backticks in these comments.\n // Computed HERE and not interpolated as calls, because the sheet below is a\n // template literal and `aiw-launcher.test.ts` refuses a backtick anywhere\n // inside it. That rule is worth keeping exactly as blunt as it is \u2014 a stray\n // backtick in a CSS comment closes the string and reports as \"';' expected\"\n // on the wrong line \u2014 so the calls come out rather than the guard being\n // loosened to understand `${...}` spans.\n const darkWhenHostSaysSo = darkRules(\n `.dark .${PREFIX}-bubble`,\n `.dark .${PREFIX}-panel`\n );\n const darkWhenNobodySaid = darkRules(\n `.${PREFIX}-bubble:not(.${PREFIX}-host-tokens)`,\n `.${PREFIX}-panel:not(.${PREFIX}-host-tokens)`\n );\n const css = `\n/* Theme tokens \u2014 every color in this sheet reads from these. Light defaults\n here; the dark media block below only REDEFINES tokens; explicit host\n overrides (accent/gradient/theme) land INLINE on the roots and win over\n both, so a themed host is never surprised by the OS color scheme. */\n/* Status surfaces (error card, failed job, flag notes) are TOKENS, not literal\n * colours. They used to be hardcoded light hexes with no dark counterpart, so\n * in dark mode a failed job painted #fdf3f3 (near-white) while its title stayed\n * var(--aiw-text) = #eee \u2014 white on white, i.e. invisible. Same class of bug\n * left the error card's rust text on its own near-black background. Anything\n * that needs a red/green surface must use these four vars so the dark override\n * below reaches it automatically.\n *\n * NOTE: this whole sheet is a JS template literal \u2014 no backticks in here.\n * --aiw-muted is a CONTRAST FLOOR, not a shade preference. It was #888,\n * which measures 3.54:1 on this sheet's own white surface \u2014 under the 4.5:1\n * body-text minimum, on the token carrying timestamps, job states and event\n * detail. #6e6e6e clears it against every surface the widget paints\n * (5.10:1 on #fff, 4.76:1 on the darkest, #f7f7f8). The dark counterpart\n * (#9b9b9b) already passes at 6.51:1 / 5.02:1 and is unchanged. Anything\n' * lighter than this in light mode is a regression, not a restyle.\n *\n * --aiw-header-bg / --aiw-header-fg are the HEADER's own pair, and they exist\n * because the bar is not an accent-filled control. Its buttons read\n * --aiw-accent-contrast, which #307 correctly made DERIVED from the accent\n * (\"white on the teal every host actually passes is 2.00:1\"). Against the teal\n * all three of our apps pass, that derivation returns brand navy #151D2F \u2014\n * which is the header background. Navy on navy: 1.00:1, an invisible toolbar,\n * shipped and reported from the product. A token means \"readable on the\n * ACCENT\"; the header is not the accent. Cream on navy is 15.66:1.\n *\n * These two deliberately do NOT flip with the colour scheme, and neither does\n * the bar. --aiw-surface does, which is how the active-icon chip came to paint\n * #161616 on #151D2F in dark mode. */\n.${PREFIX}-bubble,.${PREFIX}-panel{${motionCssVars()}--aiw-font-3xs:10px;--aiw-font-2xs:11px;--aiw-font-xs:12px;--aiw-font-sm:13px;--aiw-font-md:14px;--aiw-font-lg:15px;--aiw-font-xl:16px;--aiw-font-2xl:18px;--aiw-font-3xl:30px;--aiw-radius-2xs:2px;--aiw-radius-xs:4px;--aiw-radius-sm:6px;--aiw-radius-md:8px;--aiw-radius-lg:10px;--aiw-radius-xl:12px;--aiw-radius-2xl:14px;--aiw-radius-3xl:18px;--aiw-radius-pill:999px;--aiw-accent:#6d28d9;--aiw-accent-contrast:#fff;--aiw-accent-ink:var(--aiw-accent-ink-light,var(--aiw-accent));--aiw-header-bg:#151D2F;--aiw-header-fg:#FCF7E3;--aiw-gradient:linear-gradient(135deg,var(--aiw-accent),var(--aiw-accent));--aiw-surface:#fff;--aiw-surface-raised:#fff;--aiw-surface-2:#f7f7f8;--aiw-bg:#fafafa;--aiw-text:#111;--aiw-text-2:#555;--aiw-muted:#6e6e6e;--aiw-border:#e6e6e6;--aiw-border-strong:#ddd;--aiw-border-soft:#f0f0f0;--aiw-danger-bg:#fff6f2;--aiw-danger-border:#f3c5b6;--aiw-danger-text:#b23b18;--aiw-danger-text-2:#8a5648;--aiw-ok-bg:#f2fbf5;--aiw-ok-border:#bfe3c8;--aiw-ok-text:#2f7d43}\n@keyframes ${PREFIX}-spin{to{transform:rotate(360deg)}}\n@keyframes ${PREFIX}-rise{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}\n@keyframes ${PREFIX}-blink{0%,80%,100%{opacity:.25;transform:translateY(0)}40%{opacity:1;transform:translateY(-3px)}}\n@keyframes ${PREFIX}-richin{from{opacity:.15;transform:translateY(3px)}to{opacity:1;transform:none}}\n.${PREFIX}-rich-in{animation:${PREFIX}-richin var(--duration-base) var(--ease-out)}\n@keyframes ${PREFIX}-tokin{from{opacity:0;filter:blur(5px)}to{opacity:1;filter:blur(0)}}\n.${PREFIX}-tok{animation:${PREFIX}-tokin var(--duration-base) var(--ease-out) forwards}\n.${PREFIX}-activity{align-self:flex-start;display:inline-flex;align-items:center;gap:7px;max-width:92%;border:1px solid color-mix(in srgb,var(--aiw-accent) 15%,transparent);background:color-mix(in srgb,var(--aiw-accent) 5%,transparent);border-radius:var(--aiw-radius-lg);padding:5px 10px;font-size:var(--aiw-font-xs);font-weight:500;animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}\n.${PREFIX}-activity-done{opacity:.72}\n.${PREFIX}-act-label{color:var(--aiw-text-2)}\n.${PREFIX}-act-agent{flex:0 0 auto;font-size:var(--aiw-font-3xs);font-weight:700;letter-spacing:.3px;text-transform:uppercase;color:var(--aiw-accent-ink);background:color-mix(in srgb,var(--aiw-accent) 12%,transparent);border:1px solid color-mix(in srgb,var(--aiw-accent) 24%,transparent);border-radius:var(--aiw-radius-sm);padding:1px 6px}\n.${PREFIX}-act-model{flex:0 0 auto;font-size:var(--aiw-font-3xs);font-weight:600;letter-spacing:.2px;color:var(--aiw-muted);background:#9aa0a614;border:1px solid #9aa0a630;border-radius:var(--aiw-radius-sm);padding:1px 5px;font-variant-numeric:tabular-nums}\n.${PREFIX}-act-spin{width:11px;height:11px;flex:0 0 auto;border-radius:50%;border:2px solid color-mix(in srgb,var(--aiw-accent) 27%,transparent);border-top-color:var(--aiw-accent);animation:${PREFIX}-spin .7s linear infinite}\n.${PREFIX}-act-ok{color:var(--aiw-ok-text);font-weight:700}\n.${PREFIX}-act-x{color:var(--aiw-danger-text);font-weight:700}\n.${PREFIX}-job{align-self:flex-start;display:flex;flex-direction:column;gap:6px;max-width:92%;min-width:min(260px,100%);border:1px solid color-mix(in srgb,var(--aiw-accent) 18%,transparent);background:color-mix(in srgb,var(--aiw-accent) 5%,transparent);border-radius:var(--aiw-radius-xl);padding:9px 11px;font-size:var(--aiw-font-xs);animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}\n.${PREFIX}-job-done{opacity:.85}\n.${PREFIX}-job-failed{border-color:var(--aiw-danger-border);background:var(--aiw-danger-bg)}\n.${PREFIX}-job-head{display:flex;align-items:center;gap:7px}\n.${PREFIX}-job-title{font-weight:600;color:var(--aiw-text)}\n.${PREFIX}-job-state{margin-left:auto;font-size:var(--aiw-font-3xs);font-weight:700;letter-spacing:.3px;text-transform:uppercase;color:var(--aiw-muted)}\n.${PREFIX}-job-counts{color:var(--aiw-text-2);font-variant-numeric:tabular-nums}\n.${PREFIX}-job-bar{height:4px;border-radius:var(--aiw-radius-2xs);background:color-mix(in srgb,var(--aiw-accent) 14%,transparent);overflow:hidden}\n/* scaleX, not width (#309). Animating width reflows on every frame; a\n transform is composited. transform-origin:left so it grows from the start of\n the track rather than its middle. */\n.${PREFIX}-job-bar>i{display:block;height:100%;width:100%;transform-origin:left;background:var(--aiw-accent);transition:transform var(--duration-base) var(--ease-out)}\n.${PREFIX}-job-detail{color:var(--aiw-muted);font-size:var(--aiw-font-2xs);word-break:break-word}\n.${PREFIX}-job-mirrored{color:var(--aiw-muted);font-size:var(--aiw-font-2xs);font-style:italic}\n.${PREFIX}-pane-widget{width:100%;height:100%;overflow:auto;background:var(--aiw-surface);border-radius:var(--aiw-radius-xl);padding:16px;box-sizing:border-box}\n.${PREFIX}-job-flow{list-style:none;margin:6px 0 0;padding:0 0 0 2px;display:flex;flex-direction:column;gap:3px}\n.${PREFIX}-job-ev{position:relative;padding-left:12px;color:var(--aiw-text-2);font-size:var(--aiw-font-2xs);line-height:1.35;word-break:break-word}\n.${PREFIX}-job-ev::before{content:\"\";position:absolute;left:0;top:6px;width:5px;height:5px;border-radius:50%;background:var(--aiw-muted)}\n.${PREFIX}-job-ev-decision{color:var(--aiw-text)}\n.${PREFIX}-job-ev-decision::before{background:var(--aiw-accent,currentColor)}\n.${PREFIX}-job-ev-problem{color:var(--aiw-danger-text)}\n.${PREFIX}-job-ev-problem::before{background:currentColor}\n.${PREFIX}-job-flow-all{margin-top:4px}\n.${PREFIX}-job-flow-all>summary{cursor:pointer;color:var(--aiw-muted);font-size:var(--aiw-font-2xs);user-select:none}\n.${PREFIX}-replay-note{align-self:flex-start;display:inline-flex;align-items:center;border:1px dashed var(--aiw-border-strong);border-radius:var(--aiw-radius-md);padding:4px 10px;font-size:var(--aiw-font-xs);color:var(--aiw-muted);background:var(--aiw-bg)}\n.${PREFIX}-flag-note{align-self:center;border-style:solid;border-color:var(--aiw-danger-border);color:var(--aiw-danger-text);background:var(--aiw-danger-bg);font-weight:600}\n.${PREFIX}-flag-ok{border-color:var(--aiw-ok-border);color:var(--aiw-ok-text);background:var(--aiw-ok-bg)}\n.${PREFIX}-usage{align-self:flex-start;display:inline-flex;align-items:center;gap:6px;margin-top:-4px;padding:0 2px;font-size:var(--aiw-font-3xs);color:var(--aiw-muted);font-variant-numeric:tabular-nums}\n.${PREFIX}-usage-pill{display:inline-flex;align-items:center;border:1px solid var(--aiw-border);border-radius:var(--aiw-radius-sm);padding:0 5px;line-height:16px}\n.${PREFIX}-usage-sep{opacity:.5}\n@keyframes ${PREFIX}-pulse{0%{box-shadow:0 0 0 0 rgba(96,199,200,.5)}70%{box-shadow:0 0 0 12px rgba(96,199,200,0)}100%{box-shadow:0 0 0 0 rgba(96,199,200,0)}}\n@keyframes ${PREFIX}-float{0%,100%{transform:translateY(0)}50%{transform:translateY(-2.5px)}}\n@keyframes ${PREFIX}-blink2{0%,90%,100%{transform:scaleY(1)}95%{transform:scaleY(.12)}}\n/* The crescent (#305). No float loop, no drop-shadow glow: those were two of\n the eight effects the launcher stacked at rest, and a control that is loud at\n rest has nothing left to say when a reply is waiting. */\n.${PREFIX}-ayca{display:block;width:100%;height:100%;overflow:visible}\n/* THE INVERSION, in two rules. Light ground: navy disc, gradient mark. Ink\n ground: cream disc, navy mark. Nothing else needs to know about grounds. */\n.${PREFIX}-av-mark{fill:url(#${PREFIX}-av-sweep)}\n.${PREFIX}-on-ink .${PREFIX}-av-mark{fill:#151D2F}\n/* THE OBJECT INVERTS, not just the mark. Inverting only the fill left a navy\n mark on a navy disc \u2014 invisible, and precisely the failure the rule exists to\n prevent: \"a navy pebble on a navy section is a hole with a moon in it\". Found\n by looking at it on the dark marketing hero, where the class was correct and\n the result was still nothing. */\n/* The ink variant carries a heavier shadow (it has less to push against) and a\n HAIRLINE of the opposite value. The hairline is what makes a stale ground\n reading survivable: a cream disc that ends up over white is 1.06:1 and\n effectively gone, and the ground under a fixed launcher genuinely changes as\n the reader scrolls. One pixel of navy is enough to keep it an object. */\n.${PREFIX}-bubble.${PREFIX}-on-ink{background:#FCF7E3;box-shadow:0 8px 24px rgba(0,0,0,.45),0 0 0 1px rgba(21,29,47,.55)}\n.${PREFIX}-bubble.${PREFIX}-on-ink .${PREFIX}-bubble-label{color:#151D2F}\n.${PREFIX}-bubble.${PREFIX}-on-ink .${PREFIX}-bubble-dot{box-shadow:0 0 0 2px #FCF7E3}\n.${PREFIX}-eyes{transform-origin:24px 23px;animation:${PREFIX}-blink2 5.5s ease-in-out infinite}\n/* The collapsed launcher is a real, recognisable FAB: a circular surface chip\n with a soft shadow and a hairline ring, the animated mascot centred inside.\n Without the chip the mascot floated as a formless gradient blob that did not\n read as \"a chat\" at all \u2014 the reported \"I can't see any chatbox\" (#88): the\n widget was mounted and working, just invisible as an affordance. */\n/* Layering: the widget sits ABOVE page content and content-level overlays\n (viewers/drawers, z<=40) but BELOW the app's Radix modals (z-50) \u2014 a dialog\n the user opens must cover the chat, not hide behind it. The old max-int\n z-index put the chat over every modal. */\n/* THE LAUNCHER AT REST IS STILL. It is a navy disc carrying the gradient\n crescent \u2014 the object straddles navy, per the ground rule above \u2014 with one\n shadow to lift it off the page. The gradient RING is gone with the glow and\n the float: the mark already carries the brand, and the ring was a third\n place saying the same thing. #88 (\"I can't see any chatbox\") is answered by\n the disc's own contrast (navy on white is 16.82:1) and, on a first visit, by\n the named pill below \u2014 a word rather than a puzzle. */\n.${PREFIX}-bubble{position:fixed;bottom:18px;${side}:18px;z-index:48;width:56px;height:56px;border:none;border-radius:50%;background:#151D2F;color:#FCF7E3;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:9px;padding:0;box-shadow:0 6px 20px rgba(21,29,47,.22);transition:transform var(--duration-fast,150ms) var(--ease-out),box-shadow var(--duration-fast,150ms) var(--ease-out)}\n/* 26 in 56. The mark was 34 at first and the object read flat \u2014 a disc with a\n shape stamped on it rather than a mark sitting in space. The ratio is the\n richness here, not an effect. */\n.${PREFIX}-bubble .${PREFIX}-ayca{width:26px;height:26px;transition:transform var(--duration-base,300ms) var(--ease-out)}\n.${PREFIX}-bubble:active{transform:translateY(0) scale(.96)}\n/* The first-visit variant: a word, not a puzzle. Collapses to the pebble once\n the reader has opened it. */\n.${PREFIX}-bubble-pill{width:auto;height:48px;border-radius:var(--aiw-radius-pill);padding:0 18px 0 14px}\n.${PREFIX}-bubble-pill .${PREFIX}-ayca{width:22px;height:22px}\n.${PREFIX}-bubble-pill .${PREFIX}-bubble-label{display:block}\n.${PREFIX}-bubble-label{display:none;font:inherit;font-size:var(--aiw-font-md);font-weight:600;white-space:nowrap}\n/* Only TWO of the six states move, which is what makes movement mean\n something again. */\n.${PREFIX}-bubble-unread{animation:${PREFIX}-pulse var(--duration-slow,420ms) var(--ease-out) 1}\n.${PREFIX}-bubble-working .${PREFIX}-ayca{animation:${PREFIX}-breathe 2.4s var(--ease-in-out) infinite}\n.${PREFIX}-bubble-offline{opacity:.72}\n/* A DOT, not the count badge in grey. It reuses the same element, and the\n count's geometry (18px min-width plus 5px of padding, sized for two digits)\n renders an empty string as a wide lozenge \u2014 which reads as a broken badge\n rather than a status light. */\n.${PREFIX}-bubble-offline .${PREFIX}-bubble-dot{min-width:0;width:10px;height:10px;padding:0;top:4px;background:var(--aiw-muted);box-shadow:0 0 0 2px #151D2F}\n.${PREFIX}-bubble-offline.${PREFIX}-on-ink .${PREFIX}-bubble-dot{box-shadow:0 0 0 2px #FCF7E3}\n.${PREFIX}-bubble-parked{width:40px;height:40px;opacity:.6}\n.${PREFIX}-bubble-parked .${PREFIX}-ayca{width:20px;height:20px}\n/* The unread count and the offline dot \u2014 one badge element, two looks. */\n.${PREFIX}-bubble-dot{position:absolute;top:2px;${side === \"left\" ? \"right\" : \"left\"}:2px;min-width:18px;height:18px;padding:0 5px;border-radius:var(--aiw-radius-pill);background:#FA712D;color:#FCF7E3;font-size:var(--aiw-font-2xs);font-weight:700;line-height:18px;text-align:center;box-shadow:0 0 0 2px #151D2F}\n@keyframes ${PREFIX}-breathe{0%,100%{opacity:1}50%{opacity:.55}}\n/* GATED. There was no hover:hover query anywhere in this widget, so on a\n touchscreen the lift-and-scale stuck after the tap. The file already carries\n a correct hover:none block for the per-message actions \u2014 the launcher was\n simply never given the same treatment. Lift only; the scale went with it,\n because a control that grows under the cursor is a third thing saying \"I am\n interactive\" after the shadow and the pointer. */\n@media (hover: hover) and (pointer: fine){\n.${PREFIX}-bubble:hover{transform:translateY(-2px);box-shadow:0 10px 26px rgba(21,29,47,.3)}\n.${PREFIX}-bubble:hover .${PREFIX}-ayca{transform:rotate(-12deg)}\n}\n.${PREFIX}-bubble-av{position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center}\n.${PREFIX}-av-img{width:100%;height:100%;object-fit:cover;border-radius:50%}\n.${PREFIX}-bubble svg{width:26px;height:26px}\n.${PREFIX}-panel{position:fixed;bottom:20px;${side}:20px;z-index:48;width:368px;max-width:calc(100vw - 32px);height:540px;max-height:calc(100vh - 40px);background:var(--aiw-surface);color:var(--aiw-text);border-radius:var(--aiw-radius-3xl);box-shadow:0 18px 52px rgba(0,0,0,.32);display:flex;flex-direction:column;overflow:hidden;font-family:system-ui,-apple-system,sans-serif;animation:${PREFIX}-rise var(--duration-fast) var(--ease-out);}\n/* NAVY, with the sweep as a 2px rule under it (#305). It was the gradient bar\n itself, which is why the avatar needed an rgba(12,17,30,.55) scrim to survive\n \u2014 a scrim over a gradient is a patch, not a decision. On navy the mark\n inverts by the rule above and the scrim is unnecessary, so it is gone. */\n.${PREFIX}-header{background:var(--aiw-header-bg);color:var(--aiw-header-fg);border-bottom:2px solid transparent;border-image:var(--aiw-gradient) 1;padding:12px 14px;display:flex;align-items:center;gap:10px}\n/* Drag-to-reposition. touch-action:none is what makes this work on a\n touchscreen \u2014 without it the browser claims the gesture as a scroll and the\n panel never moves. user-select:none stops the title being selected mid-drag. */\n/* Question card \u2014 the assistant asking the human to decide. Visually distinct\n from a proposal card: this one BLOCKS progress until answered, so it should\n read as something needing you, not as another message. */\n.${PREFIX}-question{margin:8px 0;padding:12px;border-radius:var(--aiw-radius-xl);background:var(--aiw-surface-raised);border:1px solid var(--aiw-border);animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}\n.${PREFIX}-question-critical{border-color:var(--aiw-danger-text);box-shadow:0 0 0 1px color-mix(in srgb,var(--aiw-danger-text) 25%,transparent)}\n.${PREFIX}-question-title{font-size:var(--aiw-font-md);font-weight:600;margin-bottom:4px;color:var(--aiw-text)}\n.${PREFIX}-question-ctx{font-size:var(--aiw-font-xs);color:var(--aiw-muted);margin-bottom:8px;line-height:1.45}\n.${PREFIX}-question-opts{display:flex;flex-direction:column;gap:6px}\n.${PREFIX}-question-opt{display:flex;flex-direction:column;gap:2px;text-align:left;padding:9px 11px;border-radius:var(--aiw-radius-md);border:1px solid var(--aiw-border);background:var(--aiw-surface);color:var(--aiw-text);cursor:pointer;font:inherit;transition:border-color .14s ease,background .14s ease}\n.${PREFIX}-question-opt:hover{border-color:var(--aiw-accent);background:var(--aiw-surface-raised)}\n.${PREFIX}-question-opt-on{border-color:var(--aiw-accent);background:var(--aiw-surface-raised)}\n.${PREFIX}-question-opt-label{font-size:var(--aiw-font-sm);font-weight:600}\n.${PREFIX}-question-opt-desc{font-size:var(--aiw-font-xs);color:var(--aiw-muted);line-height:1.4}\n.${PREFIX}-question-free{display:flex;gap:6px;margin-top:2px}\n.${PREFIX}-question-input{flex:1 1 auto;min-width:0;padding:9px 11px;border-radius:var(--aiw-radius-md);border:1px solid var(--aiw-border);background:var(--aiw-surface);color:var(--aiw-text);font:inherit;font-size:var(--aiw-font-sm)}\n.${PREFIX}-question-send{padding:9px 14px;border-radius:var(--aiw-radius-md);border:none;background:var(--aiw-accent);color:var(--aiw-accent-contrast);font:inherit;font-size:var(--aiw-font-sm);font-weight:600;cursor:pointer;margin-top:6px}\n/* A multi-select confirm with nothing picked has nothing to send \u2014 it says so\n rather than looking clickable and doing nothing. */\n.${PREFIX}-question-send:disabled{opacity:.5;cursor:not-allowed}\n.${PREFIX}-question-err{margin-top:8px;font-size:var(--aiw-font-xs);line-height:1.45;color:var(--aiw-danger-text)}\n/* Answered: collapsed to the decision, so the transcript reads as a\n conversation rather than a dead form. */\n.${PREFIX}-question-done{border-style:dashed;opacity:.75}\n.${PREFIX}-question-answer{font-size:var(--aiw-font-sm);color:var(--aiw-text-2)}\n.${PREFIX}-question-answer::before{content:\"\u2713 \";color:var(--aiw-accent-ink)}\n.${PREFIX}-draggable{cursor:grab;touch-action:none;user-select:none;-webkit-user-select:none}\n.${PREFIX}-draggable:active{cursor:grabbing}\n/* Controls inside the header keep normal behaviour \u2014 the drag handler ignores\n them, and this keeps the cursor honest about that. */\n.${PREFIX}-draggable button,.${PREFIX}-draggable a{cursor:pointer;touch-action:auto}\n/* Advanced view is a fixed edge-to-edge layout \u2014 dragging is disabled there\n (the pointerdown handler bails), so drop the grab affordance too. */\n.${PREFIX}-advanced .${PREFIX}-draggable{cursor:default}\n/* Cream disc on the navy header \u2014 the ink half of the inversion. The scrim it\n used to carry is gone; see the header rule. */\n.${PREFIX}-avatar{position:relative;width:38px;height:38px;flex:0 0 auto;display:flex;align-items:center;justify-content:center;border-radius:50%;background:#FCF7E3}\n.${PREFIX}-avatar .${PREFIX}-ayca{width:34px;height:34px}\n.${PREFIX}-hname{display:flex;flex-direction:column;line-height:1.15;min-width:0;flex:1 1 auto}\n.${PREFIX}-title{font-weight:700;font-size:var(--aiw-font-lg);letter-spacing:.04em}\n.${PREFIX}-sub{font-size:var(--aiw-font-2xs);opacity:.85}\n.${PREFIX}-metachip{align-self:flex-start;margin-top:3px;font-size:var(--aiw-font-3xs);font-weight:700;letter-spacing:.04em;text-transform:uppercase;padding:1px 6px;border-radius:var(--aiw-radius-sm);background:rgba(255,255,255,.22);color:var(--aiw-accent-contrast);white-space:nowrap;max-width:100%;overflow:hidden;text-overflow:ellipsis}\n.${PREFIX}-close{background:color-mix(in srgb,var(--aiw-header-fg) 15%,transparent);border:none;color:var(--aiw-header-fg);font-size:var(--aiw-font-2xl);line-height:1;width:26px;height:26px;border-radius:var(--aiw-radius-md);cursor:pointer;flex:0 0 auto}\n.${PREFIX}-log{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;padding:14px;display:flex;flex-direction:column;gap:10px;background:var(--aiw-bg);scrollbar-width:thin}\n.${PREFIX}-log:focus-visible{outline:none}\n.${PREFIX}-log::-webkit-scrollbar{width:8px}\n.${PREFIX}-log::-webkit-scrollbar-thumb{background:rgba(0,0,0,.18);border-radius:var(--aiw-radius-md)}\n@keyframes ${PREFIX}-caret{0%,55%{opacity:.85}55.01%,100%{opacity:0}}\n.${PREFIX}-streaming::after{content:\"\";display:inline-block;width:2px;height:1.05em;margin-left:1px;border-radius:var(--aiw-radius-2xs);background:var(--aiw-accent);vertical-align:-2px;animation:${PREFIX}-caret 1.1s steps(1) infinite}\n.${PREFIX}-msg{max-width:85%;padding:9px 12px;border-radius:var(--aiw-radius-2xl);font-size:var(--aiw-font-md);line-height:1.45;white-space:pre-wrap;word-break:break-word;animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}\n.${PREFIX}-msg.${PREFIX}-user{align-self:flex-end;background:var(--aiw-user-bg,color-mix(in srgb,var(--aiw-accent) 76%,${USER_BUBBLE_INK}));color:var(--aiw-user-contrast,#fff);border-bottom-right-radius:4px}\n.${PREFIX}-assistant{align-self:flex-start;background:var(--aiw-surface-raised);color:var(--aiw-text);border:1px solid var(--aiw-border);border-bottom-left-radius:4px}\n.${PREFIX}-assistant p{margin:0 0 8px}.${PREFIX}-assistant>:last-child{margin-bottom:0}\n.${PREFIX}-assistant h1,.${PREFIX}-assistant h2,.${PREFIX}-assistant h3,.${PREFIX}-assistant h4{margin:10px 0 6px;font-weight:700;line-height:1.25}\n.${PREFIX}-assistant h1{font-size:var(--aiw-font-xl)}.${PREFIX}-assistant h2{font-size:var(--aiw-font-xl)}.${PREFIX}-assistant h3{font-size:var(--aiw-font-md)}.${PREFIX}-assistant h4{font-size:var(--aiw-font-sm)}\n.${PREFIX}-assistant ul,.${PREFIX}-assistant ol{margin:6px 0;padding-left:20px}\n.${PREFIX}-assistant li{line-height:1.45;margin:2px 0}\n.${PREFIX}-assistant a{color:var(--aiw-accent-ink);text-decoration:underline;text-underline-offset:2px}\n.${PREFIX}-assistant code{background:rgba(0,0,0,.06);border-radius:var(--aiw-radius-sm);padding:1px 5px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:var(--aiw-font-xs)}\n.${PREFIX}-assistant pre.md-pre{background:#0d1117;color:#e6edf3;border-radius:var(--aiw-radius-lg);padding:10px 12px;overflow:auto;margin:8px 0}\n.${PREFIX}-assistant pre.md-pre code{background:none;padding:0;color:inherit;font-size:var(--aiw-font-xs);white-space:pre}\n.${PREFIX}-assistant blockquote{margin:8px 0;padding:2px 12px;border-left:3px solid color-mix(in srgb,var(--aiw-accent) 40%,transparent);color:var(--aiw-text-2)}\n.${PREFIX}-assistant hr{border:none;border-top:1px solid var(--aiw-border);margin:10px 0}\n.${PREFIX}-assistant table.md-table{width:100%;border-collapse:collapse;font-size:var(--aiw-font-xs);margin:8px 0}\n.${PREFIX}-assistant table.md-table th{text-align:left;font-weight:700;color:var(--aiw-text-2);border-bottom:1px solid var(--aiw-border);padding:5px 8px}\n.${PREFIX}-assistant table.md-table td{border-bottom:1px solid var(--aiw-border-soft);padding:5px 8px}\n.${PREFIX}-assistant strong{font-weight:700}.${PREFIX}-assistant del{opacity:.7}\n.${PREFIX}-widget-host{padding:8px}\n.${PREFIX}-typing{align-self:flex-start;display:flex;gap:4px;padding:10px 12px}\n.${PREFIX}-typing span{width:7px;height:7px;border-radius:50%;background:var(--aiw-accent);animation:${PREFIX}-blink 1.2s infinite}\n.${PREFIX}-typing span:nth-child(2){animation-delay:.2s}\n.${PREFIX}-typing span:nth-child(3){animation-delay:.4s}\n.${PREFIX}-error{align-self:stretch;border:1px solid var(--aiw-danger-border);background:var(--aiw-danger-bg);border-radius:var(--aiw-radius-2xl);padding:11px 12px;animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}\n.${PREFIX}-error-text{font-size:var(--aiw-font-sm);font-weight:600;color:var(--aiw-danger-text)}\n.${PREFIX}-error-detail{font-size:var(--aiw-font-2xs);color:var(--aiw-danger-text-2);margin-top:3px;word-break:break-word}\n.${PREFIX}-error-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:9px}\n.${PREFIX}-error-btn{border:1px solid var(--aiw-danger-border);background:var(--aiw-surface);color:var(--aiw-danger-text);border-radius:var(--aiw-radius-md);padding:6px 12px;font-size:var(--aiw-font-xs);font-weight:600;cursor:pointer}\n.${PREFIX}-error-retry{background:var(--aiw-accent);border-color:var(--aiw-accent);color:var(--aiw-accent-contrast)}\n.${PREFIX}-error-btn:disabled{opacity:.6;cursor:default}\n/* Quality prompt (#299) \u2014 the widget asking, rather than waiting to be told.\n Reads as a quiet aside, not an alert: it must never look like the error card,\n because on a slow-but-good answer nothing has gone wrong. */\n.${PREFIX}-quality{align-self:stretch;border:1px solid var(--aiw-border);background:var(--aiw-surface-2);border-radius:var(--aiw-radius-2xl);padding:11px 12px;animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}\n.${PREFIX}-quality-title{font-size:var(--aiw-font-xs);font-weight:600;color:var(--aiw-text);line-height:1.35}\n.${PREFIX}-quality-row{display:flex;align-items:center;gap:8px;margin-top:9px;flex-wrap:wrap}\n.${PREFIX}-quality-reason{flex:1 1 160px;min-width:0;border:1px solid var(--aiw-border);background:var(--aiw-surface);color:var(--aiw-text);border-radius:var(--aiw-radius-md);padding:7px 10px;font:inherit;font-size:var(--aiw-font-xs)}\n.${PREFIX}-quality-reason::placeholder{color:var(--aiw-text-3)}\n.${PREFIX}-quality-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:9px}\n.${PREFIX}-quality-send{border:1px solid var(--aiw-accent);background:var(--aiw-accent);color:var(--aiw-accent-contrast);border-radius:var(--aiw-radius-md);padding:6px 12px;font-size:var(--aiw-font-xs);font-weight:600;cursor:pointer}\n.${PREFIX}-quality-send:disabled{opacity:.6;cursor:default}\n.${PREFIX}-quality-dismiss{border:1px solid var(--aiw-border);background:var(--aiw-surface);color:var(--aiw-text-2);border-radius:var(--aiw-radius-md);padding:6px 12px;font-size:var(--aiw-font-xs);font-weight:600;cursor:pointer}\n/* Answered state: the card collapses to a single line of thanks. */\n.${PREFIX}-quality-done{font-size:var(--aiw-font-xs);color:var(--aiw-text-2);font-weight:500}\n.${PREFIX}-suggestions{display:flex;flex-wrap:wrap;gap:6px;padding:8px 10px 0;background:var(--aiw-surface)}\n.${PREFIX}-suggestion{border:1px solid color-mix(in srgb,var(--aiw-accent) 20%,transparent);background:color-mix(in srgb,var(--aiw-accent) 5%,transparent);color:var(--aiw-accent-ink);border-radius:var(--aiw-radius-pill);padding:6px 11px;font-size:var(--aiw-font-xs);font-weight:500;line-height:1.2;cursor:pointer;transition:background .15s ease,border-color .15s ease;text-align:left}\n.${PREFIX}-suggestion:hover{background:color-mix(in srgb,var(--aiw-accent) 10%,transparent);border-color:color-mix(in srgb,var(--aiw-accent) 40%,transparent)}\n.${PREFIX}-form{display:flex;gap:8px;padding:10px;border-top:1px solid var(--aiw-border);background:var(--aiw-surface)}\n.${PREFIX}-input{flex:1;background:var(--aiw-surface);color:var(--aiw-text);border:1px solid var(--aiw-border-strong);border-radius:var(--aiw-radius-lg);padding:10px 12px;font-size:var(--aiw-font-md);outline:none}\n.${PREFIX}-meter{padding:7px 12px 0;background:var(--aiw-surface)}\n.${PREFIX}-meter-bar{height:4px;border-radius:var(--aiw-radius-pill);background:var(--aiw-surface-2);overflow:hidden}\n.${PREFIX}-meter-bar>span{display:block;height:100%;width:100%;transform-origin:left;border-radius:var(--aiw-radius-pill);background:linear-gradient(90deg,var(--aiw-accent),#FBAA34);transition:transform var(--duration-base) var(--ease-out)}\n.${PREFIX}-meter-row{display:flex;justify-content:space-between;gap:8px;margin-top:3px;font-size:var(--aiw-font-3xs);color:var(--aiw-muted)}\n.${PREFIX}-status{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:7px 12px;font-size:var(--aiw-font-2xs);background:var(--aiw-surface);border-top:1px solid var(--aiw-border-soft)}\n.${PREFIX}-status-role{display:inline-flex;align-items:center;padding:2px 8px;border-radius:var(--aiw-radius-pill);font-weight:600;color:var(--aiw-accent-ink);background:color-mix(in srgb,var(--aiw-accent) 10%,transparent)}\n.${PREFIX}-status-credits{font-weight:600;color:var(--aiw-text-2);font-variant-numeric:tabular-nums}\n.${PREFIX}-status-credits-val{display:inline-block;transition:color .2s ease}\n.${PREFIX}-credits-live{color:var(--aiw-accent-ink)}\n.${PREFIX}-cta{display:flex;justify-content:center;padding:6px 0 2px}\n.${PREFIX}-cta-btn{display:inline-flex;align-items:center;justify-content:center;border-radius:var(--aiw-radius-pill);padding:9px 18px;font-size:var(--aiw-font-sm);font-weight:600;color:var(--aiw-accent-contrast);text-decoration:none;background:linear-gradient(90deg,var(--aiw-accent),#FBAA34);box-shadow:0 4px 14px color-mix(in srgb,var(--aiw-accent) 25%,transparent)}\n.${PREFIX}-lead{align-self:stretch;border:1px solid var(--aiw-border);border-radius:var(--aiw-radius-2xl);padding:11px 12px;background:var(--aiw-surface-raised);animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}\n.${PREFIX}-form-title{font-size:var(--aiw-font-sm);font-weight:600;margin-bottom:8px}\n.${PREFIX}-lead-form{display:flex;flex-direction:column;gap:8px}\n/* \u2500\u2500 Form controls \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 Everything the chat can draw as an input goes through ONE token layer\n (--aiw-field-*) instead of its own hexes and radii, and that layer is\n remapped below to the platform's semantic control variables when the host\n page has them. The geometry (40px tall, 12px inline padding, 14px text, a\n recessed inner shadow, a 2px focus ring) MIRRORS the host design system's\n input.tsx \u2014 the widget is vanilla DOM in a detached root and cannot import\n the React primitive, so the numbers are copied deliberately; that file is the\n source of truth if they ever move. */\n.${PREFIX}-bubble,.${PREFIX}-panel{--aiw-field-bg:var(--aiw-surface);--aiw-field-fg:var(--aiw-text);--aiw-field-border:var(--aiw-border-strong);--aiw-field-placeholder:var(--aiw-muted);--aiw-field-ring:var(--aiw-accent);--aiw-field-ring-fg:var(--aiw-accent-contrast);--aiw-field-radius:10px}\n/* Host-token mode. Added at mount only when the page really defines our\n variables, so a customer site that happens to own an --input cannot repaint\n the chat's controls. Values stay as var() references, never snapshots, so the\n app's dark-mode class keeps driving them after mount. */\n.${PREFIX}-bubble.${PREFIX}-host-tokens,.${PREFIX}-panel.${PREFIX}-host-tokens{--aiw-field-bg:hsl(var(--card));--aiw-field-fg:hsl(var(--foreground));--aiw-field-border:hsl(var(--input));--aiw-field-placeholder:hsl(var(--muted-foreground));--aiw-field-ring:hsl(var(--ring));--aiw-field-ring-fg:hsl(var(--primary-foreground));--aiw-field-radius:calc(var(--radius) - 2px)}\n.${PREFIX}-field{width:100%;box-sizing:border-box;min-height:40px;padding:8px 12px;font-family:inherit;font-size:var(--aiw-font-md);line-height:1.45;color:var(--aiw-field-fg);background:var(--aiw-field-bg);border:1px solid var(--aiw-field-border);border-radius:var(--aiw-field-radius);outline:none;box-shadow:inset 0 1px 2px 0 color-mix(in srgb,var(--aiw-field-fg) 5%,transparent);transition:border-color .15s ease,box-shadow .15s ease}\n.${PREFIX}-field::placeholder{color:var(--aiw-field-placeholder);opacity:1}\n.${PREFIX}-field:hover:not(:disabled){border-color:color-mix(in srgb,var(--aiw-field-border) 70%,var(--aiw-field-fg))}\n.${PREFIX}-field:focus{border-color:var(--aiw-field-ring);box-shadow:0 0 0 2px var(--aiw-field-ring)}\n.${PREFIX}-field:disabled{opacity:.5;cursor:not-allowed}\ntextarea.${PREFIX}-field{min-height:76px;resize:vertical}\n/* The native select arrow is drawn by the OS and ignores our palette \u2014 in dark\n mode it renders a black-on-dark wedge. Draw our own instead. */\nselect.${PREFIX}-field{appearance:none;-webkit-appearance:none;cursor:pointer;padding-right:32px;background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%239aa0a6' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right 10px center;background-size:15px}\n/* Options are drawn by the OS popup, which does NOT inherit the panel \u2014 without\n this a dark-mode select opens as black text on a black list. */\n.${PREFIX}-field option{background:var(--aiw-field-bg);color:var(--aiw-field-fg)}\n.${PREFIX}-field-group{display:flex;flex-direction:column;gap:6px}\n/* \"This is the field that's holding you up.\" An outline rather than a border so\n it lands the same on a bare input, a checkbox row and a radio GROUP \u2014 the\n three shapes buildField can hand back. */\n.${PREFIX}-field-invalid{outline:1px solid var(--aiw-danger-text);outline-offset:2px;border-radius:var(--aiw-field-radius)}\n.${PREFIX}-form-err,.${PREFIX}-proposal-err{font-size:var(--aiw-font-xs);line-height:1.45;color:var(--aiw-danger-text)}\n.${PREFIX}-field-check{display:flex;align-items:center;gap:8px;font-size:var(--aiw-font-sm);line-height:1.4;color:var(--aiw-field-fg);cursor:pointer}\n.${PREFIX}-field-check-label{min-width:0}\n.${PREFIX}-check{appearance:none;-webkit-appearance:none;position:relative;flex:0 0 auto;width:16px;height:16px;margin:0;box-sizing:border-box;background:var(--aiw-field-bg);border:1px solid var(--aiw-field-border);border-radius:var(--aiw-radius-xs);cursor:pointer;outline:none;transition:background .15s ease,border-color .15s ease,box-shadow .15s ease}\n.${PREFIX}-check[type=radio]{border-radius:50%}\n.${PREFIX}-check:hover:not(:disabled){border-color:color-mix(in srgb,var(--aiw-field-border) 70%,var(--aiw-field-fg))}\n.${PREFIX}-check:focus-visible{border-color:var(--aiw-field-ring);box-shadow:0 0 0 2px var(--aiw-field-ring)}\n.${PREFIX}-check:checked{background:var(--aiw-field-ring);border-color:var(--aiw-field-ring)}\n/* Tick + dot are drawn from borders rather than a glyph so they scale with the\n box and take the accent's contrast colour in both schemes. */\n.${PREFIX}-check[type=checkbox]:checked::after{content:\"\";position:absolute;left:4.5px;top:1.5px;width:4px;height:8px;border:solid var(--aiw-field-ring-fg);border-width:0 2px 2px 0;transform:rotate(45deg)}\n.${PREFIX}-check[type=radio]:checked::after{content:\"\";position:absolute;left:3.5px;top:3.5px;width:7px;height:7px;border-radius:50%;background:var(--aiw-field-ring-fg)}\n.${PREFIX}-check:disabled{opacity:.5;cursor:not-allowed}\n.${PREFIX}-lead-btn{border:none;background:var(--aiw-accent);color:var(--aiw-accent-contrast);border-radius:var(--aiw-radius-lg);padding:10px 16px;font-size:var(--aiw-font-md);font-weight:600;cursor:pointer}\n.${PREFIX}-lead-btn:disabled{opacity:.6;cursor:default}\n.${PREFIX}-lead-ok{font-size:var(--aiw-font-sm);font-weight:600;color:var(--aiw-accent-ink)}\n.${PREFIX}-input:focus{border-color:var(--aiw-accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--aiw-accent) 13%,transparent)}\n.${PREFIX}-send{border:none;background:var(--aiw-accent);color:var(--aiw-accent-contrast);border-radius:var(--aiw-radius-lg);padding:0 16px;font-size:var(--aiw-font-md);font-weight:600;cursor:pointer}\n.${PREFIX}-send:disabled{opacity:.5;cursor:default}\n.${PREFIX}-attach{flex:0 0 auto;display:flex;align-items:center;justify-content:center;border:1px solid var(--aiw-border-strong);background:var(--aiw-surface);border-radius:var(--aiw-radius-lg);width:38px;line-height:1;cursor:pointer;color:var(--aiw-text-2);transition:border-color .12s,color .12s,background .12s}\n.${PREFIX}-attach:hover{border-color:var(--aiw-accent);color:var(--aiw-accent-ink)}\n.${PREFIX}-attach:disabled{opacity:.5;cursor:default}\n.${PREFIX}-attbar{display:flex;flex-wrap:wrap;gap:6px;padding:8px 10px 0;background:var(--aiw-surface)}\n.${PREFIX}-artifacts{display:flex;flex-wrap:wrap;align-items:center;gap:6px;padding:6px 10px 0;background:var(--aiw-surface)}\n.${PREFIX}-artifacts-title{font-size:var(--aiw-font-2xs);font-weight:600;color:var(--aiw-muted)}\n.${PREFIX}-artifact{display:inline-flex;align-items:center;gap:6px;max-width:200px;border:1px solid var(--aiw-border);background:var(--aiw-surface-2);border-radius:var(--aiw-radius-md);padding:3px 8px;font-size:var(--aiw-font-2xs);color:var(--aiw-text-2);white-space:nowrap}\n.${PREFIX}-artifact-name{overflow:hidden;text-overflow:ellipsis;max-width:130px}\n.${PREFIX}-artifact-save{border:none;background:transparent;color:#0b6cff;font-size:var(--aiw-font-2xs);font-weight:600;cursor:pointer;padding:0}\n.${PREFIX}-artifact-save:disabled{color:var(--aiw-muted);cursor:default}\n.${PREFIX}-att{display:inline-flex;align-items:center;gap:5px;max-width:180px;border:1px solid var(--aiw-border);background:var(--aiw-surface-2);border-radius:var(--aiw-radius-md);padding:3px 8px;font-size:var(--aiw-font-xs);color:var(--aiw-text-2);white-space:nowrap}\n.${PREFIX}-att>span{overflow:hidden;text-overflow:ellipsis}\n.${PREFIX}-atts{display:flex;flex-wrap:wrap;gap:6px;max-width:92%}\n.${PREFIX}-atts.${PREFIX}-user{align-self:flex-end;justify-content:flex-end}\n.${PREFIX}-att-x{border:none;background:transparent;color:var(--aiw-muted);font-size:var(--aiw-font-lg);line-height:1;cursor:pointer;padding:0 0 0 2px}\n.${PREFIX}-att-x:hover{color:#e11}\n.${PREFIX}-att-err{border-color:var(--aiw-danger-border);background:var(--aiw-danger-bg);color:var(--aiw-danger-text)}\n.${PREFIX}-msgactions{display:inline-flex;align-items:center;gap:6px;margin-top:3px;color:var(--aiw-muted);opacity:.55;transition:opacity .15s ease}\n.${PREFIX}-msgactions:hover,.${PREFIX}-msgactions:focus-within{opacity:1}\n/* User edit sits at the message's bottom-RIGHT, hugging the bubble edge, and is\n HIDDEN until the user hovers their message (or the row/button itself) \u2014 an\n always-visible bordered pencil floating under every user turn read as a stray\n box. Its row is the immediate next sibling of the user bubble, so a hover-plus\n sibling selector reveals it; the row's own :hover/:focus-within keeps it up\n while you reach the\n button across the small gap. Higher specificity than the base .msgactions:hover\n rule so it wins the cascade. */\n.${PREFIX}-msgactions.${PREFIX}-user{align-self:flex-end;justify-content:flex-end;padding-right:2px;opacity:0}\n.${PREFIX}-msg.${PREFIX}-user:hover + .${PREFIX}-msgactions.${PREFIX}-user,.${PREFIX}-msgactions.${PREFIX}-user:hover,.${PREFIX}-msgactions.${PREFIX}-user:focus-within{opacity:1}\n/* HOVER IS NOT A UNIVERSAL INPUT. On a touch device there is no hover state\n to enter, so opacity 0 revealed only on hover meant Edit and Copy did not\n EXIST on a phone \u2014 the widget's mobile layout is a full-screen sheet, i.e.\n the case where they matter most. hover:none is the honest test (it asks\n what the primary pointer can do, not how wide the screen is), and the\n answer there is: always visible, just quiet.\n NOTE: this whole sheet is a JS template literal, so no backticks. */\n@media (hover:none){\n.${PREFIX}-msgactions.${PREFIX}-user{opacity:.75}\n}\n.${PREFIX}-msgactions.${PREFIX}-assistant{align-self:flex-start}\n.${PREFIX}-msgact{display:inline-flex;align-items:center;gap:4px;border:none;background:transparent;color:inherit;border-radius:var(--aiw-radius-sm);padding:2px 5px;font-size:var(--aiw-font-2xs);font-weight:500;cursor:pointer;line-height:1}\n.${PREFIX}-msgact:hover{color:var(--aiw-accent-ink);background:color-mix(in srgb,var(--aiw-accent) 7%,transparent)}\n/* Icon-only variant (edit) \u2014 a small, quiet chip (subtle surface + border) that\n is revealed on message hover, then tints ACCENT (not a hard white-on-accent\n fill) on its own hover so it reads as an affordance, not a loud button. */\n.${PREFIX}-msgact-icon{padding:5px;border-radius:var(--aiw-radius-md);color:var(--aiw-muted);background:color-mix(in srgb,var(--aiw-surface) 92%,transparent);border:1px solid var(--aiw-border);transition:color .15s ease,background .15s ease,border-color .15s ease}\n.${PREFIX}-msgact-icon:hover{color:var(--aiw-accent-ink);background:color-mix(in srgb,var(--aiw-accent) 12%,transparent);border-color:color-mix(in srgb,var(--aiw-accent) 45%,transparent)}\n.${PREFIX}-msgact-icon svg{width:15px;height:15px}\n/* Per-message vote (thumbs) \u2014 light icon buttons; the chosen vote stays lit. */\n/* \u2500\u2500 Accessibility floor (#308) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 ONE focus rule for all 40 buttons, on a base class every one of them carries.\n Nothing in this sheet ever REMOVED a button's ring \u2014 there is no outline:none\n on one \u2014 so this was never a keyboard trap. It is that the ring was never\n designed and never measured against a surface the host is explicitly allowed\n to repaint (the theme option), and \"whatever the UA draws\" is not an answer we can\n ship to a third party once the widget is public (#306).\n\n Two-toned so it holds on ANY ground: the accent fills the offset gap and a\n fixed dark ring sits outside it, so the pair cannot vanish into either a\n light or a dark panel. Same construction the design house prescribes for a\n brand ring that fails on one theme. */\n.${PREFIX}-btn:focus-visible{outline:2px solid var(--aiw-accent);outline-offset:2px;box-shadow:0 0 0 4px #151D2F}\n/* A hit area of at least 24x24 (WCAG 2.2 AA) WITHOUT changing the drawn size \u2014\n a transparent ::before costs nothing visually, so a 20px vote button and a\n 6px carousel dot stay exactly as designed.\n\n The GAPS below grow with them, and that is not cosmetic. WCAG's spacing\n exception only holds when a 24px circle centred on each target does not\n intersect its neighbour's, so the gap has to put the CENTRES 24px apart.\n Enlarging a hit area without doing that just makes two controls fight over\n the same pixels, which is worse than the small target was. */\n.${PREFIX}-tap::before{content:\"\";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:24px;height:24px}\n/* 20px drawn + 4px gap = centres 24px apart. */\n.${PREFIX}-votes{display:inline-flex;align-items:center;gap:4px}\n/* 20x20 drawn (14px icon + 3px padding); the .-tap ::before takes the TARGET\n to 24x24. The chat-quality control (#299) being the smallest thing in the\n widget was the sharpest finding in the audit. */\n.${PREFIX}-vote{position:relative;padding:3px;border-radius:var(--aiw-radius-sm);color:var(--aiw-muted);background:transparent}\n.${PREFIX}-vote::before{content:\"\";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:24px;height:24px}\n.${PREFIX}-vote:hover{color:var(--aiw-accent-ink);background:color-mix(in srgb,var(--aiw-accent) 10%,transparent)}\n.${PREFIX}-vote-on{color:var(--aiw-accent-ink)}\n.${PREFIX}-vote svg{width:14px;height:14px;display:block}\n.${PREFIX}-branchnav{display:inline-flex;align-items:center;gap:2px;color:inherit;font-variant-numeric:tabular-nums}\n.${PREFIX}-branch-btn{display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;color:inherit;border-radius:var(--aiw-radius-sm);padding:2px;cursor:pointer;line-height:1}\n.${PREFIX}-branch-btn:hover:not(:disabled){color:var(--aiw-accent-ink)}\n.${PREFIX}-branch-btn:disabled{opacity:.35;cursor:default}\n.${PREFIX}-branch-count{font-size:var(--aiw-font-2xs);padding:0 2px}\n.${PREFIX}-edit{display:flex;flex-direction:column;gap:6px;max-width:94%;animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}\n.${PREFIX}-edit.${PREFIX}-user{align-self:stretch}\n/* Edit mode keeps the USER BUBBLE look (same fill + radius), so clicking edit\n doesn't swap the message for a different-looking box \u2014 just a subtle focus\n ring, matching the inline-edit request. */\n.${PREFIX}-edit-input{width:100%;background:color-mix(in srgb,var(--aiw-accent) 76%,#04191b);color:#fff;caret-color:#fff;box-sizing:border-box;border:1.5px solid transparent;border-radius:var(--aiw-radius-2xl);padding:9px 12px;font-size:var(--aiw-font-md);line-height:1.45;font-family:inherit;resize:none;outline:none;transition:border-color .15s ease,box-shadow .15s ease}\n.${PREFIX}-edit-input::placeholder{color:rgba(255,255,255,.6)}\n.${PREFIX}-edit-input:focus{border-color:rgba(255,255,255,.55);box-shadow:0 0 0 3px color-mix(in srgb,var(--aiw-accent) 28%,transparent)}\n.${PREFIX}-edit-actions{display:flex;justify-content:flex-end;gap:6px}\n.${PREFIX}-edit-cancel{border:none;background:transparent;color:var(--aiw-muted);border-radius:var(--aiw-radius-pill);padding:5px 11px;font-size:var(--aiw-font-xs);font-weight:600;cursor:pointer}\n.${PREFIX}-edit-cancel:hover{background:var(--aiw-surface-2)}\n.${PREFIX}-edit-save{border:none;background:var(--aiw-accent);color:var(--aiw-accent-contrast);border-radius:var(--aiw-radius-pill);padding:5px 13px;font-size:var(--aiw-font-xs);font-weight:600;cursor:pointer}\n.${PREFIX}-edit-save:hover{opacity:.92}\n.${PREFIX}-hactions{display:flex;align-items:center;gap:4px;flex:0 0 auto}\n.${PREFIX}-icon{background:color-mix(in srgb,var(--aiw-header-fg) 15%,transparent);border:none;color:var(--aiw-header-fg);width:26px;height:26px;border-radius:var(--aiw-radius-md);cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0}\n.${PREFIX}-icon:hover{background:color-mix(in srgb,var(--aiw-header-fg) 28%,transparent)}\n.${PREFIX}-icon-on{background:var(--aiw-header-fg);color:var(--aiw-header-bg)}\n.${PREFIX}-icon-on:hover{background:var(--aiw-header-fg)}\n.${PREFIX}-morewrap{position:relative;display:inline-flex}\n.${PREFIX}-menu{position:absolute;top:calc(100% + 8px);right:0;z-index:6;display:flex;flex-direction:column;min-width:212px;padding:6px;background:var(--aiw-surface-raised);color:var(--aiw-text);border:1px solid var(--aiw-border);border-radius:var(--aiw-radius-xl);box-shadow:0 12px 34px rgba(15,23,42,.18);\n/* A TRANSITION, not a keyframe, and anchored at the trigger (#309). It was\n an entrance KEYFRAME with no transform-origin, so it grew from its own centre\n instead of from the button that opened it \u2014 and because keyframes RESTART\n while transitions RETARGET, a fast open-close-open stuttered. It stays in the\n DOM (display was toggled), which is exactly the rapidly-retriggered case\n house law names.\n visibility rather than display because display is not animatable; it\n flips discretely at the end of the transition, which is what keeps a closed\n menu out of the tab order. */\ntransform-origin:top right;transform:translateY(-4px) scale(.98);opacity:0;visibility:hidden;pointer-events:none;transition:opacity var(--duration-fast) var(--ease-out),transform var(--duration-fast) var(--ease-out),visibility 0s linear var(--duration-fast)}\n.${PREFIX}-menu-open{transform:none;opacity:1;visibility:visible;pointer-events:auto;transition-delay:0s}\n.${PREFIX}-menu::before{content:\"\";position:absolute;top:-5px;right:14px;width:10px;height:10px;background:var(--aiw-surface-raised);border-left:1px solid var(--aiw-border);border-top:1px solid var(--aiw-border);transform:rotate(45deg)}\n.${PREFIX}-menu-item{display:flex;align-items:center;gap:10px;width:100%;text-align:left;border:none;background:transparent;color:var(--aiw-text-2);border-radius:var(--aiw-radius-md);padding:9px 10px;font-size:var(--aiw-font-sm);font-weight:500;cursor:pointer;line-height:1.2}\n.${PREFIX}-menu-item:hover{background:color-mix(in srgb,var(--aiw-accent) 7%,transparent);color:var(--aiw-accent-ink)}\n.${PREFIX}-menu-item:disabled{opacity:.5;cursor:default}\n.${PREFIX}-menu-ico{flex:0 0 auto;display:flex;align-items:center;justify-content:center;width:18px;height:18px;color:var(--aiw-text-2)}\n.${PREFIX}-menu-item:hover .${PREFIX}-menu-ico,.${PREFIX}-menu-item-on .${PREFIX}-menu-ico{color:var(--aiw-accent-ink)}\n.${PREFIX}-menu-label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.${PREFIX}-menu-state{flex:0 0 auto;font-size:var(--aiw-font-3xs);font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--aiw-muted);background:var(--aiw-surface-2);border-radius:var(--aiw-radius-pill);padding:2px 7px}\n.${PREFIX}-menu-item-on .${PREFIX}-menu-state{color:var(--aiw-accent-contrast);background:var(--aiw-accent)}\n.${PREFIX}-autonav{align-self:flex-start;display:inline-flex;align-items:center;gap:7px;border:1px solid color-mix(in srgb,var(--aiw-accent) 20%,transparent);background:color-mix(in srgb,var(--aiw-accent) 6%,transparent);color:var(--aiw-accent-ink);border-radius:var(--aiw-radius-lg);padding:8px 12px;font-size:var(--aiw-font-sm);font-weight:600;animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}\n.${PREFIX}-expanded{width:min(760px,calc(100vw - 32px));height:calc(100vh - 40px)}\n.${PREFIX}-expanded .${PREFIX}-msg{max-width:min(75%,62ch)}\n/* Advanced view \u2014 chat column + drivable app pane. The chat column always wraps\n the chat (fills the panel in normal mode); the pane only shows in advanced. */\n.${PREFIX}-chatcol{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;min-width:0;height:100%}\n.${PREFIX}-pane{display:none;flex-direction:column;min-width:0;min-height:0;background:var(--aiw-bg)}\n.${PREFIX}-advbtn-on{background:rgba(255,255,255,.34)}\n/* Advanced view IS the wide view (owns the screen), so the plain \"wide\" toggle\n is redundant here \u2014 hide it and leave advanced/exit + close. */\n.${PREFIX}-advanced .${PREFIX}-expand{display:none}\n/* Fullscreen toggle only makes sense in advanced view \u2014 hidden otherwise. */\n.${PREFIX}-fullbtn{display:none}\n.${PREFIX}-advanced .${PREFIX}-fullbtn{display:flex}\n.${PREFIX}-advanced{width:calc(100vw - 40px);max-width:1240px;height:calc(100vh - 40px);flex-direction:row;align-items:stretch}\n/* Fullscreen: drop the inset margins, width cap and radius so advanced view\n fills the whole viewport edge-to-edge. Two classes \u2192 wins over .advanced. */\n.${PREFIX}-advanced.${PREFIX}-advanced-full{inset:0;width:100vw;height:100vh;height:100dvh;max-width:none;max-height:none;border-radius:0}\n.${PREFIX}-advanced .${PREFIX}-chatcol{flex:0 0 var(--aiw-chatw,400px);min-width:320px;border-right:1px solid var(--aiw-border)}\n.${PREFIX}-advanced .${PREFIX}-pane{display:flex;flex:1 1 auto}\n.${PREFIX}-advanced.${PREFIX}-pane-collapsed .${PREFIX}-chatcol{flex:1 1 auto;min-width:0;max-width:none;border-right:0}\n/* Draggable divider between chat + app pane. Hidden unless advanced & expanded.\n A thin bar with a wider invisible hit-area; brightens on hover/drag. */\n.${PREFIX}-resizer{display:none}\n.${PREFIX}-advanced .${PREFIX}-resizer{display:block;flex:0 0 6px;align-self:stretch;cursor:col-resize;background:var(--aiw-border);touch-action:none;position:relative;transition:background .15s}\n.${PREFIX}-advanced .${PREFIX}-resizer::after{content:\"\";position:absolute;top:0;bottom:0;left:-4px;right:-4px}\n.${PREFIX}-advanced .${PREFIX}-resizer:hover,.${PREFIX}-resizing .${PREFIX}-resizer{background:var(--aiw-accent,#6366f1)}\n.${PREFIX}-advanced.${PREFIX}-pane-collapsed .${PREFIX}-resizer{display:none}\n/* While dragging: kill text selection + let pointer events pass over the iframe\n (an iframe would otherwise swallow the drag and drop the pointer). */\n.${PREFIX}-resizing{user-select:none}\n.${PREFIX}-resizing .${PREFIX}-pane-frame{pointer-events:none}\n/* Collapsed: the pane shrinks to a thin strip that still shows the toggle (so it\n can be re-opened), and its url + framed body hide. */\n.${PREFIX}-advanced.${PREFIX}-pane-collapsed .${PREFIX}-pane{flex:0 0 40px;min-height:0}\n.${PREFIX}-pane-collapsed .${PREFIX}-pane-body{display:none}\n.${PREFIX}-pane-collapsed .${PREFIX}-pane-url{display:none}\n.${PREFIX}-pane-collapsed .${PREFIX}-pane-bar{padding:6px 5px}\n.${PREFIX}-pane-collapsed .${PREFIX}-pane-collapse{transform:rotate(180deg)}\n.${PREFIX}-pane-bar{display:flex;align-items:center;gap:8px;padding:8px 12px;border-bottom:1px solid var(--aiw-border);background:var(--aiw-surface-2);flex:0 0 auto}\n.${PREFIX}-pane-url{font-size:var(--aiw-font-2xs);color:var(--aiw-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}\n/* Padded gutter around the driven app so it reads as an inset \"screen\". */\n.${PREFIX}-pane-body{flex:1 1 auto;min-width:0;min-height:0;display:flex;padding:12px}\n.${PREFIX}-pane-frame{flex:1 1 auto;width:100%;border:1px solid var(--aiw-border);border-radius:var(--aiw-radius-xl);background:var(--aiw-surface);min-height:0;box-shadow:0 1px 4px rgba(15,23,42,.07)}\n/* Narrow: stack the app pane on top and the chat (with composer) below. */\n@media (max-width:820px){\n .${PREFIX}-advanced{flex-direction:column-reverse}\n .${PREFIX}-advanced .${PREFIX}-chatcol{flex:1 1 auto;min-width:0;max-width:none;border-right:0;border-top:1px solid var(--aiw-border)}\n .${PREFIX}-advanced .${PREFIX}-pane{flex:1 1 auto;min-height:38%}\n /* Stacked layout: the horizontal divider doesn't apply. */\n .${PREFIX}-advanced .${PREFIX}-resizer{display:none}\n .${PREFIX}-pane-body{padding:8px}\n}\n\n.${PREFIX}-history{position:absolute;inset:0;background:var(--aiw-surface);display:flex;flex-direction:column;z-index:5;animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}\n.${PREFIX}-history-head{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-bottom:1px solid var(--aiw-border);font-weight:600;font-size:var(--aiw-font-md)}\n.${PREFIX}-history-back{border:1px solid var(--aiw-border-strong);background:var(--aiw-surface-raised);border-radius:var(--aiw-radius-md);padding:5px 11px;font-size:var(--aiw-font-xs);font-weight:600;cursor:pointer;color:var(--aiw-text-2)}\n.${PREFIX}-history-list{flex:1 1 auto;min-height:0;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:4px;font-size:var(--aiw-font-sm);color:var(--aiw-text-2)}\n.${PREFIX}-history-sep{padding:8px 4px 2px;font-size:var(--aiw-font-3xs);font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--aiw-muted)}\n.${PREFIX}-history-item{display:flex;align-items:center;justify-content:space-between;gap:8px;text-align:left;border:1px solid var(--aiw-border);background:var(--aiw-surface-raised);border-radius:var(--aiw-radius-lg);padding:10px 12px;cursor:pointer;width:100%}\n.${PREFIX}-history-item:hover{border-color:var(--aiw-accent);background:color-mix(in srgb,var(--aiw-accent) 4%,transparent)}\n.${PREFIX}-history-title{font-weight:600;color:var(--aiw-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.${PREFIX}-history-date{font-size:var(--aiw-font-2xs);color:var(--aiw-muted);flex:0 0 auto}\n.${PREFIX}-history-star{font-size:var(--aiw-font-lg);line-height:1;color:#cbcbcb;flex:0 0 auto;padding:0 2px;cursor:pointer}\n.${PREFIX}-history-star:hover{color:#f59e0b}\n.${PREFIX}-widget{align-self:stretch;border:1px solid var(--aiw-border);border-radius:var(--aiw-radius-2xl);padding:12px;background:var(--aiw-surface-raised);animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}\n.${PREFIX}-widget-title{font-size:var(--aiw-font-xs);font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--aiw-muted);margin-bottom:8px}\n.${PREFIX}-widget-stat{font-size:var(--aiw-font-3xl);font-weight:800;line-height:1.1;color:var(--aiw-text)}\n.${PREFIX}-widget-cap{font-size:var(--aiw-font-sm);color:var(--aiw-text-2);margin-top:2px}\n.${PREFIX}-widget-delta{font-size:var(--aiw-font-xs);font-weight:600;color:var(--aiw-accent-ink);margin-top:4px}\n.${PREFIX}-widget-kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(90px,1fr));gap:8px}\n/* Composed UI cards ([[ui:\u2026]]) \u2014 tiles the assistant lays out itself. */\n.${PREFIX}-ui{align-self:stretch;border:1px solid var(--aiw-border);border-radius:var(--aiw-radius-2xl);padding:12px;background:var(--aiw-surface-raised);animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}\n.${PREFIX}-ui-title{font-size:var(--aiw-font-xs);font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--aiw-muted);margin-bottom:6px}\n.${PREFIX}-ui-cap{font-size:var(--aiw-font-sm);color:var(--aiw-text-2);margin-bottom:10px}\n/* A strip SCROLLS rather than shrinking its tiles: squeezing eight scenes into\n a 340px panel makes every one of them too small to judge, which defeats the\n point of showing them. Snap points keep the scroll landing on whole tiles. */\n.${PREFIX}-ui-strip{display:flex;gap:8px;overflow-x:auto;scroll-snap-type:x mandatory;padding-bottom:4px;-webkit-overflow-scrolling:touch}\n.${PREFIX}-ui-strip>.${PREFIX}-ui-tile{flex:0 0 132px;scroll-snap-align:start}\n/* Carousel: one item at a time. The track is the same tile row, translated. */\n/* ONE number governs the carousel's geometry, because the controls have to be\n anchored to the MEDIA and nothing else knows how tall that is.\n Two real defects came from not having it: a 9:16 scene box computed from the\n panel width was 558px tall in a 368px panel \u2014 one empty scene taller than the\n whole visible chat, with the paging pushed off screen \u2014 and the dots, pinned\n to the card's BOTTOM, painted over the caption instead of over the picture. */\n.${PREFIX}-ui-carousel{position:relative;--aiw-cmedia:min(38vh,300px)}\n/* Height leads, width follows from the aspect \u2014 so a portrait scene stays\n portrait instead of being letterboxed or cropped to fit a cap. */\n.${PREFIX}-ui-carousel .${PREFIX}-ui-media{height:var(--aiw-cmedia);width:auto;max-width:100%;margin:0 auto}\n.${PREFIX}-ui-carousel-stage{position:relative;overflow:hidden;border-radius:var(--aiw-radius-lg)}\n.${PREFIX}-ui-carousel .${PREFIX}-ui-items{display:flex;gap:0;transition:transform .25s ease;overflow:visible}\n.${PREFIX}-ui-carousel .${PREFIX}-ui-tile{flex:0 0 100%;min-width:100%}\n.${PREFIX}-ui-arrow{position:absolute;top:calc(var(--aiw-cmedia) / 2 - 15px);z-index:2;display:flex;height:30px;width:30px;align-items:center;justify-content:center;border:0;border-radius:var(--aiw-radius-pill);background:rgba(0,0,0,.5);color:#fff;font-size:var(--aiw-font-lg);line-height:1;cursor:pointer}\n.${PREFIX}-ui-arrow:hover{background:rgba(0,0,0,.7)}\n.${PREFIX}-ui-arrow-left{left:6px}\n.${PREFIX}-ui-arrow-right{right:6px}\n.${PREFIX}-ui-counter{position:absolute;right:8px;top:8px;z-index:2;padding:2px 8px;border-radius:var(--aiw-radius-pill);background:rgba(0,0,0,.55);color:#fff;font-size:var(--aiw-font-3xs);font-weight:700}\n/* gap 5px -> 18px: 6px dots with 18px between them put the centres 24px apart,\n which is what the WCAG 2.2 spacing exception actually requires (#308). */\n.${PREFIX}-ui-dots{position:absolute;left:0;right:0;top:calc(var(--aiw-cmedia) - 14px);bottom:auto;z-index:2;display:flex;justify-content:center;gap:18px}\n/* 6x6 drawn, real <button>s. Same treatment: the dot stays 6px, the target is 24. */\n.${PREFIX}-ui-dot{position:relative;height:6px;width:6px;padding:0;border:0;border-radius:var(--aiw-radius-pill);background:rgba(255,255,255,.55);cursor:pointer}\n.${PREFIX}-ui-dot::before{content:\"\";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:24px;height:24px}\n/* The active dot widens to a pill. NO transition on that width (#309): house\n law allows transform and opacity, and scaleX on a 6px circle distorts the\n round cap into an ellipse \u2014 so the honest answer is to change instantly\n rather than animate the wrong property or fake it with the right one. */\n.${PREFIX}-ui-dot-on{width:16px;background:#fff}\n.${PREFIX}-ui-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(104px,1fr));gap:8px}\n.${PREFIX}-ui-list{display:flex;flex-direction:column;gap:8px}\n.${PREFIX}-ui-list>.${PREFIX}-ui-tile{display:grid;grid-template-columns:64px 1fr;gap:10px;align-items:start}\n.${PREFIX}-ui-list .${PREFIX}-ui-media{grid-row:span 3}\n.${PREFIX}-ui-media{position:relative;aspect-ratio:1;border-radius:var(--aiw-radius-lg);overflow:hidden;background:var(--aiw-surface-2);border:1px solid var(--aiw-border-soft);display:flex;align-items:center;justify-content:center}\n.${PREFIX}-ui-a-portrait .${PREFIX}-ui-media{aspect-ratio:4/5}\n.${PREFIX}-ui-a-story .${PREFIX}-ui-media{aspect-ratio:9/16}\n.${PREFIX}-ui-a-landscape .${PREFIX}-ui-media,.${PREFIX}-ui-a-wide .${PREFIX}-ui-media{aspect-ratio:16/9}\n.${PREFIX}-ui-media-el{width:100%;height:100%;object-fit:cover;display:block}\n/* An audio tile has nothing to LOOK at, so the box stops pretending to be a\n picture: no forced aspect ratio, and the player sits at its natural height\n instead of being stretched to fill a square. */\n.${PREFIX}-ui-media:has(> audio.${PREFIX}-ui-media-el){aspect-ratio:auto;padding:8px}\naudio.${PREFIX}-ui-media-el{height:auto;object-fit:unset}\n.${PREFIX}-ui-media-ph{font-size:var(--aiw-font-2xs);color:var(--aiw-muted);text-align:center;padding:0 6px}\n.${PREFIX}-ui-badge{position:absolute;left:6px;top:6px;padding:2px 7px;border-radius:var(--aiw-radius-pill);font-size:var(--aiw-font-3xs);font-weight:700;letter-spacing:.02em;background:rgba(0,0,0,.55);color:#fff;backdrop-filter:blur(4px)}\n.${PREFIX}/* The two FILLED badges stay literal on purpose, and are not the bypass the\n audit counted. They are opaque chips carrying the base rule's white text\n (5.43:1 and 7.54:1), so they neither flip with the scheme nor need to:\n --aiw-ok-bg / --aiw-danger-bg are PALE SURFACES for cards, and pouring one\n into a filled pill would put pale text on a pale fill. Same reasoning as the\n preview chrome's traffic-light dots. */\n-ui-badge-ready{background:#16794a}\n.${PREFIX}-ui-badge-running{background:var(--aiw-accent);color:var(--aiw-accent-contrast)}\n.${PREFIX}-ui-badge-failed{background:#a32020}\n.${PREFIX}-ui-tile-title{font-size:var(--aiw-font-xs);font-weight:600;color:var(--aiw-text);margin-top:6px;line-height:1.25}\n.${PREFIX}-ui-tile-cap{font-size:var(--aiw-font-2xs);color:var(--aiw-text-2);margin-top:2px;line-height:1.3}\n.${PREFIX}-ui-tile-actions,.${PREFIX}-ui-actions{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}\n.${PREFIX}-ui-actions{margin-top:10px}\n.${PREFIX}-ui-act{display:inline-flex;flex-wrap:wrap;align-items:center;gap:4px}\n.${PREFIX}-ui-btn{padding:5px 10px;border-radius:var(--aiw-radius-md);border:1px solid var(--aiw-border-strong);background:var(--aiw-bg);color:var(--aiw-text);font-size:var(--aiw-font-2xs);font-weight:600;cursor:pointer}\n.${PREFIX}-ui-btn:hover{background:var(--aiw-surface-2)}\n.${PREFIX}-ui-btn:disabled{opacity:.5;cursor:default}\n.${PREFIX}-ui-btn-primary{background:var(--aiw-accent);color:var(--aiw-accent-contrast);border-color:transparent}\n.${PREFIX}-ui-btn-danger{color:var(--aiw-danger-text);border-color:color-mix(in srgb,var(--aiw-danger-text) 40%,transparent)}\n.${PREFIX}-ui-btn-ghost{padding:5px 8px;border-radius:var(--aiw-radius-md);border:1px solid transparent;background:transparent;color:var(--aiw-muted);font-size:var(--aiw-font-2xs);font-weight:600;cursor:pointer}\n.${PREFIX}-ui-confirm-q{font-size:var(--aiw-font-2xs);color:var(--aiw-text-2)}\n.${PREFIX}-preview{align-self:stretch;border:1px solid var(--aiw-border);border-radius:var(--aiw-radius-2xl);overflow:hidden;background:var(--aiw-surface);animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}\n.${PREFIX}-preview-bar{display:flex;align-items:center;gap:8px;padding:8px 12px;border-bottom:1px solid var(--aiw-border-soft)}\n.${PREFIX}-preview-dots{display:inline-flex;gap:4px}\n.${PREFIX}-preview-dots i{width:9px;height:9px;border-radius:50%;background:#e2e2e2}\n.${PREFIX}-preview-dots i:nth-child(1){background:#ff5f57}\n.${PREFIX}-preview-dots i:nth-child(2){background:#febc2e}\n.${PREFIX}-preview-dots i:nth-child(3){background:#28c840}\n.${PREFIX}-preview-title{font-size:var(--aiw-font-xs);font-weight:600;color:var(--aiw-muted)}\n.${PREFIX}-preview-frame{display:block;width:100%;height:340px;border:0;background:var(--aiw-surface)}\n.${PREFIX}-proposal-media{display:block;width:100%;max-height:200px;object-fit:cover;border-radius:var(--aiw-radius-xl);border:1px solid var(--aiw-border);background:var(--aiw-surface-2)}\n.${PREFIX}-proposal-media-holder{position:relative}\n/* NOT --aiw-accent-contrast: this badge sits on 60% black over an image, not on\n the accent, so it must stay light whatever the accent resolves to. Reading the\n accent token here would have turned it navy-on-black the moment #307 made the\n contrast derived. */\n.${PREFIX}-proposal-media-badge{position:absolute;left:8px;bottom:8px;padding:2px 8px;border-radius:var(--aiw-radius-pill);background:rgba(0,0,0,.6);color:#FCF7E3;font-size:var(--aiw-font-2xs);font-weight:600}\n.${PREFIX}-chips{display:flex;flex-wrap:wrap;gap:6px;margin:2px 0 10px}\n.${PREFIX}-chip{padding:8px 14px;border-radius:var(--aiw-radius-pill);border:1px solid color-mix(in srgb,var(--aiw-accent) 35%,transparent);background:color-mix(in srgb,var(--aiw-accent) 7%,transparent);color:var(--aiw-accent-ink);font-size:var(--aiw-font-sm);font-weight:600;cursor:pointer;transition:background .12s,color .12s,border-color .12s}\n.${PREFIX}-chip:hover{background:color-mix(in srgb,var(--aiw-accent) 14%,transparent)}\n.${PREFIX}-chip-on,.${PREFIX}-chip-send{background:var(--aiw-accent);color:var(--aiw-accent-contrast);border-color:transparent}\n.${PREFIX}-chip-send:hover{background:var(--aiw-accent)}\n.${PREFIX}-chip-other{border-style:dashed;background:transparent;color:var(--aiw-muted);border-color:var(--aiw-border-strong)}\n.${PREFIX}-chip:disabled{opacity:.45;cursor:default}\n.${PREFIX}-kpi{border:1px solid var(--aiw-border-soft);border-radius:var(--aiw-radius-lg);padding:8px 10px;background:var(--aiw-bg)}\n.${PREFIX}-kpi-v{font-size:var(--aiw-font-2xl);font-weight:700;color:var(--aiw-text)}\n.${PREFIX}-kpi-l{font-size:var(--aiw-font-2xs);color:var(--aiw-muted);margin-top:1px}\n.${PREFIX}-kpi-d{font-size:var(--aiw-font-2xs);font-weight:600;color:var(--aiw-accent-ink);margin-top:2px}\n.${PREFIX}-widget-table{width:100%;border-collapse:collapse;font-size:var(--aiw-font-xs)}\n.${PREFIX}-widget-table th{text-align:left;font-weight:700;color:var(--aiw-text-2);border-bottom:1px solid var(--aiw-border);padding:6px 8px}\n.${PREFIX}-widget-table td{border-bottom:1px solid var(--aiw-border-soft);padding:6px 8px;color:var(--aiw-text)}\n.${PREFIX}-widget-list{margin:0;padding-left:18px;font-size:var(--aiw-font-sm);color:var(--aiw-text);display:flex;flex-direction:column;gap:3px}\n.${PREFIX}-nav{align-self:flex-start;animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}\n.${PREFIX}-nav-btn{display:inline-flex;align-items:center;gap:8px;border:1px solid var(--aiw-accent);background:color-mix(in srgb,var(--aiw-accent) 6%,transparent);color:var(--aiw-accent-ink);border-radius:var(--aiw-radius-lg);padding:9px 14px;font-size:var(--aiw-font-sm);font-weight:600;cursor:pointer}\n.${PREFIX}-nav-btn:hover{background:color-mix(in srgb,var(--aiw-accent) 11%,transparent)}\n.${PREFIX}-nav-btn:disabled{opacity:.7;cursor:default}\n.${PREFIX}-proposal{align-self:stretch;border:1px solid color-mix(in srgb,var(--aiw-accent) 20%,transparent);background:color-mix(in srgb,var(--aiw-accent) 4%,transparent);border-radius:var(--aiw-radius-2xl);padding:12px;display:flex;flex-direction:column;gap:8px;animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}\n.${PREFIX}-proposal-title{font-size:var(--aiw-font-sm);font-weight:700}\n.${PREFIX}-proposal-edit{display:flex;flex-direction:column;gap:4px}\n.${PREFIX}-proposal-edit-label{font-size:var(--aiw-font-2xs);font-weight:600;color:var(--aiw-text-2)}\n.${PREFIX}-proposal-summary{font-size:var(--aiw-font-xs);color:var(--aiw-text-2);white-space:pre-wrap;line-height:1.45}\n/* Storyboard plan inside a confirm card: one row per shot, so the thing the\n renders are bought from is READ rather than skimmed as a paragraph. */\n/* Two lines of the prompt: enough to judge the shot, not so much that four of\n them bury the buttons under the fold. */\n.${PREFIX}-proposal-ok{font-size:var(--aiw-font-sm);font-weight:600;color:var(--aiw-ok-text);display:inline-flex;align-items:center;gap:6px}\n.${PREFIX}-confirm-q{font-size:var(--aiw-font-sm);color:var(--aiw-text-2);margin-bottom:8px}\n.${PREFIX}-confirm-row{display:flex;gap:8px}\n.${PREFIX}-confirm-no{border:1px solid var(--aiw-border-strong);background:var(--aiw-surface-raised);color:var(--aiw-text-2);border-radius:var(--aiw-radius-lg);padding:9px 14px;font-size:var(--aiw-font-sm);font-weight:600;cursor:pointer}\n${darkWhenHostSaysSo}\n@media (prefers-color-scheme:dark){${darkWhenNobodySaid}}\n\n@keyframes ${PREFIX}-sheetup{from{transform:translateY(100%)}to{transform:translateY(0)}}\n/* On phones the panel becomes a full-width bottom sheet (slides up from the\n bottom edge, ~90% of the dynamic viewport, rounded top, grab handle) so the\n chat is comfortably usable instead of a cramped corner card. */\n/* A phone in LANDSCAPE is about 844x390 \u2014 wider than 640, so the desktop\n corner card applied and max-height:calc(100vh - 40px) clamped it to 350px\n tall. Header and composer take ~120 of that, leaving ~230px of message log\n in the exact posture where the soft keyboard also appears (#309).\n Short viewport AND coarse pointer, not short alone: a short desktop window\n is not a phone, and a full-screen sheet there would be wrong. */\n@media (max-width:640px),(max-height:520px) and (pointer:coarse){\n /* z-index: the desktop 48 sits deliberately BELOW the app's Radix modals\n (z-50) so a dialog covers the chat. That rule did not anticipate page\n CHROME at the same level: the marketing site's header is\n \"fixed inset-x-0 top-0 z-50\", so it painted over the top 66px of the\n full-screen sheet \u2014 its logo, locale switcher and hamburger landing on\n top of the widget's own avatar and buttons (reported 2026-08-07).\n A full-screen sheet covers the viewport and IS the modal surface while\n it is open, so here \u2014 and only here \u2014 it outranks host chrome. */\n .${PREFIX}-panel{inset:0;z-index:51;width:100%;max-width:100%;height:100vh;height:100dvh;max-height:none;border-radius:0;animation:${PREFIX}-sheetup var(--duration-base) var(--ease-out);transition:none}\n .${PREFIX}-expanded{width:100%;max-width:100%;height:100vh;height:100dvh}\n .${PREFIX}-expanded .${PREFIX}-msg{max-width:86%}\n /* Give the title its own full-width row: avatar + actions share the top row,\n the name/subtitle drop to a dedicated line below so the title never gets\n squeezed by the action buttons on narrow phones. */\n .${PREFIX}-header{position:relative;padding-top:calc(14px + env(safe-area-inset-top));display:grid;grid-template-columns:auto 1fr;grid-template-areas:\"avatar actions\" \"name name\";align-items:center;gap:8px 10px;touch-action:none}\n .${PREFIX}-header::before{content:\"\";position:absolute;top:calc(5px + env(safe-area-inset-top));left:50%;transform:translateX(-50%);width:38px;height:4px;border-radius:var(--aiw-radius-xs);background:color-mix(in srgb,var(--aiw-header-fg) 55%,transparent)}\n .${PREFIX}-avatar{grid-area:avatar}\n .${PREFIX}-hactions{grid-area:actions;justify-self:end}\n .${PREFIX}-hname{grid-area:name}\n .${PREFIX}-title{font-size:var(--aiw-font-xl)}\n .${PREFIX}-log{padding-bottom:calc(14px + env(safe-area-inset-bottom))}\n .${PREFIX}-form{padding-bottom:calc(10px + env(safe-area-inset-bottom))}\n /* iOS auto-zooms the page when a focused field is under 16px, and the zoom\n is what then makes the sheet scroll sideways. 16px is the threshold, not\n a preference \u2014 do not lower it. text-size-adjust stops iOS inflating the\n rest of the text to compensate. */\n .${PREFIX}-input,.${PREFIX}-edit-input,.${PREFIX}-question-input,\n .${PREFIX}-lead-form input,.${PREFIX}-lead-form textarea{font-size:var(--aiw-font-xl)}\n .${PREFIX}-panel{-webkit-text-size-adjust:100%;text-size-adjust:100%}\n /* While the soft keyboard is up it covers the home indicator, but iOS keeps\n reporting safe-area-inset-bottom as if it were still there \u2014 leaving a\n dead band under the composer. The keyboard handler sets this flag. */\n .${PREFIX}-panel.${PREFIX}-kb .${PREFIX}-form{padding-bottom:10px}\n .${PREFIX}-panel.${PREFIX}-kb .${PREFIX}-log{padding-bottom:14px}\n .${PREFIX}-bubble{bottom:16px;${side}:16px}\n /* Expand/restore is meaningless once the sheet is full-screen \u2014 hide it. */\n .${PREFIX}-expand{display:none}\n}\n/* \u2500\u2500 Reduced motion, gated at the KEYFRAME (#308) \u2500\u2500\u2500\u2500\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 This used to name eleven selectors. Twenty-two were animated, four of the\n eleven carried no animation at all, and the list could only ever fall further\n behind \u2014 the shared rise keyframe alone has sixteen consumers.\n\n Redefining the keyframes reaches every consumer, including ones added later\n by someone who never reads this block. That is the difference between a rule\n and a list.\n\n NOT all the way to zero, per house law: entrances keep their fade so spatial\n continuity survives, and lose only the movement. The spinner keeps turning\n because it reports STATE \u2014 a still spinner says \"stuck\", which is a worse\n answer than a moving one. Purely decorative loops stop entirely.\n\n One thing CSS cannot reach: the launcher blob is SMIL animate elements, and\n animation:none has no authority over SMIL. #305 removes it by drawing a\n static mark; noted so the two issues do not both try. */\n@media (prefers-reduced-motion:reduce){\n@keyframes ${PREFIX}-rise{from{opacity:0}to{opacity:1}}\n@keyframes ${PREFIX}-richin{from{opacity:0}to{opacity:1}}\n@keyframes ${PREFIX}-tokin{from{opacity:0}to{opacity:1}}\n@keyframes ${PREFIX}-sheetup{from{opacity:0}to{opacity:1}}\n@keyframes ${PREFIX}-float{0%,100%{transform:none}}\n@keyframes ${PREFIX}-pulse{0%,100%{opacity:1}}\n@keyframes ${PREFIX}-blink{0%,100%{opacity:1}}\n@keyframes ${PREFIX}-blink2{0%,100%{opacity:1}}\n@keyframes ${PREFIX}-caret{0%,100%{opacity:1}}\n/* -breathe is the launcher's working state (#305). It is a STATUS signal, like\n the spinner, so stillness would say 'idle' when a turn is running \u2014 but it is\n also a loop in the corner of every page, which the spinner is not. Slowed and\n shallowed rather than stopped: it still reads as alive, at a fraction of the\n amplitude. */\n@keyframes ${PREFIX}-breathe{0%,100%{opacity:1}50%{opacity:.88}}\n.${PREFIX}-log{scroll-behavior:auto}\n}\n`;\n const style = document.createElement(\"style\");\n style.setAttribute(\"data-sgiant-aiw\", \"\");\n style.textContent = css;\n document.head.appendChild(style);\n}\n", "/**\n * Every string the widget's own chrome can show, and the host adapter that\n * translates them.\n *\n * Its own module (#320) because it is read by three hosts and a parity test,\n * and none of them wants the 5800-line widget closure to get at a string table.\n * `WidgetLabels` being derived from this object is what makes a missing\n * translation a TYPE error in each host rather than an English word appearing\n * in a Turkish UI.\n */\n/**\n * SINGLE SOURCE for every user-facing string baked into the vanilla-DOM widget,\n * with its English default. The widget can't call i18next, so the host passes a\n * translated `labels` bag (built by `resolveWidgetLabels`); the widget falls back\n * to these English defaults for any missing key. A `{token}` in a value is filled\n * at runtime by the widget (single-brace so a host's i18next leaves it intact).\n */\nexport const WIDGET_LABELS = {\n // Question cards \u2014 the assistant asking the human to decide.\n questionConfirm: \"Send\",\n questionPlaceholder: \"Type your answer\u2026\",\n questionSendFailed:\n \"Couldn't send your answer \u2014 wait for the current reply to finish, then try again.\",\n // Header / bubble\n openBubble: \"Open {name}\",\n openBubbleUnread: \"Open {name} \u2014 {count} unread\",\n panelAria: \"{name} chat\",\n newChat: \"New chat\",\n pastConversations: \"Past conversations\",\n /** History-row spinner tooltip: this conversation has a reply in flight. */\n threadAnswering: \"Answering\u2026\",\n /** Shown when the live stream drops mid-reply: the server finishes the turn\n * and the widget polls the thread until the reply lands. */\n streamLostRecovering:\n \"Connection lost \u2014 still working on the reply; it will appear here when ready.\",\n history: \"History\",\n moreOptions: \"More options\",\n more: \"More\",\n expandChat: \"Expand chat\",\n expand: \"Expand\",\n restore: \"Restore\",\n restoreChat: \"Restore chat size\",\n closeChat: \"Close chat\",\n conversation: \"Conversation\",\n // \"More\" overflow menu\n on: \"On\",\n off: \"Off\",\n downloadChat: \"Download chat (.txt)\",\n flagConversation: \"Flag this conversation\",\n flagConversationHint: \"Flag this conversation for review\",\n flagSendFirst: \"Send a message first \u2014 then you can flag this chat.\",\n flagPrompt: \"Flag this conversation \u2014 why? (reason is logged for review)\",\n flagged: \"\uD83D\uDEA9 Conversation flagged for review \u2713\",\n flagFailed: \"Couldn't flag: {msg}\",\n flagFailedGeneric: \"please try again\",\n notificationSound: \"Notification sound\",\n soundOnHint: \"Chime when a reply arrives \u2014 click to mute\",\n soundOffHint: \"Muted \u2014 click to enable the reply chime\",\n autoNavigate: \"Auto-navigate\",\n autoNavOnHint: \"Copilot opens pages for you \u2014 click to turn off\",\n autoNavOffHint: \"Copilot asks before opening pages \u2014 click to turn on\",\n // Status bar (Copilot role + credits)\n roleTalk: \"Talk\",\n roleAnalytics: \"Analytics\",\n creditsSuffix: \" credits\",\n // Composer\n askAnything: \"Ask {name} anything\u2026\",\n messageAria: \"Message {name}\",\n send: \"Send\",\n removeAttachment: \"Remove {file}\",\n attachFile: \"Attach a file\",\n attachFileHint: \"Attach images, PDFs or documents\",\n // Live background-job card \u2014 a job the chat started and now watches. The\n // titles are per known TYPE, with a neutral fallback for a type this build\n // has never heard of (an AI-defined job type must still render).\n jobTitleCoder: \"Working on a code task\",\n jobTitleReport: \"Generating the report\",\n jobTitleFallback: \"Background job\",\n jobQueued: \"Queued\",\n jobRunning: \"Running\",\n jobDone: \"Finished\",\n jobFailed: \"Failed\",\n jobCancelled: \"Cancelled\",\n jobCancel: \"Cancel\",\n jobCancelling: \"Cancelling\u2026\",\n jobProgress: \"{done} of {total}\",\n /** Used while the total is still unknown \u2014 a report learns it once planned. */\n jobProgressOpen: \"{done} so far\",\n jobOpenResult: \"Open the result\",\n /** Toggle under the flow tail that reveals the job's full activity feed. */\n jobFlowAll: \"Show all {count} steps\",\n /** Shown INSTEAD of the step feed when the app pane is already showing this\n * run \u2014 see the collapse in renderJobCard. */\n jobWatchingInPane: \"Following along in the panel \u2192\",\n /** Advanced view \u2014 the pane is showing a computed widget, not a route. */\n paneBackToPage: \"Back to the page\",\n paneShowingWidget: \"Result\",\n jobUnreachable: \"Still running in the background \u2014 check back shortly\",\n // Render STEPS. The runner writes its narration in English (it has no request\n // locale), but each event also carries a machine `code`, so a Turkish chat can\n // read the flow in Turkish instead of in the runner's English. An event whose\n // code we have no label for falls back to the server's own sentence \u2014 which is\n // why the set below can grow without the widget ever showing a blank line.\n jobEvCancelled: \"Cancelled\",\n jobEvCancelledBeforeStart: \"Cancelled before it started\",\n jobEvInterrupted: \"Interrupted by a restart \u2014 not retried automatically\",\n // Proposal confirm cards\n // The SEVEN write tools that used to hit the generic English fallback\n // (\"Apply this change?\") \u2014 including generate_report, the most expensive\n // confirm in the product. The widget's contract is that visible copy lives\n // here so a host can translate it; a `??` literal in the render path is\n // outside that contract and untranslatable by construction.\n /**\n * The card title for a write proposal the host has not named.\n *\n * The fifteen sgiant-specific titles that sat beside this \u2014 and the six\n * report-card labels \u2014 moved to @sgiant/ai-apply on 2026-09-02, along with\n * the branches that used them. A package a stranger installs should not ship\n * one company's tool vocabulary. Hosts pass their own via `proposalTitles`.\n */\n proposalGeneric: \"Apply this change?\",\n // The generate_report confirm card. These were four hardcoded English lines\n // in `proposalSummary` \u2014 on a card the user reads before spending minutes of\n // model time, in a widget whose whole contract is that visible copy lives\n // here so it can be translated.\n artifactsTitle: \"This chat's files:\",\n artifactSave: \"Save\",\n artifactSaving: \"Saving\u2026\",\n apply: \"Apply\",\n dismiss: \"Dismiss\",\n applying: \"Applying\u2026\",\n applied: \"Applied\",\n openReport: \"Open the report\",\n tryAgain: \"Try again\",\n // History panel\n close: \"Close\",\n loading: \"Loading\u2026\",\n noConversations: \"No past conversations yet.\",\n untitledConversation: \"Untitled conversation\",\n star: \"Star this conversation\",\n unstar: \"Unstar\",\n historyLoadFailed: \"Couldn't load history \u2014 try again.\",\n bucketToday: \"Today\",\n bucketYesterday: \"Yesterday\",\n bucketWeek: \"Earlier this week\",\n bucketMonth: \"This month\",\n bucketOlder: \"Older\",\n // Lead form / preview / action chips\n submit: \"Submit\",\n sending: \"Sending\u2026\",\n /** Why a form's Send, or a proposal card's Apply, refused to go through. */\n requiredFields: \"Please fill in the highlighted fields first.\",\n done: \"Done \u2713\",\n preview: \"Preview\",\n otherOption: \"+ Other\u2026\",\n confirm: \"Confirm\",\n cancel: \"Cancel\",\n // Error state\n errorSnag: \"{name} hit a snag and couldn't answer. Please try again.\",\n reportIssue: \"Report issue\",\n reporting: \"Reporting\u2026\",\n reported: \"Reported \u2713 \u2014 our team will look into it\",\n reportFailed: \"Couldn't report \u2014 try later\",\n // Quality prompt (#299) \u2014 the widget ASKING, because the whole signal was\n // passive on surfaces where the control was invisible or absent.\n //\n // The two triggers deliberately ask DIFFERENT questions. A slow answer that\n // was excellent, prompted with \"was this helpful?\", teaches us \"slow = bad\",\n // which we already knew. So a long wait asks about the WAIT, and a failed\n // turn asks about the GOAL.\n qualitySlowTitle: \"That took a while \u2014 was the answer worth it?\",\n qualityFailedTitle: \"That didn't work \u2014 what were you trying to do?\",\n qualityReasonPlaceholder: \"Anything you'd add? (optional)\",\n qualityFailedPlaceholder: \"What were you trying to do?\",\n qualitySend: \"Send\",\n qualityDismiss: \"No thanks\",\n qualityThanks: \"Thanks \u2014 noted \u2713\",\n // Free-token meter. Shown ONLY when `signupUrl` is set, i.e. exactly on the\n // anonymous surfaces \u2014 the marketing visitor chat and the public demo \u2014 both\n // of which serve Turkish. These three were hardcoded English, so a Turkish\n // visitor read \"60,000 free tokens left\" under a Turkish conversation.\n meterTokensLeft: \"{count} free tokens left\",\n meterFreePreview: \"Free preview\",\n meterUsedThisSession: \"{count} used this session\",\n // Signup CTA (anonymous surfaces)\n signupCta: \"Sign up \u2014 14 days free\",\n // Composed UI cards ([[ui:\u2026]]) \u2014 the four states the widget colours. Any\n // OTHER status the model writes is shown in its own words, untranslated,\n // because only these four are ours to name.\n uiStatusPending: \"Waiting\",\n uiStatusRunning: \"Working\u2026\",\n uiStatusReady: \"Ready\",\n uiStatusFailed: \"Failed\",\n /** A tile whose media hasn't been produced yet \u2014 the space is deliberately\n * kept so the card doesn't reflow when the picture lands. */\n uiMediaPending: \"Not made yet\",\n /** A tile whose media id resolved to nothing (deleted, or not this account's).\n * Said plainly, because a blank tile reads as a broken product. */\n uiMediaMissing: \"Unavailable\",\n /** Carousel paging controls on a composed card. */\n prev: \"Previous\",\n next: \"Next\",\n /** ACTION CHIPS \u2014 the in-chat \"open this page / run this\" affordances.\n * These were template literals built inline, so they had no key at all and\n * a host could not translate them however hard it tried: a fully Turkish\n * session still read \"Opening Billing\u2026\", \"Billing \u2713\", \"Couldn't open\n * Billing\". `{label}` is the action's own name, which the host localises. */\n actionOpening: \"Opening {label}\u2026\",\n actionDone: \"{label} \u2713\",\n actionOpenFailed: \"Couldn't open {label}\",\n actionRunFailed: \"Couldn't run {label}\",\n actionRetry: \"{label} \u2014 try again\",\n /** Outcomes of an action the HOST performed on the page behind the chat. */\n actionShownOnPage: \"Shown on the page\",\n actionDoneOnPage: \"Done on the page\",\n actionPageFailed: \"Couldn't do that on the page\",\n actionOpened: \"Opened\",\n /** A thread the transcript endpoint would not return. */\n threadOpenFailed: \"Couldn't open that conversation.\",\n} as const;\n\n/** The widget's label bag \u2014 same keys as WIDGET_LABELS, all resolved to strings. */\nexport type WidgetLabels = Record<keyof typeof WIDGET_LABELS, string>;\n\n/**\n * Build the widget's `labels` bag from a host translator (react-i18next's `t`).\n * Each label resolves the `chatWidget.<key>` locale key, defaulting to the\n * English string in WIDGET_LABELS. Call once per (re-)mount so the widget picks\n * up the active language.\n */\nexport function resolveWidgetLabels(\n t: (key: string, defaultValue: string) => string\n): WidgetLabels {\n const out = {} as WidgetLabels;\n for (const k of Object.keys(WIDGET_LABELS) as (keyof WidgetLabels)[]) {\n out[k] = t(`chatWidget.${k}`, WIDGET_LABELS[k]);\n }\n return out;\n}\n", "/**\n * Replaying a saved thread, and the \u2039n/m\u203A branch navigation over it.\n *\n * Extracted from index.ts (#320). Measured at ZERO leakage: nothing in these\n * 250 lines reaches a top-level declaration in the file it came from, which is\n * why it moves as a unit and why the move is reviewable.\n *\n * The shape is worth naming, because it is the seam #306 needs: a PURE\n * TRANSFORM \u2014 a server payload in, a list of render items out \u2014 with no DOM,\n * no transport and no host context. `buildThreadReplay` is already documented\n * as taking a report ID rather than a path for exactly that reason (\"a pure\n * function with no host context, and the widget runs in three shells with\n * different route shapes\"). A published widget needs more regions like this\n * one and fewer inside the 6000-line closure.\n */\n\n/** One item replayed from a past thread: a chat message, or an inline data\n * widget (so reopening restores the conversation's charts/tables, not just\n * text). The render hooks / fallback handle the actual drawing. */\nexport type LoadedThreadItem =\n | {\n role: \"user\" | \"assistant\";\n content: string;\n /** Stable message id (branching). Absent on hosts that predate branching. */\n id?: string;\n /** The model that produced this message, so a vote cast on REPLAYED\n * history is attributable per model like a live one (#299). */\n model?: string;\n /** Parent turn in the conversation tree (null on a root message). */\n parentId?: string | null;\n /** Persisted per-turn token spend (assistant messages) \u2014 replays the\n * \u2191/\u2193 tokens caption that the live stream draws, so it survives the\n * end-of-turn thread reload and thread reopen. Shown even on the free\n * staff lane: not billed there, still worth seeing. */\n inputTokens?: number;\n outputTokens?: number;\n /** \u2039n/m\u203A sibling switcher data \u2014 present when this turn has siblings. */\n branch?: {\n index: number;\n count: number;\n prevLeaf?: string;\n nextLeaf?: string;\n };\n }\n | { kind: \"widget\"; spec: unknown; rows: unknown; comparisonRows?: unknown }\n | {\n kind: \"activity\";\n label: string;\n status: string;\n agent?: string;\n model?: string;\n /** The REPORT this step produced, if any.\n *\n * An ID and not a path: `buildThreadReplay` is a pure function with no\n * host context, and the widget runs in three shells with different route\n * shapes. The renderer turns it into a path via `opts.reportHref`. */\n reportId?: string;\n };\n/** The message variant of a replay item (carries the branch metadata). */\nexport type ReplayMessageItem = Extract<\n LoadedThreadItem,\n { role: \"user\" | \"assistant\" }\n>;\n\n/** \u2039n/m\u203A sibling navigation for one message (mirrors the panel's BranchNav). */\nexport interface BranchNav {\n index: number;\n count: number;\n prevLeaf?: string;\n nextLeaf?: string;\n}\n\n/** A stored message reduced to what branch navigation needs. */\nexport interface BranchMsg {\n id?: string;\n parentId?: string | null;\n createdAt?: string;\n}\n\nexport const ROOT_KEY = \"__root__\";\n\n/** Deepest leaf under a message, following the most recent child at each step\n * (memoised, cycle-guarded) \u2014 the target the \u2039n/m\u203A switcher jumps to. Mirrors\n * the full-page panel's `leafUnder`. */\nexport function leafUnder(\n id: string,\n childrenByParent: Map<string, BranchMsg[]>,\n memo: Map<string, string>,\n guard = 0\n): string {\n const cached = memo.get(id);\n if (cached) return cached;\n const kids = childrenByParent.get(id);\n if (!kids || kids.length === 0 || guard > 1000) {\n memo.set(id, id);\n return id;\n }\n const last = kids[kids.length - 1];\n const leaf = last?.id\n ? leafUnder(last.id, childrenByParent, memo, guard + 1)\n : id;\n memo.set(id, leaf);\n return leaf;\n}\n\n/**\n * Compute the \u2039n/m\u203A sibling switcher for every message on the active path.\n * Siblings share a parent; editing a user turn or regenerating an assistant\n * reply adds one. The prev/next targets are the deepest leaf under the adjacent\n * sibling, so switching lands on a full branch. Mirrors the panel's\n * `computeBranchNav` exactly.\n */\nexport function computeBranchNav(\n messages: BranchMsg[],\n activePathIds: string[]\n): Map<string, BranchNav> {\n const childrenByParent = new Map<string, BranchMsg[]>();\n for (const m of messages) {\n const key = m.parentId ?? ROOT_KEY;\n const arr = childrenByParent.get(key);\n if (arr) arr.push(m);\n else childrenByParent.set(key, [m]);\n }\n for (const arr of childrenByParent.values()) {\n arr.sort((a, b) => (a.createdAt ?? \"\").localeCompare(b.createdAt ?? \"\"));\n }\n const memo = new Map<string, string>();\n const byId = new Map<string, BranchMsg>();\n for (const m of messages) if (m.id) byId.set(m.id, m);\n const nav = new Map<string, BranchNav>();\n for (const id of activePathIds) {\n const m = byId.get(id);\n if (!m) continue;\n const siblings = childrenByParent.get(m.parentId ?? ROOT_KEY) ?? [];\n if (siblings.length < 2) continue;\n const i = siblings.findIndex((s) => s.id === id);\n if (i < 0) continue;\n const prev = siblings[i - 1];\n const next = siblings[i + 1];\n nav.set(id, {\n index: i + 1,\n count: siblings.length,\n ...(prev?.id\n ? { prevLeaf: leafUnder(prev.id, childrenByParent, memo) }\n : {}),\n ...(next?.id\n ? { nextLeaf: leafUnder(next.id, childrenByParent, memo) }\n : {}),\n });\n }\n return nav;\n}\n\n/**\n * Map a thread's messages + artifacts (the `/ai/threads/:id/messages` response)\n * into an ordered replay list \u2014 text messages interleaved with their data\n * widgets by `createdAt`, exactly like the full-page assistant. Pure; hosts pass\n * it the fetched payload so the widget reopens a conversation WITH its charts.\n *\n * Branching: when the server sends a non-empty `activePath`, the visible\n * transcript is that branch ONLY \u2014 messages (and message-scoped artifacts) are\n * filtered to it, and each surviving message carries its \u2039n/m\u203A sibling switcher\n * (computed over ALL messages). Absent/empty path \u2192 behave exactly as before\n * (back-compat with pre-branching hosts).\n */\nexport function buildThreadReplay(payload: {\n messages?: Array<{\n id?: string;\n parentId?: string | null;\n role: string;\n content: string;\n createdAt?: string;\n /** Persisted per-turn token spend (assistant rows; both read endpoints\n * return them) \u2014 carried into the replay's tokens caption. */\n inputTokens?: number | null;\n outputTokens?: number | null;\n }>;\n artifacts?: Array<{\n kind: string;\n messageId?: string | null;\n payload?: unknown;\n /** proposed | applied | discarded \u2014 what tells a replayed proposal card\n * whether Apply is still a live decision. */\n status?: string;\n createdAt?: string;\n }>;\n activePath?: string[];\n}): LoadedThreadItem[] {\n const activePathIds =\n payload.activePath && payload.activePath.length > 0\n ? payload.activePath\n : null;\n const activeSet = activePathIds ? new Set(activePathIds) : null;\n const branchNav = activePathIds\n ? computeBranchNav(payload.messages ?? [], activePathIds)\n : null;\n const items: Array<{ t: string; item: LoadedThreadItem }> = [];\n for (const m of payload.messages ?? []) {\n // Off-branch messages don't appear in the active transcript.\n if (activeSet && m.id && !activeSet.has(m.id)) continue;\n if ((m.role === \"user\" || m.role === \"assistant\") && m.content.trim()) {\n const item: ReplayMessageItem = {\n role: m.role,\n content: m.content,\n ...(m.id ? { id: m.id } : {}),\n ...(m.parentId !== undefined ? { parentId: m.parentId } : {}),\n ...(m.inputTokens ? { inputTokens: m.inputTokens } : {}),\n ...(m.outputTokens ? { outputTokens: m.outputTokens } : {}),\n ...(m.id && branchNav?.has(m.id)\n ? { branch: branchNav.get(m.id) }\n : {}),\n };\n items.push({ t: m.createdAt ?? \"\", item });\n }\n }\n for (const a of payload.artifacts ?? []) {\n // Branch-scoped artifacts (tied to a message) only belong to the active\n // branch; artifacts with no messageId are thread-wide and always stay.\n if (activeSet && a.messageId && !activeSet.has(a.messageId)) continue;\n if (a.kind === \"widget\") {\n const p = (a.payload ?? {}) as {\n spec?: unknown;\n rows?: unknown;\n comparisonRows?: unknown;\n };\n if (!p.spec) continue;\n items.push({\n t: a.createdAt ?? \"\",\n item: {\n kind: \"widget\",\n spec: p.spec,\n rows: p.rows ?? [],\n comparisonRows: p.comparisonRows ?? null,\n },\n });\n } else if (a.kind === \"activity\") {\n // A persisted process step \u2014 replays the timeline (+ acting agent) on reopen.\n const p = (a.payload ?? {}) as {\n label?: string;\n status?: string;\n agent?: string;\n model?: string;\n reportId?: string;\n };\n if (!p.label) continue;\n // The report id was persisted on the artifact and DROPPED here, so a\n // finished report replayed as a chip you could read and not open.\n const reportId =\n p.status !== \"error\" && typeof p.reportId === \"string\" && p.reportId\n ? p.reportId\n : undefined;\n items.push({\n t: a.createdAt ?? \"\",\n item: {\n kind: \"activity\",\n label: p.label,\n status: p.status ?? \"ok\",\n agent: p.agent,\n model: p.model,\n ...(reportId ? { reportId } : {}),\n },\n });\n }\n }\n items.sort((x, y) => x.t.localeCompare(y.t));\n return items.map((s) => s.item);\n}\n", "/**\n * The directive vocabulary: what the model may emit inside `[[tag:{json}]]`,\n * and the parsing that turns it into something a renderer can draw.\n *\n * Extracted from index.ts (#320) at zero leakage. This is the half of the\n * widget's extension seam that #306 calls \"a real extension point\" \u2014 the model\n * emits a directive, the host registers a renderer, the widget hands over the\n * parsed JSON. A third party writing their own renderer needs these shapes,\n * and needs to find them without reading 7000 lines.\n *\n * `stripDirectivesForReplay` lives here rather than beside the replay code\n * because it is the same grammar read in the other direction: it removes\n * exactly what this file defines.\n */\n\nimport { parseJsonDirective } from \"./directive\";\nimport { el } from \"./dom\";\nimport { PREFIX } from \"./prefix\";\n\n/** A data widget the assistant can render inline via `[[widget:{json}]]`. */\nexport interface WidgetSpec {\n /** \"stat\" | \"kpis\" | \"list\" | \"table\". Unknown kinds fall back to a list. */\n kind?: string;\n title?: string;\n /** stat: the big value + caption + optional delta. */\n value?: string | number;\n caption?: string;\n delta?: string;\n /** kpis: tiles of {label,value}. */\n items?: Array<{ label?: string; value?: string | number; delta?: string }>;\n /** list: plain bullet lines. */\n lines?: string[];\n /** table: header columns + row cells. */\n columns?: string[];\n rows?: Array<Array<string | number>>;\n}\n\n/** A navigation suggestion the assistant emits via `[[navigate:{json}]]`. */\nexport interface NavigateSpec {\n path: string;\n label?: string;\n}\n\n/** A dynamic HTML preview the assistant draws via `[[preview:{json}]]` \u2014 rendered\n * in a fully sandboxed iframe (no scripts, no same-origin) so arbitrary HTML+CSS\n * paints the real look but nothing can execute or reach out. */\nexport interface PreviewSpec {\n html: string;\n title?: string;\n}\n\n/** An in-app action the assistant proposes via `[[action:{json}]]`. The host\n * maps `name` to a real operation; `confirm` (if set) requires user approval. */\nexport interface ActionSpec {\n name: string;\n label?: string;\n /** Confirmation prompt \u2014 when set, the user must approve before it runs. */\n confirm?: string;\n /** Opaque data passed to the host's onWidgetAction(name, data). */\n data?: Record<string, string>;\n}\n\n/** Quick-reply chips the assistant offers via `[[chips:{json}]]` \u2014 tappable\n * answer options so the user picks instead of typing. Single-select sends on\n * tap; multi-select toggles + a Send button; `other` adds a \"type your own\"\n * chip. The chosen text is sent as a NORMAL message (history stays in order). */\nexport interface ChipsSpec {\n options: string[];\n multi?: boolean;\n other?: boolean;\n}\n\n/** Sentinel the assistant emits to ask the widget to render an email form. */\nexport const LEAD_TOKEN = \"[[collect-email]]\";\n\n/** One field in an AI-rendered form directive. */\nexport interface FormField {\n name: string;\n label?: string;\n /** Kept in step with what `buildField` can actually draw \u2014 a spec allowed to\n * ask for a control the builder cannot render is a promise to the model that\n * the UI then breaks. */\n type?:\n | \"text\"\n | \"email\"\n | \"number\"\n | \"textarea\"\n | \"select\"\n | \"checkbox\"\n | \"radio\";\n placeholder?: string;\n required?: boolean;\n options?: string[];\n}\nexport interface FormSpec {\n action: string;\n title?: string;\n fields: FormField[];\n submit?: string;\n}\n\n/**\n * A field the ASSISTANT asked the user to fill in on a proposal card.\n *\n * Declared by the proposal, never by the widget. A table of \"which args are\n * editable for which tool\" hardcoded in the UI would mean the chat can only\n * ever ask the questions the frontend was built to ask \u2014 a new tool, or a\n * decision the model wants confirmed, would need a UI release. The model knows\n * what it is unsure about; it says so, and the card renders it.\n *\n * Same shape as a `[[form:\u2026]]` field, and built by the same `buildField`, so an\n * input looks identical wherever the chat draws one.\n */\nexport interface ProposalField {\n /** The proposal ARG this field overwrites on apply. */\n arg: string;\n label?: string;\n type?: string;\n placeholder?: string;\n options?: string[];\n required?: boolean;\n}\n\n/**\n * A control `buildField` produced, plus how to read its answer.\n *\n * The reader is part of the return value because \"what the user chose\" is not\n * `.value` for every control type \u2014 a checkbox is `.checked`, a radio group is\n * whichever of its inputs is checked \u2014 and every caller wants the same thing: a\n * string to put in the payload.\n */\nexport interface BuiltField {\n /** Append THIS \u2014 the control, or the wrapper a labelled/grouped one needs. */\n node: HTMLElement;\n read: () => string;\n /** The control draws its own caption; a caller adding one would double it. */\n selfLabelled?: boolean;\n}\n\n/** Truthiness for a prefilled checkbox \u2014 model output, so accept the obvious\n * spellings rather than demanding exactly `\"true\"`. */\nexport function isTruthyValue(v: string | undefined): boolean {\n return [\"true\", \"1\", \"yes\", \"on\"].includes((v ?? \"\").trim().toLowerCase());\n}\n\n/** A proposal arg a field can be PREFILLED from: anything JSON-primitive, which\n * is everything with one obvious rendering. Objects/arrays have none, and\n * null/undefined mean the assistant proposed nothing for that arg. */\nexport function isPrimitiveArg(v: unknown): v is string | number | boolean {\n return (\n typeof v === \"string\" || typeof v === \"number\" || typeof v === \"boolean\"\n );\n}\n\n/** Read the fields off a proposal frame, defensively \u2014 this is model output. */\nexport function proposalFields(raw: unknown): ProposalField[] {\n if (!Array.isArray(raw)) return [];\n return raw\n .filter(\n (f): f is ProposalField =>\n Boolean(f) &&\n typeof f === \"object\" &&\n typeof (f as ProposalField).arg === \"string\" &&\n Boolean((f as ProposalField).arg)\n )\n .slice(0, 6);\n}\n\n/** Pull a `[[form:{json}]]` directive out of assistant text, if present. Uses\n * the shared brace-matching extractor, then validates the form shape. */\nexport function parseFormDirective(\n text: string\n): { spec: FormSpec; stripped: string } | null {\n const r = parseJsonDirective<FormSpec>(text, \"form\");\n if (!r) return null;\n const { spec } = r;\n if (!spec || typeof spec.action !== \"string\" || !Array.isArray(spec.fields))\n return null;\n return r;\n}\n\n/**\n * REPLAY view of a stored message: strip every interactive directive (which the\n * live turn already rendered as buttons/forms) so a reopened conversation shows\n * clean prose instead of raw `[[navigate:\u2026]]` / `[[form:\u2026]]` code, with a short\n * INERT note per directive (no re-execution). Used by the history/thread\n * restore path; live turns still render the real interactive widgets.\n */\nexport function stripDirectivesForReplay(text: string): {\n clean: string;\n notes: string[];\n navs: NavigateSpec[];\n uis: unknown[];\n} {\n let t = text;\n const notes: string[] = [];\n const uis: unknown[] = [];\n // Navigation is idempotent and side-effect-free, so on replay we hand it back\n // to be re-rendered as a REAL clickable chip rather than flattened to an inert\n // note \u2014 otherwise every \"Open <page>\" the assistant offered goes dead the\n // moment the thread reloads (which send() does after each turn for canonical\n // ids). This is the true cause of #111: the model DID emit [[navigate]], the\n // restore path is what killed it.\n const navs: NavigateSpec[] = [];\n for (let i = 0; i < 8; i++) {\n const w = parseJsonDirective<NavigateSpec>(t, \"navigate\");\n if (!w) break;\n t = w.stripped;\n if (w.spec.path) navs.push(w.spec);\n else notes.push(`\u2197 ${w.spec.label || \"Open page\"}`);\n }\n for (let i = 0; i < 8; i++) {\n const w = parseJsonDirective<ActionSpec>(t, \"action\");\n if (!w) break;\n t = w.stripped;\n notes.push(`\u2022 ${w.spec.label || w.spec.name}`);\n }\n for (let i = 0; i < 8; i++) {\n const w = parseJsonDirective<WidgetSpec>(t, \"widget\");\n if (!w) break;\n t = w.stripped;\n notes.push(`\u25A6 ${w.spec.title || \"widget\"}`);\n }\n // Composed UI cards are handed BACK to be re-drawn, not flattened to a note.\n // A card is the substance of the turn, not a decoration on it: a client who\n // reopens the conversation tomorrow to look at the scenes they were approving\n // would otherwise find \"\u25A6 card\" where the scenes had been. Re-drawing is safe\n // because every button on a card requires a click \u2014 nothing here can run by\n // itself on restore.\n for (let i = 0; i < 4; i++) {\n const u = parseJsonDirective<unknown>(t, \"ui\");\n if (!u) break;\n t = u.stripped;\n uis.push(u.spec);\n }\n for (let i = 0; i < 3; i++) {\n const p = parseJsonDirective<PreviewSpec>(t, \"preview\");\n if (!p) break;\n t = p.stripped;\n notes.push(`[preview] ${p.spec.title || \"Preview\"}`);\n }\n for (let i = 0; i < 4; i++) {\n const c = parseJsonDirective<ChipsSpec>(t, \"chips\");\n if (!c) break;\n t = c.stripped;\n notes.push(\"\uD83D\uDCAC options offered\");\n }\n const f = parseFormDirective(t);\n if (f) {\n t = f.stripped;\n notes.push(`\uD83D\uDCDD ${f.spec.title || \"Form\"} \u2014 submitted`);\n }\n if (t.includes(LEAD_TOKEN)) {\n t = t.replace(LEAD_TOKEN, \"\").trim();\n notes.push(\"\uD83D\uDCDD Form \u2014 submitted\");\n }\n return { clean: t, notes, navs, uis };\n}\n\n/**\n * Build one editable control for a proposal form, plus how to READ it back.\n *\n * Moved here from index.ts (#320) with ZERO closure dependencies \u2014 the whole\n * function reaches only `el`, `PREFIX`, and this file's own `BuiltField` and\n * `isTruthyValue`. It sat in the 6000-line closure purely because that is\n * where it was typed; nothing about it was ever widget state.\n *\n * `read` is the point of the return shape: every control answers as a STRING\n * whatever it was drawn as, because the submit path posts a flat\n * `Record<string, string>`. A checkbox that returned a boolean would be the\n * one field in the form with a different contract.\n */\nexport function buildField(field: {\n name: string;\n type?: string;\n label?: string;\n placeholder?: string;\n options?: string[];\n required?: boolean;\n value?: string;\n}): BuiltField {\n const type = field.type ?? \"text\";\n // Boolean/choice controls carry their own caption \u2014 a bare 16px box with\n // the label somewhere above it reads as decoration, not as a question.\n if (type === \"checkbox\") {\n const row = el(\"label\", `${PREFIX}-field-check`);\n const box = el(\"input\", `${PREFIX}-check`) as HTMLInputElement;\n box.type = \"checkbox\";\n box.checked = isTruthyValue(field.value);\n if (field.required) box.required = true;\n row.appendChild(box);\n const cap = el(\"span\", `${PREFIX}-field-check-label`);\n cap.textContent = field.label ?? field.placeholder ?? field.name;\n row.appendChild(cap);\n // Stringified so a field's answer is always a string, whatever it was\n // drawn as \u2014 the submit path posts a flat Record<string,string>.\n return {\n node: row,\n selfLabelled: true,\n read: () => (box.checked ? \"true\" : \"false\"),\n };\n }\n if (type === \"radio\" && field.options?.length) {\n const group = el(\"div\", `${PREFIX}-field-group`);\n // The `name` attribute is what makes radios mutually exclusive, so it has\n // to be unique per rendered group \u2014 two proposal cards asking the same\n // question would otherwise fight over one selection.\n const groupName = `${PREFIX}-${field.name}-${Math.random().toString(36).slice(2, 9)}`;\n for (const o of field.options) {\n const row = el(\"label\", `${PREFIX}-field-check`);\n const radio = el(\"input\", `${PREFIX}-check`) as HTMLInputElement;\n radio.type = \"radio\";\n radio.name = groupName;\n radio.value = o;\n // Required propagates to the inputs (one required radio makes the whole\n // group required). It was dropped here, so a required choice reported\n // itself as answered while nothing was selected.\n if (field.required) radio.required = true;\n if (o === field.value) radio.checked = true;\n row.appendChild(radio);\n const cap = el(\"span\", `${PREFIX}-field-check-label`);\n cap.textContent = o;\n row.appendChild(cap);\n group.appendChild(row);\n }\n return {\n node: group,\n read: () =>\n group.querySelector<HTMLInputElement>(\"input:checked\")?.value ?? \"\",\n };\n }\n let input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;\n if (type === \"textarea\") {\n input = el(\"textarea\", `${PREFIX}-field`) as HTMLTextAreaElement;\n } else if (type === \"select\") {\n const sel = el(\"select\", `${PREFIX}-field`) as HTMLSelectElement;\n for (const o of field.options ?? []) {\n const opt = document.createElement(\"option\");\n opt.value = o;\n opt.textContent = o;\n sel.appendChild(opt);\n }\n input = sel;\n } else {\n const i = el(\"input\", `${PREFIX}-field`) as HTMLInputElement;\n i.type = type === \"number\" ? \"number\" : type;\n input = i;\n }\n if (\"placeholder\" in input && field.placeholder)\n (input as HTMLInputElement).placeholder = field.label\n ? `${field.label} \u2014 ${field.placeholder}`\n : field.placeholder;\n else if (\"placeholder\" in input && field.label)\n (input as HTMLInputElement).placeholder = field.label;\n if (field.required) (input as HTMLInputElement).required = true;\n // Prefilled: the card hands back a value the user can accept or rewrite.\n if (field.value !== undefined) input.value = field.value;\n return { node: input, read: () => input.value.trim() };\n}\n", "/**\n * What the widget will and will not navigate to.\n *\n * Extracted from index.ts (#320) at zero leakage, and kept apart from the\n * directive shapes next door on purpose: this is the security surface, not the\n * vocabulary. Model output is untrusted \u2014 it can carry text scraped from\n * third-party sites \u2014 so every URL the assistant proposes passes through here.\n *\n * It becomes a public surface when the widget publishes (#306): an embedder\n * pointing the widget at their own backend inherits these rules, so they have\n * to be findable and readable rather than buried mid-file.\n */\n\nimport { isKnownNavTarget } from \"./host-actions\";\n\n/**\n * Is this an AI-supplied in-app path we're willing to navigate to? Model output\n * is untrusted (it can carry text scraped from third-party sites), so a path is\n * only ever a SITE-ROOT-RELATIVE route: it must start with a single `/` and\n * must not start with `//` or `/\\` (both read as protocol-relative, i.e. another\n * origin) \u2014 which also rules out `javascript:`/`data:` payloads.\n */\nexport function isSafeRelPath(path: unknown): path is string {\n return (\n typeof path === \"string\" &&\n path.startsWith(\"/\") &&\n !path.startsWith(\"//\") &&\n !path.startsWith(\"/\\\\\")\n );\n}\n\n/**\n * THE navigation gate \u2014 the one the advanced view's frame stands behind.\n *\n * Module-level and exported on purpose. `dispatchAction` is a closure over a\n * live DOM, so with the decision inline there the single check that keeps a\n * model-authored path out of an un-sandboxed, same-origin, session-bearing\n * iframe was unreachable by any test: deleting it left the whole suite green.\n * Here it is exercised directly by the host repo's nav-gate test and the\n * call site is pinned by source assertion.\n *\n * Throws rather than returning a verdict: an undefined return from\n * `dispatchAction` resolves as SUCCESS, and the chip ticks green over a path we\n * had just declined.\n *\n * `getTargets` is a getter, not an array: `refreshTargets` REPLACES the cache,\n * and the second read has to see what the first one fetched.\n */\nexport async function gateNavigationTarget(\n path: unknown,\n getTargets: () => readonly { path?: string }[],\n refreshTargets: () => Promise<void>\n): Promise<void> {\n // AI-supplied navigation targets are untrusted: only a root-relative in-app\n // route is ever followed \u2014 in the advanced-view frame OR by the host router.\n if (!isSafeRelPath(path))\n throw new Error(\"that navigation target is not an in-app page\");\n // \u2026and the shape test is not the gate. It admits every path in the app,\n // including another tenant's: `/accounts/<someone-else>/settings` is\n // root-relative, and the host's `orgPath`/`adminPath` return an\n // already-`/accounts/`-prefixed path untouched. The manifest this turn was\n // built from is the real allow-list; it has been present as `knownNavTargets`\n // since #111 and nothing but the prose-nav fallback ever read it.\n //\n // The cache holds the LAST TURN THIS SESSION SENT, which is not every chip\n // that can be clicked: `persistKey` restores a transcript across a reload and\n // its nav chips are live before a word has been sent. Ask the host for its\n // catalogue rather than refuse a page the manifest declares.\n if (!getTargets().length) await refreshTargets();\n if (!isKnownNavTarget(path, getTargets()))\n throw new Error(\"that page is not one I can open\");\n}\n\n/**\n * Final gate in front of the advanced-view iframe's `src`. That frame is\n * deliberately un-sandboxed (it hosts our own app with the live session), so\n * only a same-origin http(s) URL may ever be loaded into it.\n */\nexport function isSafeFrameUrl(url: string): boolean {\n try {\n const u = new URL(url, location.origin);\n return (\n u.origin === location.origin &&\n (u.protocol === \"http:\" || u.protocol === \"https:\")\n );\n } catch {\n return false;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["index_exports", "__export", "KNOWN_UI_STATUSES", "OPEN_ASSISTANT_EVENT", "OPERATE_ACTIONS", "PREFIX", "STANDARD_ACTION_PATHS", "UI_CONTROL_ACTIONS", "UI_SAY_ACTION", "WIDGET_LABELS", "broadcastAiChange", "buildThreadReplay", "clearHighlight", "createAiChatWidget", "createHostActions", "gateNavigationTarget", "genericProposalSummary", "isKnownNavTarget", "isNavigationAction", "isOperateAction", "isUiControlAction", "makePageContext", "matchManifest", "normalizeUiSpec", "parseJsonDirective", "resolveWidgetLabels", "scanAiTargets", "subscribeAiChange", "subscribeLiveSync", "uiSpecMediaIds", "UI_CONTROL_ACTIONS", "isUiControlAction", "name", "attrEscape", "s", "resolveTarget", "id", "el", "labelFor", "aria", "placeholder", "scanAiTargets", "max", "out", "seen", "els", "OVERLAY_ID", "KEYFRAMES_ID", "ensureKeyframes", "style", "clearTimer", "highlightEl", "ms", "clearHighlight", "pad", "ring", "place", "r", "onMove", "cleanup", "existing", "runUiControl", "action", "targetId", "OPERATE_ACTIONS", "isOperateAction", "setNativeValue", "value", "proto", "setter", "runOperateAction", "BRIDGE_CHANNEL", "isBridgeMessage", "data", "BRIDGE_CHANNEL", "originAllowed", "origin", "allowed", "createFrameTransport", "iframe", "opts", "targetOrigin", "allowed", "targets", "path", "ready", "seq", "pending", "post", "msg", "win", "onMessage", "ev", "originAllowed", "data", "isBridgeMessage", "requestScan", "p", "BRIDGE_CHANNEL", "action", "resolve", "id", "timer", "LOCALE_SEGMENTS", "isLocaleSegment", "seg", "normalizePath", "path", "p", "m", "entrySegments", "decodeFully", "raw", "cur", "i", "next", "decodeSegment", "decoded", "navPathSegments", "bare", "out", "matchManifest", "manifest", "segs", "pathSegs", "best", "bestLen", "e", "entrySegs", "off", "isKnownNavTarget", "targets", "t", "entry", "s", "makePageContext", "app", "recentPages", "page", "ctx", "STANDARD_ACTION_PATHS", "STANDARD_ACTION_DONE", "isNavigationAction", "name", "STANDARD_ACTION_ALREADY", "resolveTarget", "base", "alreadyThere", "strip", "targetPath", "targetQuery", "current", "wanted", "k", "v", "createHostActions", "cfg", "tr", "_k", "d", "standard", "data", "folderId", "assetId", "query", "target", "map", "action", "isUiControlAction", "runUiControl", "isOperateAction", "runOperateAction", "fn", "live", "key", "readItem", "fallback", "writeItem", "value", "readFlag", "whenUnavailable", "writeFlag", "on", "readJson", "isValid", "raw", "parsed", "writeJson", "motionCssVars", "CONTRAST_DARK", "CONTRAST_LIGHT", "parseHex", "hex", "h", "r", "g", "b", "relativeLuminance", "rgb", "c", "s", "contrastRatio", "a", "la", "lb", "resolveAccentContrast", "accent", "dark", "light", "mixSrgb", "ratioA", "pa", "pb", "p", "chan", "i", "accentInk", "surface", "target", "surfaceLum", "pole", "candidate", "ratio", "samePanePath", "a", "b", "norm", "p", "path", "shouldAutoNavigate", "i", "shouldCollapseNarration", "parseJsonDirective", "text", "tag", "open", "start", "braceStart", "depth", "inStr", "esc", "end", "i", "ch", "spec", "after", "extraBraces", "stripped", "UI_SAY_ACTION", "ASPECTS", "KNOWN_UI_STATUSES", "STATUS_SYNONYMS", "MAX_ITEMS", "MAX_ACTIONS", "MAX_TEXT", "MAX_DATA_KEYS", "clamp", "v", "max", "s", "isRecord", "sanitizeMediaId", "normalizeStatus", "key", "normalizeData", "out", "k", "raw", "normalizeAction", "name", "label", "confirm", "data", "variant", "normalizeActions", "a", "normalizeItem", "id", "title", "caption", "mediaId", "posterMediaId", "status", "media", "jobId", "actions", "normalizeUiSpec", "input", "items", "it", "layout", "aspect", "uiSpecMediaIds", "spec", "ids", "AI_LIVE_CHANNEL", "broadcastAiChange", "accountId", "domains", "ch", "subscribeAiChange", "cb", "handler", "ev", "d", "subscribeLiveSync", "opts", "closed", "ctrl", "backoff", "connect", "token", "res", "reader", "dec", "buf", "value", "done", "nl", "frame", "line", "l", "wait", "HTML_ESC", "escapeHtml", "s", "c", "safeUrl", "url", "u", "inline", "src", "codes", "_m", "code", "text", "safe", "i", "tableCells", "line", "SEP_RE", "FENCE_RE", "renderMarkdown", "blocks", "lines", "body", "out", "isBlank", "l", "fenceRef", "m", "startsBlock", "idx", "fref", "h", "level", "header", "thead", "rows", "r", "quote", "itemRe", "listItem", "ordered", "items", "tag", "para", "PREFIX", "parseLine", "line", "json", "attIcon", "kind", "el", "tag", "cls", "node", "PREFIX", "relBucket", "iso", "d", "now", "startToday", "day", "t", "relTime", "diff", "min", "hr", "wk", "maskMarkdown", "s", "escapeHtml", "c", "wrapPreviewHtml", "html", "addMsg", "log", "role", "text", "attachments", "chips", "a", "chip", "msg", "AVATAR_DEFS", "PREFIX", "AVATAR_SVG", "ICON_HISTORY", "ICON_EXPAND", "ICON_COLLAPSE", "ICON_COMPASS", "ICON_ADVANCED", "ICON_CHEVRON_R", "ICON_DOWNLOAD", "ICON_FLAG", "ICON_BELL", "ICON_BELL_OFF", "ICON_MORE", "ICON_ATTACH", "ICON_EDIT", "ICON_REGEN", "ICON_CHEV_L", "ICON_CHEV_R", "ICON_THUMB_UP", "ICON_THUMB_DOWN", "createMessageChrome", "deps", "switchBranch", "getThreadId", "vote", "voteUpLabel", "voteDownLabel", "buildBranchNav", "branch", "nav", "el", "PREFIX", "prev", "ICON_CHEV_L", "leaf", "count", "next", "ICON_CHEV_R", "buildVoteButtons", "messageId", "model", "wrap", "cur", "up", "down", "ICON_THUMB_UP", "ICON_THUMB_DOWN", "paint", "cast", "value", "createUiRenderers", "ctx", "L", "log", "scrollDown", "send", "richDisposers", "dispatchAction", "subscribeJob", "showPaneWidget", "input", "opts", "renderWidget", "spec", "card", "el", "PREFIX", "t", "kind", "v", "c", "d", "grid", "it", "tile", "kv", "kl", "kd", "table", "thead", "th", "tbody", "row", "tr", "cell", "ul", "line", "li", "uiStatusLabel", "status", "buildUiAction", "a", "itemId", "wrap", "variant", "btn", "label", "data", "run", "UI_SAY_ACTION", "msg", "q", "yes", "no", "restore", "uiBadgeClass", "uiStatusForJob", "paintUiTileLive", "view", "box", "badge", "s", "produced", "fillUiMedia", "buildUiTile", "ph", "initialStatus", "looksLikeVideo", "url", "ids", "urls", "boxes", "mid", "isVideo", "isAudio", "node", "poster", "p", "buildUiCarousel", "items", "count", "stage", "index", "dots", "paint", "i", "counter", "go", "delta", "arrow", "side", "b", "e", "dotRow", "renderUiCard", "raw", "normalizeUiSpec", "uiSpecMediaIds", "renderServerWidget", "rows", "comparisonRows", "note", "host", "dispose", "title", "first", "keys", "valueKey", "columns", "tableRows", "r", "renderChips", "options", "o", "multi", "chosen", "answered", "answer", "text", "opt", "other", "picks", "renderPreview", "bar", "frame", "wrapPreviewHtml", "operateConfirmText", "target", "short", "genericProposalSummary", "args", "k", "v", "describeArg", "depth", "x", "rec", "named", "detail", "keys", "stylesInjected", "USER_BUBBLE_INK", "hostDefinesPlatformTokens", "cs", "hslTriplet", "v", "darkRules", "bubble", "panel", "PREFIX", "injectStyles", "side", "darkWhenHostSaysSo", "darkWhenNobodySaid", "css", "motionCssVars", "style", "WIDGET_LABELS", "resolveWidgetLabels", "out", "k", "ROOT_KEY", "leafUnder", "id", "childrenByParent", "memo", "guard", "cached", "kids", "last", "leaf", "computeBranchNav", "messages", "activePathIds", "m", "key", "arr", "a", "b", "byId", "nav", "siblings", "i", "s", "prev", "next", "buildThreadReplay", "payload", "activeSet", "branchNav", "items", "item", "p", "reportId", "x", "y", "LEAD_TOKEN", "isTruthyValue", "v", "isPrimitiveArg", "proposalFields", "raw", "f", "parseFormDirective", "text", "r", "parseJsonDirective", "spec", "stripDirectivesForReplay", "t", "notes", "uis", "navs", "i", "w", "u", "p", "c", "buildField", "field", "type", "row", "el", "PREFIX", "box", "cap", "group", "groupName", "o", "radio", "input", "sel", "opt", "isSafeRelPath", "path", "gateNavigationTarget", "getTargets", "refreshTargets", "isKnownNavTarget", "isSafeFrameUrl", "url", "u", "OPEN_ASSISTANT_EVENT", "createAiChatWidget", "opts", "accent", "gradient", "side", "root", "name", "L", "key", "params", "WIDGET_LABELS", "avatarInner", "PREFIX", "AVATAR_SVG", "alive", "threadId", "busy", "viewGen", "NEW_TURN_KEY", "QUALITY_SLOW_MS", "pendingThreads", "isThreadPending", "syncBusy", "sendBtn", "applyLauncher", "panel", "setLauncher", "lastUserContent", "knownNavTargets", "refreshNavTargets", "targets", "ns", "storeKey", "layoutScope", "openStateKey", "draftKey", "saveDraft", "v", "writeItem", "loadDraft", "readItem", "rememberOpen", "isOpen", "writeFlag", "history", "RESTORE_MAX_AGE_MS", "loadState", "s", "readJson", "saveState", "writeJson", "injectStyles", "bubble", "el", "OPENED_KEY", "hasOpenedBefore", "readFlag", "markOpened", "launcher", "resolveGround", "r", "stack", "node", "bg", "m", "r8", "g8", "b8", "dot", "label", "showCount", "next", "hostDefinesPlatformTokens", "themeTokens", "fg", "resolveAccentContrast", "suffix", "surface", "ink", "accentInk", "mixSrgb", "USER_BUBBLE_INK", "k", "posKey", "SHEET_QUERY", "isSheet", "clampPos", "x", "y", "w", "h", "maxX", "maxY", "toCorner", "applyPos", "p", "readPos", "savePos", "header", "panelDrag", "ev", "endPanelDrag", "onResize", "avatar", "hName", "titleEl", "subEl", "metaChip", "hActions", "newChatBtn", "historyBtn", "ICON_HISTORY", "moreWrap", "moreBtn", "ICON_MORE", "moreMenu", "menuOpen", "setMenu", "open", "e", "starredThreads", "paintStarItem", "menuItem", "btn", "ico", "lab", "state", "on", "downloadItem", "ICON_DOWNLOAD", "exportConversation", "starItem", "id", "known", "flagItem", "ICON_FLAG", "flagNote", "reason", "err", "SOUND_KEY", "soundOn", "soundItem", "syncSound", "ICON_BELL", "ICON_BELL_OFF", "maybeDing", "force", "AudioCtx", "ctx", "now", "freq", "at", "o", "g", "AUTONAV_KEY", "autoNav", "autoNavItem", "ICON_COMPASS", "syncAutoNav", "expandBtn", "ICON_EXPAND", "advancedBtn", "ICON_ADVANCED", "closeBtn", "log", "pinned", "scrollQueued", "scrollDown", "richDisposers", "RESERVED_TAGS", "customRenderers", "registerRenderer", "tag", "renderer", "liveActivity", "clearRich", "dispose", "shortModel", "model", "stripTick", "paintActivity", "chip", "status", "agent", "icon", "badge", "escapeHtml", "modelBadge", "addActivityChip", "href", "isSafeRelPath", "path", "dispatchAction", "liveActivityFrame", "callId", "existing", "waitAlive", "ms", "resolve", "timer", "onAbort", "JOB_POLL_MS", "JOB_POLL_MAX_MS", "refKey", "ref", "refOf", "view", "jobsKey", "TRACKED_JOB_TTL_MS", "jobCards", "jobPolling", "jobWatchers", "emitJob", "card", "paintJob", "subs", "sub", "watchJob", "read", "started", "stalled", "forgetJob", "subscribeJob", "paint", "loadTrackedJobs", "parsed", "cutoff", "j", "saveTrackedJobs", "jobs", "trackedRef", "rememberJob", "tid", "jobTitle", "jobCounts", "done", "total", "terminal", "failed", "counts", "detail", "pct", "followRef", "autoNavigateFrame", "mirrored", "frameShowing", "events", "eventText", "code", "tailStart", "flowLine", "flow", "cancelFor", "jobId", "cancel", "pendingJobView", "trackJob", "userConfirmedWork", "trackConfirmedJob", "reattachTrackedJobs", "listRunning", "running", "applyAssistantRich", "text", "animate", "renderMarkdown", "addAssistantMessage", "clean", "notes", "navs", "uis", "stripDirectivesForReplay", "addMsg", "n", "note", "u", "renderUiCard", "nav", "renderNavigate", "usageBadge", "inTok", "outTok", "cap", "renderThreadItems", "items", "carriedQuestions", "it", "renderServerWidget", "before", "anchor", "addMessageActions", "q", "buildBranchNav", "buildVoteButtons", "createMessageChrome", "leaf", "switchBranch", "removeFrom", "beginEdit", "item", "nodes", "editor", "ta", "row", "save", "restore", "submit", "send", "renderQualityPrompt", "variant", "messageId", "title", "picked", "mk", "b", "ICON_THUMB_UP", "ICON_THUMB_DOWN", "actions", "dismiss", "qualityAsked", "threadTurns", "isUser", "canRegen", "canVote", "rowEl", "ICON_EDIT", "parentId", "ICON_REGEN", "paintOpeningView", "attachWheelScroll", "elm", "step", "ok", "meterEl", "quotaGranted", "quotaRemaining", "sessionUsed", "renderMeter", "remTxt", "usedTxt", "ROLE_NAMES", "ACTION_ROLE", "TOOL_ROLE", "statusEl", "statusRoleEl", "statusCreditsEl", "statusCreditsVal", "activeRole", "creditBalance", "displayedCredits", "creditInit", "creditRaf", "setCreditsText", "renderStatus", "animateCredits", "to", "from", "reduce", "dur", "start", "refreshBalance", "setRole", "role", "suggestionsEl", "form", "input", "artifactsEl", "savingArtifact", "refreshArtifacts", "a", "stagedAtts", "attBar", "attachBtn", "fileInput", "renderStaged", "attIcon", "MAX_ATT_BYTES", "attCap", "file", "capLabel", "bytes", "attError", "msg", "uploadFiles", "files", "uploadEndpoint", "token", "fd", "res", "ct", "ICON_ATTACH", "chatCol", "pane", "defs", "AVATAR_DEFS", "startNewChat", "renderSuggestions", "prefill", "forceNew", "pr", "bindKeyboard", "close", "unbindKeyboard", "toggle", "vvCleanup", "vv", "apply", "dragStartY", "dragging", "dy", "endDrag", "expanded", "setExpanded", "ICON_COLLAPSE", "advanced", "advancedFull", "paneCollapsed", "transport", "advFrame", "frameUrlLabel", "collapseBtnEl", "advKey", "rememberAdvanced", "setFrameUrlLabel", "url", "frameRequestedPath", "frameUserDriven", "framePath", "navigateFrame", "isSafeFrameUrl", "autoNavigated", "shouldAutoNavigate", "shouldCollapseNarration", "setPaneCollapsed", "buildFrame", "bar", "collapseBtn", "ICON_CHEVRON_R", "frame", "here", "body", "createFrameTransport", "teardownFrame", "disposePaneWidget", "paneWidgetDispose", "d", "paneShowsWidget", "showPaneRoute", "showPaneWidget", "spec", "rows", "comparisonRows", "back", "host", "updateAdvancedBtn", "setAdvancedFull", "advWidthKey", "resizer", "resizeLabel", "clampChatWidth", "px", "max", "setChatWidth", "persist", "restoreChatWidth", "raw", "resizing", "onResizeMove", "stopResize", "openAdvanced", "closeAdvanced", "data", "gateNavigationTarget", "isUiControlAction", "isOperateAction", "candidate", "relPath", "openHistory", "suggestToken", "empty", "my", "stillEmpty", "showSignupCta", "wrap", "msgs", "blob", "overlay", "head", "listEl", "threads", "lastBucket", "th", "bucket", "relBucket", "sep", "ti", "spin", "dt", "relTime", "star", "prev", "loadPastThread", "onOpenEvent", "content", "renderDirectives", "t", "i", "parseJsonDirective", "renderWidget", "renderPreview", "act", "renderAction", "chips", "renderChips", "parseFormDirective", "renderForm", "LEAD_TOKEN", "linkifyProseNav", "lead", "kept", "line", "bare", "hit", "PROPOSAL_LABELS", "proposalSummary", "args", "genericProposalSummary", "renderQuestion", "answered", "summary", "undelivered", "deliver", "optionIds", "chosen", "answer", "list", "confirm", "go", "renderProposal", "fields", "proposalAccountId", "proposalArtifactId", "edits", "disposePreview", "src", "video", "img", "field", "built", "buildField", "isPrimitiveArg", "editErr", "edited", "missing", "f", "isTruthyValue", "reportId", "recoverLostTurn", "turnThread", "sentContent", "last", "lastUser", "fork", "isRegen", "myGen", "live", "atts", "typing", "assistant", "assistantRaw", "producedAny", "deferredProposals", "turnIn", "turnOut", "failure", "liveMessageId", "liveModel", "turnStartedAt", "transportLost", "shownLen", "flushSegment", "final", "streamDirectiveTag", "appendStreamText", "masked", "maskMarkdown", "tok", "renderStreamDirective", "custom", "processStreamDisplay", "pending", "cut", "mode", "rest", "baseCtx", "pageContext", "ctxTargets", "reader", "decoder", "buf", "value", "idx", "parseLine", "piece", "proposalFields", "turn", "showError", "lastBubble", "hasLiveProposal", "hasErrorCard", "reloaded", "threadKey", "turns", "elapsed", "controls", "c", "first", "operateConfirmText", "createUiRenderers", "replay", "isNavigationAction", "run", "yes", "no", "txt", "retry", "report", "offAiChange", "subscribeAiChange", "scope", "syncOnline", "groundTimer", "lastGround", "watchGround"]
7
+ }