Skip to content

ui: add clipboard fallback for copy button (#34092)#34171

Open
lsdcc01 wants to merge 2 commits intoopenclaw:mainfrom
lsdcc01:fix/34092-copy-button-fallback-upstream
Open

ui: add clipboard fallback for copy button (#34092)#34171
lsdcc01 wants to merge 2 commits intoopenclaw:mainfrom
lsdcc01:fix/34092-copy-button-fallback-upstream

Conversation

@lsdcc01
Copy link
Copy Markdown

@lsdcc01 lsdcc01 commented Mar 4, 2026

PR #34092: Copy button clipboard fallback for HTTP context

Summary

  • Problem: On Windows 11 + Chrome, clicking the "Copy as markdown" button on chat messages does nothing; content is not copied to clipboard when accessing via http://192.168.x.x (non-HTTPS).
  • Why it matters: navigator.clipboard.writeText() only works in secure contexts (HTTPS or localhost). Over HTTP it throws silently and the current code had no fallback.
  • What changed: Added document.execCommand('copy') fallback via temporary textarea when Clipboard API fails, matching the pattern in src/auto-reply/reply/export-html/template.js.
  • What did NOT change: No changes to UI layout, button styling, or other clipboard consumers.

Change Type

  • Bug fix

Scope

  • UI / DX

Linked Issue/PR

User-visible / Behavior Changes

Copy button now works when accessing the web UI over HTTP (e.g. http://192.168.x.x). No change for HTTPS/localhost users.

Security Impact

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: Windows 11
  • Browser: Chrome
  • Access: http://192.168.x.x (gateway web UI)

Steps

  1. Open chat UI over HTTP (not localhost/HTTPS).
  2. Click "Copy as markdown" on a message.
  3. Paste into another app.

Expected

Text is copied to clipboard.

Actual (before fix)

Nothing copied; button appears to do nothing.

Evidence

  • Trace/log snippets: Clipboard API throws in HTTP context; fallback uses execCommand('copy').
  • Reference implementation: src/auto-reply/reply/export-html/template.js lines 1277–1291.

Human Verification

  • Verified: Fallback logic matches template.js; pnpm check passes.
  • Edge cases: Empty text returns false; Clipboard API success path unchanged.
  • Not verified: Manual test on Windows 11 + Chrome over HTTP (reporter's environment).

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

  • Risk: execCommand('copy') is deprecated (but widely supported).
  • Mitigation: Used only as fallback when Clipboard API fails; modern browsers still support it for this use case.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 4, 2026

Greptile Summary

This PR fixes a bug where the "Copy as markdown" button silently failed in HTTP (non-HTTPS) contexts — such as http://192.168.x.x — because navigator.clipboard.writeText() requires a secure context. The fix adds a document.execCommand('copy') fallback via a temporary textarea, mirroring the identical pattern already used in src/auto-reply/reply/export-html/template.js.

Key changes:

  • copyTextToClipboard now attempts the Clipboard API first, and falls through to the execCommand fallback only when the API is unavailable or throws.
  • The guard navigator.clipboard?.writeText correctly handles the case where navigator.clipboard is undefined rather than relying on a catch.
  • One minor concern: the fallback textarea is not cleaned up with a finally block, so a rare exception from textarea.select() or document.execCommand() would leave a hidden element orphaned in document.body. This matches the existing pattern in the reference file but is worth fixing in both places.

Confidence Score: 4/5

  • This PR is safe to merge; it adds a well-scoped fallback with no behavioral change in HTTPS/localhost environments.
  • The change is small, focused, and correctly implements the same clipboard fallback pattern already present in the codebase. The only deduction is for the minor DOM cleanup gap (missing finally block) in the fallback path, which is unlikely to trigger in practice but is not ideal.
  • No files require special attention beyond the minor finally-block suggestion in ui/src/ui/chat/copy-as-markdown.ts.

Last reviewed commit: ddae218

Comment on lines +32 to +43
try {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
textarea.style.opacity = "0";
document.body.appendChild(textarea);
textarea.select();
success = document.execCommand("copy");
document.body.removeChild(textarea);
} catch (err) {
console.error("Failed to copy:", err);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Textarea not cleaned up if execCommand throws

If textarea.select() or document.execCommand("copy") throws before reaching document.body.removeChild(textarea), the hidden textarea is permanently left in the DOM. While this matches the existing pattern in src/auto-reply/reply/export-html/template.js, using a finally block guarantees cleanup regardless of outcome:

    try {
      const textarea = document.createElement("textarea");
      textarea.value = text;
      textarea.style.position = "fixed";
      textarea.style.opacity = "0";
      document.body.appendChild(textarea);
      try {
        textarea.select();
        success = document.execCommand("copy");
      } finally {
        document.body.removeChild(textarea);
      }
    } catch (err) {
      console.error("Failed to copy:", err);
    }

This ensures the element is always removed even when an unexpected exception is raised.

Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/ui/chat/copy-as-markdown.ts
Line: 32-43

Comment:
**Textarea not cleaned up if `execCommand` throws**

If `textarea.select()` or `document.execCommand("copy")` throws before reaching `document.body.removeChild(textarea)`, the hidden textarea is permanently left in the DOM. While this matches the existing pattern in `src/auto-reply/reply/export-html/template.js`, using a `finally` block guarantees cleanup regardless of outcome:

```
    try {
      const textarea = document.createElement("textarea");
      textarea.value = text;
      textarea.style.position = "fixed";
      textarea.style.opacity = "0";
      document.body.appendChild(textarea);
      try {
        textarea.select();
        success = document.execCommand("copy");
      } finally {
        document.body.removeChild(textarea);
      }
    } catch (err) {
      console.error("Failed to copy:", err);
    }
```

This ensures the element is always removed even when an unexpected exception is raised.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ddae218e1c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +39 to +40
success = document.execCommand("copy");
document.body.removeChild(textarea);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Always clean up fallback textarea on copy failure

If document.execCommand("copy") (or textarea.select()) throws in the fallback path—for example on browsers where execCommand is unavailable or blocked—the temporary <textarea> is never removed because cleanup happens only after the successful call. In repeated failed copy attempts this leaks hidden nodes and leaves copied message text in the DOM longer than intended, so cleanup should be moved to a finally block.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Chat "copy button" not working on Windows

1 participant