05 — PWA best practices (2025–2026) and embedding strategy for Rasd Forms
Research date: 2026-08-15. Scope: how an embeddable React / React Native forms library should behave inside a customer's PWA (service worker, caching, sync, install, iOS constraints) and how it should be shipped so a customer can "add it to their website or app" (npm, script tag/CDN, Web Component, iframe), without the library owning the customer's service worker.
Summary
- Workbox is alive but in maintenance mode:
workbox-*7.4.1 shipped 2026‑05‑04 (MIT); the 7.1→7.4 releases are dependency/security bumps only, no new features [2][4]. Build on it, but keep our own abstractions thin. - Framework integrations to target:
vite-plugin-pwa1.3.0 (2026‑05‑05, MIT, supports Vite 3–8, pinsworkbox-build/workbox-window^7.4.1) [3];serwist/@serwist/next9.5.12 (2026‑07‑22, MIT) plus@serwist/turbopackfor Next 15/16 [5][23][24].next-pwa(5.6.0, last publish 2022‑08‑23; repo archived Aug 2023) and its fork@ducanh2912/next-pwa(10.2.9, 2024‑09‑18) are dead/dormant — do not document them [4]. - Chrome no longer requires a service worker to install a PWA (Chrome 108 mobile / 112 desktop) and Lighthouse removed its PWA category in Lighthouse 12 (PSI switched 2024‑05‑10) [12][13][22]. "PWA score" is not a marketing metric any more; installability = HTTPS + manifest (
name/short_name, 192 & 512 px icons,start_url,display) [14]. - iOS 26 (Sep 2025): every site added to the Home Screen opens as a web app by default; a manifest is no longer required, SVG icons are supported [16]. Home‑screen web apps get browser‑level storage quota (up to 60 % of disk per origin) and are exempt from the ITP 7‑day script‑storage purge; the purge still applies to forms filled in a normal Safari tab [7][8][9].
- Background Sync API is Chromium‑only (Chrome 49+, Edge 79+, Samsung Internet 5+; not Safari through 26.5, not Firefox through 153; 76.7 % global usage) and Periodic Background Sync requires an installed Chromium PWA with a positive site‑engagement score [10][11]. Rasd's sync engine must be an IndexedDB outbox that works without either API;
syncis an optional accelerator. - Web Push works on iOS only for Home Screen web apps (16.4+); iOS 18.4 / macOS 15.5 added Declarative Web Push (
{"web_push": 8030, "notification": {...}}), which needs no service worker and is backward compatible with standard Web Push [17]. Push is optional for Rasd; if used, send declarative payloads. - Update UX: Workbox's recommended pattern is
waiting→ prompt →messageSkipWaiting()→controlling→ reload;workbox-builddefaults areskipWaiting:false,clientsClaim:false,cleanupOutdatedCaches:false,maximumFileSizeToCacheInBytes: 2 097 152[19][20][21]. A forms library must never trigger a reload while a draft is being edited. - Cross‑origin iframes are the wrong vehicle for offline‑first: Chrome 115+ partitions IndexedDB, Cache Storage and SW registrations by top‑level site; Safari gives cross‑origin frames only 10 % of the parent origin's quota [7][9][38]. Typeform and Tally embed via iframe (online SaaS), while SurveyJS, Form.io and Formbricks render in the host DOM (npm + CDN script) [25][27][28][29][30].
- Shadow DOM is now practical for React widgets: React 19 passes Custom Elements Everywhere; the proven recipe is
attachShadow({mode:'open'})+createRoot(shadowRoot)+ stylesheet injected into the shadow root (SurveyJS ships this exact demo; CSS‑in‑JS added 150–200 ms in one production write‑up) [26][31][32]. - A library should not own the customer's SW. The established third‑party patterns are: (a)
importScripts()‑able worker bundle + exported route/plugin factories for Workbox/Serwist users (OneSignal, Firebase), (b) a scoped, separately registered helper SW when the app has none (OneSignal recommends separate scopes), (c) a CLI that copies a static worker intopublic/(MSWnpx msw init) [35][36][37]. Rasd should ship all three, defaulting to (a).
1. Service‑worker toolchain status (verified from npm registry, 2026‑08‑15)
| Package | Latest | Published | Licence | Notes |
|---|---|---|---|---|
workbox-build, workbox-window, workbox-core, workbox-background-sync | 7.4.1 | 2026‑05‑04 | MIT | 7.3/7.4 = "critical dependency updates"; 7.4.1 migrated to Rollup 4, replaced lodash [2][4] |
vite-plugin-pwa | 1.3.0 | 2026‑05‑05 | MIT | peers vite ^3–^8, workbox-build ^7.4.1; strategies generateSW / injectManifest; `registerType: 'prompt' |
serwist, @serwist/next | 9.5.12 | 2026‑07‑22 | MIT | Workbox‑derived, TypeScript‑first; Serwist class options precacheEntries, skipWaiting, clientsClaim, navigationPreload, runtimeCaching, fallbacks; @serwist/turbopack builds sw.ts from a route handler app/serwist/[path]/route.ts with esbuild; injection point self.__SW_MANIFEST [5][23][24] |
next-pwa | 5.6.0 | 2022‑08‑23 | MIT | Repo archived 2023‑08‑18; requires webpack (next build --webpack on Next 16) [4][40] |
@ducanh2912/next-pwa | 10.2.9 | 2024‑09‑18 | MIT | Fork; effectively superseded by Serwist [4] |
@vite-pwa/nuxt / @vite-pwa/remix | 1.1.1 / 0.2.0 | 2026‑02‑06 / 2025‑03‑30 | MIT | Same core as vite-plugin-pwa [4] |
| Expo web | SDK 53/54 | docs updated 2026‑06‑03 | — | Metro has no first‑class SW support; official recipe is expo export -p web → dist/ then npx workbox-cli generateSW workbox-config.js as a post‑build step; manifest at public/manifest.json; Expo explicitly warns SWs "are known to cause unexpected behavior on web" [34] |
Implication: Rasd must integrate with customer‑owned Workbox 7 / Serwist 9 workers, and with the "no bundler" case (workbox‑cli generateSW + importScripts), because Expo web customers will be in that case.
2. Caching design: precache vs runtime, budgets, expiration, versioning
- Precache only the app shell the customer builds; the library must not add entries to the customer's precache manifest.
workbox-builddefaults:globPatterns ['**/*.{js,wasm,css,html}'],maximumFileSizeToCacheInBytes2 MB (CRA raised its template to 5 MB after hitting the limit on lazy chunks; Excalidraw hit the same wall) [21][39]. Rasd's web runtime therefore should stay comfortably below 2 MB uncompressed per chunk, and the builder (drag‑and‑drop) must be a separate lazy chunk so field‑runner PWAs never precache it. - Runtime caching is where a forms library legitimately contributes: form definitions (JSON), choice lists, question media, i18n bundles, fonts (Arabic). Workbox strategies + plugins:
NetworkFirst/StaleWhileRevalidatefor definitions,CacheFirst+ExpirationPlugin({maxEntries, maxAgeSeconds})+CacheableResponsePlugin({statuses:[0,200]})for media;runtimeCaching[].options.expirationand.backgroundSyncare first‑class ingenerateSWconfig [1][21]. - Cache versioning: precache entries carry revisions; enable
cleanupOutdatedCaches()(default off) so old precaches are removed on activate; the plain‑JS pattern is aCACHE_VERSIONconstant that busts oninstall[18][20][21]. Rasd runtime caches should be namespaced (rasd-defs-v1,rasd-media-v1) and versioned independently of the customer's precache so a library upgrade can migrate its own caches without touching theirs. - Offline fallback: precache a self‑contained
offline.html(inline CSS/JS); handlerequest.mode === 'navigate'with navigation preload → network → fallback; note that 4xx/5xx responses do not hitcatch(), only network errors do [18]. Serwist exposesfallbacks: { entries: [{ url: '/~offline', matcher }] }[5]. Rasd should provide an optional offline component (form list from IndexedDB) rather than a fallback page — the SPA shell is the customer's.
3. Background Sync, Periodic Sync and what a sync engine may assume
workbox-background-syncBackgroundSyncPlugin(queueName, {maxRetentionTime})hooksfetchDidFail; it queues only on network exceptions, not on 4xx/5xx; browsers without the API replay "whenever your service worker starts up" (i.e., needs an open page) [1].- Support: Chrome 49+, Edge 79+, Opera 42+, Samsung Internet 5+; no Safari (through 26.5), no Firefox (through 153) [10]. Periodic Background Sync: Chromium only, installed PWA only, frequency governed by site‑engagement score, only on previously‑joined networks,
minIntervalis a floor not a schedule [11]. - Consequence for enumerator apps on low‑end Android (Chrome/Samsung Internet dominate) and on iPhones: sync must be an app‑level outbox in IndexedDB with triggers on
online,visibilitychange, app start, timer, and manual "Sync now"; when'sync' in registrationexists, register a tag as an accelerator; whenperiodicSyncexists and permissionperiodic-background-syncis granted, refresh form definitions opportunistically [10][11]. This is exactly why the SW cannot be the source of truth for the queue.
4. Update UX (skipWaiting vs prompt)
Workbox's guidance: register with new Workbox('/sw.js'), listen for waiting, show a prompt, call wb.messageSkipWaiting(), then on controlling call location.reload(); the SW listens for {type:'SKIP_WAITING'}. Blind skipWaiting() is discouraged because lazily loaded, hash‑versioned chunks may no longer exist in the new precache [19][20]. vite‑plugin‑pwa exposes this as registerType: 'prompt' (default) vs 'autoUpdate' (which forces skipWaiting+clientsClaim and reloads) [3][6]. Workbox events expose isUpdate / isExternal flags so a UI can distinguish first install from update [20].
For a forms library the extra rule is transactional safety: an update‑triggered reload while an enumerator is mid‑form is a data‑loss event. Rasd should (a) autosave drafts to IndexedDB on every change and (b) export a useRasdBusy() / rasd.isDirty() signal so the host's update prompt can defer reload; the library must never call skipWaiting, clientsClaim or location.reload() itself.
5. Manifest, install prompts, iOS behaviour
- Chromium install criteria (web.dev, updated 2024‑09‑19): HTTPS; manifest with
name/short_name,iconsincluding 192 px and 512 px,start_url,display∈ {fullscreen, standalone, minimal‑ui, window‑controls‑overlay},prefer_related_applicationsabsent/false; user has clicked once and spent ≥30 s on the site; not already installed [14]. A SW fetch handler is no longer required for menu installation (Chrome 108 Android / 112 desktop); Chrome recommends maskable icons;screenshots+descriptionenable the richer install sheet [12][13][14]. beforeinstallprompt(Chromium only):preventDefault(), keep the event, show your own button,prompt()once, readuserChoice; listen toappinstalled; detect standalone withmatchMedia('(display-mode: standalone)')plusnavigator.standaloneon iOS [15]. iOS has nobeforeinstallprompt; installation is Share → Add to Home Screen, and since iOS 26 (Sept 2025) the "Open as Web App" toggle is on by default for any site, manifest or not [16]. Rasd should ship a small<InstallHint>that renders Chromium's prompt button or iOS instructions (RTL‑aware, Arabic strings), because enumerators must be installed to get durable storage (next section).
6. iOS / Safari constraints that matter for field data
| Constraint | Fact (primary source) | Impact on Rasd |
|---|---|---|
| Storage quota (Safari 17+, iOS 17+) | Origin quota up to 60 % of disk in browser apps / 15 % in other apps' WebViews; overall 80 % / 20 %; cross‑origin frames get 10 % of the main frame's origin quota; Home‑Screen web apps get browser quotas [7][9] | Fine for surveys; iframes are the weak spot |
| 7‑day script‑storage purge (ITP, Safari 13.1+) | IndexedDB, localStorage, Cache and SW registrations deleted after 7 days of Safari use without interaction; Home‑Screen web apps have their own counter and are not expected to lose data [8][9] | Non‑installed Safari users can lose queued submissions; push users to install; sync aggressively |
navigator.storage.persist() | Safari 15.2+; granted by heuristic (e.g., opened as Home‑Screen web app), Chrome grants silently by engagement/installed/notification permission, Firefox prompts [7][9][17] | Call it after first successful sync/install; surface persisted() in a diagnostics panel |
| Web Push | iOS 16.4+ Home‑Screen web apps only; Declarative Web Push on iOS 18.4/macOS 15.5, SW optional, standard Web Push still works [17] | Optional feature; use declarative JSON payload for both |
| Background Sync / Periodic Sync | Not supported in Safari [10][11] | Outbox + foreground triggers (section 3) |
| Cache limits | Older "50 MB cache" figures circulating in blogs predate the Safari 17 policy; treat as obsolete unless re‑verified [7] | Do not hard‑code |
Chrome/Android quotas for comparison: up to 60 % of disk per origin, 80 % overall, LRU eviction under pressure unless persistent; localStorage is 5 MiB per origin (never use it for submissions) [9].
7. Storage partitioning and why iframes are online‑only
Chrome 115+ partitions IndexedDB, Cache Storage, localStorage, OPFS, Broadcast Channel, SharedWorker and service worker registrations created in third‑party iframes by (origin, top‑level site) [38]. Safari applies similar partitioning plus the 10 % frame quota [7]. So a <iframe src="https://forms.rasd.io/..."> embed cannot share offline data with the host, cannot register a useful SW for the host page, and gets a fraction of the quota — acceptable for a hosted, online "share link" product, unacceptable as the primary embed for offline field work.
8. Lighthouse and how to prove "PWA quality" instead
Lighthouse removed the PWA category (issue #15535; PageSpeed Insights moved to Lighthouse 12 on 2024‑05‑10) because it tracked Chrome's installability criteria, which changed [12][22]. Recommendation: replace "Lighthouse PWA score" in marketing with a checklist (installable manifest, offline shell, persisted storage, sync queue health, update prompt) plus a rasd-forms doctor dev command and DevTools Application‑panel screenshots.
9. Embedding models compared
| Model | CSS isolation | Same‑origin storage & SW | SSR/RSC | React duplication | CSP friction | Used by |
|---|---|---|---|---|---|---|
| npm ESM package (React/RN peer deps) | none unless Shadow DOM opt‑in | yes | needs 'use client' + browser guards | none | lowest (self‑hosted, hashed assets) | SurveyJS, Form.io, Formbricks [25][28][29] |
Script tag / CDN IIFE (<script src=… data-…>) | via Shadow DOM inside the bundle | yes | n/a | bundles its own React (~45 KB gz widget in one guide) [32] | needs script-src allow‑list or self‑host + SRI | SurveyJS (unpkg), Form.io (cdn.form.io), Formbricks (/api/packages/website), Typeform (embed.typeform.com) [25][27][28][30] |
Web Component (<rasd-form> custom element + Shadow DOM) | strong (styles cannot leak in/out; theme via CSS custom properties) | yes | React 19 renders custom elements incl. SSR attribute serialisation [31] | same as IIFE unless externalised | style injection must be CSSOM/<link> not inline <style> under strict CSP | Not yet mainstream among form vendors; SurveyJS documents a Shadow‑DOM render recipe [26] |
| iframe | total | partitioned; 10 % quota on Safari; no host SW [7][38] | trivial | none | needs frame-src; vendor CSP blocks insecure parents [27] | Typeform, Tally [27][30] |
How the reference products embed (verified 2026‑08‑15):
- Typeform —
@typeform/embed(npm) or//embed.typeform.com/next/embed.js+ CSS; declarativedata-tf-widget="<id>"attributes;createWidget/createPopup/createSlider/createSidetab/createPopover; forms render inside iframes (iframeProps); vendor CSP blocks embedding on non‑HTTPS pages [27]. - Tally —
https://tally.so/widgets/embed.js,<iframe data-tally-src=…>,Tally.loadEmbeds(),Tally.openPopup()/closePopup(), eventsTally.FormLoaded/FormPageView/FormSubmitted/PopupClosed; iframe with dynamic‑height messaging [30]. - SurveyJS —
survey-core+survey-js-ui(orsurvey-react-ui3.0.0, 2026‑08‑11, MIT) from npm or unpkg;new Survey.Model(json);survey.render(el); theming via CSS variables and, in v3.0, a shared design‑token system with Bootstrap/MUI/shadcn adapters; documented Shadow‑DOM recipe = appendsurvey-core.cssinside the shadow root [25][26][41]. - Form.io —
@formio/js5.5.1 (2026‑08‑11, MIT);https://cdn.form.io/js/formio.full.min.js+.css;Formio.createForm(el, url); one‑line embedformio.embed.min.js?src=…&libs=true; "Inherit Page CSS" toggle (no Shadow DOM) [28]. - Formbricks —
@formbricks/js5.0.0 (2026‑05‑27, MIT);formbricks.setup({workspaceId, appUrl})(environmentIddeprecated) or a loader script that injects${appUrl}/api/packages/websiteand callswindow.formbricks.init; surveys render in the host DOM as an overlay [29] (rendering internals not re‑verified today).
Web Component mechanics that are now settled: React 19 maps primitive props to attributes and object/function props to properties, and listens to custom events without refs [31]; the widget recipe is attachShadow({mode:'open'}), createRoot(shadowRoot) (or hydrateRoot to avoid a first‑paint flicker), CSS injected via <link>/constructed stylesheet inside the shadow root, config from document.currentScript.dataset, IIFE output via Vite/Rollup library mode with react/react-dom either bundled (script‑tag build) or external + output.globals (UMD build) [32][33]. Vite lib mode formats are es | cjs | umd | iife, CSS is emitted as a separate file (build.lib.cssFileName) that must be listed in exports [33]. Known Shadow‑DOM pain points to design for: portals (date pickers, dropdown menus must portal inside the shadow root or they lose styles), document.activeElement retargeting, and form‑associated custom elements if the host wants native <form> submission.
10. Exposing SW helpers without owning the customer's service worker
Third‑party precedents:
- OneSignal: customers add
importScripts("https://cdn.onesignal.com/sdks/web/v16/OneSignalSDK.sw.js")to their own SW, or register OneSignal's worker at a separate scope (serviceWorkerPath: "push/onesignal/OneSignalSDKWorker.js",serviceWorkerParam: {scope: "/push/onesignal/"}); OneSignal explicitly says separate scopes are simpler and warns that once merged, the old file must be kept for ~a year while clients re‑register [35]. Workbox users addexclude: [/OneSignal.*\.js$/]so the vendor worker is not precached [35]. - Firebase Cloud Messaging: default
firebase-messaging-sw.jsat root or reuse an existing SW viagetToken({serviceWorkerRegistration})andimport … from 'firebase/messaging/sw'inside a bundled worker; the compat CDN path usesimportScripts('https://www.gstatic.com/firebasejs/10.13.2/firebase-messaging-compat.js')[36]. - MSW:
npx msw init <publicDir>copies a staticmockServiceWorker.jsinto the app so it is served same‑origin at/;worker.start()must be awaited [37]. - Workbox
generateSWhas animportScriptsoption precisely for "additional JavaScript files … within the service worker" [21]; vite‑plugin‑pwainjectManifestand Serwist let the customer authorsw.tsand composeregisterRoute(...), plugins andruntimeCachingarrays [5][6].
Design consequences: the SW spec requires the worker script to be same‑origin (CSP worker-src, falling back to child-src → script-src → default-src, blocks navigator.serviceWorker.register("https://not-example.com/sw.js")) [42]; therefore a CDN‑hosted "Rasd service worker" is impossible, and importScripts() from a CDN inside the customer's worker is subject to the CSP delivered with the worker script — customers with strict CSP will need a self‑hosted copy. Ship the worker code as an npm asset they can copy/bundle, not as a hosted URL.
11. SSR / Next.js / RSC and Expo web
- Put
'use client'at the top of every component/hook entry that touches state, effects, DOM or IndexedDB; keep it in built output (bundlers treat it as the boundary); pure schema/validation modules stay directive‑free so they can run in Server Components and Node [33]. Client components are still SSR‑rendered, so IndexedDB/window/navigator.serviceWorkeraccess belongs in effects or lazily created adapters; documentnext/dynamic(() => import('@rasd/forms-react'), { ssr:false })for the builder. - Next 16 defaults to Turbopack; document Serwist (
@serwist/nextfor webpack,@serwist/turbopackroute‑handler build) rather thannext-pwa[23][24]. - Expo web (Metro): no SW; provide a
workbox-config.jsrecipe withimportScripts: ['rasd-sw.js']andruntimeCachingentries generated by@rasd/forms-pwa/workbox-config[34].react-native-webshares the same JS runtime, so the IndexedDB adapter is reused; native uses SQLite (out of scope here).
12. CSP checklist for an embeddable form runtime
connect-src for the customer's API + Rasd licence endpoint; worker-src 'self' (SW is always same‑origin) [42]; img-src/media-src for question media incl. blob: for offline photos; font-src for Arabic web fonts (self‑host, do not fetch Google Fonts at runtime); style-src — prefer constructed stylesheets/<link> over injected inline <style> so strict CSPs without 'unsafe-inline' still work (verify per customer); script-src — npm build needs nothing extra, CDN build needs the CDN host or a nonce with 'strict-dynamic'; publish SRI hashes for every CDN release; never require eval (no runtime expression new Function — use a sandboxed expression parser for form logic).
Implications & recommendations for Rasd Forms
- Distribution tiers: (a) primary — npm ESM/CJS packages
@rasd/forms-core(schema, logic, validation, i18n; no DOM),@rasd/forms-react('use client'components),@rasd/forms-native,@rasd/forms-builder(lazy‑loaded),@rasd/forms-pwa(storage adapters + SW helpers),@rasd/forms-element(Web Component); (b) secondary — a CDN/self‑hostable IIFErasd-forms.iife.jsthat defines<rasd-form>and bundles React; (c) later — hosted "share‑link" iframe embed for online respondents only. - Web Component with open Shadow DOM as the framework‑agnostic embed; expose theming through CSS custom properties (
--rasd-*) that inherit through the shadow boundary; render popovers/date pickers into a portal container inside the shadow root; supportdir="rtl"andlangattributes; keep React apps on the plain React components (no Shadow DOM) by default with an opt‑in<RasdIsolatedForm>wrapper. - Never register, update or reload a SW from the library.
@rasd/forms-pwaexports:rasdRuntimeCaching(options)(array usable ingenerateSW, vite‑plugin‑pwa and SerwistruntimeCaching),registerRasdRoutes(options)(forinjectManifest/Serwistsw.ts),RasdOutboxPlugin(afetchDidFail/syncbridge), and a prebuilt IIFErasd-sw.jsforimportScripts/ Expoworkbox-cli. - Fallback for apps without a SW:
npx rasd-forms init public/copiesrasd-sw.jsand the client registers it at a narrow scope (/rasd/) — enough forsync/pushevents without hijacking navigation caching (OneSignal pattern). Document the "keep the old file a year" migration warning. - Sync engine is IndexedDB‑first: outbox + idempotency keys + foreground triggers;
SyncManagerandperiodicSyncare optional accelerators feature‑detected at runtime; never depend onBackgroundSyncPluginsemantics (network‑error only, replay needs an open page on Safari). - Update‑safety contract: autosave every keystroke; export
isDirty()/useRasdBusy(); document the Workboxwaiting→prompt→messageSkipWaitingrecipe and vite‑plugin‑pwaregisterType:'prompt'; tell customers to holdupdateServiceWorker()while a form is dirty. - Budgets: form‑runner ≤ 120 KB gz JS (excluding React), builder lazy chunk separate, every chunk < 2 MB uncompressed (Workbox default), Arabic font subsetted; runtime caches
rasd-defs-v{n}(StaleWhileRevalidate,maxEntries200) andrasd-media-v{n}(CacheFirst,maxAgeSeconds30 d,maxEntries500,CacheableResponsePlugin0/200). - Install & persistence UX components:
<InstallHint>(Chromiumbeforeinstallpromptbutton / iOS Add‑to‑Home‑Screen steps, iOS 26 note),requestPersistence()after first sync, diagnostics panel showingstorage.estimate(),persisted(), standalone mode, queue length; warn Safari‑tab users about the 7‑day purge. - Manifest guidance, not manifest ownership: docs template with
name,short_name,id,start_url,display: standalone, 192/512 + maskable icons,screenshots,lang,dir: rtlwhere relevant; the customer's build plugin generates it. - Push (optional module): standard Web Push + VAPID, payloads in Declarative Web Push JSON so iOS 18.4+ needs no SW; gate iOS behind installed detection.
- Framework recipes to publish and test in CI: Vite + vite‑plugin‑pwa (
generateSWandinjectManifest), Next 15/16 + Serwist (webpack and Turbopack), Expo web + workbox‑cli, plain HTML + IIFE. Dropnext-pwa. - CSP + licence offline: publish the CSP checklist above with SRI hashes; the licence token check must tolerate offline (grace period cached in IndexedDB) so field workers are never blocked — coordinate with the licensing research doc.
- Marketing: do not claim a "Lighthouse PWA score"; publish an offline‑readiness checklist and a
rasd-forms doctorcommand instead.
Sources (accessed 2026‑08‑15)
- Chrome for Developers — workbox‑background‑sync module. https://developer.chrome.com/docs/workbox/modules/workbox-background-sync/
- GitHub — GoogleChrome/workbox Releases (v7.4.1, v7.4.0, v7.3.0, v7.0.0). https://github.com/GoogleChrome/workbox/releases
- npm registry — vite-plugin-pwa 1.3.0 metadata (published 2026‑05‑05; peers). https://registry.npmjs.org/vite-plugin-pwa
- npm registry — workbox‑build/workbox‑window/workbox‑core/workbox‑background‑sync 7.4.1; serwist & @serwist/next 9.5.12; next‑pwa 5.6.0; @ducanh2912/next‑pwa 10.2.9; @vite‑pwa/nuxt 1.1.1; @vite‑pwa/remix 0.2.0; survey‑react‑ui 3.0.0; @formio/js 5.5.1; @formio/react 6.2.1; @formbricks/js 5.0.0. https://registry.npmjs.org/
- Serwist — @serwist/next Getting started. https://serwist.pages.dev/docs/next/getting-started
- Vite PWA — injectManifest guide; Prompt for update; Auto update. https://vite-pwa-org.netlify.app/guide/inject-manifest.html , https://vite-pwa-org.netlify.app/guide/prompt-for-update , https://vite-pwa-org.netlify.app/guide/auto-update.html
- WebKit blog — Updates to Storage Policy (Safari 17, 2023‑08‑10). https://webkit.org/blog/14403/updates-to-storage-policy/
- WebKit blog — Full Third‑Party Cookie Blocking and More (7‑day script‑writable storage). https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more/
- MDN — Storage quotas and eviction criteria. https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria
- Can I use — Background Sync API. https://caniuse.com/background-sync
- Chrome for Developers — Periodic Background Sync. https://developer.chrome.com/docs/capabilities/periodic-background-sync
- Chrome for Developers — Revisiting Chrome's installability criteria. https://developer.chrome.com/blog/update-install-criteria
- GitHub — Lighthouse issue #15535 "Remove PWA Category". https://github.com/GoogleChrome/lighthouse/issues/15535
- web.dev — What does it take to be installable? (updated 2024‑09‑19). https://web.dev/articles/install-criteria
- web.dev — How to provide your own in‑app install experience. https://web.dev/articles/customize-install
- WebKit blog — WebKit Features in Safari 26.0 (2025‑09‑15). https://webkit.org/blog/17333/webkit-features-in-safari-26-0/
- WebKit blog — Meet Declarative Web Push; web.dev — Persistent storage. https://webkit.org/blog/16535/meet-declarative-web-push/ , https://web.dev/articles/persistent-storage
- web.dev — Create an offline fallback page. https://web.dev/articles/offline-fallback-page
- Chrome for Developers — Handling service worker updates with immediacy. https://developer.chrome.com/docs/workbox/handling-service-worker-updates/
- Chrome for Developers — workbox‑window module. https://developer.chrome.com/docs/workbox/modules/workbox-window/
- Chrome for Developers — workbox‑build module (defaults). https://developer.chrome.com/docs/workbox/modules/workbox-build/
- Google — PageSpeed Insights release notes (Lighthouse 12, 2024‑05‑10). https://developers.google.com/speed/docs/insights/release_notes
- Serwist — @serwist/next overview. https://serwist.pages.dev/docs/next
- Serwist — Turbopack guide (@serwist/turbopack). https://serwist.pages.dev/docs/next/turbo
- SurveyJS — Get started (HTML/CSS/JavaScript). https://surveyjs.io/form-library/documentation/get-started-html-css-javascript
- SurveyJS — Render a Survey inside Shadow DOM (React). https://surveyjs.io/form-library/examples/render-survey-inside-shadow-dom/reactjs
- GitHub — Typeform/embed README. https://github.com/Typeform/embed/blob/main/packages/embed/README.md
- Form.io — Embedding a Form (developer guide). https://help.form.io/dev/form-embedding
- GitHub — formbricks/js README; Formbricks framework guides. https://github.com/formbricks/js , https://formbricks.com/docs/website-surveys/framework-guides
- Tally — Widgets introduction; Embed your form. https://developers.tally.so/widgets/introduction , https://tally.so/help/embed-your-form
- React — React v19 release post (custom elements support). https://react.dev/blog/2024/12/05/react-19
- Makerkit — Building Embeddable React Widgets. https://makerkit.dev/blog/tutorials/embeddable-widgets-react
- React —
'use client'reference; Vite — Building for Production / Library Mode. https://react.dev/reference/rsc/use-client , https://vite.dev/guide/build.html - Expo — Progressive web apps (modified 2026‑06‑03). https://docs.expo.dev/guides/progressive-web-apps/
- OneSignal — OneSignal service worker. https://documentation.onesignal.com/docs/en/onesignal-service-worker
- Firebase — Receive messages in a JavaScript client. https://firebase.google.com/docs/cloud-messaging/js/receive
- MSW — Browser integration. https://mswjs.io/docs/integrations/browser
- Privacy Sandbox — Storage partitioning (Chrome 115+). https://privacysandbox.google.com/cookies/storage-partitioning
- GitHub — create‑react‑app PR #10048 (raise maximumFileSizeToCacheInBytes); excalidraw issue #9354. https://github.com/facebook/create-react-app/pull/10048 , https://github.com/excalidraw/excalidraw/issues/9354
- GitHub — shadowwalker/next-pwa (archived 2023‑08‑18). https://github.com/shadowwalker/next-pwa
- SurveyJS — v3.0 major update. https://surveyjs.io/stay-updated/major-updates/2025-2026
- MDN — CSP
worker-srcdirective. https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/worker-src