@guidelytech/guide-runtime 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/messenger.ts","../src/sanitize.ts","../src/help-view.ts","../src/messenger-styles.ts","../src/render-safe.ts"],"sourcesContent":["import type {\n ConversationMessagesResponse,\n MessengerConfig,\n MessengerConversation,\n MessengerMessage,\n ResourceCenterArticle,\n ResourceCenterConfig,\n SdkArticlesResponse,\n} from \"@guidelytech/shared\";\nimport { createHelpView, el, type HelpData, type HelpGuideRef } from \"./help-view\";\nimport { MESSENGER_STYLES } from \"./messenger-styles\";\nimport { renderSafeText } from \"./render-safe\";\n\n/** Callbacks the transport invokes while a message is sent and Scout replies. */\nexport interface MessengerStreamHandlers {\n /** A new conversation was created (first message only). */\n onConversation?(conversation: MessengerConversation): void;\n onMessage(message: MessengerMessage): void;\n onDelta(messageId: string, text: string): void;\n onDone(message: MessengerMessage | null, conversation: MessengerConversation): void;\n onError(message: string): void;\n}\n\n/** Server access for the messenger; implemented by sdk-core over the public SDK API. */\nexport interface MessengerApi {\n listConversations(): Promise<MessengerConversation[]>;\n loadMessages(conversationId: string, since?: string | null): Promise<ConversationMessagesResponse>;\n start(text: string, handlers: MessengerStreamHandlers): Promise<void>;\n send(conversationId: string, text: string, handlers: MessengerStreamHandlers): Promise<void>;\n handoff(conversationId: string): Promise<ConversationMessagesResponse>;\n rate(conversationId: string, rating: number): Promise<void>;\n listArticles(): Promise<SdkArticlesResponse>;\n getArticle(id: string): Promise<ResourceCenterArticle | null>;\n searchArticles(q: string): Promise<ResourceCenterArticle[]>;\n}\n\nexport type MessengerTab = \"home\" | \"messages\" | \"help\";\n\nexport interface MessengerOptions {\n scope: HTMLElement;\n config: MessengerConfig;\n api: MessengerApi;\n /** Resource center extras (recommended guides, checklists, FAQs, links) shown in the Help tab. */\n resourceCenter?: ResourceCenterConfig | null;\n guides?: HelpGuideRef[];\n onStartGuide?: (guideKeyOrId: string) => void;\n onOpenChecklist?: (guideId: string) => void;\n onArticleView?: (article: ResourceCenterArticle) => void;\n onOpen?: () => void;\n onClose?: () => void;\n onMessage?: (message: MessengerMessage, conversation: MessengerConversation) => void;\n hideLauncher?: boolean;\n /** Poll intervals (ms): open thread, and unread badge while closed. */\n pollMs?: { thread: number; background: number };\n}\n\nexport interface MessengerWidget {\n el: HTMLElement;\n open(tab?: MessengerTab): void;\n close(): void;\n toggle(): void;\n isOpen(): boolean;\n /** Opens a new conversation, optionally pre-filling the composer. */\n showNewMessage(text?: string): void;\n openArticle(id: string): void;\n destroy(): void;\n}\n\ntype View = { kind: \"tab\"; tab: MessengerTab } | { kind: \"thread\"; conversationId: string | null };\n\nconst SVG_NS = \"http://www.w3.org/2000/svg\";\nfunction chatIcon(doc: Document): SVGSVGElement {\n const svg = doc.createElementNS(SVG_NS, \"svg\");\n svg.setAttribute(\"viewBox\", \"0 0 24 24\");\n svg.setAttribute(\"aria-hidden\", \"true\");\n const path = doc.createElementNS(SVG_NS, \"path\");\n path.setAttribute(\"d\", \"M12 3C6.5 3 2 6.8 2 11.5c0 2.4 1.2 4.6 3.1 6.1L4.5 21l3.9-2c1.1.3 2.3.5 3.6.5 5.5 0 10-3.8 10-8.5S17.5 3 12 3z\");\n svg.appendChild(path);\n return svg;\n}\n\nexport function relativeTime(iso: string, now = Date.now()): string {\n const s = Math.max(0, Math.round((now - Date.parse(iso)) / 1000));\n if (!Number.isFinite(s) || s < 60) return \"now\";\n if (s < 3600) return `${Math.floor(s / 60)}m`;\n if (s < 86400) return `${Math.floor(s / 3600)}h`;\n return `${Math.floor(s / 86400)}d`;\n}\n\nfunction ensureStyles(scope: HTMLElement) {\n const root = scope.getRootNode() as ShadowRoot | Document;\n const container = \"host\" in root ? root : scope.ownerDocument.head;\n if (container.querySelector?.(\"style[data-g-messenger]\")) return;\n const style = scope.ownerDocument.createElement(\"style\");\n style.setAttribute(\"data-g-messenger\", \"\");\n style.textContent = MESSENGER_STYLES;\n container.appendChild(style);\n}\n\n/** Intercom-style messenger: launcher + panel with Home, Messages (chat with Scout / the team) and Help tabs. */\nexport function renderMessenger(options: MessengerOptions): MessengerWidget {\n const { scope, config, api } = options;\n const doc = scope.ownerDocument;\n const poll = options.pollMs ?? { thread: 5000, background: 60000 };\n ensureStyles(scope);\n\n let open = false;\n let destroyed = false;\n let view: View = { kind: \"tab\", tab: \"home\" };\n let conversations: MessengerConversation[] = [];\n let messages: MessengerMessage[] = [];\n let current: MessengerConversation | null = null;\n const streaming = new Map<string, string>();\n let sending = false;\n let draft = \"\";\n let articles: SdkArticlesResponse | null = null;\n let articlesLoading = false;\n let threadTimer: ReturnType<typeof setTimeout> | null = null;\n let backgroundTimer: ReturnType<typeof setInterval> | null = null;\n let threadSeq = 0;\n\n const pos = `g-pos-${config.launcher.position}`;\n // Other launchers (checklists, resource center) in the same corner move aside for the chat bubble.\n const rootNode = scope.getRootNode();\n const shadowHost = \"host\" in rootNode ? (rootNode.host as HTMLElement) : null;\n shadowHost?.setAttribute(\"data-g-messenger\", config.launcher.position);\n const root = el(doc, \"div\", \"g-messenger-root\");\n const launcher = el(doc, \"button\", `g-m-launcher ${pos}`);\n launcher.type = \"button\";\n launcher.setAttribute(\"aria-label\", `Open ${config.name}`);\n launcher.setAttribute(\"aria-expanded\", \"false\");\n launcher.appendChild(chatIcon(doc));\n const badge = el(doc, \"span\", \"g-m-badge\");\n badge.hidden = true;\n launcher.appendChild(badge);\n if (options.hideLauncher) launcher.hidden = true;\n\n const panel = el(doc, \"div\", `g-panel g-messenger ${pos}`);\n panel.setAttribute(\"role\", \"dialog\");\n panel.setAttribute(\"aria-label\", config.name);\n panel.hidden = true;\n const head = el(doc, \"div\", \"g-m-head\");\n const main = el(doc, \"div\", \"g-m-main\");\n const tabs = el(doc, \"div\", \"g-m-tabs\");\n tabs.setAttribute(\"role\", \"tablist\");\n panel.append(head, main, tabs);\n root.append(launcher, panel);\n scope.appendChild(root);\n\n const rcExtras = (ids: string[] = []) => ids.map((id) => options.guides?.find((g) => g.id === id || g.key === id)).filter((g): g is HelpGuideRef => !!g);\n const help = createHelpView({\n doc,\n data: (): HelpData => ({\n collections: articles?.collections ?? options.resourceCenter?.collections ?? [],\n articles: articles?.articles ?? options.resourceCenter?.articles ?? [],\n recommended: rcExtras(options.resourceCenter?.recommendedGuideIds),\n checklists: rcExtras(options.resourceCenter?.checklistGuideIds),\n faqs: options.resourceCenter?.faqs,\n links: options.resourceCenter?.links,\n }),\n search: (q) => api.searchArticles(q),\n loadArticle: (id) => api.getArticle(id),\n onStartGuide: (id) => {\n setOpen(false);\n options.onStartGuide?.(id);\n },\n onOpenChecklist: (id) => {\n setOpen(false);\n options.onOpenChecklist?.(id);\n },\n onArticleView: options.onArticleView,\n });\n\n const unreadTotal = () => conversations.reduce((n, c) => n + (c.unread > 0 ? 1 : 0), 0);\n const updateBadge = () => {\n const n = unreadTotal();\n badge.hidden = n === 0 || open;\n badge.textContent = n > 9 ? \"9+\" : String(n);\n launcher.setAttribute(\"aria-label\", n ? `Open ${config.name}, ${n} unread` : `Open ${config.name}`);\n };\n\n const upsertConversation = (c: MessengerConversation) => {\n conversations = [c, ...conversations.filter((x) => x.id !== c.id)].sort((a, b) => b.lastMessageAt.localeCompare(a.lastMessageAt));\n if (current?.id === c.id) current = c;\n updateBadge();\n };\n const upsertMessage = (m: MessengerMessage) => {\n const i = messages.findIndex((x) => x.id === m.id);\n if (i >= 0) messages[i] = m;\n else messages.push(m);\n messages.sort((a, b) => a.createdAt.localeCompare(b.createdAt));\n if (m.status !== \"pending\") streaming.delete(m.id);\n };\n\n const loadArticles = () => {\n if (articles || articlesLoading) return;\n articlesLoading = true;\n api\n .listArticles()\n .then((res) => {\n articles = res;\n if (view.kind === \"tab\" && view.tab === \"help\") help.render();\n })\n .catch(() => {})\n .finally(() => (articlesLoading = false));\n };\n\n const refreshConversations = () =>\n api\n .listConversations()\n .then((list) => {\n conversations = [...list].sort((a, b) => b.lastMessageAt.localeCompare(a.lastMessageAt));\n updateBadge();\n if (open && view.kind === \"tab\" && view.tab !== \"help\") render();\n })\n .catch(() => {});\n\n // ---------------------------------------------------------------------------\n // Rendering\n // ---------------------------------------------------------------------------\n\n function renderHead(title: string, back?: () => void) {\n head.textContent = \"\";\n if (back) {\n const b = el(doc, \"button\", \"g-m-back\", \"←\");\n b.type = \"button\";\n b.setAttribute(\"aria-label\", \"Back\");\n b.addEventListener(\"click\", back);\n head.appendChild(b);\n }\n head.appendChild(el(doc, \"h2\", \"\", title));\n const close = el(doc, \"button\", \"g-close\", \"×\");\n close.type = \"button\";\n close.setAttribute(\"aria-label\", \"Close\");\n close.addEventListener(\"click\", () => setOpen(false));\n head.appendChild(close);\n }\n\n function renderTabs() {\n tabs.textContent = \"\";\n tabs.hidden = view.kind === \"thread\";\n const items: Array<[MessengerTab, string, string]> = [\n [\"home\", \"⌂\", \"Home\"],\n [\"messages\", \"💬\", \"Messages\"],\n [\"help\", \"?\", \"Help\"],\n ];\n for (const [tab, icon, label] of items) {\n const b = el(doc, \"button\", \"g-m-tab\");\n b.type = \"button\";\n b.setAttribute(\"role\", \"tab\");\n b.setAttribute(\"aria-selected\", String(view.kind === \"tab\" && view.tab === tab));\n const i = el(doc, \"span\", \"g-m-tab-icon\", icon);\n i.setAttribute(\"aria-hidden\", \"true\");\n b.append(i, el(doc, \"span\", \"\", label));\n b.addEventListener(\"click\", () => go({ kind: \"tab\", tab }));\n tabs.appendChild(b);\n }\n }\n\n function conversationCard(c: MessengerConversation): HTMLButtonElement {\n const card = el(doc, \"button\", \"g-m-card\");\n card.type = \"button\";\n const row = el(doc, \"div\", \"g-m-row\");\n if (c.unread > 0) row.appendChild(el(doc, \"span\", \"g-m-dot\"));\n const text = el(doc, \"div\", \"g-m-grow\");\n text.append(el(doc, \"div\", \"g-m-card-title g-m-ellipsis\", c.subject || \"Conversation\"), el(doc, \"div\", \"g-m-card-sub g-m-ellipsis\", c.lastMessagePreview));\n row.append(text, el(doc, \"span\", \"g-m-time\", relativeTime(c.lastMessageAt)));\n card.appendChild(row);\n card.setAttribute(\"aria-label\", `${c.subject || \"Conversation\"}${c.unread ? \", unread\" : \"\"}`);\n card.addEventListener(\"click\", () => go({ kind: \"thread\", conversationId: c.id }));\n return card;\n }\n\n function renderHome() {\n renderHead(config.name);\n const scroll = el(doc, \"div\", \"g-m-scroll\");\n scroll.append(el(doc, \"div\", \"g-m-hero\", \"Hi there 👋\"), el(doc, \"p\", \"g-m-greet\", config.greeting));\n const ask = el(doc, \"button\", \"g-m-card\");\n ask.type = \"button\";\n ask.append(el(doc, \"div\", \"g-m-card-title\", \"Ask a question\"), el(doc, \"div\", \"g-m-card-sub\", config.aiAvailable ? `${config.name} answers right away, and the team can help too` : \"The team will reply here\"));\n ask.addEventListener(\"click\", () => go({ kind: \"thread\", conversationId: null }));\n scroll.appendChild(ask);\n const recent = conversations[0];\n if (recent) {\n scroll.appendChild(el(doc, \"div\", \"g-rc-section\", \"Recent conversation\"));\n scroll.appendChild(conversationCard(recent));\n }\n const search = el(doc, \"button\", \"g-m-card\");\n search.type = \"button\";\n search.append(el(doc, \"div\", \"g-m-card-title\", \"Search for help\"), el(doc, \"div\", \"g-m-card-sub\", \"Browse articles and guides\"));\n search.addEventListener(\"click\", () => go({ kind: \"tab\", tab: \"help\" }));\n scroll.appendChild(search);\n main.appendChild(scroll);\n }\n\n function renderMessagesTab() {\n renderHead(\"Messages\");\n const scroll = el(doc, \"div\", \"g-m-scroll\");\n if (!conversations.length) scroll.appendChild(el(doc, \"div\", \"g-m-empty\", \"No conversations yet.\"));\n conversations.forEach((c) => scroll.appendChild(conversationCard(c)));\n const start = el(doc, \"button\", \"g-btn g-btn-primary\", \"Send us a message\");\n start.type = \"button\";\n start.style.width = \"100%\";\n start.addEventListener(\"click\", () => go({ kind: \"thread\", conversationId: null }));\n scroll.appendChild(start);\n main.appendChild(scroll);\n }\n\n function renderHelpTab() {\n renderHead(\"Help\");\n loadArticles();\n help.render();\n main.appendChild(help.el);\n }\n\n let log: HTMLElement | null = null;\n let foot: HTMLElement | null = null;\n let textarea: HTMLTextAreaElement | null = null;\n let sendButton: HTMLButtonElement | null = null;\n\n function messageNode(m: MessengerMessage): HTMLElement {\n const wrap = el(doc, \"div\", `g-m-msg g-from-${m.author}${m.status === \"failed\" ? \" g-m-failed\" : \"\"}`);\n if (m.author === \"system\") {\n wrap.textContent = m.body;\n return wrap;\n }\n if (m.author !== \"user\") {\n const author = el(doc, \"div\", \"g-m-author\", m.author === \"scout\" ? config.name : m.authorName || \"Support team\");\n if (m.author === \"scout\") author.appendChild(el(doc, \"span\", \"g-m-ai\", \"AI\"));\n wrap.appendChild(author);\n }\n const bubble = el(doc, \"div\", \"g-m-bubble\");\n const live = streaming.get(m.id);\n if (m.status === \"pending\" && !live) {\n const dots = el(doc, \"span\", \"g-m-typing\");\n dots.setAttribute(\"aria-label\", `${config.name} is typing`);\n dots.append(el(doc, \"span\"), el(doc, \"span\"), el(doc, \"span\"));\n bubble.appendChild(dots);\n } else if (m.status === \"failed\") {\n bubble.textContent = \"This reply couldn't be completed.\";\n } else {\n bubble.appendChild(renderSafeText(doc, live ?? m.body));\n }\n if (m.status === \"pending\") bubble.setAttribute(\"aria-busy\", \"true\");\n bubble.dataset.messageId = m.id;\n wrap.appendChild(bubble);\n if (m.citations.length || m.suggestedGuides.length) {\n const chips = el(doc, \"div\", \"g-m-chips\");\n for (const g of m.suggestedGuides) {\n const b = el(doc, \"button\", \"g-m-chip g-m-guide\", `▶ Show me: ${g.name}`);\n b.type = \"button\";\n b.addEventListener(\"click\", () => {\n setOpen(false);\n options.onStartGuide?.(g.key);\n });\n chips.appendChild(b);\n }\n for (const c of m.citations) {\n const b = el(doc, \"button\", \"g-m-chip\", `📄 ${c.title}`);\n b.type = \"button\";\n b.addEventListener(\"click\", () => openArticle(c.articleId));\n chips.appendChild(b);\n }\n wrap.appendChild(chips);\n }\n return wrap;\n }\n\n function renderLog() {\n if (!log) return;\n const atBottom = log.scrollHeight - log.scrollTop - log.clientHeight < 40;\n log.textContent = \"\";\n if (!messages.length) {\n const intro: MessengerMessage = { id: \"intro\", author: config.aiAvailable ? \"scout\" : \"agent\", authorName: \"Support team\", body: config.greeting, citations: [], suggestedGuides: [], status: \"complete\", createdAt: new Date().toISOString() };\n log.appendChild(messageNode(intro));\n }\n messages.forEach((m) => log!.appendChild(messageNode(m)));\n if (atBottom || sending) log.scrollTop = log.scrollHeight;\n renderFoot();\n }\n\n function renderFoot() {\n if (!foot || !textarea || !sendButton) return;\n sendButton.disabled = sending || !textarea.value.trim();\n textarea.disabled = sending;\n for (const extra of Array.from(foot.querySelectorAll(\".g-m-human,.g-m-rate,.g-m-note\"))) extra.remove();\n if (current?.status === \"closed\" && current.rating == null) {\n const rate = el(doc, \"div\", \"g-m-rate\", \"How was this conversation? \");\n for (const [score, face, label] of [\n [1, \"😞\", \"Bad\"],\n [3, \"😐\", \"OK\"],\n [5, \"😀\", \"Great\"],\n ] as const) {\n const b = el(doc, \"button\", \"\", face);\n b.type = \"button\";\n b.setAttribute(\"aria-label\", label);\n b.addEventListener(\"click\", () => {\n if (!current) return;\n const id = current.id;\n current = { ...current, rating: score };\n upsertConversation(current);\n renderFoot();\n api.rate(id, score).catch(() => {});\n });\n rate.appendChild(b);\n }\n foot.prepend(rate);\n } else if (current && current.rating != null && current.status === \"closed\") {\n foot.prepend(el(doc, \"div\", \"g-m-note\", \"Thanks for the feedback!\"));\n }\n if (config.handoffEnabled && config.aiAvailable && current?.status === \"bot\" && !sending) {\n const human = el(doc, \"button\", \"g-m-human\", \"Talk to a person\");\n human.type = \"button\";\n human.addEventListener(\"click\", () => {\n if (!current) return;\n const id = current.id;\n human.disabled = true;\n api\n .handoff(id)\n .then((res) => {\n if (current?.id !== id) return;\n upsertConversation(res.conversation);\n res.messages.forEach(upsertMessage);\n renderLog();\n })\n .catch(() => (human.disabled = false));\n });\n foot.appendChild(human);\n }\n }\n\n function renderThread(conversationId: string | null) {\n renderHead(current?.subject || (conversationId ? \"Conversation\" : \"New conversation\"), () => go({ kind: \"tab\", tab: \"messages\" }));\n log = el(doc, \"div\", \"g-m-log\");\n log.setAttribute(\"role\", \"log\");\n log.setAttribute(\"aria-live\", \"polite\");\n foot = el(doc, \"div\", \"g-m-foot\");\n const compose = el(doc, \"form\", \"g-m-compose\");\n textarea = el(doc, \"textarea\");\n textarea.rows = 1;\n textarea.maxLength = 4000;\n textarea.placeholder = current?.status === \"open\" ? \"Reply to the team…\" : \"Ask a question…\";\n textarea.setAttribute(\"aria-label\", \"Message\");\n textarea.value = draft;\n sendButton = el(doc, \"button\", \"g-m-send\", \"↑\");\n sendButton.type = \"submit\";\n sendButton.setAttribute(\"aria-label\", \"Send\");\n compose.append(textarea, sendButton);\n foot.appendChild(compose);\n textarea.addEventListener(\"input\", () => {\n draft = textarea!.value;\n if (sendButton) sendButton.disabled = sending || !draft.trim();\n });\n textarea.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" && !e.shiftKey && !e.isComposing) {\n e.preventDefault();\n submit();\n }\n });\n compose.addEventListener(\"submit\", (e) => {\n e.preventDefault();\n submit();\n });\n main.append(log, foot);\n renderLog();\n textarea.focus?.({ preventScroll: true });\n }\n\n function render() {\n if (destroyed) return;\n main.textContent = \"\";\n log = foot = null;\n textarea = null;\n sendButton = null;\n if (view.kind === \"thread\") renderThread(view.conversationId);\n else if (view.tab === \"home\") renderHome();\n else if (view.tab === \"messages\") renderMessagesTab();\n else renderHelpTab();\n renderTabs();\n }\n\n // ---------------------------------------------------------------------------\n // Behaviour\n // ---------------------------------------------------------------------------\n\n const handlers = (seq: number): MessengerStreamHandlers => ({\n onConversation(c) {\n if (seq !== threadSeq) return;\n current = c;\n view = { kind: \"thread\", conversationId: c.id };\n upsertConversation(c);\n },\n onMessage(m) {\n if (seq !== threadSeq) return;\n upsertMessage(m);\n renderLog();\n },\n onDelta(id, text) {\n if (seq !== threadSeq) return;\n streaming.set(id, (streaming.get(id) ?? \"\") + text);\n const bubble = log?.querySelector<HTMLElement>(`[data-message-id=\"${id}\"]`);\n if (!bubble) return renderLog();\n bubble.textContent = \"\";\n bubble.appendChild(renderSafeText(doc, streaming.get(id)!));\n if (log) log.scrollTop = log.scrollHeight;\n },\n onDone(m, c) {\n if (seq !== threadSeq) return;\n if (m) {\n upsertMessage(m);\n options.onMessage?.(m, c);\n }\n upsertConversation({ ...c, unread: 0 });\n current = { ...c, unread: 0 };\n },\n onError() {\n /* the server also stores a system message explaining what happened */\n },\n });\n\n function submit() {\n if (!textarea || sending) return;\n const text = textarea.value.trim();\n if (!text) return;\n sending = true;\n draft = \"\";\n textarea.value = \"\";\n renderFoot();\n const seq = threadSeq;\n const h = handlers(seq);\n const done = current ? api.send(current.id, text, h) : api.start(text, h);\n done\n .catch(() => {\n if (seq !== threadSeq) return;\n draft = text;\n if (textarea) textarea.value = text;\n const note: MessengerMessage = { id: `local_${Date.now()}`, author: \"system\", body: \"Your message couldn't be sent. Please try again.\", citations: [], suggestedGuides: [], status: \"complete\", createdAt: new Date().toISOString() };\n upsertMessage(note);\n })\n .finally(() => {\n if (seq !== threadSeq) return;\n sending = false;\n streaming.clear();\n if (view.kind === \"thread\") {\n if (current) renderHead(current.subject || \"Conversation\", () => go({ kind: \"tab\", tab: \"messages\" }));\n renderLog();\n }\n scheduleThreadPoll();\n });\n }\n\n function scheduleThreadPoll() {\n if (threadTimer) clearTimeout(threadTimer);\n threadTimer = null;\n if (!open || view.kind !== \"thread\" || !current || destroyed) return;\n threadTimer = setTimeout(pollThread, poll.thread);\n }\n\n function pollThread() {\n if (!current || sending) return scheduleThreadPoll();\n const seq = threadSeq;\n const pending = messages.find((m) => m.status === \"pending\");\n const last = messages.filter((m) => !m.id.startsWith(\"local_\")).at(-1);\n // Re-fetch from a pending reply (its body changes in place); otherwise only newer messages.\n const since = pending ? new Date(Date.parse(pending.createdAt) - 1).toISOString() : (last?.createdAt ?? null);\n api\n .loadMessages(current.id, since)\n .then((res) => {\n if (seq !== threadSeq) return;\n const before = messages.length;\n const changed = res.messages.some((m) => {\n const prev = messages.find((x) => x.id === m.id);\n return !prev || prev.status !== m.status || prev.body !== m.body;\n });\n res.messages.forEach(upsertMessage);\n upsertConversation({ ...res.conversation, unread: 0 });\n current = { ...res.conversation, unread: 0 };\n if (changed || messages.length !== before) {\n renderLog();\n res.messages.filter((m) => m.author !== \"user\").forEach((m) => options.onMessage?.(m, res.conversation));\n } else renderFoot();\n })\n .catch(() => {})\n .finally(scheduleThreadPoll);\n }\n\n function go(next: View) {\n threadSeq++;\n if (threadTimer) clearTimeout(threadTimer);\n threadTimer = null;\n if (view.kind === \"tab\" && view.tab === \"help\" && !(next.kind === \"tab\" && next.tab === \"help\")) help.reset();\n view = next;\n if (next.kind === \"thread\") {\n messages = [];\n streaming.clear();\n sending = false;\n current = next.conversationId ? (conversations.find((c) => c.id === next.conversationId) ?? null) : null;\n render();\n if (next.conversationId) {\n const seq = threadSeq;\n api\n .loadMessages(next.conversationId)\n .then((res) => {\n if (seq !== threadSeq) return;\n res.messages.forEach(upsertMessage);\n current = { ...res.conversation, unread: 0 };\n upsertConversation(current);\n renderHead(current.subject || \"Conversation\", () => go({ kind: \"tab\", tab: \"messages\" }));\n renderLog();\n })\n .catch(() => {})\n .finally(scheduleThreadPoll);\n }\n } else {\n draft = \"\";\n render();\n }\n }\n\n function openArticle(id: string) {\n go({ kind: \"tab\", tab: \"help\" });\n help.openArticle(id);\n }\n\n function startBackgroundPoll() {\n if (backgroundTimer) clearInterval(backgroundTimer);\n backgroundTimer = setInterval(() => {\n if (!open && conversations.length && !destroyed) void refreshConversations();\n }, poll.background);\n }\n\n const setOpen = (v: boolean) => {\n if (open === v || destroyed) return;\n open = v;\n panel.hidden = !v;\n launcher.setAttribute(\"aria-expanded\", String(v));\n launcher.setAttribute(\"aria-label\", v ? `Close ${config.name}` : `Open ${config.name}`);\n updateBadge();\n if (v) {\n render();\n void refreshConversations();\n if (view.kind === \"thread\") scheduleThreadPoll();\n options.onOpen?.();\n } else {\n if (threadTimer) clearTimeout(threadTimer);\n threadTimer = null;\n options.onClose?.();\n }\n };\n launcher.addEventListener(\"click\", () => setOpen(!open));\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n e.stopPropagation();\n setOpen(false);\n launcher.focus();\n }\n };\n panel.addEventListener(\"keydown\", onKey);\n\n void refreshConversations();\n startBackgroundPoll();\n\n return {\n el: root,\n open(tab?: MessengerTab) {\n if (tab) view = { kind: \"tab\", tab };\n if (open && tab) render();\n setOpen(true);\n },\n close: () => setOpen(false),\n toggle: () => setOpen(!open),\n isOpen: () => open,\n showNewMessage(text?: string) {\n setOpen(true);\n go({ kind: \"thread\", conversationId: null });\n if (text && textarea) {\n textarea.value = text.slice(0, 4000);\n draft = textarea.value;\n renderFoot();\n }\n },\n openArticle(id: string) {\n setOpen(true);\n openArticle(id);\n },\n destroy() {\n destroyed = true;\n if (threadTimer) clearTimeout(threadTimer);\n if (backgroundTimer) clearInterval(backgroundTimer);\n panel.removeEventListener(\"keydown\", onKey);\n help.destroy();\n shadowHost?.removeAttribute(\"data-g-messenger\");\n root.remove();\n },\n };\n}\n","const ALLOWED_TAGS = new Set([\"p\", \"br\", \"strong\", \"b\", \"em\", \"i\", \"u\", \"a\", \"ul\", \"ol\", \"li\", \"img\", \"h1\", \"h2\", \"h3\", \"h4\", \"code\", \"pre\", \"blockquote\", \"span\", \"div\", \"hr\", \"button\"]);\nconst DROP_TAGS = new Set([\"script\", \"style\", \"iframe\", \"object\", \"embed\", \"form\", \"input\", \"textarea\", \"select\", \"link\", \"meta\", \"base\", \"template\", \"svg\", \"math\", \"frame\", \"frameset\", \"noscript\"]);\nconst ALLOWED_ATTRS = new Set([\"href\", \"src\", \"alt\", \"title\", \"target\", \"rel\", \"class\", \"data-g-action\", \"data-g-value\"]);\nfunction isSafeUrl(value: string, attr: string, tag: string): boolean {\n // eslint-disable-next-line no-control-regex\n const v = value.trim().replace(/[\\u0000-\\u001f\\s]/g, \"\").toLowerCase();\n if (!v) return false;\n const m = /^([a-z][a-z0-9+.-]*):/.exec(v);\n if (!m) return true; // relative URL\n const scheme = m[1];\n if (scheme === \"data\") return tag === \"img\" && attr === \"src\" && v.startsWith(\"data:image/\");\n return scheme === \"http\" || scheme === \"https\" || scheme === \"mailto\" || scheme === \"tel\";\n}\n\nfunction sanitizeElement(el: Element): void {\n const tag = el.tagName.toLowerCase();\n for (const attr of Array.from(el.attributes)) {\n const name = attr.name.toLowerCase();\n if (name.startsWith(\"on\") || !ALLOWED_ATTRS.has(name)) {\n el.removeAttribute(attr.name);\n continue;\n }\n if ((name === \"href\" || name === \"src\") && !isSafeUrl(attr.value, name, tag)) el.removeAttribute(attr.name);\n }\n if (tag === \"a\") {\n const target = el.getAttribute(\"target\");\n if (target && target !== \"_blank\" && target !== \"_self\") el.removeAttribute(\"target\");\n if (el.getAttribute(\"target\") === \"_blank\") el.setAttribute(\"rel\", \"noopener noreferrer\");\n }\n if (tag === \"button\") el.setAttribute(\"type\", \"button\");\n if (tag === \"img\") {\n el.setAttribute(\"loading\", \"lazy\");\n if (!el.getAttribute(\"alt\")) el.setAttribute(\"alt\", \"\");\n }\n}\n\nfunction walk(node: Node): void {\n const children = Array.from(node.childNodes);\n for (const child of children) {\n if (child.nodeType === 8 /* comment */) {\n node.removeChild(child);\n continue;\n }\n if (child.nodeType !== 1) continue;\n const el = child as Element;\n const tag = el.tagName.toLowerCase();\n if (DROP_TAGS.has(tag)) {\n node.removeChild(el);\n continue;\n }\n if (!ALLOWED_TAGS.has(tag)) {\n // Unwrap: keep text/children, drop the element itself.\n while (el.firstChild) node.insertBefore(el.firstChild, el);\n node.removeChild(el);\n // Re-walk the parent since children moved up.\n walk(node);\n return;\n }\n sanitizeElement(el);\n walk(el);\n }\n}\n\n/**\n * Parses HTML into a sanitized DocumentFragment using a strict allowlist. No external\n * dependency: a `<template>` element parses inertly and the tree is walked element by element.\n */\nexport function sanitizeToFragment(html: string, doc: Document = document): DocumentFragment {\n const tpl = doc.createElement(\"template\");\n tpl.innerHTML = html || \"\";\n walk(tpl.content);\n return tpl.content;\n}\n\n/** Returns sanitized HTML as a string. */\nexport function sanitizeHtml(html: string, doc: Document = document): string {\n const frag = sanitizeToFragment(html, doc);\n const div = doc.createElement(\"div\");\n div.appendChild(frag);\n return div.innerHTML;\n}\n\n/** Replaces the content of `el` with the sanitized HTML. */\nexport function setSanitizedHtml(el: Element, html: string): void {\n el.textContent = \"\";\n el.appendChild(sanitizeToFragment(html, el.ownerDocument));\n}\n","import type { ResourceCenterArticle, ResourceCenterConfig } from \"@guidelytech/shared\";\nimport { setSanitizedHtml } from \"./sanitize\";\n\nexport interface HelpGuideRef {\n id: string;\n key: string;\n name: string;\n kind: string;\n}\n\n/** What the help view lists. Article bodies may be missing (`bodyOmitted` or summaries only): they load on open. */\nexport interface HelpData {\n collections: ResourceCenterConfig[\"collections\"];\n articles: Array<Omit<ResourceCenterArticle, \"body\"> & { body?: string }>;\n recommended?: HelpGuideRef[];\n checklists?: HelpGuideRef[];\n faqs?: ResourceCenterConfig[\"faqs\"];\n links?: ResourceCenterConfig[\"links\"];\n}\n\nexport interface HelpViewOptions {\n doc: Document;\n data: () => HelpData;\n /** Server search. Results are merged with the client-side title/summary filter. */\n search?: (q: string) => Promise<ResourceCenterArticle[]>;\n /** Loads an article body that isn't in `data()`. */\n loadArticle?: (id: string) => Promise<ResourceCenterArticle | null>;\n onStartGuide?: (guideId: string) => void;\n onOpenChecklist?: (guideId: string) => void;\n onArticleView?: (article: ResourceCenterArticle) => void;\n}\n\nexport interface HelpView {\n /** Search box + scrollable body. */\n el: HTMLElement;\n render(): void;\n /** Back to the article list and clear the search. */\n reset(): void;\n openArticle(id: string): void;\n focusSearch(): void;\n setQuery(q: string): void;\n destroy(): void;\n}\n\nexport function el<K extends keyof HTMLElementTagNameMap>(doc: Document, tag: K, cls?: string, text?: string): HTMLElementTagNameMap[K] {\n const e = doc.createElement(tag);\n if (cls) e.className = cls;\n if (text != null) e.textContent = text;\n return e;\n}\n\nexport function listItem(doc: Document, icon: string, title: string, summary?: string): HTMLButtonElement {\n const b = el(doc, \"button\", \"g-rc-item\");\n b.type = \"button\";\n const i = el(doc, \"span\", \"g-rc-icon\", icon);\n i.setAttribute(\"aria-hidden\", \"true\");\n const text = el(doc, \"span\");\n text.appendChild(el(doc, \"div\", \"g-rc-title\", title));\n if (summary) text.appendChild(el(doc, \"div\", \"g-rc-sum\", summary));\n b.append(i, text);\n return b;\n}\n\ntype ListedArticle = HelpData[\"articles\"][number];\n\n/** Article browser shared by the resource center and the messenger Help tab. */\nexport function createHelpView(options: HelpViewOptions): HelpView {\n const { doc } = options;\n let query = \"\";\n let serverResults: ResourceCenterArticle[] = [];\n let searchSeq = 0;\n let openSeq = 0;\n let active: { article: ListedArticle; loading: boolean } | null = null;\n\n const root = el(doc, \"div\", \"g-help\");\n const searchWrap = el(doc, \"div\", \"g-rc-search\");\n const input = el(doc, \"input\", \"g-input\");\n input.type = \"search\";\n input.placeholder = \"Search help articles…\";\n input.setAttribute(\"aria-label\", \"Search help articles\");\n searchWrap.appendChild(input);\n const body = el(doc, \"div\", \"g-panel-body\");\n root.append(searchWrap, body);\n\n const hasBody = (a: ListedArticle): a is ResourceCenterArticle => typeof a.body === \"string\" && !a.bodyOmitted;\n\n const openArticle = (article: ListedArticle) => {\n if (article.externalUrl) {\n window.open(article.externalUrl, \"_blank\", \"noopener,noreferrer\");\n if (hasBody(article)) options.onArticleView?.(article);\n return;\n }\n const seq = ++openSeq;\n if (hasBody(article) || !options.loadArticle) {\n active = { article, loading: false };\n if (hasBody(article)) options.onArticleView?.(article);\n return render();\n }\n active = { article, loading: true };\n render();\n options\n .loadArticle(article.id)\n .then((full) => {\n if (seq !== openSeq || !active) return;\n active = { article: full ?? { ...article, body: \"\" }, loading: false };\n if (full) options.onArticleView?.(full);\n render();\n })\n .catch(() => {\n if (seq !== openSeq || !active) return;\n active = { article: { ...article, body: \"\" }, loading: false };\n render();\n });\n };\n\n const renderArticle = ({ article, loading }: NonNullable<typeof active>) => {\n const wrap = el(doc, \"div\", \"g-rc-article\");\n const back = el(doc, \"button\", \"g-btn g-btn-link g-rc-back\", \"← Back\");\n back.type = \"button\";\n back.addEventListener(\"click\", () => {\n openSeq++;\n active = null;\n render();\n });\n const title = el(doc, \"h3\", \"g-title\", article.title);\n const content = el(doc, \"div\", \"g-body\");\n if (loading) content.appendChild(el(doc, \"div\", \"g-rc-empty\", \"Loading…\"));\n else if (article.body) setSanitizedHtml(content, article.body);\n else content.appendChild(el(doc, \"div\", \"g-rc-empty\", \"This article couldn't be loaded.\"));\n wrap.append(back, title, content);\n body.appendChild(wrap);\n back.focus?.({ preventScroll: true });\n };\n\n const section = (label: string) => body.appendChild(el(doc, \"div\", \"g-rc-section\", label));\n const articleButton = (a: ListedArticle) => {\n const b = listItem(doc, \"📄\", a.title, a.summary);\n b.addEventListener(\"click\", () => openArticle(a));\n body.appendChild(b);\n };\n\n function render() {\n body.textContent = \"\";\n if (active) return renderArticle(active);\n const data = options.data();\n const q = query.trim().toLowerCase();\n if (q) {\n const local = data.articles.filter((a) => a.title.toLowerCase().includes(q) || a.summary.toLowerCase().includes(q));\n const seen = new Set(local.map((a) => a.id));\n const merged: ListedArticle[] = [...local, ...serverResults.filter((a) => !seen.has(a.id))];\n section(`Results (${merged.length})`);\n if (!merged.length) body.appendChild(el(doc, \"div\", \"g-rc-empty\", \"No articles match your search.\"));\n merged.forEach(articleButton);\n return;\n }\n if (data.recommended?.length) {\n section(\"Recommended\");\n data.recommended.forEach((g) => {\n const b = listItem(doc, \"▶\", g.name, \"Start guide\");\n b.addEventListener(\"click\", () => options.onStartGuide?.(g.id));\n body.appendChild(b);\n });\n }\n if (data.checklists?.length) {\n section(\"Checklists\");\n data.checklists.forEach((g) => {\n const b = listItem(doc, \"☑\", g.name, \"Open checklist\");\n b.addEventListener(\"click\", () => options.onOpenChecklist?.(g.id));\n body.appendChild(b);\n });\n }\n const collections = [...data.collections].sort((a, b) => a.order - b.order);\n const uncollected = data.articles.filter((a) => !a.collectionId || !collections.some((c) => c.id === a.collectionId));\n collections.forEach((c) => {\n const articles = data.articles.filter((a) => a.collectionId === c.id);\n if (!articles.length) return;\n section(c.name);\n articles.forEach(articleButton);\n });\n if (uncollected.length) {\n section(\"Articles\");\n uncollected.forEach(articleButton);\n }\n if (data.faqs?.length) {\n section(\"FAQ\");\n data.faqs.forEach((f) => {\n const b = listItem(doc, \"?\", f.question);\n b.setAttribute(\"aria-expanded\", \"false\");\n const a = el(doc, \"div\", \"g-faq-a\");\n setSanitizedHtml(a, f.answer);\n a.hidden = true;\n b.addEventListener(\"click\", () => {\n a.hidden = !a.hidden;\n b.setAttribute(\"aria-expanded\", String(!a.hidden));\n });\n body.append(b, a);\n });\n }\n if (data.links?.length) {\n section(\"Links\");\n data.links.forEach((l) => {\n const a = el(doc, \"a\", \"g-rc-item\");\n a.href = l.url;\n a.target = \"_blank\";\n a.rel = \"noopener noreferrer\";\n const i = el(doc, \"span\", \"g-rc-icon\", \"↗\");\n i.setAttribute(\"aria-hidden\", \"true\");\n a.append(i, el(doc, \"span\", \"g-rc-title\", l.label));\n body.appendChild(a);\n });\n }\n if (!body.childNodes.length) body.appendChild(el(doc, \"div\", \"g-rc-empty\", \"Nothing here yet.\"));\n }\n\n let debounce: ReturnType<typeof setTimeout> | null = null;\n const onInput = () => {\n query = input.value;\n openSeq++;\n active = null;\n render();\n if (debounce) clearTimeout(debounce);\n if (!options.search || !query.trim()) {\n serverResults = [];\n return;\n }\n const seq = ++searchSeq;\n debounce = setTimeout(() => {\n options\n .search?.(query.trim())\n .then((results) => {\n if (seq !== searchSeq) return;\n serverResults = Array.isArray(results) ? results : [];\n render();\n })\n .catch(() => {});\n }, 250);\n };\n input.addEventListener(\"input\", onInput);\n\n return {\n el: root,\n render,\n reset() {\n openSeq++;\n active = null;\n query = \"\";\n input.value = \"\";\n serverResults = [];\n },\n openArticle(id: string) {\n const listed = options.data().articles.find((a) => a.id === id) ?? { id, slug: id, title: \"Article\", summary: \"\", updatedAt: \"\", bodyOmitted: true };\n openArticle(listed);\n },\n focusSearch: () => input.focus?.({ preventScroll: true }),\n setQuery(q: string) {\n input.value = q;\n onInput();\n },\n destroy() {\n if (debounce) clearTimeout(debounce);\n input.removeEventListener(\"input\", onInput);\n root.remove();\n },\n };\n}\n","/** Messenger styles, injected into the Guidely shadow root the first time the messenger renders. */\nexport const MESSENGER_STYLES = `\n:host([data-g-messenger=\"bottom-right\"]) .g-launcher.g-pos-bottom-right{right:72px}\n:host([data-g-messenger=\"bottom-left\"]) .g-launcher.g-pos-bottom-left{left:72px}\n.g-m-launcher{position:fixed;z-index:calc(var(--g-z) + 1);pointer-events:auto;width:56px;height:56px;margin:16px;border-radius:999px;border:0;background:var(--g-primary);color:var(--g-primary-text);cursor:pointer;box-shadow:var(--g-shadow);display:inline-flex;align-items:center;justify-content:center;padding:0;transition:transform .15s}\n.g-m-launcher:hover{transform:scale(1.05)}\n.g-m-launcher:focus-visible{outline:2px solid var(--g-primary);outline-offset:3px}\n.g-m-launcher svg{width:26px;height:26px;fill:currentColor}\n.g-m-badge{position:absolute;top:-2px;right:-2px;min-width:20px;height:20px;padding:0 5px;border-radius:999px;background:#e11d48;color:#fff;font-size:11px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;border:2px solid var(--g-bg)}\n.g-messenger{bottom:76px;height:min(640px,calc(100vh - 110px))}\n.g-m-head{padding:16px 48px 14px 20px;background:var(--g-primary);color:var(--g-primary-text);position:relative;display:flex;align-items:center;gap:8px;min-height:56px}\n.g-m-head .g-close{color:inherit;top:50%;transform:translateY(-50%)}\n.g-m-head .g-close:hover,.g-m-head .g-close:focus-visible{background:rgba(255,255,255,.18);color:inherit}\n.g-m-head h2{margin:0;font-size:16px;font-weight:600;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.g-m-back{border:0;background:transparent;color:inherit;font:inherit;font-size:18px;cursor:pointer;width:32px;height:32px;border-radius:999px;margin-left:-8px}\n.g-m-back:hover,.g-m-back:focus-visible{background:rgba(255,255,255,.18);outline:none}\n.g-m-main{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}\n.g-m-scroll{flex:1;overflow:auto;padding:16px}\n.g-m-hero{font-size:22px;font-weight:700;margin:4px 0 4px}\n.g-m-greet{color:var(--g-muted);margin:0 0 16px}\n.g-m-card{display:block;width:100%;text-align:left;border:1px solid var(--g-border);background:var(--g-bg);color:var(--g-text);border-radius:calc(var(--g-radius) * .8);padding:14px 16px;margin:0 0 12px;font:inherit;cursor:pointer;box-shadow:0 1px 2px rgba(0,0,0,.04)}\n.g-m-card:hover{background:var(--g-subtle)}\n.g-m-card:focus-visible{outline:2px solid var(--g-primary);outline-offset:2px}\n.g-m-card-title{font-weight:600}\n.g-m-card-sub{color:var(--g-muted);font-size:13px;margin-top:2px}\n.g-m-row{display:flex;align-items:flex-start;gap:10px}\n.g-m-row .g-m-grow{flex:1;min-width:0}\n.g-m-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.g-m-time{color:var(--g-muted);font-size:12px;flex:none}\n.g-m-dot{width:8px;height:8px;border-radius:999px;background:var(--g-primary);flex:none;margin-top:6px}\n.g-m-tabs{display:flex;border-top:1px solid var(--g-border);background:var(--g-bg)}\n.g-m-tab{flex:1;border:0;background:transparent;color:var(--g-muted);font:inherit;font-size:12px;font-weight:600;padding:10px 4px 12px;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:2px}\n.g-m-tab[aria-selected=\"true\"]{color:var(--g-primary)}\n.g-m-tab:focus-visible{outline:2px solid var(--g-primary);outline-offset:-2px}\n.g-m-tab-icon{font-size:18px;line-height:1}\n.g-m-log{flex:1;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:10px}\n.g-m-msg{max-width:85%;display:flex;flex-direction:column;gap:4px}\n.g-m-msg.g-from-user{align-self:flex-end;align-items:flex-end}\n.g-m-msg.g-from-system{align-self:center;max-width:95%;text-align:center;color:var(--g-muted);font-size:12px}\n.g-m-author{font-size:12px;color:var(--g-muted);display:flex;align-items:center;gap:6px}\n.g-m-ai{font-size:10px;font-weight:700;letter-spacing:.04em;padding:1px 5px;border-radius:4px;background:var(--g-subtle);color:var(--g-muted)}\n.g-m-bubble{padding:10px 12px;border-radius:14px;background:var(--g-subtle);color:var(--g-text);overflow-wrap:anywhere}\n.g-from-user .g-m-bubble{background:var(--g-primary);color:var(--g-primary-text)}\n.g-from-user .g-m-bubble a{color:inherit}\n.g-m-bubble p{margin:0 0 8px}.g-m-bubble p:last-child{margin:0}\n.g-m-bubble ul,.g-m-bubble ol{margin:4px 0 8px;padding-left:20px}\n.g-m-bubble code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;background:color-mix(in srgb,var(--g-text) 8%,transparent);padding:1px 4px;border-radius:4px}\n.g-m-bubble a{color:var(--g-primary)}\n.g-m-failed .g-m-bubble{opacity:.7;font-style:italic}\n.g-m-typing{display:inline-flex;gap:4px;padding:4px 0}\n.g-m-typing span{width:6px;height:6px;border-radius:999px;background:var(--g-muted);animation:g-m-bounce 1s infinite}\n.g-m-typing span:nth-child(2){animation-delay:.15s}.g-m-typing span:nth-child(3){animation-delay:.3s}\n@keyframes g-m-bounce{0%,60%,100%{opacity:.3;transform:translateY(0)}30%{opacity:1;transform:translateY(-3px)}}\n@media (prefers-reduced-motion: reduce){.g-m-typing span{animation:none}.g-m-launcher{transition:none}}\n.g-m-chips{display:flex;flex-direction:column;gap:6px;margin-top:2px}\n.g-m-chip{display:flex;align-items:center;gap:8px;text-align:left;border:1px solid var(--g-border);background:var(--g-bg);color:var(--g-text);border-radius:10px;padding:8px 10px;font:inherit;font-size:13px;cursor:pointer}\n.g-m-chip:hover{background:var(--g-subtle)}\n.g-m-chip:focus-visible{outline:2px solid var(--g-primary);outline-offset:2px}\n.g-m-chip.g-m-guide{border-color:var(--g-primary);color:var(--g-primary);font-weight:600}\n.g-m-foot{border-top:1px solid var(--g-border);padding:10px 12px 12px;background:var(--g-bg)}\n.g-m-compose{display:flex;align-items:flex-end;gap:8px}\n.g-m-compose textarea{flex:1;resize:none;min-height:40px;max-height:120px;border:1px solid var(--g-border);border-radius:10px;padding:9px 10px;font:inherit;font-size:14px;color:var(--g-text);background:var(--g-bg)}\n.g-m-compose textarea:focus-visible{outline:2px solid var(--g-primary);outline-offset:1px}\n.g-m-send{width:40px;height:40px;border-radius:999px;border:0;background:var(--g-primary);color:var(--g-primary-text);cursor:pointer;font-size:18px;flex:none}\n.g-m-send:disabled{opacity:.45;cursor:not-allowed}\n.g-m-send:focus-visible{outline:2px solid var(--g-primary);outline-offset:2px}\n.g-m-human{border:0;background:transparent;color:var(--g-muted);font:inherit;font-size:12px;cursor:pointer;padding:6px 2px 0;text-decoration:underline}\n.g-m-human:hover{color:var(--g-text)}\n.g-m-rate{text-align:center;padding:4px 0 2px;font-size:13px;color:var(--g-muted)}\n.g-m-rate button{border:0;background:transparent;font-size:22px;cursor:pointer;padding:4px 8px;border-radius:8px}\n.g-m-rate button:hover,.g-m-rate button:focus-visible{background:var(--g-subtle);outline:none}\n.g-m-note{color:var(--g-muted);font-size:12px;margin-top:6px}\n.g-m-empty{color:var(--g-muted);text-align:center;padding:24px 8px}\n`;\n","/**\n * Renders untrusted chat text (Scout replies, user and teammate messages) with createElement and\n * textContent only. Supports paragraphs, line breaks, `- ` / `1. ` lists, **bold**, `code` and\n * http(s) links. Never creates images, raw HTML or non-http links, so a prompt-injected reply can't\n * load remote resources or run script.\n */\n\nconst CITATION = /\\s*\\[\\[article:[^\\]]*\\]\\]/g;\n// Inline tokens: **bold**, `code`, [label](url), bare https?:// URLs.\nconst INLINE = /\\*\\*([^*\\n]+)\\*\\*|`([^`\\n]+)`|(!?)\\[([^\\]\\n]{1,200})\\]\\((https?:\\/\\/[^\\s)]+)\\)|(https?:\\/\\/[^\\s<>()\"']+[^\\s<>().,;:!?'\"])/g;\n\n/** Removes `[[article:ID]]` markers (citations are rendered from structured data instead). */\nexport function stripCitationMarkers(text: string): string {\n return text.replace(CITATION, \"\");\n}\n\nexport function safeHttpUrl(raw: string): string | null {\n try {\n const u = new URL(raw);\n return u.protocol === \"http:\" || u.protocol === \"https:\" ? u.href : null;\n } catch {\n return null;\n }\n}\n\nfunction link(doc: Document, label: string, href: string): Node {\n const url = safeHttpUrl(href);\n if (!url) return doc.createTextNode(label);\n const a = doc.createElement(\"a\");\n a.href = url;\n a.target = \"_blank\";\n a.rel = \"noopener noreferrer nofollow\";\n a.textContent = label;\n return a;\n}\n\nfunction appendInline(doc: Document, parent: Node, text: string) {\n let last = 0;\n for (const m of text.matchAll(INLINE)) {\n const start = m.index ?? 0;\n if (start > last) parent.appendChild(doc.createTextNode(text.slice(last, start)));\n if (m[1] !== undefined) {\n const b = doc.createElement(\"strong\");\n b.textContent = m[1];\n parent.appendChild(b);\n } else if (m[2] !== undefined) {\n const c = doc.createElement(\"code\");\n c.textContent = m[2];\n parent.appendChild(c);\n } else if (m[4] !== undefined && m[5] !== undefined) {\n // Markdown images (![alt](url)) are never loaded or linked: keep the alt text only.\n parent.appendChild(m[3] ? doc.createTextNode(m[4]) : link(doc, m[4], m[5]));\n } else if (m[6] !== undefined) {\n parent.appendChild(link(doc, m[6], m[6]));\n }\n last = start + m[0].length;\n }\n if (last < text.length) parent.appendChild(doc.createTextNode(text.slice(last)));\n}\n\nconst BULLET = /^\\s*[-*•]\\s+/;\nconst NUMBERED = /^\\s*\\d{1,3}[.)]\\s+/;\n\n/** Builds a fragment for `text`. Safe for any input. */\nexport function renderSafeText(doc: Document, text: string): DocumentFragment {\n const frag = doc.createDocumentFragment();\n const blocks = stripCitationMarkers(text).replace(/\\r\\n?/g, \"\\n\").trim().split(/\\n{2,}/);\n for (const block of blocks) {\n if (!block.trim()) continue;\n const lines = block.split(\"\\n\");\n if (lines.every((l) => BULLET.test(l)) || lines.every((l) => NUMBERED.test(l))) {\n const ordered = NUMBERED.test(lines[0]!);\n const list = doc.createElement(ordered ? \"ol\" : \"ul\");\n for (const line of lines) {\n const li = doc.createElement(\"li\");\n appendInline(doc, li, line.replace(ordered ? NUMBERED : BULLET, \"\"));\n list.appendChild(li);\n }\n frag.appendChild(list);\n continue;\n }\n const p = doc.createElement(\"p\");\n lines.forEach((line, i) => {\n if (i) p.appendChild(doc.createElement(\"br\"));\n appendInline(doc, p, line);\n });\n frag.appendChild(p);\n }\n return frag;\n}\n"],"mappings":"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","names":["el","el"]}
@@ -0,0 +1,61 @@
1
+ import { MessengerConversation, ConversationMessagesResponse, MessengerMessage, SdkArticlesResponse, ResourceCenterArticle, MessengerConfig, ResourceCenterConfig } from '@guidelytech/shared';
2
+ import { H as HelpGuideRef } from './help-view-B4oPbhpO.cjs';
3
+
4
+ /** Callbacks the transport invokes while a message is sent and Scout replies. */
5
+ interface MessengerStreamHandlers {
6
+ /** A new conversation was created (first message only). */
7
+ onConversation?(conversation: MessengerConversation): void;
8
+ onMessage(message: MessengerMessage): void;
9
+ onDelta(messageId: string, text: string): void;
10
+ onDone(message: MessengerMessage | null, conversation: MessengerConversation): void;
11
+ onError(message: string): void;
12
+ }
13
+ /** Server access for the messenger; implemented by sdk-core over the public SDK API. */
14
+ interface MessengerApi {
15
+ listConversations(): Promise<MessengerConversation[]>;
16
+ loadMessages(conversationId: string, since?: string | null): Promise<ConversationMessagesResponse>;
17
+ start(text: string, handlers: MessengerStreamHandlers): Promise<void>;
18
+ send(conversationId: string, text: string, handlers: MessengerStreamHandlers): Promise<void>;
19
+ handoff(conversationId: string): Promise<ConversationMessagesResponse>;
20
+ rate(conversationId: string, rating: number): Promise<void>;
21
+ listArticles(): Promise<SdkArticlesResponse>;
22
+ getArticle(id: string): Promise<ResourceCenterArticle | null>;
23
+ searchArticles(q: string): Promise<ResourceCenterArticle[]>;
24
+ }
25
+ type MessengerTab = "home" | "messages" | "help";
26
+ interface MessengerOptions {
27
+ scope: HTMLElement;
28
+ config: MessengerConfig;
29
+ api: MessengerApi;
30
+ /** Resource center extras (recommended guides, checklists, FAQs, links) shown in the Help tab. */
31
+ resourceCenter?: ResourceCenterConfig | null;
32
+ guides?: HelpGuideRef[];
33
+ onStartGuide?: (guideKeyOrId: string) => void;
34
+ onOpenChecklist?: (guideId: string) => void;
35
+ onArticleView?: (article: ResourceCenterArticle) => void;
36
+ onOpen?: () => void;
37
+ onClose?: () => void;
38
+ onMessage?: (message: MessengerMessage, conversation: MessengerConversation) => void;
39
+ hideLauncher?: boolean;
40
+ /** Poll intervals (ms): open thread, and unread badge while closed. */
41
+ pollMs?: {
42
+ thread: number;
43
+ background: number;
44
+ };
45
+ }
46
+ interface MessengerWidget {
47
+ el: HTMLElement;
48
+ open(tab?: MessengerTab): void;
49
+ close(): void;
50
+ toggle(): void;
51
+ isOpen(): boolean;
52
+ /** Opens a new conversation, optionally pre-filling the composer. */
53
+ showNewMessage(text?: string): void;
54
+ openArticle(id: string): void;
55
+ destroy(): void;
56
+ }
57
+ declare function relativeTime(iso: string, now?: number): string;
58
+ /** Intercom-style messenger: launcher + panel with Home, Messages (chat with Scout / the team) and Help tabs. */
59
+ declare function renderMessenger(options: MessengerOptions): MessengerWidget;
60
+
61
+ export { type MessengerApi, type MessengerOptions, type MessengerStreamHandlers, type MessengerTab, type MessengerWidget, relativeTime, renderMessenger };
@@ -0,0 +1,61 @@
1
+ import { MessengerConversation, ConversationMessagesResponse, MessengerMessage, SdkArticlesResponse, ResourceCenterArticle, MessengerConfig, ResourceCenterConfig } from '@guidelytech/shared';
2
+ import { H as HelpGuideRef } from './help-view-B4oPbhpO.js';
3
+
4
+ /** Callbacks the transport invokes while a message is sent and Scout replies. */
5
+ interface MessengerStreamHandlers {
6
+ /** A new conversation was created (first message only). */
7
+ onConversation?(conversation: MessengerConversation): void;
8
+ onMessage(message: MessengerMessage): void;
9
+ onDelta(messageId: string, text: string): void;
10
+ onDone(message: MessengerMessage | null, conversation: MessengerConversation): void;
11
+ onError(message: string): void;
12
+ }
13
+ /** Server access for the messenger; implemented by sdk-core over the public SDK API. */
14
+ interface MessengerApi {
15
+ listConversations(): Promise<MessengerConversation[]>;
16
+ loadMessages(conversationId: string, since?: string | null): Promise<ConversationMessagesResponse>;
17
+ start(text: string, handlers: MessengerStreamHandlers): Promise<void>;
18
+ send(conversationId: string, text: string, handlers: MessengerStreamHandlers): Promise<void>;
19
+ handoff(conversationId: string): Promise<ConversationMessagesResponse>;
20
+ rate(conversationId: string, rating: number): Promise<void>;
21
+ listArticles(): Promise<SdkArticlesResponse>;
22
+ getArticle(id: string): Promise<ResourceCenterArticle | null>;
23
+ searchArticles(q: string): Promise<ResourceCenterArticle[]>;
24
+ }
25
+ type MessengerTab = "home" | "messages" | "help";
26
+ interface MessengerOptions {
27
+ scope: HTMLElement;
28
+ config: MessengerConfig;
29
+ api: MessengerApi;
30
+ /** Resource center extras (recommended guides, checklists, FAQs, links) shown in the Help tab. */
31
+ resourceCenter?: ResourceCenterConfig | null;
32
+ guides?: HelpGuideRef[];
33
+ onStartGuide?: (guideKeyOrId: string) => void;
34
+ onOpenChecklist?: (guideId: string) => void;
35
+ onArticleView?: (article: ResourceCenterArticle) => void;
36
+ onOpen?: () => void;
37
+ onClose?: () => void;
38
+ onMessage?: (message: MessengerMessage, conversation: MessengerConversation) => void;
39
+ hideLauncher?: boolean;
40
+ /** Poll intervals (ms): open thread, and unread badge while closed. */
41
+ pollMs?: {
42
+ thread: number;
43
+ background: number;
44
+ };
45
+ }
46
+ interface MessengerWidget {
47
+ el: HTMLElement;
48
+ open(tab?: MessengerTab): void;
49
+ close(): void;
50
+ toggle(): void;
51
+ isOpen(): boolean;
52
+ /** Opens a new conversation, optionally pre-filling the composer. */
53
+ showNewMessage(text?: string): void;
54
+ openArticle(id: string): void;
55
+ destroy(): void;
56
+ }
57
+ declare function relativeTime(iso: string, now?: number): string;
58
+ /** Intercom-style messenger: launcher + panel with Home, Messages (chat with Scout / the team) and Help tabs. */
59
+ declare function renderMessenger(options: MessengerOptions): MessengerWidget;
60
+
61
+ export { type MessengerApi, type MessengerOptions, type MessengerStreamHandlers, type MessengerTab, type MessengerWidget, relativeTime, renderMessenger };