Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ updates:
ignore:
- dependency-name: "@types/node"
update-types: ["version-update:semver-major"]
# Forced in package.json overrides (see CLAUDE.md → package.json overrides).
# Dependabot PRs would fight the pin / reopen known-accepted risk.
- dependency-name: "lodash"
- dependency-name: "three"
- dependency-name: "brace-expansion"
groups:
development-dependencies:
dependency-type: "development"
Expand Down
17 changes: 17 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,25 @@ Fleet-standard Vitest layout:
npm run lint && npm run check && npm run build && npm test
```

`npm run release` intentionally skips `npm test` in some sims — append `&& npm test` before the version bump so a release cannot ship a failing suite.

## Development notes

- `ConfigurationsZodiacStrip` and `PtolemaicZodiacStrip` are separate view nodes; `MotionsOfTheSun/` cherry-picks constellation data and strip mapping from here.
- **`npm run decompile`** extracts NAAP Flash ActionScript via JPEXS FFDec from `../Baseline/Astronomy/flash-animations` into gitignored `NAAP/decompiled/`.
- After `npm run build`, the sim is installable offline via Workbox (`dist/manifest.webmanifest`).

## Compliance carve-outs

### `package.json` overrides

JSON cannot carry comments, so the rationale for forced transitive pins lives here. Prefer
**tilde (`~`) or exact** versions — caret (`^`) lets minors drift under what is meant to be a
hard pin. Dependabot ignores these three names (see `.github/dependabot.yml`) so it does not
open PRs that fight the overrides. Revisit when SceneryStack drops or re-pins them upstream.

| Override | Pin | Why |
|---|---|---|
| `lodash` | `~4.18.1` | SceneryStack declares `~4.17.12`. Bump clears Dependabot/npm advisories patched in 4.18.x (e.g. GHSA-r5fr-rjxr-66jc, GHSA-f23m-r3pf-42rh). |
| `three` | `~0.125.2` | SceneryStack declares `^0.104.0`. Floor is 0.125.0 for GHSA-fq6p-x6j3-cmmq (ReDoS). Staying on the 0.125 line avoids a larger API jump; **0.125.x still has open CVEs** (e.g. XSS GHSA-7vvq-7r29-5vg3, fixed only in ≥0.137.0). Remove this override if/when SceneryStack stops depending on `three` or pins a patched line itself. LightPropagation keeps a higher `three` pin — do not force 0.125 there. |
| `brace-expansion` | `~5.0.9` | Transitive via `vite-plugin-pwa` / Workbox. Clears npm audit (originally GHSA-mh99-v99m-4gvg; keep ≥5.0.9 for GHSA-rgw5-rvv9-x895). |
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Solar System Models

[![CI](https://github.com/OpenPhysics/SolarSystemModels/actions/workflows/ci.yml/badge.svg)](https://github.com/OpenPhysics/SolarSystemModels/actions/workflows/ci.yml)

A two-screen [SceneryStack](https://scenerystack.org/) port of the NAAP **Solar System Models** lab:
**Ptolemaic System** and **Planetary Configurations**. Built with Vite 8, TypeScript 7, and Biome 2.

Expand Down
22 changes: 22 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,28 @@
<meta name="theme-color" content="#1a1a2e" />
<meta name="phet-sim-level" content="production" />

<meta
name="description"
content="A two-screen SceneryStack simulation porting the NAAP Solar System Models lab (Ptolemaic System + Planetary Configurations)."
/>

<!-- Open Graph (LMS / social share previews). Update og:url after deploy. -->
<meta property="og:type" content="website" />
<meta property="og:title" content="Solar System Models" />
<meta
property="og:description"
content="A two-screen SceneryStack simulation porting the NAAP Solar System Models lab (Ptolemaic System + Planetary Configurations)."
/>
<meta property="og:image" content="./icons/icon-512.png" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="Solar System Models" />
<meta
name="twitter:description"
content="A two-screen SceneryStack simulation porting the NAAP Solar System Models lab (Ptolemaic System + Planetary Configurations)."
/>
<meta name="twitter:image" content="./icons/icon-512.png" />


<link rel="icon" href="./favicon.ico" sizes="any" />
<link rel="icon" href="./icons/icon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="./icons/apple-touch-icon.png" />
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@
"node": ">=24"
},
"overrides": {
"lodash": "^4.18.0",
"three": "^0.125.0",
"brace-expansion": "^5.0.8"
"lodash": "~4.18.1",
"three": "~0.125.2",
"brace-expansion": "~5.0.9"
}
}
Binary file added public/screenshots/narrow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/screenshots/wide.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 30 additions & 3 deletions scripts/generate-icons.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
/**
* generate-icons.ts
*
* Rasterizes public/icons/icon.svg into the PNG icons and favicon.ico used by the PWA.
* Run with: npm run icons
* Rasterizes public/icons/icon.svg into the PNG icons, favicon.ico, and placeholder
* PWA install screenshots used by the manifest. Run with: npm run icons
*
* Replace public/screenshots/{wide,narrow}.png with real sim shots before shipping
* (e.g. Baton/scripts/generate-screenshots.sh → copy into public/screenshots/).
*/

import { readFileSync, writeFileSync } from "node:fs";
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import pngToIco from "png-to-ico";
Expand All @@ -15,6 +18,9 @@ const here = dirname(fileURLToPath(import.meta.url));
const publicDir = resolve(here, "..", "public");
const svg = readFileSync(resolve(publicDir, "icons", "icon.svg"));

/** Theme background matching `theme_color` / icon.svg fill (`#1a1a2e`). */
const THEME_BG = { r: 26, g: 26, b: 46, alpha: 1 };

const density = 512;

const pngTargets = [
Expand All @@ -31,3 +37,24 @@ const icoBuffers = await Promise.all(
[16, 32, 48, 64].map((size) => sharp(svg, { density }).resize(size, size).png().toBuffer()),
);
writeFileSync(resolve(publicDir, "favicon.ico"), await pngToIco(icoBuffers));

/** Branded placeholder screenshots for the Web App Manifest `screenshots` member. */
async function writeScreenshot(width: number, height: number, file: string): Promise<void> {
const iconSize = Math.round(Math.min(width, height) * 0.4);
const icon = await sharp(svg, { density }).resize(iconSize, iconSize).png().toBuffer();
await sharp({
create: {
width,
height,
channels: 4,
background: THEME_BG,
},
})
.composite([{ input: icon, gravity: "center" }])
.png()
.toFile(resolve(publicDir, file));
}

mkdirSync(resolve(publicDir, "screenshots"), { recursive: true });
await writeScreenshot(1280, 720, "screenshots/wide.png");
await writeScreenshot(720, 1280, "screenshots/narrow.png");
46 changes: 43 additions & 3 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { VitePWA } from "vite-plugin-pwa";
* Security headers required for:
* - COOP/COEP: SharedArrayBuffer support
* - CSP: restrict resource loading to same-origin + known blob/data exceptions
* - Referrer / Permissions: tighten default browser leakage
* - X-Content-Type-Options: prevent MIME sniffing
* - X-Frame-Options: prevent clickjacking (belt-and-suspenders alongside frame-ancestors)
*/
Expand All @@ -14,9 +15,17 @@ const securityHeaders: Record<string, string> = {
"Cross-Origin-Embedder-Policy": "require-corp",
"Content-Security-Policy": [
"default-src 'self'",
// TODO(scenerystack): drop 'unsafe-eval' when SceneryStack no longer needs

// Function/eval for query-parameter parsing — reopen a CSP audit then.

// 'unsafe-eval' is required for SceneryStack query parameter parsing
"script-src 'self' 'unsafe-eval'",
"worker-src blob: 'self'",
// TODO(scenerystack): drop 'unsafe-inline' when SceneryStack stops setting

// element.style / cssText for theming (same CSP revisit as unsafe-eval).

// Inline styles are set via element.style / cssText throughout the UI layer
"style-src 'self' 'unsafe-inline'",
// data: for icons
Expand All @@ -29,10 +38,18 @@ const securityHeaders: Record<string, string> = {
"base-uri 'self'",
"frame-ancestors 'none'",
].join("; "),
"Referrer-Policy": "strict-origin-when-cross-origin",
"Permissions-Policy": "camera=(), microphone=(), geolocation=()",
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
};

/** Single-file mode: inline every imported asset as base64 (effectively unlimited). */
const INLINE_LIMIT_BYTES = 100 * 1024 * 1024;

/** Workbox precache ceiling — SceneryStack bundles exceed the default 2 MB limit. */
const WORKBOX_MAX_FILE_BYTES = 12 * 1024 * 1024;

/** Escape a string for literal use inside a `RegExp`. */
function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
Expand Down Expand Up @@ -130,7 +147,7 @@ export default defineConfig(({ mode }) => {
chunkSizeWarningLimit: 5000,
...(single && {
// Inline every imported asset as a base64 data URI instead of emitting files.
assetsInlineLimit: 100_000_000,
assetsInlineLimit: INLINE_LIMIT_BYTES,
// Emit one CSS file (no per-chunk split) so there is a single tag to inline.
cssCodeSplit: false,
// Skip copying public/ (favicon, icons) — nothing external should remain.
Expand All @@ -154,16 +171,20 @@ export default defineConfig(({ mode }) => {
registerType: "autoUpdate",
includeAssets: ["favicon.ico", "icons/apple-touch-icon.png"],
manifest: {
id: "solar-system-models",
name: "Solar System Models",
// biome-ignore lint/style/useNamingConvention: Web App Manifest spec requires snake_case keys
short_name: "Solar System Models",
description: "NAAP Solar System Models: Ptolemaic System and Planetary Configurations",
categories: ["education", "science"],
// biome-ignore lint/style/useNamingConvention: Web App Manifest spec requires snake_case keys
theme_color: "#1a1a2e",
// biome-ignore lint/style/useNamingConvention: Web App Manifest spec requires snake_case keys
background_color: "#000000",
display: "standalone",
orientation: "landscape",
// biome-ignore lint/style/useNamingConvention: Web App Manifest spec requires snake_case keys
display_override: ["window-controls-overlay", "standalone"],
// No `orientation` — leave free so portrait-friendly sims are not forced landscape.
icons: [
{
src: "icons/icon-192.png",
Expand All @@ -182,9 +203,28 @@ export default defineConfig(({ mode }) => {
purpose: "maskable",
},
],
// Placeholder shots from `npm run icons`; replace with real sim screenshots before shipping.
screenshots: [
{
src: "screenshots/wide.png",
sizes: "1280x720",
type: "image/png",
// biome-ignore lint/style/useNamingConvention: Web App Manifest spec requires snake_case keys
form_factor: "wide",
label: "Solar System Models",
},
{
src: "screenshots/narrow.png",
sizes: "720x1280",
type: "image/png",
// biome-ignore lint/style/useNamingConvention: Web App Manifest spec requires snake_case keys
form_factor: "narrow",
label: "Solar System Models",
},
],
},
workbox: {
maximumFileSizeToCacheInBytes: 12 * 1024 * 1024,
maximumFileSizeToCacheInBytes: WORKBOX_MAX_FILE_BYTES,
globPatterns: ["**/*.{js,css,html,svg,png,woff2}"],
},
}),
Expand Down