Repository navigation
Description
Living tracking issue, modelled on FormidableLabs/victory#3093 (and nearform/llm-splitter for the single-package, JSDoc-typed setup), but much smaller. We'll keep editing it as we go, and record what changed in the Decision log at the bottom.
Status (2026-11-12): merged. #156 merged chore/2026-uplift into master with a merge commit (1e16aab); release.yml's first push run on master published nothing, as expected (run). Post-merge cleanup is done apart from #70 (waiting on Dependabot's rescan). Follow-up work is tracked in the issues it names. History: Phase 1 is done: #141 (pnpm 12, Node 26, vendored test cases, Codecov and AppVeyor removed, mocha 11), changesets v3 + OIDC release.yml with canary (65c9439), and a canary 3.2.3-canary-20261105232110 published to next through OIDC with provenance (run). Phase 2 so far: oxlint + oxfmt (#142 config, #143 format, #144 lint); #145 reverted #130's unreleased type guard so index.d.ts matches 3.2.2 again; #146 added pack:check, which compares the packed tarball with a snapshot of the published 3.2.2; #147 added TypeScript 7 + JSDoc types and types:check. #148 added the type probe: test/types/ compiled against the packed tarball inside pack:check. The size report (pnpm size, CI job summary, compared with the parent commit) replaced the compress/size-min-gz scripts and the README badge. Phase 3: Vitest 5 (node project + browser mode in Chromium, Firefox and WebKit) replaced mocha, nyc, Karma, webpack, Babel and the rest of the Phase 2 cruft; React 19 + Preact 11 in the lockfile, a CI job on React 18.0.0 + Preact 10, v8 coverage in the job summary. zizmor runs in a CI job (lint-gha) and in pnpm check via pnpm lint:gha (skipped when zizmor isn't installed); .github/pinact.yml drives pinact run --update. pnpm check (format, lint, lint:gha, types:check, test, pack:check) passes. Phase 3 is done. Phase 4: pnpm bench (Vitest 5 benchmarking, bench/compare.bench.mjs) compares the working tree, 3.2.2 (aliased devDep) and fast-deep-equal per fixture group, with a report-only bench CI job writing the tables to the job summary; benchmark/, the benchmark/lodash/nano-equal/shallow-equal-fuzzy devDeps and the README chart are gone. Phase 2 leftovers: pnpm dep:check (npm-check-updates, .ncurc.cjs with a 7-day cooldown) and the update routine in CONTRIBUTING; a knip audit (pnpm dlx knip, knip.jsonc) found no unused devDependencies; first dep:check run bumped Vitest to 5.1 and pnpm to 12.17. AGENTS.md (+ CLAUDE.md pointer, @AGENTS.md) closes Phase 2. Phase 5: README/CONTRIBUTING/PR template docs pass (#155, Nearform naming); Dependabot PRs and obsolete issues closed, stale branches and the NPM_TOKEN secret deleted. Phases 1–5 are done. Next: #139 (Temporal) + #137 as the first 3.x release from the new pipeline.
Why
- Nothing has been released since 3.2.2 (Mar 2023).
release.ymlpublishes withNPM_TOKENand gives its one job broad write permissions; no OIDC. - The dev stack is from 2020: yarn 1,
builder, mocha + nyc, Karma + webpack + Babel + core-js (for IE11), ESLint 8, TS 4.9, Node 18 in CI, Codecov. - Correctness tests mostly come from a git dependency,
fast-deep-equal-git(epoberezkin/fast-deep-equal#v3.1.3): 62 generic + 49 ES6 suites from itsspec/. Upstream hasn't released since 2020. - Open dev-only security alerts (Audit security alerts #70) and three stale Dependabot PRs (Bump minimatch from 3.0.4 to 3.1.2 #122, Bump engine.io from 6.4.1 to 6.4.2 #124, Bump socket.io-parser from 4.2.2 to 4.2.3 #128) all come from the Karma/webpack tree.
- New bug: isEqual throws on Temporal values because it calls valueOf #139, Temporal values throw (
valueOf). It's the first source change after the uplift, and the first release from the new pipeline.
By the numbers (Nov 2026)
- ~107M downloads/month. Last week by version: 3.2.2 ~27.5M, 2.0.4 ~6.1M, 3.2.0 ~1.45M. Most users are pinned through dependents' ranges (
^3,^2). - Published surface:
index.js(CJS,module.exports = isEqual, ES5) andindex.d.ts(export =). Noexportsmap, no ESM, nosideEffects. There's no UMD bundle to drop, unlike victory. - Quick probe (2026-11-05) of current
index.js:- React 19.3.1 (dev and prod): elements compare correctly. In dev,
_owneris enumerable and still skipped; the_debug*props are non-enumerable. - Preact 10.29 / 11.1 with
preact/compat: correct. - Plain
preacth()vnodes with compat not loaded comparefalse(no$$typeof, so__vis traversed). This is pre-existing and the same in 10 and 11; only compat is documented. A later source item, not uplift work.
- React 19.3.1 (dev and prod): elements compare correctly. In dev,
- JSDoc spike (TS 7.0.2,
allowJs+checkJs+strict,emitDeclarationOnly): a 5-line JSDoc block onisEqualemitted exactly theindex.d.tsonmasterat the time (the unreleased Convert function to TypeGuard #130 type guard, since reverted). It needs redoing against 3.2.2'sdeclare function isEqual<A = any, B = any>(a: A, b: B): boolean;(@template A, B,@returns {boolean}).strictreports 7 errors insideequal()(implicitanyparams,ArrayBufferViewindexing,catch (error)asunknown). All can be fixed with JSDoc annotations or casts, with no runtime change.
Principles
- Infra first, source last. Mechanical source churn (oxfmt, lint fixes, JSDoc types) lands in its own PR with no behavior change, and goes into
.git-blame-ignore-revs. - The uplift doesn't change what users get. Same files, same
module.exports = isEqual, same ES5 code, same declarations. Only formatting and comments may change inindex.js, andpack:checkproves the rest (see Phase 2). ESM,exportsand a modern target wait for 4.0.0. - Types come from JSDoc, as in llm-splitter. Source stays plain JS with no build step for the code.
tsctype-checks it (checkJs) and, from 4.0, generates the.d.ts. - Plain
package.jsonscripts. No task runner, since this is a single package. - Crib from victory and llm-splitter where they fit a one-file library.
Versioning (decided 2026-11-05)
- Uplift →
masterwith no published change. No changeset is needed for the infra PRs; there's nothing to release from them alone. - isEqual throws on Temporal values because it calls valueOf #139 (Temporal) → 3.x (patch or minor), released from the new OIDC pipeline. It reaches every
^3user, and it's the pipeline's first real run, like victory's interim 37.4.0. Adding sideEffects: false to package.json #137 (sideEffects: false) can ride along; it's a safe manifest addition. - 4.0.0 later: ESM + CJS,
exportsmap, modern target (drop ES5/IE),.d.tsgenerated from JSDoc. Its own design section when we get there (see "4.0.0" below).
Branching and merge strategy
- Default branch stays
master(decided 2026-11-05). - Integration branch:
chore/2026-uplift, cut frommaster. Small PRs into it (squash is fine). CI runs onmaster+chore/2026-uplift*. - Final merge: one PR into
masterwith a merge commit, which keeps the blame-ignore SHAs valid. Repo settings change first (same traps as victory); steps in "Final merge checklist". - Release gate for the merge: green CI, and
pack:checkshows the packed tarball matches 3.2.2 (see Phase 2). A canary tonextfrom the branch proves OIDC before isEqual throws on Temporal values because it calls valueOf #139 needs it.
Phase 1: Package manager and release pipeline (OIDC)
- pnpm 12 for development ([uplift] chore: pnpm 12, Node 26, vendored fast-deep-equal test cases #141) (
packageManager,pnpm-workspace.yamlsettings,minimumReleaseAge: 10080,allowBuilds). Removeyarn.lock. README install instructions say onlynpm install react-fast-compare; CONTRIBUTING says pnpm. - Repo files, as in victory (
.nvmrc,.editorconfig,.gitignoredone in [uplift] chore: pnpm 12, Node 26, vendored fast-deep-equal test cases #141):.nvmrc→26, used by CI vianode-version-fileand documented in CONTRIBUTING. Still noenginesfield..editorconfig(2 spaces, LF, final newline; Markdown keeps trailing whitespace).- Later phases add
.oxlintrc.json,.oxfmtrc.json,.git-blame-ignore-revs,.ncurc.cjs(dep:check),.github/zizmor.ymland.github/pinact.yml, plusAGENTS.md/CLAUDE.md. .gitignore: trim the 2017 template (bower, grunt, jspm, lib-cov, …).pnpm-lock.yamlis committed;package-lock.jsonandyarn.lockare ignored.
- changesets v3 + official
@changesets/changelog-github(disableThanks), replacing the deprecated@svitejs/changesets-changelog-github-compact.snapshot.useCalculatedVersionfor canaries.baseBranch: master. -
release.ymlonchangesets/actionv2 split jobs (llm-splitter's single-package version is the closest template; victory's adds pnpm and a canary): select-mode → version | pack → publish.permissions: {}with per-job grants,id-token: writeonly on publish/canary,package-manager-cache: false,persist-credentials: false. Onev{version}tag +gh release create --generate-notes.- Canary via
workflow_dispatchin the same file →X.Y.Z-canary-<datetime>onnext.
- Canary via
- Drop
publishConfig.provenance(automatic under trusted publishing).
npm and GitHub setup (manual, needs a maintainer)
One package, so this is done by hand in the npm and GitHub UIs (no npm-trust.sh like victory's).
- GitHub: create the
npm-publishenvironment (Settings → Environments), deployment branchesmaster+chore/2026-uplift. Required reviewers optional. NotProduction, to keep it separate from any deployment environments (victory decision log, 2026-10-26). - npm: on npmjs.com →
react-fast-compare→ Settings → Trusted Publisher → GitHub Actions: organizationFormidableLabs, repositoryreact-fast-compare, workflowrelease.yml, environmentnpm-publish. Renaming the workflow file or the environment breaks publishing until this is updated. - Validate: canary from the integration branch (
gh workflow run release.yml --ref chore/2026-uplift) publishesX.Y.Z-canary-<datetime>tonextwith provenance. It needs at least one pending changeset; use a throwaway one and delete it afterwards. Done 2026-11-05:3.2.3-canary-20261105232110, published bynpm-oidc-no-reply@github.com, SLSA v1 provenance forrelease.ymlonchore/2026-uplift. - npm → Publishing access → "Require two-factor authentication and disallow tokens" (done 2026-11-05, after the canary proved OIDC; earlier than planned).
- Delete the
NPM_TOKENrepo secret (unusable now that tokens are disallowed). Done 2026-11-12. - Before and after the final merge: see "Final merge checklist".
Phase 2: Tooling
- oxlint + oxfmt replace ESLint 8 ([uplift] chore: replace ESLint with oxlint + oxfmt #142 config, [uplift] chore: format with oxfmt #143 format, [uplift] chore: fix oxlint errors #144 lint) (+
@typescript-eslint/parser,eslint-plugin-react). Exact-pinned (0.x). Config PR, then a format-churn PR, then a lint-churn PR, with each churn SHA in.git-blame-ignore-revs.index.jsstays ES5 in 3.x: nono-var/prefer-const/ES2015 autofixes on it (test and tooling files can be modern).index.js'sSTART:/END: fast-deep-equalcomment blocks must survive the reformat; they're how upstream diffs are tracked (CONTRIBUTING).
- TypeScript 7 + JSDoc (llm-splitter pattern; [uplift] chore: TypeScript 7 + JSDoc types with types:check #147):
tsconfig.json:allowJs,checkJs,strict,noEmit,module/moduleResolution: nodenext,libwithdom(forElement). It currently has ESLint keys (env,parserOptions) in it; those go.- JSDoc on
isEqual(@template [A=any],@template [B=any],@param {A} a,@param {B} b,@returns {boolean}, matching 3.2.2), plus annotations inequal()for the 7stricterrors. Its own PR, no runtime change. - Keep the hand-written
index.d.tsas the published file in 3.x. Atypes:checkstep emits declarations from JSDoc to a temp dir and fails if they differ fromindex.d.ts, so the two can't drift. In 4.0 the generated file replaces it. - Type probe (llm-splitter's
type-probeidea, wired into CI; [uplift] chore: type probe against the packed tarball #148):test/types/compiled with TS 7 (nodenext,strict) against the packed tarball, as a step inpack:check(no new script). A.ctsconsumer (import = requireand default import) and a.mtsconsumer, each asserting 3.2.2's exact signature, plusReact.memo(C, isEqual),shouldComponentUpdate, auseSelector-style(a, b) => booleancallback, no narrowing toneverafter afalseresult (Convert function to TypeGuard #130), and@ts-expect-errornegatives. Replacestest-ts-defs/test-ts-usage; dropsreact-dom,react-redux,redux,@types/react-dom,@types/react-redux;@types/react16.9 → 19. No resolution matrix: see decision log (2026-11-06).
- Revert Convert function to TypeGuard #130's unreleased type guard ([uplift] fix: revert unreleased type guard to 3.2.2 types #145) (
a is B) to 3.2.2'sbooleansignature, with atest-ts-usagecase that fails under the guard. Lands beforepack:check. -
pack:check(simplified from victory's; snapshotpack-snapshot.jsongenerated from the 3.2.2 tarball,--updatefor intended changes,--tarballto check any tarball; Linux CI only; [uplift] chore: add pack:check against the published 3.2.2 tarball #146): onpnpm packoutput:- publint + attw.
- Isolated
require()andimport():require('react-fast-compare')(a, b)must stay callable, andimport isEqual frommust work in Node ESM. - Compatibility with 3.2.2: same file list and manifest fields (
main,types/typings,files), identicalindex.d.ts, andindex.jsminified byte-identical to 3.2.2's, so formatting, comments and JSDoc are the only changes. Dropped once 4.0 work starts.
- Size report, replacing
terser | gzip | wc -cand the hand-edited README badge:scripts/size.mjs(terser, already a dep forpack:check; raw, min, min+gzip, min+brotli), printed locally and in CI's job summary.--base <git ref>adds a diff; CI passesHEAD^1(fetch-depth: 2). Reported, not gated. The badge is gone (README and PR template point topnpm size). - Remove cruft (
andcodecov+ README badgeAppVeyor/IE11 Karma config and referencesdone in [uplift] chore: pnpm 12, Node 26, vendored fast-deep-equal test cases #141; the rest with the Vitest migration in Phase 3):builder,nyc,sinon(usevi.spyOn),jsdom-global, Babel, core-js, webpack, Karma and its launchers,karma.conf.ie.js,test/mocha.opts,.eslintrc.yml/.eslintignore. - Minimal dev dependencies. Keep only what a script, test or CI step strictly needs; audit with knip at the end of each phase.
buildergoes first. Audited after Phase 4: nothing unused. knip runs throughpnpm dlxwithknip.jsonc(not a devDependency); its four false positives (.ncurc.cjs, thetest/types/probe files,attwrun through a helper,zizmor) are listed there. - Small script surface, like victory. The entry points are
check,formatandtest:check: everything CI gates on, run in sequence (format, lint,lint:gha, types, test,pack:check).format/format:fix: oxfmt check / write.test: Vitest.- Only the pieces
checkcalls stay as scripts (lint/lint:fix,lint:gha,types:check,pack:check), plusdep:check,bench,sizeandchangeset. The rest go (eslint,tslint,test-*,compress,size-min-gz,preversion,benchmark).dep:checkadded last;benchmark→benchin Phase 4.
-
AGENTS.md(+CLAUDE.mdpointer), as in victory and llm-splitter. It covers: ES5 + no published change in 3.x, JSDoc types, the upstream comment blocks, changeset rules. -
dep:check(npm-check-updates, 7-day cooldown) and a documented manual update routine. No Renovate/Dependabot version PRs; Dependabot security alerts are already on (67 open on 2026-11-12, all fromyarn.lockonmaster); close Audit security alerts #70 once the final merge clears them.
Phase 3: Tests
One set of spec files, run in Node and in real browsers.
- Vitest 5 with two projects (decided 2026-11-05). Specs are
test/*.spec.mjs;test/fixtures/index.mjsholds our own cases.index.jsand the vendored fixtures are CommonJS: Node loads them natively, and the browser project wraps them with a 10-line inline plugin (decision log, 2026-11-07).node: plain Node;jsdomonly for the specs that need a DOM.browser: Vitest browser mode + Playwright, Chromium, Firefox and WebKit headless. This replaces Karma and closes Browser tests: integrate sauce labs. #12 (Sauce Labs).
- Vendor the upstream test cases ([uplift] chore: pnpm 12, Node 26, vendored fast-deep-equal test cases #141; pulled forward: pnpm packs git dependencies, which honors upstream's
fileslist, sospec/wasn't installed): copyfast-deep-equal@3.1.3'sspec/tests.jsandspec/es6tests.jsintotest/fixtures/with its MIT notice, and drop thefast-deep-equal-gitgit dependency. They're frozen upstream anyway, and isEqual throws on Temporal values because it calls valueOf #139/Bug: Set compares by reference not value #50 will add our own cases next to them. - Drop comparative tests and deps (Phase 4):
lodash,nano-equal,shallow-equal-fuzzy. No test uses them; onlybenchmark/index.jsdoes, so they go with it in Phase 4. - React tests on React 19 and a CI job on React 18.0.0, plus
preact/compat10 and 11. Replacereact-test-renderer(deprecated in 19) withreact-dom(createRoot+flushSync;renderToStringnever callsshouldComponentUpdate).@testing-library/preactand@testing-library/domdropped too: Preact's ownrenderis synchronous. Keep React 16/17 out of CI. The library doesn't depend on React, and its React handling ($$typeof+_owner) is the same shape across versions. - Coverage via
@vitest/coverage-v8in the CI job summary (no Codecov). Node project only (pnpm test --project node --coverage): v8 coverage doesn't run in Firefox or WebKit. - CI:
ubuntu with Node(done with Vitest). zizmor in CI (.nvmrc+ browsers; keep windows for the Node project onlylint-ghajob,zizmorcore/zizmor-action, zizmor 1.31.1, online audits) andpnpm lint:gha; hybrid action pinning (actions/*/github/*on tags, the rest on SHAs, enforced by zizmor'sunpinned-uses;pinact run --updatewith a 7-daymin_age);permissions: {}was already in place. Configs as in victory. Build-free, so the CI step that matters most besides tests ispack:check.
Phase 4: Benchmarks (decided 2026-11-05: self-regression)
-
vitest bench(Vitest 5:test(name, ({ bench }) => bench.compare(...))inbench/compare.bench.mjs, run in the derivednode (bench)project) over each fixture group (generic,es6,react,custom), comparing:- the working tree (
this tree), - the last published version (
react-fast-compare-published@npm:react-fast-compare@3.2.2; bump after each release, once it clearsminimumReleaseAge), and fast-deep-equal@3.1.3(es6/react) as the upstream baseline we forked from.- A candidate that throws on a group's cases is left out of that group and named in the test title (today: fast-deep-equal on
custom, theObject.create(null)cases).
- the working tree (
-
pnpm benchlocally, and a report-onlybenchCI job:bench/summary-reporter.mjswrites one table per group (ops/s, mean, RME, samples, % vs.published) to the job summary. No threshold; the job fails only if the benchmark breaks. - Remove
benchmark/index.js,benchmark(the npm package),lodash,nano-equal,shallow-equal-fuzzy,assets/benchmarking.png, the README chart and the Google Sheet step in CONTRIBUTING. The README keeps a short "Benchmarking" section on how to run it; CONTRIBUTING has a "Benchmarks" section. - First numbers (Node 26, local arm64 Mac): this tree = 3.2.2 within noise in every group; fast-deep-equal is ~3% faster on
genericand ~20% faster ones6(likely our ES5 hand-lowered Map/Set iteration; a 4.0 question). - Why keep it at all: speed is the library's whole pitch, and the next source changes (isEqual throws on Temporal values because it calls valueOf #139 Temporal, Bug: Set compares by reference not value #50 Sets) touch the hot path.
Phase 5: Docs and housekeeping
- README (Nearform naming; repo URL stays
FormidableLabs/react-fast-compare): npm-only install; fix "tracksfast-deep-equal@3.1.1" (it's 3.1.3); fix the broken LICENSE link (blob/readme/LICENSE); update badges; settle maintenance status/branding (Formidable → Nearform?). The "ES5 / IE9" claims stay true in 3.x and change in 4.0. - CONTRIBUTING rewritten for pnpm, Vitest, oxlint/oxfmt, JSDoc types, changesets, canary,
dep:check, bench. -
.github/pull_request_template.md: drop the bundle-badge andtest-ts-*items. - Close or settle: Dependabot PRs Bump minimatch from 3.0.4 to 3.1.2 #122/Bump engine.io from 6.4.1 to 6.4.2 #124/Bump socket.io-parser from 4.2.2 to 4.2.3 #128 (Karma tree, gone), Browser tests: integrate sauce labs. #12 (Sauce Labs → Playwright), Investigate using Backstroke to track changes to fork source #24 (Backstroke, obsolete). Audit security alerts #70 (alerts) stays open until the final merge (see "Final merge checklist").
- Delete stale remote branches:
ci-provenance,experiment/es6-circ-refs,feature/es6-extras,fix/missing-patch,task/update-dependencies,viper1104-dom, the dependabot ones, andchangeset-release/masterwhen idle. -
Optional: moveDeleted: it was Formidable-branded; the README uses the Nearform banner (react-fast-compare-Hero.pnginto.github/oss.nearform.com/api/banner.svg) as victory does.
Final merge checklist
Reviewed 2026-11-12: master hasn't moved since the branch was cut; CI is green on chore/2026-uplift; master has no branch protection or rulesets (no stale required checks); npm-publish allows master; no pending changesets.
Before:
- Last integration commit (b882172):
AGENTS.md"Branch" line says PRs targetmaster; dropchore/2026-uplift*fromci.yml's push branches (the merge PR'spull_requestrun still covers it). - Repo settings: allow merge commits (
mergeCommitAllowedisfalse); turn off "Automatically delete head branches" (deleteBranchOnMergeistrue) so the integration branch survives the merge. - Open the PR
chore/2026-uplift→master(Chore/2026 uplift #156). Gate: CI green, includingpack:check(published files match 3.2.2).
Merge:
- Merge commit (not squash; 1e16aab, 2026-11-12), so the
.git-blame-ignore-revsSHAs stay valid. - Watch
release.yml's first push run onmaster(run: select-mode passed, every other job skipped;lateststill 3.2.2): with no changesets and 3.2.2 already published, it should publish nothing. It's the first run of the split version/publish jobs (only the canary path has run so far).
After:
- Remove
chore/2026-upliftfrom thenpm-publishdeployment branches, then delete the branch (and the localchore/uplift-*branches). - Restore the repo settings (merge commits off, squash on, auto-delete head branches on).
- Check the 67
yarn.lockDependabot alerts closed on their own, then close Audit security alerts #70. Still open on 2026-11-12 after the merge (rescan pending). - Settle open PRs against
master: Add coment to explain NaN verify #135 (rebase after the oxfmt churn, merge) and feat: add comparison of functions by content #115 (function comparison, declined so far; it will conflict with the uplift).
After the uplift (source, PRs straight to master)
- isEqual throws on Temporal values because it calls valueOf #139 Temporal → 3.x release from the new pipeline. Check
toString-based Temporal types beforevalueOf(or detectTemporal.*and use.equals). Bench before/after. - Adding sideEffects: false to package.json #137
sideEffects: false: rides along with isEqual throws on Temporal values because it calls valueOf #139 (manifest-only, safe for 3.x). - PR Add coment to explain NaN verify #135 (NaN comment): rebase after the oxfmt churn and merge.
- Later and undecided: Bug: Set compares by reference not value #50 (Set by value; perf cost), There should be a 'debug' feature to determine which fields are breaking equality. #68 (debug mode), Add ability to compare functions #114 / PR feat: add comparison of functions by content #115 (function comparison, declined so far as unsafe by default), plain-Preact vnodes (see probe).
4.0.0 (later; sketch only)
- ESM + CJS with an
exportsmap,sideEffects: false, modern target (drop ES5/IE;for...ofinstead of the hand-lowered iterators),.d.tsgenerated from JSDoc (.d.ctswithexport =,.d.mtswithexport default). - Open question for then: author ESM and generate the CJS (tsdown/rolldown, keeping
module.exports = isEqual), or keep CJS source with a thin ESM wrapper (no dual-package hazard, since the function is stateless). - Type probe: once there's an
exportsmap, add the resolution matrix (node10ignoresexports, so it falls back tomain/types). TS 7 can't runnode10oresModuleInterop: false, so that needs an aliased TS 6 devDep (e.g.typescript-6@npm:typescript@6.0.3,ignoreDeprecations: "6.0"). - Changeset migration note: deep imports (
react-fast-compare/index.js) stop resolving underexports.
Decision log
| Date | Decision | Why |
|---|---|---|
| 2026-11-05 | Integration branch chore/2026-uplift, PRs into it, merge commit to master |
Same as victory; keeps master releasable. |
| 2026-11-05 | Keep master as the default branch |
No churn for contributors, links and dependents' tooling. |
| 2026-11-05 | Uplift has no published change; #139 ships as 3.x from the new pipeline; ESM/exports/modern target in a later 4.0.0 |
Most of the ~107M monthly downloads come through ^3/^2 ranges. A 3.x fix reaches them; a 4.0 wouldn't until dependents bump. Keeps the pipeline's first release low-risk. |
| 2026-11-05 | Types via JSDoc + checkJs on plain JS (llm-splitter pattern), not a TS port |
No build step for the code. TS 7 emits today's index.d.ts exactly from a 5-line JSDoc block. |
| 2026-11-05 | Benchmarks: self-regression (tree vs last published vs fast-deep-equal), report-only | Speed is the pitch, and upcoming fixes touch the hot path. Other libraries' numbers don't help maintain this one. |
| 2026-11-05 | No task runner; plain scripts | Single package, sub-second tasks. |
| 2026-11-05 | Tokens disallowed on npm right after the canary, not after #139 | The canary proved OIDC end to end; no reason to keep a token path open. |
| 2026-11-05 | Canary 3.2.3-canary-* stays on next |
Harmless; the next canary replaces it. |
| 2026-11-05 | Scripts: check, format, test as the entry points, plus only the sub-scripts they call |
Same shape as victory; fewer names to learn and keep working. |
| 2026-11-05 | Vitest (Node + browser mode/Playwright) over node:test + another browser runner |
One spec set, one tool, both environments; matches victory. |
| 2026-11-05 | Vendor fast-deep-equal's spec cases, drop the git dependency | Upstream frozen since 2020; git deps are slow and fragile. |
| 2026-11-05 | npm trusted publishing and the GitHub environment are set up by hand, not scripted | One package; victory's npm-trust.sh only pays off across 29. |
| 2026-11-05 | Dev dependencies kept to the strict minimum; builder removed |
Every dependency is upkeep (alerts, dep:check, release-age holds). A single-file library needs few tools. |
| 2026-11-05 | Codecov removed early (#141), not replaced until Vitest coverage | The codecov@3 uploader crashes in CI; nyc still prints a summary. |
| 2026-11-05 | Dev files follow victory: .nvmrc (26), .editorconfig, oxc configs, .ncurc.cjs, zizmor/pinact configs |
Same habits across our repos. |
| 2026-11-05 | No .vscode/ in git; it stays gitignored |
Maintainer call while reviewing the oxc config PR; departs from victory. |
| 2026-11-05 | Revert #130's type guard (merged 2023, never released) to 3.2.2's boolean types; reconsider in 4.0 as a separate opt-in function |
A guard narrows the first argument to never after a false result when both arguments share a type, which breaks common if (isEqual(prev, next)) return; prev.x code. Too risky for a 3.x release reaching every ^3 user. |
| 2026-11-06 | types:check compares emitted vs. hand-written index.d.ts ignoring statement order |
TS 7 emits export = isEqual; before the declaration; 3.2.2's file has it after, and pack:check pins index.d.ts byte for byte. Order doesn't matter in a declaration file. |
| 2026-11-06 | Type probe without a resolution matrix: one .cts and one .mts consumer under TS 7 nodenext; matrix deferred to 4.0 |
TS 7.0.2 removed moduleResolution: node10/node/classic and esModuleInterop: false (TS5108, not overridable). And in 3.x the matrix tests nothing: one CJS file, main + types, no exports, so every mode resolves the same index.d.ts (attw's resolutions in pack-snapshot.json already pin that). Only the CJS vs. ESM import of export = differs. |
| 2026-11-06 | strict fixes in equal() go in comments only: casts on the isView() arguments (so a/b aren't narrowed), not on each use |
Leaves fast-deep-equal's upstream lines untouched apart from the if, and casting each use pushed the loop past 80 columns, where curly would have needed braces. |
| 2026-11-07 | Size report uses terser, compares with HEAD^1 in CI, and the README size badge is dropped rather than made dynamic |
terser is already a dependency and matches the old compress numbers (653 B min+gz, unchanged since 3.2.2). One file, so the base is a git show, not a second checkout and build as in victory. Bundlephobia-style badges measure unmangled code (README already explains the gap); a hand-edited one drifts. |
| 2026-11-07 | Browser project loads the CommonJS index.js and vendored fixtures through a small inline Vite plugin (wraps module.exports on the first line) |
index.js can't change shape in 3.x, the upstream fixtures are copied unchanged, and all three only assign module.exports. A plugin dependency (@rollup/plugin-commonjs, vite-plugin-commonjs) would be more upkeep for three files. The Node project loads them natively, so the CJS path users hit stays tested there (and in pack:check). |
| 2026-11-07 | One extra CI job for React 18.0.0 + Preact 10, Node project only, installed with pnpm add in the working tree |
The renderers only matter for the $$typeof/_owner/__v handling, which doesn't depend on the browser. One job instead of a matrix keeps CI small; the step prints and checks the installed versions. |
| 2026-11-07 | lodash, nano-equal, shallow-equal-fuzzy, benchmark stay until Phase 4 |
No test uses them, only the benchmark, which Phase 4 rewrites. benchmark/index.js now require()s the ESM fixtures (Node 26 require(esm)). |
| 2026-11-07 | zizmor in its own CI job (as victory's lint step) and in pnpm check as lint:gha; pinact is a local update tool only, not run in CI |
lint:gha is called by check, so it fits the small script surface; it skips without zizmor, and CI's test job doesn't install it, so the audit gates only in lint-gha. zizmor's unpinned-uses already enforces the pinning policy, and its online audits (impostor-commit, ref-version-mismatch) check each SHA and its version comment, so a pinact check in CI would add nothing. |
| 2026-11-07 | The older-renderers job's pnpm add is ignored inline for zizmor's adhoc-packages |
It installs outside the lockfile on purpose; it's still under minimumReleaseAge, and the job has no secrets and read-only permissions. A second lockfile for one job would be more upkeep. |
| 2026-11-08 | Benchmarks per fixture group, not the old "generic" and "generic + React" totals; a candidate that throws on a group is left out of it | fast-deep-equal throws on 3 Object.create(null) cases, and timing the exceptions made it ~10× slower on the combined set, hiding everything else. Per group also shows where differences come from (es6). |
| 2026-11-08 | Benchmarks run in Node only by default (--project 'node (bench)'); browsers are opt-in (pnpm exec vitest bench --run --project 'browser (chromium) (bench)') |
Vitest 5 adds a (bench) project for every project, browsers included. Browser timers are coarse (0.1 ms), and the comparison code doesn't depend on the engine's DOM. |
| 2026-11-08 | Job summary from a ~50-line custom Vitest reporter, not the JSON reporter + a script | One command; the reporter sees the same benchmarks() data. It's a no-op outside GitHub Actions, where the default reporter's table is enough. |
| 2026-11-09 | knip runs ad hoc (pnpm dlx knip) with a committed knip.jsonc, not as a devDependency or CI step |
It's an occasional audit when tools change; a dependency would be upkeep for a check that rarely finds anything in a repo this small. The config records the false positives so a re-run is clean. |
| 2026-11-12 | Hero PNG deleted; README uses the Nearform banner service (oss.nearform.com/api/banner.svg), as victory does |
The PNG had the Formidable logo baked in, and it was never published. |
| 2026-11-12 | Naming: "Nearform" everywhere in docs (not "Formidable" or "Nearform Commerce"); the repo stays FormidableLabs/react-fast-compare. LICENSE keeps its 2018 "Formidable Labs" copyright line |
Current company name; the repo isn't moving. A copyright notice records who held it then, so it isn't rebranded. |
| 2026-11-09 | .ncurc.cjs has only cooldown: 7; no parked majors |
Nothing is held back today (React 19, Preact 11, TS 7 are all current). Exact-pinned tools still show up, as they should: their upgrade is a separate PR. |
Activity
[uplift] chore: pnpm 12, Node 26, vendored fast-deep-equal test cases (…
[uplift] chore: replace ESLint with oxlint + oxfmt (#142)
[uplift] chore: format with oxfmt (#143)
[uplift] chore: fix oxlint errors (#144)
[uplift] fix: revert unreleased type guard to 3.2.2 types (#145)
[uplift] chore: add pack:check against the published 3.2.2 tarball (#146
[uplift] chore: TypeScript 7 + JSDoc types with types:check (#147)
[uplift] chore: type probe against the packed tarball (#148)
[uplift] chore: size report (#149)
[uplift] chore: Vitest 5 in Node and browsers, remove test cruft (#151)
[uplift] chore: zizmor + pinact for GitHub Actions (#152)
[uplift] chore: Vitest benchmarks vs. last release and fast-deep-equal (
[uplift] chore: dep:check + knip audit (#154)
[uplift] docs: README, CONTRIBUTING and PR template pass (Nearform na…