Repository navigation
Description
Living tracking issue. It supersedes the infra parts of #2863 (2024 Roadmap). We'll keep editing this as we experiment, and record what changed in the Decision log at the bottom.
Status (2026-11-04): Phase 1 is done and on main. 37.4.0 and 37.4.1 shipped from the new OIDC pipeline (plus a hand-published legacy victory-native@37.3.7). Phase 2 is done on chore/2026-uplift-02: Turborepo has replaced wireit + nps (#3102), pnpm pack:check checks published output (#3103), the UMD bundles are gone (#3107), tsdown builds the victory-* packages into dist/ with exports maps (#3108), and Vitest has replaced Jest (dadb6af) with coverage reported in CI (#3109), and oxlint + oxfmt have replaced ESLint + Prettier (#3110, #3111, #3112). Unused root dependencies are gone (#3113), and root dev dependencies are current, with a pnpm dep:check routine (#3114). Website dependencies are current, Tailwind 4 included (#3116, #3119), and Vercel previews land on the docs (#3117). A headless browser probe of the docs and the ESM browser page drove a bugfix pass (#3120, #3123, #3124). victory-vendor's build tools are current and it stays on Babel 7 (#3127; a single-tool build is #3126, after the uplift). The CI rewrite is done: hardening (#3130) and an ESM bundle-size report (#3131), which also showed that { VictoryPie } now tree-shakes through the victory barrel. TypeScript is underway: #3077's namespace fix landed (#3132), the repo moved to bundler resolution on TS 6 (#3133), and types:check runs on TS 7 with published declarations type-checked in pack:check (#3134). cpx2 and rimraf are gone (#3136), and the docs site is on react-live 5 (#3137). The React peer range is now >=18, with a CI job on React 18.0.0 (#3138). Development and CI are on Node 26, and pnpm installs only versions at least 7 days old (#3139). Changelogs come from the official @changesets/changelog-github (#3140). The 38.0.0 canary checked out against 37.4.1. 38.0.0 is out (2026-11-03): the integration branch merged to main with a merge commit (#3141), then Version Packages #3143 published all 29 packages. Browser docs are at @38 (#3144). Phase 3 is underway: the stories are a workspace package with local story types (#3145) and have a Vite gallery, type-checked in CI (#3146); Every story has an SVG snapshot test in CI (#3147); next, fixing the stories that log errors and warnings and failing tests on console output, then removing Storybook and Chromatic. 38.x work lands as PRs straight to main (decision log, 2026-11-03). Phases 3–5 follow as 38.x releases.
Why
- Nothing has been released since 37.3.6 (Jan 2025). The release pipeline is stuck (Release pending: changesets would publish 37.4.0 for bug fixes (victory-candlestick changeset is "minor"); Version Packages PR #3047 is stale #3091, Version Packages #3047): every
release.ymlrun since Dec 2025 fails at checkout becauseCHANGESETS_TOKENno longer works, and the shared setup action's wireit GitHub Actions cache step fails too (the legacy cache API is gone). - React 19 users hit broken typings and broken container events (Victory typing breaks on upgrade to React 19 #3076, Container component mouse events / tooltip stopped working after upgrading react 18 -> 19 #3048). Both were closed by the stale bot, not fixed.
- The build is hard to maintain:
- wireit config is generated into each of 30 packages.
- Each package builds four ways: Babel ESM, Babel CJS, a webpack UMD bundle, and
tsctypes. - Type errors don't fail the build (
types:pkg:compile || types:warning). - No
exportsmaps except invictory-vendor. - CI runs Node 18. The React peer range is
>=16.6.0.
- The legacy
victory-native(37.x, npm taglegacy) is still built here, thoughvictory-nativelatest(43.x) is published from victory-native-xl.
By the numbers (Oct 2026)
victory-vendor: about 215M downloads a month, about 99% via recharts, which depends onvictory-vendor@^37.0.2and usesd3-scaleandd3-shape. It's our highest-stakes package.victory: about 1.7M a month. Each sub-package gets roughly the same downloads (within about 7%), so very few people install them on their own.- React downloads by major (last week): 19 is 75%, 18 is 22%, and 16 and 17 together are under 3%.
Principles
- Infra first, source last. Hold off on substantive edits to
packages/*/src. Some tooling changes will inevitably touch source mechanically: the oxfmt reformat, oxlint autofixes and suppressions, and fixes for type errors from the TS upgrade and from making type errors fail the build. Each of those lands as its own PR, separate from the config change, with no behavior changes mixed in, and its commit goes into.git-blame-ignore-revs. - Keep tasks as plain
package.jsonscripts. A task runner orchestrates and caches them; it doesn't own them. - Every phase is releasable. Small PRs, and
mainstays green. - Follow react-live's lead (Chore: full project upgrade (mostly infra, little code) react-live#418) where it fits a 29-package monorepo.
Branching and merge strategy
- Integration branch:
chore/2026-uplift-02, cut frommainafter 37.4.1, pushed toorigin. It replaceschore/2026-uplift(Phase 1), which is retired; see the decision log. - All uplift work lands as small PRs into the integration branch, not
main. Each PR gets a changeset if it changes what users of a published package get (code, types,exports, published files,dependencies/peerDependencies). Changes to onlyscriptsordevDependenciesdon't need one. Squash-merging these PRs into the integration branch is fine. mainkeeps getting fixes. Mergemaininto the integration branch regularly (merge, not rebase, since it's shared) so the final merge stays small.- CI:
ci.ymlruns onmainandchore/2026-uplift*(push and pull_request). The release workflow publishes only frommain, except a deliberate canary (see Phase 1). - Final merge: one PR from the integration branch into
main, merged with a merge commit, not a squash. That keeps per-PR history and keeps the SHAs in.git-blame-ignore-revsvalid. The accumulated changesets then produce a Version Packages PR for 38.0.0.- The repo allows squash merges and the merge button can default to squash (that's how 2026 Uplift: phase 1 (CI, pnpm 12, OIDC release pipeline) #3098 ended up squashed). For the final merge, temporarily allow only merge commits.
- "Automatically delete head branches" deletes the integration branch on merge. Keep it off, or protect
chore/2026-uplift*with a ruleset. - Release gate before merging: the Road to 38.0.0 checklist below (Phase 2 plus the breaking items; decision log, 2026-11-01). Also required:
mainmerged in, green CI, and a successful canary publish from the integration branch. - After 38.0.0 is published, update the browser docs examples: esm.sh
victory@37→@38; jsDelivr's pinned version, plus its React version and remap if jsDelivr baked in a different React. Check both with Test the "Using Victory in the browser" docs examples #3106 (or its prototypes, by hand).
Releases and in-flight work
- Interim 37.x releases, from the new pipeline (done 2026-10-26).
- Canary
37.4.0-canary-20261026180958fromchore/2026-uplift: all 29 tarballs diffed against 37.3.6. Same file lists;victory-vendorunchanged (all 299 files,exports); only the expected changes (fix: handle empty labelStyle values #3043, FIX: Handle error bar empty theme object #3046,enginesremoval, version pins). - Phase 1 merged to
mainin 2026 Uplift: phase 1 (CI, pnpm 12, OIDC release pipeline) #3098 (squash by accident; the tree is identical to the integration branch). - 37.4.0 (Version Packages Version Packages #3047): all 29 packages via OIDC with provenance, one
v37.4.0tag and GitHub release. - 37.4.1 (victory-vendor: License notification improvements #3092 → Version Packages Version Packages #3099):
victory-vendorlicenses, shipped on its own so the change to our highest-stakes package is isolated. Diffed before merge: onlyLICENSE.txt(all packages) plus vendor's README andlicensefield;exportsand code unchanged.
- Canary
- 38.0.0 (2026-11-03): final merge [uplift] 38.0.0: 2026 infra uplift (Phase 2 and breaking changes) #3141 → Version Packages Version Packages #3143 (see Road to 38.0.0, step 6).
- Infra work doesn't wait on open PRs. Unmerged work (Phase 0 and community PRs) comes in during or after the uplift, whichever is easier for each PR:
- During: change the PR's base to
chore/2026-uplift-02on GitHub, so it runs against the new CI. Preferred once Phase 2's CI is up. - After: leave it targeting
main, then mergemaininto the integration branch. - After the oxlint + oxfmt churn PR: run
pnpm format:fixand the lint fix on the PR branch first, then merge. That clears most of the mechanical conflicts.
- During: change the PR's base to
Road to 38.0.0
The integration branch already has breaking changes (UMD removal, dist/ + exports, ES2022 output, src/ no longer published), so its next release is a major. Ship it as soon as Phase 2 and the breaking items are done, instead of after every phase (decision log, 2026-11-01). Anything breaking that misses 38.0.0 waits for 39 or ships behind an opt-in.
- Finish Phase 2:
TS 7 (tsgo) for([uplift] chore: TypeScript 7 for types:check, check published declara… #3134),types:check([uplift] chore: website react-live 5 #3137),react-livev5the last cruft (([uplift] chore: replace cpx2 and rimraf with Node scripts #3136). tsdown'scpx2,rimraf)dts.generator: "tsgo"can follow in 38.x (needs TS 7.1 stable). - Breaking items from Phase 4:
- React peer range
>=16.6.0→>=18, with a CI job pinned to React18.0.0([uplift] chore: require React 18 (peer >=18) with a React 18.0.0 CI job #3138). React 19 type work (Victory typing breaks on upgrade to React 19 #3076, Container component mouse events / tooltip stopped working after upgrading react 18 -> 19 #3048) can follow in 38.x. - victory-vendor: ESM import of d3-color, d3-format, d3-path, d3-time-format, d3-voronoi fails without hoisting #3104: decided (2026-11-02): keep victory-vendor's
./d3-*subpaths. - victory-vendor: es/*.js is ESM in a package without "type" (relies on Node syntax detection) #3105: decided (2026-11-02):
es/package.json("type": "module") is not in 38.0.0.
- React peer range
- victory-vendor's version: decided (2026-11-02): it stays in the
fixedgroup and ships as 38.0.0. recharts (victory-vendor@^37) stays on 37.4.x until it opts in. - End-of-uplift review (Housekeeping),
pnpm dep:check, mergemainin, green CI. Done in [uplift] chore: Node 26, pnpm minimumReleaseAge 7 days, dev deps #3139 (Node 26,minimumReleaseAge, dev-dependency updates; CI green on the integration branch);mainhad nothing new (2026-11-03). Changelog generator swapped in [uplift] chore: use the official @changesets/changelog-github #3140.- Changelog generator ([uplift] chore: use the official @changesets/changelog-github #3140): the deprecated
@svitejs/changesets-changelog-github-compactis replaced by the official@changesets/changelog-github(disableThanks, like react-live).scripts/changelog.jsnow only drops "Updated dependencies" lines (with thefixedgroup, every release would list sibling bumps in all 29 changelogs). Rendered the pending changesets with both: same entries; each now leads with PR + commit links, and bare#123references are linked.
- Changelog generator ([uplift] chore: use the official @changesets/changelog-github #3140): the deprecated
- Canary to
nextfrom the integration branch:38.0.0-canary-20261103184440(all 29, frombce12bb4b, SLSA v1 provenance), checked against 37.4.1 (2026-11-03). The recharts smoke test waits until recharts opts in tovictory-vendor@38(see step 3).- Every file in the 29 tarballs is identical to a local
pnpm packof the same commit (except the canary'sCHANGELOG.md), so the localpack:checkresults (publint, attw, isolatedrequire()/import(), vendor snapshot, declaration check) hold for the published tarballs. victory-vendor: same 300 files;exports,dependenciesand the other manifest fields unchanged (onlyversion,devDependencies,scripts). 105lib-vendor/files differ only by Babel 7.29's interop helpers and redundant parentheses, as expected from [uplift] chore: update victory-vendor build tools #3127.- Registry install of
victory(React 19, Node 26): all 28victory-*packages export the same 234 names as 37.4.1, viarequire()andimport()alike (37.4.1'simport()loaded CJS and added__esModule/default/module.exports).renderToStringworks for bar, line + voronoi, pie, stack + zoom and polar charts, via ESM and CJS. Markup changes vs 37.4.1: no strayindexattribute on bars ([uplift] fix: keep Victory props off primitives' DOM elements (#3118) #3123), and the path precision note under fix: normalize SVG path precision to prevent hydration errors #3087. - The changelog generator from [uplift] chore: use the official @changesets/changelog-github #3140 ran in the release workflow (canary
CHANGELOG.md: PR + commit links, no "Updated dependencies" lines).
- Every file in the 29 tarballs is identical to a local
- Final merge (merge commit) → Version Packages PR → 38.0.0, then update the browser docs examples to
@38.- Final merge [uplift] 38.0.0: 2026 infra uplift (Phase 2 and breaking changes) #3141 (merge commit
23ab5056e). Its review added aturbo.jsonfix: the shared test helpers now importd3-voronoidirectly instead of victory-voronoi's source, andtest/**andscripts/pack-check.mjsmoved toglobalDependencies(decision log, 2026-11-03). - Version Packages Version Packages #3143 → 38.0.0 (all 29 on
latest, SLSA v1 provenance,v38.0.0release). Its CI runs were held for approval ("Approve and run"), not skipped (see Releases and in-flight work). - Published tarballs diffed against the canary: only
version,CHANGELOG.md, the canary's rewrite of thevictory-chart: "*"devDependency, and a union member order in four declaration files (number[] | string[], same type). Every siblingdependencies/peerDependenciespin is38.0.0. - Browser docs at
@38([uplift] docs: browser examples at 38, Approve and run for Version Packages #3144) (esm.shvictory@38, jsDelivrvictory@38.0.0, which bakes in React 19.3.1 like the canary). Both examples checked in headless Chromium: 4 bars, Randomize works, one React, no errors. Same PR: AGENTS.md, CONTRIBUTING andrelease.ymldescribe "Approve and run" for the Version Packages PR.
- Final merge [uplift] 38.0.0: 2026 infra uplift (Phase 2 and breaking changes) #3141 (merge commit
After 38.0.0: Phase 3, the rest of Phase 4 (React 19 support, runtime dependency updates, SSR smoke tests, types issues) and Phase 5 ship as 38.x minors and patches. Decided (2026-11-03): they land as PRs straight to main, no new integration branch.
Phase 0: Intake (doesn't block the uplift)
- victory-vendor: License notification improvements #3092: victory-vendor licenses (fixes victory-vendor: published package ships no license file, and the
licensefield misses BSD-3-Clause #3089, victory-vendor declares "license": "MIT AND ISC" but repo ships only the MIT text — no ISC license text anywhere #3090). Shipped in 37.4.1. - Stop using typescript module syntax #3077:
export module→export namespace, needed to typecheck under TS 7. Landed on the integration branch as [uplift] fix: use namespace instead of module in immutable types (#3077) #3132 (author credited as co-author). - fix: normalize SVG path precision to prevent hydration errors #3087: SVG path precision (Hydration error using VictoryLine on NextJS 15. #2941 SSR hydration)
- Canary check (2026-11-03):
victory-vendor's CJSlib-vendor/is a snapshot of the lockfile's d3-shape 3.1.0 / d3-path 3.0.1 (no rounding), while its ESMes/re-exports the user's installed d3 (3.2.x / 3.1.x round paths to 3 decimals). So CJS and ESM render different path strings (e.g.183.33333333333334vs183.333), as in 37.4.1 for bundlers. New in 38: Nodeimport()now takes the ESM path, so Node ESM SSR matches bundled clients; CJS SSR still differs. Updating vendor's d3 (Phase 4 runtime deps) would align them.
- Canary check (2026-11-03):
- Stale Version Packages PR Version Packages #3047: the new pipeline reused its branch and it became the 37.4.0 Version Packages PR.
Phase 1: Scope and release pipeline (OIDC)
Remove victory-native
- Delete
packages/victory-nativeanddemo/rn, plus the metro Babel preset and related deps, theignoreentry in the changesets config, and the native special case in the wireit sync script. Update the README, CONTRIBUTING and website docs to point at victory-native-xl (2026 Uplift: remove legacy victory-native package and RN demo #3094).- The "Overridden in victory-native" comments and
victory-core'sisNativeTouchEventpath stay until a source PR.
- The "Overridden in victory-native" comments and
- No npm deprecation.
latest(43.x) is where users are. - Legacy patch
victory-native@37.3.7onlegacywith the unreleased fix(native): fix container prop forwarding #3065 fix (Updating version to 37.1.1 - viewBox issue #2895, VictoryCursorContainer broken #3063). Published once, by hand, from a branch offv37.3.6(npm publish --tag legacy), outside this repo's release pipeline. Verified tarball vs 37.3.6 (only the four containers + version differ);lateststays 43.x (XL).
Tooling for releases
-
packageManager→pnpm@12.14.0(chore: upgrade pnpm 9.13 -> 12.14 #3095). pnpm settings moved from.npmrc/package.json"pnpm"topnpm-workspace.yaml(pnpm 11+ reads them only there), plus anallowBuildsallowlist.pnpm/action-setupv6. Lockfile resolution and packed output unchanged. -
@changesets/cliv3 ([uplift] ci: changesets v3 + OIDC release pipeline with canary #3096). Dropped the@changesets/cli@2.24.1patch (it only madechangeset publishhonorpublishConfig.tagforvictory-native). The existing changelog wrapper (scripts/changelog.jsover@svitejs/changesets-changelog-github-compact) works with v3 unchanged. - Keep the
fixed: [["victory*"]]group. All packages, includingvictory-vendor, version together.
Release workflow (.github/workflows/release.yml)
- Rebuilt on
changesets/actionv2 split jobs ([uplift] ci: changesets v3 + OIDC release pipeline with canary #3096), modelled on react-live's:- select-mode (read-only) → version (opens the Version Packages PR) or pack (build, then
changeset pack) → publish (changeset publish --from-pack-dir). permissions: {}at the top; each job gets only what it needs. Only publish and canary getid-token: write.package-manager-cache: falsein all release jobs (avoids cache poisoning).- publish and canary run in the
npm-publishenvironment (branch rules:main+ the integration branch; required reviewers optional, currently none). Notproduction: Vercel's docs deployments use it, and environment names are case-insensitive ([uplift] ci(release): use npm-publish environment instead of Production #3097). - Provenance is automatic under trusted publishing (SLSA v1 on 37.4.x).
- select-mode (read-only) → version (opens the Version Packages PR) or pack (build, then
- Tags and release notes: per-package tags and releases are off (
create-github-releases: false,push-git-tags: false). A follow-up step runsgh release create v{version} --generate-notes, which creates the one tag and the aggregate release.scripts/release.ts, its deps, andnps git:tagwere removed (see the decision log). - Canary:
workflow_dispatchin the same file (npm trust is bound to one workflow file).gh workflow run release.yml --ref <branch>publishesX.Y.Z-canary-<datetime>tonext(snapshot.useCalculatedVersion). - Version PR token: CI runs on PRs opened or updated with
GITHUB_TOKENare held for approval (seen on Version Packages #3143; earlier they didn't run at all). Current practice: after the PR's last update, a maintainer clicks "Approve and run". Better: a fine-grained token (this repo, contents + pull-requests) or a GitHub App token in the version job.
npm and GitHub setup (needs a maintainer)
- GitHub:
npm-publishenvironment, deployment branchesmain+chore/2026-uplift-02(waschore/2026-uplift). - npm:
scripts/npm-trust.sh(29 packages,--file release.yml --env npm-publish --allow-publish, npm ≥11.15.0,--dry-run). Run once. Kept for adding packages or re-creating trust: renamingrelease.ymlor the environment breaks publishing until trust is re-created (npm allows one trust config per package). - Now that OIDC publishing has worked (37.4.0, 37.4.1): set each package's npm publishing access to "require 2FA and disallow tokens", then delete the
NPM_TOKENandCHANGESETS_TOKENsecrets.
Validate
- Canary from the integration branch (see Releases and in-flight work). Repeat before the final merge, and smoke-test recharts against the canary
victory-vendor. - Interim 37.4.0 (and 37.4.1).
Phase 2: Build tooling (mechanical source churn only)
Config-only work goes first (turbo, tsdown, vitest, CI). The changes that churn source go last, each in its own PR:
- Type-error fixes once TS errors fail the build
- The TS upgrade and tsconfig changes
- The oxlint + oxfmt switch: the oxfmt reformat plus oxlint autofixes and suppressions (done as one format PR and one lint PR, [uplift] chore: format with oxfmt #3111 and [uplift] chore: fix oxlint errors #3112)
Open PRs don't block these. They catch up afterwards using the format-then-merge approach above.
- Task runner: Turborepo in place of wireit +
nps+scripts/sync-pkgs-wireit.js([uplift] chore: replace wireit + nps with Turborepo #3102).- Root
turbo.jsonplus package-level ones forvictory-vendorandwebsite. Task names kept (build,types:check,jest,lint, ...); ordering comes from^taskworkspace deps, so no generator. - CI cache via
actions/cache(no Vercel account needed);testrestores thebuildjob's cache,lintruns in parallel.--affectedon PRs. Telemetry off in all workflows. - ESLint and Prettier run with
--cache;lintno longer waits on any build. - Fixed: wireit inputs pointed at missing files (
test/jest-config.js,config/webpack.config*.js), so config edits didn't invalidate tasks. - Timings (local): warm
check4.3s → 0.6s; coldcheck~40s → ~45s (Jest worker pools per package; the Vitest PR should fix it). - Fixed in [uplift] chore: add pack:check #3103: the root
package.jsondepended onvictory-vendor/victory-voronoi(fortest/helpers), which put them and their dependencies into Turborepo's global hash. Any edit tovictory-core,victory-voronoiorvictory-vendorinvalidated every task.
- Root
- tsdown per package: ESM + CJS +
.d.ts, with publint and@arethetypeswrong/coreon every build ([uplift] chore: switch to tsdown. #3108). Remove the Babel configs.- Also did Phase 4's
dist/+exportsmove (see the decision log, 2026-10-27): ESM.mjs+.d.mts, CJS.cjs+.d.cts, one output file per source file,es2022(tsconfigtargetnow matches), no sourcemaps or declaration maps,src/no longer published. One sharedtsdown.config.mts; one turbobuildtask. - publint and attw run in
pnpm pack:check(CI), not in every build. .babelrc.build.jsandcross-envremoved..babelrc.jsstays forbabel-jestuntil Vitest;@babel/clistays for victory-vendor's build.- Only source change:
ComponentEventtype exported from victory-core, so dependents' declarations referencevictory-coreinstead of inlining its internal types. - Timings (local): cold
pnpm build12.1s (85 tasks) → 8.6s (29); coldcheck~45s → ~30s; warmcheck0.6s → 68ms. Exported names match 37.4.1 for all 28 packages (require()andimport()).
- Also did Phase 4's
- Type errors fail the build. Fix whatever surfaces.
- TS 5.7.2 had 0 errors in all 27
victory-*packages (build andtypes:checkconfigs, 2026-10-27). Since [uplift] chore: switch to tsdown. #3108, declaration errors fail the build (the|| echowarning is gone).
- TS 5.7.2 had 0 errors in all 27
- victory-vendor: port
scripts/build.js(d3 ESM → CJS vendoring) to tsdown/Rolldown, or keep it as-is behind a turbo task. Must still emitLICENSE.txt.- Kept as-is behind a turbo task, with its tools updated ([uplift] chore: update victory-vendor build tools #3127; see the decision log, 2026-10-29). A single-tool build is victory-vendor: explore a single-tool build (drop Babel) #3126, after the uplift.
- Published-output check:
pnpm pack:check([uplift] chore: add pack:check #3103), in CI. For every package: publint, attw, andrequire()+import()of every entrypoint from an isolated install (declared deps only).victory-vendormust also matchpack-snapshot.json: manifest fields, per-entrypoint resolution under node10/node16/bundler, known attw and load failures, file list. - Found by it: victory-vendor: ESM import of d3-color, d3-format, d3-path, d3-time-format, d3-voronoi fails without hoisting #3104 (ESM
importof five undocumentedd3-*subpaths fails without hoisting) and victory-vendor: es/*.js is ESM in a package without "type" (relies on Node syntax detection) #3105 (es/*.jsrelies on Node's ESM syntax detection). Neither affects recharts'd3-scale/d3-shape.
- Vitest replaces Jest (jsdom). Pushed directly as dadb6af (no PR; description in this comment).
- Vitest 5, run per package through turbo with one shared
test/vitest.config.mts(globals: true). Task renamedjest→test(pnpm test). - Tests:
jest.*→vi.*, onexit→it.skip. No snapshot changes; 619 passed, 2 skipped (same as Jest). - jsdom pinned to 20.x (what Jest used); see the dev-deps item below.
@vitest/eslint-pluginreplaceseslint-plugin-jest. The root.babelrc.jsand its presets are gone. - Timings (local): tests only ~10.5s → ~4.3s; cold
check~30s → ~18s; warmcheck66ms. - victory-vendor's
tests/*.test.tswere never run by Jest. Since [uplift] chore: report Vitest coverage in CI #3109 their runtime checks (subpath exports are functions) run inpnpm test. Their type checks (type re-exports,@ts-expect-erroron a bad name) still aren't type-checked:tsccan't resolve thevictory-vendor/d3-*self-imports undermoduleResolution: node(TS 7 item).pack:checkcovers most of this more strictly. Type-checked since [uplift] chore: TypeScript 6 with bundler module resolution #3133 (bundlerresolution). - Coverage, reported but not gated (Infra: Add coverage reporting to CI #2348), [uplift] chore: report Vitest coverage in CI #3109:
pnpm test:coverageruns all tests in one Vitest run with v8 (packages/*/src/**only); CI writes the summary to the job summary and doesn't fail on it. Baseline: 52% statements, 39% branches. pool: "vmThreads"([uplift] chore: report Vitest coverage in CI #3109): jsdom set up once per worker. Coverage run ~4.1s → ~1.5s;pnpm test~5.0s → ~4.6s.isolate: falsewas as fast but fails 5–57 tests when file order is shuffled (state leaks between files), so it's not used.
- Vitest 5, run per package through turbo with one shared
- oxlint + oxfmt replace ESLint + Prettier: config [uplift] chore: replace ESLint + Prettier with oxlint + oxfmt #3110, format churn [uplift] chore: format with oxfmt #3111, lint churn [uplift] chore: fix oxlint errors #3112. Both churn commits are in
.git-blame-ignore-revs.- oxfmt evaluation (0.75.0): it handles every file type we have (TS/TSX/JS, JSON, MD, MDX, YAML, CSS). On JS/TS it differs from Prettier 3.9 only in how long union types wrap (13 files); most of its diff from our Prettier 3.3 is what Prettier 3.9 changes too. The oxc VS Code extension covers both tools. Pinned to exact versions because oxfmt is still 0.x.
- oxfmt formats everything now, including docs (part of Infra: Lint docs in CI #2217), at
printWidth: 80. The unusedprintWidth: 40override for*.mdxis gone. Linting code blocks inside MDX is still not covered. - Lint does no formatting:
eslint-plugin-prettierandeslint-config-prettierare gone. CI'slintjob now also runspnpm format, so formatting is checked in CI for the first time. formatandlintare root-only scripts run outside turbo: each checks the whole repo in under a second, so caching saves nothing. Per-packagelintscripts and the turbo lint/format tasks are removed.- Rule parity:
.oxlintrc.jsoncomes from@oxlint/migrate. Three rules are oxlint nursery (experimental) rules, enabled by name:no-undef(JS files only;tsccovers TS),react/require-render-return,promise/no-return-in-finally. The rules not migrated are obsolete under strict mode, React 17+ or TS. Thecorrectnesscategory is off so the rule set matches ESLint's (see Phase 5). - oxlint can't set rule options in comments, so the 8 inline
/* eslint no-magic-numbers: [...] */comments became line-leveleslint-disablecomments.lintfails on unused disable directives. - Format churn left build output byte-identical (all 864
dist/and vendor files), as did the lint churn. @types/eslintoverride kept inpnpm-workspace.yaml: webpack (via Storybook) still pulls it in. Remove it with Storybook (Phase 3).- Timings (local, cold): format 2.3s (JS/TS/JSON only) → 0.9s (all 725 files); lint 8.7s (38s CPU, 30 turbo tasks) → 0.24s;
check~18s → ~15.6s. Warmcheck66ms → 1.2s, because format and lint aren't cached. - Type-aware linting (
oxlint --type-aware, needs tsgo) waits for the TS 7 item.
- TypeScript 7 + tsgo (deferred until after tsdown; when we get here, decide whether to give it its own issue). Move
tsconfigoffbaseUrl/moduleResolution: node(targetis alreadyES2022, [uplift] chore: switch to tsdown. #3108), fix Stop using typescript module syntax #3077, switchtypes:checkto tsgo, and set tsdown'sdts.generator: "tsgo"(needstypescript@7.1; experimental in rolldown-plugin-dts). Optionally use project references.- Measured 2026-10-27 on TS 7.0.2 (tsconfig adjusted): 0 source errors, 6 in
victory-candlestick.test.tsx, about 9x faster than tsc onvictory-core. Stop using typescript module syntax #3077'sexport moduleblocks pass only becauseskipLibCheckskips them. - Kept in this issue, as four small PRs (decision log, 2026-10-31): Stop using typescript module syntax #3077 → TS 6 + tsconfig → TS 7
types:check→ tsgo declarations. - Stop using typescript module syntax #3077 ([uplift] fix: use namespace instead of module in immutable types (#3077) #3132):
export module→export namespacein victory-core's vendoredimmutable-types.d.ts. TS 7.0.2 reported 18 TS1540 errors on the published.d.mtswithskipLibCheck: false; now 0. Only those lines change indist/. Ride-along: the website'stypes:checkhad no task dependencies (website/turbo.jsonoverrode the root entry), so on cold runs it raced the package builds; it now waits for^build. - TS 6.0.3 + tsconfig ([uplift] chore: TypeScript 6 with bundler module resolution #3133):
moduleResolution: "bundler",module: "esnext", nobaseUrl(TS 6 deprecatesnode10andbaseUrl; TS 7 removes them). Website: Docusaurus'sbaseUrlunset,@site/*declared locally,strict: false(TS 6's new default istrue). Build output byte-identical;pack:checkand bundle sizes unchanged; no changeset. SSR smoke via packed tarballs (ESM + CJS, React 18 and 19) found two pre-existing problems, now under Phase 4's SSR item.types:check --force12.4s → 13.0s (+1 task).getTicks's inferred return union (number[] | string[]vsstring[] | number[]) flips between identical builds, on TS 5.9 and 6 alike. Declaration diffs have to ignore it, or a source PR annotates the return type.
- Type-check victory-vendor's
tests/([uplift] chore: TypeScript 6 with bundler module resolution #3133):types:checkadded; underbundlerthevictory-vendor/d3-*self-imports resolve throughexports. - TS 7.0 for
types:check(tsgo), [uplift] chore: TypeScript 7 for types:check, check published declara… #3134. TS 7.0 has no stable JS API (onlyunstable/*exports), so declarations stay on TS 6 via rolldown-plugin-dts. Roottypescript@7.0.2; a.pnpmfile.cjshook pins the tools that import the JS API (rolldown-plugin-dts, cosmiconfig, react-docgen-typescript, @storybook/react-docgen-typescript-plugin, fork-ts-checker-webpack-plugin) to 6.0.3. A pnpm override can't do it: a peer still resolves to the root's copy (decision log, 2026-11-01).pack:checknow type-checks each package's published declarations withskipLibCheck: falseon TS 7, somoduleblocks can't come back; 18 pre-existing errors are allowlisted (Phase 4 types item). 0 source errors; the 6 invictory-candlestick.test.tsxwere a moved@ts-expect-error(TS 7 reports per prop); declarations unchanged; tsc-onlytypes:check4.6s → 2.4s. - Later (38.x is fine):
dts.generator: "tsgo"once TS 7.1 is stable; compare declarations byte-for-byte. Remove.pnpmfile.cjsand the 6.0.3 pin with it (check the website and Storybook tools then, or after Phase 3 removes Storybook). Then type-aware oxlint.
- Measured 2026-10-27 on TS 7.0.2 (tsconfig adjusted): 0 source errors, 6 in
- Node: add
.nvmrc(lts/*), used by CI (node-version-file) and documented in CONTRIBUTING. Remove theenginesfields from everypackage.jsonand from the wireit sync generator. (Done early, with dropping the broken wireit GHA cache step so CI passes again.) - CI rewrite:
- Hardening ([uplift] chore: harden GitHub Actions workflows #3130):
permissions: {}defaults with per-job grants,persist-credentials: false, current action majors, hybrid pinning, zizmor in CI (pnpm lint:ghalocally), issue workflows onGITHUB_TOKEN/ubuntu-latest. - Bundle-size report on ESM imports instead of the UMD file (reported, not gated), [uplift] chore: add ESM bundle-size report to CI #3131:
pnpm bundle-size(rolldown, minified + gzip, React external). The CIbundle-sizejob writes the table to the job summary; on PRs it builds the base (HEAD^1of the merge commit) in a worktree and shows the gzip change, with a warning annotation above +1%. Baseline (dist/from [uplift] chore: switch to tsdown. #3108):{ VictoryPie }fromvictory-pieorvictory54.7 kB gz, all ofvictory-core67.5 kB, all ofvictory120.7 kB. Job ~30s, in parallel withtest.
- Hardening ([uplift] chore: harden GitHub Actions workflows #3130):
- Chromatic has failed on every run since Dec 2025 (now: Storybook 8's SWC addon preset fails to load under Node 24). Leave it until Phase 3 replaces it; ignore its status.
- Remove cruft:
the([uplift] chore: remove UMD bundles and webpack; add browser ESM docs #3107),startscript (points at the missingdemo/ts),config/webpack/demo,react-hot-loader([uplift] chore: replace wireit + nps with Turborepo #3102),nps([uplift] chore: switch to tsdown. #3108),cross-envandcpx2([uplift] chore: replace cpx2 and rimraf with Node scripts #3136;rimrafsyncandclean:*are now small async Node scripts; victory-vendor's build droppedrimrafin [uplift] chore: update victory-vendor build tools #3127), andunused root devDeps([uplift] chore: remove unused root dependencies #3113).- [uplift] chore: remove unused root dependencies #3113 removed
clsx,fs-extra+@types/fs-extra,@babel/plugin-transform-export-namespace-from,prop-types+@types/prop-types,react-icons(the website declares its own),react-test-rendererandts-nodefrom the root, and moved victory-vendor's four Babel build deps into its owndevDependencies(it relied on hoisting). Vendor output byte-identical. Swept with knip.concurrentlystays until Phase 3 (see there). - Remove the unused
lodashfromdependenciesinvictory-canvas,victory-histogramandvictory-scatter(none of their source imports it; found by knip). It changes what users install, so it's its own small PR with a patch changeset, kept out of the infra cruft PR ([uplift] fix: VictoryLabel default font and unused lodash deps #3115). - Same PR (small published fixes, [uplift] fix: VictoryLabel default font and unused lodash deps #3115):
VictoryLabel's defaultfontFamily(victory-core/src/victory-label/victory-label.tsx) has an invisible soft hyphen (U+00AD) inside'Seravek', so that font never matches. Found by the jsdom 30 update; the label test's inline snapshot has it too.
- [uplift] chore: remove unused root dependencies #3113 removed
- Update all dev dependencies to latest, including the website's. Do this after the tool swaps above, so we don't upgrade things we're about to delete.
Add a([uplift] chore: add dep:check and update root dev dependencies #3114: npm-check-updates as a pinned root devDependency, root + workspaces, 7-daydep:checkscriptcooldown; parked majors in.ncurc.cjs).- B1, root ([uplift] chore: add dep:check and update root dev dependencies #3114): jsdom 30 (
toCssColortest helper for itsrgb()normalization; pie fixture's invalid#ffffffixed), immutable 5 (Victory's duck-typed support still passes), rimraf 6 (--glob), cpx2 9, jest-dom 7, changelog wrapper 1.2 (wrapper keeps the blank line between entries), and latest in-major React 18.3,@types/react18.3 (one type-only cast inadd-events.tsx), TypeScript 5.9,@types/node24 (.nvmrcpinned to24). Coverage-run tests ~1.5s → ~1.9s (jsdom 30 environment setup); builds unchanged. - B2, website (
pnpm build:docs): Docusaurus 3.10, TS 5.9, React 18.3, tailwindcss 4 (config migration), d3-array/d3-scale/d3-time (docs examples), date-fns 4, typedoc 0.28, docusaurus-search-local, find-cache-dir 6. react-live 5 stays its own item below (parked in.ncurc.cjs).- Everything but Tailwind ([uplift] chore: update website dependencies #3116). Docusaurus 3.10 needed
blog: false(it now writes an empty/blogpage) andonBrokenMarkdownLinksundermarkdown.hooks. Same 87 pages, build ~30s → ~27s. - tailwindcss 4: CSS-first config (
@theme),@tailwindcss/postcss, drop autoprefixer. Modern-browser floor accepted (Safari 16.4+, Chrome 111+, Firefox 128+). Done in [uplift] chore: update website to Tailwind 4 #3119: keeps v3 behavior (no preflight, unlayered utilities, same content sources), class renames from@tailwindcss/upgrade. Visible changes: theme builder headings now use Inter (the nested rule v3 dropped), and the landing page LinkButton shows its green border. Docs build ~27s → ~15s. -
vercel.jsonredirects/→/open-source/victory(307) so Vercel preview links land on the docs ([uplift] chore: redirect Vercel root to /open-source/victory #3117).
- Everything but Tailwind ([uplift] chore: update website dependencies #3116). Docusaurus 3.10 needed
- B3, victory-vendor build tools ([uplift] chore: update victory-vendor build tools #3127): Babel 7.29,
babel-plugin-module-resolver5.0.3;execaandrimrafdropped (Node built-ins; Babel's CLI now runs without a shell, so the build works on Windows). The nested-modules check never ran (typo, and pnpm never nests), so it now checks that every d3 dependency of a vendored package is vendored too.lib-vendor/diff: newer Babel interop helpers and redundant parentheses in 3 d3-array files;pack-snapshot.jsonunchanged. - Website leads from knip, resolved in [uplift] chore: update website dependencies #3116:
postcss-importremoved. It was a no-op (CSS output identical) and only resolved because Docusaurus loads the config from insidenode_modules/.pnpm, where Tailwind 3's copy is reachable.@docusaurus/plugin-google-gtagandplugin-google-tag-managerremoved;preset-classicdepends on both (tags still in the built HTML).@algolia/client-searchandsearch-insightsremoved after all: they're required peers of@algolia/autocomplete-coreundertheme-search-algolia(pulled in bypreset-classic, unused: the site uses local search), and pnpm fills them viaautoInstallPeers: true.@docusaurus/plugin-content-docsstays declared:sidebars.tsimports its types.
- Website follow-ups found in B2:
- Delete the unused
src/plugins/victory-typedoc/(unregistered since Next Generation Documentation #2938/Upgrades ESLint to Version 9 #2958; no doc usesauto_generated). Drops typedoc, find-cache-dir and mdast-util-from-markdown, plus its undeclaredjs-yamlandunist-util-visitimports ([uplift] fix: docs example warnings and website cleanups #3124). - Declare
lodashinwebsite(3 files insrcimport it; it resolves only by hoisting) ([uplift] fix: docs example warnings and website cleanups #3124). - Make the website
tscchecksrc(its tsconfig only includesvendors.d.ts) ([uplift] fix: docs example warnings and website cleanups #3124). 33 errors oncesrcwas included, mostly missing Docusaurus module types; all type-only fixes. The script was namedtypecheck, which nothing ran; it's nowtypes:check, sopnpm checkand CI run it (victory-docs#types:checkwaits only for the packages' builds, not the docs build). The landing page casts its Date accessors until DataGetterPropType doesn't accept Date (x/y accessors returning a Date) #3125 is fixed. - Nested
h1–h6rule under.theme-builder *incustom.cssis dropped from the built CSS (Css Minimizer warnings; also in production). Fixed by Tailwind 4's built-in nesting ([uplift] chore: update website to Tailwind 4 #3119).
- Delete the unused
- B1, root ([uplift] chore: add dep:check and update root dev dependencies #3114): jsdom 30 (
- Browser probe + bugfix pass. Drive headless Chromium over the docs (dev server and production build), Storybook and the ESM browser page, with interactions (hover, drag, wheel, clicks). Collect console errors and warnings, uncaught exceptions and failed requests, then fix them in small PRs split by concern: library fixes (patch changeset), one docs-examples PR, upstream issues noted. The probe script is not committed; Phase 3's browser tests are its long-term home.
- Probe results (2026-10-28): docs dev server and production build (86 pages, 447 charts each), plus a local-
dist/ESM page via an import map (19 charts, one per chart and container type). No uncaught exceptions or failed requests. Storybook skipped: its static build fails with the same SWC preset error (Phase 3). -
delaunay-findESM interop (regression from [uplift] chore: switch to tsdown. #3108, unreleased):victory-voronoi-containerdefault-imports Babel-compiled CJS, so strict ESM (Node, webpack for.mjs) getsmodule.exportsandDelaunay.fromis not a function on mouse move. Fixed, andpack:checknow fails on default imports of CJS withexports.default([uplift] fix: delaunay-find default import in strict ESM #3120). - Chart props leak to the DOM from primitives inside VictoryChart (
standalonewarning) #3118 (pulled forward from Phase 5): primitives strip chart-context props before spreading onto SVG elements ([uplift] fix: keep Victory props off primitives' DOM elements (#3118) #3123). - Docs examples: missing
keyinerror-bar.mdxandbar-horizontal-stacked; d3fc "multiple ticks fall within a discontinuity" warning from the skip-weekends examples (line,area,victory-common-theme-props); theme builder<select value={null}>([uplift] fix: docs example warnings and website cleanups #3124). d3fc's ownscale.ticks()emits the same warning, so the examples tick on the data dates instead. Re-probe of the touched pages: 0 findings. - Found while fixing Chart props leak to the DOM from primitives inside VictoryChart (
standalonewarning) #3118, filed for later: VictoryChart overrides width/height set on its children #3121 (VictoryChartoverrideswidth/heightset on its children; Phase 5 prop passing) and Primitive prop types don't include SVG attributes (x1, d, cx, ...) #3122 (primitive prop types don't include SVG attributes; Phase 4 types). Found in [uplift] fix: docs example warnings and website cleanups #3124: DataGetterPropType doesn't accept Date (x/y accessors returning a Date) #3125 (DataGetterPropTypedoesn't acceptDate; Phase 4 types).
- Probe results (2026-10-28): docs dev server and production build (86 pages, 447 charts each), plus a local-
- Dependency updates without bots (decision log 2026-10-28): no Renovate or Dependabot version-update PRs.
- Dependabot security alerts: turned on after the final merge (see Housekeeping).
- Document the routine in
AGENTS.mdand CONTRIBUTING ([uplift] chore: add dep:check and update root dev dependencies #3114): runpnpm dep:checkbefore each release (and when an alert comes in), and update in one PR.
- Docs site (Docusaurus): build through turbo (
pnpm build:docs, used by Vercel; [uplift] chore: replace wireit + nps with Turborepo #3102). - Docs site: bump
react-liveto v5 ([uplift] chore: website react-live 5 #3137). Docusaurus's live-codeblock still installs its own 4.1.8, unused: our swizzledPlaygroundandReactLiveScopereplace its components.
Phase 3: Stories and visual testing (replacing Storybook + Chromatic)
After 38.0.0 (no published changes; see Road to 38.0.0). Victory renders SVG, so visual regressions matter more here than in react-live. We shouldn't drop Chromatic without a replacement in place.
- Keep the 197 existing story files as-is (Storybook's file format, with seeded deterministic data). Replace
import type { Meta } from "@storybook/react"with a local type.- [uplift] chore: stories workspace package, local story types, package imports #3145:
stories/is a private workspace package,victory-stories(changesetsignore). LocalMeta/StoryObj/Decoratorinstories/utils/types.ts; stories importvictoryandvictory-vendor/d3-*instead of the@/source alias. Mechanical, so it's in.git-blame-ignore-revs.
- [uplift] chore: stories workspace package, local story types, package imports #3145:
- A small Vite app that loads
stories/**/*.stories.tsxand gives a browsable gallery (as in react-live'sstories/).- [uplift] chore: Vite stories gallery, type-check stories #3146:
stories/utils/load-stories.ts(UI-free, for the snapshot tests too) renders each story as Storybook did: global, meta, then story args; meta decorators; Storybook's story ids; each story as its own component, since some call hooks inrender. Gallery instories/app/(sidebar by title, filter,#<story-id>URLs, error boundary).pnpm stories:devrunsturbo watch(package rebuilds reload Vite). Stories are type-checked inpnpm checkand CI (one error fixed). - All 197 render in headless Chromium. Known story problems, left as-is: VictoryBar "Focus With Refs" throws (it reads the DOM
indexattribute, which [uplift] fix: keep Victory props off primitives' DOM elements (#3118) #3123 stopped writing in 38.0.0); VictoryScatter "Data Accessors" renders NaN in two charts (story data has nox); two VictoryLine stories warn about missingkeys.
- [uplift] chore: Vite stories gallery, type-check stories #3146:
- SVG snapshot tests for every story (Vitest browser mode, serialized
<svg>markup). They're cheap and deterministic, with no font or pixel noise.- [uplift] test: SVG snapshot tests for every story (Vitest browser mode) #3147:
stories/tests/renders each story (viaload-stories.ts) in headless Chromium (Vitest browser mode, Playwright) and compares its serialized markup with one file per story instories/tests/__snapshots__/<title>/<name>.html. Runs inpnpm test/checkand in CI'stestandreact-18jobs (pnpm stories:installinstalls Chromium, about 17s in CI; the tests take about 5s locally, 30s for CI's Test step). - Deterministic across machines: text is sized with Victory's
TextSizeapproximation instead of the installed fonts, the browser runs in UTC withen-US, lodashuniqueIdids are renumbered, and attribute numbers are rounded to 2 decimals. Waits until the markup stops changing (portals, mount effects). A test fails on snapshot files whose story is gone. Verified on macOS and Linux, React 18.0.0 included. - Story ids now match Storybook's (
--bar-ratio, not--barratiofrom [uplift] chore: Vite stories gallery, type-check stories #3146). VictoryBar "Focus With Refs" is checked for its known error instead of a snapshot. - 196 files, 11 MB on disk (about 0.5 MB compressed).
- [uplift] test: SVG snapshot tests for every story (Vitest browser mode) #3147:
- Screenshot tests (
toMatchScreenshot) for a curated subset, run in a pinned Playwright container so baselines are stable. - Interaction tests for tooltips, containers and events (covers the area of Container component mouse events / tooltip stopped working after upgrading react 18 -> 19 #3048).
- Test the "Using Victory in the browser" docs examples #3106: test the browser docs examples (esm.sh and jsDelivr) headless in CI, on this phase's browser-testing setup.
- Remove Storybook, Chromatic,
chromatic.config.jsonand the workflow.- Also
concurrently(onlystorybook:devuses it). Storybook's dev server doesn't start under Node 24 (same SWC preset error as Chromatic), so it wasn't swapped forturbo watchin Phase 2. The stories app's dev script should useturbo watchfrom the start.
- Also
Phase 4: Packaging and React (v38)
The breaking items here ship in 38.0.0 (see Road to 38.0.0). The rest (React 19 support, runtime dependency updates, SSR smoke tests, types issues) can ship in 38.x.
-
exportsmaps on everyvictory-*package (Infra: Add types field to package.json #2400), generated by tsdown:"."(import/require, types resolved from the sibling.d.mts/.d.cts) plus"./package.json". Output moves todist/. Keep the legacymain/module/typesfields for older tooling. attw + publint must pass. Done in [uplift] chore: switch to tsdown. #3108 (decision log, 2026-10-27).- Deep imports into
lib/andes/stop resolving. GitHub code search finds only about 15 such imports; the v38 changelog gets a migration note. victory-vendor's existing map stays byte-for-byte compatible. Everyvictory-vendor/d3-*subpath, plustypes/import/default, must resolve exactly as today (recharts depends on it).pnpm pack:checkand vendor'spack-snapshot.jsonguard this ([uplift] chore: add pack:check #3103).- victory-vendor: ESM import of d3-color, d3-format, d3-path, d3-time-format, d3-voronoi fails without hoisting #3104: decided to keep
./d3-*as is (decision log, 2026-11-02). Fallback if someone reports it: point the five ESM shims at the bundled CJS copies (option B). - victory-vendor: es/*.js is ESM in a package without "type" (relies on Node syntax detection) #3105:
es/package.jsonwith{"type": "module"}soes/is real ESM without changingexports(needs a recharts canary smoke test). Not in 38.0.0 (decision log, 2026-11-02); later, as its own change with a canary. - Dropped
pack:check's allowance for publintFILE_INVALID_FORMATin thevictory-*packages; it now applies only to victory-vendor ([uplift] chore: switch to tsdown. #3108). - The changeset's migration note covers deep imports, the ES2022 output and
src/no longer being published.
- Deep imports into
- Ship both ESM and CJS. CJS demand is still real.
- Remove the UMD bundles (
dist/*.js) and the webpack config. Add a docs page on using Victory as ESM in the browser ([uplift] chore: remove UMD bundles and webpack; add browser ESM docs #3107, done early to make the tsdown PR smaller).- The page has esm.sh and jsDelivr examples; Test the "Using Victory in the browser" docs examples #3106 tracks testing them.
- React peer range
>=16.6.0→>=18(unblocksuseIdfor SSR, Hydration error using VictoryLine on NextJS 15. #2941). Develop on React 19 with@types/react19, and add a CI job pinned to React18.0.0(as react-live does).- Peer
>=18and thereact-18CI job (React 18.0.0) landed in [uplift] chore: require React 18 (peer >=18) with a React 18.0.0 CI job #3138; library code can now use React 18 APIs (useId,useSyncExternalStore, ...), not 19-only ones (AGENTS.md). Developing on React 19 (@types/react19, parked in.ncurc.cjs) goes with React 19 support below, in 38.x. - React 19 support: fix the types (Victory typing breaks on upgrade to React 19 #3076) and container events (Container component mouse events / tooltip stopped working after upgrading react 18 -> 19 #3048). A grep finds no
findDOMNode,ReactDOM.render,element.refreads, globalJSXorpropTypesin the source. The expected work is typingcloneElement/child.props(118cloneElementcalls) under@types/react19.
- Peer
- Update runtime dependencies (lodash, react-fast-compare, delaunay-find, and the vendored d3 modules) to latest within their current majors. Major bumps get evaluated one at a time, and anything in
victory-vendoris checked against recharts. - SSR / Node smoke tests: CJS
require, ESMimport, andrenderToString(Tests: Add Node.js / SSR tests. #1965).- Prototype (2026-11-01, TS 6 PR): packed tarballs in an isolated install,
renderToStringof 20 chart setups (every chart type and container) via ESM and CJS, on React 18.3.1 and 19.3.1. 19/20 render on both. - victory-canvas can't render on the server:
CanvasBar/CanvasPointcallnew Path2D()inuseMemoduring render (browser-only API). Move it into the draw effect, or document canvas as client-only. -
VictorySharedEventswith neithereventsnorcontainercrashes (render()clonesprops.container, which is undefined). Not SSR-specific; fall back togroupComponent.
- Prototype (2026-11-01, TS 6 PR): packed tarballs in an isolated install,
- Revisit types issues after the TS upgrade: Type Error in Victory Container 36.9.1 #2815, Typescript 5.2.2 issue in VictoryLineBase #2639.
- Primitive prop types don't include SVG attributes (x1, d, cx, ...) #3122: primitive prop types (
Line,Rect, ...) don't include SVG attributes, so<Line x1={25} />fails to type-check. Widen with the element'sReact.SVGProps(published type change; maybe with Victory typing breaks on upgrade to React 19 #3076). - DataGetterPropType doesn't accept Date (x/y accessors returning a Date) #3125:
DataGetterPropType(x/y/y0) doesn't acceptDate, so Date accessors fail to type-check (they work at runtime). AddDateto the union (published type change); then drop the website'sdateXcast. - Published declarations have type errors that users with
skipLibCheck: falsesee (TS 6 and 7 alike; found by the TS 7 PR'spack:checkstep, allowlisted inALLOWED_DECLARATION_ERRORS). victory-create-container:createContainer's inferred return type indexes the container map with an unnarrowed type parameter (TS2344, 16). victory-errorbar:ErrorBar's props usetypeof ErrorBar.default, which doesn't exist (anyin source; TS2339, 2). Fix (explicit return type; a real props type that stays loose enough for current callers), empty the allowlist, patch changeset. -
getTicks(victory-coreaxis.ts): annotate the return type, so its inferred union stops flipping order between identical builds (see the TS 6 item).
- Primitive prop types don't include SVG attributes (x1, d, cx, ...) #3122: primitive prop types (
Phase 5: Source modernization (later; continues #2863)
Tracked here for visibility; each item gets its own issue.
- Finish function components (Convert class components to function components #2778): add-events, clip-container, transition, and the chart components.
- Remove lodash (Remove dependency on
lodash#2711) - Hooks/context-based events and prop passing (VictoryGroup fails to pass shared events to children #2232, sharedEvents in victoryChart disappears after initial interaction #1900, 'active' variable in style for VictoryBar no longer works as expected #2861, React state change doesn't always trigger a chart redraw #2221, Use forward refs instead of
containerReffor references to the rendered chart #2678, Unable to wrap victory components into customizable components #2342)- Chart props leak to the DOM from primitives inside VictoryChart (
standalonewarning) #3118: chart props (standalone,polar,theme, ...) leak to the DOM from primitives and plain elements insideVictoryChart(React dev warning; seen on the docs intro page). Follow-up to Warning: Receivedfalsefor a non-boolean attributestandalone. #2609. Fixed in Phase 2's bugfix pass ([uplift] fix: keep Victory props off primitives' DOM elements (#3118) #3123; in the primitives, so custom children ofVictoryChartstill receive the props). - VictoryChart overrides width/height set on its children #3121:
VictoryChart(andVictoryGroup/VictoryStack) overridewidth/heightset on children; a<Rect width={10}>inside a chart loses its width (or gets the chart's). Behavior change for Victory children too, so decide with the prop-passing work.
- Chart props leak to the DOM from primitives inside VictoryChart (
- Animation (Refactor VictoryTransition and animations #2104, Components flashing and re-rendering when
animateis set to true #1997)- VictoryBar onExit transition renders invalid path (y0 undefined) in the Animations guide #3142:
VictoryBarexit transitions render invalid paths (d="M x, undefined ...", console errors on the Animations guide'sonExitexample). Exiting data has_y0/_y1null, andgetBarPositiononly defaultsundefined. Pre-existing (37.4.1); likely also Expected number in path attribute error #2780.
- VictoryBar onExit transition renders invalid path (y0 undefined) in the Animations guide #3142:
- Turn on oxlint's
correctnesscategory (off for ESLint parity in [uplift] chore: replace ESLint + Prettier with oxlint + oxfmt #3110) and triage its 52 warnings: 35 Vitest test-hygiene (vi.fn()without type parameters, tests withoutexpect, ...) and 12 React (react/refs,react/set-state-in-effect,react/no-did-mount-set-state), which may point to real bugs. - New chart types are out of scope for the uplift. Wishlist carried over from 2024 Roadmap #2863: violin plot (Violin plots #1611), spider graph (Spider Graph #1610), Gantt (gantt chart #2769), heat map (Component request: Heat map #262), and (no issues yet) Mercator projection, hexbin, bubble map, tree map, cash/funds flow.
Housekeeping
- Triage the about 50
Issue: Acceptedissues untouched since early 2024. Decide whetherIssue: Acceptedshould keep exempting issues from the stale bot. - Docs landing page's version chart uses a stale hard-coded list (Docs: landing page version chart uses a stale hard-coded version list #3100, low priority).
- Delete retired branches (
chore/2026-uplift,changeset-release/mainwhen idle) and the ~30 stalestaging-17xxenvironments. - End-of-uplift review (discuss before the final merge), [uplift] chore: Node 26, pnpm minimumReleaseAge 7 days, dev deps #3139:
- Node 26:
.nvmrcis pinned to24(and@types/node@24). Move to 26 if it's the active LTS by then; update.nvmrc,@types/nodeand the.ncurc.cjsentry together.- Decided (2026-11-02): yes, Node 26 (LTS). In [uplift] chore: Node 26, pnpm minimumReleaseAge 7 days, dev deps #3139:
.nvmrc26,@types/node^26,.ncurc.cjsentry dropped. Vercel doesn't read.nvmrc: a maintainer sets the project's Node.js Version to 26.x.
- Decided (2026-11-02): yes, Node 26 (LTS). In [uplift] chore: Node 26, pnpm minimumReleaseAge 7 days, dev deps #3139:
- pnpm
minimumReleaseAge: not set. pnpm 12 also checks the existing lockfile against it, and 7 days rejected 47 recent entries (e.g. oxfmt 0.75.0, which we pin). Options: leave unset (dep:checkhas a 7-daycooldown), 1 day, or 7 days plusminimumReleaseAgeExcludefor deliberate pins.- Decided (2026-11-02): 7 days (
10080), in [uplift] chore: Node 26, pnpm minimumReleaseAge 7 days, dev deps #3139. By then only 3 transitive lockfile entries were too new;pnpm updatere-resolved them to older versions, so no exclusions.minimumReleaseAgeExcludeis for urgent security fixes only (CONTRIBUTING "Updating dependencies").
- Decided (2026-11-02): 7 days (
- Node 26:
- After the final merge to
main:-
Close/redirect onPress event not firing on Android devices (victory-native) #3018 (victory-native)(closed 2026-11-03). Re-check VictoryTooltip overlap in VictoryGroup #2201 on web.- Checked on 38.0.0 (2026-11-03, headless Chromium): a
VictoryGroupof threeVictoryBars withVictoryTooltip; hovering the first (bottom-layer) series draws its tooltip above the later series, because web tooltips render inVictoryPortal's overlay SVG. VictoryTooltip overlap in VictoryGroup #2201 is victory-native (iOS) only.
- Checked on 38.0.0 (2026-11-03, headless Chromium): a
- Delete
chore/2026-uplift-02(remote and local) once 38.0.0 is stable in real projects. Its history is inmainthrough the [uplift] 38.0.0: 2026 infra uplift (Phase 2 and breaking changes) #3141 merge commit. - Close 2024 Roadmap #2863 (2024 Roadmap): closed 2026-11-03; its open items are tracked here (Phase 5, chart wishlist above).
- Maintainer: turn on GitHub's Dependabot security alerts (Settings → Code security). Alerts only; no security-update PRs. Waits until every dependency change in the uplift has landed on
main(Storybook/Chromatic removal, runtime dependency updates, react-live 5, ...), so it starts from a clean baseline instead of a backlog of stale alerts. Some of these now land after 38.0.0, so this may come after the final merge.
-
- Vitest's shared cache (
node_modules/.vitest-cache, used by every package's run) can race on the firstpnpm checkafter a lockfile change: one run clears it while another reads it (ENOTEMPTY, seen once 2026-11-01; a rerun passes). CI starts from a fresh install, so it's local only so far. Give each package its own cache dir if it recurs. - Bring in the open community PRs (fix(tooltip): pass lineHeight from style to label component (#2908) #3088, fix(victory-bar): support object return from cornerRadius callback #3086, Fixes clipping/loss of interactivity bug in VictoryCursorContainer (#3063) #3064, fillData sorted data assumption fixed #3062, fix: fixed the logic for scroll event on zoom-voronoi container #3060, Victory 2298 Tooltip Bug Fix #3027) during or after the uplift, as described above. Retarget them to
main(the integration branch is merged).
Out of scope (separate tracks)
Tree-shaking through theResolved by the tsdown build ([uplift] chore: switch to tsdown. #3108): since then both imports bundle to the same 54.7 kB gz ([uplift] chore: add ESM bundle-size report to CI #3131; decision log, 2026-10-31).victorybarrel. In early measurements,{ VictoryPie } from "victory"comes out at about 131k gz vs 62k fromvictory-pie(esbuild, 37.3.6). This gets its own investigation and issue.- Package consolidation (one
victorypackage with subpath exports): revisit after v38.
Decision log
| Date | Decision | Why |
|---|---|---|
| 2026-10-24 | Turborepo replaces wireit. Runner-up: Vite+ vp run |
Turbo is mature, MIT, actively released, plain orchestration over scripts, and has a free GHA cache path. wireit works but depends on one maintainer. Vite+ fits our toolchain but is barely past 1.0, and remote/CI caching is still experimental; revisit in about 6 months. Nx and moon are heavier than a small OSS team needs. tsdown's workspace mode has no ordering or caching. |
| 2026-10-24 | Remove victory-native from this repo, with no npm deprecation |
It's published from victory-native-xl (latest = 43.x). Legacy has gone almost 2 years without a publish, and nobody has asked for more. |
| 2026-10-24 | Stay on pnpm and upgrade to the current major | It doesn't block OIDC: changesets v3 packs and publishes with pnpm, and pnpm 11+ handles OIDC itself. pnpm still gives us --filter graph selection, catalogs, strict dependency resolution (catches undeclared deps), pnpm patch, and supply-chain defaults. |
| 2026-10-24 | Keep all packages, including victory-vendor, on one shared version |
A lockstep bump of an unchanged vendor is harmless, and it keeps releases simple. |
| 2026-10-24 | All work lands via PRs into the chore/2026-uplift integration branch, then one merge-commit PR to main (branch superseded 2026-10-26 by chore/2026-uplift-02) |
Keeps main releasable and community fixes flowing while the uplift is in progress, and keeps per-PR history and blame-ignore SHAs. |
| 2026-10-24 | CI on main is unreliable. A canary from the integration branch proves the pipeline first. |
|
| 2026-10-24 | Open PRs don't block the uplift | They're retargeted to the integration branch (or merged via main) during or after the uplift. |
| 2026-10-24 | .nvmrc sets the dev/CI Node version. No engines fields in any package.json |
engines restrictions mostly cause install friction for users, and .nvmrc covers contributors and CI. |
| 2026-10-24 | exports maps (root only) + dist/ output in v38 |
Simplest with tsdown (generated, checked by attw/publint), and it makes Node ESM/CJS resolution unambiguous. Deep imports are rare (~15 found on GitHub), and the layout change would break them anyway. victory-vendor's subpath map is unchanged. |
| 2026-10-24 | Remove the UMD bundles in v38 | Use ESM in the browser instead (docs example). |
| 2026-10-24 | Ship ESM + CJS | victory-vendor is our most-downloaded package and is consumed as CJS. |
| 2026-10-24 | React peer >=18, tested on 18.0.0 and 19 |
16 and 17 together are under 3% of React downloads. Supporting 18 costs mostly types work; react-live does the same. |
| 2026-10-24 | Tree-shaking and package consolidation are separate tracks | Package layout shouldn't affect tree-shaking. Analyze it on its own. |
| 2026-10-25 | Interim 37.4.0 from the new pipeline: finish Phase 1, merge the integration branch into main (merge commit), release, continue. 38.0.0 is still the first major |
Tests OIDC, changesets v3 and the new release.yml on a low-stakes minor instead of the major, and ships fixes stuck since Jan 2025. Fixing main's old workflow (new CHANGESETS_TOKEN and NPM_TOKEN, setup action) would be throwaway work. The integration branch has no source changes yet, so this is the window. |
| 2026-10-25 | Ship #3065 as a one-off victory-native@37.3.7 on legacy, published by hand; don't restore native for 37.4.0 |
victory-native latest is published from victory-native-xl via npm trusted publishing, so this repo's release.yml shouldn't publish it. Without the changesets patch, a pipeline publish would also default to latest and replace XL 43.x. Legacy 37.x still gets ~20k downloads a week. |
| 2026-10-26 | Publishing environment is npm-publish, not Production |
GitHub environment names are case-insensitive, and production is Vercel's docs deployment environment. A distinct name keeps the npm publish gate separate. |
| 2026-10-26 | Replace scripts/release.ts with gh release create --generate-notes |
release.ts never worked: it read CHANGESETS_GITHUB_TOKEN while the workflow set GITHUB_TOKEN, read the always-empty victory changelog, and swallowed errors. Past releases were created by hand with GitHub's generated notes, so this matches them. |
| 2026-10-26 | Ship #3092 (victory-vendor licenses) as its own 37.4.1 | Isolates a change to our highest-stakes package in its own release, and exercises the new pipeline a second time. |
| 2026-10-26 | Phase 1 reached main as a squash (#3098, accidental). Restart on chore/2026-uplift-02 cut from main instead of repairing ancestry |
main's tree is identical to the old integration branch, and a new branch from main needs no history repair. Per-PR history lives in the merged PRs. No .git-blame-ignore-revs SHAs existed yet. |
| 2026-10-26 | Keep scripts/npm-trust.sh |
Needed to add packages or re-create trust; trust is bound to release.yml's filename and the npm-publish environment. |
| 2026-10-26 | Leave Chromatic broken until Phase 3 | It's Storybook tooling under Node 24, not library code, and Phase 3 replaces it. |
| 2026-10-26 | Turborepo details (#3102): keep task names; Turbo cache in CI via plain actions/cache, no remote cache; --affected on PRs; website is outside the root build (built by build:docs, uncached); telemetry off |
Renaming tasks would churn docs and habits for no gain. actions/cache needs no account or token. Vercel builds the docs from scratch and Docusaurus has its own cache. |
| 2026-10-26 | No changeset for scripts/devDependencies-only manifest changes |
Consumers don't see them, and a changeset would add an empty entry to the 38.0.0 changelog. |
| 2026-10-27 | Root package.json must not depend on workspace packages |
Turborepo puts the root's workspace dependencies (transitively) into the global hash, so any edit to them invalidates every task. Root tooling imports workspace packages by relative path instead. |
| 2026-10-27 | Published-output check (pack:check): every package gets publint, attw and isolated entrypoint loading; only victory-vendor gets a committed snapshot |
Snapshots of all 29 packages would churn on every new source file. Vendor is the package whose layout must not change (recharts). Known problems are recorded in the snapshot rather than failing, so changes in either direction show up. |
| 2026-10-26 | oxfmt replaces Prettier, switched together with oxlint, with formatting taken out of lint; one churn PR for both (pending an oxfmt evaluation) | It's fast, aims for Prettier-compatible output, and comes from the same toolchain as oxlint. Doing both switches together means source takes the formatting and lint churn once. |
| 2026-10-27 | tsdown PR: dist/ + exports + ESM .mjs/CJS .cjs with .d.mts/.d.cts, one output file per source file, es2022, no sourcemaps or declaration maps, src/ no longer published. Declarations via tsc (TS 5.x); TS 7/tsgo later |
One breaking layout change instead of two. Per-file output keeps bundlers' sideEffects pruning and stays readable without maps. TS 5.7 already has 0 errors, so declaration errors can fail the build now. tsdown's tsgo generator is experimental and needs TS 7.1 (beta), and TS 7 needs tsconfig and #3077 changes that touch source. |
| 2026-10-27 | Vitest per package via turbo; task renamed jest → test; one shared test/vitest.config.mts; globals: true with jest.* → vi.* |
Keeps per-package caching and --affected. The tool changed, so keeping the old task name would mislead. Globals keep the test-file diff to vi.* calls. |
| 2026-10-27 | jsdom stays on 20.x (what Jest used) for the Vitest switch | jsdom 30 normalizes inline CSS colors and breaks 3 assertions; that upgrade goes with the dev-dependency update so the runner swap stays a pure swap. |
| 2026-10-28 | oxfmt (not Prettier) for every file type, docs included, at printWidth: 80 |
The evaluation found it handles all our file types and stays close to current Prettier. One formatter and one reformat; nobody wanted the 40-column MDX override. |
| 2026-10-28 | oxlint only, no ESLint for leftover rules; three nursery rules (no-undef for JS, react/require-render-return, promise/no-return-in-finally) for full parity; correctness category off for now |
Every ESLint rule we used has an oxlint equivalent, so a hybrid setup adds nothing. oxlint is pinned, so nursery changes only arrive with a deliberate upgrade. correctness adds new findings that need source changes (Phase 5). |
| 2026-10-28 | format and lint run from the root, outside turbo |
Each checks the whole repo in under a second; caching would only add input globs that can go stale. |
| 2026-10-28 | Three squash-merged PRs (config, format churn, lint churn); each churn SHA goes into .git-blame-ignore-revs in the next PR |
Keeps formatter output separate from hand edits for review. A squash merge creates the SHA, so a PR can't list its own. |
| 2026-10-28 | Per-file rule options become line-level eslint-disable comments, not per-file config overrides; lint fails on unused directives |
oxlint can't set options in comments. Disables stay next to the code they cover, and stale ones fail lint instead of piling up. |
| 2026-10-28 | No Renovate or Dependabot version-update PRs. Dependabot security alerts only, plus a manual pnpm dep:check (npm-check-updates) before releases |
Bot PRs are high-volume, noisy and verbose; merging them is a chore, and auto-merges can land on the default branch unnoticed. For a library, only dependencies reach users, and the core packages have few. Alerts cover known vulnerabilities. |
| 2026-10-28 | Pin .nvmrc to 24 (was lts/*); revisit Node 26 at the end of the uplift |
@types/node has to match a Node major, and lts/* would silently move CI to 26 when it becomes LTS. No CI change today (LTS is 24). |
| 2026-10-28 | Parked majors live in .ncurc.cjs (minor-only, or rejected for tools Phase 3 removes); each needs a tracking item |
dep:check then lists only actionable updates, and nothing is parked without a plan. |
| 2026-10-28 | Small type-only fixes needed by a tooling or dependency bump can ride along in that PR | Splitting a one-line, no-runtime-change fix into its own PR costs more than it saves. Larger churn still gets its own PR. |
| 2026-10-28 | #3118 is fixed in the primitives (strip chart-context props before spreading onto SVG), not in VictoryChart's child cloning |
Custom children of VictoryChart read the injected props (scale, theme, ...); not passing them would break users. |
| 2026-10-29 | victory-vendor's build stays on Babel 7 during the uplift (#3127). A single-tool (Rolldown) build is #3126, after the uplift, with recharts maintainers consulted first. Babel 8 stays parked | Rolldown's CJS output differs from Babel's, so a port can't be checked by byte diff, and the CJS output is what recharts' require() users get. Updating the Babel tools in place keeps the output diff reviewable. |
| 2026-10-30 | Hybrid action pinning: version tags for actions/* and github/*, commit SHA + # vX.Y.Z comment for third-party actions. Enforced by zizmor (.github/zizmor.yml); refreshed with pinact (.github/pinact.yml, 7-day minimum age) in the dep:check routine |
Moved tags are the main supply-chain risk (tj-actions, Mar 2025), and third-party actions carry most of it, notably changesets/action next to id-token: write. Without update bots, SHA-pinning GitHub's own actions adds upkeep for little gain. zizmor's online impostor-commit audit covers the fork-SHA caveat. |
| 2026-10-30 | zizmor audits workflows in CI (lint job, pinned version, online audits on, no code-scanning upload). Locally pnpm lint:gha is optional and skips if zizmor isn't installed |
It caught missing permissions, persisted credentials, and same-repo action syntax. It isn't a Node tool, so CI is the gate. |
| 2026-10-31 | Bundle-size report (#3131): scripts/bundle-size.mjs with rolldown (root devDep, same range as tsdown's), ESM from dist/, minified + gzip, React external. Scenarios: { VictoryPie } from victory-pie and from victory, all of victory-core, all of victory. Reported, not gated; a warning annotation when a scenario's gzip grows more than 1% |
rolldown is already installed and is Vite 8's bundler; esbuild gave the same picture (within ~5%). Four scenarios cover the per-package vs barrel question and whole-library growth; few and stable, since renaming one loses its comparison. Annotations need no pull-requests: write and work on fork PRs, unlike a PR comment. |
| 2026-10-31 | The bundle-size base is built in the same job: a worktree at the PR merge commit's first parent (HEAD^1), measured with the PR's script |
Exact against what the PR is merged onto, same scenarios and bundler on both sides, nothing to bootstrap. Base builds hit the shared turbo cache: the job takes ~30s, in parallel with test (~70s). A cached report from base pushes would lag the merge base and need bootstrapping. |
| 2026-10-31 | Tree-shaking through the victory barrel is resolved by the tsdown build (#3108), not a separate track |
Measured in #3131: { VictoryPie } from victory is byte-identical to importing it from victory-pie (54.7 kB gz, rolldown; 58.6 kB, esbuild 0.18). Per-file output plus sideEffects: false lets bundlers prune the barrel. CI now watches it. |
| 2026-10-31 | TS 7 work stays in this issue as four small PRs: #3077 → TS 6 + tsconfig → TS 7 types:check → tsgo declarations (TS 7.1). Declarations from TS 6 (rolldown-plugin-dts' tsc generator), types:check on TS 7.0 |
TS 7.0 ships only the CLI plus unstable/* API exports, so rolldown-plugin-dts can't emit declarations with it; its tsgo generator needs 7.1, and we don't ship on a beta. TS 6 deprecates what TS 7 removes, so it forces the tsconfig cleanup first. |
| 2026-10-31 | Outside contributors' PRs that can't be retargeted are re-applied on the integration branch with them as commit author, and credited with Co-authored-by in the squash message and in the changeset text |
Keeps credit through squash merges; scripts/changelog.js doesn't credit authors. |
| 2026-11-01 | moduleResolution: "bundler" + module: "esnext", not nodenext (#3133) |
Both had 0 errors. Source is only run through tsdown, Vitest and bundlers, never by Node directly. Node is covered by pack:check (isolated require()/import()) and the SSR smoke; build output was byte-identical. bundler also resolves victory-vendor's self-imports for its tests/ type check. |
| 2026-11-01 | Version ranges: ^ by default; exact only for tools whose upgrade needs its own PR (oxfmt, oxlint, vendor Babel); ~ only to match another package's range; sibling victory-* dependencies exact (#3133; AGENTS.md, CONTRIBUTING) |
One rule instead of per-package habits (the website's ~ came from the Docusaurus template). |
| 2026-11-01 | Release 38.0.0 once Phase 2 and the breaking items are done (Road to 38.0.0), not after Phases 3–4. Phase 3, the non-breaking rest of Phase 4, and Phase 5 ship as 38.x. Supersedes the "Phases 2–4 done" release gate | The integration branch is already breaking, so it can't ship as 37.x, and fixes have waited since 37.4.1. A smaller major is easier to debug if packaging breaks someone. Phase 3 is test tooling, and most of Phase 4 isn't breaking. Breaking items left after 38.0.0 wait for 39 or ship behind an opt-in. |
| 2026-11-01 | TS 7 next to TS 6: root typescript@7.0.2, with a .pnpmfile.cjs readPackage hook that turns typescript from a peer into a 6.0.3 dependency for the tools that import its JS API. Not a pnpm override, not root TS 6 plus a TS 7 alias |
Overrides (and packageExtensions) only change a peer's range; the peer still resolves to the root's TS 7. An alias would leave tsc as TS 6 (both packages ship a tsc bin), so every types:check script and editors would need a path or wrapper to reach TS 7. The hook is one small file, deleted with the tsgo generator (TS 7.1). |
| 2026-11-01 | Published declarations are type-checked with skipLibCheck: false in pack:check, with an allowlist (<file>:<code> with an error count; any other error, or a count that no longer matches, fails) for 18 pre-existing errors, fixed later in a source PR |
skipLibCheck skips our emitted .d.ts too, so nothing stopped module blocks (#3077) coming back. pack:check already checks published output and holds the other allowlists. Fixing the 18 changes published types, so it gets its own PR and changeset. |
| 2026-11-02 | victory-vendor stays in the fixed group and ships as 38.0.0; recharts (victory-vendor@^37) stays on 37.4.x until it opts in (^38, or ^37 || ^38) |
Nothing from the integration branch reaches recharts users until recharts chooses to move. Costs are small: vendor re-exports d3, which rarely changes, so 37.x backports should be rare, and apps on both victory 38 and recharts get a second vendor copy (mostly the CJS lib-vendor/; ESM resolves through to shared d3 packages). Keeps the existing fixed row as is. |
| 2026-11-02 | #3104: keep victory-vendor's ./d3-* subpaths (no narrowing) |
Narrowing is breaking in our highest-stakes package, for undocumented subpaths nobody has reported. If someone does, point the five ESM shims at the bundled CJS copies (option B: no file-list or exports change). |
| 2026-11-02 | #3105: es/package.json ("type": "module") is not in 38.0.0 |
Nothing is broken on supported Node or in bundlers. Keep 38.0.0's packaging changes small; do it later as its own change with a recharts canary. |
| 2026-11-02 | Node 26 for development and CI: .nvmrc 26, @types/node ^26 (supersedes the 2026-10-28 pin to 24) |
Node 26 is the active LTS, and 38.0.0 is the natural point to move. CI follows .nvmrc. Still no engines fields, so users aren't affected. |
| 2026-11-02 | pnpm minimumReleaseAge: 10080 (7 days), strict, with minimumReleaseAgeExclude only for urgent security fixes (each with a comment and a removal date) |
Extends dep:check's 7-day cooldown from direct to transitive dependencies, and pnpm 12 applies it to the existing lockfile too. pnpm 12's default (1 day) is non-strict: it installs a newer version when nothing qualifies. A version the lockfile already has that's too new can be re-resolved with pnpm update instead of excluded. |
| 2026-11-03 | Files outside the packages that tasks read (test/**, scripts/pack-check.mjs) are globalDependencies, not $TURBO_ROOT$ task inputs; the shared test helpers import no workspace package's source (#3141). Amends 2026-10-26: --affected stays on PRs |
--affected only sees package files and global dependencies, so PRs that edited only test/ or pack-check.mjs ran no tests, type checks or pack:check, and an edit to victory-voronoi's source skipped the other packages whose tests used it. Global costs a full rebuild when those rare files change. |
| 2026-11-03 | After 38.0.0, uplift work lands as PRs straight to main; no new integration branch. CI drops its chore/2026-uplift* triggers, and chore/2026-uplift-02 is deleted once 38.0.0 is stable |
The breaking changes are out, so the remaining phases ship as 38.x from main, where CI and releases already run. An integration branch would only delay releases and add merges. |
| 2026-11-04 | stories/ is a private workspace package (victory-stories), not root config; stories import the packages by name (no @/ alias) |
It declares its own dependencies and gets per-package turbo tasks (types:check, later test), and stories render the built packages the way users import them. |
| 2026-11-04 | Phase 3 order: gallery, then SVG snapshot tests, then remove Storybook + Chromatic, then screenshots, interactions, #3106. Storybook stays broken (untouched) until removed | Snapshots replace Chromatic's coverage before it goes; removing Storybook ahead of screenshots gets the red check off main sooner. Fixing Storybook on Node 24 would be wasted work. |
| 2026-11-04 | Story snapshot tests size text with Victory's TextSize approximation (the hook victory-core's own tests use), and the browser runs in UTC, en-US |
Victory measures text in the page, so snapshots depended on installed fonts and on the OS: with one bundled font and no hinting, widths matched but heights didn't (Blink rounds font ascent and descent on Linux, not on macOS). The approximation is the same everywhere and needs no container locally. The in-page measurement path is left to the screenshot tests, in a pinned container. |
| 2026-11-04 | Story snapshots round attribute numbers to 2 decimals and are one file per story | Under a pixel, keeps files small, and keeps d3 versions that round paths (d3-path 3.1, Phase 4) from changing every snapshot. One file per story is easier to review than one big .snap. |
Activity
[uplift] ci: changesets v3 + OIDC release pipeline with canary (#3096)
2026 Uplift: phase 1 (CI, pnpm 12, OIDC release pipeline) (#3098)
[uplift] chore: add pack:check (#3103)
[uplift] chore: remove UMD bundles and webpack; add browser ESM docs (#…
[uplift] chore: switch to tsdown. (#3108)
ryan-roemer commentedon Oct 28, 2026
Vitest landed on
chore/2026-uplift-02as a direct push in dadb6af (no PR, by accident). This comment stands in for the PR description.testscript uses the sharedtest/vitest.config.mts(jsdom, globals). The 30 Jest configs are gone.jestis renamed totest(pnpm test), in CI, AGENTS.md and CONTRIBUTING too.jest.*→vi.*(20 files) andxit→it.skip(1 file). No snapshot changes; 619 pass, 2 skipped.@vitest/eslint-pluginreplaceseslint-plugin-jest..babelrc.jsand its presets. victory-vendor's Babel build is unchanged, andpack:checkpasses.check~30s → ~18s, warm 66ms.[uplift] chore: report Vitest coverage in CI (#3109)
[uplift] chore: replace ESLint + Prettier with oxlint + oxfmt (#3110)
[uplift] chore: format with oxfmt (#3111)
[uplift] chore: fix oxlint errors (#3112)
[uplift] chore: remove unused root dependencies (#3113)
[uplift] chore: add dep:check and update root dev dependencies (#3114)
[uplift] fix: VictoryLabel default font and unused lodash deps (#3115)
[uplift] chore: update victory-vendor build tools (#3127)
[uplift] chore: add ESM bundle-size report to CI (#3131)
[uplift] chore: TypeScript 7 for types:check, check published declara… (
[uplift] 38.0.0: 2026 infra uplift (Phase 2 and breaking changes) (#3141
[uplift] docs: browser examples at 38, Approve and run for Version Pa…
[uplift] chore: stories workspace package, local story types, package…
[uplift] test: SVG snapshot tests for every story (Vitest browser mod…