Skip to content

feat(ui): premium app features — installability, WCO, share, badging#2982

Open
tomayac wants to merge 19 commits into
npmx-dev:mainfrom
tomayac:pwa-and-premium-app-features
Open

feat(ui): premium app features — installability, WCO, share, badging#2982
tomayac wants to merge 19 commits into
npmx-dev:mainfrom
tomayac:pwa-and-premium-app-features

Conversation

@tomayac

@tomayac tomayac commented Jul 1, 2026

Copy link
Copy Markdown

Summary

  • Enable PWA installability (@vite-pwa/nuxt) with a manifest id and generated install-UI screenshots.
  • Add Web Share API support on package pages and a Window Controls Overlay + app shortcuts layout.
  • Add the App Badging API to surface new-likes counts on the app icon.
  • Replace the custom toggle component with the native <input type="checkbox" switch> polyfill, kept live-synced with the accent color/theme.
  • Keep theme-color in sync with --bg so the WCO title-bar strip matches the header.

Test plan

  • pnpm test:unit — 1652/1652 passing
  • pnpm run test:types — passing (fixed 2 pre-existing type errors in the switch polyfill plugin)
  • vp lint — 0 errors (3 pre-existing style warnings, unrelated to correctness)
  • pnpm run build — succeeds

tomayac added 10 commits June 18, 2026 19:34
Enable the service worker (previously disabled), configure Workbox with
network-only navigation fallback to preserve ISR freshness, and add
standalone display mode to the manifest so browsers offer installation.

Add a PwaPrompt component that surfaces a toast when a new SW version is
waiting, and an install button in settings that appears only when the
browser fires beforeinstallprompt.
Adds a Share button to the package header button group (alongside Compare
and Likes) that invokes navigator.share with the package name, description,
and current URL. The button only renders when the browser supports the Web
Share API and is also registered as a command palette action.
Enable display_override: window-controls-overlay so the app header fills
the installed-PWA title bar. The header background becomes the drag handle;
the nav is explicitly non-draggable. env(titlebar-area-y/x) push content
below the OS controls and clear the macOS traffic lights.

Add three manifest shortcuts (Search, Compare, Settings) so users can
jump straight to key destinations from the home-screen/taskbar icon.
Use input-switch-polyfill so Safari renders a native OS switch control
and other browsers get a consistent polyfilled fallback. The elaborate
hand-painted CSS is replaced by a single accent-color custom property
that integrates with the user's chosen accent. Polyfill JS is loaded
lazily only when 'switch' is absent from HTMLInputElement.prototype.
useAppBadge wraps navigator.setAppBadge/clearAppBadge with feature
detection. useLikesBadge activates when the npm connector is connected:
it loads the user's packages once, then polls /api/social/likes/:pkg
every 10 minutes and sets the badge to the count of new likes received
since the previous check. The baseline is persisted in localStorage per
npm username so the badge never fires on the very first visit.
Adds a Playwright-based script (scripts/generate-pwa-screenshots.ts)
that captures the homepage and a package page in light + dark mode at
desktop (1280×800) and mobile (390×844) viewports.

The script auto-starts nuxt preview, takes all 8 screenshots, then
stops the server. Pass --url <url> to skip the local server and
screenshot any running instance (useful in CI: --url https://npmx.dev).

The PWA manifest now includes a screenshots[] array (nuxt.config.ts)
with form_factor 'wide'/'narrow' entries, enabling Chrome's richer
install dialog on desktop (Chrome 108+) and Android (Chrome 94+).

Usage:
  pnpm build && pnpm generate:screenshots
  # commit public/screenshots/*.png
Add id: '/' to the web app manifest for stable PWA identity across
manifest URL changes (spec-recommended practice).

Generate all 8 PWA screenshots (desktop/mobile × dark/light × home/package)
and commit them so Vercel CI picks them up. Chrome 108+ on desktop will
show these in the richer install dialog carousel.
Window Controls Overlay:
- Fix header and scroll container being 15 px short of right edge by
  resetting scrollbar-gutter to auto in WCO mode (html { scrollbar-gutter:
  stable } reserved a gutter even when overflow: hidden removed the bar)
- Make header position:fixed and span full viewport width (inset-inline: 0)
  in WCO mode so its border-bottom reaches the window edge
- Add #app-scroll fixed scroll container starting at the header's bottom
  border so the scrollbar track never appears in the title bar
- Solid opaque header background (--bg) and no backdrop-filter in WCO mode
- Full-width nav with two-value padding-inline to handle both macOS traffic
  lights (left) and Windows min/max/close (right)
- All interactive descendants declare no-drag; empty header space is drag
- Keep theme-color meta in sync with --bg (oklch) by writing directly to the
  DOM node and guarding against @unhead re-asserting the PWA module's static
  value after onMounted

Share button:
- Register 'v' keyboard shortcut; programmatic click anchors the share sheet
  at the button position instead of the mouse cursor
- Include og:image as a shareable file when the browser supports file sharing
- Expose click() on ButtonBase via defineExpose for programmatic triggering

input-switch-polyfill:
- Bump to 1.12.0
- Add MutationObserver that re-syncs --switch-accent on every color-mode or
  accent-color change so switches react live without a page reload
Adds an ambient module declaration for the untyped input-switch-polyfill
package and fixes a noUncheckedIndexedAccess violation when reading the
first matched switch element.
@vercel

vercel Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Jul 2, 2026 10:36am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Jul 2, 2026 10:36am
npmx-lunaria Ignored Ignored Jul 2, 2026 10:36am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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
📝 Walkthrough

Walkthrough

This PR adds PWA install/update support and manifest updates, package sharing, badge handling, window-controls-overlay shell behaviour, and switch polyfill support, with matching locale, schema, dependency, and screenshot tooling changes.

Changes

PWA, sharing, badges, and shell support

Layer / File(s) Summary
PWA config, manifest, and screenshots
nuxt.config.ts, scripts/generate-pwa-screenshots.ts, package.json, app/pages/settings.vue, app/components/PwaPrompt.client.vue, i18n/locales/en.json, i18n/schema.json, test/unit/a11y-component-coverage.spec.ts
Enables service-worker generation with workbox config, expands the manifest with install metadata, shortcuts, and screenshots, adds a screenshot-generation script, an update/install prompt component, a settings install section, corresponding locale/schema strings, and a11y test allowlist entries.
Package sharing UI and command wiring
app/components/Button/Base.vue, app/components/Package/ShareButton.client.vue, app/components/Package/Header.vue, i18n/locales/en.json, i18n/schema.json
Adds a Web Share API-gated share button with OG image attachment, exposes a click method on the base button, wires a package-share command into the header, renders the share button in package metrics, and adds share locale/schema strings.
App badge and likes polling
app/composables/useAppBadge.ts, app/composables/useLikesBadge.ts, app/plugins/likes-badge.client.ts
Adds an app badge composable and a likes-polling composable with localStorage persistence and interval-based refresh, initialised via a client plugin.
Window controls overlay shell and header
app/app.vue, app/components/AppHeader.vue
Synchronises the theme-colour meta tag with the --bg CSS variable, restructures the layout with a dedicated scroll container, and applies fixed-position header/scroll styling for window-controls-overlay display mode.
Switch polyfill and toggle styling
app/plugins/input-switch-polyfill.client.ts, nuxt.config.ts, package.json, app/components/Settings/Toggle.client.vue, pnpm-workspace.yaml
Adds a client plugin that conditionally loads a switch input polyfill and synchronises accent colour, registers the polyfill stylesheet/dependency, pins Vue, and simplifies the settings toggle input styling.

Sequence Diagram(s)

sequenceDiagram
  participant ServiceWorker
  participant PwaPrompt
  participant SettingsPage
  participant User

  ServiceWorker->>PwaPrompt: needRefresh
  PwaPrompt->>User: show update toast
  User->>PwaPrompt: updateServiceWorker()
  User->>SettingsPage: open install section
  SettingsPage->>User: show install button
  User->>SettingsPage: $pwa.install()
Loading
sequenceDiagram
  participant User
  participant PackageHeader
  participant ShareButton
  participant Navigator

  User->>PackageHeader: choose share command or button
  PackageHeader->>ShareButton: trigger sharePackage()
  ShareButton->>ShareButton: read og:image and build ShareData
  ShareButton->>Navigator: navigator.share(data)
  Navigator-->>User: native share sheet
Loading
sequenceDiagram
  participant LikesBadgePlugin
  participant useLikesBadge
  participant API
  participant LocalStorage
  participant useAppBadge

  LikesBadgePlugin->>useLikesBadge: initialise
  useLikesBadge->>API: list likes for cached packages
  API-->>useLikesBadge: counts
  useLikesBadge->>LocalStorage: loadStored / saveStored
  useLikesBadge->>useAppBadge: setBadge() or clearBadge()
Loading
sequenceDiagram
  participant BrowserChrome
  participant AppShell
  participant AppHeader
  participant AppScroll

  BrowserChrome->>AppShell: display-mode: window-controls-overlay
  AppShell->>AppHeader: fixed top header styles
  AppShell->>AppScroll: fixed internal scroll region
  AppShell->>BrowserChrome: theme-color meta updated from --bg
Loading

Suggested reviewers: danielroe, ghostdevv

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately highlights the main additions: installability, Window Controls Overlay, sharing, and app badging.
Description check ✅ Passed The description matches the changeset and summarises the PWA, share, WCO, badging, toggle polyfill, and theme-colour updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

Hello! Thank you for opening your first PR to npmx, @tomayac! 🚀

Here’s what will happen next:

  1. Our GitHub bots will run to check your changes.
    If they spot any issues you will see some error messages on this PR.
    Don’t hesitate to ask any questions if you’re not sure what these mean!

  2. In a few minutes, you’ll be able to see a preview of your changes on Vercel

  3. One or more of our maintainers will take a look and may ask you to make changes.
    We try to be responsive, but don’t worry if this takes a few days.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

Lunaria Status Overview

🌕 This pull request will trigger status changes.

Learn more

By default, every PR changing files present in the Lunaria configuration's files property will be considered and trigger status changes accordingly.

You can change this by adding one of the keywords present in the ignoreKeywords property in your Lunaria configuration file in the PR's title (ignoring all files) or by including a tracker directive in the merged commit's description.

Tracked Files

File Note
i18n/locales/en.json Source changed, localizations will be marked as outdated.
Warnings reference
Icon Description
🔄️ The source for this localization has been updated since the creation of this pull request, make sure all changes in the source have been applied.

@socket-security

socket-security Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedinput-switch-polyfill@​1.12.07310010091100
Addedsrvx@​0.11.151001009695100

View full report

@socket-security

socket-security Bot commented Jul 1, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm @emnapi/runtime is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: pnpm-lock.yamlnpm/@nuxt/a11y@1.0.0-alpha.1npm/@nuxt/fonts@0.14.0npm/@unocss/nuxt@66.6.7npm/unocss@66.6.7npm/@nuxtjs/i18n@10.2.4npm/tsdown@0.21.7npm/docus@5.9.0npm/@nuxt/ui@4.6.1npm/@storybook/addon-docs@10.3.5npm/@nuxt/scripts@1.0.1npm/@nuxt/test-utils@4.0.3npm/@voidzero-dev/vite-plus-test@0.1.20npm/vite-plugin-pwa@1.3.0npm/unplugin-vue-markdown@32.0.0npm/nuxt@4.4.8npm/nuxt-og-image@6.7.0npm/@emnapi/runtime@1.11.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@emnapi/runtime@1.11.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm es-abstract is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: pnpm-lock.yamlnpm/vite-plugin-pwa@1.3.0npm/es-abstract@1.24.2

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/es-abstract@1.24.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm js-yaml is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: pnpm-lock.yamlnpm/@nuxtjs/i18n@10.2.4npm/nuxt@4.4.8npm/@e18e/eslint-plugin@0.5.1npm/eslint-plugin-regexp@3.1.1npm/js-yaml@4.2.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/js-yaml@4.2.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm rollup is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: pnpm-lock.yamlnpm/vite-plugin-pwa@1.3.0npm/rollup@2.80.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/rollup@2.80.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm seroval is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: pnpm-lock.yamlnpm/seroval@1.5.3

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/seroval@1.5.3. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

❌ 10 Tests Failed:

Tests completed Failed Passed Skipped
2979 10 2969 5
View the top 3 failed test(s) by shortest run time
test/nuxt/components/HeaderConnectorModal.spec.ts > HeaderConnectorModal > Connected state > shows disconnect button
Stack Traces | 0.0971s run time
AssertionError: expected undefined not to be undefined
 ❯ .../nuxt/components/HeaderConnectorModal.spec.ts:444:6
 ❯ node_modules/.pnpm/@voidzero-dev+vite-plus-test@0.1.20_@opentelemetry+api@1.9.0_@types+node@24.12.0_@vites_5c64e05fabd4903676e666e9babf7cd7/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/runner/chunk-artifact.js?v=e7ff225d:1903:22
test/nuxt/components/HeaderConnectorModal.spec.ts > HeaderConnectorModal > Connected state > shows logged in username
Stack Traces | 0.104s run time
AssertionError: expected 'Local ConnectorRun the connector on y…' to contain 'testuser'

Expected: "testuser"
Received: "Local ConnectorRun the connector on your machine to enable admin features.$npx npmx-connectornpmpnpmyarnbundenovltvpThen paste the token below to connect:TokenAdvanced optionsPortAutomatically open auth pageWARNINGThis allows npmx to access your npm CLI. Only connect to sites you trust.Connect"

 ❯ .../nuxt/components/HeaderConnectorModal.spec.ts:435:6
 ❯ node_modules/.pnpm/@voidzero-dev+vite-plus-test@0.1.20_@opentelemetry+api@1.9.0_@types+node@24.12.0_@vites_5c64e05fabd4903676e666e9babf7cd7/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/runner/chunk-artifact.js?v=e7ff225d:1903:22
test/nuxt/components/HeaderConnectorModal.spec.ts > HeaderConnectorModal > Connected state > shows connected status
Stack Traces | 0.11s run time
AssertionError: expected 'Local ConnectorRun the connector on y…' to contain 'Connected'

Expected: "Connected"
Received: "Local ConnectorRun the connector on your machine to enable admin features.$npx npmx-connectornpmpnpmyarnbundenovltvpThen paste the token below to connect:TokenAdvanced optionsPortAutomatically open auth pageWARNINGThis allows npmx to access your npm CLI. Only connect to sites you trust.Connect"

 ❯ .../nuxt/components/HeaderConnectorModal.spec.ts:430:6
 ❯ node_modules/.pnpm/@voidzero-dev+vite-plus-test@0.1.20_@opentelemetry+api@1.9.0_@types+node@24.12.0_@vites_5c64e05fabd4903676e666e9babf7cd7/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/runner/chunk-artifact.js?v=e7ff225d:1903:22
test/nuxt/components/HeaderConnectorModal.spec.ts > HeaderConnectorModal > Connected state > hides connection form when connected
Stack Traces | 0.131s run time
AssertionError: expected <form class="space-y-4">…(6)</form> to be null

- Expected:
null

+ Received:
<form
  class="space-y-4"
>
  <p
    class="text-sm text-fg-muted"
  >
    Run the connector on your machine to enable admin features.
  </p>
  <div
    class="flex items-center p-3 bg-bg-muted border border-border rounded-lg font-mono text-sm"
    dir="ltr"
  >
    <span
      class="text-fg-subtle"
    >
      $
    </span>
    <span
      class="text-fg-subtle ms-2"
    >
      npx npmx-connector
    </span>
    <div
      class="ms-auto flex items-center gap-2"
    >
      <!-- Disable teleport in a modal dialog -->
      
      <button
        aria-expanded="false"
        aria-haspopup="listbox"
        aria-label="Package manager"
        class="cursor-pointer flex items-center gap-1.5 px-2 py-2 font-mono text-xs text-fg-muted bg-bg-subtle border border-border-subtle border-solid rounded-md transition-colors duration-150 hover:text-fg hover:border-border-hover active:scale-95 focus:border-border-hover focus-visible:outline-accent/70"
        type="button"
      >
        
        
        <span
          aria-hidden="true"
          class="inline-block h-3 w-3 pm-select-content i-simple-icons:npm"
          data-pm-select="npm"
        />
        <span
          aria-hidden="false"
          class="pm-select-content"
          data-pm-select="npm"
        >
          npm
        </span>
        
        
        <span
          aria-hidden="true"
          class="inline-block h-3 w-3 pm-select-content i-simple-icons:pnpm"
          data-pm-select="pnpm"
        />
        <span
          aria-hidden="true"
          class="pm-select-content"
          data-pm-select="pnpm"
        >
          pnpm
        </span>
        
        
        <span
          aria-hidden="true"
          class="inline-block h-3 w-3 pm-select-content i-simple-icons:yarn"
          data-pm-select="yarn"
        />
        <span
          aria-hidden="true"
          class="pm-select-content"
          data-pm-select="yarn"
        >
          yarn
        </span>
        
        
        <span
          aria-hidden="true"
          class="inline-block h-3 w-3 pm-select-content i-simple-icons:bun"
          data-pm-select="bun"
        />
        <span
          aria-hidden="true"
          class="pm-select-content"
          data-pm-select="bun"
        >
          bun
        </span>
        
        
        <span
          aria-hidden="true"
          class="inline-block h-3 w-3 pm-select-content i-simple-icons:deno"
          data-pm-select="deno"
        />
        <span
          aria-hidden="true"
          class="pm-select-content"
          data-pm-select="deno"
        >
          deno
        </span>
        
        
        <span
          aria-hidden="true"
          class="inline-block h-3 w-3 pm-select-content i-custom-vlt"
          data-pm-select="vlt"
        />
        <span
          aria-hidden="true"
          class="pm-select-content"
          data-pm-select="vlt"
        >
          vlt
        </span>
        
        
        <span
          aria-hidden="true"
          class="inline-block h-3 w-3 pm-select-content i-simple-icons:vite"
          data-pm-select="vp"
        />
        <span
          aria-hidden="true"
          class="pm-select-content"
          data-pm-select="vp"
        >
          vp
        </span>
        
        
        <span
          aria-hidden="true"
          class="i-lucide:chevron-down w-3 h-3 transition-transform duration-200"
        />
      </button>
      <!-- Dropdown menu (teleported to body to avoid clipping) -->
      <!--teleport start-->
      <transition-stub
        appear="false"
        css="true"
        enteractiveclass="transition-opacity duration-150"
        enterfromclass="opacity-0"
        entertoclass="opacity-100"
        leaveactiveclass="transition-opacity duration-100"
        leavefromclass="opacity-100"
        leavetoclass="opacity-0"
        persisted="false"
      >
        <!--v-if-->
      </transition-stub>
      <!--teleport end-->
      
      <button
        aria-label="Copy command"
        class="group gap-x-1 items-center justify-center font-mono border border-border rounded-md transition-all duration-200 cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed disabled:border-transparent inline-flex text-sm p-2 bg-transparent text-fg hover:enabled:bg-fg/10 focus-visible:enabled:bg-fg/10 aria-pressed:bg-fg/10 aria-pressed:border-fg/20 aria-pressed:hover:enabled:bg-fg/20 aria-pressed:hover:enabled:text-fg/50 ms-auto"
        type="button"
      >
        <span
          aria-hidden="true"
          class="size-[1em] i-lucide:copy"
        />
        
        
        <!--v-if-->
      </button>
    </div>
  </div>
  <p
    class="text-sm text-fg-muted"
  >
    Then paste the token below to connect:
  </p>
  <div
    class="space-y-3"
  >
    <div>
      <label
        class="block text-xs text-fg-subtle uppercase tracking-wider mb-1.5"
        for="connector-token"
      >
        Token
      </label>
      <input
        autocapitalize="off"
        autocomplete="off"
        autocorrect="off"
        class="appearance-none bg-bg-subtle border border-border font-mono text-fg placeholder:text-fg-subtle transition-[border-color,outline-color] duration-300 hover:border-fg-subtle outline-2 outline-transparent outline-offset-2 focus:border-accent focus-visible:outline-accent/70 disabled:opacity-50 disabled:cursor-not-allowed text-sm leading-none px-3 py-2.5 rounded-lg w-full"
        id="connector-token"
        name="connector-token"
        placeholder="paste token here..."
        spellcheck="false"
        type="password"
      />
    </div>
    <details
      class="text-sm"
    >
      <summary
        class="text-fg-subtle hover:text-fg-muted transition-colors duration-200"
      >
        Advanced options
      </summary>
      <div
        class="mt-3"
      >
        <label
          class="block text-xs text-fg-subtle uppercase tracking-wider mb-1.5"
          for="connector-port"
        >
          Port
        </label>
        <input
          autocapitalize="off"
          autocomplete="off"
          autocorrect="off"
          class="appearance-none bg-bg-subtle border border-border font-mono text-fg placeholder:text-fg-subtle transition-[border-color,outline-color] duration-300 hover:border-fg-subtle outline-2 outline-transparent outline-offset-2 focus:border-accent focus-visible:outline-accent/70 disabled:opacity-50 disabled:cursor-not-allowed text-sm leading-none px-3 py-2.5 rounded-lg w-full"
          id="connector-port"
          inputmode="numeric"
          name="connector-port"
          spellcheck="false"
          type="text"
        />
        <div
          class="border-t border-border my-3"
        />
        <div
          class="flex flex-col gap-2"
        >
          
          <label
            class="grid items-center gap-1.5 py-1 -my-1 grid-cols-[auto_1fr_auto] cursor-pointer"
            for="v-0-0-1"
            style="grid-template-areas: \"label-text . toggle\";"
          >
            <span
              class="text-sm text-fg font-medium text-start"
              style="grid-area: label-text;"
            >
              Automatically open auth page
            </span>
            <input
              class="shrink-0 focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2 switch horizontal-tb"
              id="v-0-0-1"
              role="switch"
              style="grid-area: toggle; justify-self: end; accent-color: var(--accent); --switch-accent: oklch(0.5 0.16 247.27);"
              switch=""
              type="checkbox"
            />
          </label>
          <!--v-if-->
          
        </div>
      </div>
    </details>
  </div>
  <!-- Error message (only show after user explicitly clicks Connect) -->
  <!--v-if-->
  <!-- Warning message -->
  <div
    class="p-3 text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-md"
    role="alert"
  >
    <p
      class="inline-block text-xs font-bold uppercase tracking-wider text-fg rounded"
    >
      WARNING
    </p>
    <p
      class="text-sm text-fg-muted mt-1"
    >
      This allows npmx to access your npm CLI. Only connect to sites you trust.
    </p>
  </div>
  <button
    class="group gap-x-1 items-center justify-center font-mono border border-border rounded-md transition-all duration-200 cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed disabled:border-transparent inline-flex text-sm py-2 px-4 text-bg bg-fg hover:enabled:bg-fg/50 focus-visible:enabled:bg-fg/50 aria-pressed:bg-fg aria-pressed:text-bg aria-pressed:border-fg aria-pressed:hover:enabled:text-bg/50 w-full"
    disabled=""
    type="submit"
  >
    <!--v-if-->
    
    Connect
    
    <!--v-if-->
  </button>
</form>

 ❯ .../nuxt/components/HeaderConnectorModal.spec.ts:450:6
 ❯ node_modules/.pnpm/@voidzero-dev+vite-plus-test@0.1.20_@opentelemetry+api@1.9.0_@types+node@24.12.0_@vites_5c64e05fabd4903676e666e9babf7cd7/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/runner/chunk-artifact.js?v=e7ff225d:1903:22
test/nuxt/components/HeaderConnectorModal.spec.ts > HeaderConnectorModal > Disconnected state > shows error message when connection fails
Stack Traces | 0.139s run time
AssertionError: expected undefined not to be undefined
 ❯ .../nuxt/components/HeaderConnectorModal.spec.ts:423:6
 ❯ node_modules/.pnpm/@voidzero-dev+vite-plus-test@0.1.20_@opentelemetry+api@1.9.0_@types+node@24.12.0_@vites_5c64e05fabd4903676e666e9babf7cd7/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/runner/chunk-artifact.js?v=e7ff225d:1903:22
test/nuxt/components/HeaderConnectorModal.spec.ts > HeaderConnectorModal > Auth URL button > opens auth URL in new tab when button is clicked
Stack Traces | 0.144s run time
AssertionError: expected "vi.fn()" to be called with arguments: [ …(3) ]

Number of calls: 0

 ❯ Proxy.<anonymous> node_modules/.pnpm/@voidzero-dev+vite-plus-test@0.1.20_@opentelemetry+api@1.9.0_@types+node@24.12.0_@vites_5c64e05fabd4903676e666e9babf7cd7/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/expect/index.js?v=e7ff225d:1487:13
 ❯ Proxy.<anonymous> node_modules/.pnpm/@voidzero-dev+vite-plus-test@0.1.20_@opentelemetry+api@1.9.0_@types+node@24.12.0_@vites_5c64e05fabd4903676e666e9babf7cd7/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/expect/index.js?v=e7ff225d:1156:19
 ❯ Proxy.methodWrapper node_modules/.pnpm/@voidzero-dev+vite-plus-test@0.1.20_@opentelemetry+api@1.9.0_@types+node@24.12.0_@vites_5c64e05fabd4903676e666e9babf7cd7/node_modules/@.../dist/vendor/chai.mjs?v=e7ff225d:1333:23
 ❯ .../nuxt/components/HeaderConnectorModal.spec.ts:273:6
 ❯ node_modules/.pnpm/@voidzero-dev+vite-plus-test@0.1.20_@opentelemetry+api@1.9.0_@types+node@24.12.0_@vites_5c64e05fabd4903676e666e9babf7cd7/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/runner/chunk-artifact.js?v=e7ff225d:1903:22
test/nuxt/components/HeaderConnectorModal.spec.ts > HeaderConnectorModal > Operations queue in connected state > renders OTP prompt when operations have OTP failures
Stack Traces | 0.18s run time
AssertionError: expected '<!-- Modal top header section --><div…' to contain 'otp-input'

Expected: "otp-input"
Received: "<!-- Modal top header section --><div data-v-80f59d66="" class="flex items-center justify-between mb-6"><div data-v-80f59d66=""><h2 data-v-80f59d66="" id="connector-modal-title" class="font-mono text-lg font-medium">Local Connector</h2><!--v-if--></div><button data-v-80f59d66="" class="group gap-x-1 items-center justify-center font-mono border border-border rounded-md transition-all duration-200 cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed disabled:border-transparent inline-flex text-sm p-2 bg-transparent text-fg hover:enabled:bg-fg/10 focus-visible:enabled:bg-fg/10 aria-pressed:bg-fg/10 aria-pressed:border-fg/20 aria-pressed:hover:enabled:bg-fg/20 aria-pressed:hover:enabled:text-fg/50" type="button" aria-label="Close"><span class="size-[1em] i-lucide:x" aria-hidden="true"></span><!--v-if--></button></div><!-- Modal body content --><!-- Connected state --><!-- Disconnected state --><form class="space-y-4"><p class="text-sm text-fg-muted">Run the connector on your machine to enable admin features.</p><div class="flex items-center p-3 bg-bg-muted border border-border rounded-lg font-mono text-sm" dir="ltr"><span class="text-fg-subtle">$</span><span class="text-fg-subtle ms-2">npx npmx-connector</span><div class="ms-auto flex items-center gap-2"><!-- Disable teleport in a modal dialog --><button type="button" class="cursor-pointer flex items-center gap-1.5 px-2 py-2 font-mono text-xs text-fg-muted bg-bg-subtle border border-border-subtle border-solid rounded-md transition-colors duration-150 hover:text-fg hover:border-border-hover active:scale-95 focus:border-border-hover focus-visible:outline-accent/70" aria-expanded="false" aria-haspopup="listbox" aria-label="Package manager"><span class="inline-block h-3 w-3 pm-select-content i-simple-icons:npm" data-pm-select="npm" aria-hidden="true"></span><span class="pm-select-content" data-pm-select="npm" aria-hidden="false">npm</span><span class="inline-block h-3 w-3 pm-select-content i-simple-icons:pnpm" data-pm-select="pnpm" aria-hidden="true"></span><span class="pm-select-content" data-pm-select="pnpm" aria-hidden="true">pnpm</span><span class="inline-block h-3 w-3 pm-select-content i-simple-icons:yarn" data-pm-select="yarn" aria-hidden="true"></span><span class="pm-select-content" data-pm-select="yarn" aria-hidden="true">yarn</span><span class="inline-block h-3 w-3 pm-select-content i-simple-icons:bun" data-pm-select="bun" aria-hidden="true"></span><span class="pm-select-content" data-pm-select="bun" aria-hidden="true">bun</span><span class="inline-block h-3 w-3 pm-select-content i-simple-icons:deno" data-pm-select="deno" aria-hidden="true"></span><span class="pm-select-content" data-pm-select="deno" aria-hidden="true">deno</span><span class="inline-block h-3 w-3 pm-select-content i-custom-vlt" data-pm-select="vlt" aria-hidden="true"></span><span class="pm-select-content" data-pm-select="vlt" aria-hidden="true">vlt</span><span class="inline-block h-3 w-3 pm-select-content i-simple-icons:vite" data-pm-select="vp" aria-hidden="true"></span><span class="pm-select-content" data-pm-select="vp" aria-hidden="true">vp</span><span class="i-lucide:chevron-down w-3 h-3 transition-transform duration-200" aria-hidden="true"></span></button><!-- Dropdown menu (teleported to body to avoid clipping) --><!--teleport start--><transition-stub enteractiveclass="transition-opacity duration-150" enterfromclass="opacity-0" entertoclass="opacity-100" leaveactiveclass="transition-opacity duration-100" leavefromclass="opacity-100" leavetoclass="opacity-0" appear="false" persisted="false" css="true"><!--v-if--></transition-stub><!--teleport end--><button class="group gap-x-1 items-center justify-center font-mono border border-border rounded-md transition-all duration-200 cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed disabled:border-transparent inline-flex text-sm p-2 bg-transparent text-fg hover:enabled:bg-fg/10 focus-visible:enabled:bg-fg/10 aria-pressed:bg-fg/10 aria-pressed:border-fg/20 aria-pressed:hover:enabled:bg-fg/20 aria-pressed:hover:enabled:text-fg/50 ms-auto" type="button" aria-label="Copy command"><span class="size-[1em] i-lucide:copy" aria-hidden="true"></span><!--v-if--></button></div></div><p class="text-sm text-fg-muted">Then paste the token below to connect:</p><div class="space-y-3"><div><label for="connector-token" class="block text-xs text-fg-subtle uppercase tracking-wider mb-1.5">Token</label><input autocapitalize="off" autocomplete="off" autocorrect="off" spellcheck="false" class="appearance-none bg-bg-subtle border border-border font-mono text-fg placeholder:text-fg-subtle transition-[border-color,outline-color] duration-300 hover:border-fg-subtle outline-2 outline-transparent outline-offset-2 focus:border-accent focus-visible:outline-accent/70 disabled:opacity-50 disabled:cursor-not-allowed text-sm leading-none px-3 py-2.5 rounded-lg w-full" id="connector-token" type="password" name="connector-token" placeholder="paste token here..."></div><details class="text-sm"><summary class="text-fg-subtle hover:text-fg-muted transition-colors duration-200">Advanced options</summary><div class="mt-3"><label for="connector-port" class="block text-xs text-fg-subtle uppercase tracking-wider mb-1.5">Port</label><input autocapitalize="off" autocomplete="off" autocorrect="off" spellcheck="false" class="appearance-none bg-bg-subtle border border-border font-mono text-fg placeholder:text-fg-subtle transition-[border-color,outline-color] duration-300 hover:border-fg-subtle outline-2 outline-transparent outline-offset-2 focus:border-accent focus-visible:outline-accent/70 disabled:opacity-50 disabled:cursor-not-allowed text-sm leading-none px-3 py-2.5 rounded-lg w-full" id="connector-port" type="text" name="connector-port" inputmode="numeric"><div class="border-t border-border my-3"></div><div class="flex flex-col gap-2"><label for="v-0-0-1" class="grid items-center gap-1.5 py-1 -my-1 grid-cols-[auto_1fr_auto] cursor-pointer" style="grid-template-areas: &quot;label-text . toggle&quot;;"><span class="text-sm text-fg font-medium text-start" style="grid-area: label-text;">Automatically open auth page</span><input switch="" type="checkbox" id="v-0-0-1" class="shrink-0 focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2 switch horizontal-tb" role="switch" style="grid-area: toggle; justify-self: end; accent-color: var(--accent); --switch-accent: oklch(0.5 0.16 247.27);"></label><!--v-if--></div></div></details></div><!-- Error message (only show after user explicitly clicks Connect) --><!--v-if--><!-- Warning message --><div role="alert" class="p-3 text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-md"><p class="inline-block text-xs font-bold uppercase tracking-wider text-fg rounded">WARNING</p><p class="text-sm text-fg-muted mt-1">This allows npmx to access your npm CLI. Only connect to sites you trust.</p></div><button class="group gap-x-1 items-center justify-center font-mono border border-border rounded-md transition-all duration-200 cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed disabled:border-transparent inline-flex text-sm py-2 px-4 text-bg bg-fg hover:enabled:bg-fg/50 focus-visible:enabled:bg-fg/50 aria-pressed:bg-fg aria-pressed:text-bg aria-pressed:border-fg aria-pressed:hover:enabled:text-bg/50 w-full" type="submit" disabled=""><!--v-if-->Connect<!--v-if--></button></form>"

 ❯ .../nuxt/components/HeaderConnectorModal.spec.ts:310:6
 ❯ node_modules/.pnpm/@voidzero-dev+vite-plus-test@0.1.20_@opentelemetry+api@1.9.0_@types+node@24.12.0_@vites_5c64e05fabd4903676e666e9babf7cd7/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/runner/chunk-artifact.js?v=e7ff225d:1903:22
test/nuxt/components/HeaderConnectorModal.spec.ts > HeaderConnectorModal > Auth URL button > shows auth URL button when a running operation has an authUrl
Stack Traces | 0.207s run time
AssertionError: expected undefined to be truthy

- Expected:
true

+ Received:
undefined

 ❯ .../nuxt/components/HeaderConnectorModal.spec.ts:245:6
 ❯ node_modules/.pnpm/@voidzero-dev+vite-plus-test@0.1.20_@opentelemetry+api@1.9.0_@types+node@24.12.0_@vites_5c64e05fabd4903676e666e9babf7cd7/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/runner/chunk-artifact.js?v=e7ff225d:1903:22
test/nuxt/composables/use-command-palette-commands.spec.ts > useCommandPaletteCommands > adds atproto account commands and disconnect support when a profile is connected
Stack Traces | 2.12s run time
AssertionError: expected undefined to be truthy

- Expected:
true

+ Received:
undefined

 ❯ .../nuxt/composables/use-command-palette-commands.spec.ts:333:6
 ❯ checkCallback node_modules/.pnpm/@voidzero-dev+vite-plus-test@0.1.20_@opentelemetry+api@1.9.0_@types+node@24.12.0_@vites_5c64e05fabd4903676e666e9babf7cd7/node_modules/@.../dist/chunks/test.DNmyFkvJ.js?v=e7ff225d:3383:23
test/nuxt/composables/use-command-palette-commands.spec.ts > useCommandPaletteCommands > adds npm account commands and disconnect support when npm is connected
Stack Traces | 3.92s run time
AssertionError: expected undefined to be truthy

- Expected:
true

+ Received:
undefined

 ❯ .../nuxt/composables/use-command-palette-commands.spec.ts:301:4
 ❯ node_modules/.pnpm/@voidzero-dev+vite-plus-test@0.1.20_@opentelemetry+api@1.9.0_@types+node@24.12.0_@vites_5c64e05fabd4903676e666e9babf7cd7/node_modules/@voidzero-dev/vite-plus-test/dist/@vitest/runner/chunk-artifact.js?v=e7ff225d:1903:22

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@coderabbitai coderabbitai Bot 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: 4

🧹 Nitpick comments (5)
app/composables/useLikesBadge.ts (1)

54-60: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Non-null assertion on array index bypasses the strict index-safety guideline.

userPackages.value[i]! at Line 58 uses a non-null assertion instead of checking the value before use. As per coding guidelines, "Ensure you write strictly type-safe code, for example by ensuring you always check when accessing an array value by index." Prefer destructuring the package name alongside the result to avoid re-indexing.

♻️ Proposed fix
     const current: Record<string, number> = {}
-    for (let i = 0; i < userPackages.value.length; i++) {
-      const r = results[i]
-      if (r?.status === 'fulfilled') {
-        current[userPackages.value[i]!] = r.value.totalLikes
-      }
-    }
+    userPackages.value.forEach((pkg, i) => {
+      const r = results[i]
+      if (r?.status === 'fulfilled') {
+        current[pkg] = r.value.totalLikes
+      }
+    })
🤖 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/composables/useLikesBadge.ts` around lines 54 - 60, The loop in
useLikesBadge is re-indexing userPackages.value and using a non-null assertion
on the package name, which bypasses strict index safety. Update the logic in the
current/results processing to avoid userPackages.value[i]! by destructuring or
otherwise carrying the package name together with the corresponding promise
result before awaiting, then use that safe value when populating current.

Source: Coding guidelines

app/app.vue (1)

248-280: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Header-height magic number duplicated across files.

calc(env(titlebar-area-y, 0px) + 3.5rem + 1px) (Line 275) must stay in sync with AppHeader.vue's nav height (min-h-14 = 3.5rem) plus its border-bottom. Any future change to the header's height in AppHeader.vue will silently desync this offset, causing the scrollbar/content to start at the wrong position in WCO mode.

Consider exposing the header height as a shared CSS custom property (e.g. set on :root from AppHeader.vue, consumed here) to keep the two files in sync.

🤖 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/app.vue` around lines 248 - 280, The WCO scroll offset in App.vue is
duplicating AppHeader.vue’s header height, so update the fixed `#app-scroll`
positioning to derive its top offset from a shared CSS custom property instead
of hardcoding 3.5rem + 1px. Set that custom property from AppHeader.vue based on
the nav’s actual height and border, then consume it in the `@media` (display-mode:
window-controls-overlay) block so both files stay in sync if the header changes.
app/components/AppHeader.vue (1)

260-263: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Interactive-element selector may miss some focusable/clickable elements.

The :is(a, button, input, select, [role='button'], [role='combobox']) list doesn't cover textarea, [role='menuitem']/[role='tab'], or arbitrary elements with a tabindex and click handler that some components (e.g. LogoContextMenu) might render. Any such element left with inherited drag would swallow clicks under WCO.

🤖 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/AppHeader.vue` around lines 260 - 263, The interactive-element
selector in AppHeader’s header drag override is too narrow and can leave
focusable/clickable elements inheriting drag behavior. Update the selector in
the `header :deep(:is(...))` rule to also cover missing interactive cases such
as `textarea`, `[role='menuitem']`, `[role='tab']`, and generic
tabbable/clickable elements (for example those with `tabindex`), so components
like `LogoContextMenu` don’t have clicks swallowed under WCO.
test/unit/a11y-component-coverage.spec.ts (1)

64-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Please test PwaPrompt.client.vue instead of skipping it.

This component does not need a real service worker to render; a vitest mount can stub useNuxtApp().$pwa.needRefresh and exercise the new alert and action buttons. Keeping it on the skip list leaves the update prompt without any accessibility coverage. As per coding guidelines, **/*.{test,spec}.{ts,tsx}: "Write unit tests for core functionality using vitest".

🤖 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 `@test/unit/a11y-component-coverage.spec.ts` at line 64, The a11y coverage list
is incorrectly skipping PwaPrompt.client.vue, so it never gets tested. Remove
the skip entry from the coverage spec and add a vitest mount-based test for
PwaPrompt.client.vue that stubs useNuxtApp().$pwa.needRefresh to render the
prompt and verify the alert plus action buttons. Locate the change in the
a11y-component-coverage.spec.ts entry for PwaPrompt.client.vue and update the
related component test coverage accordingly.

Source: Coding guidelines

app/components/Package/Header.vue (1)

81-87: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share through one code path.

This duplicates the payload building in app/components/Package/ShareButton.client.vue but omits the optional og:image file, so the command-palette action already behaves differently from the visible share button. Please extract a shared helper/composable and reuse it from both entry points.

Also applies to: 114-123

🤖 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/Header.vue` around lines 81 - 87, The package sharing
logic is duplicated between Header.vue’s sharePackage and
ShareButton.client.vue, and the Header path currently omits the optional
og:image file, causing behavior drift. Extract the payload-building and
navigator.share invocation into a shared helper/composable, then reuse it from
both share entry points so both paths include the same fields and fallbacks,
including the og:image file when available.
🤖 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 45-50: The share flow in ShareButton.client.vue is setting
shareData.files after getOgImageFile() returns, but the real payload is not
being validated before navigator.share is called. Update the ShareButton logic
around the shareData object so the final ShareData shape is checked with files
included before mutating shareData, using getOgImageFile() and the
navigator.share call as the key points to adjust. Keep the URL/text fallback
available by only attaching the file once the full payload is confirmed valid.

In `@app/composables/useLikesBadge.ts`:
- Around line 25-43: The `useLikesBadge` logic has a race between the `npmUser`
refresh watcher and the immediate `checkLikes()` watcher, which can persist a
stale package baseline for a new user. Update `useLikesBadge` so `checkLikes()`
only runs after the package list refresh for the current `npmUser` has
completed, either by sharing the refresh path in the `npmUser` watcher or by
introducing a readiness guard that blocks the immediate watcher until
`userPackages` is current. Make the fix around the `watch(npmUser, ...)` block
and the `checkLikes()` watcher so the first poll always uses the new user’s
package list.

In `@scripts/generate-pwa-screenshots.ts`:
- Around line 42-44: The `generate-pwa-screenshots.ts` argument parsing for
`--url` does not validate that a value follows the flag before reading
`args[urlFlagIdx + 1]`. Update the `explicitUrl` handling to first confirm
`--url` is present and that the next array entry exists and is a valid non-empty
string; if it is missing, fail fast with a clear error instead of falling back
to the preview-server path. Keep the fix localized to the
`args`/`urlFlagIdx`/`explicitUrl` logic so the script’s existing flow remains
unchanged.
- Around line 96-109: The startup flow in startPreviewServer currently leaves
the spawned nuxt preview running if waitForServer(url) fails, and
server.on('error') throws outside main().catch(). Change the preview startup to
reject/propagate errors through the Promise returned by startPreviewServer, and
make sure the child process is terminated/cleaned up before rejecting or
rethrowing. Use the existing startPreviewServer, waitForServer, and server event
handlers as the main places to fix this.

---

Nitpick comments:
In `@app/app.vue`:
- Around line 248-280: The WCO scroll offset in App.vue is duplicating
AppHeader.vue’s header height, so update the fixed `#app-scroll` positioning to
derive its top offset from a shared CSS custom property instead of hardcoding
3.5rem + 1px. Set that custom property from AppHeader.vue based on the nav’s
actual height and border, then consume it in the `@media` (display-mode:
window-controls-overlay) block so both files stay in sync if the header changes.

In `@app/components/AppHeader.vue`:
- Around line 260-263: The interactive-element selector in AppHeader’s header
drag override is too narrow and can leave focusable/clickable elements
inheriting drag behavior. Update the selector in the `header :deep(:is(...))`
rule to also cover missing interactive cases such as `textarea`,
`[role='menuitem']`, `[role='tab']`, and generic tabbable/clickable elements
(for example those with `tabindex`), so components like `LogoContextMenu` don’t
have clicks swallowed under WCO.

In `@app/components/Package/Header.vue`:
- Around line 81-87: The package sharing logic is duplicated between
Header.vue’s sharePackage and ShareButton.client.vue, and the Header path
currently omits the optional og:image file, causing behavior drift. Extract the
payload-building and navigator.share invocation into a shared helper/composable,
then reuse it from both share entry points so both paths include the same fields
and fallbacks, including the og:image file when available.

In `@app/composables/useLikesBadge.ts`:
- Around line 54-60: The loop in useLikesBadge is re-indexing userPackages.value
and using a non-null assertion on the package name, which bypasses strict index
safety. Update the logic in the current/results processing to avoid
userPackages.value[i]! by destructuring or otherwise carrying the package name
together with the corresponding promise result before awaiting, then use that
safe value when populating current.

In `@test/unit/a11y-component-coverage.spec.ts`:
- Line 64: The a11y coverage list is incorrectly skipping PwaPrompt.client.vue,
so it never gets tested. Remove the skip entry from the coverage spec and add a
vitest mount-based test for PwaPrompt.client.vue that stubs
useNuxtApp().$pwa.needRefresh to render the prompt and verify the alert plus
action buttons. Locate the change in the a11y-component-coverage.spec.ts entry
for PwaPrompt.client.vue and update the related component test coverage
accordingly.
🪄 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: cc5c172b-099f-4e84-8a6c-497d259c7474

📥 Commits

Reviewing files that changed from the base of the PR and between 5efdb53 and 7e682b9.

⛔ Files ignored due to path filters (9)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • public/screenshots/desktop-dark-home.png is excluded by !**/*.png
  • public/screenshots/desktop-dark-package.png is excluded by !**/*.png
  • public/screenshots/desktop-light-home.png is excluded by !**/*.png
  • public/screenshots/desktop-light-package.png is excluded by !**/*.png
  • public/screenshots/mobile-dark-home.png is excluded by !**/*.png
  • public/screenshots/mobile-dark-package.png is excluded by !**/*.png
  • public/screenshots/mobile-light-home.png is excluded by !**/*.png
  • public/screenshots/mobile-light-package.png is excluded by !**/*.png
📒 Files selected for processing (18)
  • app/app.vue
  • app/components/AppHeader.vue
  • app/components/Button/Base.vue
  • app/components/Package/Header.vue
  • app/components/Package/ShareButton.client.vue
  • app/components/PwaPrompt.client.vue
  • app/components/Settings/Toggle.client.vue
  • app/composables/useAppBadge.ts
  • app/composables/useLikesBadge.ts
  • app/pages/settings.vue
  • app/plugins/input-switch-polyfill.client.ts
  • app/plugins/likes-badge.client.ts
  • app/types/input-switch-polyfill.d.ts
  • i18n/locales/en.json
  • nuxt.config.ts
  • package.json
  • scripts/generate-pwa-screenshots.ts
  • test/unit/a11y-component-coverage.spec.ts

Comment thread app/components/Package/ShareButton.client.vue
Comment thread app/composables/useLikesBadge.ts Outdated
Comment thread scripts/generate-pwa-screenshots.ts
Comment thread scripts/generate-pwa-screenshots.ts
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

⚠️ Dependency Count

This PR adds 122 new dependencies (2348 → 2470), which exceeds the threshold of 15.

📊 Dependency Size Changes

Warning

This PR adds 34.4 MB of new dependencies, which exceeds the threshold of 200 kB.

📦 Package 📏 Size
@napi-rs/wasm-runtime@1.1.5 6.3 MB
@babel/types@7.29.0 2.8 MB
@vue/compiler-sfc@3.5.34 2.6 MB
@nuxt/vite-builder@4.4.5 2.4 MB
@emnapi/core@1.11.1 2 MB
@babel/parser@7.29.3 2 MB
lodash@4.18.1 1.4 MB
seroval@1.5.3 1.1 MB
ajv@8.20.0 1 MB
ajv@6.15.0 938.2 kB
ajv@6.14.0 -938.2 kB
js-yaml@4.2.0 874 kB
@tybys/wasm-util@0.10.2 813 kB
@babel/traverse@7.29.7 708.1 kB
@vue/compiler-dom@3.5.34 647.3 kB
@vue/compiler-core@3.5.34 630.1 kB
vite-plugin-checker@0.13.0 590.3 kB
acorn@8.17.0 563 kB
@babel/generator@7.29.7 538.5 kB
minimatch@10.2.5 536.1 kB
@babel/helper-create-class-features-plugin@7.29.7 441.2 kB
@emnapi/runtime@1.11.1 433.5 kB
enhanced-resolve@5.24.0 409.5 kB
magicast@0.5.2 379.2 kB
unhead@2.1.13 333.7 kB
fuse.js@7.3.0 311.6 kB
@oxc-parser/binding-linux-x64-gnu@0.135.0 → @oxc-parser/binding-linux-x64-gnu@0.137.0 -245.8 kB
@emnapi/wasi-threads@1.2.2 225.1 kB
postcss@8.5.14 205.3 kB
postcss-selector-parser@7.1.1 187.5 kB
giget@3.2.0 183.1 kB
@nuxt/schema@4.4.5 170 kB
resolve@1.22.12 165.4 kB
webpack-sources@3.5.0 164.1 kB
@babel/helper-module-transforms@7.29.7 161.7 kB
fast-uri@3.1.2 156.9 kB
chokidar@4.0.3 148.8 kB
css-declaration-sorter@7.3.1 125.2 kB
srvx@0.11.15 117.1 kB
srvx@0.11.18 114.3 kB
srvx@0.11.17 -114 kB
unimport@6.2.0 104.9 kB
@nuxt/kit@4.4.6 101.6 kB
@nuxt/kit@4.4.5 101.5 kB
es-module-lexer@2.1.0 100.7 kB
semver@7.8.1 100.2 kB
@vue/shared@3.5.34 87.8 kB
terser-webpack-plugin@5.3.16 → terser-webpack-plugin@5.6.1 86.7 kB
@vitejs/plugin-vue@6.0.6 75.5 kB
nuxtseo-shared@5.3.1 72.1 kB
nuxtseo-shared@5.3.0 -71.8 kB
@babel/template@7.29.7 70.7 kB
@babel/compat-data@7.29.7 68.2 kB
@rollup/pluginutils@5.4.0 66.3 kB
@unhead/vue@2.1.13 66.3 kB
@babel/helper-module-imports@7.29.7 63.7 kB
postcss-merge-longhand@7.0.7 55.4 kB
exsolve@1.1.0 55.3 kB
@babel/helper-member-expression-to-functions@7.29.7 54.9 kB
@babel/helper-compilation-targets@7.29.7 53.8 kB
nypm@0.6.8 51 kB
@babel/helper-define-polyfill-provider@0.6.6 → @babel/helper-define-polyfill-provider@0.6.8 48.9 kB
@babel/helper-validator-identifier@7.28.5 48.8 kB
@vue/compiler-ssr@3.5.34 48.5 kB
nuxtseo-shared@5.1.3 48 kB
nypm@0.6.7 -48 kB
nuxt-site-config@4.1.1 47.9 kB
brace-expansion@5.0.6 47.6 kB
nypm@0.6.6 47.5 kB
which-typed-array@1.1.22 45.4 kB
@babel/helper-replace-supers@7.29.7 43.3 kB
stylehacks@7.0.11 38.9 kB
tinyglobby@0.2.16 38.7 kB
is-core-module@2.16.2 38.6 kB
readdirp@4.1.2 36.1 kB
tinyexec@1.1.1 35.4 kB
@babel/code-frame@7.29.7 34.6 kB
nanoid@3.3.14 -32.8 kB
nanoid@3.3.13 32.7 kB
nanoid@3.3.11 32.6 kB
postcss-ordered-values@7.0.4 32.4 kB
@babel/helper-string-parser@7.27.1 31.8 kB
postcss-merge-rules@7.0.11 31.2 kB
terser@5.46.0 → terser@5.48.0 31.2 kB
site-config-stack@4.1.1 27.4 kB
postcss-minify-selectors@7.1.2 27.1 kB
@types/estree@1.0.9 26.2 kB
cssnano-preset-default@7.0.17 25.8 kB
nuxt-site-config-kit@4.1.1 25.3 kB
input-switch-polyfill@1.12.0 25.2 kB
nuxt-og-image@6.6.0 → nuxt-og-image@6.7.0 25.1 kB
call-bind@1.0.9 24.1 kB
@rolldown/pluginutils@1.0.0-rc.13 23.6 kB
@babel/helper-globals@7.29.7 23.1 kB
side-channel@1.1.1 22.9 kB
empathic@2.0.0 21.2 kB
postcss-reduce-initial@7.0.9 18.7 kB
postcss-minify-font-values@7.0.3 18.6 kB
side-channel-list@1.0.1 17 kB
rollup@2.79.2 → rollup@2.80.0 16.5 kB
postcss-discard-comments@7.0.8 16 kB
es-abstract-get@1.0.0 15.9 kB
smob@1.5.0 → smob@1.6.2 -15.3 kB
@humanfs/types@0.15.0 15.2 kB
postcss-convert-values@7.0.12 14.5 kB
watchpack@2.5.1 → watchpack@2.5.2 12.8 kB
caniuse-api@3.0.0 12.2 kB
es-object-atoms@1.1.2 12 kB
@babel/helper-validator-option@7.29.7 11.8 kB
@babel/helper-plugin-utils@7.29.7 11.8 kB
postcss-normalize-string@7.0.3 11.7 kB
postcss-colormin@7.0.10 11.4 kB
postcss-normalize-url@7.0.3 11.1 kB
postcss-svgo@7.1.3 10.6 kB
postcss-minify-gradients@7.0.5 10.5 kB
hasown@2.0.4 10.3 kB
@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.7 9.9 kB
postcss-reduce-transforms@7.0.3 9.2 kB
is-document.all@1.0.0 9 kB
postcss-normalize-positions@7.0.4 8.5 kB
postcss-minify-params@7.0.9 8.2 kB
tinyclip@0.1.15 8.1 kB
cssnano@7.1.9 7.4 kB
postcss-normalize-unicode@7.0.9 7.3 kB
postcss-normalize-repeat-style@7.0.4 7.3 kB
postcss-discard-duplicates@7.0.4 7.1 kB
tinyclip@0.1.14 -6.8 kB
cssnano-utils@5.0.3 6.7 kB
@babel/helper-optimise-call-expression@7.29.7 6.7 kB
postcss-discard-overridden@7.0.3 6.6 kB
postcss-normalize-timing-functions@7.0.3 6.5 kB
tinyclip@0.1.12 6.5 kB
postcss-normalize-display-values@7.0.3 6 kB
postcss-normalize-whitespace@7.0.3 6 kB
@babel/helper-skip-transparent-expression-wrappers@7.29.7 6 kB
vue-component-type-helpers@3.3.3 5.6 kB
vue-component-type-helpers@3.3.6 5.6 kB
vue-component-type-helpers@3.3.5 -5.6 kB
postcss-unique-selectors@7.0.7 5 kB
postcss-normalize-charset@7.0.3 4.6 kB
postcss-discard-empty@7.0.3 4.5 kB
@babel/helper-annotate-as-pure@7.29.7 4 kB
function.prototype.name@1.1.8 → function.prototype.name@1.2.0 2.7 kB
string.prototype.trim@1.2.10 → string.prototype.trim@1.2.11 2.2 kB
@unocss/core@66.7.2 → @unocss/core@66.7.4 1.8 kB
string.prototype.trimend@1.0.9 → string.prototype.trimend@1.0.10 1.7 kB
core-js-compat@3.48.0 → core-js-compat@3.49.0 1.7 kB
typed-array-length@1.0.7 → typed-array-length@1.0.8 1.6 kB
es-abstract@1.24.1 → es-abstract@1.24.2 1.5 kB
@apideck/better-ajv-errors@0.3.6 → @apideck/better-ajv-errors@0.3.7 1.3 kB
es-to-primitive@1.3.0 → es-to-primitive@1.3.1 1.2 kB
safe-array-concat@1.1.3 → safe-array-concat@1.1.4 1.2 kB
@babel/preset-env@7.29.0 → @babel/preset-env@7.29.7 828 B
picomatch@2.3.1 → picomatch@2.3.2 653 B
@babel/plugin-transform-modules-systemjs@7.29.0 → @babel/plugin-transform-modules-systemjs@7.29.7 584 B
loader-runner@4.3.1 → loader-runner@4.3.2 538 B
@babel/runtime@7.28.6 → @babel/runtime@7.29.7 -448 B
@nuxt/cli@3.36.0 → @nuxt/cli@3.36.1 372 B
@humanfs/node@0.16.7 → @humanfs/node@0.16.8 332 B
flatted@3.3.4 → flatted@3.4.2 -245 B
oxc-parser@0.135.0 → oxc-parser@0.137.0 229 B
@eslint/config-array@0.21.1 → @eslint/config-array@0.21.2 162 B
@eslint/eslintrc@3.3.4 → @eslint/eslintrc@3.3.5 -126 B
jsonfile@6.2.0 → jsonfile@6.2.1 115 B
@bomb.sh/tab@0.0.16 → @bomb.sh/tab@0.0.17 82 B
@babel/plugin-transform-function-name@7.27.1 → @babel/plugin-transform-function-name@7.29.7 -81 B
@babel/plugin-transform-for-of@7.27.1 → @babel/plugin-transform-for-of@7.29.7 -79 B
@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5 → @babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7 -48 B
@babel/plugin-transform-destructuring@7.28.5 → @babel/plugin-transform-destructuring@7.29.7 -46 B
@babel/plugin-transform-typeof-symbol@7.27.1 → @babel/plugin-transform-typeof-symbol@7.29.7 -42 B
@babel/plugin-transform-arrow-functions@7.27.1 → @babel/plugin-transform-arrow-functions@7.29.7 -40 B
@babel/helper-create-regexp-features-plugin@7.28.5 → @babel/helper-create-regexp-features-plugin@7.29.7 -36 B
@babel/plugin-transform-unicode-escapes@7.27.1 → @babel/plugin-transform-unicode-escapes@7.29.7 -28 B
@babel/plugin-transform-block-scoped-functions@7.27.1 → @babel/plugin-transform-block-scoped-functions@7.29.7 -24 B
@humanfs/core@0.19.1 → @humanfs/core@0.19.2 24 B
regjsparser@0.13.0 → regjsparser@0.13.2 13 B
@babel/helper-remap-async-to-generator@7.27.1 → @babel/helper-remap-async-to-generator@7.29.7 0 B
@babel/helper-wrap-function@7.28.6 → @babel/helper-wrap-function@7.29.7 0 B
@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1 → @babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7 0 B
@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1 → @babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7 0 B
@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1 → @babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7 0 B
@babel/plugin-transform-optional-chaining@7.28.6 → @babel/plugin-transform-optional-chaining@7.29.7 0 B
@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6 → @babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7 0 B
@babel/plugin-syntax-import-assertions@7.28.6 → @babel/plugin-syntax-import-assertions@7.29.7 0 B
@babel/plugin-syntax-import-attributes@7.28.6 → @babel/plugin-syntax-import-attributes@7.29.7 0 B
@babel/plugin-transform-async-generator-functions@7.29.0 → @babel/plugin-transform-async-generator-functions@7.29.7 0 B
@babel/plugin-transform-async-to-generator@7.28.6 → @babel/plugin-transform-async-to-generator@7.29.7 0 B
@babel/plugin-transform-block-scoping@7.28.6 → @babel/plugin-transform-block-scoping@7.29.7 0 B
@babel/plugin-transform-class-properties@7.28.6 → @babel/plugin-transform-class-properties@7.29.7 0 B
@babel/plugin-transform-class-static-block@7.28.6 → @babel/plugin-transform-class-static-block@7.29.7 0 B
@babel/plugin-transform-classes@7.28.6 → @babel/plugin-transform-classes@7.29.7 0 B
@babel/plugin-transform-computed-properties@7.28.6 → @babel/plugin-transform-computed-properties@7.29.7 0 B
@babel/plugin-transform-dotall-regex@7.28.6 → @babel/plugin-transform-dotall-regex@7.29.7 0 B
@babel/plugin-transform-duplicate-keys@7.27.1 → @babel/plugin-transform-duplicate-keys@7.29.7 0 B
@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0 → @babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7 0 B
@babel/plugin-transform-dynamic-import@7.27.1 → @babel/plugin-transform-dynamic-import@7.29.7 0 B
@babel/plugin-transform-explicit-resource-management@7.28.6 → @babel/plugin-transform-explicit-resource-management@7.29.7 0 B
@babel/plugin-transform-exponentiation-operator@7.28.6 → @babel/plugin-transform-exponentiation-operator@7.29.7 0 B
@babel/plugin-transform-export-namespace-from@7.27.1 → @babel/plugin-transform-export-namespace-from@7.29.7 0 B
@babel/plugin-transform-json-strings@7.28.6 → @babel/plugin-transform-json-strings@7.29.7 0 B
@babel/plugin-transform-literals@7.27.1 → @babel/plugin-transform-literals@7.29.7 0 B
@babel/plugin-transform-logical-assignment-operators@7.28.6 → @babel/plugin-transform-logical-assignment-operators@7.29.7 0 B
@babel/plugin-transform-member-expression-literals@7.27.1 → @babel/plugin-transform-member-expression-literals@7.29.7 0 B
@babel/plugin-transform-modules-amd@7.27.1 → @babel/plugin-transform-modules-amd@7.29.7 0 B
@babel/plugin-transform-modules-commonjs@7.28.6 → @babel/plugin-transform-modules-commonjs@7.29.7 0 B
@babel/plugin-transform-modules-umd@7.27.1 → @babel/plugin-transform-modules-umd@7.29.7 0 B
@babel/plugin-transform-named-capturing-groups-regex@7.29.0 → @babel/plugin-transform-named-capturing-groups-regex@7.29.7 0 B
@babel/plugin-transform-new-target@7.27.1 → @babel/plugin-transform-new-target@7.29.7 0 B
@babel/plugin-transform-nullish-coalescing-operator@7.28.6 → @babel/plugin-transform-nullish-coalescing-operator@7.29.7 0 B
@babel/plugin-transform-numeric-separator@7.28.6 → @babel/plugin-transform-numeric-separator@7.29.7 0 B
@babel/plugin-transform-object-rest-spread@7.28.6 → @babel/plugin-transform-object-rest-spread@7.29.7 0 B
@babel/plugin-transform-parameters@7.27.7 → @babel/plugin-transform-parameters@7.29.7 0 B
@babel/plugin-transform-object-super@7.27.1 → @babel/plugin-transform-object-super@7.29.7 0 B
@babel/plugin-transform-optional-catch-binding@7.28.6 → @babel/plugin-transform-optional-catch-binding@7.29.7 0 B
@babel/plugin-transform-private-methods@7.28.6 → @babel/plugin-transform-private-methods@7.29.7 0 B
@babel/plugin-transform-private-property-in-object@7.28.6 → @babel/plugin-transform-private-property-in-object@7.29.7 0 B
@babel/plugin-transform-property-literals@7.27.1 → @babel/plugin-transform-property-literals@7.29.7 0 B
@babel/plugin-transform-regenerator@7.29.0 → @babel/plugin-transform-regenerator@7.29.7 0 B
@babel/plugin-transform-regexp-modifiers@7.28.6 → @babel/plugin-transform-regexp-modifiers@7.29.7 0 B
@babel/plugin-transform-reserved-words@7.27.1 → @babel/plugin-transform-reserved-words@7.29.7 0 B
@babel/plugin-transform-shorthand-properties@7.27.1 → @babel/plugin-transform-shorthand-properties@7.29.7 0 B
@babel/plugin-transform-spread@7.28.6 → @babel/plugin-transform-spread@7.29.7 0 B
@babel/plugin-transform-sticky-regex@7.27.1 → @babel/plugin-transform-sticky-regex@7.29.7 0 B
@babel/plugin-transform-template-literals@7.27.1 → @babel/plugin-transform-template-literals@7.29.7 0 B
@babel/plugin-transform-unicode-property-regex@7.28.6 → @babel/plugin-transform-unicode-property-regex@7.29.7 0 B
@babel/plugin-transform-unicode-regex@7.27.1 → @babel/plugin-transform-unicode-regex@7.29.7 0 B
@babel/plugin-transform-unicode-sets-regex@7.28.6 → @babel/plugin-transform-unicode-sets-regex@7.29.7 0 B
babel-plugin-polyfill-corejs2@0.4.15 → babel-plugin-polyfill-corejs2@0.4.17 0 B
babel-plugin-polyfill-corejs3@0.14.0 → babel-plugin-polyfill-corejs3@0.14.2 0 B
babel-plugin-polyfill-regenerator@0.6.6 → babel-plugin-polyfill-regenerator@0.6.8 0 B
@unocss/config@66.7.2 → @unocss/config@66.7.4 0 B
@oxc-project/types@0.135.0 → @oxc-project/types@0.137.0 0 B
lodash.memoize@4.1.2 Unknown
lodash.uniq@4.5.0 Unknown

Total size change: 34.4 MB

⚠️ Package Trust Level Decreased

Caution

Decreased trust levels may indicate a higher risk of supply chain attacks. Please review these changes carefully.

📦 Package 🔒 Before 🔓 After
@babel/helper-string-parser trusted-with-provenance none
chokidar trusted-with-provenance none
empathic trusted-with-provenance provenance
readdirp trusted-with-provenance provenance

tomayac added 2 commits July 1, 2026 16:57
navigator.canShare() was only checked against a dummy file to test
generic files-sharing support, not against the actual title/text/url/files
combination. Some implementations support sharing files or url/text but
not both together, so attaching the file unconditionally could make
navigator.share() reject silently. Now the file is only attached once
canShare() confirms the full payload is shareable.
Previously a missing value after --url silently fell through to the
preview-server path instead of erroring, which is confusing when the
flag was clearly intended to be used.

@gameroman gameroman left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This probably needs a discussion first

@gameroman gameroman added the needs discussion An idea that needs more discussion to understand the scope and impact. label Jul 1, 2026
@tomayac

tomayac commented Jul 1, 2026

Copy link
Copy Markdown
Author

This probably needs a discussion first

(I was chatting about this with @danielroe.)

@gameroman gameroman requested a review from danielroe July 1, 2026 15:05
…lure

The child's 'error' event handler threw directly, which crashes the
process instead of surfacing through the promise chain. It's now raced
against waitForServer() so a spawn failure or readiness timeout both
reject startPreviewServer() and kill the still-running child process
before propagating, instead of leaking it.
@gameroman gameroman added the front Frontend, Design label Jul 1, 2026
@tomayac tomayac changed the title feat(pwa): premium app features — installability, WCO, share, badging feat(ui): premium app features — installability, WCO, share, badging Jul 1, 2026
- Drop the standalone input-switch-polyfill.d.ts ambient module file
  (knip flagged it as an unused file) in favor of a targeted
  @ts-expect-error at the one dynamic-import call site.
- Regenerate i18n/schema.json to include the new pwa.*, settings.app,
  package.links.share and package.share_aria_label keys.
- Pin vue to 3.5.39 in pnpm-workspace.yaml overrides; the regenerated
  lockfile had resolved two separate vue versions (3.5.34 and 3.5.39).
@tomayac

tomayac commented Jul 1, 2026

Copy link
Copy Markdown
Author

Installed PWA with Window Controls Overlay enabled

Screenshot 2026-07-01 at 18 38 42

Installed PWA with Window Controls Overlay disabled

Screenshot 2026-07-01 at 18 40 12

Share button for packages

Screenshot 2026-07-01 at 18 38 58

App icon shortcuts

Screenshot 2026-07-01 at 18 40 01

Install prompt with screenshots

Screenshot 2026-07-01 at 18 41 30

Accent colors for switches

(Uses native switch on Safari with support for dragging the toggle, polyfill on other browsers)

Screenshot 2026-07-01 at 18 44 51

Package sub-headers use sticky top-14 to clear the fixed <header> when
the viewport is the scroll container. In WCO mode #app-scroll already
starts below the header, so that offset left a redundant 3.5rem gap
between the title bar and the sticky sub-header instead of gluing it
to the header.
@vite-pwa/nuxt's client plugin only registers a beforeinstallprompt
listener when pwa.client.installPrompt is truthy; it defaults to
false. Without it, showInstallPrompt never flips to true and the
Settings "Install app" button never appears, even though the browser
fires the event normally.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

front Frontend, Design needs discussion An idea that needs more discussion to understand the scope and impact.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants