Skip to content

feat(pwa): open and export offline; installable app - #1857

Merged
magic-peach merged 2 commits into
mainfrom
feat/pwa-offline
Nov 5, 2026
Merged

magic-peach merged 2 commits into
mainfrom
feat/pwa-offline

Conversation

@magic-peach

Copy link
Copy Markdown
Collaborator

Phase 5, item 8: offline support and installing as an app. There are no new dependencies.

What users get

  • Offline use: once a video has been opened with a connection, the editor opens, loads videos and exports with the network off.
  • Status note: the footer says Works offline once that's true. Before then it says "Open a video once while online to use the editor offline", or "Saving the editor for offline use…".
  • Installing: the app can be installed. There's a web app manifest, with icons (192 and 512, maskable, and Apple touch) rendered from the favicon.
  • Updates: a new deploy shows a small "A new version of Reframe is ready. Reload" notice. It never reloads by itself, so an export can't be interrupted.

How it works

  • public/sw.js is hand-written.
    • Precache: on install it precaches the app (3 pages and about 66 files, roughly 2.3 MB).
    • Pages: network-first with a 4 s timeout, then the cache. Unknown paths fall back to the editor.
    • Other app files: cache-first. ignoreSearch handles Next's ?_rsc= page data.
    • FFmpeg cores (@ffmpeg/core@ / core-mt@ on jsDelivr): cache-first in their own cache, filled the first time they download. They're CORS responses, so the worker's SRI checks still apply.
    • Everything else: left alone, including analytics and other origins. User videos and exports are blob: URLs and never reach the worker.
  • Precache list: scripts/build-sw-manifest.mjs runs at the end of bun run build. It writes the list and a content hash into out/sw.js in place of a placeholder. Every deploy that changes the site therefore changes sw.js, which is how browsers detect updates. Activating removes old app caches. Source maps, _headers and sw.js are excluded.
  • Isolation: cached pages keep the headers they were served with, COOP/COEP included, so offline matches online on every host. I first planned to force COOP/COEP onto cached pages. I dropped that, because it would make a host without isolation behave differently offline.
  • Readiness check: "Works offline" requires every engine file this page loads: the core and wasm, plus ffmpeg-core.worker.js for the multi-threaded core. The e2e test caught an earlier version that reported ready on the .wasm alone; the multi-threaded engine then failed offline because its worker script hadn't been cached yet.
  • Engine URLs: they move to src/lib/engineUrls.ts, shared by the engine worker and the offline check. A unit test checks that sw.js routes every one of them to the engine cache.
  • Headers: /sw.js is served with Cache-Control: no-cache in netlify.toml, public/_headers and vercel.json. The COOP/COEP rules are unchanged.
  • Development builds don't register the worker.

Storage and memory

  • Storage: about 2.3 MB for the app, plus the engine (about 31 MB decompressed). The browser may evict this under storage pressure; it's saved again on the next online visit.
  • Memory: no impact on exports.

Telemetry

pwa (installed, offline_ready once per browser, update). Documented in docs/DATADOG.md.

Tests

  • Unit (serviceWorker.test.ts):
    • The precache list: pages vs assets, and what's skipped.
    • Writing the list into sw.js.
    • sw.js routing (engine, page, asset, network) and page lookup.
    • The service worker doing nothing for the app before a build has written the list.
  • Unit (deployConfig.test.ts): updated for the shared engine URL module.
  • Unit totals: 540 passed.
  • test/e2e/offline.spec.ts runs on both projects:
    • Load online, wait for the worker to take control and for "Works offline".
    • Go offline in the browser and reload. Assert the editor loads, navigator.onLine is false, and crossOriginIsolated is the same as online (true on the isolated server).
    • Then export a 2 s clip offline and check it with ffprobe.
    • A second test checks the manifest link, its fields and that every icon is served.
  • Full local Playwright run: with the worker active in every test, 48 passed and 8 skipped (the optional large-file runs).

Docs

  • docs/USAGE.md: new "Offline and installing" section.
  • docs/DEPLOYMENT.md: new "Offline support" section.

A service worker (public/sw.js) precaches the static export, using a
list and version hash that scripts/build-sw-manifest.mjs writes into the
built sw.js, and keeps the FFmpeg cores from jsDelivr in their own cache
the first time they download (still SRI-checked). Pages come from the
network first and from the cache offline, with the headers they were
served with, so isolation and the multi-threaded engine behave as
online. The footer says when the editor works offline, which needs every
file of the engine this page loads; a new version shows a Reload notice
and never reloads by itself. A web app manifest and icons make it
installable. sw.js is served with no-cache on every host.
@netlify

netlify Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for reframe-os ready!

Name Link
🔨 Latest commit b123f08
🔍 Latest deploy log https://app.netlify.com/projects/reframe-os/deploys/6aecd6d1c69aef675c8e8726
😎 Deploy Preview https://deploy-preview-1857--reframe-os.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

# Conflicts:
#	docs/DATADOG.md
#	src/lib/featureTelemetry.ts
@magic-peach
magic-peach merged commit 1f990ba into main Nov 5, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant