Skip to content

fix(ui): safari clipboard fallback for copy as markdown button#2189

Open
howwohmm wants to merge 6 commits intonpmx-dev:mainfrom
howwohmm:fix/safari-readme-clipboard
Open

fix(ui): safari clipboard fallback for copy as markdown button#2189
howwohmm wants to merge 6 commits intonpmx-dev:mainfrom
howwohmm:fix/safari-readme-clipboard

Conversation

@howwohmm
Copy link

@howwohmm howwohmm commented Mar 22, 2026

🔗 Linked issue

Fixes #2151

🧭 Context

Safari requires clipboard writes to happen synchronously within a user gesture. The copyReadmeHandler does await fetchReadmeMarkdown() before writing to clipboard, breaking that chain — so writeText() silently rejects.

📚 Description

Uses the ClipboardItem with Promise pattern: pass the async fetch as a Promise into ClipboardItem, so clipboard.write() stays synchronous within the gesture while the data resolves internally.

const item = new ClipboardItem({
  'text/plain': (async () => {
    await fetchReadmeMarkdown()
    return new Blob([markdown], { type: 'text/plain' })
  })(),
})
await navigator.clipboard.write([item])

Fallback chain for older browsers: writeText()execCommand('copy').

What it doesn't change

  • No changes to prefetch behavior or existing hover prefetch
  • No new dependencies or components
  • Template stays untouched — only the handler logic changes

Fixes npmx-dev#2151

Safari requires navigator.clipboard.writeText() to be called
synchronously within a user gesture. The async fetchReadmeMarkdown()
breaks that chain, causing the clipboard write to silently fail.

Added a document.execCommand('copy') fallback via a temporary textarea
when the Clipboard API rejects. Also bypassed useClipboard's copy()
to directly use navigator.clipboard.writeText() so the rejection is
catchable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Mar 22, 2026

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

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Mar 22, 2026 9:44am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Mar 22, 2026 9:44am
npmx-lunaria Ignored Ignored Mar 22, 2026 9:44am

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 22, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ca53ed16-6758-460c-b92f-ad992f4d0e48

📥 Commits

Reviewing files that changed from the base of the PR and between 85e31f3 and 61f74ca.

📒 Files selected for processing (1)
  • app/pages/package/[[org]]/[name].vue
✅ Files skipped from review due to trivial changes (1)
  • app/pages/package/[[org]]/[name].vue

📝 Walkthrough

Walkthrough

Replaces the previous useClipboard copied/copy flow with a local copiedReadme = shallowRef(false) and a new copyReadmeHandler(). The handler fetches README Markdown on demand and attempts clipboard writes in sequence: navigator.clipboard.write() with a ClipboardItem whose text/plain payload is produced from an async Promise that converts the fetched markdown to a Blob; if that fails it retries navigator.clipboard.writeText(markdown); if that fails it falls back to document.execCommand('copy') using a temporary offscreen textarea. copiedReadme is set true and reset after 2000ms only when a clipboard method succeeds.

Possibly related PRs

Suggested reviewers

  • ghostdevv
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The PR description clearly explains the problem with Safari clipboard functionality and describes the implementation using the ClipboardItem-with-Promise pattern along with fallback chains.
Linked Issues check ✅ Passed The PR implementation aligns with issue #2151 requirements: uses ClipboardItem-with-Promise for Safari compatibility, provides fallback chain (writeText → execCommand), maintains existing UX, and restores clipboard copy functionality.
Out of Scope Changes check ✅ Passed All changes are confined to the README copy handler logic in a single file, directly addressing the Safari clipboard issue without modifying prefetch behaviour, dependencies, or template.

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

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

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.

Tip

CodeRabbit can use OpenGrep to find security vulnerabilities and bugs across 17+ programming languages.

OpenGrep is compatible with Semgrep configurations. Add an opengrep.yml or semgrep.yml configuration file to your project to enable OpenGrep analysis.

@codecov
Copy link

codecov bot commented Mar 22, 2026

Codecov Report

❌ Patch coverage is 0% with 15 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
app/pages/package/[[org]]/[name].vue 0.00% 11 Missing and 4 partials ⚠️

📢 Thoughts on this report? Let us know!

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: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b55b1cec-5923-49fb-99ba-f3ae41bd8f37

📥 Commits

Reviewing files that changed from the base of the PR and between 7f2fc1a and 2e946dc.

📒 Files selected for processing (1)
  • app/pages/package/[[org]]/[name].vue

copiedReadme from useClipboard's copied is a read-only computed —
direct assignment fails type checking. Since we now bypass
useClipboard's copy() entirely, replace with a writable shallowRef.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@howwohmm howwohmm changed the title fix: Safari clipboard fallback for Copy as Markdown button fix(ui): Safari clipboard fallback for Copy as Markdown button Mar 22, 2026
@howwohmm howwohmm changed the title fix(ui): Safari clipboard fallback for Copy as Markdown button fix(ui): safari clipboard fallback for copy as markdown button Mar 22, 2026
howwohmm and others added 2 commits March 22, 2026 14:56
Replace writeText() + execCommand fallback with the ClipboardItem
Promise pattern. Passing the async fetch as a Promise into
ClipboardItem keeps the clipboard.write() call synchronous within
the user gesture, which is what Safari requires.

Ref: https://wolfgangrittner.dev/how-to-use-clipboard-api-in-safari/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@howwohmm
Copy link
Author

hey @ghostdevv — this uses the ClipboardItem-with-Promise pattern from the blog post you linked on #2182 (https://wolfgangrittner.dev/how-to-use-clipboard-api-in-safari/). the key difference from #2182 is that the async fetch happens inside the ClipboardItem blob, keeping clipboard.write() synchronous within the user gesture — which is what Safari requires.

also has a full fallback chain (writeText → execCommand) for browsers without ClipboardItem Promise support. all CI is green.

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.

"Copy as Markdown" button on README doesn't copy to clipboard in Safari

1 participant