Dependency refresh — 2026-10 (first monthly run)

Owner’s request, 2026-10-23: the libraries credited on the About page have had recent upstream work; bring our copies up to date where it benefits us. Branch claude/deps-refresh, one commit per dependency.

This file is meant to be diffed by the next run. Keep the table’s columns and row order; a future run copies the table, updates ours / latest / checked, and writes a new dated section below rather than editing this one.

How the next run repeats this

# 1. What we have
for p in mathjs opentype.js planck d3-geo d3-geo-projection topojson-client @sentry/browser dompurify \
         @observablehq/plot lottie-web tailwindcss; do
  echo "$p installed=$(node -p "try{require('./node_modules/$p/package.json').version}catch(e){'-'}") latest=$(npm view $p version)"; done
head -c 400 js/vendor/<file>                      # vendored versions are in each file's header
cat js/vendor/transformers/VERSION.txt js/vendor/web-llm/VERSION.txt
# 2. What upstream has
for p in paper earcut mp4-muxer webm-muxer jspdf pdfjs-dist gif.js @huggingface/transformers @mlc-ai/web-llm \
         @mediapipe/tasks-vision; do echo "$p $(npm view $p version) $(npm view $p time --json | tail -2 | head -1)"; done
npm view <pkg>@<major> version | tail -1           # newest release inside our major
# 3. Local patches: git log --follow js/vendor/<file>; the headers of mp4-muxer, webm-muxer, earcut;
#    claude-docs/vendored-paper-patches.md; PinePaper.js ~L1130-1230 (runtime Paper.js overrides).
# 4. Advisories: https://github.com/<owner>/<repo>/security/advisories, `npm audit --omit=dev`

npm pack <pkg>@<ver> + diff of the two tarballs is the fastest way to see what changed in a small library; it is how the earcut, mp4-muxer and planck findings below were made.

Table — checked 2026-10-23

Decision key: bumped (in this branch) · hold (needs the owner, reason given) · current (nothing newer)

Dependency Source in repo Licence Ours Latest (date) Newest in our major Local patches Decision
Paper.js js/vendor/paper-full.min.js (+ runtime overrides in PinePaper.js) MIT 0.12.18 + 9 cherry-picks 0.16.0 (2026-10-21) — (0.x) localStorage shim; #2102 #2028 #1880 #1801 #1936 #1866 #1957 #2066 #1108; runtime: Style.getFontStyle quoting, PointText._draw/_getBounds (direction, tabular figures) hold — no upgrade (owner); port list below
math.js npm mathjs Apache-2.0 15.2.0 15.2.0 (2026-04-07) 15.2.0 none current
opentype.js npm opentype.js MIT 1.3.4 2.0.0 (2026-05-06) 1.3.4 (1.3.5 is a mis-tagged 2.0 prerelease, deprecated) none hold — major
planck (Box2D) npm planck (was planck-js, deprecated name) MIT 1.3.0 → 1.5.1 1.5.1 (2026-10-12) 1.x none bumped 2026-10-24 (owner decision), knockdown re-tuned — see below
d3-geo npm ISC 3.1.1 3.1.1 — none current
d3-geo-projection npm ISC 4.0.0 4.0.0 — none current
topojson-client npm ISC 3.1.0 3.1.0 — none current
@observablehq/plot npm ISC 0.6.17 0.6.17 — none current
DOMPurify npm dompurify Apache-2.0 / MPL-2.0 3.4.17 3.4.17 (2026-10-12) — none current
@sentry/browser npm MIT 10.32.1 → 10.76.2 11.9.0 (2026-10-23) 10.76.2 none bumped within 10.x; 11 held
gif.js js/vendor/gif.js, gif.worker.js MIT 0.2.0 0.2.0 (2016-12-06) — none current (abandoned upstream)
jsPDF js/vendor/jspdf.umd.min.js MIT 2.5.2 4.2.1 (2026-03-17) 2.5.2 none in the file; PdfFonts.js hooks pdf.internal.events hold — major; advisories assessed below
PDF.js js/vendor/pdf.min.js, pdf.worker.min.js Apache-2.0 3.11.174 6.4.299 (2026-10-03) 3.11.174 none hold — major; CVE-2024-4367 mitigated in this branch
mp4-muxer js/vendor/mp4-muxer.min.js MIT 5.1.3 (patched) 5.2.2 (2025-07-02; package deprecated for Mediabunny) 5.2.2 AAC edit list (PP_EDIT_LIST), clip-only elst (PP_CLIP_LIST), self.Mp4Muxer global for the worker hold — 5.2 adds a colr box, see below
webm-muxer js/vendor/webm-muxer.min.js MIT 5.1.4 (patched) 5.1.4 — per-frame alpha in BlockAdditional (PP_ALPHA) current
earcut js/vendor/earcut.js ISC 3.2.3 → 3.2.4 3.2.4 (2026-09-28) — default → named export (structural) bumped
@huggingface/transformers (+ onnxruntime-web) js/vendor/transformers/ Apache-2.0 3.0.2 4.3.1 (2026-10-06) 3.8.1 none (browser ESM build, see VERSION.txt) hold — needs a WebGPU/R2-weights check
WebLLM js/vendor/web-llm/index.js Apache-2.0 0.2.79 0.2.86 (2026-10-11) 0.2.86 none hold — needs a WebGPU device to verify
MediaPipe Tasks Vision js/vendor/mediapipe/ Apache-2.0 0.10.14 1.1.1 (2026-10-14) 0.10.35 none hold — needs a webcam check; 19 MB of binaries
lottie-web npm (devDependency, parity e2e only) MIT 5.12.2 → 5.13.0 5.13.0 — none bumped
Tailwind CSS npm (build-time CSS) MIT 3.4.19 4.3.3 3.4.19 none hold — major (v4 is a new engine and config format)
three.js — — not a dependency (World3D is our own renderer) — — — n/a

npm audit --omit=dev: 0 vulnerabilities (2026-10-23). Note that it cannot see anything under js/vendor/; the two advisories that matter this month (PDF.js, jsPDF) are both vendored.

What changed upstream that matters to us

Paper.js — upstream is active again

The premise that upstream is abandoned (claude-docs/vendored-paper-patches.md, and the owner’s note of 2026-10-23) is out of date: lehni published 0.12.19, 0.12.20, 0.13.0, 0.14.0, 0.15.0 and 0.16.0 between 2026-10-18 and 2026-10-21. Per the owner’s instruction there is no upgrade plan here, only what is worth porting.

Several of our cherry-picks are now upstream, in their own form:

Our patch Upstream now
#2028 splitBoolean drops options 0.13: boolean ops with trace:false/stroke:true honour insert (#2028)
#1957 compact arc flags 0.13: arc flags not separated from numbers (#1808, #1885)
#1866 rect with only rx or ry 0.13: rounded rectangles with only rx or ry (#1863)
#1801 Option+Space stuck key 0.13: space key staying down after Option+Space
#1108 multi-line text SVG export 0.15: point text keeps a <tspan> per line
#2066 letterSpacing (px, Style#letterSpacing) 0.13: tracking, in thousandths of an em — a different API; our letterSpacing callers would need mapping
shortlist #1822 gradient + strokeScaling:false 0.13 (#1821, #1822)
shortlist #1994 SVG image de-dupe 0.13 reuseImages (#1993)
shortlist #1774 hit-test container class 0.13: groups hit wherever a child is hit (#2069)

Individual upstream fixes worth porting into our 0.12.18 (each is small, and each fixes something we can hit):

  1. PathFlattener on straight curves (0.15): points at offsets on lines without handles were “off everywhere but at the ends”. Feeds getPointAt on straight segments — trim paths, dashes, moves_along_path on polylines. Highest value; also the most likely to move existing output, so it needs a before/after render of the path-follow and trim scenes.
  2. Shadows of filled-and-stroked items (#892) and shadows of text (#2347) (0.15). Check against our own _canComposite shadow work (27 SHADOWBODY) before porting — it may already route around this.
  3. Stroke bounds after scaling items that apply their matrix, and after changing a parentless item’s stroke (#2343) (0.15). Affects selection boxes and fit.
  4. Curve#getTimeOf near the ends of straight curves, and beyond ~2e8 (#2291) (0.15) — the infinite canvas.
  5. SVG import: <use> takes the styles of where it is used (#2090), defs referenced before they are defined (#1146, #1884, #1978), inline style beats presentation attributes (#1962), numeric ids (#1754) (0.13/0.15). We import user SVGs, and DOMPurify does not change any of this.
  6. Temporary canvases release their pixels (0.14) — memory over long sessions; independent of #2102.
  7. Boolean operations (0.15): many correctness fixes (#1285, #1837, #1929, #1940, #2064, #2290 …), an array form that unites 500 paths in 0.22 s instead of 2.9 s, and a planar-graph rewrite. The rewrite is not a cherry-pick; the array form could be, if pattern/collage code unites many paths.

Do not port without a decision: 0.13 Rectangle#left/right/top/bottom stretch instead of move (and so do item bounds sides); 0.14 Path.RegularPolygon segment order and orientation; 0.15 PointText bounds from font ascent and descent, leading → lineHeight, Style#getFontStyle() returning the CSS style (our override in PinePaper.js ~L1136 wraps exactly that function), 0.16 RTL layout (overlaps our PointText._draw direction override). Each of these changes existing scenes.

PDF.js — CVE-2024-4367 was exploitable here (fixed in this branch)

3.11.174 compiles font glyph programs with new Function; a crafted FontMatrix runs script (GHSA-wgrm-67xf-hhpq, fixed upstream in 4.2.67). _headers gives the editor script-src … 'unsafe-eval', so the CSP did not stop it, and Import PDF opens user files. Commit security(pdf-import) passes isEvalSupported: false to getDocument, the advisory’s own mitigation, and a test pins it.

Upgrade plan (major, held): 4.x+ ships ES modules only (pdf.min.mjs, pdf.worker.min.mjs), so _loadPdfJs becomes a webpackIgnore dynamic import() and the worker path changes; sw.js precache entries change; the AboutCredits mapping keys change. Verify by importing a PDF with embedded Type1/CFF fonts and comparing the raster with the 3.11 import.

jsPDF — ten advisories since 2.5.2, none on a path we call

Advisories 2026-01 → 2026-03 (all fixed by 4.2.1): HTML injection in output methods (critical), PDF object injection via addJS (critical) and via FreeText annotation colour, AcroForm JavaScript injection (×2), GIF and BMP dimension DoS, XMP metadata injection, an addJS race, and Node-only path traversal. Our calls are new jsPDF, addPage, text, setFont, addFileToVFS/addFont, addImage of PNG/JPEG data URLs we rendered ourselves, and output('blob') — no addJS, AcroForm, annotations, metadata, GIF/BMP input or the window-opening output methods. So the exposure is low, which is why it is held rather than forced.

Upgrade plan (major): 2.5.2 → 4.2.1. Read js/core/PdfFonts.js first — it subscribes to pdf.internal.events (putFont) and renames /BaseFont; that internal is the most likely break. Then the PDF export specs, the print PDF (bleed/TrimBox), the text-layer and CJK/Arabic font embedding tests. Also remove the dead jsDelivr fallback in ExportEngine.js (L4370) — the CSP blocks that host anyway.

mp4-muxer 5.1.3 → 5.2.2 — held because it changes every MP4

5.2.x writes a colr/nclx box from decoderConfig.colorSpace (primaries, transfer, matrix, full-range flag). Today our MP4s carry no colr; players read colour from the H.264 VUI or guess. Adding it is probably right for the broadcast BT.709 path (BroadcastColor.js), but it changes the bytes of every export, and a full-range or sRGB (iec61966-2-1) report from Chrome would change how players display existing scenes. The maps also write 0 (“reserved”) for any value they do not list. Also in 5.2: minFragmentDuration, chunked writing folded into the stream writer, chunkSize must be ≥ 1024. Plan: re-apply the three local patches (PP_EDIT_LIST, PP_CLIP_LIST, self.Mp4Muxer) to 5.2.2’s minified build, export the broadcast, sRGB-canvas and alpha scenes, and compare the decoded luma (_startLumaProbe) and ffprobe colour fields before and after. Or migrate to Mediabunny, which supersedes both muxers and writes elst itself (the README already names this).

planck — the upgrade fixes a centre-of-mass bug, and our knockdown is tuned on the bug

planck-js is deprecated; its successor is planck (same code base, same exports). Swapping the import to [email protected] fails one test: ExtrudedTypePhysics “after contact the letters react” — the largest letter turn is 10.3° instead of > 20°. Bisected: [email protected] and 1.4.2 pass, 1.4.3 fails. 1.4.3 is one character in src/common/Matrix.ts:

 export function addVec2(out, v, w) {
   out.x = v.x + w.x;
-  out.y = v.x + w.y;
+  out.y = v.y + w.y;

addVec2 is called by PolygonShape.computeMass, so in the version we ship every polygon body’s centre of mass has the wrong y (it takes the centroid’s x). Letters (extruded type, D79) and boxes rotate about the wrong point. 1.5.x adds fixes for non-finite sweeps and duplicate contact ids that made World.step() throw.

Owner’s choice, not taken here because it changes an existing feature’s look: (a) take [email protected] and re-tune the knockdown (ball speed/aim, or the test’s 20° bar) on correct physics; (b) stay on [email protected]; © [email protected] — the rename only, same behaviour, still has the bug. Sites to change for (a): js/PhysicsWorld.js L15, js/PinePaper.js createExtrudedText (import('planck-js')), __tests__/ExtrudedTypePhysics.test.js, the About row’s name.

Owner decision (2026-10-24): (a). Commits build(deps): planck-js 1.3.0 -> planck 1.5.1 and fix(world3d): re-tune the extruded-type knockdown.

What the bug did, measured with body.getLocalCenter() on Anton “IMPACT” (metres, y down; the hulls are centred on their own centroids, so the right answer is 0): P −0.641, C −0.490, A −0.063 on 1.3.0, all 0.000 on 1.5.1. P was 64 px and C 49 px top-heavy. A non-square planck.Box in the Physics World tools was off by hx − hy the same way.

The re-tune. The knockdown’s defaults now live in one exported constant, LetterPhysics.KNOCKDOWN, which createExtrudedText, bakeLetterImpact and the test read (the test used to carry a copy of them):

default before after
aim (height on the first letter) 0.75 0.85
ball speed 7.5 cap heights/s 8.5
ball density 6 4
letter friction 0.6 0.8

Method: a harness bakes Anton “IMPACT” exactly as ExtrudedTypePhysics does and reports, per letter, the largest turn, the turn at rest and the distance moved. About 1,000 settings of friction, aim, ball density, speed, restitution and angular damping were scored against the 1.3.0 per-letter profile; the closest few were then run on seven seeds and on STUDIO, PINEPAPER, BOOM, HELLO and WOW.

Seed 1 (largest turn / turn at rest / px moved):

I M P A C T settled
1.3.0, old defaults 53.6° / 53.6° / 32 100 87 82 100, rests at 20.3° 108, rests at 24.1° 3.50 s
1.5.1, old defaults 10.3° / 0.5° / 141 130 117 113 105 102 2.10 s
1.5.1, new defaults 54.8° / 54.8° / 23 102 89 85 80 75 2.98 s

Seven seeds, largest turn [letters past 20°]:

1 2 3 4 5 6 7
1.3.0 old 53.6 [ICT] 51.4 [I] 132.7 [CT] 58.4 [I] 51.4 [I] 51.6 [I] 55.4 [IT]
1.5.1 new 54.8 [I] 50.7 [I] 58.4 [I] 57.6 [I] 52.8 [I] 56.0 [I] 56.1 [I]

So the 1.3.0 look — the struck I tips back and rests leaning on the M, the rest of the word is shoved along upright — comes back, and more reliably than before (7 of 7 seeds; 1.3.0 had 6 of 7). What does not come back is C and T resting tilted at ~20°: that needed C’s centre of mass 49 px too high, and correct bodies settle upright.

Other words change too, as they must on correct physics: on 1.3.0 STUDIO’s O and HELLO’s letters cartwheeled (> 200°) while BOOM and WOW barely moved; with the new defaults STUDIO’s S tips ~32°, HELLO tips 35–118°, BOOM’s B falls, WOW leans ~26°, PINEPAPER 15–47°. Nothing was tuned for those words; they are listed so a later re-tune can compare.

Tests: the “> 20° turn” assertion did not assert the bug and is unchanged. wordmarkSpec now reads KNOCKDOWN, and the test also asserts the look: the first letter rests leaning 30–80°, every other letter moves > 40 px (fails on 1.5.1 with the old defaults, passes on 1.3.0 with them).

The rest

  • opentype.js 2.0.0 (major): variable fonts (fvar/avar/gvar parsing and rendering), COLRv0/CPAL colour glyphs, CFF2, cmap 13/14, WOFF2 handled gracefully, ESM build. deltaGlyphId became signed. Worth it for the Font Studio and setFontAxes, but it is imported in five places and used by PdfFonts subsetting; plan it with the PDF text-layer tests.
  • Sentry 11 (major): integrations split out (consoleIntegration, interactionsIntegration, userTimingSpansIntegration), unhandled errors report session status unhandled instead of crashed, default session lifecycle page. Our init uses only browserTracingIntegration; check the dashboards’ crash-rate panel before taking it.
  • transformers.js 3.0.2 → 3.8.1 (same major) or 4.x (new C++ WebGPU runtime, Safari 26 WebGPU). Re-vendor with npm run vendor:ml, deploy the matching onnxruntime wasm, then run DETR and OWL-ViT on a real image in Chrome with the R2 weights (the CSP is connect-src 'self'). 3.8 also brings SAM2/SAM3, relevant to extract_object.
  • WebLLM 0.2.79 → 0.2.86: npm run vendor:web-llm, then the on-device Assistant on a WebGPU machine; WebLLMAdapter.primePrefix is the call most likely to notice an internal change.
  • MediaPipe 0.10.14 → 0.10.35 (same line) or 1.x: re-copy vision_bundle.mjs + wasm, keep the SIMD-only choice, test the Vision Puppeteer with a webcam.
  • Tailwind 4: a rewrite (CSS-first config, new engine); not a dependency refresh.
  • gif.js: no release since 2016. If GIF export needs work, gifenc is the maintained option; not a bump.

Results of this run

Commits on claude/deps-refresh (each revertible on its own):

  1. build(vendor): earcut 3.2.3 -> 3.2.4 — body byte-identical to upstream src/[email protected] except the named-export line; MeshAuthoring, ExtrudedTypePhysics, AuthoredMesh*, World3D suites pass (148 tests).

  2. security(pdf-import): pdf.js never evals a PDF's font programs (CVE-2024-4367) + PdfImportNoEval.test.js.

  3. build(deps-dev): lottie-web 5.12.2 -> 5.13.0 — 11 Lottie suites (310 tests) pass; parity e2e passes except isometric-data-analysis, which times out inside PinePaper’s own import (also in graph-lottie-roundtrip, which does not load lottie-web).

  4. build(deps): @sentry/browser 10.32.1 -> 10.76.2 — +5.9 KB gzip on the sentry split chunk (esbuild, our calls).

  5. docs(about): credits for PDF.js, mp4-muxer, webm-muxer, earcut and MediaPipe, which ship from js/vendor/ and were not credited; AboutCredits.test.js now fails when a vendored library has no credit.

  6. This plan, and js/vendor/README.md corrected (it listed webm-muxer as unmodified and missed two mp4-muxer patches).

  7. build(deps): planck-js 1.3.0 -> planck 1.5.1 and 8. fix(world3d): re-tune the extruded-type knockdown (owner decision 2026-10-24; the first of the two leaves ExtrudedTypePhysics red until the second).

Test evidence and bundle sizes: see “Verification” at the end of this file.

Verification (2026-10-23/24)

Check origin/main (b5c4a2d4) this branch
npx jest (full suite, 2 workers) 991 suites / 18,910 passed, 0 failed 993 suites / 18,974 passed, 1 failed*
npm run build ok ok
dist/js + dist/chunks JS total 26,544,049 B (151 files) 26,602,077 B (150 files), +58,028 B
Sentry chunks (gzip -9) sentry.*.js 79,200 + 5225.*.js 78,181 = 157,381 sentry.*.js 175,289, +17,908 B
app.js / PinePaper.js 5,863,823 / 4,069,478 5,864,496 / 4,070,206
js/vendor/earcut.js 11,579 11,692

* SitemapLastmodIsReal “a spot-checked page matches its source’s commit date”: the dist/sitemap.xml on disk was built before about.html was committed. After npm run build it passes (16/16 with AboutCredits).

Browser checks against the production build (Chromium, scripts/e2e-serve.mjs, ad-hoc spec not committed):

  • Import PDF with isEvalSupported: false: a jsPDF page with bold Helvetica text and a filled rectangle imports (imported: 1) and re-renders 3,048 dark text pixels in the top band; pdf.js 3.11.174.
  • createExtrudedText('IMPACT', { physics: { ball: {} } }) in a World3D field: ok, 6 letters, contact at 0.733 s (earcut 3.2.4 caps).
  • /editor?sentry=true with every request to sentry.io aborted: Sentry.SDK_VERSION is 10.76.2, no page error, captureMessage runs.
  • Lottie: see commit 3 above.

The Sentry chunks grew by 17.9 KB gzip, more than the 5.9 KB the esbuild measurement of our calls predicted (the webpack split also absorbed the old 5225 chunk). It is one commit (fe64cc9e) and can be reverted alone if that cost is not worth the 44 minor releases of fixes.

Verification of the planck commits (2026-10-24)

  • npx jest: 994 suites, 18,975 passed, 0 failed (5 skipped, as before).
  • npm run build: ok. Shipped JS 26,602,077 → 26,602,736 B (+659 B); the physics chunk 270,057 → 270,119 B.
  • 3D/physics e2e (world3d-animation, world3d-geometry, world3d-restore, physics-rubber-demo, audio-reactive-world-timeline): 27 passed, 2 failed. Both failures are in audio-reactive-world-timeline (ReferenceError: canDecodeMp4 is not defined, a helper the spec never defines) and fail the same way on a build of the commit before the planck change.
  • In the browser (production builds of before and after, Anton “IMPACT” at 160 px in a World3D field, frames at t = 0, contact + 0.25 s, contact + 0.8 s, 4.5 s): before, the I rests at 47.7° against the ball and C and T sit tilted 28.8° and 28.4° on their corners; after, the I rests at 38.9° against the ball and the rest of the word stands upright, shoved 39–58 px. Contact 0.733 s → 0.692 s, settled 2.94 s → 2.86 s.