feat: add Share button to package pages via Web Share API - #2997
Conversation
Adds a share button to the package header using navigator.share(), with a fallback to url/text-only sharing when the OG image can't be attached, plus a "v" keyboard shortcut and a command palette entry.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughSummary by CodeRabbit
WalkthroughThis PR adds native Web Share support for package pages through a client-only button and header actions. It exposes programmatic button clicks and adds translation, schema, and accessibility test updates. ChangesPackage Web Share feature
Sequence Diagram(s)sequenceDiagram
participant User
participant Header as Package/Header.vue
participant ShareButton as PackageShareButton
participant NavigatorShare as navigator.share
User->>Header: select package share action
Header->>Header: call sharePackage()
Header->>NavigatorShare: share package metadata and current URL
User->>ShareButton: click or press "v"
ShareButton->>ShareButton: build ShareData and optional image file
ShareButton->>NavigatorShare: share(shareData)
NavigatorShare-->>ShareButton: resolve or reject
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Lunaria Status Overview🌕 This pull request will trigger status changes. Learn moreBy default, every PR changing files present in the Lunaria configuration's You can change this by adding one of the keywords present in the Tracked Files
Warnings reference
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
app/components/Package/ShareButton.client.vue (2)
22-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winNo test coverage for share fallback logic.
getOgImageFile()andshare()contain the core fallback behaviour (capability probing, fetch failure handling, combined-payload gating) but no unit tests were added for this new logic.As per path instructions,
**/*.{test,spec}.{ts,tsx}should "Write unit tests for core functionality usingvitest"; this new share logic is a good candidate.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/components/Package/ShareButton.client.vue` around lines 22 - 36, Add unit tests for the share fallback behavior around getOgImageFile() and share() in ShareButton.client.vue, since the new capability probing, fetch/error handling, and combined-payload gating are not covered. Create vitest tests that exercise the main branches: navigator.canShare unavailable/false returns null, og:image missing or non-image content returns null, fetch failure is swallowed, and share() only includes the OG file when getOgImageFile() succeeds. Use the existing ShareButton.client.vue logic and its getOgImageFile/share symbols to locate the code.Source: Path instructions
57-57: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueSilently swallowing all share errors.
.catch(() => {})suppresses both expected cancellations (AbortError) and genuine failures. Consider only swallowingAbortErrorand logging other errors for observability.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/components/Package/ShareButton.client.vue` at line 57, The share flow in ShareButton.client.vue is swallowing every error from navigator.share, which hides real failures. Update the share handling around navigator.share(shareData) to only ignore expected AbortError cancellations, and route any other errors to the existing logging/observability path so genuine share failures can be diagnosed. Use the ShareButton component and its shareData/navigator.share call site to locate the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/components/Package/ShareButton.client.vue`:
- Around line 38-58: The package share payload is implemented inconsistently
between ShareButton.client.vue and Header.vue, so the command-palette share
action uses a less rich ShareData than the button and shortcut. Extract the
shared share flow into a common helper/composable such as usePackageShare() that
builds the title/text/url payload, optionally attaches the og:image file when
navigator.canShare allows it, and is reused by both share() and Header.vue's
sharePackage() so all entry points behave the same.
- Around line 10-20: The ShareButton.client.vue keyboard handler currently uses
a raw onKeyStroke, so the v shortcut ignores the global shortcuts toggle. Update
the shortcut registration in the ShareButton component to go through
useShortcuts(), matching the pattern used by Header.vue and other package
actions, and keep the existing isKeyWithoutModifiers / isEditableElement checks
and buttonRef click behavior intact.
---
Nitpick comments:
In `@app/components/Package/ShareButton.client.vue`:
- Around line 22-36: Add unit tests for the share fallback behavior around
getOgImageFile() and share() in ShareButton.client.vue, since the new capability
probing, fetch/error handling, and combined-payload gating are not covered.
Create vitest tests that exercise the main branches: navigator.canShare
unavailable/false returns null, og:image missing or non-image content returns
null, fetch failure is swallowed, and share() only includes the OG file when
getOgImageFile() succeeds. Use the existing ShareButton.client.vue logic and its
getOgImageFile/share symbols to locate the code.
- Line 57: The share flow in ShareButton.client.vue is swallowing every error
from navigator.share, which hides real failures. Update the share handling
around navigator.share(shareData) to only ignore expected AbortError
cancellations, and route any other errors to the existing logging/observability
path so genuine share failures can be diagnosed. Use the ShareButton component
and its shareData/navigator.share call site to locate the change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 26b214ff-9bba-4cf5-875b-f3a1d72c6e10
📒 Files selected for processing (6)
app/components/Button/Base.vueapp/components/Package/Header.vueapp/components/Package/ShareButton.client.vuei18n/locales/en.jsoni18n/schema.jsontest/unit/a11y-component-coverage.spec.ts
| async function share() { | ||
| const shareData: ShareData = { | ||
| title: props.packageName, | ||
| text: props.description ?? props.packageName, | ||
| url: window.location.href, | ||
| } | ||
|
|
||
| const imageFile = await getOgImageFile() | ||
| if (imageFile) { | ||
| const shareDataWithFile: ShareData = { ...shareData, files: [imageFile] } | ||
| // Some implementations support sharing files or url/text, but not the | ||
| // combination of both. Only attach the file once the full payload | ||
| // (title + text + url + files) is confirmed shareable, so we keep the | ||
| // url/text fallback otherwise. | ||
| if (navigator.canShare?.(shareDataWithFile)) { | ||
| shareData.files = shareDataWithFile.files | ||
| } | ||
| } | ||
|
|
||
| await navigator.share(shareData).catch(() => {}) | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Share payload diverges from the command-palette share action.
share() here conditionally attaches an og:image file when fully shareable, but Header.vue's sharePackage() (used by the "package-share" command) only ever shares title/text/url. Users triggering share via the command palette get a different, less-rich payload than users clicking the button or using the v shortcut.
Consider extracting the shared logic (title/text/url + optional image attachment) into a composable (e.g. usePackageShare()) consumed by both Header.vue and this component, so all three entry points behave identically.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/components/Package/ShareButton.client.vue` around lines 38 - 58, The
package share payload is implemented inconsistently between
ShareButton.client.vue and Header.vue, so the command-palette share action uses
a less rich ShareData than the button and shortcut. Extract the shared share
flow into a common helper/composable such as usePackageShare() that builds the
title/text/url payload, optionally attaches the og:image file when
navigator.canShare allows it, and is reused by both share() and Header.vue's
sharePackage() so all entry points behave the same.
…ate PRs Per review feedback, keeps this PR focused on PWA installability, WCO, and app badging. The Share button and native switch polyfill now live in npmx-dev#2997 and npmx-dev#2998 respectively.
|
CC: @userquin per his request :-) |
Summary
navigator.share(), attaching the package's OG image when the full payload (title/text/url/files) is confirmed shareable, falling back to url/text-only otherwise.vkeyboard shortcut and a command palette entry for sharing the current package.Split out of #2982 per review feedback to keep that PR focused on PWA/WCO/badging.
Test plan
pnpm test:unit— 1673/1673 passingpnpm run test:types— passingvp lint— 0 errorsnode scripts/find-invalid-translations.ts— no missing/unused i18n keysknip— clean