Skip to content

fix(web): add error handling for copy message button#1564

Merged
Wirasm merged 3 commits into
coleam00:devfrom
YrFnS:fix/web/copy-message-button
May 15, 2026
Merged

fix(web): add error handling for copy message button#1564
Wirasm merged 3 commits into
coleam00:devfrom
YrFnS:fix/web/copy-message-button

Conversation

@YrFnS

@YrFnS YrFnS commented May 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Describe this PR in 2-5 bullets:

  • Problem: The copy message button in Web UI chat silently fails when navigator.clipboard.writeText() rejects (e.g., HTTPS required, permission denied). Users see no feedback when copy fails.
  • Why it matters: Users rely on the copy button to copy chat messages. Silent failures cause confusion and force manual text selection.
  • What changed: Added copyError state and .catch() handler to handle clipboard API failures. Shows X icon with error color when copy fails, resets after 2 seconds.
  • What did not change (scope boundary): No API changes, no database changes, no workflow changes.

UX Journey

Before

User                   Archon Web UI
─────                   ─────────────
sends message
hovers message ──────▶ copy icon appears
clicks copy ──────────▶ [silently fails - no feedback]
pastes text ──────────▶ nothing copied

After

User                   Archon Web UI
─────                   ─────────────
sends message
hovers message ──────▶ copy icon appears
clicks copy ──────────▶ [success: checkmark, 1.5s]
                       [failure: X icon with error color, 2s]
pastes text ──────────▶ [success: text copied]
                       [failure: user sees X icon, can retry]

Architecture Diagram

Before

┌─────────────────┐
│ MessageBubble   │
│   component     │
└────────┬────────┘
         │
         │ uses
         ▼
┌─────────────────┐
│ copyMessage()   │
│  .then() only   │──────▶ clipboard API
└─────────────────┘       (no error handling)

After

┌─────────────────┐
│ MessageBubble   │
│   component     │
└────────┬────────┘
         │
         ├─── setCopied()
         │         │
         ▼         ▼
┌─────────────────┐    ┌─────────────────┐
│ copyMessage()   │───▶│ copyError state  │
│  .then()        │    │  .catch()       │
└────────┬────────┘    └────────┬────────┘
         │                       │
         ▼                       ▼
┌─────────────────┐    ┌─────────────────┐
│ Check icon      │    │ X icon (error)  │
│ (text-success)  │    │ (text-error)    │
└─────────────────┘    └─────────────────┘

Connection inventory (list every module-to-module edge, mark changes):

From To Status Notes
MessageBubble navigator.clipboard modified Added .catch() for error handling
MessageBubble copyError state new Tracks clipboard API failures
copyMessage setCopied/setCopyError modified Both states updated on success/failure

Label Snapshot

  • Risk: risk: low
  • Size: size: XS
  • Scope: web
  • Module: web:MessageBubble

Change Metadata

  • Change type: bug
  • Primary scope: web

Linked Issue

Validation Evidence (required)

Commands and result summary:

bun run type-check  # ✅ All packages pass
bun run lint        # ✅ 0 errors, 0 warnings
bun run format:check # ✅ All files formatted
bun run test        # ✅ All tests pass (per-package isolation)
# Or all at once:
bun run validate    # ✅ All checks pass
  • Evidence provided: Tests pass, manual testing in browser confirms button works correctly
  • If any command is intentionally skipped, explain why: None

Security Impact (required)

  • New permissions/capabilities? (No)
  • New external network calls? (No)
  • Secrets/tokens handling changed? (No)
  • File system access scope changed? (No)

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Database migration needed? (No)

Human Verification (required)

What was personally validated beyond CI:

  • Verified scenarios: Tested copy button in Chrome browser - checkmark appears on success
  • Edge cases checked: Error state displays X icon when clipboard fails (simulated via browser restrictions)
  • What was not verified: Testing in Safari, Firefox, or non-browser environments

Side Effects / Blast Radius (required)

  • Affected subsystems/workflows: None - isolated change to MessageBubble component only
  • Potential unintended effects: None expected - error handling is additive
  • Guardrails/monitoring for early detection: CI catches any regressions

Rollback Plan (required)

  • Fast rollback command/path: git revert <commit-sha>
  • Feature flags or config toggles (if any): None
  • Observable failure symptoms: None - rollback restores previous behavior

Risks and Mitigations

None - this is a simple error handling addition that improves UX without changing existing behavior for successful cases.

Summary by CodeRabbit

  • Bug Fixes
    • Added robust handling for clipboard copy failures with a visible error indicator so users know when copying fails.
    • Improved copy button feedback and states: labels/tooltips and visual icon state now reflect success or failure, and temporary error indicators clear after a short delay.

Handle navigator.clipboard.writeText() failures gracefully:
- Add copyError state to track clipboard API errors
- Show X icon with error color when copy fails
- Reset error state after 2 seconds
- Closes coleam00#1540
@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e2c8894a-1e4d-49d0-aa19-03f82cdf41f8

📥 Commits

Reviewing files that changed from the base of the PR and between 2954e7c and cf11f6d.

📒 Files selected for processing (1)
  • packages/web/src/components/chat/MessageBubble.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/web/src/components/chat/MessageBubble.tsx

📝 Walkthrough

Walkthrough

Added clipboard-copy failure handling to the chat message bubble: introduced a copyError React state, updated copyMessage to set/clear copyError on clipboard write rejection, and changed the copy button's icon, title, and aria-label to reflect success or error.

Changes

Clipboard Copy Error Handling

Layer / File(s) Summary
State & Imports
packages/web/src/components/chat/MessageBubble.tsx
Added copyError React state and imported X icon alongside existing Copy and Check icons.
Core Logic
packages/web/src/components/chat/MessageBubble.tsx
Reworked copyMessage to handle navigator.clipboard.writeText promise: set copied on success (clear after timeout) and set copyError on failure (log error and clear after timeout); ensure copyError cleared on successful copy.
UI & Accessibility
packages/web/src/components/chat/MessageBubble.tsx
Copy button title/aria-label now reflect copyError vs copied; icon renders X on error, Check on success, and Copy otherwise.

Estimated Code Review Effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

I nibbled on the copy key, then found a glitch,
A tiny X appeared — a hiccup in the stitch,
It blinks away, then checks the line,
Clipboard calm returns in time,
A rabbit cheers: the copy's fixed — no hitch! 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(web): add error handling for copy message button' accurately and concisely describes the main change—adding error handling to the copy button in the web component.
Description check ✅ Passed The PR description is comprehensive, following the template with all required sections filled out including summary, UX journey, architecture diagrams, validation evidence, security impact, compatibility, human verification, side effects, rollback plan, and risks.
Linked Issues check ✅ Passed The PR successfully addresses issue #1540 by implementing error handling for the copy button. It provides visual feedback (X icon on failure, checkmark on success) and ensures users know when clipboard operations fail, meeting the acceptance criteria.
Out of Scope Changes check ✅ Passed All changes are scoped to the MessageBubble component's copy functionality. The PR adds error state management and UI feedback for clipboard failures without introducing unrelated modifications to APIs, databases, or workflows.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/web/src/components/chat/MessageBubble.tsx (1)

157-171: ⚡ Quick win

Untracked setTimeout calls cause stale-timer races on rapid clicks.

Each call to copyMessage enqueues a fresh setTimeout without cancelling any prior pending timer. On rapid repeated failures the first timer fires before the second and prematurely resets copyError (e.g. two failures 500 ms apart — the first timer fires 1 500 ms into the second's 2 000 ms window and clears the error early). The same issue applies to copied. The same pattern exists in Header.tsx but that doesn't make it correct.

♻️ Proposed fix — cancel stale timers before scheduling new ones
+  const copiedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+  const copyErrorTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

   const copyMessage = (): void => {
     void navigator.clipboard
       .writeText(message.content)
       .then(() => {
+        if (copiedTimerRef.current) clearTimeout(copiedTimerRef.current);
+        if (copyErrorTimerRef.current) clearTimeout(copyErrorTimerRef.current);
         setCopied(true);
         setCopyError(false);
-        setTimeout(() => {
+        copiedTimerRef.current = setTimeout(() => {
           setCopied(false);
         }, 1500);
       })
       .catch(() => {
+        setCopied(false);
+        if (copyErrorTimerRef.current) clearTimeout(copyErrorTimerRef.current);
         setCopyError(true);
-        setTimeout(() => {
+        copyErrorTimerRef.current = setTimeout(() => {
           setCopyError(false);
         }, 2000);
       });
   };

The useRef import is already present on Line 1. The timer refs should be declared alongside the other state variables (around Line 143).

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

In `@packages/web/src/components/chat/MessageBubble.tsx` around lines 157 - 171,
The copyMessage handler enqueues setTimeouts without cancelling previous timers
causing stale-timer races for copied and copyError; fix it by adding two refs
(e.g. copiedTimerRef and copyErrorTimerRef via useRef<number | null>) near the
existing state declarations, clear any existing timer (clearTimeout on
copiedTimerRef/current or copyErrorTimerRef/current) before creating a new
setTimeout, store the new timer id in the corresponding ref, and also clear both
timers in a cleanup effect (useEffect cleanup) to avoid leaks; update the
promise .then and .catch branches to use these refs instead of raw setTimeout so
clicks won’t prematurely reset state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/web/src/components/chat/MessageBubble.tsx`:
- Around line 166-171: In MessageBubble's copy Promise .catch() handler, ensure
the copied state is reset so the error icon/label can appear; update the
.catch() to call setCopied(false) before (or alongside) setCopyError(true) and
keep the existing timeout to clear copyError (and ensure the timeout doesn't
rely on copied). Locate the .catch() in the copy handler where
setCopyError(true) is called and add a setCopied(false) there so failed copies
won't leave the success state visible.

---

Nitpick comments:
In `@packages/web/src/components/chat/MessageBubble.tsx`:
- Around line 157-171: The copyMessage handler enqueues setTimeouts without
cancelling previous timers causing stale-timer races for copied and copyError;
fix it by adding two refs (e.g. copiedTimerRef and copyErrorTimerRef via
useRef<number | null>) near the existing state declarations, clear any existing
timer (clearTimeout on copiedTimerRef/current or copyErrorTimerRef/current)
before creating a new setTimeout, store the new timer id in the corresponding
ref, and also clear both timers in a cleanup effect (useEffect cleanup) to avoid
leaks; update the promise .then and .catch branches to use these refs instead of
raw setTimeout so clicks won’t prematurely reset state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3b707ada-c62e-4fea-b1dd-8d82193bb3bb

📥 Commits

Reviewing files that changed from the base of the PR and between 88d0109 and 63a8066.

📒 Files selected for processing (1)
  • packages/web/src/components/chat/MessageBubble.tsx

Comment on lines +166 to +171
.catch(() => {
setCopyError(true);
setTimeout(() => {
setCopyError(false);
}, 2000);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

.catch() must reset copied to avoid suppressing the error icon.

When a copy succeeds the copied flag stays true for 1 500 ms. If the user clicks again within that window and the second copy fails, the .catch() handler sets copyError = true but leaves copied = true. Because both the icon ternary and the aria-label evaluate copied first, the UI still shows the checkmark and announces "Copied" — the X icon and "Failed to copy" label never appear. This directly defeats the purpose of this PR.

🐛 Proposed fix
      .catch(() => {
+       setCopied(false);
        setCopyError(true);
        setTimeout(() => {
          setCopyError(false);
        }, 2000);
      });
📝 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
.catch(() => {
setCopyError(true);
setTimeout(() => {
setCopyError(false);
}, 2000);
});
.catch(() => {
setCopied(false);
setCopyError(true);
setTimeout(() => {
setCopyError(false);
}, 2000);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/components/chat/MessageBubble.tsx` around lines 166 - 171,
In MessageBubble's copy Promise .catch() handler, ensure the copied state is
reset so the error icon/label can appear; update the .catch() to call
setCopied(false) before (or alongside) setCopyError(true) and keep the existing
timeout to clear copyError (and ensure the timeout doesn't rely on copied).
Locate the .catch() in the copy handler where setCopyError(true) is called and
add a setCopied(false) there so failed copies won't leave the success state
visible.

Add docstrings to MessageBubbleRaw component and copyMessage function
to satisfy 80% docstring coverage requirement.

Closes coleam00#1540
@Wirasm

Wirasm commented May 4, 2026

Copy link
Copy Markdown
Collaborator

Review Summary

Verdict: minor-fixes-needed

Your PR adds a useful UX improvement — surfacing clipboard copy failures to users with an X icon — and the implementation is clean. Two JSDoc comment blocks added to MessageBubble.tsx restate what the code already conveys through names and types. These should be removed per the project's comment policy.

Suggested fixes

  • MessageBubble.tsx:138-145 — Delete the MessageBubble component JSDoc block. "Don't explain WHAT (well-named identifiers do that)." The props are already typed, the function name is already clear. This adds only comment rot risk.

  • MessageBubble.tsx:164-167 — Delete the copyMessage JSDoc block. "Copies to clipboard" is what the function name already says; "shows checkmark / X icon" is what the code does; "resets visual feedback after timeout" is what the setTimeout does. Nothing here explains a non-obvious constraint.

Minor / nice-to-have

  • MessageBubble.tsx:184 — The .catch() handler ignores the error parameter. Consider a console.debug('Clipboard write failed:', error); before setting copyError(true). Low priority since the error is surfaced visually to the user — useful mainly for debugging.

Compliments

  • The error handling implementation is correct: using .catch() on the promise chain rather than try/catch is the right approach for navigator.clipboard.writeText().
  • The visual feedback (icon swap + tooltip update) is a simple, clear UX pattern.
  • PR template is thorough with clear before/after diagrams and architecture documentation.

Reviewed via maintainer-review-pr workflow (Pi/Minimax). Aspects run: code-review, error-handling, test-coverage, comment-quality.

…logging

Per Wirasm review - remove redundant JSDoc blocks that restate code.
The function names and types already convey what the code does.
Add console.debug for clipboard errors for debugging support.

Closes coleam00#1540
@YrFnS

YrFnS commented May 5, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review! Applied all fixes:

  • Removed both JSDoc blocks
  • Added console.debug for clipboard errors

Thanks for the detailed feedback! 🙏

@Wirasm Wirasm merged commit c397c4e into coleam00:dev May 15, 2026
4 checks passed
cropse pushed a commit to cropse/Archon that referenced this pull request May 19, 2026
* fix(web): add error handling for copy message button

Handle navigator.clipboard.writeText() failures gracefully:
- Add copyError state to track clipboard API errors
- Show X icon with error color when copy fails
- Reset error state after 2 seconds
- Closes coleam00#1540

* docs(MessageBubble): add JSDoc comments for CodeRabbit coverage

Add docstrings to MessageBubbleRaw component and copyMessage function
to satisfy 80% docstring coverage requirement.

Closes coleam00#1540

* refactor(MessageBubble): remove JSDoc comments per policy, add debug logging

Per Wirasm review - remove redundant JSDoc blocks that restate code.
The function names and types already convey what the code does.
Add console.debug for clipboard errors for debugging support.

Closes coleam00#1540
@Wirasm Wirasm mentioned this pull request May 28, 2026
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.

The "Copy Message" button in the Web UI chat does not work

2 participants