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
16 changes: 16 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,20 @@ Follows the shared [OpenPhysics accessibility convention](https://github.com/Ope
- **Hardcoded colors:** `view/TableRenderer.ts` builds the data table as real DOM and uses `TRACK_COLORS[…].toCSS()` with a `"#000000"` fallback literal — CSS-string carve-out for track visualization colors, not `TrackLabColors` UI tokens.
- **Domain clock:** `VideoPlaybackModel` drives video timing/scrubbing instead of composing fleet-standard `TimeModel` (`src/common/TimeModel.ts` is present for shared reference only).


### `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). |

## Testing

Fleet-standard Vitest layout:
Expand Down Expand Up @@ -91,6 +105,8 @@ npm test
npm run generate-svg-icon # bouncing-ball icon SVG
```

`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.

## Conventions & deliberate deviations

Extra `src/` root files beyond the standard set, each justified by cross-screen use:
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# TrackLab

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

A browser-based video analysis tool built with [SceneryStack](https://scenerystack.org/). Load a physics
video, calibrate real-world distances, and track or digitize object motion with kinematics graphs and CSV
export.
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="Browser-based video motion-analysis tool. Load a video, calibrate real-world distances, and auto-track objects across frames using computer vision."
/>

<!-- Open Graph (LMS / social share previews). Update og:url after deploy. -->
<meta property="og:type" content="website" />
<meta property="og:title" content="trackLab" />
<meta
property="og:description"
content="Browser-based video motion-analysis tool. Load a video, calibrate real-world distances, and auto-track objects across frames using computer vision."
/>
<meta property="og:image" content="./icons/icon-512.png" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="trackLab" />
<meta
name="twitter:description"
content="Browser-based video motion-analysis tool. Load a video, calibrate real-world distances, and auto-track objects across frames using computer vision."
/>
<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 @@ -66,8 +66,8 @@
"overrides": {
"sourcemap-codec": "npm:@jridgewell/sourcemap-codec@^1.5.0",
"source-map": "^0.7.4",
"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"
}
}
23 changes: 22 additions & 1 deletion scripts/generate-icons.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
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 Down Expand Up @@ -33,3 +33,24 @@ for (const size of faviconSizes) {
const icoBuffer: Buffer = await pngToIco(faviconPngs);
const faviconDest: string = resolve(root, "public", "favicon.ico");
writeFileSync(faviconDest, icoBuffer);

/** 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");
44 changes: 42 additions & 2 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
* Security headers required for:
* - COOP/COEP: SharedArrayBuffer (OpenCV WASM in the tracking worker)
* - 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 @@ -129,10 +130,18 @@
"Content-Security-Policy": [
"default-src 'self'",
// 'wasm-unsafe-eval' is required for the OpenCV WASM module
// 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' 'wasm-unsafe-eval' 'unsafe-eval'",
// OpenCV spins up blob: workers
"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'",
// blob: for video playback and CSV download; data: for icons
Expand All @@ -147,10 +156,18 @@
"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;

// https://vite.dev/config/
export default defineConfig({
// So the build can be served from an arbitrary path
Expand All @@ -174,16 +191,20 @@
registerType: "autoUpdate",
includeAssets: ["favicon.ico", "icons/apple-touch-icon.png"],
manifest: {
id: "track-lab",
name: "trackLab",
// biome-ignore lint/style/useNamingConvention: Web App Manifest spec requires snake_case keys
short_name: "trackLab",
description: "trackLab simulation",
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 @@ -202,9 +223,28 @@
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: "trackLab",
},
{
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: "trackLab",
},
],
},
workbox: {
maximumFileSizeToCacheInBytes: 12 * 1024 * 1024,
maximumFileSizeToCacheInBytes: WORKBOX_MAX_FILE_BYTES,
globPatterns: ["**/*.{js,css,html,svg,png,woff2}"],
// opencv.js (≈11 MB) is loaded on-demand; skip precaching to speed up
// the initial service-worker install. It is still cached at runtime by
Expand Down
Loading