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):
- PathFlattener on straight curves (0.15): points at offsets on lines without handles were “off everywhere but at
the ends”. Feeds
getPointAton straight segments — trim paths, dashes,moves_along_pathon 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. - Shadows of filled-and-stroked items (#892) and shadows of text (#2347) (0.15). Check against our own
_canCompositeshadow work (27 SHADOWBODY) before porting — it may already route around this. - 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.
Curve#getTimeOfnear the ends of straight curves, and beyond ~2e8 (#2291) (0.15) — the infinite canvas.- SVG import:
<use>takes the styles of where it is used (#2090), defs referenced before they are defined (#1146, #1884, #1978), inlinestylebeats presentation attributes (#1962), numeric ids (#1754) (0.13/0.15). We import user SVGs, and DOMPurify does not change any of this. - Temporary canvases release their pixels (0.14) — memory over long sessions; independent of #2102.
- 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/gvarparsing and rendering), COLRv0/CPAL colour glyphs, CFF2, cmap 13/14, WOFF2 handled gracefully, ESM build.deltaGlyphIdbecame signed. Worth it for the Font Studio andsetFontAxes, but it is imported in five places and used byPdfFontssubsetting; plan it with the PDF text-layer tests. - Sentry 11 (major): integrations split out (
consoleIntegration,interactionsIntegration,userTimingSpansIntegration), unhandled errors report session statusunhandledinstead ofcrashed, default session lifecyclepage. Our init uses onlybrowserTracingIntegration; 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 isconnect-src 'self'). 3.8 also brings SAM2/SAM3, relevant toextract_object. - WebLLM 0.2.79 → 0.2.86:
npm run vendor:web-llm, then the on-device Assistant on a WebGPU machine;WebLLMAdapter.primePrefixis 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,
gifencis the maintained option; not a bump.
Results of this run
Commits on claude/deps-refresh (each revertible on its own):
-
build(vendor): earcut 3.2.3 -> 3.2.4— body byte-identical to upstreamsrc/[email protected]except the named-export line; MeshAuthoring, ExtrudedTypePhysics, AuthoredMesh*, World3D suites pass (148 tests). -
security(pdf-import): pdf.js never evals a PDF's font programs (CVE-2024-4367)+PdfImportNoEval.test.js. -
build(deps-dev): lottie-web 5.12.2 -> 5.13.0— 11 Lottie suites (310 tests) pass; parity e2e passes exceptisometric-data-analysis, which times out inside PinePaper’s own import (also ingraph-lottie-roundtrip, which does not load lottie-web). -
build(deps): @sentry/browser 10.32.1 -> 10.76.2— +5.9 KB gzip on thesentrysplit chunk (esbuild, our calls). -
docs(about): credits for PDF.js, mp4-muxer, webm-muxer, earcut and MediaPipe, which ship fromjs/vendor/and were not credited;AboutCredits.test.jsnow fails when a vendored library has no credit. -
This plan, and
js/vendor/README.mdcorrected (it listed webm-muxer as unmodified and missed two mp4-muxer patches). -
build(deps): planck-js 1.3.0 -> planck 1.5.1and 8.fix(world3d): re-tune the extruded-type knockdown(owner decision 2026-10-24; the first of the two leavesExtrudedTypePhysicsred 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=truewith every request tosentry.ioaborted:Sentry.SDK_VERSIONis10.76.2, no page error,captureMessageruns.- 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 inaudio-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.