# Chat widget verification and troubleshooting Read this reference before styling chat in any site build or restyle, and when a user asks to add, verify, or debug the answer.cloud chat/search widget. The widget and its API are platform-owned; site authors supply answerable static content. ## Verification flow 1. Run `list_sites` and identify the exact site, kind, live URL, current deploy, and page usage. 2. Run `list_deploys`; confirm the intended deploy is `live`. 3. Open the live URL. Do not use the local Astro preview to judge widget injection. 4. Confirm the page contains substantive HTML and the injected widget launcher. 5. Open the widget and submit one short, non-sensitive question grounded in the page whenever live chat verification is in scope, including every first launch. 6. Confirm the response renders, relevant references use the site's canonical host, and the browser reports no failed chat request. Do not add `https://app.answer.cloud/embed.js` to Astro source. The platform injects it before `` during publishing. ## Brand-matched styling workflow For a site build or visual restyle, create a chat theme by default unless the user explicitly requests the stock theme or disables chat. For a new unbranded site, begin with the official answer.cloud style template's `public/assets/answercloud-chat.css`. Treat it as a starting point: older copies may style the launcher wrapper without its label, or put borders and backgrounds around legacy message spacing. Apply the quality requirements below before shipping it. 1. Read the site's global style sources and inspect representative rendered components. Extract the fonts, surfaces, text colors, accent and on-accent colors, border, focus ring, radii, shadow, and motion character actually in use. Do not invent a second brand system. 2. Write the theme to `public/assets/answercloud-chat.css`. Astro must emit it unchanged at `dist/assets/answercloud-chat.css`. 3. Scope every selector under `#llmo-chat-button`, `#llmo-chat-iframe`, or `#llmo-chat-widget`. The same URL loads into the customer's page and the iframe, so unscoped rules such as `:root`, `html`, `body`, `*`, `button`, `a`, or generic utility classes can damage the host site. 4. Exercise all meaningful states: launcher default/hover/focus, desktop/mobile frame, empty chat, user and AI messages, long Markdown, links and references, composer focus, send/close/ clear controls, loader, errors, and reduced motion. 5. Build first and verify the asset exists. For a `byo_cms` artifact, update the package script so `assets/answercloud-chat.css` ships next to `assets/aeo-cms.css`. 6. A new hosting site’s first production deploy automatically attaches this asset when no CSS URL is configured. Existing deployed sites and explicit themes are preserved. Once an authorized deploy is live, inspect `list_sites`, verify the canonical HTTPS asset, and use `configure_widget` only when needed for theme or copy. Never point live configuration at localhost, a preview server, or a path that has not deployed yet. The supported stable hooks are: - Host page: `#llmo-chat-button` is a wrapper containing `.llmo-chat-button-icon` and a read-only input, `.llmo-chat-button-input`. The visible label is that input's `::placeholder`, not text on the wrapper. `#llmo-chat-iframe` is the outer panel frame. - Chat iframe: `#llmo-chat-widget`, `.llmo-chat-popup`, `.llmo-messages`, `.llmo-message.llmo-user`, `.llmo-message.llmo-ai`, `.llmo-markdown`, `.llmo-message-references`, `.composer-wrap`, `.composer`, `.chat_textarea`, `.chat_send`, `.llmo-close`, `.llmo-clear-chat`, `.llmo-loader`, and `.ai-icon`. - Inside the iframe, `.llmo-messages` is the outer scroll region; `#llmo-messages` is the inner list of messages. They are different elements. The composer `.chat_textarea` is a `contenteditable` div with a `:empty::before` placeholder, not a native textarea. Target these real hooks; guessed selectors such as `header` or `.assistant-message` may match nothing. - Legacy theme variables on `#llmo-chat-widget` remain supported: `--bg`, `--ink`, `--ink-mid`, `--muted`, `--panel`, `--ring`, `--ring-soft`, `--brand`, `--accent`, `--accent-deep`, `--accent-soft`, `--accent-tint`, `--accent-tint-2`, `--accent-line`, `--radius`, `--ease-brand`, `--font-sans`, and `--font-mono`. Use the shared properties below on all three roots. The launcher and iframe are separate HTML documents, so declaring tokens only on the panel does not style the launcher. Host-page variables and font declarations do not cross into the iframe: provide resolved values or define every referenced variable in this standalone theme. Replace these neutral example values with the site's actual palette; any color is supported. ```css #llmo-chat-button, #llmo-chat-iframe, #llmo-chat-widget { --ac-chat-font-family: "Site Sans", system-ui, sans-serif; --ac-chat-font-size: 16px; --ac-chat-surface: #ffffff; --ac-chat-text: #1a1b1c; --ac-chat-secondary-text: #3a3d40; --ac-chat-muted: #667085; --ac-chat-panel: #f6f5f4; --ac-chat-border: #d9d9d9; --ac-chat-accent: #1a1b1c; --ac-chat-on-accent: #ffffff; --ac-chat-focus-ring: #1a1b1c; --ac-chat-launcher-text: #1a1b1c; --ac-chat-icon-color: #1a1b1c; --ac-chat-user-surface: #f0f1f2; --ac-chat-assistant-surface: transparent; --ac-chat-input-surface: #ffffff; --ac-chat-radius: 8px; } #llmo-chat-iframe { --ac-chat-frame-width: min(420px, calc(100vw - 48px)); --ac-chat-frame-height: min(640px, calc(100dvh - 48px)); --ac-chat-mobile-width: calc(100vw - 16px); --ac-chat-mobile-height: min(640px, calc(100dvh - 16px)); --ac-chat-frame-padding: 0px; --ac-chat-frame-shadow: 0 16px 48px rgb(26 27 28 / 16%); } ``` Use `--ac-chat-icon-color` for the supplied Answer Cloud spark, `--ac-chat-link-color` for answer links, `--ac-chat-user-surface` / `--ac-chat-assistant-surface` for message backgrounds, and `--ac-chat-send-background` / `--ac-chat-send-color` for the send button. Other optional properties control `launcher-text`, `launcher-radius`, `launcher-width`, `launcher-shadow`, `mobile-width`, `mobile-height`, `offset-bottom`, `offset-right`, `message-radius`, `code-surface`, `input-surface`, `control-surface`, `send-shadow`, and `loader-shine` (all prefixed `--ac-chat-`). Frame geometry uses these properties directly, so token changes do not need `!important`. Keep custom fonts available inside the iframe as well as the host page. Theme loading is nonblocking, with a brief bounded first-paint hold for fast stylesheets. Slow or failed themes fall back to usable stock chat; a late stylesheet can apply without restarting the conversation. Existing selectors and legacy variables remain valid; migrate a working older theme only when requested. Check stock chat and an existing custom theme alongside the new palette. ## Chat design requirements Use a calm, readable conversation surface with compact controls and restrained brand accents. Carry the site's font, palette, border treatment, and radii into chat without copying decorative hero or card styles into every message. A themed asset that loads successfully is not sufficient. ### Launcher: one surface with a visible invitation - Use a short, explicit label such as "Ask Example" or "Ask a question". Keep it visible beside the spark in default, hover, and focus states. Do not ship a blank field or an unexplained icon in place of the label unless the user explicitly asks for an icon-only launcher. - Style `.llmo-chat-button-input`, its `::placeholder`, and the icon as a unit. Set `--ac-chat-launcher-text` and `button_text_color` against the **input's actual background**; white text on a white input disappears even when the wrapper is brightly colored. Keep placeholder opacity at 1. Verify the computed colors, not just the configured hex values. - Prefer a neutral input surface, subtle border, and one soft floating shadow. Keep the wrapper transparent or the same surface; remove old accent fills, thick borders, and redundant shadows that create a heavy frame around the input. Reserve a strong outline for keyboard focus. - Keep the control at least 44px tall and within the viewport, including its padding and page offset. Preserve room for the icon and label at 320px width. Do not override the platform's open/close `display`, `opacity`, `transform`, or pointer-event state with `!important`. ### Panel: compact conversation layout - Use one continuous panel. Default to left-aligned assistant text on its surface and compact, right-aligned user bubbles. Avoid full-width bordered cards around each answer or greeting, repeated dividers, oversized avatars, and decorative empty space. - Use roughly 16px body text, 1.5–1.65 line height, 16–20px content insets, and 12–16px between messages. Keep the greeting to one or two useful sentences. A short reply should occupy only the height its content needs; never set a minimum message height. - Normalize legacy layout when restyling messages: stock assistant rules include `margin-top: -92px` and `padding-top: 112px`, and user messages float with a `54px` top margin. Adding a background or border without resetting those rules produces tall, overlapping cards. Use a normal flow or column flex list and reset **both** roles together. For example: ```css #llmo-chat-widget #llmo-messages { display: flex; flex-direction: column; gap: 16px; min-width: 0; } #llmo-chat-widget .llmo-message.llmo-user, #llmo-chat-widget .llmo-message.llmo-ai { float: none; position: static; margin: 0; min-width: 0; max-width: 100%; padding: 0; } #llmo-chat-widget .llmo-message.llmo-user { align-self: flex-end; max-width: 85%; padding: 8px 12px; } #llmo-chat-widget .llmo-message.llmo-ai { align-self: stretch; background: transparent; border: 0; box-shadow: none; } ``` Replace conflicting inherited theme rules, including old `!important` declarations, rather than layering this example under them. Prefer tokens for colors and scoped rules for layout. - Keep clear/close controls in a reserved top area, with at least 44px hit areas. Messages must not run behind them. Keep the Answer Cloud attribution visible, but do not apply answer-link underlines or card decoration to `.ai-icon`; scope prose links to `.llmo-markdown` and references. - Wrap long words and URLs; constrain images to the message width. Wide tables and code blocks may scroll within the answer, but must not widen the panel or create page-level scrolling. ### Composer: always available - Keep the composer visible at the panel bottom while **only the transcript scrolls**. Prefer `#llmo-chat-widget .llmo-chat-popup { grid-template-rows: minmax(0, 1fr) auto; }`, a scroll region with `min-height: 0`, and a composer in normal flow (`position: relative` instead of the stock absolute positioning). Reserve top space for controls and bottom safe-area padding. If keeping an overlay composer, reserve its actual expanded height so it cannot cover the last answer. - Show the "Ask a question…" placeholder, typed text, caret, focus ring, and send icon clearly. Style `.chat_textarea` and its `:empty::before` placeholder; native textarea rules alone do nothing here. Use at least 16px input text, a comfortable initial height around 52px, and a bounded multiline height with internal scrolling. Leave room for a 44px send target. - Keep send, close, and clear controls visible and distinct in default, focus, disabled, and loading states. Preserve keyboard behavior: Enter sends, Shift+Enter inserts a newline, and focus reaches the input and controls. Do not hide failure text or loading feedback with CSS. - Check text and placeholder contrast against their own surfaces (at least 4.5:1 for normal text), and control icons/focus indicators (at least 3:1). Check dark mode only if supported by the site, and honor reduced motion. Do not use a faint translucent accent as the only focus cue. ### Required visual and interaction check After an authorized deployment, inspect the **actual injected launcher and iframe** at a desktop viewport and at 320px and 390px mobile widths, including a short viewport or open software keyboard. Local Astro pages and HTTP success alone cannot verify chat. Capture and inspect the closed launcher, empty open chat, a completed exchange, and a long/scrolling exchange; do not send private content. Exercise loading, failure, and long Markdown with controlled local fixtures if needed, without generating excessive live requests. Check that: - The launcher invitation is readable and has no unintended colored frame. - Short messages have compact spacing; controls, attribution, references, and answers do not overlap. - The composer and send control remain reachable before and after sending, with long content, multiline input, and a reduced viewport. The latest answer can scroll fully above the composer. - Keyboard focus is visible; Enter, Shift+Enter, clear, close, and reopen work. The host page retains its original layout and controls. Fix visual failures before calling the chat verified. If browser or mobile-keyboard testing is unavailable, report that specific gap; do not claim it passed. For local-only work, validate the theme against the platform markup in a temporary fixture and report live verification as pending; do not add widget markup or scripts to the site's production source or deploy solely to test it. ## Configure the live theme Configure after the stylesheet is live: ```json { "site_slug": "example", "enabled": true, "config": { "css_path": "https://example.answer.cloud/assets/answercloud-chat.css", "chat": { "button_text": "Ask Example", "intro_text": "What can we help you find?", "button_text_color": "#1a1b1c" } } } ``` `config` replaces the stored widget configuration when supplied; `{}` resets custom settings. Omit `config` to toggle `enabled` without losing an existing theme. Text must be concise and site-specific. `button_text_color` must be a readable hex color. After configuration, inspect the canonical live page at desktop and mobile widths. Confirm the CSS URL returns successfully, `list_sites` shows it under `widget.config.css_path`, the launcher is styled in the parent page, the panel is styled inside the iframe, focus is visible, text meets contrast expectations, content does not clip, and the host page outside the widget is unchanged. ## Identify the request path The widget iframe URL contains the platform organization and site identifiers. Treat session identifiers and chat history as private; do not quote or persist them. The widget normally makes two independent requests: - `POST https://app.answer.cloud/api/messages/get_pages` retrieves relevant indexed pages. - `POST https://app.answer.cloud/api/messages` creates the conversation and generates the answer. Use the same short, non-sensitive question for both checks. Prefer the live widget first. When a direct HTTP diagnostic is necessary, send only the minimum JSON fields and do not include API keys, cookies, personal information, or copied chat history. ## HTTP fallback for a meaningful chat check Prefer a conversation through the live widget. If browser interaction is unavailable but HTTP requests are authorized, exercise the public message API with a short, non-sensitive question whose answer is present on this deployed site: ```text POST https://app.answer.cloud/api/messages Content-Type: application/json ``` ```json { "organization_id": 123, "site_origin": "https://reserved-site.answer.cloud", "content": "What services does this organization offer?" } ``` Replace `123` with the bootstrap's actual numeric `organization_id`, `site_origin` with its actual reserved `site_url`, and the question with one appropriate to the published content. Preserve an explicit test application's origin rather than switching environments. `site_origin` scopes hosted-site retrieval and reference URLs; an organization id alone must not be used to assume the right site. If the live widget supplies a `site_id`, preserve that real id as well rather than guessing one. This endpoint is public and does not use the guest MCP credential. Do not send a build Bearer token, API key, cookies, account email, personal information, or a custom system prompt. A successful create returns HTTP 201 with JSON `conversation`, `user_message`, `ai_message`, and `references`. Check the status and content type before parsing. Verify that `ai_message.content` is substantive and agrees with the actual site's facts; a generic greeting, nonempty error text, or HTTP success alone does not prove a useful answer. Check any cited URLs against the intended live site. For a follow-up, retain the returned `conversation.id` privately in session memory and send it as `conversation_id` with the same `organization_id` and `site_origin`, plus the follow-up `content`. Do not include private conversations, raw responses, or session identifiers in logs, project files, screenshots, or downloadable artifacts. The test creates an ordinary stored site conversation, so keep its content non-sensitive. An optional retrieval diagnostic uses the same `organization_id`, `site_origin`, and `content` JSON at `POST https://app.answer.cloud/api/messages/get_pages`; it returns `{ "titles": [...] }`. Relevant titles do not establish that answer generation works. An API conversation verifies message generation only. Report widget rendering, controls, and any unavailable visual checks as unverified; do not describe this fallback as a successful browser test. If no meaningful chat interaction can be completed, retain the working site URL and state precisely what remains unresolved. ## Interpret failures | Observation | Likely layer | Next action | | --- | --- | --- | | No widget launcher on a live page | Platform injection or stale deploy | Confirm the current deploy, valid HTML with ``, and live hostname. Do not add the script manually. | | Theme is absent everywhere | Missing/bad `css_path` or asset | Confirm `list_sites`, fetch the absolute CSS URL, and verify the file shipped in the live deploy. | | Iframe is themed but launcher is not | Stale `embed.js` or unscoped selector mismatch | Confirm the current embed loads the theme in the parent page and that launcher selectors start with `#llmo-chat-button` or `#llmo-chat-iframe`. | | Host website changes after theming | Leaking custom CSS | Remove unscoped selectors; every rule must begin with a supported widget ID. | | `get_pages` fails | Ingestion, embeddings, or retrieval | Inspect deploy warnings, page ingestion, and whether published pages have embeddings. | | `get_pages` succeeds with relevant titles, but `/api/messages` returns 500 | Chat-generation backend | Capture the request ID and response type; investigate the platform application. A site redeploy is not a fix. | | `/api/messages` succeeds, but the widget shows a generic error | Widget parsing or rendering | Inspect the response shape and browser error. Confirm the endpoint returned JSON. | | Answer renders but cites the wrong host | Canonical URL construction | Verify hosted pages use `Site#canonical_url`; verify companion pages use the configured `ai.` host. | | Answer has no useful context | Content or retrieval quality | Confirm the page is ingested, answerable in raw HTML, and retrieved by `get_pages`. | An HTML 500 page from `/api/messages` is especially important: the widget expects JSON, so attempting to parse HTML usually collapses to “Sorry, something went wrong.” Record the `x-request-id`, but do not mistake this client fallback for the root cause. ## Platform-source checks Only inspect or change the answer.cloud application repository when the user has put that backend in scope. Otherwise report the cross-repository defect and the evidence needed by the platform owner. For hosted `hosting` sites: - Public page and reference URLs must derive from `page.site.canonical_url`. - A hosted site at `{slug}.answer.cloud` must not require `Organization#subdomain` or `Organization#custom_domain`. - Normalize `source_path` to its pretty public URL. For example, `index.html` becomes `/`, and `guides/index.html` becomes `/guides/`. For `byo_cms` companion sites: - Canonical URLs should resolve through the site's `ai.{custom_domain}` host. - Missing organization domain configuration is a real provisioning error; do not silently replace it with a new `hosting` site. When fixing canonical URL handling, add regression coverage for: 1. A standalone hosted site with no custom domain. 2. A standalone hosted site with a custom domain. 3. A `byo_cms` companion site. 4. Root and nested `index.html` source paths. 5. Chat references and generated `.md` mirror URLs. Also make the widget's message client check `response.ok` and content type before parsing JSON so future server failures preserve useful diagnostics instead of surfacing only a generic parsing error. ## Completion gate Do not call chat fixed until all of these are true: - Retrieval returns the expected deployed page title. - Message generation returns a JSON success response. - The live widget renders the answer. - References use the canonical live hostname. - No chat request returns 4xx/5xx. - The launcher and iframe match the site's visual system at desktop and mobile widths. - The required visual and interaction check above passes, including readable launcher text, compact messages, and a reachable composer after scrolling and resizing. - The custom stylesheet causes no style changes outside the widget. - The evidence comes from the live site, not only a local unit test. Workflow version: launch-f4679309a5dc7dc7