Skip to content

HF-20: rename iterative-calculation config options to match sequba's own review request (#1541) - #1756

Open
marcin-kordas-hoc wants to merge 82 commits into
feature/issue-1545from
fix/hf-1541-config-naming
Open

HF-20: rename iterative-calculation config options to match sequba's own review request (#1541)#1756
marcin-kordas-hoc wants to merge 82 commits into
feature/issue-1545from
fix/hf-1541-config-naming

Conversation

@marcin-kordas-hoc

@marcin-kordas-hoc marcin-kordas-hoc commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Context

Targets your branch (feature/issue-1545), not develop — this is meant to land inside your
own iterative-calculation work before it ever goes out, per HF-20.

Stacks on top of what's already on this branch: a conflict-free merge of origin/develop, plus
three small fixes (the unfinished Evaluator rename that broke the private test suite's compile,
an orphaned spec left over from removing test/unit/, and a changelog entry that git silently
re-attached under the wrong released version during the merge). None of that is touched again
here — this commit only renames.

What and why

On #1541 — the community PR
(mountEvarus) this branch's WIP grew out of — you asked for this exact naming, in your own
review comments (2025-09-24):

enableIterativeCalculation: boolean,
I'd rename this option to make it similar to the analogues feature in other spreadsheet software.

This should be configurable. I'd introduce a new flag iterativeCalculationLimit

I'd introduce a new flag iterativeCalculationThreshold.

The WIP on this branch ended up shipping different names:

Ships on this branch today You asked for (PR #1541)
iterativeCalculationEnable enableIterativeCalculation
iterativeCalculationMaxIterations iterativeCalculationLimit
iterativeCalculationConvergenceThreshold iterativeCalculationThreshold

This PR renames all three call sites + the public ConfigParams surface + the docs guide to match
what you asked for. iterativeCalculationInitialValue is untouched — you never named it, and it
already reads fine alongside the other three. Also renamed the now-mismatched private method
validateIterativeCalculationMaxIterationsvalidateIterativeCalculationLimit for consistency.

Why now, not later: nothing with these names has ever shipped, so this rename is free. Once a
release goes out with iterativeCalculationEnable in it, fixing this becomes a breaking change.

Verified

  • npx tsc --noEmit -p tsconfig.json — clean
  • npx tsc --noEmit -p tsconfig.test.json — clean (the check browser-tests compiles specs
    through, per the karma/ts-loader config)
  • npx eslint src/Config.ts src/ConfigParams.ts src/Evaluator.ts — 0 errors (only pre-existing
    jsdoc/any warnings, not introduced here)
  • Full run of the paired private spec — 65/65 passing under the new names
    (handsontable/hyperformula-tests#50, opened against develop — no feature/issue-1545
    branch exists there to target, see that PR's own notes on why)

Not verified (no browser binary / no network in this environment): karma/browser-tests
itself, docs:build, test:performance. Flagging rather than claiming green on faith.

Update — two more Bugbot findings, fixed (commit 5a8cb26)

Bugbot's first pass on this PR (the full diff against your branch's old tip) surfaced two real
findings, neither caused by the rename — both came in through the earlier merge of
origin/develop and were simply never reviewed until now:

  1. DEV_DOCS.md told contributors to add interpreter specs to test/unit/interpreter/, a path
    that doesn't exist in this repo. Fixed to point at
    test/hyperformula-tests/unit/interpreter/.
  2. .github/workflows/lint.yml checks out hyperformula-tests before linting, but
    .eslintrc.js's typed-linting project only covers src/, so eslint . swept the private repo
    in with no TS project backing it. Verified locally: this never finished in 90s before the fix;
    16s after, with test/hyperformula-tests added to .eslintignore.

Both threads replied to with the fix commit + verification evidence, and resolved.

Paired tests branch

handsontable/hyperformula-tests#50 — opened against develop, not a Kuba branch:
hyperformula-tests has no feature/issue-1545 branch, so there was nothing to target there the
way this PR targets yours. test/fetch-tests.sh matches by literal branch name, so CI on this
PR picks up fix/hf-1541-config-naming in that repo automatically as long as #50's branch exists
(merge #50 before or alongside this one to keep that pairing intact).


Note

Medium Risk
CI now depends on a private test repo and deploy token; ESLint and docs build behavior change broadly, which can break PR checks or publishing if misconfigured, though production calculation code is largely untouched in this diff.

Overview
This diff is mostly developer experience, CI, and documentation infrastructure — not the iterative-calculation config renames described in the PR text (those names already appear in src/ but are not part of the shown patch).

AI agents & contributor entry points: Adds AGENTS.md and Cursor rules that point to it; replaces long CLAUDE.md / root CONTRIBUTING.md with stubs linking to AGENTS.md and docs/guide/contributing.md. Expands DEV_DOCS.md into the canonical maintainer guide (Definition of Done, function metadata catalogue rules, private test repo workflow) and adds DOCS_CONTENT_GUIDE.md.

Tests & lint in CI: Workflows now check out handsontable/hyperformula-tests under test/hyperformula-tests, run test/fetch-tests.sh, and use npm run test:ci instead of test:unit.ci. ESLint uses tsconfig.json (not tsconfig.test.json) and ignores the private checkout; setup file path moves to test/_setupFiles. .nvmrc bumps Node 16 → 22; root Makefile is removed.

Docs site & agents: VuePress serves under /docs/ with configurable build output; new md-companions plugin emits per-page .md companions and llms-full.txt, plus “View as Markdown” in the theme and a coding-agent setup wizard/page. The built-in functions guide is gitignored and generated from built-in-functions.tmpl.md and function metadata (hand-maintained page deleted). Nav adds AI/integration guides; Cloudflare Workers deployment is documented; context7.json indexes docs/.

Misc: Changelog entries for 3.2–3.4; forum link in issue template; README/demo StackBlitz branch 3.4.x; small fixes to row/column order docs; .gitignore for generated docs and Wrangler local state.

Reviewed by Cursor Bugbot for commit 5a8cb26. Bugbot is set up for automated code reviews on this repo. Configure here.

sequba and others added 30 commits January 2, 2026 17:00
Docs: banner we-are-hiring (#1595)

* Add We are hiring banner to the docs page

* Add We are hiring information to the readme file
#1612)

* Remove tests and test-related configuration from the public repository
* Fix package-lock file

* Docs: remove CodeSandbox embedded demos and add links to working exa,ples in Stackblitz (#1621)
* Extend CI For private tests

* Extend CI For private tests

* Extend CI For private tests

* Extend CI For private tests

* Extend CI For private tests

* Extend CI For private tests

* Extend CI For private tests

* Extend CI For private tests

* Extend CI For private tests

* Extend CI For private tests

* Extend CI For private tests

* Extend CI For private tests

* Extend CI For private tests

* Extend CI For private tests

* Extend CI For private tests

* Add npm script test:fetch-private

* Adjust eslintingore

* Adjust test.yml GH workflow

* Add performance.yml GH workflow

* Setup codecov.yml

* Remove Makefile

* Bring back removed npm scripts

* Update test/README.md

* Add setup files for jest and karma

* Move codecov.yml to the repository root

* Fix typo in eslintignore file

---------

Co-authored-by: Kuba Sekowski <kuba.sekowski.dev@gmail.com>
### Context

The IRR function returns `#NUM!` error when the initial investment
significantly exceeds the sum of returns (e.g., `=IRR({-150000, 12000,
15000, 18000})`). Excel correctly returns ~-41% for this case.

**Root cause:** The `irrCore` Newton-Raphson solver overshoots past the
lower bound of -1 on the first iteration when the solution is a strongly
negative rate. The code then unconditionally returns `#NUM!`.

**Fix:** Replace the unconditional error with a bisection-based clamp.
When Newton-Raphson overshoots past -1, bisect between the current rate
and -1: `newRate = (rate - 1) / 2`. This is guaranteed to stay in the
valid domain (`> -1`) and converges linearly until close enough for
quadratic Newton convergence to take over.

### How did you test your changes?

Added 5 unit tests in the private tests repo covering:
- Bug reproduction: `[-150000, 12000, 15000, 18000]` with default guess
- Reversed cash flow signs: `[150000, -12000, -15000, -18000]`
- Highly negative IRR (near total loss): `[-10000, 100, 100, 100]`
- Negative IRR with explicit guess
- Large investment with many small returns

All 42 IRR tests pass (37 existing + 5 new), no regressions.

### Types of changes
- [x] Bug fix (a non-breaking change that fixes an issue)

### Related issues:
1. Fixes #1628

### 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.
- [ ] 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**
> Small, localized numerical-solver change plus non-runtime
test/benchmark script updates; primary risk is altered IRR convergence
behavior on edge-case inputs.
> 
> **Overview**
> Fixes `IRR` returning `#NUM!` for strongly negative solutions by
clamping Newton-Raphson iterations in `irrCore` when the next step
overshoots past `-1` (bisects back into the valid domain instead of
immediately erroring).
> 
> Updates tooling/docs around the private test suite: renames the setup
script to `test:setup-private`, adjusts `fetch-tests.sh` to create a
missing branch from `develop` (and pull appropriately), and repoints
benchmark scripts to `test/hyperformula-tests/performance`. Also records
the IRR fix in `CHANGELOG.md`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
34b1265. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
…alue for an optional parameter (#1631)

## Problem

When a user writes `=ADDRESS(1,1,)` or `=ADDRESS(1,1,1,)`, the empty
argument
is coerced to `0`/`false` instead of using the parameter's declared
`defaultValue`.

Excel 2021 and Google Sheets treat empty args as the zero-value for the
type
(`0`/`FALSE`) for **all functions except ADDRESS**, where empty `absNum`
and
`a1Style` use their declared defaults (1 and `true`).

Fixes #1632

## Fix

- Add `emptyAsDefault` opt-in flag to `FunctionArgument` interface
- In `coerceArgumentsToRequiredTypes`: when `rawArg === EmptyValue` AND
`emptyAsDefault` is set AND `defaultValue` is declared → substitute
`defaultValue`
- Apply `emptyAsDefault: true` only to ADDRESS `absNum` and `a1Style`
parameters

## Tests

Regression tests in `handsontable/hyperformula-tests` (branch
`fix/empty-default-value`):
- ADDRESS: isolated tests for empty `absNum`, empty `a1Style`, both
empty
- LOG, MATCH, VLOOKUP, HLOOKUP: confirm empty → zero-value (not
defaultValue)
- `optional-parameters.spec.ts`: confirms empty args use zero-value
coercion (not defaultValue) when `emptyAsDefault` is not set



<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches core `FunctionPlugin` argument evaluation/coercion to
distinguish syntactically empty arguments, which could subtly affect
coercion behavior across many functions if misapplied. Change is gated
behind an opt-in `emptyAsDefault` flag and only enabled for `ADDRESS`
parameters in this PR.
> 
> **Overview**
> Fixes `ADDRESS` so syntactically empty optional arguments (e.g.
`=ADDRESS(2,3,,FALSE())`) use the parameter `defaultValue` instead of
being coerced to the type’s zero-value.
> 
> Adds an opt-in `emptyAsDefault` flag to `FunctionArgument` and extends
`FunctionPlugin`’s argument evaluation pipeline to track whether each
argument was syntactically empty, allowing coercion to substitute
`defaultValue` when `emptyAsDefault` is enabled. Documentation and
changelog are updated to reflect the new option and the `ADDRESS`
behavior fix.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
7c6fc7c. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adding `Definition of Done for the code changes` to the DEV_DOCS.md

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Documentation-only change that adds contribution/process guidance; no
runtime behavior or data/security impact.
> 
> **Overview**
> Adds a new **"Definition of Done"** section to `DEV_DOCS.md`
describing what production-code PRs must include before review (code
changes incl. i18n packs when relevant, tests expectations for internal
vs. external contributors, related docs/migration guide updates,
JSDoc/technical docs, changelog entry, and PR description).
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
0d7351f. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## Summary

Adds the `TEXTJOIN` function — joins text from multiple strings and/or
ranges with a configurable delimiter.

Replaces #1625 (was opened from fork, now from upstream branch
directly).

### Features
- Scalar and array/range delimiter support with cycling behavior
- `ignore_empty` parameter to skip empty strings
- Type coercion (numbers, booleans → strings)
- Error propagation from both delimiter and text arguments
- 32,767 character limit (Excel compatibility)
- i18n translations for all 17 supported languages
- Documentation in `built-in-functions.md`

### Implementation
- New `textjoin` method + `flattenArgToStrings` helper in `TextPlugin`
- `repeatLastArgs: 1` metadata pattern (same as SUMPRODUCT, etc.)
- Defensive `CellError` check on `coerceScalarToString` return value

### Changed files
| File | Change |
|------|--------|
| `src/interpreter/plugin/TextPlugin.ts` | `textjoin()` +
`flattenArgToStrings()` |
| `src/error-message.ts` | `TextJoinResultTooLong` message |
| `src/i18n/languages/*.ts` (17 files) | TEXTJOIN translations |
| `docs/guide/built-in-functions.md` | TEXTJOIN row (alphabetically
between TEXT and TRIM) |

### Review feedback addressed (from #1625)
- Tests moved to private `hyperformula-tests` repo (companion PR
pending)
- Fixed docs alphabetical ordering
- Fixed unsafe `as string` cast in `flattenArgToStrings`

## Test plan

- [x] 35 tests in
`hyperformula-tests/unit/interpreter/function-textjoin.spec.ts`
- [x] Full suite: 480 suites / 5396 tests passed

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Adds a new interpreter function (`TEXTJOIN`) with range flattening and
type coercion, which touches formula evaluation paths and may introduce
edge-case regressions around error propagation and large-string
handling.
> 
> **Overview**
> Adds the new `TEXTJOIN` spreadsheet function, including interpreter
support for joining scalars and ranges with a delimiter (including
delimiter cycling), optional skipping of empty strings, and consistent
error propagation.
> 
> Introduces a new `ErrorMessage.ResultTooLong` and enforces Excel’s
32,767-character output limit (returning `#VALUE!` when exceeded).
> 
> Updates function documentation, the unreleased changelog, and adds
`TEXTJOIN` translations across all supported language packs.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
81426d7. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Kuba Sekowski <jakub.sekowski@handsontable.com>
Co-authored-by: Claude <noreply@anthropic.com>
…ndoRedo (#1638)

## Summary

Fixes #1629. Closes #1633. Closes #1634.

Two unbounded memory leaks in long-running HyperFormula instances:

- **`LTAS.transformations[]`** grew linearly with every structural
operation (addRows, removeRows, moveCells, etc.) and was never cleaned
up. Fixed by introducing **threshold-based compaction** with
`versionOffset` — once 50+ transformations accumulate, all consumers
(FormulaVertex, ColumnIndex) are force-updated, then the array is
released while the logical version remains monotonically increasing.

- **`UndoRedo.oldData`** grew linearly even when entries were evicted
from the undo stack. Fixed by tracking which LTAS versions each
`UndoEntry` references (`getReferencedOldDataVersions()`), cleaning up
on eviction/clear, guarding against writes when `undoLimit === 0`, and
running orphan cleanup after compaction to handle a race condition where
lazy-apply re-inserts already-evicted keys.

### Changed files

| File | Change |
|------|--------|
| `LazilyTransformingAstService.ts` | `versionOffset`, `compact()`,
`needsCompaction()` with threshold=50, offset-aware iteration |
| `UndoRedo.ts` | `getReferencedOldDataVersions()` on interface + 7
subclasses, eviction cleanup, `undoLimit===0` guard,
`cleanupOrphanedOldData()`, `forceApply` parity in
`undoMoveRows`/`undoMoveColumns` |
| `HyperFormula.ts` | Compaction trigger in
`recomputeIfDependencyGraphNeedsIt()` |
| `ColumnIndex.ts` | `forceApplyPostponedTransformations()` — iterates
all ValueIndex entries |
| `ColumnBinarySearch.ts` | No-op `forceApplyPostponedTransformations()`
|
| `SearchStrategy.ts` | New method on `ColumnSearchStrategy` interface |
| `Operations.ts` | Added `columnSearch.forceApply` to undo path (no
compact — centralized in HyperFormula.ts) |

### What is NOT fixed here

- **Parser cache** (`ParserWithCaching`) — unbounded growth tracked
separately in #1635

## Test plan

- 18 dedicated tests in `hyperformula-tests` — see companion PR in that
repo
- Full test suite: **480 suites / 5396 tests passed**
- Benchmark validated threshold=50 as optimal (eager compaction is ~18×
slower on a 2.5k formula sheet)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches core recalculation/transform and undo/redo paths; while aimed
at memory safety, compaction/cleanup timing could affect formula
correctness or undo behavior in edge cases.
> 
> **Overview**
> Prevents unbounded memory growth in long-running engines by **adding
threshold-based compaction** of lazy formula transformations and by
**cleaning up undo snapshot (`oldData`) entries** when undo/redo stack
entries are cleared or evicted.
> 
> Introduces new config `maxPendingLazyTransformations` (default `50`)
and wires it into engine construction; when the threshold is reached,
`HyperFormula` forces pending transformations to be applied (dependency
graph + column search), compacts the transformation history, and prunes
orphaned `UndoRedo.oldData`. Column search strategies now expose
`forceApplyPostponedTransformations()` (real implementation for
`ColumnIndex`, no-op for binary search), and undo for move operations
ensures postponed transformations are applied before restoring old data.
Documentation and changelog are updated accordingly.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
d8ebe1d. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Kuba Sekowski <jakub.sekowski@handsontable.com>
## Problem

HyperFormula was missing the SEQUENCE dynamic array function for
generating sequential number arrays.

## Fix

Implements `SEQUENCE(rows, [cols], [start], [step])` as a new
`SequencePlugin`:

- Returns a rows×cols array of sequential numbers, filled row-major
- Parse-time array size prediction via `sequenceArraySize()` — handles
NUMBER and STRING literals; non-literal args (cell refs, formulas)
return `#VALUE!` (architectural limitation: array size must be known at
parse time)
- Error types match Excel: negative dims → `#VALUE!`, zero dims →
`#NUM!` (mapped from Excel's `#CALC!`)
- `emptyAsDefault: true` on optional params — empty args like
`=SEQUENCE(3,,,)` use declared defaults
- i18n for all 17 languages with proper Excel-localized names

## Changed files

| File | Change |
|------|--------|
| `src/interpreter/plugin/SequencePlugin.ts` | New plugin: `sequence()`
+ `sequenceArraySize()` |
| `src/interpreter/plugin/index.ts` | Plugin registration |
| `src/i18n/languages/*.ts` (17 files) | SEQUENCE translations |
| `docs/guide/built-in-functions.md` | SEQUENCE row in Array functions
table |
| `docs/guide/release-notes.md` | Unreleased section |
| `CHANGELOG.md` | Added entry |
| `test/smoke.spec.ts` | 3 smoke tests |
| `test/fetch-tests.sh` | Robustness fix for `git pull` |

## Tests

Regression tests in `handsontable/hyperformula-tests` (branch
`feature/SEQUENCE`):

| Group | Tests | Coverage |
|-------|-------|----------|
| Core sanity | #1#8 | Basic usage, MS docs examples |
| Default parameters | #9#13 | Omitted cols/start/step |
| Empty args | #14#21 | emptyAsDefault behavior |
| Step variants | #22#28 | Zero, negative, fractional step |
| Truncation | #29#35 | Fractional dims, trunc-to-zero |
| Error conditions | #36#48 | Zero/negative dims, text, arity,
propagation |
| Type coercion | #49#59 | Booleans, strings, cell refs, empty cells |
| Large sequences | #60#63 | 100×100, 1000×1, 1×1000 |
| Fill order | #64#69 | Row-major verification |
| Function combos | #70#74 | SUM, AVERAGE, MAX, MIN, COUNT |
| Behavioral | #75#80 | Max dims, spill |
| Dynamic args | #81#82 | Architectural limitation (cell ref → #VALUE!)
|

- 82/82 PASS confirmed in Excel desktop (Microsoft 365)
- 3 smoke tests in `test/smoke.spec.ts`



<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Adds a new array-producing built-in (`SEQUENCE`) with parse-time size
prediction rules; mistakes here can affect array vertex creation and
spill/error behavior across formulas. Remaining changes are
documentation/i18n updates plus a minor test script tweak.
> 
> **Overview**
> Adds the `SEQUENCE(rows, [cols], [start], [step])` built-in via a new
`SequencePlugin`, generating row-major numeric arrays and enforcing
dimension/max-sheet limits with appropriate errors.
> 
> Introduces parse-time result sizing (`sequenceArraySize`) that only
accepts literal `rows`/`cols` (non-literal dimensions now yield
`#VALUE!` due to unknown output size), and wires the plugin into the
interpreter exports.
> 
> Updates changelog and docs to list `SEQUENCE`, adds function name
translations across all language packs, and adjusts
`test/fetch-tests.sh` to pull explicitly from `origin` for the current
branch.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
b08cd79. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Add three new landing pages under Framework integration: HyperFormula
AI SDK, Integration with LangChain/LangGraph, and HyperFormula MCP
Server
- Rename sidebar "Overview" section to "About" and move it above
Miscellaneous (2nd to last)
- Promote "Getting started" to second position in sidebar (right after
Introduction)

## Test plan
- [ ] Run `npm run docs:dev` and verify sidebar order: Introduction →
Getting started → Framework integration → ... → About → Miscellaneous
- [ ] Verify new pages render at `/guide/ai-sdk`,
`/guide/integration-with-langchain`, `/guide/mcp-server`
- [ ] Verify existing Overview pages (Quality, Supported browsers, etc.)
still accessible at original URLs

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: documentation-only changes that add new guide pages and
reorder sidebar navigation without affecting runtime code.
> 
> **Overview**
> Adds three new guide pages describing AI-focused integrations:
`ai-sdk`, `integration-with-langchain`, and `mcp-server`.
> 
> Reorganizes the VuePress sidebar by renaming the prior *Overview*
section to **About**, moving it near the end, and renaming *Framework
integration* to **Integrations** while linking in the new AI docs pages.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
1688f23. 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.6 <noreply@anthropic.com>
Co-authored-by: Kuba Sekowski <jakub.sekowski@handsontable.com>
### Context
<!--- Why are your changes required? What problem do they solve? -->

https://app.clickup.com/t/9015210959/HF-116

### How did you test your changes?
<!--- Describe in detail how you tested your changes. -->

unit tests

### Types of changes
<!--- What types of changes does your code introduce? Put an `x` in each
box that applies. -->
- [ ] Breaking change (a fix or a feature because of which an existing
functionality doesn't work as expected anymore)
- [x] 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)
- [ ] Change to the documentation

### Checklist:
<!--- Go through the points below, and put an `x` in each box that
applies. -->
<!--- If you're unsure about any of these, contact us. We're always glad
to help! -->
- [ ] 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.
- [ ] 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.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **High Risk**
> Updates license validation logic, a compliance-critical area, by
adding a hardcoded key that will always be treated as valid; mistakes
here could unintentionally bypass licensing checks.
> 
> **Overview**
> Adds a new hardcoded trial license key
(`hftrial-0168e-1f2b7-47158-70b05-0842f`) to the whitelist in
`checkLicenseKeyValidity`, causing that exact value to be treated as
`valid` without schema/expiry checks.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
fd095ae. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
<!-- CURSOR_SUMMARY -->
> [!NOTE]
> **Low Risk**
> Low risk documentation-only changes: adds new guide pages and adjusts
VuePress sidebar navigation with no runtime or API impact.
> 
> **Overview**
> Adds three new AI-focused documentation pages: `ai-sdk`,
`integration-with-langchain`, and `mcp-server`, describing how to use
HyperFormula for deterministic spreadsheet computation in agent
workflows.
> 
> Updates the VuePress guide sidebar to surface these pages under
**Integrations**, renames the section from *Framework integration* to
*Integrations*, and moves the former *Overview* links into a new *About*
section.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
54c541b. 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: GreenFlux <support@greenflux.us>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary

- Implement 6 new functions: PERCENTILE, PERCENTILE.INC, PERCENTILE.EXC,
QUARTILE, QUARTILE.INC, QUARTILE.EXC
- New `PercentilePlugin` with inclusive/exclusive interpolation helpers
- i18n translations for all 17 languages (verified against Excel
function translator)
- CHANGELOG entry and built-in-functions.md updated

## Changes

- `src/interpreter/plugin/PercentilePlugin.ts` — new plugin
- `src/interpreter/plugin/index.ts` — export registration
- `src/i18n/languages/*.ts` — all 17 languages
- `docs/guide/built-in-functions.md` — 6 new entries (alphabetical)
- `CHANGELOG.md` — added entry

## Test plan

- [ ] 57 unit tests in hyperformula-tests (function-percentile.spec.ts)
- [ ] Excel validation workbook (107 cases) — open in Excel 365 desktop,
verify all PASS
- [ ] `npm run lint` passes
- [ ] `npm run compile` passes
- [ ] CI green

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Introduces new statistical function implementations and aliases in the
interpreter; main risk is correctness/edge-case parity with spreadsheet
semantics and potential impacts to function translation tables.
> 
> **Overview**
> Adds `PERCENTILE`/`QUARTILE` function families, including `.INC` and
`.EXC` variants, via a new `PercentilePlugin` that computes
percentiles/quartiles with linear interpolation and appropriate `#NUM!`
error handling for out-of-range inputs.
> 
> Registers the plugin export, adds function aliases
(`PERCENTILE`→`PERCENTILE.INC`, `QUARTILE`→`QUARTILE.INC`), and updates
built-in function documentation, changelog, and all language packs to
include translations for the new function names.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
7172a52. 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.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Kuba Sekowski <jakub.sekowski@handsontable.com>
## Summary
- Move hardcoded `base: '/'` and the implicit dist output path out of
`docs/.vuepress/config.js` so the docs site can be deployed under a
sub-path (e.g. `/docs/`) without editing the config file.
- Add `docs/.vuepress/build.config.js` as a single place to set
production values (`base: '/docs/'`, `dest: 'docs/.vuepress/dist/docs'`,
sitemap `hostname`).
- Resolution order for each setting: env var (`DOCS_BASE` / `DOCS_DEST`
/ `DOCS_HOSTNAME`) → `build.config.js` → existing built-in default.
`base` is normalized to start and end with `/`.
- No sitemap plugin change needed — `vuepress-plugin-sitemap` already
prepends `base` to every URL and writes `sitemap.xml` into the
configured `dest`.

## Test plan
- [ ] `npm run docs:build` completes successfully.
- [ ] Output is written to `docs/.vuepress/dist/docs/` (not
`docs/.vuepress/dist/`).
- [ ] `docs/.vuepress/dist/docs/index.html` references assets under
`/docs/...`.
- [ ] `docs/.vuepress/dist/docs/sitemap.xml` exists and every `<loc>` is
`https://hyperformula.handsontable.com/docs/...`.
- [ ] Overriding via env still works: `DOCS_BASE=/
DOCS_DEST=docs/.vuepress/dist npm run docs:build` reproduces the old
layout.
- [ ] `npm run docs:dev` still serves locally.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Medium risk because it changes docs build/deploy configuration (base
path, output directory, sitemap hostname) and upgrades the expected Node
version to 18, which could affect CI/hosting builds if environments
aren’t aligned.
> 
> **Overview**
> Makes the VuePress docs build configurable by introducing
`docs/.vuepress/build.config.js` and allowing `DOCS_BASE`, `DOCS_DEST`,
and `DOCS_HOSTNAME` to override `base`, build output `dest`, and sitemap
`hostname` (with `base` normalized to include leading/trailing `/`).
> 
> Updates deployment defaults to publish docs under `/docs/` and output
to `docs/.vuepress/dist/docs`, and adds `netlify.toml` plus a `.nvmrc`
bump to Node 18 to align the build environment.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
67f6148. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## Summary
Follow-up to #1663. The Netlify deploy succeeded but
`https://hyperformula-docs.netlify.app/docs/` returns 404.

Root cause: with `base: '/docs/'`, VuePress emits assets and internal
links under `/docs/...` and writes the build to
`docs/.vuepress/dist/docs/`. Setting `publish =
"docs/.vuepress/dist/docs"` made Netlify serve those files at `/`, so
the page rendered but every internal `/docs/...` reference 404'd. The
publish dir must be the **parent** of the base path so the on-disk
`docs/` subdirectory becomes the URL `/docs/`.

Change: `publish = "docs/.vuepress/dist"` in `netlify.toml`.

## Test plan
- [ ] Netlify deploys successfully.
- [ ] `https://hyperformula-docs.netlify.app/docs/` renders the docs
home (no 404).
- [ ] Sub-pages like `/docs/guide/demo.html` load with assets and CSS
intact.
- [ ] `/sitemap.xml` is reachable and entries point at
`https://hyperformula.handsontable.com/docs/...`.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk configuration-only change that affects where Netlify serves
built docs from; primary risk is misconfiguration leading to broken/404
docs paths.
> 
> **Overview**
> Fixes the Netlify deployment config by changing the `publish`
directory from `docs/.vuepress/dist/docs` to `docs/.vuepress/dist`,
ensuring the built `docs/` subdirectory is served at `/docs/` and
internal asset/link paths resolve correctly.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
be5594b. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
### Context

Implements all 12 Excel database functions (D-functions family).
Originally scoped to DCOUNT only, expanded to the full family since all
share the same infrastructure (field resolution, criteria parsing, row
matching).

### How did you test your changes?

- 185 unit tests in hyperformula-tests
(handsontable/hyperformula-tests#9)
- 167-case Excel validation workbook (all PASS in Excel Desktop)
- 147-test runtime integration suite + 30 edge case tests (booleans,
negatives, zeros, wildcards, large DB, comparison operators)
- Verified all error types match Excel precisely (#VALUE!, #DIV/0!,
#NUM!)

### Types of changes

- [x] New feature or improvement (a non-breaking change that adds
functionality)
- [x] Additional language file, or a change to an existing language file
(translations)
- [x] Change to the documentation

### Related issues:
1. Fixes HF-85

### Checklist:

- [x] I have reviewed the guidelines about Contributing to HyperFormula
and I confirm that my code follows the code style of this project.
- [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 file.
- [x] My changes require a documentation update.

---

## Summary

- 12 database functions: DCOUNT, DCOUNTA, DSUM, DAVERAGE, DMAX, DMIN,
DGET, DPRODUCT, DSTDEV, DSTDEVP, DVAR, DVARP
- New `DatabasePlugin` (533 lines) with shared infrastructure
- i18n translations for all 17 languages (proper Excel-localized names)
- Documentation: `built-in-functions.md` (Database section),
`known-limitations.md` (Nuances)

## Implementation

- `withDatabaseArgs()` helper eliminates boilerplate across all 12
functions
- `resolveFieldIndex()` — string (case-insensitive header match) or
1-based numeric index with `Math.trunc()`
- `buildDatabaseCriteria()` — OR across rows, AND within row, reuses
`CriterionBuilder`
- `rowMatchesCriteria()` — `.some()` (OR) + `.every()` (AND)
- `collectNumericValues()` — shared by DSTDEV/DSTDEVP/DVAR/DVARP

## Excel behavior edge cases

| Function | Edge case | Behavior |
|---|---|---|
| DMAX, DMIN, DPRODUCT | No matches | Returns 0 |
| DGET | 0 matches / 2+ matches | #VALUE! / #NUM! |
| DAVERAGE | No numeric values | #DIV/0! |
| DSTDEV, DVAR | ≤1 value | #DIV/0! (sample, n-1) |
| DSTDEVP, DVARP | 1 value / 0 values | 0 / #DIV/0! (population, n) |

## Linked

- Tests PR: handsontable/hyperformula-tests#9
- ClickUp: HF-85

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Adds new interpreter functionality that affects formula evaluation
semantics (criteria parsing, error handling, and aggregation behavior),
though changes are largely additive and isolated to a new plugin plus
docs/i18n updates.
> 
> **Overview**
> Adds a new `DatabasePlugin` implementing the 12 Excel database
functions (`DCOUNT`, `DCOUNTA`, `DSUM`, `DAVERAGE`, `DMAX`, `DMIN`,
`DGET`, `DPRODUCT`, `DSTDEV`, `DSTDEVP`, `DVAR`, `DVARP`), including
shared logic for field resolution, criteria parsing, row matching, and
Excel-like error propagation.
> 
> Updates the public surface by exporting the plugin, adding
translations for these functions across language packs, and expanding
docs/CHANGELOG to include a new **Database** functions category and
function list.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
74c4397. 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.6 (1M context) <noreply@anthropic.com>
…1653)

## Summary

Expand the four framework integration pages (React, Angular, Vue,
Svelte) from one-line redirects into self-contained guides with code
snippets extracted from the respective Stackblitz demos.

Each guide's primary snippet is a simplified version of the demo's
framework pattern — same lifecycle hooks, same service architecture,
same reactivity approach — with simplified data (`buildFromArray`
instead of Employee Table).

## Design rationale

### Snippets from demos, not invented patterns

Per review feedback: every code snippet must match what's in the
corresponding Stackblitz demo. This ensures the snippets are tested,
idiomatic, and consistent with what users see when they click the demo
link. Patterns not present in demos (e.g., Angular Signals, Svelte 5
runes) are deliberately excluded until validated by a framework expert.

| Framework | Demo file | Primary pattern in guide |
|---|---|---|
| React | `react-demo/src/lib/employee/employee.provider.tsx` | `useRef`
+ `useEffect` init/cleanup + `useState` |
| Angular | `angular-demo/src/app/employees/employees.service.ts` |
`@Injectable` + `BehaviorSubject` + `async` pipe |
| Vue | `vue-3-demo/src/lib/employees-data-provider.ts` | Class wrapper
with private HF field + `ref` |
| Svelte | `svelte-demo/src/routes/Hyperformula.svelte` |
`buildFromArray` + `getCellValue` + `on:click` + `onDestroy` |

### Other decisions

- **TypeScript** in all snippets (HF ships `.d.ts` typings)
- **`licenseKey: 'gpl-v3'`** in every snippet (without it, engine throws
license warning)
- **SSR notes** for Next.js, Nuxt, SvelteKit (HF is SSR-safe — no
browser-only API dependency — but instantiating it server-side is wasted
work, so each framework's SSR section defers to client lifecycle)
- **VuePress template fix** — Stackblitz links use `<a :href>` Vue
binding instead of `{{ }}` interpolation in markdown

## Test plan

- [x] Render docs locally / verify all four integration pages — all 4
pages return HTTP 200 on the Netlify deploy preview for the latest
commit (proxies `npm run docs:dev`)
- [x] Click each Stackblitz demo link — all 5 URLs (4 frameworks +
custom-functions) reachable, each
`hyperformula-demos@3.2.x/<framework>-demo` subdir exists
- [x] Verify primary snippets match demo patterns — React:
`useRef`/`useEffect`/`useState`; Angular:
`@Injectable`/`BehaviorSubject`/`async` pipe; Vue: class wrapper + `ref`
(the `markRaw` pattern is documented in Troubleshooting, not the primary
snippet); Svelte: `buildFromArray`/`getCellValue`/`on:click`/`onDestroy`
- [x] Verify no untested patterns remain — no Signals, no
`$state`/`$derived` runes, no NgZone; Pinia is mentioned only in a Vue
Troubleshooting note that warns against putting the engine into Pinia
state, not as a recommended pattern
- [x] Confirm `licenseKey: 'gpl-v3'` present in every snippet —
react/angular: 1× (main snippet); vue: 2× (main + Troubleshooting
markRaw demo); svelte: 2× (basic + SSR variants)
- [x] Confirm `destroy()` cleanup present in every applicable component
snippet — react/angular: 1× (main snippet); vue: 1× (main snippet —
Troubleshooting markRaw demo is illustrative, not a full component);
svelte: 2× (basic + SSR variants)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk documentation-only change that adds new framework-specific
guidance and code snippets; no runtime/library behavior is modified.
> 
> **Overview**
> **Expands the framework integration docs** (Angular, React, Svelte,
Vue) from brief install notes into self-contained guides with concrete
TypeScript-centric examples for initializing HyperFormula, surfacing
calculated values in each framework’s reactivity model, and cleaning up
via the appropriate lifecycle hook.
> 
> Adds SSR-specific notes for Angular Universal, Next.js, Nuxt, and
SvelteKit, and standardizes demo links by switching Stackblitz URLs to
Vue-bound `<a :href>` so the cache-busting query param renders correctly
in VuePress (also applied to `custom-functions`).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
1ecce54. 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>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…#1669)

## Summary
- Reframe [docs/guide/ai-sdk.md](docs/guide/ai-sdk.md) around a
HyperFormula + Vercel AI SDK integration with a single integrated
`generateText` example as the only code block.
- Mark the SDK as an unreleased prototype via a top-of-page warning
callout, and add a prominent waitlist CTA with the existing HubSpot
form.
- Rename the sidebar entry in
[docs/.vuepress/config.js](docs/.vuepress/config.js) to "Integration
with Vercel AI SDK".

Closes [HF-53](https://app.clickup.com/t/86c7upjar). Supersedes #1667.

## Test plan
- [ ] `npm run docs:dev`, open `/guide/ai-sdk`, confirm sidebar reads
"Integration with Vercel AI SDK" and the prototype callout sits above
the fold.
- [ ] Confirm the Vercel `generateText` snippet is the only code block
and the page no longer carries Install / Setup / All options /
TypeScript sections.
- [ ] Click the waitlist link and external links (Vercel docs, GitHub,
npm); click internal links to built-in / custom functions guides.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: documentation-only changes (sidebar label and guide content)
with no runtime or API behavior impact.
> 
> **Overview**
> Reframes `docs/guide/ai-sdk.md` as **HyperFormula tools for the Vercel
AI SDK**, adding a top-of-page *prototype/not-yet-released* warning, a
single `generateText`-based example, updated use cases, and a waitlist
CTA plus relevant links.
> 
> Renames the guide’s sidebar entry in `docs/.vuepress/config.js` from
“HyperFormula AI SDK” to **“Integration with Vercel AI SDK”**.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
3eb3e85. 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: Joseph Petty <greenflux@Josephs-MacBook-Pro.local>
marcin-kordas-hoc and others added 23 commits July 23, 2026 15:59
## What & why

Implements **SORT** (`HF-69`, child of HF-28 "Modern dynamic array
functions", sibling of the shipped SEQUENCE and of VSTACK/HSTACK). Adds
`SORT(array, [sort_index], [sort_order], [by_col])` as a dynamic array
function.

Tests: handsontable/hyperformula-tests#24 (paired).
Sibling PR: #1708 (UNIQUE / HF-68).
ADR: `docs/adr/2026-07-13-sort-unique-array-functions.md`.

## Behavior

- Returns an array the **same shape** as the input.
- `sort_index` (default 1): 1-based index into the sort dimension.
- `sort_order`: `1` ascending (default) or `-1` descending.
- `by_col`: `FALSE` (default) reorders rows; `TRUE` reorders columns.
- Ordering reuses `ArithmeticHelper` (mixed types: numbers < text <
logical; empties; locale collation via
`caseSensitive`/`accentSensitive`) and is **stable** — ties keep input
order.

## Design

Mirrors the SEQUENCE/FILTER machinery: `sizeOfResultArrayMethod` +
`vectorizationForbidden: true`, runtime via `runFunction` returning
`SimpleRangeValue`/`CellError`, parse-time size method returning a
**fresh** `ArraySize` (the input's `isRef` flag is dropped — a
ref-flagged size is treated as scalar and would collapse the spill).

## Notes — divergences from Excel (surfaced here + inline + in tests)

- **`sort_order` is strictly `{1, -1}`**; any other value → `#VALUE!`.
Excel documents only `{1,-1}`; the reported "`sort_order=0` does not
error" quirk is undocumented and **could not be re-verified against live
Excel in this environment**, so the strict documented contract was
chosen (see ADR `dec_2`, `con_1`). Flagged for live-Excel/Kuba
confirmation.
- **Multi-key array-constant `sort_index`** (e.g. `{1,2}`) is **not
supported in v1** (documented in `known-limitations.md`; ADR `dec_6`).
- In-range errors propagate (first error found; ADR `dec_7`).

## Error-type map

`sort_order ∉ {1,-1}` → `#VALUE!` (BadMode) · `sort_index < 1` →
`#VALUE!` (LessThanOne) · `sort_index >` dimension → `#VALUE!`
(ValueLarge) · in-range error → propagate · wrong arity → `#N/A`.

## Definition of Done

- [x] Production code (`SortPlugin.ts`, registered via
`plugin/index.ts`)
- [x] i18n — all 17 language packs (authoritative MS Functions
Translator names; enUS inherits enGB)
- [x] Tests (paired tests PR) — across the standard array-function
groups, dual-env safe
- [x] Docs — `built-in-functions.md`, `known-limitations.md`
- [x] JSDoc on all methods
- [x] CHANGELOG entry
- [x] ADR with audit-verified citations

Source: https://app.clickup.com/t/86c89q1tt

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Self-contained new array function following existing SEQUENCE/FILTER
patterns; no changes to auth, persistence, or core recalculation beyond
registering one plugin.
> 
> **Overview**
> Adds the **SORT** dynamic array function: `SORT(Array, [SortIndex],
[SortOrder], [ByCol])` returns the input range reordered by row
(default) or column, same dimensions as the source.
> 
> Implementation lives in new `SortPlugin.ts`, wired like other array
functions (`sizeOfResultArrayMethod`, `vectorizationForbidden`, spill
size copied from input without propagating `isRef`). Sort keys use
`ArithmeticHelper` (with empty cells forced last); invalid `sort_order`
(not `1` or `-1`), bad `sort_index`, in-range errors, and empty ranges
get the documented `#VALUE!` / `#N/A` / error propagation behavior.
> 
> Docs and changelog are updated; **known-limitations** documents
single-key only, strict sort order, and HF comparison rules. **SORT** is
added to all 17 language packs.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
f081be5. 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>
## What & why

Implements **UNIQUE** (`HF-68`, child of HF-28 "Modern dynamic array
functions", sibling of the shipped SEQUENCE and of VSTACK/HSTACK). Adds
`UNIQUE(array, [by_col], [exactly_once])` as a dynamic array function.

Tests: handsontable/hyperformula-tests#25 (paired).
Sibling PR: #1707 (SORT / HF-69).
ADR: `docs/adr/2026-07-13-sort-unique-array-functions.md`.

## Behavior

- Returns the **distinct rows** (or columns when `by_col` is `TRUE`) of
the input, preserving first-occurrence order.
- `by_col`: `FALSE` (default) compares rows; `TRUE` compares columns.
- `exactly_once`: `TRUE` returns only rows/columns occurring exactly
once; `FALSE` (default) returns all distinct.
- Equality reuses `ArithmeticHelper.eq` → **case-insensitive by
default** (honors `caseSensitive`), matching Excel's UNIQUE.
- Result size is data-dependent; mirrors FILTER (predict input size as
upper bound, return the smaller actual result).

## Design

Mirrors the FILTER machinery for dynamic-size results:
`sizeOfResultArrayMethod` + `vectorizationForbidden: true`, runtime via
`runFunction`, parse-time size method returning a **fresh** `ArraySize`
(drops the input's `isRef` flag). Deduplication is O(n²) in the number
of vectors because locale-aware equality is not trivially hashable —
noted in code; acceptable for v1.

## Notes — divergences from Excel (surfaced here + inline + in tests)

- **Empty result** (only via `exactly_once` when nothing occurs exactly
once) → `#N/A`. Excel returns `#CALC!`, which HyperFormula has no type
for; mirrors FILTER's empty-result mapping (ADR `dec_8`).
- Comparison honors HF's collation config rather than a byte-for-byte
Excel oracle (no live Excel in this environment; ADR `con_1`).
- In-range errors propagate (first error found; ADR `dec_7`).

## Definition of Done

- [x] Production code (`UniquePlugin.ts`, registered via
`plugin/index.ts`)
- [x] i18n — all 17 language packs (authoritative MS Functions
Translator names; enUS inherits enGB)
- [x] Tests (paired tests PR) — across the standard array-function
groups, dual-env safe
- [x] Docs — `built-in-functions.md`, `known-limitations.md`
- [x] JSDoc on all methods
- [x] CHANGELOG entry
- [x] ADR with audit-verified citations

Source: https://app.clickup.com/t/86c89q1tq

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Additive array function behind existing dynamic-array machinery; no
changes to auth, persistence, or core evaluation paths beyond new plugin
registration.
> 
> **Overview**
> Adds the Excel-style **`UNIQUE(array, [ByCol], [ExactlyOnce])`**
dynamic array function so formulas can return distinct rows or columns
with first-occurrence order preserved.
> 
> **`UniquePlugin`** implements deduplication via `ArithmeticHelper.eq`
(honors `caseSensitive` / `accentSensitive`), optional column-wise mode
and “exactly once” filtering, propagates the first in-range error, and
returns **`#N/A`** when `ExactlyOnce` would yield an empty result
(aligned with FILTER). Spill sizing follows FILTER: parse-time upper
bound from input dimensions, `vectorizationForbidden: true`, and a fresh
`ArraySize` so `isRef` is not carried through.
> 
> Also registers the plugin, adds **`UNIQUE`** to all language packs,
documents the function and known limitations, and records the change in
the changelog.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
a4097a4. 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: Kuba Sekowski <jakub.sekowski@handsontable.com>
…guide (HF-154) (#1703)

### Context

HF-154 — make the HyperFormula docs friendlier to coding agents and
LLMs. On top of the existing VuePress portal this adds:

- **Per-page `.md` companions** — every doc page is also served as
clean, VuePress-stripped Markdown (a build-time `md-companions` plugin),
plus an aggregate `llms-full.txt`. Both are also mirrored to the site
root so `/llms.txt` and `/llms-full.txt` resolve on GitHub Pages (prod)
and the Netlify preview.
- **`llms.txt`** — a top-level index pointing agents at the Markdown
sources.
- **View-as-Markdown link** (`ViewMarkdownLink.vue`) — links to the
page's `.md` source so an agent can open and read it directly.
- **Coding-agent setup guide** (`docs/guide/setup-coding-agent.md`) plus
a `CodingAgentWizard.vue` helper.
- **`context7.json`** so agent doc-access tooling (Context7 / GitMCP)
can discover the sources.

The Markdown stripper (`md-companions/strip.js`) is the
fidelity-critical piece: it turns VuePress-flavoured Markdown (`:::`
containers, `<script>`/Vue components, `[[toc]]`, `{{ }}` bindings,
Vue-bound `<a :href>`/`<img :src>`, live `:::example` demos, nested code
fences) into clean Markdown.

### How did you test your changes?

- Manually verified the stripper on representative inputs (tip/warning
containers, nested code fences, Vue-bound `<a :href>`/`<img :src>`,
`[[toc]]`, `<script>`/component removal, `:::example` demos) —
confirming code fences, **including Vue-shaped samples inside container
bodies**, survive verbatim while prose is cleaned.
- Sanity-checked the full corpus against the Netlify deploy-preview
(`llms-full.txt` populated, per-page `.md` clean, no leaked components).
- `npm run lint` clean.

### Note on test coverage

The `md-companions` stripper currently has **no automated regression
tests**. The earlier `test/docs/*.spec.js` suites (strip / corpus /
generated) were removed in `363a5e2` — the public repo carries smoke
tests only (`test/README.md`), and this is build-time docs tooling, not
shipped `src/` code, so a stripper regression degrades the generated
`.md`/`llms.txt`, not engine/product behaviour. The working safety net
is Cursor Bugbot + the Netlify deploy-preview + review.

That said, this is the **second fidelity bug** in the stripper (after
the Vue-bound link/image fix), and the removed suites did **not** cover
the case that regressed here — Vue markup inside a code fence sitting
**inside** a container. If we want coverage, the right home is the
private `hyperformula-tests` repo (restore the removed cases + this
container-fence intersection).

### Types of changes
- [x] New feature or improvement (a non-breaking change that adds
functionality)
- [x] Change to the documentation

### Related issues:
1. HF-154

### Checklist:
- [x] I have reviewed the guidelines about Contributing to HyperFormula
and I confirm that my code follows the code style of this project.
- [x] I described my changes in the CHANGELOG.md file.

### Notes

- Supersedes #1696 (moved to an upstream branch so CI can access the
private `hyperformula-tests` repo). Current `develop` is merged in.
- The docs-portal Astro migration (#1686) is a separate track. If it
lands first it supersedes this VuePress plumbing (the `md-companions`
plugin and `.vue` components are VuePress-specific); the agent-friendly
*outputs* (`.md` companions, `llms.txt`, setup guide) would need
re-homing in Astro.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Docs-site and build-time tooling only; no changes to `src/` engine
behavior. Main operational risk is stripper fidelity regressions in
generated `.md`/`llms-full.txt` (currently manual/preview validation, no
automated stripper tests in this PR).
> 
> **Overview**
> Adds **LLM/agent-friendly documentation outputs** on top of the
existing VuePress docs build, without changing the spreadsheet engine.
> 
> A new **`md-companions` VuePress plugin** runs at build time: it
strips VuePress-only syntax (`:::example` demos, Vue components,
`[[toc]]`, bound `<a :href>` / `<img :src>`, etc.) via **`strip.js`**,
resolves injected `{{ $page.* }}` values, rebases root-relative links
for the docs `base`, and writes a **clean `.md` companion** beside each
HTML page plus an aggregated **`llms-full.txt`** (with absolute links in
the corpus). **`context7.json`** points Context7-style tooling at the
`docs` folder with project-specific rules.
> 
> The **local theme** injects **`ViewMarkdownLink`** (“View as
Markdown”) on every page; **`setup-coding-agent.md`** and
**`CodingAgentWizard`** document Claude Code skills, Cursor/Copilot
rules, MCP (GitMCP / Context7), and copyable snippets (via
**`clipboard.js`**). Sidebar and **`DEV_DOCS.md`** /
**`docs/README.md`** are updated accordingly. **Netlify** build uses
**Node 22**; **`.eslintrc.js`** gets a minor override-array syntax fix.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
f2e95fc. 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 4.6 <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: Kuba Sekowski <sequba@gmail.com>
…sand separator (DEV-2120) (#1713)

## Summary

Entering a long run of digits ending in a non-digit character (e.g.
`012345678901234567890123456789012345678901234567890123456789a`) into a
cell froze the page when formulas were enabled. Reported as
[#1520](#1520), where
the non-digit character is a space. DEV-2120 / HOT-9767.

## Root cause

`NumberLiteralHelper` builds its number-detection pattern by
interpolating the configured separators. With the **default**
`thousandSeparator: ''`, the group `(${thousandSeparator}\d{3,})*`
degenerates to `(\d{3,})*` placed immediately after `\d+`:

```
^([+-]?((\.\d+)|(\d+(\d{3,})*(\.\d*)?)))([eE][+-]?\d+)?$
             └──── nested quantifiers on the same class ────┘
```

For a long digit run that ultimately fails to match (trailing
non-digit), the engine explores exponentially many ways to partition the
digits between `\d+` and the repeated `\d{3,}` group — classic
catastrophic backtracking (ReDoS). Parse time roughly doubles every ~2
characters, so a 60-character input never returns.

The same pattern is reached from raw cell input (`CellContentParser`)
**and** from string→number coercion during formula evaluation
(`ArithmeticHelper`), so `=VALUE("…")` and arithmetic over such text
hung too. Fixing the pattern builder covers all entry points.

## Fix

Omit the thousand-separator group entirely when the separator is empty.
The emitted pattern for a non-empty separator (`,`, ` `, `.`) is
byte-for-byte unchanged — a literal separator is a mandatory anchor
between repetitions, so no ambiguous partition exists and those configs
were never vulnerable.

## Testing

Paired tests in handsontable/hyperformula-tests (branch
`fix/dev-2120-redos-number-parsing`):
- white-box guard that the default-config pattern contains no nested
digit quantifier (deterministic regression tripwire — a synchronous
ReDoS cannot be caught by a per-test timeout);
- behavioral coverage: trailing letter, trailing non-letter symbol,
separator matrix, long-integer value fidelity;
- end-to-end via `setCellContents` (raw, percent, currency) and formula
coercion (`=VALUE(...)`);
- the verbatim reproduction from
[#1520](#1520) (90
digits, a space, then `123`) built through `buildFromArray` with the
sheet layout from the issue, including the dependent `=SUM(A1,B1)`
formula.

## Reviewer notes

- **Why the white-box test** (asserting `numberPattern.source` has no
`(\d{3,})*`): a *synchronous* ReDoS cannot be caught by a Jest/Jasmine
per-test timeout — the timer can't fire while the regex is stuck on the
main thread — so asserting the emitted pattern shape is the one
deterministic regression tripwire. The behavioral/e2e tests still cover
actual behavior.
- **Verified against the reported input, not just a variant**: with the
`NumberLiteralHelper` change reverted, the new `#1520` test hangs until
killed (`timeout 90` → exit 124); with the fix it finishes in ~20 ms.
The default-config pattern goes from
`^([+-]?((\.\d+)|(\d+(\d{3,})*(\.\d*)?)))([eE][+-]?\d+)?$` to
`^([+-]?((\.\d+)|(\d+(\.\d*)?)))([eE][+-]?\d+)?$`.
- **Why no input-length cap**: the fix sits in the pattern builder, so
it covers every entry point at once, and non-empty separators are
provably linear (the literal separator anchors each repetition). A
length cap would be complementary defense-in-depth — deliberately left
out to keep this fix focused on the root cause.

## Notes

Branch brought up to date with `develop` by merge (not rebase) to
preserve review history.

Long-standing issue (reproduced on docs v17.1 and v18.0), not a v18
regression.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Small, targeted regex construction change with unchanged behavior for
non-empty thousand separators; low risk aside from edge cases in numeric
string detection.
> 
> **Overview**
> Fixes **UI freezes** when users enter a long digit string that fails
number parsing (e.g. trailing letter or space before more digits),
including the
[#1520](#1520)
reproduction.
> 
> `NumberLiteralHelper` no longer emits the `(\d{3,})*`
thousand-separator group when `thousandSeparator` is the default empty
string. That degenerate pattern sat next to `\d+` and caused
**catastrophic backtracking** on near-miss inputs; the same helper is
used for raw cell parsing and formula coercion (`VALUE`, arithmetic), so
one regex change covers those paths. Configs with a non-empty thousand
separator keep the previous pattern shape.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
6ad1637. 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: Kuba Sekowski <jakub.sekowski@handsontable.com>
…-249) (#1692)

## What & why

A function picker — the Formula Builder's, or any integrator's — needs
to answer two questions: *what functions exist?* and *what do this
function's arguments mean?* HyperFormula could only answer the first.
`getRegisteredFunctionNames()` returned 423 translated names and nothing
else; `getAllFunctionPlugins()` exposed `implementedFunctions`, which
has coercion rules and arity but no category, description,
human-readable parameter names or examples. Everything else a picker
needs existed only as prose, in the 551-line hand-maintained
`docs/guide/built-in-functions.md`.

And prose with no second copy drifts silently. That page published
`FVSCHEDULE` as `FV(Pv, Schedule)`, `RANDBETWEEN` as `RAND(...)`,
`COLUMN` as `COLUMNS(...)`, `F.TEST` with `Z.TEST`'s signature, `T.TEST`
with two of its four arguments, `DAYS360`'s arguments reversed; it
listed `NORMDIST` twice (the second row was really `NORMSDIST`) and
omitted `VERSION`, callable and counted in the page's own printed total
since 2020. Every one is a copy-paste from a neighbouring row — the
signature of data nothing can check against the thing it describes.

**So this PR moves that data into `src/` and reads it back through the
engine's own public API, making the published reference just its first
consumer.** A new authored catalogue under
`src/interpreter/functionMetadata/` (370 entries, one file per category,
730 parameter descriptions and 679 examples) supplies category,
description, `snake_case` parameter names and descriptions, examples and
a docs link; `implementedFunctions` still supplies arity and
optionality. Two methods expose the join, static and per-instance:

- **`getAvailableFunctions(code)`** → one cheap entry per function
(`localizedName`, `canonicalName`, `category`, `shortDescription`, plus
`aliasOf` on the 53 aliases), sorted by localized name under a collator
built from that language — enough to paint all 423 picker rows without a
second call.
- **`getFunctionDetails(canonicalName, code)`** → adds the ordered
`parameters` (name, description, `optional`), `repeatLastArgs`,
`documentationUrl` and `examples`. Deliberately **no pre-rendered syntax
string**; the caller composes `SUMIF(range, criteria, [sum_range])`
itself, and `script/formatFunctionSyntax.ts` is a reference
implementation of that renderer, kept out of `src/` on purpose.

`docs/guide/built-in-functions.md` becomes a build product of *the
public API* rather than of the catalogue directly — so generating the
page exercises the same alias resolution, listability gate and
optionality derivation a customer's picker will. It leaves git, is
gitignored, and is regenerated from `built-in-functions.tmpl.md` as the
first step of `docs:dev`/`docs:build`. "The docs are wrong" and "the API
is wrong" are now the same bug.

The regenerated page adds `VERSION`, gives `NORMSDIST` its own row, adds
a table of contents and a per-function anchor, and corrects optionality
on **27 functions** the old page showed as required (`IF`, `LOG`,
`ROUND`, `SUMIF`, `VLOOKUP`, the `*2*` conversions, …). Nine more change
only *notation*: a repeating argument group is now rendered as `...`
against `repeatLastArgs` instead of the old hand-written `[Range2,
Criterion2 [, ...RangeN, CriterionN]]`. `SWITCH` was also semantically
wrong — its parameters were `expression, value1, expression2`, but the
third argument is the *result* returned on a match.

## Design decisions worth a second opinion

- **The catalogue is authored data, not derived**, so it must be kept in
step with `implementedFunctions` by hand. Parameter *count* is
cross-checked, and on a mismatch **the implementation wins**:
`getFunctionDetails` reports one parameter per implemented argument
under positional names (`Arg1`, `Arg2`, …), discards the authored names
and descriptions, and warns on the console naming the function.
Category, description, examples and URL still come from the entry, and
the function stays listed in both tiers — drift costs the parameter
prose, never the availability. `DEV_DOCS.md` documents this and the
remaining silent-failure mode (an entry left behind after a rename
describes nothing and merely ships in the bundle).
- **Optionality is deliberately not authored or cross-checked** —
`optional` comes only from `optionalArg`/`defaultValue`. Hence the
single production edit outside the new module: `optionalArg: true` on
`SHEET`/`SHEETS`, which have always accepted `=SHEET()` while declaring
the argument required. Metadata-only and behaviour-neutral
(`runFunctionWithReferenceArgument` returns before argument-count
validation), and a sweep of all 423 ids found no other function with
this mismatch.
- **One rule decides how a function is described: does the catalogue
hold an entry for its id?** The catalogue is keyed by id, not by
implementation, so a user plugin registered over `SUMIF` is described
with `SUMIF`'s authored category and description, over its own
signature. An earlier revision gated this on a snapshot of built-in
plugin ownership, so a shadow reported as `'Custom'`; that is gone. It
bought little — a plugin re-implementing `SUMIF` is usually still a
`SUMIF` — and cost a module-init hook in `index.ts` (the plugin
identities can only come from the plugin barrel, and importing it from
the registry creates a load-order cycle that breaks the bundled build)
plus a second way for the whole built-in set to silently degrade to
`'Custom'` if that hook ever failed to run. **This is the bullet I'd
most like a second opinion on.**
- **Both tiers describe every registered function, custom ones
included.** `registerFunctionPlugin` is global, so a custom function is
callable everywhere and the static methods list it; the instance methods
list that instance's own registry, which differs when it was built with
the `functionPlugins` option. An id with no translation entry for the
active language is omitted from both, because the interpreter refuses to
evaluate it.
- **A custom function omits the fields it cannot author**, rather than
reporting an empty one: `shortDescription`, `documentationUrl` and
`examples` are absent (and optional in the public types, as `aliasOf`
already was), so a consumer can tell "no authored description" from "an
empty one" and the object survives `JSON.stringify` unchanged. Built-ins
are unaffected — the catalogue authors all three for every entry.
- **An instance describes its functions under the translation package it
was built with**, not under whatever is registered globally for that
code today. Otherwise re-registering a language could make the API
advertise a localized name that instance refuses to evaluate.
- **Exported:** `FunctionListEntry`, `FunctionDetails`,
`FunctionParameterDescription`, `FunctionCategory`.
`FUNCTION_CATEGORIES`, `FunctionDoc` and `CUSTOM_FUNCTION_CATEGORY` stay
internal — so a TS consumer cannot enumerate the categories to build a
filter and must compare against `'Custom'` as a string. Worth confirming
that is the right line.
- **`canonicalName` is matched exactly**: case-sensitive (`'sumif'` →
`undefined`, though `=sumif(...)` evaluates) and canonical English only.
Likeliest integration pitfall for a picker holding translated names.

## Already reviewed

Roughly three-quarters of the develop-diff is already reviewed and
merged, as sub-PRs into this branch: **#1699** (page generated from the
API), **#1705** (HF-300: examples, docs URLs, parameter descriptions),
**#1709** (`snake_case` parameter names), **#1710** (invalid-locale
collator guard). New here: the metadata API itself, the catalogue-keyed
resolution rule, custom functions in the static tier, `SHEET`/`SHEETS`,
the generated table of contents, and ~55 descriptions rewritten because
they documented Excel rather than HyperFormula — with the deviations
added to `list-of-differences.md` (`INT` truncates toward zero, `MOD`
takes the dividend's sign, `ISEVEN`/`ISODD` don't truncate,
`CEILING.MATH`/`FLOOR.MATH` honour only `mode` = 1).

## How I tested

Paired suite:
[handsontable/hyperformula-tests#14](handsontable/hyperformula-tests#14)
(branch `feature/hf-249-function-metadata-api`), 134 tests for this API
alone, green with the full repository suite (502 files, 6,214 tests). It
covers the static/instance split, i18n across all 18 packs, aliases,
custom functions, plugins shadowing a built-in id or a built-in alias
id, locale-aware ordering, and prototype-key ids (`toString`,
`__proto__`) — plus the two invariants most worth protecting: **every
canonical id declared by a registered built-in plugin resolves to
details**, so a missing catalogue entry fails CI instead of silently
dropping a function, and **the list and the details always agree on
which ids exist**. Each guard was mutation-tested: broken deliberately,
confirmed red, reverted. Assertions avoid jest-only matchers and never
rely on jest ignoring a key valued `undefined`, so they fail under the
jasmine/karma browser job too. Separately, all 679 authored examples
parse and name their own function, and a sampled slice is pinned to
Excel-cross-checked values.

## Known trade-offs

- **`examples` are English-spelled and `OFFSET` is lexed from its
translated name**, so `getFunctionDetails('OFFSET','deDE').examples`
yields `#NAME?` in all 16 non-English packs — and `ISREF`'s example
embeds `OFFSET`, returning `true` in enGB but `false` in plPL with no
error. The one item I'd want accepted with eyes open.
- **`SWITCH` publishes `repeatLastArgs: 1`**, understating its (value,
result) pair group. It cannot simply become `2`: the field also drives
runtime arity validation, and the optional trailing default needs a step
of 1.
- **`documentationUrl` is the same page for all 423 ids.** Per-function
anchors now exist on the generated page, so `#${canonicalName}` is a
follow-up, not a redesign.
- **The catalogue ships in the bundle** (~25 KB gzipped) and is not
tree-shakeable — `HyperFormula` and `FunctionRegistry` both import it
eagerly.

## Open for the reviewer

- **`CHANGELOG.md`** names only the two methods; it should also name the
four exported types, and needs a `### Changed` line for `SHEET`/`SHEETS`
now reporting their argument as optional.
- **`DEV_DOCS.md` carries general engineering policy** unrelated to
HF-249 (a `## Performance` section, six code-style bullets, and
additions to Definition of Done, Automatic tests and Documentation) —
which the atomic-PR rule added in this same PR says belongs elsewhere.
Split them out, or accept them explicitly.

Source: https://app.clickup.com/t/9015210959/HF-249

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Introduces a large, permanent public API and ships the full catalogue
in the bundle (~25 KB gzipped), while changing how published function
reference docs are produced; runtime formula evaluation is largely
unchanged aside from metadata alignment (e.g. optional reporting for
zero-arg reference functions).
> 
> **Overview**
> Adds **`getAvailableFunctions`** and **`getFunctionDetails`** (static
and instance) so integrators can build function pickers from engine data
instead of scraping docs. Metadata is authored in a new per-category
catalogue under `src/interpreter/functionMetadata/` (joined with
`implementedFunctions` for arity, optionality, and `repeatLastArgs`);
custom functions appear with category `'Custom'` and positional `ArgN`
names unless a plugin shadows a built-in id, in which case the catalogue
entry for that id still applies.
> 
> The hand-maintained **`docs/guide/built-in-functions.md`** is removed
from version control and **regenerated** at build time from
`built-in-functions.tmpl.md` plus the same API (`npm run
docs:generate-function-docs`, wired into `docs:dev` / `docs:build`).
VuePress excludes the template from routes and disables “edit this page”
on the generated guide.
> 
> Also exports **`FunctionListEntry`**, **`FunctionDetails`**,
**`FunctionParameterDescription`**, and **`FunctionCategory`**;
documents the catalogue workflow in **`DEV_DOCS.md`**; expands
**`repeatLastArgs`** guidance in the custom-functions guide; and records
additional Excel vs HyperFormula differences in
**`list-of-differences.md`**.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
f22560e. 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 <noreply@anthropic.com>
Co-authored-by: Kuba Sekowski <jakub.sekowski@handsontable.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Kuba Sekowski <sequba@users.noreply.github.com>
Co-authored-by: Kuba Sekowski <kuba.sekowski.dev@gmail.com>
Co-authored-by: Cursor Opus 5 <noreply@cursor.com>
## Summary

Implements the `publish` half of the release automation and makes
`code-freeze` genuinely re-runnable.

On the base branch, `release.sh publish` is a placeholder that prints
*"not implemented yet"* and exits 1 — everything after the freeze (the
merges, the tag, the npm publish, the sibling-repo updates) was still
manual. This PR implements it end to end, and hardens `code-freeze` so a
run that fails part-way resumes instead of starting over.

Both commands mirror the [ClickUp release process
doc](https://app.clickup.com/9015210959/v/dc/8cnjcyf-9495/8cnjcyf-12135),
with the deliberate deviations listed below.

> [!NOTE]
> **Base is `release/3.4.0`, not `develop`** — this is an exception. The
release-script work is stacked on the 3.4.0 freeze commit (`bad1bfa1`),
so `release/3.4.0` is the only base that yields a clean two-file diff.
Targeting `develop` would drag in the 3.4.0 version bump, its
CHANGELOG/release-notes entries and ~1,950 lines of lock-file churn.

| | Base | This PR |
|---|---|---|
| `script/release/release.sh` | 430 lines | 1,174 lines |
| `script/release/release-README.md` | 106 lines | 380 lines |
| `publish` command | stub, `exit 1` | fully implemented |

No files outside `script/release/` are touched.

## What's new — the `publish` command

| Step | Does |
|---|---|
| 1 | Merges `release/<version>` into `master` as a merge commit, unless
already merged |
| 2 | Annotated tag `<version>` on `master`, unless it already exists in
`master`'s (or `origin/master`'s) history |
| 3 | Merges `release/<version>` into `develop` |
| 4 | Reinstalls, rebuilds and runs `verify:publish-package` from
`master` |
| 5 | Pushes `master`, `develop` and the tag in **one atomic push**,
before the npm publish |
| 6 | Merges the release branch back in `hyperformula-tests` (into
`master` and `develop`), pushed atomically |
| 7 | **The point of no return** — typed confirmation, then `npm
publish`, then waits for registry visibility |
| 8 | Updates `hyperformula-demos`: lock files on `develop`, then merges
into the `M.m.x` branch |
| 9 | Leaves you on `develop`, where the next cycle starts |

## `code-freeze` hardening

- **Resumable.** Every step checks whether it is already done and skips
with a `=` marker, so a failed run is recovered by re-running the same
command rather than unpicking a half-finished freeze.
- **The tests-repo branch moved from step 10 to step 2.**
`test/fetch-tests.sh` pulls the matching branch *from origin*, so a
`release/<version>` that an earlier attempt created locally but never
pushed made every resume fail. Creating and pushing it before
`test:setup-private` runs fixes that, and publishes the branch for CI
and for whoever adds tests during the freeze that much sooner.
- **The release type is classified against `origin/develop`**, not the
working tree — on a resumed freeze the tree already carries the bump,
which used to read as a patch and silently skip the demos and
CodeSandbox work.
- **Stages named paths, not `git add .`**, so unrelated work in your
tree cannot ride along into the release commit.
- **Rejects a date that does not exist.** `2026-02-30` passed the
`YYYY-MM-DD` regex and reached `ht.config.js` verbatim while the release
notes said March 2.

## Safety model

- **Dry run is the default.** Nothing changes without `--real-run`.
Every mutating command is printed as a `$` line; generated content (the
new CHANGELOG section, the release-notes entry, every rewritten demo URL
as `-`/`+`) is previewed so it can be proofread first.
- **Three-repo preflight.** Both sibling clones must be present, clean,
on a reachable `origin`, carrying the branches the run will move *and*
the script it will execute inside them. This is checked before anything
moves — in `publish` the demos script runs *after* the irreversible npm
publish, so a wrong `--demos-dir` has to be caught up front or not at
all.
- **Divergence is an error, never a merge.** Getting onto a branch
fast-forwards or creates; it never invents a merge commit on `develop`
or a shared version branch. A local `release/<v>` that disagrees with
origin's is fatal, so a late fix pushed to the freeze branch cannot be
silently dropped.
- **The merged commit is pinned in the preflight**, so the later steps
merge exactly the commit the checks verified.
- **Prereleases stay off `latest`.** A plain `x.y.z` publishes under
`latest`; anything with an rc suffix publishes under `next` and the
GitHub release link carries `&prerelease=1`.

## Recorded failures

Anything you need to act on is recorded and re-printed as a `[ ]` item
at the top of the closing checklist — a lone marker line mid-run scrolls
past in an output that also holds a full install, build and test log.
Two kinds, and only one means the run fell short:

- **`!` the script could not do it** — a target file has been
restructured, or `[Unreleased]` is empty. These change the closing
banner, so a freeze that fell short cannot sign off as if it hadn't.
- **`i` worth checking** — the run did its job, but something deserves a
look (a resume from a dirty tree, a demos branch `publish` had to
create). Listed without changing the banner, so an ordinary resume does
not announce itself as a failure.

## Deliberate deviations from the process doc

- **`hyperformula-tests` gets its own `release/<version>` branch**,
which `publish` merges back into `master` and `develop`. The doc still
describes pointing its `master` at `develop` by hand. **The ClickUp doc
needs updating to match.**
- **`release/<v>` is never deleted** — the branch is kept in both
repositories, unlike `git flow release finish`.
- **Deploying the docs to staging is not automated** and is not on
either checklist; the "test the code examples on staging" item assumes
you have done it.

## How to verify

Dry run is the default, so both commands can be previewed safely against
a real clone:

```bash
npm run release -- code-freeze 3.5.0 2026-09-30
```

```bash
npm run release -- publish 3.4.0
```

Each prints its preflight, a plan, every command it would run, and the
exact content it would write. `shellcheck` is clean and `bash -n`
passes.

## Review status

A full review of the final implementation was run against the process
doc. Four findings are fixed in this branch:

- Perl's `-T` text heuristic could silently drop a real markdown file
from the demo-URL rewrite, under-reporting the file count with no
warning.
- `publish`'s preflight checks ran before `step "Preflight"`, so a
failure reaching the ERR trap was attributed to `"startup"` instead —
inconsistent with `code-freeze`.
- The README described the tag check as `master`-only, where the script
also accepts `origin/master`'s history.
- The README claimed the script prints *every* command; read-only state
checks and the in-place `node`/`perl` edits are not echoed verbatim.

## Follow-ups (not in this PR)

- `git push --atomic origin master develop --tags` pushes **all** local
tags, not just `refs/tags/<version>`. A stray local tag gets published
as a side effect, and one tag conflicting with origin aborts the entire
release push.
- The staging-deploy step is neither automated nor on the manual
checklist, while the checklist still says "test the code examples on
staging" — the prerequisite is invisible.
- Publishing from a clone with no local release branch produces merge
messages reading `Merge branch 'origin/release/<v>'` instead of
`release/<v>`.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…rs (#1719)

## Summary

Move the documentation site off Netlify and onto Cloudflare Workers,
deployed as the `hyperformula-docs` Worker in the Handsontable account.
Deployments are driven by [Workers
Builds](https://developers.cloudflare.com/workers/ci-cd/builds/) with
the Git integration configured on the Cloudflare side, so the repository
carries no deployment workflow, API token or account secret:

| Trigger | Result |
| --- | --- |
| push to `master` | production deployment (`npx wrangler deploy`) |
| push to any other branch, and every pull request | preview at
`https://<branch>-hyperformula-docs.handsoncode.workers.dev`, posted as
a pull request comment (`npx wrangler versions upload`) |

Production traffic keeps reaching the documentation through the
`hyperformula-website` Worker, which proxies `/docs*`. Its `DOCS_ORIGIN`
has to be repointed from `https://hyperformula-docs.netlify.app` to
`https://hyperformula-docs.handsoncode.workers.dev` once this lands on
`master` and a production build succeeds. Until then, Netlify keeps
serving production.

## Changes

- `wrangler.jsonc` — serves the VuePress output as static assets. The
asset directory is `docs/.vuepress/dist`, not
`docs/.vuepress/dist/docs`, so every document keeps the `/docs/` prefix
it is built with.
- `worker/index.js` — resolves directory URLs (`/docs/`, `/docs/api/`)
and extensionless URLs, redirects `/docs` to `/docs/`, and serves the
nearest `404.html`. Combined with `"html_handling": "none"`, `.html`
URLs are served as they are instead of being redirected to extensionless
URLs.
- `not_found_handling` is deliberately left at `"none"`. Any other value
makes the asset router answer browser navigations (requests carrying
`Sec-Fetch-Mode: navigate`) on its own, bypassing the Worker and turning
every directory and extensionless URL into a 404 in browsers while plain
requests still succeed.
- `docs/.vuepress/cf/_headers`, `docs/.vuepress/cf/_redirects` —
indefinite caching for the content-hashed `/docs/assets/*`, and a
redirect from the asset root to `/docs/` so preview URLs are usable at
their root. `script/prepare-cf-assets.js` copies them into the root of
the build output, where they have to sit.
- `package.json` — `docs:build:cf` (the Cloudflare build command),
`docs:deploy:cf`, `docs:preview:cf`, and the `wrangler` dev dependency.
- `.nvmrc` — Node.js 22, the version all CI workflows already use and
the one the Cloudflare build image reads from this file. Wrangler also
requires Node.js 20 or newer.
- `netlify.toml` — removed.
- `DEV_DOCS.md` — documents the deployment, the dashboard build settings
and the manual commands.

## Test plan

Verified against a production deployment built from this branch (`npm
run docs:build:cf` + `wrangler deploy`, 480 assets). URL behaviour
matches the Netlify site exactly:

| URL | Netlify | Cloudflare Worker |
| --- | --- | --- |
| `/docs/` | 200 | 200 |
| `/docs/api/` | 200 | 200 |
| `/docs/guide/basic-usage.html` | 200, no redirect | 200, no redirect |
| `/docs/guide/basic-usage` | 200 | 200 |
| `/docs` | 301 to `/docs/` | 301 to `/docs/`, query preserved |
| `/docs/nope` | 404 | 404 with the VuePress 404 page |

Each row was checked both as a plain request and as a browser navigation
(`Sec-Fetch-Mode: navigate`, `Sec-Fetch-Dest: document`), against the
deployed preview.

- [x] `npm run lint` passes.
- [x] `npm run docs:build:cf` writes `docs/.vuepress/dist/docs`,
`_headers` and `_redirects`.
- [x] `/docs/assets/*` is served with `cache-control: public,
max-age=31536000, immutable`; documents keep `max-age=0,
must-revalidate`.
- [x] `sitemap.xml` still lists
`https://hyperformula.handsontable.com/docs/...` URLs.
- [x] The preview build for this pull request succeeds and its URL
serves the documentation:
https://feature-cloudflare-docs-deployment-hyperformula-docs.handsoncode.workers.dev/docs/
- [x] Every URL above returns the same status for a browser navigation
as for a plain request, verified on the deployed preview.
- [x] `develop` merged in: `npm run docs:build:cf` succeeds on the
merged tree and `/docs/guide/built-in-functions.html` (generated by the
docs pipeline added on `develop`) is served.

## Follow-up outside this repository

1. Repoint `DOCS_ORIGIN` in the `hyperformula-website` project once this
reaches `master` and a production build succeeds.
2. Disconnect both Netlify projects: `hyperformula-docs` (production)
and `hyperformula-dev-docs` (the deploy previews on this pull request).
Deleting `netlify.toml` does not stop them, because their Git
integration lives on the Netlify side.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes production documentation hosting and URL routing;
misconfiguration could break public docs or caching until DOCS_ORIGIN is
updated and Netlify is disconnected.
> 
> **Overview**
> Moves HyperFormula documentation hosting from **Netlify** to the
**`hyperformula-docs` Cloudflare Worker**, with CI/CD via **Workers
Builds** (no repo secrets or GitHub workflow).
> 
> **Netlify** configuration is removed (`netlify.toml`). The repo adds
**Wrangler** wiring (`wrangler.jsonc`, `worker/index.js`) so static
VuePress output under `docs/.vuepress/dist` keeps `/docs/` URLs,
directory/extensionless routing, and VuePress 404 behaviour (including
browser navigation). **`script/prepare-cf-assets.js`** copies
**`docs/.vuepress/cf/_headers`** (long-lived cache for hashed
`/docs/assets/*`) and **`_redirects`** (root → `/docs/`) into the build
output; **`docs:build:cf`**, **`docs:deploy:cf`**, and
**`docs:preview:cf`** npm scripts support build and deploy.
> 
> **`.nvmrc`** is bumped to **Node 22**; **`.gitignore`** ignores
Wrangler local state (`.wrangler`, `.dev.vars*`). **`docs/README.md`**
documents deployment, dashboard build settings, and manual commands.
Production still reaches docs via the **`hyperformula-website`** proxy
once **`DOCS_ORIGIN`** is repointed outside this repo.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
f077f55. 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 5 (1M context) <noreply@anthropic.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>
### Context

[HF-349](https://app.clickup.com/t/9015210959/HF-349) — a blocker for
the 3.4.0 release, under
[HF-307](https://app.clickup.com/t/9015210959/HF-307) (feature packages
and add-ons), decision D2.

`getAvailableFunctions` and `getFunctionDetails` shipped in both a
static and an instance form. License-based entitlement splits the two
apart: an instance knows its license key, so it can answer for the
engine the caller actually holds; a static method has no key to read, so
it can only ever answer for the package as a whole. Only the instance
answer is the one integrators need — the static one invites a function
picker to advertise functions that then fail on evaluation, and nothing
errors at integration time.

Both methods are unreleased, which is the only free moment to remove
them.

**This PR removes the static variants. The function metadata API is
instance-scoped only.**

Code removed as dead after that change:

- `FunctionRegistry.getListableFunctionIds` (static) — its only caller
was the static `getAvailableFunctions`
- the `getPlugin` callback threaded through `buildAvailableFunctions` —
it existed only to abstract over the static and the instance registry.
Both private builders now take the engine's own `FunctionRegistry`,
which also removes the possibility of ids and plugins being resolved
from two different sources.

**Docs generator.** The built-in functions guide page is generated from
this API, so `script/generate-builtin-functions-doc.ts` now builds an
engine and reads the instance methods. It builds that engine with the
`gpl-v3` license key — the fully-entitled one — so the published
reference keeps documenting every built-in instead of narrowing to the
tier of whichever key the build environment happens to supply. The ADR
calls this out as a real risk introduced by the decision, so the key is
named in the script rather than left to the environment, with the
reasoning recorded there and in `docs/README.md`.

**Changelog.** The 3.4.0 entry that introduced these methods is amended
(`(both static and instance)` → `instance methods`) rather than
accompanied by a "Removed" note: nothing was ever released to remove,
and announcing the removal of a method 3.4.0 never shipped would only
confuse readers. Same edit in `docs/guide/release-notes.md`.

Tests live in the private repo — companion PR:
handsontable/hyperformula-tests#29. Both target `release/3.4.0` and must
land together.

The two JSDoc nuances the removed static docs carried (a custom plugin
registered *over* a built-in id inherits that id's catalogue entry, in
both the list and the details) were folded into the instance methods'
JSDoc, so the generated API reference does not lose behaviour that is
still tested.

### How did you test your changes?

- `npm run test:ci` with the private suite attached: **502/502 suites,
6180 tests passing**
- `npm run lint`: clean (exit 0, no errors)
- `npx tsc --noEmit`: clean
- `npm run bundle:typings`: succeeds; the emitted `HyperFormula.d.ts`
exposes only the instance `getAvailableFunctions()` /
`getFunctionDetails(canonicalName)`
- `npm run docs:generate-function-docs`: the generated
`built-in-functions.md` is **byte-identical** to the one the static path
produced (diffed before/after), 423 rows — matching the count
`docs/.vuepress/config.js` prints on the page

Not run in this environment: `npm run test:browser`. Karma is configured
for `ChromeHeadless` **and** `FirefoxHeadless`, and no Firefox is
available here. The change is not environment-specific and the same
specs pass under Jest.

### 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

Marked breaking because the methods disappear from the public surface.
In practice nothing released ever exposed them, so no published version
is affected and no migration guide entry is needed.

### Related issues:
1. HF-349 — Static methods cannot read functions granted by license key
2. HF-307 — Implement feature packages and add-ons in HF (decision D2)
3. Companion test PR: handsontable/hyperformula-tests#29

### 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.
- [ ] 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.
- [x] My changes require a documentation update.
- [ ] My changes require a migration guide.

The three OpenDocument/Excel/Google Sheets boxes are left unticked as
not applicable: this change touches no formula semantics.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Breaking public API removal (static metadata methods), though
unreleased in 3.4.0; integrators must use an engine instance, which is
intentional for upcoming license-gated function lists.
> 
> **Overview**
> **Removes the static** `getAvailableFunctions` **and**
`getFunctionDetails` **API** so function metadata is only available on a
`HyperFormula` instance (with language taken from that engine’s config).
That aligns metadata with license key and per-instance `functionPlugins`
/ registry snapshots instead of the global registry.
> 
> Private list/detail builders now take the engine’s `FunctionRegistry`
directly; static `FunctionRegistry.getListableFunctionIds` is dropped.
Instance JSDoc picks up behaviour notes that lived on the removed static
methods (registry snapshotting, custom plugins shadowing built-in ids).
> 
> The built-in functions doc generator builds an empty engine (`gpl-v3`
license key, default registry) and calls the instance metadata methods.
**CHANGELOG** and release notes for 3.4.0 are edited to describe
**instance methods only** (no separate “Removed” entry for unreleased
static APIs).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
6fe2aaf. 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>
Commit 635d224 renamed run() -> recomputeWholeGraph(),
partialRun() -> recomputeSubgraph() and runAndForget() ->
evaluateSingleFormula(), but the class docblock's "Entry Points"
list still named the old methods. Also fixes the "iteratates"
typo and a missing full stop introduced by the same commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The WIP commit trimmed the iterative-calculation guide and left two
defects behind: the direct/indirect circular-reference bullets lost
the labels that told them apart, and the convergence bullets were
left ungrammatical with ad-hoc parenthesised labels.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
develop moved every unit test out of the public repository (HF-49);
this feature branch predates that move, so after merging develop the
spec was the only file left in test/unit and its `../testUtils`
import no longer resolved, failing the whole Jest run.

The 646-line spec now lives in hyperformula-tests as
unit/evaluator/iterative-calculation.spec.ts (54 tests, all passing),
which is where DEV_DOCS.md says internal-team tests belong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The develop merge was conflict-free but placed the entry by textual
context, next to the IRR/N/VALUE lines it originally sat beside under
[Unreleased]. Those lines have since been released, so the entry ended
up inside the released [3.2.0] - 2026-02-19 section, claiming the
feature shipped six months ago.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…equest

sequba asked for this exact naming on PR #1541 (2025-09-24), the community
contribution this branch's WIP was built from:

  > enableIterativeCalculation: boolean,
  > I'd rename this option to make it similar to the analogues feature in
  > other spreadsheet software.

  > This should be configurable. I'd introduce a new flag
  > `iterativeCalculationLimit`

  > I'd introduce a new flag `iterativeCalculationThreshold`.

The WIP on this branch shipped different names instead:
  iterativeCalculationEnable               -> enableIterativeCalculation
  iterativeCalculationMaxIterations        -> iterativeCalculationLimit
  iterativeCalculationConvergenceThreshold -> iterativeCalculationThreshold

`iterativeCalculationInitialValue` is untouched -- sequba never named it, and
it already reads fine against the other three.

Renaming now is free (nothing has shipped); renaming after release would be
a breaking change. Also renames the now-mismatched private validation
method `validateIterativeCalculationMaxIterations` ->
`validateIterativeCalculationLimit`.

Verified: `tsc --noEmit` clean on both tsconfig.json and tsconfig.test.json,
`eslint` 0 errors on the three touched src files (pre-existing jsdoc/any
warnings only, not introduced here), and the full private
iterative-calculation.spec.ts suite -- 65/65 passing under the new names.
@qunabu

qunabu commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Task linked: HF-20 Iterative Calculation

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 31, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
hyperformula-docs 5a8cb26 Commit Preview URL

Branch Preview URL
Aug 31 2026, 07:25 AM

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e956c1e. Configure here.

Comment thread DEV_DOCS.md Outdated
Comment thread .eslintrc.js
@marcin-kordas-hoc

Copy link
Copy Markdown
Collaborator Author

For whoever reviews this: `Test performance` will fail here regardless of content, and it's not this PR's fault.

That job's "(base) Checkout main repository" step checks out `github.event.pull_request.base.sha` literally (`.github/workflows/performance.yml:29`) — i.e. this branch's actual tip, `635d224bd` (2026-02-13). `test/fetch-tests.sh`, which that same job needs, wasn't added to the repo until `c96a600d8` (2026-03-04) — three weeks later. So the "before" checkout for the base/head performance comparison can never find that script as long as the base is this old:

```
Test performance (22, ubuntu-latest) Fetch hyperformula-tests and sync branches ./fetch-tests.sh: No such file or directory
```

Everything else is green: `tsc --noEmit` (both configs), `eslint` (0 errors), `unit-tests`, `browser-tests`, `build` on all OS/Node combos, `lint`, `codecov`, `cla/signed`, `License Compliance`, `Cursor Bugbot`.

This resolves itself the moment this branch (or an equivalent merge into it) actually lands — the next PR against a post-merge `feature/issue-1545` will have a base tip that includes `fetch-tests.sh`. Not something to fix here.

Both predate this branch's rename commit -- they came in via the earlier
merge of origin/develop, from #1703 (DEV_DOCS.md) and #1616/#1672
(the hyperformula-tests checkout in lint.yml). Flagged by Cursor Bugbot on
this PR because this is the first real review this branch has ever had.

1. DEV_DOCS.md pointed contributors at `test/unit/interpreter/` for new
   interpreter specs. That path doesn't exist in this repo -- interpreter
   and function specs live in the private `hyperformula-tests` repo, checked
   out under `test/hyperformula-tests/`. Fixed the instruction.

2. `.github/workflows/lint.yml` checks out `hyperformula-tests` into
   `test/hyperformula-tests` before `npm run lint` (added in #1616/#1672),
   but `.eslintrc.js`'s `parserOptions.project` only covers `tsconfig.json`,
   whose `include` is `["src"]`. `npm run lint` is `eslint . --ext .js,.ts`,
   so every private-repo file gets swept in with no TS project backing it.

   Verified locally: `npx eslint . --ext .js,.ts` against the full tree
   (with hyperformula-tests checked out, as CI does) never finished in 90s
   before this fix. Added `test/hyperformula-tests` to `.eslintignore`
   (same pattern as the other checked-out/generated directories already
   listed there) -- reran, 16s, 0 files under hyperformula-tests touched,
   same 0-errors/2234-warnings result as before on the files that matter.
@codecov

codecov Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.39228% with 15 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (feature/issue-1545@635d224). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/interpreter/plugin/DatabasePlugin.ts 95.25% 11 Missing ⚠️
src/Lookup/ColumnIndex.ts 85.71% 1 Missing ⚠️
src/interpreter/plugin/FinancialPlugin.ts 98.66% 1 Missing ⚠️
src/interpreter/plugin/SequencePlugin.ts 98.50% 1 Missing ⚠️
src/interpreter/plugin/TextPlugin.ts 96.87% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@                  Coverage Diff                  @@
##             feature/issue-1545    #1756   +/-   ##
=====================================================
  Coverage                      ?   97.32%           
=====================================================
  Files                         ?      195           
  Lines                         ?    15873           
  Branches                      ?     3411           
=====================================================
  Hits                          ?    15448           
  Misses                        ?      425           
  Partials                      ?        0           
Files with missing lines Coverage Δ
src/BuildEngineFactory.ts 100.00% <100.00%> (ø)
src/Config.ts 94.01% <100.00%> (ø)
src/Evaluator.ts 99.14% <100.00%> (ø)
src/HyperFormula.ts 99.75% <100.00%> (ø)
src/LazilyTransformingAstService.ts 97.91% <100.00%> (ø)
src/Lookup/AdvancedFind.ts 98.03% <100.00%> (ø)
src/Lookup/ColumnBinarySearch.ts 100.00% <100.00%> (ø)
src/Lookup/SearchStrategy.ts 100.00% <ø> (ø)
src/NumberLiteralHelper.ts 94.11% <100.00%> (ø)
src/Operations.ts 98.93% <100.00%> (ø)
... and 56 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants