Repository navigation
Release 3.5.0 - #1801
Release 3.5.0#1801
Conversation
### Context [HF-353](https://app.clickup.com/t/9015210959/HF-353) started from one symptom: the official HyperFormula skill for Claude Code was documented only on the [Set up your coding agent](https://hyperformula.handsontable.com/docs/guide/setup-coding-agent.html) page, and searching the docs for `skill` returned nothing. The cause is general, not specific to that page. The docs search box builds its match domain from a page's **title + `tags` frontmatter + `h2`/`h3` heading titles** only ([`docs/.vuepress/plugins/search-box/match-query.js`](docs/.vuepress/plugins/search-box/match-query.js)) — body text is never indexed. Until now **no page in the repository used `tags`**, so every page was findable only by the words it happened to put in a heading. A reader who types the Excel term, the error value, the method name or the abbreviation found nothing. Changes: - **Every guide page** (55 of them) now carries a `tags` list of the words a reader really types: the Excel or Google Sheets term (`GSheets`, `MS Excel`, `defined names`), the literal error value (`#REF!`, `#DIV/0!`), the API method (`buildFromArray`, `setRowOrder`, `registerFunctionPlugin`), the abbreviation (`CLA`, `GPL`, `CRUD`, `SSR`, `IE11`, `UDF`), the third-party name (`ExcelJS`, `Pinia`, `Chevrotain`, `Context7`) and the plain-language word for the problem (`slow`, `memory`, `division by zero`, `quickstart`). - **`docs/index.md`** and **`README.md`** — one paragraph at the end of "Installation and usage" mentioning the skill and linking to the coding-agent guide. The two files are hand-maintained mirrors of one page, so the paragraph went into both. - **`docs/guide/client-side-installation.md`** and **`docs/guide/server-side-installation.md`** — both installation guides now close with a short "Set up your coding agent" section pointing at the same guide. These are the pages a reader actually lands on when installing, and neither repeats the skill's setup commands. - **`README.md`** (same page, drive-by) — its 22 documentation links, and the one in `.github/pull_request_template.md`, still used the pre-Cloudflare `hyperformula.handsontable.com/guide/<slug>.html` form. The docs are built under `/docs/` and served from `https://hyperformula.handsontable.com/docs/` (`docs/README.md`, `docs/.vuepress/build.config.js`) — the form `src/interpreter/functionMetadata/**` already uses — so all of them now carry the `/docs/` prefix. - **`DOCS_CONTENT_GUIDE.md`** — documents `tags` in "VuePress conventions": what it is for, the 10-tag limit, why a tag ten pages claim is worthless, and that it must be a YAML **list** (a plain string makes `match-query.js` call `.join()` on a string and breaks the search box site-wide). The tags follow four rules: 1. **At most 10 per page**, fewer where fewer will do — the range is 2 to 10, 353 in total. 2. **Never a word the page's own title or headings already match** — those match anyway, so such a tag buys nothing. This is why `setup-coding-agent.md` lost `Cursor`, `Copilot` and `Claude Code` (all in its own headings) and `LLM` (a substring of `llms.txt`) when it was trimmed to the same limit. 3. **Never a generic word the page is not the best destination for**, remembering that a multi-word tag is matched word by word, so `rules file` would drag the page into results for `file` and `rules`. 4. **Only what the page truly answers** — nothing tags a feature it does not document. `timezone`, for instance, is tagged nowhere, because no page covers time zones. `tags` has exactly one consumer in the repo (that search plugin), and frontmatter is stripped from the `.md` companions and `llms-full.txt`, so nothing outside the search index changes. Tags on `built-in-functions.tmpl.md` reach the generated built-in functions page, which `script/generate-builtin-functions-doc.ts` splices from that template. ### How did you test your changes? **The search behaviour.** `npm run docs:build` cannot run in the authoring environment (no `node_modules`, no registry access), so this was tested by **executing the shipped search code** — `match-query.js` and `SearchBox.vue`'s `suggestions` / `suggestionsFromCategory` logic, loaded through `vm` with `lodash/get` stubbed — against page objects built from the real Markdown sources the way `@vuepress/core` 1.9.10 does (title from frontmatter or the first `H1`, headers from `h2`/`h3` only, frontmatter from the YAML block). Results over the whole tag set: | check | result | | --- | --- | | tag queries run (every tag of every page) | 353 | | tags that fail to surface their own page | **0** | | tags returning more than 6 rows (limit is 10, unranked) | **0** | | `skill` → the coding-agent page | `Guides: Set up your coding agent` (was: no results) | Spot checks land where they should, page-level or deep-linked: `#REF!` → `Cell references > The #REF! error` and `Types of errors`; `CLA` → `Contributing`; `GPL` → `License key`, `Licensing > GPLv3 license`; `IE11` → `Supported browsers`; `buildFromArray` → `Basic usage`; `Pinia` → `Integration with Vue`; `modulo` → `Types of operators`; `trace precedents` → `Dependency graph`; `division by zero` → `Types of errors`. The error values are quoted in YAML (`- "#REF!"`) on purpose: unquoted, `#` starts a comment, and the unprefixed `REF!` would miss the `#REF!` a reader actually types. The quoted form matches `ref`, `REF!` and `#REF!` alike. **The rest.** - Every frontmatter block parses with a real YAML parser — `tags` reads back as a list of strings on all 56 pages, and no page exceeds 10. - The applier refuses to write a tag list that breaks an invariant (over 10, duplicates, a tag that is a substring of another on the same page, an unquotable value), so none of those can slip in. - `build-docs` (a real `npm run docs:build`) and the Cloudflare preview deployment both passed on the commit carrying all 55 tagged pages, which is the authoritative check that VuePress accepts this frontmatter. - The new sections on the two installation guides keep `skill` pointed at the coding-agent page (still a single row for that query); `coding agent` now returns three rows — the guide itself plus the two new deep links. - Every rewritten README link was checked against the docs sources: all 21 distinct URLs resolve to an existing page, and the two anchors in use (`#install-with-npm-or-yarn`, `#demo`) exist. - README.md and docs/index.md were diffed under the link-form normalisation and are line-for-line identical apart from the docs page's frontmatter and its leading `<br>` spacing. - The diff is Markdown and YAML only, so no lint, unit test or build step is affected; each edited page keeps its own line-wrapping convention, no trailing whitespace, no CRLF. Worth a reviewer's eye: 1. `Codex` and `Windsurf` are tagged on the coding-agent page although it never names them — they fall under its "Cursor, Copilot & other agents" section, and pointing such a user at `llms-full.txt` is exactly the advice they need. 2. Four pages share `SSR` and four share `breaking changes`; three share the AI-integration vocabulary (`AI agents`, `LLM`, `tool calling`). Each genuinely answers those queries, and the row counts stay well under the limit, but it is the kind of thing to keep an eye on as more tags are added. 3. `docs/index.md` is deliberately untagged: it has no `H1`, so VuePress infers no title for it and its search rows would be labelled by path. ### Types of changes - [ ] Breaking change (a fix or a feature because of which an existing functionality doesn't work as expected anymore) - [ ] New feature or improvement (a non-breaking change that adds functionality) - [ ] Bug fix (a non-breaking change that fixes an issue) - [ ] Additional language file, or a change to an existing language file (translations) - [x] Change to the documentation ### Related issues: 1. [HF-353](https://app.clickup.com/t/9015210959/HF-353) ### Checklist: - [x] I have reviewed the guidelines about [Contributing to HyperFormula](https://hyperformula.handsontable.com/docs/guide/contributing.html) and I confirm that my code follows the code style of this project. - [ ] I have signed the [Contributor License Agreement](https://goo.gl/forms/yuutGuN0RjsikVpM2). - [ ] My change is compliant with the [OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html) standard. - [ ] My change is compatible with Microsoft Excel. - [ ] My change is compatible with Google Sheets. - [ ] I described my changes in the [CHANGELOG.md](https://github.com/handsontable/hyperformula/blob/master/CHANGELOG.md) file. - [x] My changes require a documentation update. - [ ] My changes require a migration guide. Deliberately left out: no `CHANGELOG.md` / release-notes entry (documentation-only change, and the 3.4.0 mention of the coding-agent page was dropped from this PR on request). Four links of the old `/guide/` form survive in `CHANGELOG.md` (3) and `docs/guide/release-notes.md` (1) — those two files were off limits here, so they are worth a separate one-line fix. --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…1751) ### Context Follow-up to [#1750](#1750), which re-pointed the documentation links in `README.md` and the pull request template but deliberately left `CHANGELOG.md` and `docs/guide/release-notes.md` untouched. These are the four links that were left behind. All four used the pre-Cloudflare `hyperformula.handsontable.com/guide/<slug>.html` form. The documentation is built under `/docs/` and served from `https://hyperformula.handsontable.com/docs/` (`docs/README.md`, `docs/.vuepress/build.config.js`) — the form the rest of the repository already uses — so each now carries the `/docs/` prefix: | file | link | | --- | --- | | `CHANGELOG.md` (2.0.2 → Removed) | `supported-browsers.html` | | `CHANGELOG.md` (2.0.0 header) | `release-notes.html` | | `CHANGELOG.md` (2.0.0 header) | `migration-from-1.0-to-2.0.html` → `migration-from-1.x-to-2.0.html` | | `docs/guide/release-notes.md` (2.0.2 → Removed) | `supported-browsers.html` | The 2.0.0 migration guide link needed more than the prefix. `c27a9d5` ("Fix language files for Node+ESM (#1377)") renamed `docs/guide/migration-from-1.0-to-2.0.md` to `docs/guide/migration-from-1.x-to-2.0.md` and did not update the link, so it pointed at a page that does not exist under either prefix. It now uses the current slug. `script/release/release.sh` generates the release notes from the changelog section verbatim, so both files keep the same absolute URL and stay mirrors of each other. ### How did you test your changes? - Every rewritten URL was checked against the sources: `docs/guide/supported-browsers.md`, `docs/guide/release-notes.md` and `docs/guide/migration-from-1.x-to-2.0.md` all exist; the old `migration-from-1.0-to-2.0` slug does not, which `git log --diff-filter=R` confirms was a rename in `c27a9d5`. - A repository-wide grep for the old `hyperformula.handsontable.com/guide/` and `/api/` forms now returns nothing in either file, and nothing anywhere else in the repository. - Markdown-only change to two documentation files, so no lint, unit test or build step is affected; both files keep their existing wrapping and no other line is touched. ### Types of changes - [ ] Breaking change (a fix or a feature because of which an existing functionality doesn't work as expected anymore) - [ ] New feature or improvement (a non-breaking change that adds functionality) - [ ] Bug fix (a non-breaking change that fixes an issue) - [ ] Additional language file, or a change to an existing language file (translations) - [x] Change to the documentation ### Related issues: 1. Follow-up to [#1750](#1750) ([HF-353](https://app.clickup.com/t/9015210959/HF-353)) ### Checklist: - [x] I have reviewed the guidelines about [Contributing to HyperFormula](https://hyperformula.handsontable.com/docs/guide/contributing.html) and I confirm that my code follows the code style of this project. - [ ] I have signed the [Contributor License Agreement](https://goo.gl/forms/yuutGuN0RjsikVpM2). - [ ] My change is compliant with the [OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html) standard. - [ ] My change is compatible with Microsoft Excel. - [ ] My change is compatible with Google Sheets. - [ ] I described my changes in the [CHANGELOG.md](https://github.com/handsontable/hyperformula/blob/master/CHANGELOG.md) file. - [ ] My changes require a documentation update. - [ ] My changes require a migration guide. No changelog entry: this only corrects links inside the changelog and the release notes, and the repository does not require an entry for a documentation-only change. The edited entries belong to already-released versions, so their wording is unchanged — only the URLs move. --- _Generated by [Claude Code](https://claude.ai/code/session_01AXFK3TQ32DUAP3LR2T9b5q)_ <!-- CURSOR_SUMMARY --> --- > [!NOTE] > <sup>[Cursor Bugbot](https://cursor.com/bugbot) is generating a summary for commit fac749e. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…#1752) ### Context `MOD` returned the result of JavaScript's `%` operator, which is the **truncated** remainder and takes the sign of the **dividend**. Excel, Google Sheets and the OpenDocument specification define `MOD` as the **floored** remainder, which takes the sign of the **divisor**. The two agree whenever the arguments share a sign and differ by exactly one divisor when they do not, so every mixed-sign call was wrong: | Formula | Excel / Google Sheets | Before | After | |---|---|---|---| | `=MOD(-3, 12)` | `9` | `-3` | `9` | | `=MOD(5, -3)` | `-1` | `2` | `-1` | | `=MOD(7, 3)` | `1` | `1` | `1` | | `=MOD(-7, -3)` | `-1` | `-1` | `-1` | Same-sign arguments and `=MOD(x, 0)` → `#DIV/0!` were already correct and are unchanged. **Why `%` plus a correction, and not a one-liner.** Both textbook formulas are too lossy for a calculation engine, so `ModuloPlugin` shifts the remainder given by `%` (which is exact for IEEE 754 doubles) only when its sign disagrees with the divisor's. The rejected alternatives, with the concrete failures now pinned by tests: - `dividend - divisor * Math.floor(dividend / divisor)` rounds twice and the multiplication scales the division's error back up — `MOD(1e308, 3)` returns `0` instead of `2`, and a dividend of `Number.MAX_VALUE` overflows to `-Infinity`. - `((dividend % divisor) + divisor) % divisor` loses a remainder that is negligible next to the divisor — `MOD(1e-20, 3)` returns `0` instead of `1e-20` — and overflows to `NaN` once the intermediate sum exceeds `Number.MAX_VALUE`. The reasoning is recorded in the JSDoc on `flooredRemainder` so the next reader does not "simplify" it back. **Docs and metadata.** `MOD` is removed from the list of differences (row and root-cause bullet) and from the deviations listed in `DEV_DOCS.md`, which both asserted the old behaviour. The sign contract moves out of the `divisor` parameter description and into `shortDescription`, because `script/renderBuiltinFunctionsTable.ts` renders only name, short description and syntax — parameter descriptions never reach the generated guide page, so the contract was invisible there before. ### How did you test your changes? Tests live in the private suite, per `DEV_DOCS.md`: **handsontable/hyperformula-tests#46**, on a branch of this same name, so `fetch-tests.sh` pairs the two and this PR's CI runs them. That PR takes `unit/interpreter/function-modulo.spec.ts` from 4 cases to 38 (one assertion each) and corrects one stale golden value in `unit/function-metadata-api.spec.ts` — see it for the full breakdown. The old spec covered only same-sign arguments (`5,2`, `36,6`, `10.5,3`), which is why this went unnoticed. The new cases add opposite signs in both positions, exact division in all four sign combinations, zero and fractional arguments, coercion, and the precision boundaries that separate the correct implementation from the plausible-but-wrong ones. Mutation-checked — the suite is not merely green, it discriminates: | Implementation | Failures out of 38 | |---|---| | The fix | **0** | | Old `dividend % divisor` | 13 | | `dividend - divisor * Math.floor(dividend / divisor)` | 5 | | `((dividend % divisor) + divisor) % divisor` | 3 | | The fix without its exact-division guard | 4 | Run locally in the exact CI arrangement (this branch, with the tests repo checked out at `test/hyperformula-tests`): ``` Test Suites: 502 passed, 502 total Tests: 3 skipped, 6214 passed, 6217 total ``` Other gates: `npx tsc --noEmit` clean; `eslint . --ext .js,.ts` 0 errors, and 0 new warnings in `src/` (the file's 2 warnings for a missing class/method JSDoc are pre-existing and shared with every sibling plugin). `npm run docs:generate-function-docs` regenerates cleanly, and the resulting `MOD` row reads *"Returns the remainder when one number is divided by another. The result has the same sign as divisor. | MOD(dividend, divisor)"*. ### Types of changes - [ ] Breaking change (a fix or a feature because of which an existing functionality doesn't work as expected anymore) - [ ] New feature or improvement (a non-breaking change that adds functionality) - [x] Bug fix (a non-breaking change that fixes an issue) - [ ] Additional language file, or a change to an existing language file (translations) - [x] Change to the documentation ### Related issues: 1. Fixes #1747 2. HF-357 3. Tests: handsontable/hyperformula-tests#46 ### Checklist: - [x] I have reviewed the guidelines about [Contributing to HyperFormula](https://hyperformula.handsontable.com/docs/guide/contributing.html) and I confirm that my code follows the code style of this project. - [ ] I have signed the [Contributor License Agreement](https://goo.gl/forms/yuutGuN0RjsikVpM2). - [x] My change is compliant with the [OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html) standard. - [x] My change is compatible with Microsoft Excel. - [x] My change is compatible with Google Sheets. - [x] I described my changes in the [CHANGELOG.md](https://github.com/handsontable/hyperformula/blob/master/CHANGELOG.md) file. - [x] My changes require a documentation update. - [ ] My changes require a migration guide. ### Notes for the reviewer Three judgement calls to confirm, none of them blocking: 1. **"Breaking change" left unticked.** Returned values do change for anyone relying on mixed-sign `MOD`, and there is no config escape hatch (only a custom-function reimplementation). I followed the closest precedent instead: the empty-cell `MATCH`/`VLOOKUP` fix shipped in 3.4.0 as a plain `### Fixed` while also editing the list of differences. Ticking the box would mean creating a migration guide, and no `migration-from-3.x` guide exists yet. Happy to reclassify. 2. **A knock-on change at an unrepresentable divisor.** A divisor that overflows to infinity is reachable through string coercion, and `=MOD(-10, "1e400")` now returns `#NUM!` where it used to return `-10`. That follows from the definition — for a positive divisor the floored result must lie in `[0, divisor)`, so the only answer here is unrepresentable — and `-10` was exactly the truncated-remainder bug. Pinned by two tests rather than left to chance; say the word if you would rather special-case non-finite divisors. 3. **`QUOTIENT` is deliberately untouched.** The identity `a = b * QUOTIENT(a, b) + MOD(a, b)` no longer holds for mixed signs, because `QUOTIENT` truncates. Excel has exactly the same inconsistency, so changing it would break Excel parity. Separately, while testing this I found a **pre-existing, unrelated bug**, now filed as [HF-358](https://app.clickup.com/t/86cbb9ufr): negative zero leaks through the array/range path for `MOD`, `ROUND`, `INT` and `*` alike (`Interpreter.evaluateAst` applies `fixNegativeZero` only when `isExtendedNumber(val)`, which is false for a `SimpleRangeValue`). It predates this change and affects the same inputs before and after it. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Mixed-sign `MOD` results change for any workbook that depended on the old dividend-sign semantics; impact is limited to that function but can alter live spreadsheet calculations. > > **Overview** > **`MOD`** no longer uses JavaScript’s `%` operator. It now returns the **floored** remainder whose sign matches the **divisor**, matching Excel, Google Sheets, and OpenDocument (e.g. `=MOD(-3, 12)` is **9**, not **-3**). Same-sign inputs and `#DIV/0!` for a zero divisor are unchanged. > > `ModuloPlugin` routes non-zero divisors through a new **`flooredRemainder`** helper that adjusts `%` only when the truncated remainder’s sign disagrees with the divisor’s, avoiding less stable one-liner formulas at extreme magnitudes. JSDoc on that helper records why. > > Docs follow the behaviour change: **CHANGELOG** entry, **`MOD`** removed from the Excel/Sheets differences table and related prose in **`DEV_DOCS.md`**, and function catalogue **`shortDescription`** updated so the divisor-sign rule appears in generated built-in function docs. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 9f423a6. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude <noreply@anthropic.com>
### Context
`MAXPOOL` and `MEDIANPOOL` computed the output array size without
checking that the pooling window actually tiles the input range. When
the range dimensions, reduced by the window size, were not whole
multiples of the stride, the kernel read past the last row of the input,
and an uncaught `TypeError` escaped the interpreter and the public API:
```ts
const hf = HyperFormula.buildEmpty({licenseKey: 'gpl-v3'})
const id = hf.getSheetId(hf.addSheet('S'))
hf.setSheetContent(id, [[3, 1, 2], [9, 7, 8], [5, 4, 6]]) // 3x3
hf.calculateFormula('=MAXPOOL(A1:C3, 2)', id)
// TypeError: Cannot read properties of undefined (reading '0')
hf.calculateFormula('=MEDIANPOOL(A1:C3, 2)', id) // same
```
The same exception escaped `setCellContents()`. `maxpoolArraySize()`
already rejected such arguments, but `ArraySize.error()` is `(1, 1,
isRef: true)`, which `isScalar()` reports as scalar, so the formula was
evaluated as an ordinary scalar formula and the invalid arguments
reached the kernel anyway.
Related failure modes found while reproducing: a window larger than the
range, a stride that makes the last window overshoot, and a zero,
negative or fractional window size or stride all threw as well
(`TypeError` or `RangeError: Invalid array length`).
### Changes
- `MatrixPlugin.maxpool()` / `MatrixPlugin.medianpool()` now validate
the window size and the stride against the range dimensions and return
`#VALUE!` with a new `ErrorMessage.PoolDimensions` (`'Range dimensions
are not compatible with the window size and the stride.'`) instead of
running the kernel out of bounds. `#VALUE!` matches how `MMULT` reports
incompatible dimensions.
- The window size and the stride are declared as
`FunctionArgumentType.INTEGER` with `minValue: 1`, so a zero, negative
or fractional value returns `#NUM!` (`Value too small.` / `Value needs
to be an integer.`) instead of throwing.
- The new `isPoolWindowFittingInputArray()` predicate is shared with
`maxpoolArraySize()`, keeping the predicted array size and the runtime
validation in sync. It also carries the positive-integer requirement,
which fixes a second defect: `maxpoolArraySize()` predicted a
**negative** array size for a negative stride, because `(4 - 2) % -1 ===
0` passes a naive divisibility check. The cell then reported `#SPILL!`
instead of the `#NUM!` the function actually returns.
- The dimension constraint is documented in the `range` / `window_size`
/ `stride` entries of
`src/interpreter/functionMetadata/categories/matrix-functions.ts` — the
single source of truth from which `docs/guide/built-in-functions.md` is
generated — and in the JSDoc of both methods.
### Tests
In the private suite: **handsontable/hyperformula-tests#48**, on the
matching `claude/vigilant-gates-3dkoua` branch so `fetch-tests.sh` pairs
the two and this PR's CI runs against it.
New cases in `unit/interpreter/matrix-plugin.spec.ts` cover dimensions
that are not a whole multiple of the window size (in a cell and via
`calculateFormula()`), only one dimension divisible, a window larger
than the range, an overshooting stride, a zero/negative/fractional
window size and stride, and a window covering the whole range. The
existing `matrix maxpool`, `matrix maxpool, custom stride` and `matrix
medianpool on even/odd square` tests already guard the working cases.
Verified locally against the full private suite: **502 suites, 6230
tests passed, 3 skipped**. `npm run compile` passes.
### Types of changes
- [ ] Breaking change (a fix or a feature because of which an existing
functionality doesn't work as expected anymore)
- [ ] New feature or improvement (a non-breaking change that adds
functionality)
- [x] Bug fix (a non-breaking change that fixes an issue)
- [ ] Additional language file, or a change to an existing language file
(translations)
- [x] Change to the documentation
### Related issues:
1. Fixes #...
### Checklist:
- [x] I have reviewed the guidelines about [Contributing to
HyperFormula](https://hyperformula.handsontable.com/guide/contributing.html)
and I confirm that my code follows the code style of this project.
- [ ] I have signed the [Contributor License
Agreement](https://goo.gl/forms/yuutGuN0RjsikVpM2).
- [ ] My change is compliant with the
[OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html)
standard. — `MAXPOOL` and `MEDIANPOOL` are HyperFormula-specific
functions, not covered by the standard.
- [ ] My change is compatible with Microsoft Excel. — not applicable,
these functions do not exist in Excel.
- [ ] My change is compatible with Google Sheets. — not applicable,
these functions do not exist in Google Sheets.
- [x] I described my changes in the
[CHANGELOG.md](https://github.com/handsontable/hyperformula/blob/master/CHANGELOG.md)
file.
- [x] My changes require a documentation update.
- [ ] My changes require a migration guide.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Localized bug fix to HyperFormula-specific pooling functions with
stricter validation and clearer errors; no auth, data, or broad API
contract changes beyond replacing throws with spreadsheet errors.
>
> **Overview**
> **MAXPOOL** and **MEDIANPOOL** no longer throw uncaught `TypeError`
(or related runtime errors) when the range does not tile cleanly with
the window and stride. They now validate arguments up front and return
**#VALUE!** with `ErrorMessage.PoolDimensions`, consistent with how
incompatible dimensions are handled elsewhere (e.g. **MMULT**).
>
> Shared helpers `isPoolWindowFittingInputArray()` and
`poolFunctionArgumentsError()` enforce positive integer window size and
stride, window fit inside the range, and divisibility of `(dimension -
window_size)` by stride. The same predicate is used in
`maxpoolArraySize()` so array-size prediction matches runtime behavior
(including cases that previously mis-predicted size for invalid
strides).
>
> `window_size` and optional `stride` are typed as **INTEGER** with
`minValue: 1` instead of generic numbers, so zero, negative, or
fractional values surface as **#NUM!** via argument coercion rather than
reaching the kernel. Function metadata and the changelog document the
tiling rules and the fix.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
60606ca. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Claude <noreply@anthropic.com>
https://app.clickup.com/t/9015210959/SU-637 Goal: Improve docs-assistant answer depth on HyperFormula named ranges / structured references Change: Named-columns section: no Excel structured refs, why SUM(Name1:Name5) fails, $A:$A workaround. ### Context Adds the missing named-column recipe to the named-expressions guide so docs-assistant can retrieve it. Cross-links the existing range-restraint on cell-references. ### How did you test your changes? Read-through against naming rules (`Name1` is A1) and range restraints (`:` cannot take named endpoints). Docs-only; no engine change. ### Types of changes - [x] Change to the documentation ### Related issues: 1. https://app.clickup.com/t/9015210959/SU-637 ### Checklist: - [x] I have reviewed the guidelines about [Contributing to HyperFormula](https://hyperformula.handsontable.com/docs/guide/contributing.html) and I confirm that my code follows the code style of this project. - [ ] I have signed the [Contributor License Agreement](https://goo.gl/forms/yuutGuN0RjsikVpM2). - [x] My change is compliant with the [OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html) standard. - [x] My change is compatible with Microsoft Excel. - [x] My change is compatible with Google Sheets. - [ ] I described my changes in the [CHANGELOG.md](https://github.com/handsontable/hyperformula/blob/master/CHANGELOG.md) file. - [ ] My changes require a documentation update. - [ ] My changes require a migration guide. Docs-only; no CHANGELOG Made with [Cursor](https://cursor.com) and Ola :) --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Kuba Sekowski <sequba@users.noreply.github.com> Co-authored-by: Kuba Sekowski <jakub.sekowski@handsontable.com>
### Context HyperFormula 3.4.0 shipped `VSTACK` and `HSTACK` with English placeholder names in language packs where Microsoft Excel uses localized names. This PR moves the previously reviewed localization fix out of the unrelated TAKE PR and into a dedicated change. This extracts the VSTACK/HSTACK portion of commit `b902d53bc`, originally authored by Kuba Sekowski. ### Changes | Language | VSTACK | HSTACK | |---|---|---| | Czech | `SROVNAT.SVISLE` | `SROVNAT.VODOROVNĚ` | | Danish | `VSTAK` | `HSTAK` | | German | `VSTAPELN` | `HSTAPELN` | | Spanish | `APILARV` | `APILARH` | | Finnish | `VPINO` | `HPINO` | | French | `ASSEMB.V` | `ASSEMB.H` | | Hungarian | `FÜGG.HALMOZÁS` | `VÍZSZ.HALMOZÁS` | | Italian | `STACK.VERT` | `STACK.ORIZ` | | Norwegian | `VSTAKK` | `HSTAKK` | | Dutch | `VERT.STAPELEN` | `HOR.STAPELEN` | | Polish | `STOS.PION` | `STOS.POZ` | | Portuguese | `JUNTARV` | `JUNTARH` | | Russian | `ВСТОЛБИК` | `ГСТОЛБИК` | | Turkish | `DÜŞEYYIĞ` | `YATAYYIĞ` | Indonesian and Swedish remain in English, matching Excel. The PR also adds source-verification guidance to `DEV_DOCS.md` and an Unreleased changelog entry. ### Source verification All 14 localized pairs and the unchanged Indonesian and Swedish pairs were checked on 2026-08-25 against Microsoft Support's current individual VSTACK/HSTACK pages, using each page title and displayed formula syntax. This caught one mismatch in the original table: Danish is [`VSTAK`](https://support.microsoft.com/da-dk/excel/functions/vstack-function) / [`HSTAK`](https://support.microsoft.com/da-dk/excel/functions/hstack-function). Microsoft's Danish alphabetical function index currently conflicts with those individual pages, so `DEV_DOCS.md` now requires checking the linked individual page rather than copying a name from the index alone. ### Migration note This is a breaking change for formulas created with HyperFormula 3.4.0 that use English `VSTACK` or `HSTACK` while one of the affected language packs is active. Those formulas must switch to the names listed above. ### Validation Companion tests: handsontable/hyperformula-tests#44 - 34/34 focused i18n tests passed. - Full browser suite passed: 12,388 assertions in Chrome and Firefox. - TypeScript compilation passed. - Targeted lint completed with zero errors. - `git diff --check` passed. ### Types of changes - [x] Breaking change - [ ] New feature or improvement - [x] Bug fix - [x] Additional language file or translation change - [x] Documentation change <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Breaking change to parsed function names in 14 locales; no calculation logic changes, but existing localized formulas using English `VSTACK`/`HSTACK` will stop resolving until renamed. > > **Overview** > Replaces English placeholder names for **`VSTACK`** and **`HSTACK`** in **14 built-in language packs** (Czech, Danish, German, Spanish, Finnish, French, Hungarian, Italian, Norwegian, Dutch, Polish, Portuguese, Russian, Turkish) with the localized identifiers Microsoft Excel uses—for example German `VSTAPELN`/`HSTAPELN` and French `ASSEMB.V`/`ASSEMB.H`. Locales where Excel keeps English (e.g. `enGB`, Indonesian, Swedish) are unchanged. > > **`DEV_DOCS.md`** now requires Excel-verified names only (no inferred translations), documents Microsoft’s per-locale alphabetical function list for newer functions, and says to confirm names on individual function pages when the index disagrees. **`CHANGELOG.md`** records the fix under Unreleased. > > **Migration:** With an affected language pack active, formulas that still call `VSTACK`/`HSTACK` in English must use the new localized names; this is treated as a breaking change for 3.4.0-era workbooks. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit e2c90f1. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Kuba Sekowski <sequba@users.noreply.github.com> Co-authored-by: Kuba Sekowski <jakub.sekowski@handsontable.com>
### Context `AVERAGEIF` returned a `#DIV/0!` error when matching values produced a valid average of `0`. This happened because the result used a logical OR fallback, which treated `0` as if no average had been calculated. This change uses nullish coalescing so that only an absent result produces `#DIV/0!`, while a valid zero is returned normally. ### How did you test your changes? Added a regression test where the matching values are `-1` and `1`, producing an average of `0`. The test was verified to fail with `#DIV/0!` before the fix and pass with `0` after the fix. I also ran: - The focused `AVERAGEIF` test suite: all 14 tests passed. - `npm run bundle:cjs`: TypeScript and the CommonJS build completed successfully. - Targeted linting: no errors. ### Types of changes - [ ] Breaking change (a fix or a feature because of which an existing functionality doesn't work as expected anymore) - [ ] New feature or improvement (a non-breaking change that adds functionality) - [x] Bug fix (a non-breaking change that fixes an issue) - [ ] Additional language file, or a change to an existing language file (translations) - [x] Change to the documentation ### Related issues: None. ### Checklist: - [x] I have reviewed the guidelines about [Contributing to HyperFormula](https:// hyperformula.handsontable.com/guide/contributing.html) and I confirm that my code follows the code style of this project. - [ ] I have signed the [Contributor License Agreement](https://goo.gl/forms/yuutGuN0RjsikVpM2). - [x] My change is compliant with the [OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/ os/part4-formula/OpenDocument-v1.3-os-part4-formula.html) standard. - [x] My change is compatible with Microsoft Excel. - [x] My change is compatible with Google Sheets. - [x] I described my changes in the [CHANGELOG.md](https://github.com/handsontable/hyperformula/blob/master/ CHANGELOG.md) file. - [ ] My changes require a documentation update. - [ ] My changes require a migration guide. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Single-operator change in AVERAGEIF result handling with no impact on auth, data, or broader aggregation APIs. > > **Overview** > **`AVERAGEIF` now returns `0` when the conditional average is legitimately zero**, instead of incorrectly surfacing `#DIV/0!`. > > The bug came from using logical OR (`||`) after `averageValue()`: a computed average of `0` was treated like a missing result. **`ConditionalAggregationPlugin`** switches that fallback to nullish coalescing (`??`), so only `undefined` (no matching numeric cells / zero count) still maps to `#DIV/0!`. **CHANGELOG** documents the fix under Unreleased. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 2eb85e9. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Kuba Sekowski <jakub.sekowski@handsontable.com>
## What & why
The docs stated a **hardcoded** count of built-in functions
("~400"/"400+") and languages ("17"/"18") that drifts from reality. The
function count already auto-derived via `{{ $page.functionsCount }}`;
this adds the parallel `{{ $page.languagesCount }}` and swaps the
remaining hardcoded counts to the interpolated variables.
## How
- `docs/.vuepress/config.js`: derive `languagesCount` once at config
load from the i18n export barrel `src/i18n/languages/index.ts`
(whitespace-tolerant regex + fail-loud guard so a barrel reformat can
never silently publish "0 languages"); inject `$page.languagesCount`.
- Docs: `~400`/`400+` → `{{ $page.functionsCount }}` (index, ai-sdk,
mcp-server, langchain); `17`/`18` → `{{ $page.languagesCount }}` (index,
built-in-functions, i18n-features, localizing-functions).
- `README.md` (not a VuePress page, so no interpolation possible):
manual `over 400` + `18`, with the language count now asserted against
the same i18n barrel at config load (`3b4fda30f`) so it fails the docs
build instead of rotting silently.
- `docs/.vuepress/plugins/md-companions/index.js`: register
`languagesCount` with the companion `{{ $page.* }}` resolver
(`3ef3fdddc`). The resolver arrived with #1703 and substitutes an
allowlist of injected keys, so without this the built `.md` companions
and `llms-full.txt` shipped the raw mustache while the HTML rendered the
number.
## Verification
`docs:build` renders the real counts (functions / languages), including
inside markdown link text, with **no un-rendered `{{ }}`** in the built
output. Docs-only + docs-build-config → no CHANGELOG per DEV_DOCS DoD.
## Known nuance
`README.md` counts stay hand-edited — it is rendered by GitHub and npm,
neither of which runs VuePress, so `{{ $page.* }}` would publish
literally. The language count is therefore guarded rather than
interpolated: the docs build fails if README disagrees with the barrel,
or if the `<n> built-in languages` phrase disappears. Verified both
failure modes. The function count needs no guard — "over 400" is chosen
so the line does not restale as functions grow. The
`localizing-functions.md` language *table* rows remain hand-maintained.
The guard couples the docs build to `README.md`, which is a deliberate
trade: the alternative was generating README as a build product, and it
is a file people edit directly.
ClickUp task: https://app.clickup.com/t/9015210959/HF-282
**Update (28.08 rebase):** `develop` replaced
`docs/guide/built-in-functions.md` with a generated page (template
`docs/guide/built-in-functions.tmpl.md` + generator, HF-249/#1692). This
PR's one-line change to that page now lives in the **template** — the
generator copies prose verbatim, and `npm run docs:build` renders `{{
$page.languagesCount }}` correctly in the generated output (verified:
zero unrendered mustaches in `dist/`). Commit SHAs in this description
refer to the rebased branch.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Docs and VuePress build configuration only; no runtime library or API
behavior changes.
>
> **Overview**
> Stops hardcoded **function** and **language** totals in docs and the
root README from drifting when the catalogue or i18n packs change.
>
> **VuePress** now derives `languagesCount` at config load from
`src/i18n/languages` (with a barrel re-export check) and injects `{{
$page.languagesCount }}` alongside the existing function count. The
function total is computed once via `getAvailableFunctions()` on a
default-config engine—the same approach as
`script/generate-builtin-functions-doc.ts`—instead of
`getRegisteredFunctionNames`, so the headline number matches the
generated built-in-functions table. Guide pages, index, and integration
previews swap `~400` / `400+` and `17` / `18` for `{{
$page.functionsCount }}` and `{{ $page.languagesCount }}`; the
**md-companions** allowlist resolves `languagesCount` in shipped `.md` /
`llms-full.txt`.
>
> **README.md** (GitHub/npm, no VuePress) is updated manually to **over
400** functions and **18** languages; the docs build **fails** if the
i18n features bullet’s language number disagrees with the derived count.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
ea8e6a4. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Kuba Sekowski <sequba@users.noreply.github.com>
## Summary The first-party docs MCP server is live at `https://docs-assistant.handsontable.com/mcp` — semantic search over the full HyperFormula + Handsontable knowledge base (docs guides, API reference, code recipes, release notes, blog posts, and GitHub issues), always current with the latest release. This PR makes it the recommended MCP route in the agent-facing docs: - **Set up your coding agent** guide: the "Live docs via MCP" section now leads with the first-party server (GitMCP and Context7 stay as alternatives); added to the page tags and the Resources list. - **Wizard**: the Cursor path now hands out a `.cursor/mcp.json` config for the server (Cursor supports MCP natively; the rules-file corpus pointer stays as the noted alternative), and the Other/API path mentions the MCP URL. - **README**: one sentence pointing MCP-capable agents at the server. The endpoint was verified live before this PR: initialize, `tools/list`, and a real `tools/call` all pass, serving the current knowledge-base pair (HT 18.1.0 / HF 3.4.0). ## Test plan - Docs-only change: `docs/guide/setup-coding-agent.md` renders with the existing anchors unchanged (the `#live-docs-via-mcp-any-agent` sidebar link still resolves) - Wizard JSON snippet is valid `.cursor/mcp.json` content 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Documentation-only updates to README, docs pages, and the coding-agent wizard; no runtime or library code changes. > > **Overview** > This PR updates agent-facing documentation to **recommend the first-party docs MCP server** at `https://docs-assistant.handsontable.com/mcp` for live, semantic search over HyperFormula and Handsontable docs. > > In **Set up your coding agent**, the “Live docs via MCP” section now leads with that server (GitMCP and Context7 remain alternatives), and the Resources list adds a link to the Docs MCP Server guide. The **CodingAgentWizard** Cursor path now copies a `.cursor/mcp.json` snippet for the server, with the `llms-full.txt` rules-file approach noted as a fallback; the Other/API option also mentions the MCP URL. **README** and **docs index** gain a short paragraph pointing MCP-capable agents at the same server. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit b0a6520. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…, API guards (#1728) ### What this is HF-307, feature packages and add-ons. A proprietary license key can now grant a *subset* of the library instead of only answering yes/no. The key carries a flat list of capability tokens, and the engine grants the union of what those tokens name. This started as a stack of four PRs and is now one. #1729, #1730 and #1743 were merged into this branch; #1731, #1736, #1737, #1740 and #1741 were closed once their content landed here. Paired tests: handsontable/hyperformula-tests#30. Twenty-four files are new; 22 of those live under `src/license/`. > The earlier revisions of this description, written while the stack still existed, are in this description's edit history. ### Two gates - **Gate A** — is the key valid? Classic keys are validated and messaged as before, except that the build release date and the expiry date in the console message are now read in UTC (see below); entitlement keys are validated too, with their own console messages. A key that is missing, invalid, or expired in a way that blocks evaluation (a classic key, or a trial past its grace period) blocks the library: - every function except `VERSION` and `OFFSET` evaluates to `#LIC!`, as before; - new: every gated public-API method throws `LicenseCapabilityMissingError` naming the key's state (`License key is missing. Feature crud is not available.`), and so does building an engine with named expressions. Before, such a key stopped only function calls: operator formulas, plain data and the whole API kept working. Code that builds an engine without a valid key now gets this error from its first gated call. The changelog lists it under Changed. - **Gate B** — what does the key grant? New. Functions and public-API areas, resolved on two axes (`functions`, `features`), each either a set or `'all'`. One invariant binds them: **only a key that lets the build evaluate restricts anything through gate B**: a valid key, or an expired subscription or perpetual key, which keeps its own grants. A missing, invalid or blocking expired key resolves to an entitlement that restricts nothing, and gate A alone stops it. Its errors therefore always name the key's state (`License key is invalid.`), never a missing grant (`… is not included in your license`), and `getAvailableFunctions()` still describes the whole catalog under it. ### What a restricted key does - A function outside the grant evaluates to `#LIC!` rather than disappearing, in its own cell: an array function the license stops reserves no spill range, so it never shows `#SPILL!` instead. - A gated public-API method throws `LicenseCapabilityMissingError`. Its `feature` property names the area the call needed, as a value of `FeatureId`, which the package now exports. - The matching `isItPossibleTo*` predicates (`isItPossibleToAddRows()` and the rest) answer `false` for a call that would throw it, and so do `isThereSomethingToUndo()` and `isThereSomethingToRedo()` without `undo_redo`. - `getAvailableFunctions()` and `getFunctionDetails()` describe only what the instance can actually evaluate. `getRegisteredFunctionNames()` still lists every registered function. - Valid classic 25-character keys, `gpl-v3`, `internal-use-in-handsontable` and `hftrial-0168e-1f2b7-47158-70b05-0842f` are unaffected: they resolve to an unrestricted entitlement and never consult the capability table. An invalid or expired classic key gets gate A's behavior above. ### Capability tokens Function grants use the packaging vocabulary: `fun:all`, `fun:<family>.<A|B|C>` and per-function `fun:<NAME>`. The callable operator forms (`HF.ADD` and friends) sit under `fun:operator.A`; infix operators work under any key. Token names are matched case-insensitively. Gated API areas use `feat:*` tokens, one area each. A key is granted **exactly the areas it names**, and `feat:all` is how it names all of them; a key naming none is granted none. The key generator (`license-key` 5.1.1) cannot write `fun:*` or `feat:*` tokens yet, so a key it issues today grants no gated function and no gated API area. An earlier revision read the absence of a `feat:*` token as "this key does not talk about features" and granted all five areas; that rule was removed deliberately. The engine knows no packages and no add-ons. A key may carry a word like `spreadsheet` or `functions_1`; it is read as any other word this version does not know: it grants nothing, nothing reports it, and it never subtracts anything. ### The key reader `src/license/handsontable-license-key-parser/` is a copy of the reader upstream publishes for products (`license-key`'s `vendor/entitlement-key-reader/`, tag 5.1.1). Twelve of its thirteen files are byte-identical; the thirteenth differs by one cast, explained below. The engine calls its `readEntitlementLicense`, as upstream's README prescribes: the reader verifies the key, picks the `hyperformula` entry, places it in its lifecycle window and reads its flags. `licenseResolution.ts` keeps what the guide leaves to the product: the meaning of the capability tokens, the console messages, and which lifecycle states block evaluation. A non-trial key past its grace period, or past the build its maintenance covers, reports EXPIRED and prints a console error but keeps evaluating with its own grants. A trial past its grace still gives `#LIC!`. Classic 25-character keys are read as before, with one fix: the build release date they are compared with is parsed in UTC, and the expiry date in their console message is printed in UTC. Previously, east of UTC, a classic key that expired the day before the build was released was still accepted, and west of UTC, the console printed an expiry date one day too early. The changelog has a Fixed entry for it. Since 5.x, keys are in format version 2: the payload carries a digest of the prose, so a key whose prose was edited or removed (the bare `[...]` block), or that has text after its block, is invalid (M7). Whitespace and line breaks saved as text (`\n`) are ignored anywhere in the key, inside the block too. Following that, the license key guide tells users to pass the whole key as issued. The spec's state table has the matching rows (S31 changed, S39 added). Console messages for entitlement keys are the specification's text, per lifecycle state, the same table Handsontable prints (`handsontable/src/helpers/mixed.ts`): the date exactly as the key carries it, a warning while the key works and an error once it has run out (the grace period included). They print every time a key is resolved: unlike classic keys, which keep their once-per-page flag, entitlement keys keep no record of what they already printed, so one key can print the same message more than once on a page. The cast in `extractKeyData.ts` stays. Following the reader changed three things: - A key that grants other products but not HyperFormula is INVALID and restricts nothing. An earlier revision had it VALID with nothing granted. - A malformed date in another product's entry invalidates the whole key again, as upstream does. - Only `no-console-warns` silences the console. `silent-console` and `silent` are unknown flags; the generator emits neither. `upstream.json` pins the tag. `npm run check:license-key-parser-drift` compares every file git tracks in the directory against the pinned commit and runs in CI (`vendored-parser.yml`; currently red: the `LICENSE_KEY_REPO_TOKEN` secret exists but cannot read the upstream repository). One declared divergence, a single cast for TypeScript 4.0, is recorded there with its expiry. ### Also in this PR - **Moving or pasting a formula with an undefined name to another sheet** no longer adds that name as an empty global named expression. Before, the formula's `#NAME?` turned into an empty value, and the workbook serialized a named expression nobody added, which a key without `named_expressions` then could not build again. The bug predates this PR; the license gate turned it into a hard failure. Only a name the source sheet defines locally is copied to the global scope now (`Operations.updateNamedExpressionsForTargetAddress`). - **One rule for stopped function calls.** `FunctionCallLicenseGate` decides which calls the license stops and with which `#LIC!` error. The interpreter and the array size predictor both use it, which is what keeps a stopped array function to one cell. - **API reference examples.** Every `@example` block in `HyperFormula` and its events, and the API reference landing page, passes `licenseKey: 'gpl-v3'`. Four examples that threw for their own reasons (`moveColumns`, `getNamedExpressionValue`, `changeNamedExpression`, `getAllNamedExpressionsSerialized`) are fixed. ### `VERSION()` `VERSION()` returns only the version (`HyperFormula v3.4.0`), with no license status, under every key. The function's catalog description and the changelog say so. ### Docs - Every engine built in a `docs/guide` example passes `licenseKey: 'gpl-v3'`: without a key, the gated methods those examples call now throw. - `DEV_DOCS.md` describes `src/license/` and the vendored reader, and the new-function checklist has a step for `functionCapabilities.ts`: a built-in missing from it is not gated at all. - The changelog has a Changed entry for what a blocking key does to the gated API. It is not marked as breaking, for the reason under "Two gates". ### Not built here, on purpose - The shared conformance fixtures both products would run in CI. Needs the other side to exist. ### Review Reviewed on 2026-09-22, 2026-09-23, 2026-09-24, 2026-09-28 and 2026-09-30, with the 2026-09-29 rulings in the thread. The `feat:import_export` token is gone. Merge order: this PR before hyperformula-tests#30. Open: - The drift-check workflow is red: its secret cannot read the upstream repository. - The key generator cannot write `fun:*` or `feat:*` tokens yet, so no key issued today grants a gated function or API area. - Spec rows S32–S35 and S38 have no tests yet. - Two places where the engine and the spec disagree: a custom function evaluates to `#LIC!` under a blocking key, and `getAvailableFunctions()` lists the whole catalog under a blocking key. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > Changes core licensing behavior for formula evaluation and most mutating public API paths; incorrect gating or key parsing could block paying customers or expose paid features. > > **Overview** > Adds **feature-package licensing**: proprietary keys can grant subsets of functions (`fun:*` tokens) and API areas (`feat:*` tokens). Restricted functions evaluate to **`#LIC!`**; gated API methods throw **`LicenseCapabilityMissingError`**, with matching **`isItPossibleTo*`** / undo-redo predicates returning **`false`**. **`getAvailableFunctions()`** / **`getFunctionDetails()`** now list only what the instance’s key allows. > > **Gate A** still blocks evaluation for missing/invalid keys and blocking expirations (classic or trial past grace): almost all functions become **`#LIC!`** (except **`VERSION`** / **`OFFSET`**), and gated API—including building with **named expressions**—throws instead of silently allowing edits. **`VERSION()`** no longer embeds license status. > > Integrates a **vendored** `handsontable/license-key` entitlement reader (pinned tag, drift CI, one TS 4.0 cast shim), **`licenseResolution`**, and capability tables in **`functionCapabilities.ts`** / **`featureCapabilities.ts`**. Classic 25-char keys get a **UTC** fix for release vs expiry comparison and console dates. > > Also fixes **move/paste** of formulas referencing undefined names on another sheet (no spurious global named expression), updates docs/examples with **`licenseKey: 'gpl-v3'`**, and excludes the vendored tree from ESLint/Codecov. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f9819f7. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Kuba Sekowski <jakub.sekowski@handsontable.com> Co-authored-by: Kuba Sekowski <kuba.sekowski.dev@gmail.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Kuba Sekowski <sequba@users.noreply.github.com>
…tics (HF-491) (#1784) ### Context The variance functions compute `sum(x^2) - sum(x)^2 / n` in one pass. When the mean is large relative to the spread (typical of measurement data), the two terms are nearly equal and cancel, so every significant digit is lost: | Formula | Before | Exact (stored doubles) | After | |---|---|---|---| | `=STDEV.S({10000000.000,10000000.001,10000000.002})` | `#NUM!` | 0.0010000001639127731 | 0.0010000001639127731 | | `=VAR.S(` same `)` | `-0.03125` (negative, no error) | 1.0000003278255731e-06 | 1.0000003278255731e-06 | | `=STDEV.S({100000000000.003,100000000000.004,100000000000.005})` | `0` | 0.0009994603901554338 | 0.0009994603901554338 | `smartRounding: false` and `precisionEpsilon: 0` do not change the old results. `DEVSQ`, `COVARIANCE.P`, `COVARIANCE.S`, `SLOPE`, `STEYX` and the database functions `DSTDEV`, `DSTDEVP`, `DVAR`, `DVARP` compute the mean first and lose digits in the same situation when the values use all 15 significant digits. For example, for three values near `100000000000`, `STEYX` returned `0.0187` instead of `0.00623` and `SLOPE` returned `1000.37` instead of `1000.53`. Affected: - `VAR.S`, `VAR.P`, `STDEV.S`, `STDEV.P`, `VARA`, `VARPA`, `STDEVA`, `STDEVPA`, their legacy names (`VAR`, `VARP`, `VARS`, `STDEV`, `STDEVP`, `STDEVS`), and `SUBTOTAL` modes 7, 8, 10 and 11 (and 107, 108, 110, 111), all backed by `MomentsAggregate` - `DEVSQ`, `COVARIANCE.P`, `COVARIANCE.S` (and `COVAR`, `COVARIANCEP`, `COVARIANCES`), `SLOPE`, `STEYX`, `DSTDEV`, `DSTDEVP`, `DVAR`, `DVARP` ### The change **Double-double helpers** (`src/interpreter/doubleDouble.ts`): TwoSum (in the Fast2Sum form, taking the error from the larger addend, so it cannot overflow while the sum is finite) and Dekker's TwoProduct with Veltkamp's split (no FMA needed), plus addition, subtraction, multiplication and division of double-doubles, and rounding to a double. Near the top of the double range they rescale by exact powers of two: `twoProduct` scales the larger factor by 2^-53 when a factor exceeds 2^996 or the product exceeds 2^1023, so its error term is exact for every finite product above 2^-969, and the divisions scale dividends above 2^1000 by 2^-64. **`MomentsAggregate`** (`src/interpreter/plugin/MomentsAggregate.ts`) stays composable, so the per-range cache keeps working. Besides the count, it keeps the sums of deviations `S1` and of squared deviations `S2` from a `shift` (the first value added), stored as double-double, and rebases them when two aggregates compose. The deviations are stored multiplied by `2^-exponent`, an exact power of two kept in the aggregate, so the shifted sums cannot overflow: the exponent is 0 until the scaled sum of squares exceeds 2^960 (deviations of about 1e144 and above), and two aggregates compose at the larger exponent, raised further when the difference of their shifts needs it. Adding one value, the common case when a range is folded cell by cell, takes a fast path. The sum of squared deviations `S2 - S1^2 / n` is evaluated in double-double as `S2 - S1 * (S1 / n)`, so the `S1^2` term cannot overflow, the variance is rounded once, and it is then multiplied by `2^(2 * exponent)`. The variance is `#NUM!` only when it exceeds the largest double, and it does not depend on the order of the arguments: `=VAR.S(0, 1e154, 1e154)` and `=VAR.S(1e154, 1e154, 0)` both return 3.33e307 (develop returns `#NUM!` for both), and `=VAR.S(-1.2e154, 0, 1.2e154)` returns 1.44e308 although the sum of squared deviations exceeds the largest double. The exponent can also go negative (down to -600) when tiny differences follow equal values, and the standard deviation is taken at the scale of the sums before scaling back, so `STDEV.*` is finite when only the variance overflows and non-zero for spreads below about 1e-154 (`=STDEV.S(1e-170, 2e-170)` returns 7.07e-171). **Deviation sums** (`src/interpreter/deviationSums.ts`): `DEVSQ`, `COVARIANCE.*`, `SLOPE` and `STEYX` compute the mean in double-double, then the deviations and the sums of their products in double-double, and round once. Each array is measured at its own power-of-two scale and the rounded result is scaled back: an array whose largest magnitude is below 2^-400 is scaled up to it (exact), so squared deviations cannot underflow and the sum of squared x deviations is 0 only when all the x values are equal; an array above 2^400 is scaled down to it only when the unscaled sums overflow, so on ordinary data the results are bit-identical to the unscaled computation. `DEVSQ` is only scaled up, because when its unscaled sum overflows, its result does too. `SLOPE` and `STEYX` share `regressionSums` and the check for equal x values. `STEYX` does not use `Syy - Sxy^2 / Sxx`, which cancels when the points lie almost on a line: it computes each residual `dy - slope * dx` in double-double, centers the residuals on their mean (which removes the shift that the rounding errors of the two means add to every residual), and sums their squares. `SLOPE` skips the residuals. **D-variance functions**: `DVAR`, `DVARP`, `DSTDEV` and `DSTDEVP` fold their values through `MomentsAggregate`, so they return the same results as `VAR.S`, `VAR.P`, `STDEV.S` and `STDEV.P`, including when the sum of squared deviations overflows. Behaviour changes besides precision: `STEYX` returns the standard error instead of `#NUM!` or `0` for points that lie almost on a line (`=STEYX({0.25,0.4,0.5499999999999999},{0.1,0.2,0.3})` returns 2.83e-17, the exact value for the stored doubles, which are not evenly spaced, where develop returns `#NUM!`; `=STEYX({100000000,200000000,300000000.00000024},{1,2,3})` returns 9.733e-8 where develop returns `0`); `SLOPE` and `STEYX` return `#DIV/0!` with the message "All x values are equal." (the new `ErrorMessage.EqualXValues`) when all the x values are equal, as Excel and Google Sheets return `#DIV/0!` (develop returns `#NUM!` or an arbitrary number), and finite results where the sum of squared deviations of the x values overflows or underflows (`=SLOPE({1,2},{1e-170,2e-170})` returns 1e170; develop returns `#NUM!`). `DVAR`, `DSTDEV` and `COVARIANCE` return finite results where only an intermediate sum overflows (`=DSTDEV` of `-1.2e154, 0, 1.2e154` returns 1.2e154, like `STDEV.S`). `AVERAGE`, `AVERAGEA` and `SUBTOTAL` 1/101 no longer use `MomentsAggregate`: they reduce an `AverageResult` (a plain sum and a count, moved from `ConditionalAggregationPlugin` to its own module) under their own cache keys (`_AVERAGE` and `_AVERAGE_A`), so they do not compute the double-double sums. Each argument is evaluated once, and the results are the same as before. ### Accuracy The reference is the exact result for the stored doubles, reported as LRE (digits of agreement, McCullough 1998). Exact references: BigInt rational arithmetic, Python's `statistics` (exact fractions) and mpmath at 60 digits. The data is 24 datasets, including all 9 NIST StRD univariate sets. Measured for `VAR`/`STDEV` at 7a88902 and not re-measured since; the variance algorithm is the same apart from the `S1 * (S1 / n)` evaluation and the power-of-two exponent, which is exact scaling and is 0 for all 24 datasets. | | min LRE over 24 datasets | |---|---| | this PR | **15** | | Gnumeric 1.12.56 | 15 | | R 4.3.3 `sd()` | 4.7 | | NumPy 2.5.3 / SciPy 1.18.1 | 0 | | plain two-pass | 0 | | Excel (two-pass; 50 identical values → 7.5e-8, exact 0) | 0 | | Welford / Chan pairwise | 2.4 | | before (one-pass) | −1 (negative variance) | Double-double arithmetic does not guarantee correct rounding, so the JSDoc states the accuracy as about one unit in the last place. Split arguments, `=STDEV.S(A1:A50, A51:A100)` in either order, hold the same accuracy. When a range reuses the cached aggregate of a smaller range or the values come in several arguments, partial aggregates are composed in a different order than the left-to-right fold of the D-functions, so the results can differ from them in the last bits. Compared with Excel: near 1e7 the results are equal, but near 1e11 Excel's two-pass result differs from the exact value in the 4th significant digit (0.000999538 vs 0.000999460), and for 50 identical values whose mean is not exactly representable Excel returns 7.5e-8 instead of 0. This PR returns the exact values. ### Alternatives measured - **Plain two-pass** for the `MomentsAggregate` functions: mean first, then deviations, which needs every value again. It cannot use the range cache, and a running `=STDEV.S(A$1:An)` over 5,000 rows became 10x slower to build and 38x slower to recalculate after one edit. The functions that already take whole arrays (`DEVSQ`, `COVARIANCE.*`, `SLOPE`, `STEYX`, D-functions) use two passes, in double-double. - **Shifted sums plus a term that reproduced Excel's rounding of the mean**: composable, but it inherited Excel's error (min LRE 0) and depended on argument order. - **Compensated (Neumaier) shifted sums**: min LRE 13.1 at about the same cost as double-double. ### How did you test your changes? - The tests PR (handsontable/hyperformula-tests#70) adds 118 tests to the spec of each affected function; 63 of them fail on develop. Expected values are the exact results for the stored doubles, cross-checked with Python's `statistics`, compared as a ratio within 1e-14 with `smartRounding` off. They cover every affected function, the legacy `STDEV` name, `SUBTOTAL` 7 and 11, two ranges as separate arguments, range + scalar, a range extending a cached smaller range, two cached ranges kept at different scales (tiny spreads of different sizes, and tiny values with huge ones) combined in either order, recalculation after an edit, the NIST NumAcc4 dataset, identical values (also near 1e300), variances near 1e300, `STEYX` on points exactly and almost on a line, `SLOPE`/`STEYX` with equal x values (including the error message), and overflow: a squared deviation close to the largest double in either order, shifted sums that overflow, two cached ranges whose combination overflows, a running total that overflows, a value equal to the largest double, products of deviations near the largest double that cancel, the sum of squared x deviations overflowing or underflowing in `SLOPE`/`STEYX`, the D-functions and `COVARIANCE` matching `VAR`/`STDEV` when the sum of squared deviations overflows, tiny spreads in `STDEV.S` and `DSTDEV`, products of large and tiny values that cancel in `COVARIANCE` and `SLOPE`, an overflowing slope in `STEYX`, the same variance for the values in any order and for a range split at any point when the squared deviations from the first value overflow, a finite variance whose sum of squared deviations overflows, `#NUM!` when the variance exceeds the largest double, `AVERAGE` and `VAR.S` and `AVERAGEA` and `VARA` over the same range, and `AVERAGE`, `AVERAGEA` and `SUBTOTAL` 1 evaluating each argument once. - Full suite (after merging develop): 6733 passed, 0 failed, 3 skipped. - `STEYX` on points almost on a line was checked against exact rational results: 4,000 datasets (n from 3 to 12, intercepts, slopes and x values over 30 to 40 orders of magnitude, residual sums of squares down to 1e-37 of the sum of squared y deviations), largest relative error 1.8e-15, plus 60k datasets of the general `COVARIANCE`/`SLOPE`/`STEYX` fuzz with no `STEYX` result off by more than 1e-12. - The double-double helpers were checked against exact BigInt arithmetic: about 31M cases each for `twoSum` and `twoProduct` (boundary grids around 2^996, 2^1023, the largest double, subnormals, signed zeros, infinities and NaN, plus random bit patterns) with no inexact result where an exact one is representable, and about 5M cases each for the two divisions. - An engine differential test against develop and exact results (about 2.9M formula evaluations over ordinary data, large means with small spreads, extreme magnitudes, and the same data as one range, two ranges, scalars and a cached range extended) found no case where develop is finite and this PR returns an error, apart from the ones listed under "Not in this PR". - A differential test of the power-of-two exponent against exact BigInt rational results: 24,000 datasets (magnitudes from 1e140 to the largest double mixed with zeros and subnormals, opposite-sign values near the largest double, large means with small spreads at every exponent, identical values, variances near the overflow threshold; n from 2 to 200), each evaluated in about 20 forms (argument orders, split ranges, range + scalar, cached prefix ranges), 10.8M checks in all, for `VAR.*`, `STDEV.*`, `VARA`, `VARPA`, `STDEVA`, `STDEVPA` and `SUBTOTAL` 7, 8, 10 and 11. Where the exact variance is a normal double, the largest error is 1 ulp (3 results at 2 ulp just above 2^-1022; largest relative error 3.75e-16), all forms agree within 2 ulp, and `#NUM!` is returned exactly when the exact variance rounds to infinity. - Benchmark against develop at 92ebfbd (Apple M4 Pro, Node 24; fresh process per run, 7 alternating runs, warm median): one `STDEV.S` over 100k cells 14.5 → 18.5 ms (1.28x), `AVERAGE` plus `VAR.S` over the same 20k-cell range 5.6 → 7.2 ms (1.29x), recalculation of a running `STDEV.S(A$1:An)` over 5k rows after one edit 12.9 → 13.9 ms (1.08x), building a running `STDEV.S(A$1:An)` or `AVERAGE(A$1:An)` over 5k rows 1.00x and 0.99x. Data that needs the exponent (values near 1e150) adds about 6% to the 100k `STDEV.S`. - The power-of-two scaling of the D-functions, `COVARIANCE`, `SLOPE` and `STEYX` was checked against exact BigInt results and against the previous head (92ebfbd): about 18k random datasets with 23 formulas each, 18k more from an independent generator, an 80k-dataset `MomentsAggregate` composition fuzz and 19,664 equal-x cases. No result differs from 92ebfbd on ordinary data, the D-functions equal `VAR`/`STDEV` in every regime, and equal x values always give `#DIV/0!`. On 100k ordinary values, every affected function is within about ±3% of 92ebfbd, and `DVAR`/`DSTDEV` are about 5% faster. - Heap after building a running formula over 5k rows: `STDEV.S` 10.49 → 11.82 MB, `AVERAGE` 10.48 → 10.96 MB. A cached `MomentsAggregate` is larger than on develop (two double-doubles, a shift and an exponent); a cached `AverageResult` is smaller than develop's aggregate. ### Not in this PR - `VAR.*` results below 2^-1022 (spreads below about 1e-154) are subnormal numbers with few significant bits; they are rounded at the scale of the sums and then scaled back, so they can be 1 subnormal ulp off. - `CORREL`, `PEARSON`, `RSQ`, `Z.TEST`, `T.TEST`, `F.TEST`, `SKEW` and `SKEW.P` still compute their sums of deviations in plain doubles (jStat), so they keep the precision loss on data with a large mean. - When an intermediate sum overflows, `COVARIANCE` and `SLOPE` scale large arrays down, which rounds away values about 2^1400 times smaller than the largest one. On extremely ill-conditioned data (values near the largest double that cancel, or data spanning about 600 orders of magnitude) they can then return a finite but inaccurate result where develop returned `#NUM!`; no double-double method can resolve these. - `STEYX` rounds twice (division, then square root), so it can be about 1 ulp off. - Double-double edge cases that predate this change: `divideDoubleDoubles` with a dividend below about 2^-950 can be a few ulps less accurate than plain division, and sums or quotients within half an ulp of the largest double return infinity instead of the largest double. - The precision of `AVERAGE`: `=AVERAGE(1e16, 1, -1e16)` returns `0`, as on develop and in Excel. - `INTERCEPT` is not implemented in HyperFormula. ### Types of changes - [ ] Breaking change (a fix or a feature because of which an existing functionality doesn't work as expected anymore) - [ ] New feature or improvement (a non-breaking change that adds functionality) - [x] Bug fix (a non-breaking change that fixes an issue) - [ ] Additional language file, or a change to an existing language file (translations) - [ ] Change to the documentation ### Related issues: 1. Tests: handsontable/hyperformula-tests#70 2. Merged into this branch: #1798 (`DEVSQ`, `COVARIANCE`, `SLOPE`, `STEYX` and the D-variance functions) ### Checklist: - [x] I have reviewed the guidelines about [Contributing to HyperFormula](https://hyperformula.handsontable.com/docs/guide/contributing.html) and I confirm that my code follows the code style of this project. - [x] I have signed the [Contributor License Agreement](https://goo.gl/forms/yuutGuN0RjsikVpM2). - [ ] My change is compliant with the [OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html) standard. - [ ] My change is compatible with Microsoft Excel. - [ ] My change is compatible with Google Sheets. - [x] I described my changes in the [CHANGELOG.md](https://github.com/handsontable/hyperformula/blob/master/CHANGELOG.md) file. - [ ] My changes require a documentation update. - [ ] My changes require a migration guide. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches core numeric paths for many spreadsheet functions; behavior changes (errors, edge magnitudes) are intentional but affect formula results users may rely on. > > **Overview** > Fixes **catastrophic cancellation** in variance, standard deviation, and related statistics when the mean is large relative to the spread (e.g. `STDEV.S` on values near `1e7` with tiny differences). > > Introduces **double-double arithmetic** (`doubleDouble.ts`) and two aggregation layers: **`MomentsAggregate`** for composable `VAR`/`STDEV`/`SUBTOTAL`/database `DVAR`/`DSTDEV` (shifted sums with power-of-two scaling), and **`deviationSums.ts`** for `DEVSQ`, `COVARIANCE`, `SLOPE`, and `STEYX`. **`STEYX`** now sums squared residuals per point (with centered residuals) instead of a formula that cancels on nearly collinear data. > > **`SLOPE`** and **`STEYX`** return **`#DIV/0!`** with *"All x values are equal."* when all x values match. **`AVERAGE`** / **`AVERAGEA`** use a lightweight **`AverageResult`** (sum + count) instead of the variance aggregate. Release script step 3 reads **`CURRENT_VERSION`** after the step label. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 91456eb. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Co-authored-by: Kuba Sekowski <kuba.sekowski.dev@gmail.com>
…te it The code-freeze step 8 repointed demo URLs only in docs/guide and docs/index.md, so the StackBlitz link in README.md stayed on 3.4.x. The step now scans README.md too, and step 9 stages it, so the rewrite is committed with the release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NNPv319TtBWAMhLYXNjoLS
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NNPv319TtBWAMhLYXNjoLS
check:licenses excluded hyperformula@3.1.1 from its own license check, so since 3.1.1 it failed on the package itself (GPL-3.0-only is not on the allow list). license-checker matches exclusions by exact name@version only, so the code-freeze step 3 now sets the exclusion to the new version next to the version bump, skips it when it already matches, and warns if the script no longer carries the exclusion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NNPv319TtBWAMhLYXNjoLS
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
hyperformula-docs | fe16eed | Commit Preview URL Branch Preview URL |
Oct 08 2026, 09:16 AM |
|
The job fails with The There is no fix to port: it needs a token with read access to Generated by Claude Code |
…changelog MAXPOOL and MEDIANPOOL (#1718): - The catalogue entries and the JSDoc said that a window_size or stride that is not a positive integer returns #VALUE!; it returns #NUM!. - A stride greater than window_size now works in a cell (it returned #VALUE! in 3.4.0, while calculateFormula() returned the result). The catalogue, the JSDoc and a new Changed entry describe it, and the isPoolWindowFittingInputArray() JSDoc no longer says the windows tile the range. - The Fixed entry now names both errors and the RangeError it replaced. Also: - STEYX: replace the garbled short description. - Changelog: name COVARIANCE.P and COVARIANCE.S instead of COVARIANCE, and list DEVSQ in the very-small-values fix. The release notes mirror the changelog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NNPv319TtBWAMhLYXNjoLS
|
FOSSA It is red on What I checked:
Someone with FOSSA access needs to open the report linked from the status and say which dependency or file it flags. Once we know, I'll fix it on this branch, or it can be ignored in FOSSA if it's acceptable. Generated by Claude Code |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1801 +/- ##
==========================================
- Coverage 97.31% 97.28% -0.04%
==========================================
Files 195 207 +12
Lines 15734 16218 +484
Branches 3456 3588 +132
==========================================
+ Hits 15312 15777 +465
- Misses 414 433 +19
Partials 8 8
🚀 New features to boost your workflow:
|
Performance comparison of head (fe16eed) vs base (af2d59d) |
Context
Release branch for HyperFormula 3.5.0. It contains
developand the release commit fromnpm run release -- code-freeze: the version bump,HT_RELEASE_DATE, the 3.5.0 CHANGELOG section, the release notes, and the demo URLs pointing at3.5.x. The release-branch review added these fixes:55467f6: README demo link, plus the release script.README.mdstill linked the3.4.xStackBlitz demo, because step 8 ofcode-freezeonly rewrote demo URLs indocs/guide/anddocs/index.md. The link now points at3.5.x. Step 8 now also scansREADME.md, and step 9 stages it so the rewrite is committed.00c0dd7: changelog formatting.MAXPOOLandMEDIANPOOLare formatted as code, like the other function names.60734d6: thecheck:licensesexclusion, plus the release script.npm run check:licensesexcludedhyperformula@3.1.1from its own license check. Since 3.1.1 it has failed on the package itself, because GPL-3.0-only is not on the allow list.license-checkeronly matches exclusions by exactname@version, so the exclusion now nameshyperformula@3.5.0.code-freezenow updates the exclusion together with the version bump. It skips the update when the exclusion is already current and warns when the script no longer has one.fe16eed: accurate docs and changelog for Fix MAXPOOL and MEDIANPOOL throwing on non-tiling dimensions #1718 and Fix precision loss in variance, standard deviation and related statistics (HF-491) #1784.MAXPOOL/MEDIANPOOLreturned the following:calculateFormula()window_size/stridenot a positive integer#NUM!#VALUE!stride>window_size#VALUE!The catalogue entries and the JSDoc said
#VALUE!for the first row, and none of the docs mentioned the third row. Both are now described in the catalogue, the JSDoc and the changelog: the Fixed entry names both errors, and a new Changed entry covers the larger stride.STEYXgets a readable short description instead of the garbled one.The changelog says
COVARIANCE.P/COVARIANCE.Sinstead ofCOVARIANCE, and listsDEVSQin the very-small-values fix.DEVSQwas fixed for very small values only; its results for large values are unchanged.release-README.mddocuments both script changes, and the release notes mirror the changelog.npm run release -- publish 3.5.0merges this branch intomasteranddevelop; this PR tracks CI and review in the meantime.How did you test your changes?
a704172is green: Test, including the private suite and browser tests, Lint, Build on various envs, Build docs, and Security.Vendored parser / driftis red on this PR for a reason outside it; see the comment below. It fails the same way ondevelop.npm ci,npm run bundle-all,verify:typings,verify:publish-package,tsc --noEmitandnpm run lint(0 errors, no new warnings) pass, and so do the smoke tests.npm run check:licensesnow passes.npm run docs:generate-function-docsrenders the new descriptions, andgetFunctionDetails()returns them.MAXPOOL/MEDIANPOOLtable and theDEVSQclaim come from evaluating the same formulas on the published 3.4.0 package and on this branch.release.sh, on copies of the old files, covering the dry run, the real run, a re-run and the warning path.bash -n script/release/release.shpasses.Types of changes
Related issues:
Checklist:
🤖 Generated with Claude Code
https://claude.ai/code/session_01NNPv319TtBWAMhLYXNjoLS