Skip to content

fix-forward #2968 (tsk-xi3mcb): the isSecureContext conjunct makes copyText refuse a working clipboard (red), and the execCommand fallback can reject instead of returning false - #2977

Merged
jaylfc merged 3 commits into
devfrom
exec/tsk-4smbaw
Sep 11, 2026

Conversation

@jaylfc

@jaylfc jaylfc commented Sep 11, 2026

Copy link
Copy Markdown
Owner

CARD TITLE (intent, not commit subject): fix-forward #2968 (tsk-xi3mcb): the isSecureContext conjunct makes copyText refuse a working clipboard (red), and the execCommand fallback can reject instead of returning false

Autonomous build of board card tsk-4smbaw.

REVISION: built on exec/tsk-xi3mcb (cut at e43bddc1a4fe5a2569f5eddee77887c87476b923), not on dev. That branch's
commits are ancestors of this one. Verified by git merge-base --is-ancestor
before the PR was opened.

Defect 1: the guard is stricter than the capability

  • navigator.clipboard is only exposed by the browser in a secure context, so the window.isSecureContext conjunct buys nothing and only adds a way to refuse a clipboard that demonstrably works
  • Drop && window.isSecureContext, rely on the optional-chained call plus the existing catch

Defect 2: stale duplicate test file

Defect 3: fallbackCopy can reject

  • fallbackCopy wraps document.execCommand("copy") in try/finally with NO catch, so a throwing execCommand propagates out of copyText as a rejected promise
  • Make it try/catch returning false, so copyText NEVER rejects
  • While in there: fallbackCopy calls textarea.focus() and never restores focus, so a keyboard user loses their place after every copy; save document.activeElement and restore it after removing the textarea
 FAIL  src/lib/clipboard.test.ts > copyText > uses navigator.clipboard.writeText when available and returns true, even when isSecureContext is false
 FAIL  src/lib/clipboard.test.ts > copyText > returns false when execCommand throws, without rejecting
Error: copy failed
 src/lib/clipboard.test.ts:52:15
    50|     Object.defineProperty(document, "execCommand", {
    51|       value: () => {
    52|         throw new Error("copy failed");
    53|       },
...
Test Files  1 failed (1)
     Tests  2 failed | 1 passed (3)
Test Files  2 passed (2)
     Tests  11 passed (11)

Carry changelog.d/tsk-xi3mcb-clipboard-helper.md from BASE; do not duplicate.

Files:
desktop/src/apps/chat/MessageList.tsx | 7 +--
desktop/src/components/CodeBlock.test.tsx | 27 ----------
desktop/src/components/CodeBlock.tsx | 18 +++++--
desktop/src/components/TaosAssistantPanel.tsx | 7 ++-
.../src/components/tests/CodeBlock.test.tsx | 50 ++++++++++++++++++
desktop/src/lib/clipboard.test.ts | 61 ++++++++++++++++++++++
desktop/src/lib/clipboard.ts | 39 ++++++++++++++
20 files changed, 319 insertions(+), 78 deletions(-)

Copy buttons called navigator.clipboard.writeText directly, which throws
TypeError on plain-HTTP LAN origins where the API is unavailable. Some
call sites swallowed the error silently, so the button appeared to work
but copied nothing.

Created desktop/src/lib/clipboard.ts with a secure-context gate and
document.execCommand fallback (mirroring the existing InstallHelperPanel
pattern). Replaced all 20 navigator.clipboard occurrences across 14
source files to use the shared helper. Failed copies now surface an
error to the user instead of being swallowed by bare .catch() blocks.

Tests added:
- CodeBlock: copies in non-secure context (fallback path)
- CodeBlock: surfaces copy-failed error to the user
- ClusterApp: does not report success when clipboard is unavailable

RED-FIRST proof:

```
FAIL  src/components/__tests__/CodeBlock.test.tsx > CodeBlock > copies in a non-secure context
AssertionError: expected \"vi.fn()\" to be called with arguments: [ \"copy\" ]

Number of calls: 0

FAIL  src/components/__tests__/CodeBlock.test.tsx > CodeBlock > a failed copy surfaces an error to the user
TestingLibraryElementError: Unable to find an element with the text: /copy failed/i.

FAIL  src/apps/__tests__/ClusterApp.test.tsx > ClusterApp clipboard in non-secure context > copy button does not report success when nothing was copied
expected document not to contain element, found <button ...>Copied</button> instead

Test Files  2 failed (2)
     Tests  3 failed | 6 passed (9)
```

GREEN:

```
Test Files  2 passed (2)
     Tests  9 passed (9)
```
Defect 1: the guard is stricter than the capability
- navigator.clipboard is only exposed by the browser in a secure context, so the window.isSecureContext conjunct buys nothing and only adds a way to refuse a clipboard that demonstrably works
- Drop && window.isSecureContext, rely on the optional-chained call plus the existing catch

Defect 2: stale duplicate test file
- desktop/src/components/CodeBlock.test.tsx was left untested by PR #2968, which only updated the __tests__/ sibling
- Fold any unique assertions into __tests__/CodeBlock.test.tsx and delete the older file

Defect 3: fallbackCopy can reject
- fallbackCopy wraps document.execCommand("copy") in try/finally with NO catch, so a throwing execCommand propagates out of copyText as a rejected promise
- Make it try/catch returning false, so copyText NEVER rejects
- While in there: fallbackCopy calls textarea.focus() and never restores focus, so a keyboard user loses their place after every copy; save document.activeElement and restore it after removing the textarea

```text
 FAIL  src/lib/clipboard.test.ts > copyText > uses navigator.clipboard.writeText when available and returns true, even when isSecureContext is false
 FAIL  src/lib/clipboard.test.ts > copyText > returns false when execCommand throws, without rejecting
Error: copy failed
 src/lib/clipboard.test.ts:52:15
    50|     Object.defineProperty(document, "execCommand", {
    51|       value: () => {
    52|         throw new Error("copy failed");
    53|       },
...
Test Files  1 failed (1)
     Tests  2 failed | 1 passed (3)
```

```text
Test Files  2 passed (2)
     Tests  11 passed (11)
```

Carry changelog.d/tsk-xi3mcb-clipboard-helper.md from BASE; do not duplicate.
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 21 seconds.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 030b978f-1387-47cb-91c6-a7b06b89c0cd

📥 Commits

Reviewing files that changed from the base of the PR and between e13d96a and aadd193.

📒 Files selected for processing (20)
  • changelog.d/tsk-xi3mcb-clipboard-helper.md
  • commit-message.txt
  • desktop/src/apps/AgentMessagesPanel.tsx
  • desktop/src/apps/ClusterApp.tsx
  • desktop/src/apps/LoRAStudioApp.tsx
  • desktop/src/apps/MCPApp.tsx
  • desktop/src/apps/MessagesApp/index.tsx
  • desktop/src/apps/ProjectsApp/InviteAgentDialog.tsx
  • desktop/src/apps/ProvidersApp.tsx
  • desktop/src/apps/SettingsApp/UsersPanel.tsx
  • desktop/src/apps/__tests__/ClusterApp.test.tsx
  • desktop/src/apps/agents/ImportWizard.tsx
  • desktop/src/apps/agents/RegistryPanel.tsx
  • desktop/src/apps/chat/MessageList.tsx
  • desktop/src/components/CodeBlock.test.tsx
  • desktop/src/components/CodeBlock.tsx
  • desktop/src/components/TaosAssistantPanel.tsx
  • desktop/src/components/__tests__/CodeBlock.test.tsx
  • desktop/src/lib/clipboard.test.ts
  • desktop/src/lib/clipboard.ts

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.

@gitar-bot

gitar-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

textarea.focus();
textarea.select();
let ok = false;
const previouslyFocused = document.activeElement;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: previouslyFocused is captured after textarea.focus(), so focus restoration targets the detached textarea instead of the user's original focus.

Suggested change
const previouslyFocused = document.activeElement;
const previouslyFocused = document.activeElement;
textarea.focus();
textarea.select();

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

try {
await navigator.clipboard.writeText(code);
const ok = await copyText(code);
if (ok) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: copyError is never cleared when a copy succeeds. If a previous copy failed, the "Copy failed" state lingers until its timeout fires even after the user successfully copies again.

Suggested change
if (ok) {
if (ok) {
setCopied(true);
setCopyError(false);
setTimeout(() => setCopied(false), 1500);
} else {

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

setCopied(true);
setTimeout(() => setCopied(false), 1500);
const ok = await copyText(lines.join("\n"));
if (ok) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: copyError is never cleared when a copy succeeds. If a previous copy failed, the "Copy failed" state lingers until its timeout fires even after the user successfully copies again.

Suggested change
if (ok) {
if (ok) {
setCopied(true);
setCopyError(false);
setTimeout(() => setCopied(false), 1500);
} else {

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 2
Issue Details (click to expand)

CRITICAL

File Line Issue
desktop/src/lib/clipboard.ts 27 previouslyFocused is captured after textarea.focus(), so focus restoration targets the detached textarea instead of the user's original focus

WARNING

File Line Issue
desktop/src/components/CodeBlock.tsx 16 copyError is never cleared when a copy succeeds, so the "Copy failed" state lingers after a successful copy
desktop/src/apps/MCPApp.tsx 1167 copyError is never cleared when a copy succeeds, so the "Copy failed" state lingers after a successful copy
Files Reviewed (3 files)
  • desktop/src/lib/clipboard.ts - 1 issue
  • desktop/src/components/CodeBlock.tsx - 1 issue
  • desktop/src/apps/MCPApp.tsx - 1 issue

Fix these issues in Kilo Cloud


Reviewed by step-3.7-flash:free · Input: 0 · Output: 0 · Cached: 0

@jaylfc

jaylfc commented Sep 11, 2026

Copy link
Copy Markdown
Owner Author

The three defects are correctly diagnosed and correctly fixed — dropping the isSecureContext
conjunct is right (the spec gates navigator.clipboard on a secure context already, so the
conjunct only ever added a way to refuse a clipboard that works), fallbackCopy is now
try/catch so copyText cannot reject, and restoring document.activeElement is a nice catch.
Two things block the merge.

1. CI is red on a test this PR's own fix contradicts

spa-build fails on run 34613645910, job 103310073116 — one test, in a file #2968 added:

FAIL src/apps/__tests__/ClusterApp.test.tsx > ClusterApp clipboard in non-secure context
     > copy button does not report success when nothing was copied

Error: expect(element).not.toBeInTheDocument()
expected document not to contain element, found <button aria-label="Copy worker name"> … Copied </button>
  ❯ src/apps/__tests__/ClusterApp.test.tsx:58:46
Test Files  1 failed | 425 passed (426)

This is not a flake and not unrelated — it is the direct consequence of Defect 1. That test sets
up a state that, per your own (correct) reasoning, no browser can produce:

Object.defineProperty(window, "isSecureContext", { value: false,});
writeText = vi.fn().mockResolvedValue(undefined);          // a WORKING clipboard
Object.defineProperty(navigator, "clipboard", { value: { writeText },});

isSecureContext === false and a navigator.clipboard whose writeText resolves. With the
conjunct removed, copyText uses the working clipboard, returns true, and ClusterApp correctly
shows "Copied" — so the assertion fails. The test is asserting on an unreachable browser state,
which is exactly the thing this PR set out to stop the helper from modelling.

The fix belongs in this PR, since this PR is what makes it fail. Make the stub the real shape of
a non-secure context:

Object.defineProperty(navigator, "clipboard", { value: undefined, configurable: true });

Then the test exercises what it is named for: no clipboard → fallbackCopy → the mocked
execCommand returns falsecopyText returns false → no "Copied". That version genuinely
proves the guard and will keep failing if someone reintroduces an unconditional setCopied.

Please keep the test rather than deleting it — with that one-line change it is the red-proof for
the original card, and it is the only test covering the "reported success but copied nothing"
path at the component level.

2. commit-message.txt is committed at the repo root

diff --git a/commit-message.txt b/commit-message.txt
new file mode 100644

35 lines of the commit message, as a tracked file in the project root. Scratch artifact — delete
it from the branch.

Note for the re-push

Bot review gate failed only because it ran at 15:01 and kilo-code-bot did not post until
15:06. I have not re-run it, because fixing the two items above re-triggers the whole suite
anyway. Everything else on this head was green, including Evil-merge gate and Gate integrity.

Once those two are in and CI is green this is good to merge — the helper itself I have no further
objections to.

jaylfc added a commit that referenced this pull request Sep 11, 2026
fix-forward #2977 (tsk-4smbaw): the ClusterApp red-proof stubs a WORKING clipboard in a non-secure context, so dropping the isSecureContext conjunct turns it red; and commit-message.txt is committed at the repo root
@jaylfc
jaylfc merged commit aadd193 into dev Sep 11, 2026
29 of 33 checks passed
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.

1 participant