Skip to content

feat: add copy-to-clipboard toolbar for message bubbles - #783

Merged
will-lamerton merged 8 commits into
Nano-Collective:mainfrom
arpan7sarkar:feat/copy-btn
Aug 5, 2026
Merged

feat: add copy-to-clipboard toolbar for message bubbles#783
will-lamerton merged 8 commits into
Nano-Collective:mainfrom
arpan7sarkar:feat/copy-btn

Conversation

@arpan7sarkar

Copy link
Copy Markdown
Contributor

Description

Adds a "Copy" button to both user prompt bubbles and AI response bubbles in the VS Code extension's chat panel (closes #746). Hovering over a message bubble reveals a subtle clipboard icon (bottom-right for user messages, bottom-left for agent messages); clicking it copies the raw markdown text via navigator.clipboard.writeText() and briefly swaps the icon for a checkmark to confirm the copy.

Screencast_20260803_234750.webm

For streamed agent responses, the copy button always reads the latest in-progress text (via a dataset.rawText kept in sync on each chunk) rather than a stale snapshot, so copying mid-stream works correctly.

Type of Change

  • New feature
  • Bug fix
  • Breaking change
  • Documentation update

Changeset

  • Added a changeset (pnpm changeset) describing this change for the changelog

nanocoder-vscode is listed in .changeset/config.json's ignore array, so no changeset is needed for this package.

Testing

Automated Tests

  • New features include passing
  • All existing tests pass (pnpm test:format, pnpm test:lint)
  • Tests cover both success and error scenarios

chat-panel.js is a plain DOM script with no existing test harness/spec coverage in this extension, so no automated test was added for the UI behavior. Verified pnpm run test:format and pnpm run
test:lint both pass.

Manual Testing

  • [x ] Tested with Ollama
  • [ x] Tested with OpenRouter
  • [ x] Tested with OpenAI-compatible API
  • Tested MCP integration

Built and installed the extension locally (pnpm run build:vscode) and confirmed it activates
correctly and the copy button rendechat panel. Full end-to-endmessage-send testing against a live provider is still pending on my end.

Checklist

  • Code follows project style gu
  • Self-review completed
  • Documentation updated (if needed)
  • No breaking changes (or clear
  • Appropriate logging added using structured logging (see CONTRIBUTING.md#logging) — n/a, no new logging paths introduced

…icks

Body (if you want more detail):
- Catch navigator.clipboard.writeText() rejections and show an error
  state instead of an unhandled promise rejection
- Reset via .finally() using a closure timer instead of storing state
  on the DOM element
- Disable pointer-events on the copy toolbar while hidden so hovering
  near a message can't trigger an accidental copy
Copilot AI lite review requested due to automatic review settings August 4, 2026 05:13

Copilot AI left a comment

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.

Pull request overview

Adds a hover-revealed copy-to-clipboard action on message bubbles in the VS Code extension chat panel, enabling quick copying of raw markdown for both user prompts and agent responses (including in-progress streamed output).

Changes:

  • Adds a reusable createCopyToolbar() and wraps message bubbles to show a copy icon on hover.
  • Ensures streamed agent responses copy the latest text by keeping wrapper.dataset.rawText synced per chunk.
  • Updates the generated Tailwind CSS output and adds a changeset entry for the main package changelog.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 2 comments.

File Description
plugins/vscode/media/chat-panel.js Adds wrapper + hover toolbar and hooks copy behavior into both static and streamed message rendering paths.
plugins/vscode/media/chat-panel.css Updates generated Tailwind output to include new utility classes used by the toolbar (e.g., pointer-events, sizing, variants).
.changeset/add-vscode-copy-button.md Records the feature in the monorepo changelog as a patch-level change.
Files not reviewed (1)
  • plugins/vscode/media/chat-panel.css: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +154 to +170
btn.addEventListener('click', () => {
const text = getText();
if (!text) return;
navigator.clipboard.writeText(text).then(() => {
btn.innerHTML = ICONS.success;
btn.title = 'Copied!';
}).catch(() => {
btn.innerHTML = ICONS.error;
btn.title = 'Copy failed';
}).finally(() => {
clearTimeout(resetTimer);
resetTimer = setTimeout(() => {
btn.innerHTML = ICONS.clipboard;
btn.title = 'Copy';
}, 1500);
});
});
Comment on lines +148 to +151
const btn = document.createElement('button');
btn.className = 'flex items-center justify-center bg-transparent border-none cursor-pointer text-vscode-fg opacity-60 hover:opacity-100 p-1 rounded hover:bg-vscode-toolbarHover [&_svg]:mr-0';
btn.title = 'Copy';
btn.innerHTML = ICONS.clipboard;
@arpan7sarkar

Copy link
Copy Markdown
Contributor Author

@akramcodez Check out this pr and tell me if any changes are needed , thanks

@akramcodez

Copy link
Copy Markdown
Collaborator

@arpan7sarkar Thanks for the contribution! I really like the overall implementation, especially the use of dataset.rawText to keep the copy button in sync with streamed responses. That's a clean solution for the mid-stream copy problem and makes sure users always copy the latest response rather than a stale snapshot.

I went through the issue, the implementation, and the user flow, and I have a few comments before I'd be comfortable approving this.

1. Missing Clipboard API Guard

What is wrong:

The copy button click handler assumes navigator.clipboard is always available.

Why is it wrong:

In some VS Code webview environments (or older runtimes), navigator.clipboard can be unavailable. Calling navigator.clipboard.writeText(...) in that case throws synchronously before the Promise is created, meaning the current .catch() block never runs and the UI won't enter the error state.

Where is it wrong:

plugins/vscode/media/chat-panel.js inside createCopyToolbar().

What should be done:

Guard against navigator.clipboard being unavailable and wrap the whole operation in a try/catch (preferably using async/await) so the button always shows the correct success or failure state.

2. Accessibility & Button Semantics

What is wrong:

The icon-only button doesn't specify type="button" and doesn't have an accessible name.

Why is it wrong:

Without an explicit button type, it could behave as a submit button if this UI is ever placed inside a form. Since the button only contains an SVG icon, screen readers also have no way of announcing its purpose.

Where is it wrong:

plugins/vscode/media/chat-panel.js inside createCopyToolbar().

What should be done:

Set:

  • btn.type = 'button'
  • btn.setAttribute('aria-label', 'Copy message')

to improve accessibility and future-proof the component.

3. Missing Test Coverage

What is wrong:

The new functionality currently relies entirely on manual verification.

Why is it wrong:

This PR introduces two important behaviors:

  • Copying static user/assistant messages.
  • Copying in-progress streamed responses using dataset.rawText.

Without automated coverage, future refactors could accidentally break the streamed-copy logic without anyone noticing.

What should be done:

I'd recommend adding tests covering:

  • Copying a normal message.
  • Copying an in-progress streamed response (ensuring the latest dataset.rawText is copied).
  • Clipboard failure path (button enters the error state).

4. UI Suggestion (Non-blocking)

This isn't a blocker, but I think the current placement of the copy button looks a little disconnected from the message bubble. It feels more like a floating action than metadata associated with that specific message.

Since Nanocoder already persists conversations, I think we could make better use of the bottom area of each message by displaying both the copy action and the local timestamp.

For example:

User message

┌──────────────┐
│ Hello        │
└──────────────┘
    2:34 PM  📋 

Assistant message

┌─────────────────────────────┐
│ Hello! I am Nanocoder...    │
└─────────────────────────────┘
📋  2:34 PM 

This keeps the layout balanced:

  • User messages: Copy button → Timestamp
  • Assistant messages: Timestamp → Copy button

I think this has a few advantages:

  • The copy action feels naturally attached to the message instead of floating beside it.
  • Users can immediately see when each prompt and response was sent.
  • Since conversations are already saved in history, users would still see the local timestamp when reopening previous chats, providing useful context about when a conversation happened.
  • It also leaves room for future message actions without changing the overall layout.

Overall, I think this is a really nice feature and the streaming solution is well thought out. Once the clipboard guard, accessibility improvements, and some automated coverage are added, I'd be happy to approve it. The timestamp suggestion is non-blocking, but I think it would make the overall chat experience feel much more polished.

@arpan7sarkar

Copy link
Copy Markdown
Contributor Author

Hey @akramcodez I looked into the best way to add these tests and wanted to flag something before just doing it.

chat-panel.js is a single top-level IIFE with no exports and no module system, and there's currently no test setup for this file at all (no DOM environment, nothing wired up). To actually test the copy-to-clipboard behavior (normal copy, streamed copy reading the live dataset.rawText, and the clipboard-failure state) against the real code, I need some way to run it in a fake DOM from Node.

I looked into jsdom for this — it's the standard tool for testing browser DOM code from Node, dev-only (never ships in the extension bundle or CLI), and it's what most projects reach for instead of hand-rolling a fake DOM. The alternative is writing our own minimal DOM stub with Node's built-in vm module, which avoids a new dependency but means we maintain that fake DOM ourselves and it'll quietly drift out of sync as chat-panel.js grows.

Do I have the go-ahead to add jsdom as a devDependency for this, or is there a different testing approach you'd prefer for this package?

@arpan7sarkar

Copy link
Copy Markdown
Contributor Author

I have added the timestamp chat messages.

image

@akramcodez

akramcodez commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

@arpan7sarkar Great work! Just a couple of small suggestions:

  • Could you remove the unnecessary code comments?
  • The timestamp and copy button look slightly misaligned. Could you align them so the bottom metadata row looks cleaner and more polished?

@arpan7sarkar

Copy link
Copy Markdown
Contributor Author

Hey @akramcodez ,
I have fixed the allignment issue
image

and removed unnecessary commits now you can merge it thanks.

@will-lamerton
will-lamerton merged commit 1b398c4 into Nano-Collective:main Aug 5, 2026
1 check passed
@will-lamerton

Copy link
Copy Markdown
Member

Thanks for this PR - feel free to add yourself as a contributor to our website via a PR which I will approve :)

https://nanocollective.org/contributors
https://github.com/Nano-Collective/organisation

@arpan7sarkar

arpan7sarkar commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Hi , @will-lamerton I have added the pr to add me as contributor here Nano-Collective/organisation#89 (comment) ,
I also have added another pr do check it out , #789

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.

[Feature] Universal Copy Buttons on Chat Bubbles in VS Code Extension

4 participants