Summary
Most OpenPhysics sims duplicate the same PWA setup: Vite PWA config, manifest defaults, security headers, icon generation, and icon-related dependencies. Create a shared npm package, likely @openphysics/sim-pwa, to centralize that setup while leaving each sim responsible only for its app-specific metadata and source SVG icon.
This should reduce per-sim boilerplate, make future PWA/security header changes easier to roll out, and avoid committing generated icon assets where possible.
Current Repetition
Representative sims currently duplicate:
vite-plugin-pwa setup in vite.config.ts
securityHeaders for dev server and preview server
- manifest defaults:
display: "standalone"
orientation: "landscape"
- standard icon entries
includeAssets: ["favicon.ico", "icons/apple-touch-icon.png"]
- Workbox defaults:
maximumFileSizeToCacheInBytes: 12 * 1024 * 1024
- common glob patterns for JS/CSS/HTML/SVG/PNG/WOFF2
- icon-generation script based on
sharp and png-to-ico
- direct dev dependencies on
sharp, png-to-ico, tsx, and vite-plugin-pwa
Most sims already keep the real source asset at:
The generated assets can be recreated from that SVG:
public/icons/apple-touch-icon.png
public/icons/icon-192.png
public/icons/icon-512.png
public/favicon.ico
Proposed Package
Create a package such as @openphysics/sim-pwa.
The package should provide:
openPhysicsPWA(options) — wraps VitePWA(...) with shared OpenPhysics defaults.
securityHeaders — shared COOP/COEP/CSP headers used by Vite dev and preview.
- optional
openPhysicsSimConfig(options) — a convenience helper that returns common Vite config.
openphysics-pwa icons — CLI command that generates PNG/ICO assets from the source SVG.
Safe Boundary
This package should stay at the Vite/PWA/build tooling layer.
It should affect:
vite.config.ts
- generated PWA manifest config
- Workbox/service worker config
- icon generation
- dev/preview security headers
It should not affect:
src/main.ts
src/brand.ts
src/splash.ts
src/assert.ts
src/init.ts
- SceneryStack runtime startup order
SceneryStack Bootstrap Constraint
SceneryStack setup is sensitive to import order. Each sim must keep this import as the first import in src/main.ts:
That preserves the existing bootstrap chain:
brand.ts -> splash.ts -> assert.ts -> init.ts
The shared PWA package should not try to abstract or reorder this startup path.
Package API Sketch
Example target usage in a sim:
import { defineConfig } from "vite";
import { openPhysicsPWA, securityHeaders } from "@openphysics/sim-pwa";
export default defineConfig({
base: "./",
build: {
target: "es2024",
},
server: {
headers: securityHeaders,
},
preview: {
headers: securityHeaders,
},
plugins: [
openPhysicsPWA({
name: "Doppler Effect",
shortName: "Doppler",
description: "An interactive simulation of the Doppler Effect using SceneryStack.",
themeColor: "#2575ba",
}),
],
});
For sims with additional offline assets, allow extensions:
openPhysicsPWA({
name: "Wave Composer",
shortName: "WaveComposer",
description: "A real-time voice-analysis simulation.",
themeColor: "#1a1a2e",
extraGlobPatterns: ["**/*.{wav,mp3,ogg}"],
});
Or allow a Workbox override:
openPhysicsPWA({
name: "Wave Composer",
shortName: "WaveComposer",
description: "A real-time voice-analysis simulation.",
themeColor: "#1a1a2e",
workbox: {
globPatterns: ["**/*.{js,css,html,svg,png,woff2,wav,mp3,ogg}"],
},
});
Manifest Defaults
The package should generate this standard manifest shape:
{
name,
short_name: shortName,
description,
theme_color: themeColor,
background_color: backgroundColor ?? "#000000",
display: "standalone",
orientation: "landscape",
icons: [
{
src: "icons/icon-192.png",
sizes: "192x192",
type: "image/png",
},
{
src: "icons/icon-512.png",
sizes: "512x512",
type: "image/png",
},
{
src: "icons/icon.svg",
sizes: "any",
type: "image/svg+xml",
purpose: "maskable",
},
],
}
Icon CLI
The package should expose a bin command:
{
"bin": {
"openphysics-pwa": "./dist/cli.js"
}
}
CLI usage:
Default behavior:
- read
public/icons/icon.svg
- write
public/icons/apple-touch-icon.png at 180x180
- write
public/icons/icon-192.png at 192x192
- write
public/icons/icon-512.png at 512x512
- write
public/favicon.ico with 16/32/48/64 PNG layers
Potential options:
openphysics-pwa icons --source public/icons/icon.svg
openphysics-pwa icons --public-dir public
openphysics-pwa icons --check
--check could fail if generated assets are missing or stale, useful for CI later.
NPM Script Setup
Each sim can replace its local generator script with package-provided commands.
Minimal setup:
{
"scripts": {
"icons": "openphysics-pwa icons",
"build": "npm run icons && tsc && vite build"
},
"devDependencies": {
"@openphysics/sim-pwa": "^0.1.0"
}
}
Keep existing dev/start behavior lightweight:
{
"scripts": {
"start": "vite",
"dev": "vite",
"preview": "vite preview"
}
}
Recommended full script set after migration:
{
"scripts": {
"start": "vite",
"dev": "vite",
"build": "npm run icons && tsc && vite build",
"preview": "vite preview",
"lint": "biome check .",
"format": "biome format --write .",
"fix": "biome check --write .",
"check": "tsc --noEmit && tsc -p tsconfig.scripts.json --noEmit",
"icons": "openphysics-pwa icons",
"clean": "rm -rf dist",
"prepare": "git rev-parse --is-inside-work-tree >/dev/null 2>&1 && git config core.hooksPath .githooks || true"
}
}
If generated icons are not committed, build should run npm run icons first. npm start can remain vite because dev mode can tolerate generating icons manually when needed.
Alternative if dev should always have fresh icons:
{
"scripts": {
"prestart": "npm run icons",
"start": "vite",
"predev": "npm run icons",
"dev": "vite"
}
}
However, the simpler default should probably be to generate icons during build, not every dev server start.
Dependency Strategy
The shared package can own:
sharp
png-to-ico
vite-plugin-pwa
Consider using peer dependencies for tooling that should remain aligned across sims:
This avoids accidentally pulling multiple Vite versions into each sim.
After migration, individual sims should no longer need direct dependencies on:
sharp
png-to-ico
- possibly
vite-plugin-pwa, depending on package design
They may still need tsx if they have other TypeScript scripts. If generate-icons.ts is the only use, tsx can potentially be removed too.
Migration Plan
- Create
@openphysics/sim-pwa package.
- Implement exported Vite helpers and the
openphysics-pwa icons CLI.
- Migrate
TemplateSingleSim first as the reference implementation.
- Validate:
npm run icons
npm run check
npm run build
npm start
- Migrate one representative real sim, such as
DopplerEffect.
- Migrate a special-case sim such as
WaveComposer to validate custom Workbox globs.
- Batch migrate the remaining sims.
- Remove duplicated
scripts/generate-icons.ts files.
- Remove redundant direct dependencies from each sim.
- Update docs in
TemplateSingleSim and shared org guidance.
Acceptance Criteria
- A shared package exists and is usable from a sim repo.
TemplateSingleSim uses the shared package for PWA config and icon generation.
- At least one real sim builds and starts after migration.
- SceneryStack bootstrap imports remain unchanged.
- Generated PNG/ICO assets can be recreated from
public/icons/icon.svg.
- The package supports sims with extra Workbox asset types.
Summary
Most OpenPhysics sims duplicate the same PWA setup: Vite PWA config, manifest defaults, security headers, icon generation, and icon-related dependencies. Create a shared npm package, likely
@openphysics/sim-pwa, to centralize that setup while leaving each sim responsible only for its app-specific metadata and source SVG icon.This should reduce per-sim boilerplate, make future PWA/security header changes easier to roll out, and avoid committing generated icon assets where possible.
Current Repetition
Representative sims currently duplicate:
vite-plugin-pwasetup invite.config.tssecurityHeadersfor dev server and preview serverdisplay: "standalone"orientation: "landscape"includeAssets: ["favicon.ico", "icons/apple-touch-icon.png"]maximumFileSizeToCacheInBytes: 12 * 1024 * 1024sharpandpng-to-icosharp,png-to-ico,tsx, andvite-plugin-pwaMost sims already keep the real source asset at:
The generated assets can be recreated from that SVG:
Proposed Package
Create a package such as
@openphysics/sim-pwa.The package should provide:
openPhysicsPWA(options)— wrapsVitePWA(...)with shared OpenPhysics defaults.securityHeaders— shared COOP/COEP/CSP headers used by Vite dev and preview.openPhysicsSimConfig(options)— a convenience helper that returns common Vite config.openphysics-pwa icons— CLI command that generates PNG/ICO assets from the source SVG.Safe Boundary
This package should stay at the Vite/PWA/build tooling layer.
It should affect:
vite.config.tsIt should not affect:
src/main.tssrc/brand.tssrc/splash.tssrc/assert.tssrc/init.tsSceneryStack Bootstrap Constraint
SceneryStack setup is sensitive to import order. Each sim must keep this import as the first import in
src/main.ts:That preserves the existing bootstrap chain:
The shared PWA package should not try to abstract or reorder this startup path.
Package API Sketch
Example target usage in a sim:
For sims with additional offline assets, allow extensions:
Or allow a Workbox override:
Manifest Defaults
The package should generate this standard manifest shape:
Icon CLI
The package should expose a bin command:
{ "bin": { "openphysics-pwa": "./dist/cli.js" } }CLI usage:
Default behavior:
public/icons/icon.svgpublic/icons/apple-touch-icon.pngat 180x180public/icons/icon-192.pngat 192x192public/icons/icon-512.pngat 512x512public/favicon.icowith 16/32/48/64 PNG layersPotential options:
--checkcould fail if generated assets are missing or stale, useful for CI later.NPM Script Setup
Each sim can replace its local generator script with package-provided commands.
Minimal setup:
{ "scripts": { "icons": "openphysics-pwa icons", "build": "npm run icons && tsc && vite build" }, "devDependencies": { "@openphysics/sim-pwa": "^0.1.0" } }Keep existing dev/start behavior lightweight:
{ "scripts": { "start": "vite", "dev": "vite", "preview": "vite preview" } }Recommended full script set after migration:
{ "scripts": { "start": "vite", "dev": "vite", "build": "npm run icons && tsc && vite build", "preview": "vite preview", "lint": "biome check .", "format": "biome format --write .", "fix": "biome check --write .", "check": "tsc --noEmit && tsc -p tsconfig.scripts.json --noEmit", "icons": "openphysics-pwa icons", "clean": "rm -rf dist", "prepare": "git rev-parse --is-inside-work-tree >/dev/null 2>&1 && git config core.hooksPath .githooks || true" } }If generated icons are not committed,
buildshould runnpm run iconsfirst.npm startcan remainvitebecause dev mode can tolerate generating icons manually when needed.Alternative if dev should always have fresh icons:
{ "scripts": { "prestart": "npm run icons", "start": "vite", "predev": "npm run icons", "dev": "vite" } }However, the simpler default should probably be to generate icons during
build, not every dev server start.Dependency Strategy
The shared package can own:
sharppng-to-icovite-plugin-pwaConsider using peer dependencies for tooling that should remain aligned across sims:
vitetypescriptThis avoids accidentally pulling multiple Vite versions into each sim.
After migration, individual sims should no longer need direct dependencies on:
sharppng-to-icovite-plugin-pwa, depending on package designThey may still need
tsxif they have other TypeScript scripts. Ifgenerate-icons.tsis the only use,tsxcan potentially be removed too.Migration Plan
@openphysics/sim-pwapackage.openphysics-pwa iconsCLI.TemplateSingleSimfirst as the reference implementation.npm run iconsnpm run checknpm run buildnpm startDopplerEffect.WaveComposerto validate custom Workbox globs.scripts/generate-icons.tsfiles.TemplateSingleSimand shared org guidance.Acceptance Criteria
TemplateSingleSimuses the shared package for PWA config and icon generation.public/icons/icon.svg.