Skip to content

Embeddable support chat widget improvements#3522

Merged
steven-tey merged 34 commits intomainfrom
embeddable-support-chat-widget-improvements
Mar 3, 2026
Merged

Embeddable support chat widget improvements#3522
steven-tey merged 34 commits intomainfrom
embeddable-support-chat-widget-improvements

Conversation

@pepeladeira
Copy link
Collaborator

@pepeladeira pepeladeira commented Mar 3, 2026

Summary by CodeRabbit

  • New Features

    • Clear chat control to restart conversations
    • Multi-account / workspace selector and explicit session flow
    • Syntax-highlighted code blocks with Copy & Download
    • Source citations panel and live status indicators
  • Improvements

    • AI answers now grounded in retrieved docs; refined fallback offering support ticket creation
    • Richer markdown rendering, improved chat layout and remount-on-reset behavior
    • Avatars now optional to avoid empty image placeholders

pepeladeira and others added 30 commits February 27, 2026 22:32
@vercel
Copy link
Contributor

vercel bot commented Mar 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dub Ready Ready Preview Mar 3, 2026 9:33pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 3, 2026

📝 Walkthrough

Walkthrough

Requires that the assistant always calls findRelevantDocs and ground answers in those results; removes context props from embedded and bubble chat components and adds reset/clear behavior; adds code block rendering, source citation extraction/display, status indicators, and combobox popover width fixes.

Changes

Cohort / File(s) Summary
System Prompt
apps/web/lib/ai/build-system-prompt.ts
Expanded BASE_SYSTEM_PROMPT to require ALWAYS calling findRelevantDocs before answering, forbid answering from memory, change fallback to detect "no useful results" and offer to create a support ticket; removed console.log side-effect.
Chat Shell / Embeds
apps/web/ui/support/chat-bubble.tsx, apps/web/ui/support/embedded-chat.tsx, apps/web/app/.../support-chat/page.tsx
Removed context prop from public signatures; added local resetKey state, Clear chat (Trash) action with Tooltip, and remounting of ChatInterface via key={resetKey}; page no longer passes context into embed components.
Chat Interface / Flow
apps/web/ui/support/chat-interface.tsx
Major refactor: new account-type / workspace / program Combobox flow, explicit accountType/chatLocation in payloads, richer Streamdown rendering (code blocks, lists, links), StatusIndicator during tool calls, ticket submission/session reset flow, and new onReset?: () => void prop.
Rendering Primitives
apps/web/ui/support/code-block.tsx, apps/web/ui/support/source-citations.tsx, apps/web/ui/support/status-indicator.tsx
Added CodeBlock and MarkdownCodeBlock (Shiki highlighting, copy/download), SourceCitations + extractSources (collect/dedupe tool docs), and StatusIndicator component for transient labels.
Message & UI tweaks
apps/web/ui/support/message.tsx, apps/web/ui/support/program-combobox.tsx, apps/web/ui/support/workspace-combobox.tsx
Made avatar optional and conditionally rendered; changed avatar alt text to "avatar"; added popoverProps to Comboboxes to align popover width with triggers (styling/UI-only).

Sequence Diagram

sequenceDiagram
    participant User
    participant ChatBubble
    participant ChatInterface
    participant ChatEngine
    participant FindDocs as findRelevantDocs
    participant SourceCitations

    User->>ChatBubble: open chat / send message
    ChatBubble->>ChatInterface: render (key=resetKey) / forward message
    ChatInterface->>ChatEngine: submit user message + accountType/chatLocation
    ChatEngine->>FindDocs: call findRelevantDocs(query)
    FindDocs-->>ChatEngine: return docs + metadata
    ChatEngine->>SourceCitations: extractSources(parts)
    SourceCitations-->>ChatEngine: return deduped source list
    ChatEngine->>ChatInterface: response (grounded in docs) + citations + code blocks
    ChatInterface->>User: display response with citations and code blocks
    User->>ChatBubble: click Clear chat (Trash)
    ChatBubble->>ChatInterface: increment resetKey -> remount interface
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested Reviewers

  • steven-tey

Poem

🐰 I hopped through prompts and fetched each doc,
I nudged the chat, I cleared the clock.
Code gleams, sources shine, the status hums—
A tidy reset, and onward it runs. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Embeddable support chat widget improvements' accurately captures the main focus of the pull request, which involves refactoring the support chat components to remove context dependencies and improve widget functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch embeddable-support-chat-widget-improvements

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/lib/ai/build-system-prompt.ts (1)

69-69: ⚠️ Potential issue | 🟡 Minor

Remove debug console.log before merging.

This logs the entire system prompt including workspace/program identifiers (id, name, slug). This appears to be a development artifact that should be removed to avoid polluting server logs and leaking prompt internals.

Proposed fix
-  console.log(systemPrompt);
-
   return systemPrompt;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/lib/ai/build-system-prompt.ts` at line 69, Remove the debug
console.log that prints the system prompt to avoid leaking identifiers;
specifically delete the console.log(systemPrompt) call in the
build-system-prompt module (where systemPrompt is created/returned, e.g., in
buildSystemPrompt) so the prompt contents (id, name, slug) are no longer emitted
to server logs.
🧹 Nitpick comments (4)
apps/web/ui/support/message.tsx (1)

26-33: Use a more accessible avatar alt strategy.

The conditional render is great, but alt="avatar" is generic. Prefer alt="" for decorative avatars, or a contextual value (e.g., sender role/name) when meaningful.

Proposed tweak
-          alt="avatar"
+          alt=""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/ui/support/message.tsx` around lines 26 - 33, The avatar <img>
currently uses a generic alt="avatar"; update it to be accessible by making the
alt attribute conditional: use alt="" for decorative avatars (when no sender
information is available) and use a contextual label (e.g., the sender's name or
role) when that data exists (for example from a prop like senderName or
sender.role). Locate the <img> in the Message component in
apps/web/ui/support/message.tsx (the element using props avatar and isUser) and
change the alt to derive from sender info if present, otherwise set it to an
empty string.
apps/web/ui/support/code-block.tsx (1)

90-97: Add explicit accessible names for icon-only buttons.

title helps, but aria-label is more reliable for assistive tech.

♿ Suggested update
     <button
       type="button"
       onClick={handleCopy}
       className="rounded p-1 text-neutral-400 transition-colors hover:bg-neutral-100 hover:text-neutral-600"
       title={copied ? "Copied!" : "Copy code"}
+      aria-label={copied ? "Copied" : "Copy code"}
     >
@@
     <button
       type="button"
       onClick={handleDownload}
       className="rounded p-1 text-neutral-400 transition-colors hover:bg-neutral-100 hover:text-neutral-600"
       title="Download file"
+      aria-label="Download code"
     >

Also applies to: 122-129

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/ui/support/code-block.tsx` around lines 90 - 97, The icon-only copy
buttons in code-block.tsx (the button using handleCopy and rendering {copied ?
<Check .../> : <Copy .../>}) rely on title but need explicit aria-labels for
assistive tech; update both button elements (the one around the handleCopy/copy
icon and the similar one at lines ~122-129) to include aria-label values that
reflect state (e.g., aria-label={copied ? "Copied" : "Copy code"}) so screen
readers get a clear, dynamic name while keeping the existing title and onClick
behavior.
apps/web/ui/support/status-indicator.tsx (1)

12-19: Add live-region semantics for dynamic status text.

Since this label updates during async operations, screen readers may not announce updates reliably without role="status" and aria-live.

♿ Suggested improvement
   return (
     <span
+      role="status"
+      aria-live="polite"
+      aria-atomic="true"
       className={cn(
         "animate-pulse text-xs text-neutral-400",
         className,
       )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/ui/support/status-indicator.tsx` around lines 12 - 19, The span
rendering the dynamic status label in StatusIndicator (the span using className,
cn and showing {label}) needs live-region semantics so screen readers announce
updates; add role="status" plus aria-live="polite" (and optionally
aria-atomic="true") to that span element to ensure asynchronous label changes
are announced.
apps/web/ui/support/source-citations.tsx (1)

19-21: Expose disclosure state for assistive tech.

The source-list toggle should advertise its expanded state and controlled region.

♿ Suggested ARIA wiring
-import { useState } from "react";
+import { useId, useState } from "react";
 
 export function SourceCitations({ sources }: { sources: SourceCitation[] }) {
   const [open, setOpen] = useState(false);
+  const listId = useId();
 
   if (!sources.length) return null;
 
   return (
     <div className="mt-4">
       <button
         type="button"
         onClick={() => setOpen((v) => !v)}
+        aria-expanded={open}
+        aria-controls={listId}
         className="flex items-center gap-1.5 text-xs font-medium text-blue-600 hover:text-blue-800"
       >
@@
-      {open && (
-        <ul className="mt-1.5 space-y-1 pl-1">
+      {open && (
+        <ul id={listId} className="mt-1.5 space-y-1 pl-1">

Also applies to: 43-45

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/ui/support/source-citations.tsx` around lines 19 - 21, The toggle
button that calls setOpen to flip the open state should expose that state and
the controlled region to assistive tech: add aria-expanded={open} and
aria-controls pointing to the source list's id on the button (also give the
button an id), and ensure the source list container element has that id and
appropriate accessibility attributes such as role="region" and aria-labelledby
referencing the button id (or aria-hidden when closed if you prefer). Apply the
same ARIA wiring to the other toggle instance referenced by the comment (the
similar button rendering using setOpen/open).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/ui/support/chat-interface.tsx`:
- Around line 379-384: The current ternary uses the global status variable
causing all assistant messages to drop citations during any streaming reply;
change the condition to use the message-specific status/flag instead (e.g.,
message.status !== "streaming" or !message.streaming) so
extractSources(message.parts) runs per-message. Update the expression that
computes sources (where extractSources is called and status is referenced) to
reference message.status or message.streaming (whichever schema the Message type
uses) so only the streaming message loses citations.

In `@apps/web/ui/support/code-block.tsx`:
- Line 109: The ext computation can produce "code." when language is an empty
string; update the logic in the code-block component where ext is derived (the
LANG_EXTENSIONS lookup and ext variable) to treat empty or whitespace-only
language as missing: normalize and trim language first (e.g. language?.trim())
then use the trimmed value for toLowerCase() lookup into LANG_EXTENSIONS and
fall back to "txt" if the trimmed language is falsy or not found; ensure you
reference the LANG_EXTENSIONS lookup and the ext variable (and the language
variable) when making this change.

In `@apps/web/ui/support/source-citations.tsx`:
- Around line 75-90: The loop assumes part.output is an array and meta.url is a
string; harden the iteration in the code handling part (the variable part,
results, r, and meta) by first checking Array.isArray((part as any).output)
before assigning results, skip non-array outputs, and before calling
meta.url.split("#") ensure typeof meta?.url === "string" (and typeof
meta?.heading === "string") so you only push valid entries into sources and
avoid runtime errors from invalid tool payloads; update the checks around
results, meta.url, and meta.heading in the block that uses seen and sources to
validate types and continue on invalid shapes.

---

Outside diff comments:
In `@apps/web/lib/ai/build-system-prompt.ts`:
- Line 69: Remove the debug console.log that prints the system prompt to avoid
leaking identifiers; specifically delete the console.log(systemPrompt) call in
the build-system-prompt module (where systemPrompt is created/returned, e.g., in
buildSystemPrompt) so the prompt contents (id, name, slug) are no longer emitted
to server logs.

---

Nitpick comments:
In `@apps/web/ui/support/code-block.tsx`:
- Around line 90-97: The icon-only copy buttons in code-block.tsx (the button
using handleCopy and rendering {copied ? <Check .../> : <Copy .../>}) rely on
title but need explicit aria-labels for assistive tech; update both button
elements (the one around the handleCopy/copy icon and the similar one at lines
~122-129) to include aria-label values that reflect state (e.g.,
aria-label={copied ? "Copied" : "Copy code"}) so screen readers get a clear,
dynamic name while keeping the existing title and onClick behavior.

In `@apps/web/ui/support/message.tsx`:
- Around line 26-33: The avatar <img> currently uses a generic alt="avatar";
update it to be accessible by making the alt attribute conditional: use alt=""
for decorative avatars (when no sender information is available) and use a
contextual label (e.g., the sender's name or role) when that data exists (for
example from a prop like senderName or sender.role). Locate the <img> in the
Message component in apps/web/ui/support/message.tsx (the element using props
avatar and isUser) and change the alt to derive from sender info if present,
otherwise set it to an empty string.

In `@apps/web/ui/support/source-citations.tsx`:
- Around line 19-21: The toggle button that calls setOpen to flip the open state
should expose that state and the controlled region to assistive tech: add
aria-expanded={open} and aria-controls pointing to the source list's id on the
button (also give the button an id), and ensure the source list container
element has that id and appropriate accessibility attributes such as
role="region" and aria-labelledby referencing the button id (or aria-hidden when
closed if you prefer). Apply the same ARIA wiring to the other toggle instance
referenced by the comment (the similar button rendering using setOpen/open).

In `@apps/web/ui/support/status-indicator.tsx`:
- Around line 12-19: The span rendering the dynamic status label in
StatusIndicator (the span using className, cn and showing {label}) needs
live-region semantics so screen readers announce updates; add role="status" plus
aria-live="polite" (and optionally aria-atomic="true") to that span element to
ensure asynchronous label changes are announced.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 889daa5 and e8442ff.

📒 Files selected for processing (10)
  • apps/web/lib/ai/build-system-prompt.ts
  • apps/web/ui/support/chat-bubble.tsx
  • apps/web/ui/support/chat-interface.tsx
  • apps/web/ui/support/code-block.tsx
  • apps/web/ui/support/embedded-chat.tsx
  • apps/web/ui/support/message.tsx
  • apps/web/ui/support/program-combobox.tsx
  • apps/web/ui/support/source-citations.tsx
  • apps/web/ui/support/status-indicator.tsx
  • apps/web/ui/support/workspace-combobox.tsx

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/web/ui/support/chat-interface.tsx (1)

344-357: Consider extracting the status indicator logic to a variable.

The IIFE pattern works but slightly reduces readability. A pre-computed variable would be cleaner.

♻️ Optional refactor
+            const statusLabel = (() => {
+              const isSearching = message.parts?.some(
+                (p: any) =>
+                  p.type === "tool-findRelevantDocs" &&
+                  p.state !== "output-available",
+              );
+              return isSearching ? "Searching docs..." : "Thinking...";
+            })();
+
             ) : !textContent ? (
-                  (() => {
-                    const isSearching = message.parts?.some(
-                      (p: any) =>
-                        p.type === "tool-findRelevantDocs" &&
-                        p.state !== "output-available",
-                    );
-                    return (
-                      <StatusIndicator
-                        label={isSearching ? "Searching docs..." : "Thinking..."}
-                        className="py-0.5"
-                      />
-                    );
-                  })()
+              <StatusIndicator label={statusLabel} className="py-0.5" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/ui/support/chat-interface.tsx` around lines 344 - 357, Extract the
inline IIFE that renders the StatusIndicator into a precomputed variable:
compute isSearching by evaluating message.parts?.some(...) (same predicate
checking p.type === "tool-findRelevantDocs" && p.state !== "output-available"),
then set a statusLabel = isSearching ? "Searching docs..." : "Thinking..." and
render <StatusIndicator label={statusLabel} className="py-0.5" /> in place of
the IIFE; keep the existing conditional branch (!textContent) and the
StatusIndicator component name unchanged so only the rendering expression is
replaced with the variable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/web/ui/support/chat-interface.tsx`:
- Around line 344-357: Extract the inline IIFE that renders the StatusIndicator
into a precomputed variable: compute isSearching by evaluating
message.parts?.some(...) (same predicate checking p.type ===
"tool-findRelevantDocs" && p.state !== "output-available"), then set a
statusLabel = isSearching ? "Searching docs..." : "Thinking..." and render
<StatusIndicator label={statusLabel} className="py-0.5" /> in place of the IIFE;
keep the existing conditional branch (!textContent) and the StatusIndicator
component name unchanged so only the rendering expression is replaced with the
variable.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e8442ff and 7cfa016.

📒 Files selected for processing (4)
  • apps/web/app/app.dub.co/embed/support-chat/page.tsx
  • apps/web/ui/support/chat-bubble.tsx
  • apps/web/ui/support/chat-interface.tsx
  • apps/web/ui/support/embedded-chat.tsx

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/ui/support/chat-interface.tsx (1)

95-100: ⚠️ Potential issue | 🟠 Major

Don’t mark ticket as submitted before the escalation request is confirmed.

Line [99] flips ticketSubmitted immediately. If the request fails, the UI still shows “Your ticket has been submitted” and blocks normal chat input.

Please move setTicketSubmitted(true) behind a success signal (server/tool confirmation), and keep current input mode until that confirmation arrives.

Also applies to: 414-429

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/ui/support/chat-interface.tsx` around lines 95 - 100, The handler
prematurely sets UI state before server confirmation: in handleEscalate (and the
analogous flow around lines 414-429) stop calling setTicketSubmitted(true)
immediately; instead send the escalation request via sendMessage or the async
API, await the server/tool success response or check the returned
promise/result, and only call setTicketSubmitted(true) after a successful
confirmation; on failure, keep current input mode and show an error/notification
without blocking chat input. Ensure you update the async path that currently
calls setTicketSubmitted(true) to move that call into the success branch and
handle errors explicitly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/ui/support/chat-interface.tsx`:
- Around line 322-324: The per-message animation is incorrectly using the global
status variable so every assistant message animates when any response streams;
change the animation guard to use the per-message boolean isCurrentlyStreaming
instead of status (i.e., replace checks of status === "streaming" with
isCurrentlyStreaming where markdown/animation is applied around sources and in
the same block used for sources), ensuring the same per-message guard already
used for sources is used at the places around lines 362–365 (and the existing
isCurrentlyStreaming declaration) so only the currently streaming message
animates.

In `@apps/web/ui/support/code-block.tsx`:
- Around line 82-87: handleCopy currently calls
navigator.clipboard.writeText(code) without error handling which can cause
unhandled promise rejections; wrap the writeText call in a try/catch inside the
handleCopy useCallback, only call setCopied(true) and start timerRef.current =
setTimeout(...) after a successful await, and in the catch block handle failures
gracefully (e.g., log the error via console.error or a logger and do not set
copied) to keep the UI stable.
- Around line 144-147: The effect watching trimmedCode and language must clear
stale highlightedHtml when trimmedCode is falsy and avoid out-of-order async
updates from highlightCode; update the useEffect (and add a ref like
requestIdRef) so that when trimmedCode is falsy you call setHighlightedHtml('')
immediately, and when calling highlightCode(trimmedCode, language) you
capture/increment a request id and only call setHighlightedHtml with the result
if the id matches the latest (cancelling/outdating prior requests); reference
useEffect, trimmedCode, language, highlightCode, setHighlightedHtml, and
highlightedHtml when making this change.

---

Outside diff comments:
In `@apps/web/ui/support/chat-interface.tsx`:
- Around line 95-100: The handler prematurely sets UI state before server
confirmation: in handleEscalate (and the analogous flow around lines 414-429)
stop calling setTicketSubmitted(true) immediately; instead send the escalation
request via sendMessage or the async API, await the server/tool success response
or check the returned promise/result, and only call setTicketSubmitted(true)
after a successful confirmation; on failure, keep current input mode and show an
error/notification without blocking chat input. Ensure you update the async path
that currently calls setTicketSubmitted(true) to move that call into the success
branch and handle errors explicitly.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7cfa016 and 0cfc6dd.

📒 Files selected for processing (4)
  • apps/web/lib/ai/build-system-prompt.ts
  • apps/web/ui/support/chat-interface.tsx
  • apps/web/ui/support/code-block.tsx
  • apps/web/ui/support/source-citations.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/ui/support/source-citations.tsx

Comment on lines +322 to +324
const isCurrentlyStreaming =
status === "streaming" && index === messages.length - 1;
const sources =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Scope markdown animation to the currently streaming message.

Line [364] uses global status, so all rendered assistant messages animate while any response streams. Use isCurrentlyStreaming here (same per-message guard already used for sources).

Proposed fix
                     <Streamdown
                       key={index}
-                      isAnimating={status === "streaming"}
+                      isAnimating={isCurrentlyStreaming}
                       className="text-content-emphasis"

Also applies to: 362-365

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/ui/support/chat-interface.tsx` around lines 322 - 324, The
per-message animation is incorrectly using the global status variable so every
assistant message animates when any response streams; change the animation guard
to use the per-message boolean isCurrentlyStreaming instead of status (i.e.,
replace checks of status === "streaming" with isCurrentlyStreaming where
markdown/animation is applied around sources and in the same block used for
sources), ensuring the same per-message guard already used for sources is used
at the places around lines 362–365 (and the existing isCurrentlyStreaming
declaration) so only the currently streaming message animates.

Comment on lines +82 to +87
const handleCopy = useCallback(async () => {
if (copied || !navigator?.clipboard?.writeText) return;
await navigator.clipboard.writeText(code);
setCopied(true);
timerRef.current = setTimeout(() => setCopied(false), 2000);
}, [code, copied]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle clipboard write failures to avoid unhandled promise rejections.

Line [84] can throw (permission denied, insecure context, browser policy). Wrap it in try/catch so the UI stays stable.

Proposed fix
   const handleCopy = useCallback(async () => {
     if (copied || !navigator?.clipboard?.writeText) return;
-    await navigator.clipboard.writeText(code);
-    setCopied(true);
-    timerRef.current = setTimeout(() => setCopied(false), 2000);
+    try {
+      await navigator.clipboard.writeText(code);
+      setCopied(true);
+      timerRef.current = setTimeout(() => setCopied(false), 2000);
+    } catch {
+      // no-op: clipboard unavailable or blocked
+    }
   }, [code, copied]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleCopy = useCallback(async () => {
if (copied || !navigator?.clipboard?.writeText) return;
await navigator.clipboard.writeText(code);
setCopied(true);
timerRef.current = setTimeout(() => setCopied(false), 2000);
}, [code, copied]);
const handleCopy = useCallback(async () => {
if (copied || !navigator?.clipboard?.writeText) return;
try {
await navigator.clipboard.writeText(code);
setCopied(true);
timerRef.current = setTimeout(() => setCopied(false), 2000);
} catch {
// no-op: clipboard unavailable or blocked
}
}, [code, copied]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/ui/support/code-block.tsx` around lines 82 - 87, handleCopy
currently calls navigator.clipboard.writeText(code) without error handling which
can cause unhandled promise rejections; wrap the writeText call in a try/catch
inside the handleCopy useCallback, only call setCopied(true) and start
timerRef.current = setTimeout(...) after a successful await, and in the catch
block handle failures gracefully (e.g., log the error via console.error or a
logger and do not set copied) to keep the UI stable.

Comment on lines +144 to +147
useEffect(() => {
if (!trimmedCode) return;
highlightCode(trimmedCode, language).then(setHighlightedHtml);
}, [trimmedCode, language]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Reset and guard highlighted state on async re-renders.

Line [145] returns early for empty code without clearing highlightedHtml, so stale highlighted markup can remain visible. Also, async highlightCode calls can resolve out-of-order after prop changes.

Proposed fix
   useEffect(() => {
-    if (!trimmedCode) return;
-    highlightCode(trimmedCode, language).then(setHighlightedHtml);
+    let cancelled = false;
+
+    if (!trimmedCode) {
+      setHighlightedHtml(null);
+      return;
+    }
+
+    highlightCode(trimmedCode, language).then((html) => {
+      if (!cancelled) setHighlightedHtml(html);
+    });
+
+    return () => {
+      cancelled = true;
+    };
   }, [trimmedCode, language]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/ui/support/code-block.tsx` around lines 144 - 147, The effect
watching trimmedCode and language must clear stale highlightedHtml when
trimmedCode is falsy and avoid out-of-order async updates from highlightCode;
update the useEffect (and add a ref like requestIdRef) so that when trimmedCode
is falsy you call setHighlightedHtml('') immediately, and when calling
highlightCode(trimmedCode, language) you capture/increment a request id and only
call setHighlightedHtml with the result if the id matches the latest
(cancelling/outdating prior requests); reference useEffect, trimmedCode,
language, highlightCode, setHighlightedHtml, and highlightedHtml when making
this change.

@pepeladeira pepeladeira changed the base branch from main to partner-tags March 3, 2026 21:35
@pepeladeira pepeladeira changed the base branch from partner-tags to main March 3, 2026 21:35
@steven-tey steven-tey merged commit c6e14a8 into main Mar 3, 2026
10 checks passed
@steven-tey steven-tey deleted the embeddable-support-chat-widget-improvements branch March 3, 2026 22:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants