HF-20: rename iterative-calculation config options to match sequba's own review request (#1541) - #1756
HF-20: rename iterative-calculation config options to match sequba's own review request (#1541)#1756marcin-kordas-hoc wants to merge 82 commits into
Conversation
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
… the test folder (#1617)
…ples in Stackblitz (#1621)
* 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>
## 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.
|
Task linked: HF-20 Iterative Calculation |
Deploying with
|
| 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 |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ 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.
|
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: ``` 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 Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## feature/issue-1545 #1756 +/- ##
=====================================================
Coverage ? 97.32%
=====================================================
Files ? 195
Lines ? 15873
Branches ? 3411
=====================================================
Hits ? 15448
Misses ? 425
Partials ? 0
🚀 New features to boost your workflow:
|

Context
Targets your branch (
feature/issue-1545), notdevelop— this is meant to land inside yourown 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, plusthree small fixes (the unfinished
Evaluatorrename that broke the private test suite's compile,an orphaned spec left over from removing
test/unit/, and a changelog entry that git silentlyre-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 ownreview comments (2025-09-24):
The WIP on this branch ended up shipping different names:
iterativeCalculationEnableenableIterativeCalculationiterativeCalculationMaxIterationsiterativeCalculationLimititerativeCalculationConvergenceThresholditerativeCalculationThresholdThis PR renames all three call sites + the public
ConfigParamssurface + the docs guide to matchwhat you asked for.
iterativeCalculationInitialValueis untouched — you never named it, and italready reads fine alongside the other three. Also renamed the now-mismatched private method
validateIterativeCalculationMaxIterations→validateIterativeCalculationLimitfor consistency.Why now, not later: nothing with these names has ever shipped, so this rename is free. Once a
release goes out with
iterativeCalculationEnablein it, fixing this becomes a breaking change.Verified
npx tsc --noEmit -p tsconfig.json— cleannpx tsc --noEmit -p tsconfig.test.json— clean (the checkbrowser-testscompiles specsthrough, per the karma/ts-loader config)
npx eslint src/Config.ts src/ConfigParams.ts src/Evaluator.ts— 0 errors (only pre-existingjsdoc/
anywarnings, not introduced here)(handsontable/hyperformula-tests#50, opened against
develop— nofeature/issue-1545branch exists there to target, see that PR's own notes on why)
Not verified (no browser binary / no network in this environment):
karma/browser-testsitself,
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/developand were simply never reviewed until now:DEV_DOCS.mdtold contributors to add interpreter specs totest/unit/interpreter/, a paththat doesn't exist in this repo. Fixed to point at
test/hyperformula-tests/unit/interpreter/..github/workflows/lint.ymlchecks outhyperformula-testsbefore linting, but.eslintrc.js's typed-linting project only coverssrc/, soeslint .swept the private repoin with no TS project backing it. Verified locally: this never finished in 90s before the fix;
16s after, with
test/hyperformula-testsadded 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-testshas nofeature/issue-1545branch, so there was nothing to target there theway this PR targets yours.
test/fetch-tests.shmatches by literal branch name, so CI on thisPR picks up
fix/hf-1541-config-namingin 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.mdand Cursor rules that point to it; replaces longCLAUDE.md/ rootCONTRIBUTING.mdwith stubs linking toAGENTS.mdanddocs/guide/contributing.md. ExpandsDEV_DOCS.mdinto the canonical maintainer guide (Definition of Done, function metadata catalogue rules, private test repo workflow) and addsDOCS_CONTENT_GUIDE.md.Tests & lint in CI: Workflows now check out
handsontable/hyperformula-testsundertest/hyperformula-tests, runtest/fetch-tests.sh, and usenpm run test:ciinstead oftest:unit.ci. ESLint usestsconfig.json(nottsconfig.test.json) and ignores the private checkout; setup file path moves totest/_setupFiles..nvmrcbumps Node 16 → 22; rootMakefileis removed.Docs site & agents: VuePress serves under
/docs/with configurable build output; newmd-companionsplugin emits per-page.mdcompanions andllms-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 frombuilt-in-functions.tmpl.mdand function metadata (hand-maintained page deleted). Nav adds AI/integration guides; Cloudflare Workers deployment is documented;context7.jsonindexesdocs/.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;
.gitignorefor 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.