diff --git a/CHANGELOG.md b/CHANGELOG.md index 55a97d8..df10d90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ entry per wave, newest first; the first six waves (V1–V6, the MVP) predate the are recorded in PLAN.md §B and §J. Each entry carries the opening sentence of its row and the reason the wave was built. +## V52.1 — Vitest 5, measured against Vitest 4 on the same commit + +The first of V52's three majors, each in its own pull request. + +_Why:_ Approved 09/10/2026 with V52. The safest of the three majors goes first: the suite it runs is also the instrument that proves it changed nothing. + ## V51 — A store that fits on a screen No behaviour change, and the proof is the suite: the 906 unit tests and the 97 Chromium e2e tests that passed before pass unchanged after. diff --git a/PLAN.md b/PLAN.md index 4e3e34b..32bfd57 100644 --- a/PLAN.md +++ b/PLAN.md @@ -610,14 +610,15 @@ runs on a branch name nobody checked. Ordering rule, the same one V35 and V38 applied: defects in shipped code come before features, and the one that produces wrong data comes first of all. -| Wave | Content | Why | -| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **V47 — delivered** | **Workers that cannot answer the wrong question.** Six lifecycle defects in shipped code, each reproduced by a test that failed before its fix. **(1) A result filed under a target it was not trained for**: `setTarget` and `toggleColumn` reset the training state without cancelling the run, the worker kept posting `model-result`, and the auto-save recorded the new target beside the old scores. The store now refuses both changes while anything computed for the current question is running (training, tuning, learning curve, robust ranking, batch scoring), the target picker and the column buttons are disabled for the same span, and a fence (`isStaleResponse`) drops any training message outside a run and any run-derived answer when no run is done; the worker answers in order, so the training status is enough to tell a late answer from a current one, without a run id in the protocol. **(2) Cancel did not reach the parallel phase**: V37's helpers were only checked after every one of them had finished, so cancelling waited for the heaviest families; the phase now polls the flag every 100 ms and terminates the helpers mid-fit. **(3) A crashed ML worker** left `trainStatus` at `training` forever and kept the dead reference; `onerror` now terminates it, clears every busy flag, and the next request starts a fresh worker. **(4) The same crash in the chat** left « thinking » up and still offered a local model that died with its worker. **(5) DuckDB was never closed**: each visit to the SQL panel left a worker and its Wasm heap behind, a failed start leaked its worker too, and a failed Parquet export left its file in the heap; the panel now closes the engine on unmount, on a late start and on failure, and export names come from a counter rather than a millisecond clock. **(6) A webcam stream granted after the page was left** kept the camera lit; it is now stopped on arrival. **(7) Found by this wave's own CI**: touching the chat ran the interpretation bench, which failed on `size-mismatch:tokenizer_config.json`. `prepare-llm.mjs` called its revision « pinned » while it held `main`, and upstream commit `1e94c62` (07/10/2026) had grown that file from 9 762 to 10 416 bytes; the revision is now the commit the sizes were measured on (`d6beae7`), which also keeps the next deploy cache miss from failing the same way. **What this wave deliberately does not do**: add a run id to the worker protocol (the order guarantee makes it redundant today, and it is the first thing to add if requests ever become concurrent), cancel analyses the user cannot cancel today, or touch the deeper share-link validation, which is V48. 883 unit tests, 123 e2e. | Approved 09/10/2026 after the post-V46 audit. The defect in (1) does not crash and does not warn: it saves a run that looks valid and is not, which is the one kind of failure an honest lab must never produce. The others are the same class of bug, a worker outliving the state it was started for, and fixing them together keeps the rule in one place. | -| **V48 — delivered** | **A damaged share link ends on a refusal, never on a crash.** The plan for this row was a deeper validator: check `insights.importance`, numeric metrics, `classes` and `confusion`, the fields the audit had named. The wave opened with a measurement instead, and the measurement changed the plan. Three real links were captured from the running app (titanic with uncertainty, threshold and segments; iris with k-NN tuning and groups; mpg as a regression) and every field of each was deleted, nulled, or retyped as a string, a number, an object or an array: **3 948 damaged links, 3 660 accepted by the decoder, 1 020 of those crashed the page, on 99 distinct paths**, from `insights.importance` to `artifacts.exploration.clusters[].traits[].clusterMean`. A validator for 99 paths would be the largest file in the share module and would drift with every analysis a run learns to carry. So the decoder keeps checking what every view reads first, and `SharedRunPage` wraps the run in a render guard: any failure while drawing it shows the same refusal page, with its own explanation (the link decoded, but part of the run does not have the shape LabML writes) and nothing from the run, so no half-read figure can pass for a real one. The three links are committed under `tests/share-links/`, and the measurement is now a test: every field shape is damaged six ways and the page must end on the run or on the refusal; it fails on the code before this wave and adds about 25 s to the unit suite, the price of covering the whole format rather than a sample. The five copies of the download helper become one, `src/lib/download.ts`, which revokes its object URL after the click has been handled instead of in the same tick, where some engines cancel the download silently. **What this wave deliberately does not do**: write a field-by-field schema for every artifact, show a partially readable run, or guard event handlers (a click on a damaged run is not reachable: the refusal replaces it first). 889 unit tests, 123 e2e. | Approved 09/10/2026 after V47. A link is input from a stranger, and the most common stranger is a chat client that cut it. The decoder already refused the shapes the V35 audit had found; the measurement showed it could not keep up with the format, and that the robust answer was at the point of failure rather than at the gate. | -| **V49 — delivered** | **CI that cannot be steered by a branch name.** The wave opened with zizmor 1.30.1 on the three workflows: **28 findings** (1 template injection, 21 actions pinned by a movable tag, 6 checkouts leaving the git token on the runner disk). **(1) Injection**: the deploy step expanded `github.head_ref` inside its script, so a branch named `x";…;"` would have run as code; the value now reaches the shell through `env:`. Low severity today (only branches of this repository deploy) and fixed before that stops being true. **(2) Pins**: every action is pinned to a full commit SHA with its release in a trailing comment (`# v7.0.1`), which Dependabot keeps current; a tag can be moved by whoever controls the action's repository, a SHA cannot. **(3) Credentials**: every checkout sets `persist-credentials: false`; no job pushes, so nothing needed the token on disk. After the fixes the offline audit reports **0 findings**, and a new `Workflow audit (zizmor)` job runs it on every pull request, installed from PyPI at a pinned version rather than through a third-party action, with the token the online audits need to check that each pinned SHA really belongs to its repository. Three new tests in `supply-chain.test.ts` hold the same rules per workflow file (SHA plus release comment on every `uses:`, no persisted credentials, no pull-request-controlled `${{ }}` inside a `run:`); they fail on the workflows before this wave. **(4) Coverage**: `npm run test:coverage` (V8 provider) replaces `npm run test` in CI with a floor set from the measured baseline rounded down, not a round number: **64.56 % statements, 53.28 % branches, 54.46 % functions, 65.62 % lines** on 898 unit tests, floors 64 / 53 / 54 / 65. The figures cover unit tests only; the interface is mostly exercised by the e2e suite, which this does not measure. Cost: the quality job grows by about a minute. **What this wave deliberately does not do**: chase a coverage target, add e2e coverage instrumentation, or gate deploys on the audit job. 898 unit tests, 123 e2e. | Approved 09/10/2026 after V48. Continues V44: a security gate is worth its maintenance only when it distinguishes a real exposure, and every rule here is now enforced by a test or a job rather than by a reviewer remembering it. | -| **V50 — delivered** | **Metrics that agree with the reference on every edge.** Measured before anything changed, then checked against scikit-learn rather than against our own reading of it. **(1) Macro averages** ran over every class the task knows; scikit-learn averages over the labels present in `y_true` or `y_pred`. A perfect score on a batch holding two of three classes read **0.667** instead of **1.0**. Both implementations (`macroPrf` and the V45 decision policy) now follow scikit-learn, and the V42 generator gained an `absentClass` case (four classes declared, three present, one predicted but never true) that the old code failed. **(2) Rare classes**: the stratified split sent a class's only row to test, and the validation carve did the same to a two-row class, so the models never saw an example of either. Measured on 100 rows plus a rare class: 1 row gave test 1 / train 0, 2 rows gave test 1 / validation 1 / train 0. A single-row class now stays on the training side (1 → train 1; 2 → test 1, train 1; 3 and more unchanged, and the seeded draw of every other class is byte-identical). This is not hypothetical: three targets the demos offer have one-row classes (titanic → parch, cafe-sales → city, cafe-sales → product). On titanic → parch every model's macro F1 rises once the empty classes stop counting (GBDT 0.619 → 0.716, ensemble 0.422 → 0.548), and the decomposition shows the metric change alone moves nothing while every class is present: the gain comes from the split. The validation-picked winner moves from GBDT to the forest because the validation rows changed, not because a metric did. When every class has one row, nothing is left to test on and training refuses by name (`too-few-rows`) instead of reporting metrics over an empty set. **(3) Empty sets**: accuracy, log loss, RMSE, MAE, R² and the macro trio now return NaN on zero rows; before, RMSE 0, MAE 0 and R² 1 described a perfect model measured on nothing. No caller passes an empty set today (the test split keeps a row, batch scoring skips unlabeled files), so this is a contract for the next caller; the one theoretical path left, tuning on fewer than four rows, is noted rather than guarded. **What this wave deliberately does not do**: change any figure on a dataset whose classes all have three rows or more (every demo target used by a test or a tutorial), add a warning banner for rare classes, or guard k-fold tuning on tiny data. 906 unit tests, 123 e2e. | Approved 09/10/2026 after V49. The same rule as V35: a number must not flatter or punish by accident. Two of the three edges were reachable from the demo picker, and the reference that would have caught the first one existed since V42 but held no case with an absent class. | -| **V51 — delivered** | **A store that fits on a screen.** No behaviour change, and the proof is the suite: the 906 unit tests and the 97 Chromium e2e tests that passed before pass unchanged after. **(1) `lab-store.ts`, 1 050 → 530 lines.** The 250-line `onmessage` chain becomes a dispatch table in `store/responses.ts`, one entry per `WorkerResponse` kind, typed as a mapped type so the compiler refuses a kind with no handler and a handler for a kind that does not exist. The chain's last `else` used to catch every unlisted kind as an error silently; that safety net is now a compile error instead. The bodies were moved by script, not retyped. The state shape and starting values go to `store/state.ts`, V47's stale-response fence to `store/fence.ts`, the threshold artifact builder to `store/artifacts.ts`; `lab-store.ts` keeps the actions and re-exports its public names, so no importer changed. Feature slices were considered and not done: the actions share one worker and one `send`, and slicing them would have moved code without separating anything. **(2) Tests named after waves** renamed after what they pin: `v35` → `honest-evaluation`, `v36` → `imbalance-ensemble`, `v37` → `knn-and-helpers`, `v38` → `french-numbers`, `v39` → `column-recipe`, `v40` → `quality-score`. The two mixed files (`v35`, `v36`) were renamed, not split, to keep the change mechanical. **(3) Duplicated helpers**: `numbersOf`, `missingRatio` and the binned shares, copied identically in `drift.ts` and `reference.ts`, now live once in `quality/distribution.ts`. The two quantile functions were left apart on purpose: on an empty list one returns 0 and the other NaN, and merging them would have been a behaviour change. **Measured side effect**: the coverage gate from V49 fired. The 35 branches became 35 functions, so the function denominator rose from 2 142 to 2 174 while covered functions barely moved, and the floor of 54 % failed at 53.72 %. Rather than lowering the floor, a table-driven test (`store/responses.test.ts`, 17 cases) now pins which fields each state-only answer writes, which is also the check a moved body needs; function coverage is back to 54.46 %. **What this wave deliberately does not do**: change a single handler body, split the mixed test files, or unify the quantile functions. 923 unit tests, 123 e2e. | Approved 09/10/2026 after V50. Only after V47 to V50, as the plan required: the store's concurrency rules and the metrics were pinned by tests before their code moved, so a regression in the move would have failed loudly. | -| **V52 — pending** | **Major upgrades, one at a time, each measured.** Vitest 5, vite-plugin-pwa 2, TypeScript 7. Each in its own pull request with the before/after numbers that matter for it (suite time, build time, service-worker behaviour offline). | V44's rule: a major update keeps its migration cost visible. | +| Wave | Content | Why | +| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **V47 — delivered** | **Workers that cannot answer the wrong question.** Six lifecycle defects in shipped code, each reproduced by a test that failed before its fix. **(1) A result filed under a target it was not trained for**: `setTarget` and `toggleColumn` reset the training state without cancelling the run, the worker kept posting `model-result`, and the auto-save recorded the new target beside the old scores. The store now refuses both changes while anything computed for the current question is running (training, tuning, learning curve, robust ranking, batch scoring), the target picker and the column buttons are disabled for the same span, and a fence (`isStaleResponse`) drops any training message outside a run and any run-derived answer when no run is done; the worker answers in order, so the training status is enough to tell a late answer from a current one, without a run id in the protocol. **(2) Cancel did not reach the parallel phase**: V37's helpers were only checked after every one of them had finished, so cancelling waited for the heaviest families; the phase now polls the flag every 100 ms and terminates the helpers mid-fit. **(3) A crashed ML worker** left `trainStatus` at `training` forever and kept the dead reference; `onerror` now terminates it, clears every busy flag, and the next request starts a fresh worker. **(4) The same crash in the chat** left « thinking » up and still offered a local model that died with its worker. **(5) DuckDB was never closed**: each visit to the SQL panel left a worker and its Wasm heap behind, a failed start leaked its worker too, and a failed Parquet export left its file in the heap; the panel now closes the engine on unmount, on a late start and on failure, and export names come from a counter rather than a millisecond clock. **(6) A webcam stream granted after the page was left** kept the camera lit; it is now stopped on arrival. **(7) Found by this wave's own CI**: touching the chat ran the interpretation bench, which failed on `size-mismatch:tokenizer_config.json`. `prepare-llm.mjs` called its revision « pinned » while it held `main`, and upstream commit `1e94c62` (07/10/2026) had grown that file from 9 762 to 10 416 bytes; the revision is now the commit the sizes were measured on (`d6beae7`), which also keeps the next deploy cache miss from failing the same way. **What this wave deliberately does not do**: add a run id to the worker protocol (the order guarantee makes it redundant today, and it is the first thing to add if requests ever become concurrent), cancel analyses the user cannot cancel today, or touch the deeper share-link validation, which is V48. 883 unit tests, 123 e2e. | Approved 09/10/2026 after the post-V46 audit. The defect in (1) does not crash and does not warn: it saves a run that looks valid and is not, which is the one kind of failure an honest lab must never produce. The others are the same class of bug, a worker outliving the state it was started for, and fixing them together keeps the rule in one place. | +| **V48 — delivered** | **A damaged share link ends on a refusal, never on a crash.** The plan for this row was a deeper validator: check `insights.importance`, numeric metrics, `classes` and `confusion`, the fields the audit had named. The wave opened with a measurement instead, and the measurement changed the plan. Three real links were captured from the running app (titanic with uncertainty, threshold and segments; iris with k-NN tuning and groups; mpg as a regression) and every field of each was deleted, nulled, or retyped as a string, a number, an object or an array: **3 948 damaged links, 3 660 accepted by the decoder, 1 020 of those crashed the page, on 99 distinct paths**, from `insights.importance` to `artifacts.exploration.clusters[].traits[].clusterMean`. A validator for 99 paths would be the largest file in the share module and would drift with every analysis a run learns to carry. So the decoder keeps checking what every view reads first, and `SharedRunPage` wraps the run in a render guard: any failure while drawing it shows the same refusal page, with its own explanation (the link decoded, but part of the run does not have the shape LabML writes) and nothing from the run, so no half-read figure can pass for a real one. The three links are committed under `tests/share-links/`, and the measurement is now a test: every field shape is damaged six ways and the page must end on the run or on the refusal; it fails on the code before this wave and adds about 25 s to the unit suite, the price of covering the whole format rather than a sample. The five copies of the download helper become one, `src/lib/download.ts`, which revokes its object URL after the click has been handled instead of in the same tick, where some engines cancel the download silently. **What this wave deliberately does not do**: write a field-by-field schema for every artifact, show a partially readable run, or guard event handlers (a click on a damaged run is not reachable: the refusal replaces it first). 889 unit tests, 123 e2e. | Approved 09/10/2026 after V47. A link is input from a stranger, and the most common stranger is a chat client that cut it. The decoder already refused the shapes the V35 audit had found; the measurement showed it could not keep up with the format, and that the robust answer was at the point of failure rather than at the gate. | +| **V49 — delivered** | **CI that cannot be steered by a branch name.** The wave opened with zizmor 1.30.1 on the three workflows: **28 findings** (1 template injection, 21 actions pinned by a movable tag, 6 checkouts leaving the git token on the runner disk). **(1) Injection**: the deploy step expanded `github.head_ref` inside its script, so a branch named `x";…;"` would have run as code; the value now reaches the shell through `env:`. Low severity today (only branches of this repository deploy) and fixed before that stops being true. **(2) Pins**: every action is pinned to a full commit SHA with its release in a trailing comment (`# v7.0.1`), which Dependabot keeps current; a tag can be moved by whoever controls the action's repository, a SHA cannot. **(3) Credentials**: every checkout sets `persist-credentials: false`; no job pushes, so nothing needed the token on disk. After the fixes the offline audit reports **0 findings**, and a new `Workflow audit (zizmor)` job runs it on every pull request, installed from PyPI at a pinned version rather than through a third-party action, with the token the online audits need to check that each pinned SHA really belongs to its repository. Three new tests in `supply-chain.test.ts` hold the same rules per workflow file (SHA plus release comment on every `uses:`, no persisted credentials, no pull-request-controlled `${{ }}` inside a `run:`); they fail on the workflows before this wave. **(4) Coverage**: `npm run test:coverage` (V8 provider) replaces `npm run test` in CI with a floor set from the measured baseline rounded down, not a round number: **64.56 % statements, 53.28 % branches, 54.46 % functions, 65.62 % lines** on 898 unit tests, floors 64 / 53 / 54 / 65. The figures cover unit tests only; the interface is mostly exercised by the e2e suite, which this does not measure. Cost: the quality job grows by about a minute. **What this wave deliberately does not do**: chase a coverage target, add e2e coverage instrumentation, or gate deploys on the audit job. 898 unit tests, 123 e2e. | Approved 09/10/2026 after V48. Continues V44: a security gate is worth its maintenance only when it distinguishes a real exposure, and every rule here is now enforced by a test or a job rather than by a reviewer remembering it. | +| **V50 — delivered** | **Metrics that agree with the reference on every edge.** Measured before anything changed, then checked against scikit-learn rather than against our own reading of it. **(1) Macro averages** ran over every class the task knows; scikit-learn averages over the labels present in `y_true` or `y_pred`. A perfect score on a batch holding two of three classes read **0.667** instead of **1.0**. Both implementations (`macroPrf` and the V45 decision policy) now follow scikit-learn, and the V42 generator gained an `absentClass` case (four classes declared, three present, one predicted but never true) that the old code failed. **(2) Rare classes**: the stratified split sent a class's only row to test, and the validation carve did the same to a two-row class, so the models never saw an example of either. Measured on 100 rows plus a rare class: 1 row gave test 1 / train 0, 2 rows gave test 1 / validation 1 / train 0. A single-row class now stays on the training side (1 → train 1; 2 → test 1, train 1; 3 and more unchanged, and the seeded draw of every other class is byte-identical). This is not hypothetical: three targets the demos offer have one-row classes (titanic → parch, cafe-sales → city, cafe-sales → product). On titanic → parch every model's macro F1 rises once the empty classes stop counting (GBDT 0.619 → 0.716, ensemble 0.422 → 0.548), and the decomposition shows the metric change alone moves nothing while every class is present: the gain comes from the split. The validation-picked winner moves from GBDT to the forest because the validation rows changed, not because a metric did. When every class has one row, nothing is left to test on and training refuses by name (`too-few-rows`) instead of reporting metrics over an empty set. **(3) Empty sets**: accuracy, log loss, RMSE, MAE, R² and the macro trio now return NaN on zero rows; before, RMSE 0, MAE 0 and R² 1 described a perfect model measured on nothing. No caller passes an empty set today (the test split keeps a row, batch scoring skips unlabeled files), so this is a contract for the next caller; the one theoretical path left, tuning on fewer than four rows, is noted rather than guarded. **What this wave deliberately does not do**: change any figure on a dataset whose classes all have three rows or more (every demo target used by a test or a tutorial), add a warning banner for rare classes, or guard k-fold tuning on tiny data. 906 unit tests, 123 e2e. | Approved 09/10/2026 after V49. The same rule as V35: a number must not flatter or punish by accident. Two of the three edges were reachable from the demo picker, and the reference that would have caught the first one existed since V42 but held no case with an absent class. | +| **V51 — delivered** | **A store that fits on a screen.** No behaviour change, and the proof is the suite: the 906 unit tests and the 97 Chromium e2e tests that passed before pass unchanged after. **(1) `lab-store.ts`, 1 050 → 530 lines.** The 250-line `onmessage` chain becomes a dispatch table in `store/responses.ts`, one entry per `WorkerResponse` kind, typed as a mapped type so the compiler refuses a kind with no handler and a handler for a kind that does not exist. The chain's last `else` used to catch every unlisted kind as an error silently; that safety net is now a compile error instead. The bodies were moved by script, not retyped. The state shape and starting values go to `store/state.ts`, V47's stale-response fence to `store/fence.ts`, the threshold artifact builder to `store/artifacts.ts`; `lab-store.ts` keeps the actions and re-exports its public names, so no importer changed. Feature slices were considered and not done: the actions share one worker and one `send`, and slicing them would have moved code without separating anything. **(2) Tests named after waves** renamed after what they pin: `v35` → `honest-evaluation`, `v36` → `imbalance-ensemble`, `v37` → `knn-and-helpers`, `v38` → `french-numbers`, `v39` → `column-recipe`, `v40` → `quality-score`. The two mixed files (`v35`, `v36`) were renamed, not split, to keep the change mechanical. **(3) Duplicated helpers**: `numbersOf`, `missingRatio` and the binned shares, copied identically in `drift.ts` and `reference.ts`, now live once in `quality/distribution.ts`. The two quantile functions were left apart on purpose: on an empty list one returns 0 and the other NaN, and merging them would have been a behaviour change. **Measured side effect**: the coverage gate from V49 fired. The 35 branches became 35 functions, so the function denominator rose from 2 142 to 2 174 while covered functions barely moved, and the floor of 54 % failed at 53.72 %. Rather than lowering the floor, a table-driven test (`store/responses.test.ts`, 17 cases) now pins which fields each state-only answer writes, which is also the check a moved body needs; function coverage is back to 54.46 %. **What this wave deliberately does not do**: change a single handler body, split the mixed test files, or unify the quantile functions. 923 unit tests, 123 e2e. | Approved 09/10/2026 after V50. Only after V47 to V50, as the plan required: the store's concurrency rules and the metrics were pinned by tests before their code moved, so a regression in the move would have failed loudly. | +| **V52 — pending** | **Major upgrades, one at a time, each measured.** Vitest 5, vite-plugin-pwa 2, TypeScript 7. Each in its own pull request with the before/after numbers that matter for it (suite time, build time, service-worker behaviour offline). | V44's rule: a major update keeps its migration cost visible. | +| **V52.1 — delivered** | **Vitest 5, measured against Vitest 4 on the same commit.** The first of V52's three majors, each in its own pull request. The migration guide was read first and the repository searched for every pattern it names: no `toThrow('')`, no `sequential`, no `bench`, no removed `vitest/*` entrypoint, every `vi.mock` already at the top level of its file. Two changes could still have moved results silently, and both were measured rather than assumed. **`clearMocks` now defaults to `true`**: the 924 unit tests pass unchanged on the first run, so no test relied on calls accumulating across tests. **Coverage paths are now matched relative to the root**: the four figures move by rounding only (statements 64.93 → 64.92 %, lines 66.06 → 66.05 %, branches and functions identical; five statements fewer in the denominator), every V49 floor still met. Timing on the same machine stays inside its own noise: three coverage runs on Vitest 4 took 124 to 143 s, three on Vitest 5 131 to 134 s; plain runs 82 to 87 s on both. **Node**: Vitest 5 requires Node 22.12, so `engines` moves from `>=20` to `>=22.12` and the README says so; CI and `.nvmrc` were already on 22. **What this sub-wave deliberately does not do**: adopt Vitest 5's new options (`sharedViteServer`, artifact directory) or touch the other two majors. 924 unit tests, 123 e2e. | Approved 09/10/2026 with V52. The safest of the three majors goes first: the suite it runs is also the instrument that proves it changed nothing. | V12 (consented generative chat) remains pending a product decision. No wave starts without an explicit launch command. diff --git a/README.md b/README.md index 7707f8f..a3a7641 100644 --- a/README.md +++ b/README.md @@ -278,7 +278,7 @@ Cloudflare Pages ## Getting started -Requires Node 20+. +Requires Node 22.12+ (the floor Vitest 5 sets; CI runs Node 22). ```bash npm ci # install dependencies diff --git a/package-lock.json b/package-lock.json index 9363ef3..cf45794 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "labml", - "version": "1.51.0", + "version": "1.52.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "labml", - "version": "1.51.0", + "version": "1.52.0", "license": "MIT", "dependencies": { "@duckdb/duckdb-wasm": "1.28.0", @@ -48,7 +48,7 @@ "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", "@vitejs/plugin-react": "^6.1.2", - "@vitest/coverage-v8": "^4.1.11", + "@vitest/coverage-v8": "^5.0.3", "eslint": "^10.12.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-react-hooks": "^7.1.1", @@ -65,7 +65,7 @@ "vite": "^8.3.3", "vite-plugin-pwa": "^1.3.0", "vite-plugin-static-copy": "^4.1.1", - "vitest": "^4.1.11", + "vitest": "^5.0.3", "wrangler": "^4.147.0", "yaml": "^2.9.1" }, @@ -4071,9 +4071,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", "dev": true, "license": "MIT" }, @@ -5322,13 +5322,6 @@ "dev": true, "license": "CC0-1.0" }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, "node_modules/@tailwindcss/node": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", @@ -6192,29 +6185,27 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.11.tgz", - "integrity": "sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-5.0.3.tgz", + "integrity": "sha512-+klsyz7BvT1vCU28Zkfzms1Ia78XD0V41U3FUtRaa3S+vOr/EXvK1O6BvVD7l1RzEjm6SONR2aybOvDDf9u0mQ==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.11", - "ast-v8-to-istanbul": "^1.0.0", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.2", - "obug": "^2.1.1", - "std-env": "^4.0.0-rc.1", - "tinyrainbow": "^3.1.0" + "@vitest/istanbul-lib-coverage": "^1.0.0", + "@vitest/istanbul-lib-report": "^1.0.0", + "ast-v8-to-istanbul": "^1.0.5", + "magicast": "^0.5.4", + "obug": "^2.1.4", + "std-env": "^4.2.0", + "tinyrainbow": "^3.1.1" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.11", - "vitest": "4.1.11" + "@vitest/browser": "5.0.3", + "vitest": "5.0.3" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -6222,34 +6213,40 @@ } } }, - "node_modules/@vitest/expect": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", - "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", + "node_modules/@vitest/istanbul-lib-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@vitest/istanbul-lib-coverage/-/istanbul-lib-coverage-1.0.2.tgz", + "integrity": "sha512-9J/JMwOf9AoJhAywhrn7ScKTL38hsWQP/qPG60OtaAFcQ5OXPwKsxZFlbnuCKmZ61m8/lGgHYnFpdyQZUvG/iA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@vitest/istanbul-lib-report": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@vitest/istanbul-lib-report/-/istanbul-lib-report-1.0.2.tgz", + "integrity": "sha512-gUsfXZJbzPamoIY5TvHFiMMoXESBrUMo+xqaj+rYrWI69+EnvRlYBlP96ZnHPY4vX8kyUpgAnFUCg5wZG/HkDQ==", "dev": true, "license": "MIT", "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" + "@vitest/istanbul-lib-coverage": "1.0.2" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">=22" } }, "node_modules/@vitest/mocker": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", - "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.3.tgz", + "integrity": "sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.11", + "@jridgewell/trace-mapping": "0.3.31", + "@vitest/spy": "5.0.3", "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" + "magic-string": "^1.2.3" }, "funding": { "url": "https://opencollective.com/vitest" @@ -6267,70 +6264,22 @@ } } }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", - "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", - "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.11", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", - "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", + "node_modules/@vitest/mocker/node_modules/magic-string": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.3.tgz", + "integrity": "sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.11", - "@vitest/utils": "4.1.11", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "@jridgewell/sourcemap-codec": "^1.6.0" } }, "node_modules/@vitest/spy": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", - "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", - "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.3.tgz", + "integrity": "sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==", "dev": true, "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.11", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, "funding": { "url": "https://opencollective.com/vitest" } @@ -7122,9 +7071,9 @@ "license": "CC-BY-4.0" }, "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", "dev": true, "license": "MIT", "engines": { @@ -9597,13 +9546,6 @@ "node": "^22.13.0 || >=24.0.0" } }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, "node_modules/html-parse-stringify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-4.0.1.tgz", @@ -10426,97 +10368,6 @@ "whatwg-fetch": "^3.4.1" } }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/istanbul-lib-report/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jackspeak": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", @@ -13690,13 +13541,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -13824,13 +13668,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -14286,11 +14123,14 @@ "license": "MIT" }, "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.1.tgz", + "integrity": "sha512-IAbQaPJIIdhVxi0mqy9lez1wEwje0UQf9F1vwtbelEG5Nbgn0nPOxCvU+mNeNS9gyJTAhrHp74CbUaSynQpZcQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } }, "node_modules/tinyexec": { "version": "1.3.1", @@ -15329,38 +15169,31 @@ } }, "node_modules/vitest": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", - "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.3.tgz", + "integrity": "sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.11", - "@vitest/mocker": "4.1.11", - "@vitest/pretty-format": "4.1.11", - "@vitest/runner": "4.1.11", - "@vitest/snapshot": "4.1.11", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" + "@types/chai": "^5.2.2", + "@vitest/mocker": "5.0.3", + "chai": "^6.2.2", + "es-module-lexer": "^2.3.2", + "expect-type": "^1.4.0", + "magic-string": "^1.2.3", + "obug": "^2.1.4", + "picomatch": "^4.0.7", + "std-env": "^4.2.0", + "tinybench": "^6.1.4", + "tinyexec": "^1.3.0", + "tinyglobby": "^0.2.17", + "why-is-node-running": "3.2.1" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^22.12.0 || ^24.0.0 || >=26.0.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -15368,16 +15201,16 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.11", - "@vitest/browser-preview": "4.1.11", - "@vitest/browser-webdriverio": "4.1.11", - "@vitest/coverage-istanbul": "4.1.11", - "@vitest/coverage-v8": "4.1.11", - "@vitest/ui": "4.1.11", + "@types/node": "^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "5.0.3", + "@vitest/browser-preview": "5.0.3", + "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", + "@vitest/coverage-istanbul": "5.0.3", + "@vitest/coverage-v8": "5.0.3", + "@vitest/ui": "5.0.3", "happy-dom": "*", "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { @@ -15418,6 +15251,16 @@ } } }, + "node_modules/vitest/node_modules/magic-string": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.3.tgz", + "integrity": "sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, "node_modules/w3c-xmlserializer": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-6.0.0.tgz", @@ -15593,20 +15436,16 @@ } }, "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-3.2.1.tgz", + "integrity": "sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==", "dev": true, "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, "bin": { "why-is-node-running": "cli.js" }, "engines": { - "node": ">=8" + "node": ">=20.11" } }, "node_modules/word-wrap": { diff --git a/package.json b/package.json index 50d26f7..33289ad 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "labml", "private": true, - "version": "1.51.0", + "version": "1.52.0", "license": "MIT", "type": "module", "engines": { - "node": ">=20" + "node": ">=22.12" }, "scripts": { "dev": "vite", @@ -75,7 +75,7 @@ "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", "@vitejs/plugin-react": "^6.1.2", - "@vitest/coverage-v8": "^4.1.11", + "@vitest/coverage-v8": "^5.0.3", "eslint": "^10.12.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-react-hooks": "^7.1.1", @@ -92,7 +92,7 @@ "vite": "^8.3.3", "vite-plugin-pwa": "^1.3.0", "vite-plugin-static-copy": "^4.1.1", - "vitest": "^4.1.11", + "vitest": "^5.0.3", "wrangler": "^4.147.0", "yaml": "^2.9.1" },