Skip to content

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

Open
tomayac wants to merge 36 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 36 commits into
npmx-dev:mainfrom
tomayac:pwa-and-premium-app-features

Conversation

@tomayac

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

vercelBot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentActionsUpdated (UTC)
docs.npmx.devReadyReadyPreviewAug 31, 2026 10:19am
npmx.devReadyReadyPreviewAug 31, 2026 10:19am
1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
npmx-lunariaIgnoredIgnoredAug 31, 2026 10:19am

Request Review

@coderabbitai

coderabbitaiBot 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 nameStatusExplanation
Title check✅ PassedThe title accurately highlights the main additions: installability, Window Controls Overlay, sharing, and app badging.
Description check✅ PassedThe description matches the changeset and summarises the PWA, share, WCO, badging, toggle polyfill, and theme-colour updates.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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

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

This comment was marked as resolved.

@socket-security

This comment was marked as resolved.

@codecov

codecovBot commented Jul 1, 2026

Copy link
Copy Markdown

@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: 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 threadapp/components/Package/ShareButton.client.vue Outdated
Comment threadapp/composables/useLikesBadge.ts Outdated
Comment threadscripts/generate-pwa-screenshots.ts
Comment threadscripts/generate-pwa-screenshots.ts
@github-actions

This comment was marked as resolved.

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.

@gameromangameroman 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

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

Copy link
Copy Markdown
Author

This probably needs a discussion first

(I was chatting about this with @danielroe.)

@gameroman
gameroman requested a review from danielroeJuly 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.
tomayacand others added 2 commits August 26, 2026 13:52
The merge of main into this branch kept both the incoming
'vue: 3.5.41' and the stale 'vue: 3.5.39' entry under overrides.
Duplicate mapping keys are a hard YAML error, so 'pnpm install'
aborted and every CI job that depends on it failed.
Drop the stale entry, matching main.
@userquin

userquin commented Aug 27, 2026

Copy link
Copy Markdown
Member

I have no idea what's doing Google with Chrome, from time to time devtools panels stop working, a few months ago, all pwa icons broken, now it is time to break Application > Manifest:

http error 429 when opening manifest at application tab at devtoolsempty manifest panel at application tab webmanifest works when opening the link in a new tab

@tomayac

Copy link
Copy Markdown
Author

This is Vercel's staging preview: it fails with HTTP error 429, not DevTools' fault. The workaround is to right-click the request, selecting Override content (instructions):

Screenshot 2026-08-28 at 10 21 11

Then you paste the manifest contents and the app starts working. I have no idea why Vercel would choke on this one request, but it's definitely on their side.

@userquin

Copy link
Copy Markdown
Member

Uhmmm, we need to enable a flag at pwa conf, I will check the content type, nitro wont add it

@userquin

Copy link
Copy Markdown
Member

@userquin

userquin commented Aug 28, 2026

Copy link
Copy Markdown
Member

This is Vercel's staging preview: it fails with HTTP error 429, not DevTools' fault.

upps, vercel using content disposition and content-security-policy

@danielroe is there a way to disable this?, I'm going to enable registerWebManifestInRouteRules and check it...

image

@userquin

userquin commented Aug 28, 2026

Copy link
Copy Markdown
Member

Initial review:

  • missing base and scope at pwa options, looks like nuxt using base ./ and the sw won't install when installing from a page !== '/' => we need to add webmanifest, sw and dev-sw at server/middleware/canonical-redirects.global.ts (at dev not working)
  • it seems pwa module breaking vue and nuxt module augmentations
  • all pwa shortcut icons using the same icon, we need to use custom ones
  • we need to enable some pwa options
  • enable devOptions via some env var: check how we did it at elk.zone
  • change pwa icons per environment: pwaAssets option must be removed, pwa assets generated with pwa assets generator CLI iirc I did it using unshift at nitro public dirs (I need to check it), but not working on local dev => pwa icons should be green (public-dev) instead white ones (public); it is not working at npmx the favicon.ico resolved from public folder, the build-env module needs a review (maybe nitro issue 🤔?)
  • figure out the best strategy for offline support: Thomas removed the navigation fallback and the html files from the precaching right now it is not working (I think enabling pwa options should fix it)
  • move strategy to custom SW (injectManifest strategy)
  • include api calls at denylist
  • add network first handler for SSR pages with custom runtime caching with max entries
    • this way offline will work if cached
    • we need to check if we can change the max entries at runtime (SW): if so, we can include a new setting (maybe using indexedDB or broadcast channel)
    • we need to include storage quota checks and iOS can be a pain (50MB only and can deleted the storage if the app not being used for a few days without any advise)
New PWA Options
registerWebManifestInRouteRules: true,experimental: {enableWorkboxPayloadQueryParams: true,},devOptions: {enabled: true,// ==> set it to false when pushing here <==type: 'module',suppressWarnings: true,},
new pwa optionas at nuxt config file

Maybe we can use the new nuxt and unplugin pwa packages via file protocol using local tgz files (I still need to release initial alpha version, but I need to do some internal deep change before releasing), I have tested it with nuxt 3, 4 and 5:

  • SW code splitting: no more SW barrel
  • build dual ESM/Classic SW
  • Vite 8 dual SW parallel build (workbox fork now detects Vite via Vite+ correctly)
  • use Nuxt virtual modules at SW: import manifest from '#app-manifest'
  • use Nuxt path alias at pwa options and external pwa config file: custom SW path and custom external PWA config file location (swSrc: '~/sw.ts' at pwa config file and pwa: { path: '~~/pwa.config.ts' } at nuxt config file)
  • I'm also working to include some new i18n and color scheme stuff at webmanifest:

@userquin

Copy link
Copy Markdown
Member

Here some screenshots with the new PWA stuff:

ESM SWESM service worker on chrome on local build
Vite PWA dual SW buildnuxt build showing the parallel build
External PWA configurationnuxt pwa option with just the path entry for the pwa configuration
Parallel SW precachingservice worker configuration to download precache assets in parallel: 5

@userquin

userquin commented Aug 28, 2026

Copy link
Copy Markdown
Member

We may need to disable a few things at pr preview, will require exporting isPreview from config/env.ts (/_v/scripts.js also with 429 in this PR):

  • disable sw parallel precaching: maybe also for production (review also what's should be precached)
  • disable prefetching links (I need to check if enabled)
  • disable payloadExtraction (it is enabled by default)

/cc @danielroe

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

frontFrontend, Designneeds discussionAn idea that needs more discussion to understand the scope and impact.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@tomayac@userquin@ghostdevv@gameroman