Skip to content

2026 Uplift #3093

Open
Open

Description

@ryan-roemer

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

By the numbers (Oct 2026)

  • victory-vendor: about 215M downloads a month, about 99% via recharts, which depends on victory-vendor@^37.0.2 and uses d3-scale and d3-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

  1. 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.
  2. Keep tasks as plain package.json scripts. A task runner orchestrates and caches them; it doesn't own them.
  3. Every phase is releasable. Small PRs, and main stays green.
  4. 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 from main after 37.4.1, pushed to origin. It replaces chore/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 only scripts or devDependencies don't need one. Squash-merging these PRs into the integration branch is fine.
  • main keeps getting fixes. Merge main into the integration branch regularly (merge, not rebase, since it's shared) so the final merge stays small.
  • CI: ci.yml runs on main and chore/2026-uplift* (push and pull_request). The release workflow publishes only from main, 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-revs valid. 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: main merged 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

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.

  1. Finish Phase 2: TS 7 (tsgo) for types:check ([uplift] chore: TypeScript 7 for types:check, check published declara… #3134), react-live v5 ([uplift] chore: website react-live 5 #3137), the last cruft (cpx2, rimraf) ([uplift] chore: replace cpx2 and rimraf with Node scripts #3136). tsdown's dts.generator: "tsgo" can follow in 38.x (needs TS 7.1 stable).
  2. Breaking items from Phase 4:
  3. victory-vendor's version: decided (2026-11-02): it stays in the fixed group and ships as 38.0.0. recharts (victory-vendor@^37) stays on 37.4.x until it opts in.
  4. End-of-uplift review (Housekeeping), pnpm dep:check, merge main in, 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); main had 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-compact is replaced by the official @changesets/changelog-github (disableThanks, like react-live). scripts/changelog.js now only drops "Updated dependencies" lines (with the fixed group, 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 #123 references are linked.
  5. Canary to next from the integration branch: 38.0.0-canary-20261103184440 (all 29, from bce12bb4b, SLSA v1 provenance), checked against 37.4.1 (2026-11-03). The recharts smoke test waits until recharts opts in to victory-vendor@38 (see step 3).
  6. 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 a turbo.json fix: the shared test helpers now import d3-voronoi directly instead of victory-voronoi's source, and test/** and scripts/pack-check.mjs moved to globalDependencies (decision log, 2026-11-03).
    • Version Packages Version Packages #3143 → 38.0.0 (all 29 on latest, SLSA v1 provenance, v38.0.0 release). 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 the victory-chart: "*" devDependency, and a union member order in four declaration files (number[] | string[], same type). Every sibling dependencies/peerDependencies pin is 38.0.0.
    • Browser docs at @38 ([uplift] docs: browser examples at 38, Approve and run for Version Packages #3144) (esm.sh victory@38, jsDelivr victory@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 and release.yml describe "Approve and run" for the Version Packages PR.

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)

Phase 1: Scope and release pipeline (OIDC)

Remove victory-native

Tooling for releases

  • packageManager → pnpm@12.14.0 (chore: upgrade pnpm 9.13 -> 12.14 #3095). pnpm settings moved from .npmrc / package.json "pnpm" to pnpm-workspace.yaml (pnpm 11+ reads them only there), plus an allowBuilds allowlist. pnpm/action-setup v6. Lockfile resolution and packed output unchanged.
  • @changesets/cli v3 ([uplift] ci: changesets v3 + OIDC release pipeline with canary #3096). Dropped the @changesets/cli@2.24.1 patch (it only made changeset publish honor publishConfig.tag for victory-native). The existing changelog wrapper (scripts/changelog.js over @svitejs/changesets-changelog-github-compact) works with v3 unchanged.
  • Keep the fixed: [["victory*"]] group. All packages, including victory-vendor, version together.

Release workflow (.github/workflows/release.yml)

  • Rebuilt on changesets/action v2 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 get id-token: write.
    • package-manager-cache: false in all release jobs (avoids cache poisoning).
    • publish and canary run in the npm-publish environment (branch rules: main + the integration branch; required reviewers optional, currently none). Not production: 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).
  • Tags and release notes: per-package tags and releases are off (create-github-releases: false, push-git-tags: false). A follow-up step runs gh release create v{version} --generate-notes, which creates the one tag and the aggregate release. scripts/release.ts, its deps, and nps git:tag were removed (see the decision log).
  • Canary: workflow_dispatch in the same file (npm trust is bound to one workflow file). gh workflow run release.yml --ref <branch> publishes X.Y.Z-canary-<datetime> to next (snapshot.useCalculatedVersion).
  • Version PR token: CI runs on PRs opened or updated with GITHUB_TOKEN are 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-publish environment, deployment branches main + chore/2026-uplift-02 (was chore/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: renaming release.yml or 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_TOKEN and CHANGESETS_TOKEN secrets.

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:

  1. Type-error fixes once TS errors fail the build
  2. The TS upgrade and tsconfig changes
  3. 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.json plus package-level ones for victory-vendor and website. Task names kept (build, types:check, jest, lint, ...); ordering comes from ^task workspace deps, so no generator.
    • CI cache via actions/cache (no Vercel account needed); test restores the build job's cache, lint runs in parallel. --affected on PRs. Telemetry off in all workflows.
    • ESLint and Prettier run with --cache; lint no 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 check 4.3s → 0.6s; cold check ~40s → ~45s (Jest worker pools per package; the Vitest PR should fix it).
    • Fixed in [uplift] chore: add pack:check #3103: the root package.json depended on victory-vendor/victory-voronoi (for test/ helpers), which put them and their dependencies into Turborepo's global hash. Any edit to victory-core, victory-voronoi or victory-vendor invalidated every task.
  • tsdown per package: ESM + CJS + .d.ts, with publint and @arethetypeswrong/core on every build ([uplift] chore: switch to tsdown. #3108). Remove the Babel configs.
    • Also did Phase 4's dist/ + exports move (see the decision log, 2026-10-27): ESM .mjs + .d.mts, CJS .cjs + .d.cts, one output file per source file, es2022 (tsconfig target now matches), no sourcemaps or declaration maps, src/ no longer published. One shared tsdown.config.mts; one turbo build task.
    • publint and attw run in pnpm pack:check (CI), not in every build.
    • .babelrc.build.js and cross-env removed. .babelrc.js stays for babel-jest until Vitest; @babel/cli stays for victory-vendor's build.
    • Only source change: ComponentEvent type exported from victory-core, so dependents' declarations reference victory-core instead of inlining its internal types.
    • Timings (local): cold pnpm build 12.1s (85 tasks) → 8.6s (29); cold check ~45s → ~30s; warm check 0.6s → 68ms. Exported names match 37.4.1 for all 28 packages (require() and import()).
  • Type errors fail the build. Fix whatever surfaces.
    • TS 5.7.2 had 0 errors in all 27 victory-* packages (build and types:check configs, 2026-10-27). Since [uplift] chore: switch to tsdown. #3108, declaration errors fail the build (the || echo warning is gone).
  • victory-vendor: port scripts/build.js (d3 ESM → CJS vendoring) to tsdown/Rolldown, or keep it as-is behind a turbo task. Must still emit LICENSE.txt.
  • 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 renamed jest → test (pnpm test).
    • Tests: jest.* → vi.*, one xit → 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-plugin replaces eslint-plugin-jest. The root .babelrc.js and its presets are gone.
    • Timings (local): tests only ~10.5s → ~4.3s; cold check ~30s → ~18s; warm check 66ms.
    • victory-vendor's tests/*.test.ts were never run by Jest. Since [uplift] chore: report Vitest coverage in CI #3109 their runtime checks (subpath exports are functions) run in pnpm test. Their type checks (type re-exports, @ts-expect-error on a bad name) still aren't type-checked: tsc can't resolve the victory-vendor/d3-* self-imports under moduleResolution: node (TS 7 item). pack:check covers most of this more strictly. Type-checked since [uplift] chore: TypeScript 6 with bundler module resolution #3133 (bundler resolution).
    • Coverage, reported but not gated (Infra: Add coverage reporting to CI #2348), [uplift] chore: report Vitest coverage in CI #3109: pnpm test:coverage runs 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: false was as fast but fails 5–57 tests when file order is shuffled (state leaks between files), so it's not used.
  • 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 unused printWidth: 40 override for *.mdx is gone. Linting code blocks inside MDX is still not covered.
    • Lint does no formatting: eslint-plugin-prettier and eslint-config-prettier are gone. CI's lint job now also runs pnpm format, so formatting is checked in CI for the first time.
    • format and lint are root-only scripts run outside turbo: each checks the whole repo in under a second, so caching saves nothing. Per-package lint scripts and the turbo lint/format tasks are removed.
    • Rule parity: .oxlintrc.json comes from @oxlint/migrate. Three rules are oxlint nursery (experimental) rules, enabled by name: no-undef (JS files only; tsc covers TS), react/require-render-return, promise/no-return-in-finally. The rules not migrated are obsolete under strict mode, React 17+ or TS. The correctness category 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-level eslint-disable comments. lint fails on unused disable directives.
    • Format churn left build output byte-identical (all 864 dist/ and vendor files), as did the lint churn.
    • @types/eslint override kept in pnpm-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. Warm check 66ms → 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 tsconfig off baseUrl / moduleResolution: node (target is already ES2022, [uplift] chore: switch to tsdown. #3108), fix Stop using typescript module syntax #3077, switch types:check to tsgo, and set tsdown's dts.generator: "tsgo" (needs typescript@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 on victory-core. Stop using typescript module syntax #3077's export module blocks pass only because skipLibCheck skips 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 namespace in victory-core's vendored immutable-types.d.ts. TS 7.0.2 reported 18 TS1540 errors on the published .d.mts with skipLibCheck: false; now 0. Only those lines change in dist/. Ride-along: the website's types:check had no task dependencies (website/turbo.json overrode 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", no baseUrl (TS 6 deprecates node10 and baseUrl; TS 7 removes them). Website: Docusaurus's baseUrl unset, @site/* declared locally, strict: false (TS 6's new default is true). Build output byte-identical; pack:check and 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 --force 12.4s → 13.0s (+1 task).
      • getTicks's inferred return union (number[] | string[] vs string[] | 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:check added; under bundler the victory-vendor/d3-* self-imports resolve through exports.
    • 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 (only unstable/* exports), so declarations stay on TS 6 via rolldown-plugin-dts. Root typescript@7.0.2; a .pnpmfile.cjs hook 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:check now type-checks each package's published declarations with skipLibCheck: false on TS 7, so module blocks can't come back; 18 pre-existing errors are allowlisted (Phase 4 types item). 0 source errors; the 6 in victory-candlestick.test.tsx were a moved @ts-expect-error (TS 7 reports per prop); declarations unchanged; tsc-only types:check 4.6s → 2.4s.
    • Later (38.x is fine): dts.generator: "tsgo" once TS 7.1 is stable; compare declarations byte-for-byte. Remove .pnpmfile.cjs and the 6.0.3 pin with it (check the website and Storybook tools then, or after Phase 3 removes Storybook). Then type-aware oxlint.
  • Node: add .nvmrc (lts/*), used by CI (node-version-file) and documented in CONTRIBUTING. Remove the engines fields from every package.json and 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:gha locally), issue workflows on GITHUB_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 CI bundle-size job writes the table to the job summary; on PRs it builds the base (HEAD^1 of 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 } from victory-pie or victory 54.7 kB gz, all of victory-core 67.5 kB, all of victory 120.7 kB. Job ~30s, in parallel with test.
  • 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 start script (points at the missing demo/ts), config/webpack/demo, react-hot-loader ([uplift] chore: remove UMD bundles and webpack; add browser ESM docs #3107), nps ([uplift] chore: replace wireit + nps with Turborepo #3102), cross-env ([uplift] chore: switch to tsdown. #3108), cpx2 and rimraf ([uplift] chore: replace cpx2 and rimraf with Node scripts #3136; sync and clean:* are now small async Node scripts; victory-vendor's build dropped rimraf in [uplift] chore: update victory-vendor build tools #3127), and unused 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-renderer and ts-node from the root, and moved victory-vendor's four Babel build deps into its own devDependencies (it relied on hoisting). Vendor output byte-identical. Swept with knip. concurrently stays until Phase 3 (see there).
    • Remove the unused lodash from dependencies in victory-canvas, victory-histogram and victory-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 default fontFamily (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.
  • 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 dep:check script ([uplift] chore: add dep:check and update root dev dependencies #3114: npm-check-updates as a pinned root devDependency, root + workspaces, 7-day cooldown; parked majors in .ncurc.cjs).
    • B1, root ([uplift] chore: add dep:check and update root dev dependencies #3114): jsdom 30 (toCssColor test helper for its rgb() normalization; pie fixture's invalid #fffff fixed), 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/react 18.3 (one type-only cast in add-events.tsx), TypeScript 5.9, @types/node 24 (.nvmrc pinned to 24). 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 /blog page) and onBrokenMarkdownLinks under markdown.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.json redirects / → /open-source/victory (307) so Vercel preview links land on the docs ([uplift] chore: redirect Vercel root to /open-source/victory #3117).
    • B3, victory-vendor build tools ([uplift] chore: update victory-vendor build tools #3127): Babel 7.29, babel-plugin-module-resolver 5.0.3; execa and rimraf dropped (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.json unchanged.
    • Website leads from knip, resolved in [uplift] chore: update website dependencies #3116:
      • postcss-import removed. It was a no-op (CSS output identical) and only resolved because Docusaurus loads the config from inside node_modules/.pnpm, where Tailwind 3's copy is reachable.
      • @docusaurus/plugin-google-gtag and plugin-google-tag-manager removed; preset-classic depends on both (tags still in the built HTML).
      • @algolia/client-search and search-insights removed after all: they're required peers of @algolia/autocomplete-core under theme-search-algolia (pulled in by preset-classic, unused: the site uses local search), and pnpm fills them via autoInstallPeers: true.
      • @docusaurus/plugin-content-docs stays declared: sidebars.ts imports its types.
    • Website follow-ups found in B2:
  • 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.
  • Dependency updates without bots (decision log 2026-10-28): no Renovate or Dependabot version-update PRs.
  • Docs site (Docusaurus): build through turbo (pnpm build:docs, used by Vercel; [uplift] chore: replace wireit + nps with Turborepo #3102).
  • Docs site: bump react-live to v5 ([uplift] chore: website react-live 5 #3137). Docusaurus's live-codeblock still installs its own 4.1.8, unused: our swizzled Playground and ReactLiveScope replace 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.
  • A small Vite app that loads stories/**/*.stories.tsx and gives a browsable gallery (as in react-live's stories/).
    • [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 in render. Gallery in stories/app/ (sidebar by title, filter, #<story-id> URLs, error boundary). pnpm stories:dev runs turbo watch (package rebuilds reload Vite). Stories are type-checked in pnpm check and 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 index attribute, 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 no x); two VictoryLine stories warn about missing keys.
  • 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 (via load-stories.ts) in headless Chromium (Vitest browser mode, Playwright) and compares its serialized markup with one file per story in stories/tests/__snapshots__/<title>/<name>.html. Runs in pnpm test/check and in CI's test and react-18 jobs (pnpm stories:install installs 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 TextSize approximation instead of the installed fonts, the browser runs in UTC with en-US, lodash uniqueId ids 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 --barratio from [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).
  • 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.json and the workflow.
    • Also concurrently (only storybook:dev uses it). Storybook's dev server doesn't start under Node 24 (same SWC preset error as Chromatic), so it wasn't swapped for turbo watch in Phase 2. The stories app's dev script should use turbo watch from the start.

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.

Phase 5: Source modernization (later; continues #2863)

Tracked here for visibility; each item gets its own issue.

Housekeeping

Out of scope (separate tracks)

  • Tree-shaking through the victory barrel. In early measurements, { VictoryPie } from "victory" comes out at about 131k gz vs 62k from victory-pie (esbuild, 37.3.6). This gets its own investigation and issue. Resolved 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).
  • Package consolidation (one victory package 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 No releases until the uplift infra works. The first release is 38.0.0 after the final merge Superseded 2026-10-25 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

  1. ryan-roemer commented on Oct 28, 2026

    @ryan-roemer
    MemberAuthor

    Vitest landed on chore/2026-uplift-02 as a direct push in dadb6af (no PR, by accident). This comment stands in for the PR description.

    • Vitest 5 replaces Jest. Each package's test script uses the shared test/vitest.config.mts (jsdom, globals). The 30 Jest configs are gone.
    • The turbo/script task jest is renamed to test (pnpm test), in CI, AGENTS.md and CONTRIBUTING too.
    • Tests: jest.* → vi.* (20 files) and xit → it.skip (1 file). No snapshot changes; 619 pass, 2 skipped.
    • jsdom is pinned to 20.x (what Jest used). jsdom 30 normalizes inline CSS colors and breaks 3 assertions, so that upgrade waits for the dev-deps update.
    • @vitest/eslint-plugin replaces eslint-plugin-jest.
    • Removed the unused root .babelrc.js and its presets. victory-vendor's Babel build is unchanged, and pack:check passes.
    • Timings (local): tests ~10.5s → ~4.3s, cold check ~30s → ~18s, warm 66ms.
    • No changeset (devDependencies, scripts and tests only). Coverage comes in a follow-up PR.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions