Skip to content

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

Closed
howwohmm wants to merge 6 commits into
npmx-dev:mainfrom
howwohmm:fix/safari-readme-clipboard
Closed

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

Conversation

@howwohmm

@howwohmmhowwohmm commented Mar 22, 2026

Copy link
Copy Markdown
Contributor

🔗 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 intoClipboardItem, so clipboard.write() stays synchronous within the gesture while the data resolves internally.

constitem=newClipboardItem({'text/plain': (async()=>{awaitfetchReadmeMarkdown()returnnewBlob([markdown],{type: 'text/plain'})})(),})awaitnavigator.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

Fixesnpmx-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

vercelBot commented Mar 22, 2026

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentActionsUpdated (UTC)
npmx.devReadyReadyPreview, CommentMar 22, 2026 9:44am
2 Skipped Deployments
ProjectDeploymentActionsUpdated (UTC)
docs.npmx.devIgnoredIgnoredPreviewMar 22, 2026 9:44am
npmx-lunariaIgnoredIgnoredMar 22, 2026 9:44am

Request Review

@coderabbitai

coderabbitaiBot commented Mar 22, 2026

Copy link
Copy Markdown
Contributor

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 useClipboardcopied/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 nameStatusExplanation
Description check✅ PassedThe 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✅ PassedThe 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✅ PassedAll 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.ymlconfiguration file to your project to enable OpenGrep analysis.

@codecov

codecovBot commented Mar 22, 2026

Copy link
Copy Markdown

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 linesPatch %Lines
app/pages/package/[[org]]/[name].vue0.00%11 Missing and 4 partials ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitaicoderabbitaiBot 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.

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

Comment threadapp/pages/package/[[org]]/[name].vue Outdated
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>
@howwohmmhowwohmm changed the title fix: Safari clipboard fallback for Copy as Markdown buttonfix(ui): Safari clipboard fallback for Copy as Markdown buttonMar 22, 2026
@howwohmmhowwohmm changed the title fix(ui): Safari clipboard fallback for Copy as Markdown buttonfix(ui): safari clipboard fallback for copy as markdown buttonMar 22, 2026
howwohmmand 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
Copy Markdown
ContributorAuthor

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.

@serhalpserhalp added the needs review This PR is waiting for a review from a maintainer label Apr 6, 2026
@serhalp
serhalp requested a review from ghostdevvApril 6, 2026 13:52
@MatteoGabriele

Copy link
Copy Markdown
Member

Thank you for the contribution, but I will close this PR as a fix has already been merged.

@ghostdevvghostdevv removed the needs review This PR is waiting for a review from a maintainer label Apr 19, 2026
Sign up for freeto 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

4 participants

@howwohmm@MatteoGabriele@serhalp@ghostdevv